1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
|
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/assistant/test_support/fake_s3_server.h"
#include <memory>
#include "base/check.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chromeos/ash/services/assistant/service.h"
#include "chromeos/assistant/internal/internal_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash::assistant {
namespace {
// TODO(b/258750971): remove when internal assistant codes are migrated to
// namespace ash.
using ::chromeos::assistant::kFakeS3ServerBinary;
using ::chromeos::assistant::kFakeS3ServerBinaryV2;
using ::chromeos::assistant::kGenerateTokenInstructions;
// Folder where the S3 communications are stored when running in replay mode.
constexpr char kTestDataFolder[] = "chromeos/assistant/internal/test_data/";
// Fake device id passed to Libassistant. By fixing this we ensure it remains
// consistent between the current session and the value stored in the stored
// test data.
// This must be a 16 characters hex string or it will be rejected.
constexpr char kDeviceId[] = "11112222333344445555666677778888";
base::FilePath GetExecutableDir() {
base::FilePath result;
base::PathService::Get(base::DIR_EXE, &result);
return result;
}
base::FilePath GetSourceDir() {
base::FilePath result;
base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &result);
return result;
}
std::string GetSanitizedTestName() {
std::string test_name = base::ToLowerASCII(base::StringPrintf(
"%s_%s",
testing::UnitTest::GetInstance()->current_test_info()->test_suite_name(),
testing::UnitTest::GetInstance()->current_test_info()->name()));
// The test name may has `disabled_`. Remove it to match the data_file
// name.
base::ReplaceSubstringsAfterOffset(&test_name, 0, "disabled_", "");
return test_name;
}
const std::string GetAccessTokenFromEnvironmentOrDie() {
const char* token = std::getenv("TOKEN");
CHECK(token && strlen(token))
<< "No token found in the environmental variable $TOKEN.\n"
<< kGenerateTokenInstructions;
return token;
}
std::string FakeS3ModeToString(FakeS3Mode mode) {
switch (mode) {
case FakeS3Mode::kProxy:
return "PROXY";
case FakeS3Mode::kRecord:
return "RECORD";
case FakeS3Mode::kReplay:
return "REPLAY";
}
NOTREACHED();
}
void AppendArgument(base::CommandLine* command_line,
const std::string& name,
const std::string& value) {
// Note we can't use |AppendSwitchASCII| as that will add "<name>=<value>",
// and the fake s3 server binary does not support '='.
command_line->AppendArg(name);
command_line->AppendArg(value);
}
} // namespace
// Selects a port for the fake S3 server to use.
// This will use a file-based lock because different test shards might be trying
// to run fake S3 servers at the same time, and we need to ensure they use
// different ports.
class PortSelector {
public:
PortSelector() { SelectPort(); }
PortSelector(PortSelector&) = delete;
PortSelector& operator=(PortSelector&) = delete;
~PortSelector() {
lock_file_.Close();
base::DeletePathRecursively(GetLockFilePath());
}
int port() const { return port_; }
private:
// The first port we'll try to use. Randomly chosen to be outside of the range
// of known ports.
constexpr static int kStartPort = 23600;
// Maximum number of ports we'll try before we give up and conclude no ports
// are available (which really should not happen).
constexpr static int kMaxAttempts = 20000;
void SelectPort() {
for (int offset = 0; offset + 1 < kMaxAttempts; offset += 2) {
port_ = kStartPort + offset;
lock_file_ = base::File(GetLockFilePath(), GetFileFlags());
if (lock_file_.IsValid())
return;
}
CHECK(false) << "Failed to find an available port.";
}
base::FilePath GetLockFilePath() const {
std::string file_name = "port_" + base::NumberToString(port_) + "_lock";
return GetLockFileDirectory().Append(file_name);
}
static base::FilePath GetLockFileDirectory() {
base::FilePath result;
bool success = base::GetTempDir(&result);
EXPECT_TRUE(success);
return result;
}
static int GetFileFlags() {
return base::File::FLAG_CREATE | base::File::FLAG_WRITE;
}
// File exclusively opened on the file-system, to ensure no other fake S3
// server uses the same port.
base::File lock_file_;
int port_;
};
FakeS3Server::FakeS3Server(int data_file_version)
: data_file_version_(data_file_version),
port_selector_(std::make_unique<PortSelector>()) {
DCHECK_GT(data_file_version, 0);
}
FakeS3Server::~FakeS3Server() {
Teardown();
}
void FakeS3Server::Setup(FakeS3Mode mode) {
SetAccessTokenForMode(mode);
StartS3ServerProcess(mode);
SetFakeS3ServerURI();
SetDeviceId();
}
void FakeS3Server::Teardown() {
StopS3ServerProcess();
UnsetDeviceId();
UnsetFakeS3ServerURI();
}
std::string FakeS3Server::GetAccessToken() const {
return access_token_;
}
void FakeS3Server::SetAccessTokenForMode(FakeS3Mode mode) {
if (mode == FakeS3Mode::kProxy || mode == FakeS3Mode::kRecord) {
access_token_ = GetAccessTokenFromEnvironmentOrDie();
}
}
void FakeS3Server::SetFakeS3ServerURI() {
// Note this must be stored in a local variable, as
// `Service::OverrideS3ServerUriForTesting` does not take ownership of the
// `const char *`.
fake_s3_server_uri_ = "localhost:" + base::NumberToString(port());
Service::OverrideS3ServerUriForTesting(fake_s3_server_uri_.c_str());
}
void FakeS3Server::SetDeviceId() {
Service::OverrideDeviceIdForTesting(kDeviceId);
}
void FakeS3Server::UnsetDeviceId() {
Service::OverrideDeviceIdForTesting(nullptr);
}
void FakeS3Server::UnsetFakeS3ServerURI() {
Service::OverrideS3ServerUriForTesting(nullptr);
fake_s3_server_uri_ = "";
}
void FakeS3Server::StartS3ServerProcess(FakeS3Mode mode) {
if (process_running_) {
LOG(WARNING)
<< "Called FakeS3Server::StartS3ServerProcess when already running.";
return;
}
base::FilePath fake_s3_server_main;
fake_s3_server_main =
GetExecutableDir().Append(FILE_PATH_LITERAL(kFakeS3ServerBinaryV2));
base::CommandLine command_line(fake_s3_server_main);
AppendArgument(&command_line, "--port", base::NumberToString(port()));
AppendArgument(&command_line, "--http_port",
base::NumberToString(port() + 1));
AppendArgument(&command_line, "--mode", FakeS3ModeToString(mode));
AppendArgument(&command_line, "--auth_token", GetAccessToken());
AppendArgument(&command_line, "--test_data_file", GetTestDataFileName());
fake_s3_server_ = base::LaunchProcess(command_line, base::LaunchOptions{});
process_running_ = true;
}
void FakeS3Server::StopS3ServerProcess() {
if (!process_running_) {
LOG(WARNING)
<< "Called FakeS3Server::StopS3ServerProcess when already stopped.";
return;
}
fake_s3_server_.Terminate(/*exit_code=*/0, /*wait=*/true);
process_running_ = false;
}
std::string FakeS3Server::GetTestDataFileName() {
auto create_file_path = [](const std::string& test_name, int version) {
return GetSourceDir()
.Append(FILE_PATH_LITERAL(kTestDataFolder))
.Append(FILE_PATH_LITERAL(test_name + ".v" +
base::NumberToString(version) +
".fake_s3.proto"));
};
// Look for the latest version of the data file, if not found, look for older
// ones.
auto data_file = create_file_path(GetSanitizedTestName(), data_file_version_);
for (int version = data_file_version_ - 1;
!base::PathExists(data_file) && version > 0; --version) {
data_file = create_file_path(GetSanitizedTestName(), version);
}
return data_file.MaybeAsASCII();
}
int FakeS3Server::port() const {
return port_selector_->port();
}
} // namespace ash::assistant
|