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