[go: nahoru, domu]

blob: df68ea0c915ec07c625ff888a4b359a5e1a38e9c [file] [log] [blame]
James Cook17f18e72023-09-15 18:23:041// Copyright 2023 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Arthur Sonzognife132ee2024-01-15 11:01:045#include <optional>
James Cook17f18e72023-09-15 18:23:046#include <string>
7#include <vector>
8
James Cook75faa8752024-01-23 18:29:069#include "base/files/file_util.h"
James Cook17f18e72023-09-15 18:23:0410#include "base/functional/bind.h"
11#include "base/json/json_reader.h"
James Cook75faa8752024-01-23 18:29:0612#include "base/location.h"
James Cook17f18e72023-09-15 18:23:0413#include "base/process/launch.h"
14#include "base/run_loop.h"
15#include "base/strings/string_split.h"
James Cook75faa8752024-01-23 18:29:0616#include "base/strings/string_util.h"
17#include "base/strings/stringprintf.h"
18#include "base/task/single_thread_task_runner.h"
James Cook17f18e72023-09-15 18:23:0419#include "base/task/task_traits.h"
Takuto Ikutaa47d7852024-02-19 03:46:5820#include "base/task/thread_pool.h"
James Cook17f18e72023-09-15 18:23:0421#include "base/test/scoped_feature_list.h"
James Cook75faa8752024-01-23 18:29:0622#include "base/threading/thread_restrictions.h"
James Cook17f18e72023-09-15 18:23:0423#include "base/values.h"
James Cook75faa8752024-01-23 18:29:0624#include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
James Cookb1c49c42023-12-18 20:46:2725#include "chrome/test/base/chromeos/crosier/ash_integration_test.h"
James Cook75faa8752024-01-23 18:29:0626#include "chrome/test/base/chromeos/crosier/chromeos_integration_login_mixin.h"
James Cook58683a6c2023-09-19 18:32:4527#include "chrome/test/base/chromeos/crosier/chromeos_test_definition.pb.h"
28#include "chrome/test/base/chromeos/crosier/crosier_util.h"
James Cook75faa8752024-01-23 18:29:0629#include "chrome/test/base/chromeos/crosier/helper/test_sudo_helper_client.h"
30#include "chrome/test/base/chromeos/crosier/upstart.h"
James Cook17f18e72023-09-15 18:23:0431#include "testing/gtest/include/gtest/gtest.h"
James Cook17f18e72023-09-15 18:23:0432
33namespace ash {
34namespace {
35
36constexpr char kHelperBinary[] =
37 "/usr/local/libexec/tast/helpers/local/cros/"
38 "featured.FeatureLibraryLateBoot.check";
39
40// The tests start the chromeos_integration_tests binary with two features
41// enabled or disabled, with or without parameters, simulating a Finch
42// experiment. One feature is default-enabled; the other feature is
43// default-disabled. The helper binary (a simulation of a platform binary)
44// returns its view of the feature state as a JSON object. The test cases have
45// expected output for the default-enabled and default-disabled features.
46struct TestCase {
47 const char* test_name = "";
48 const char* enabled_features = "";
49 const char* disabled_features = "";
50 const char* expected_default_enabled = "";
51 const char* expected_default_disabled = "";
52};
53
James Cookb1c49c42023-12-18 20:46:2754class FeaturedIntegrationTest : public AshIntegrationTest,
James Cook17f18e72023-09-15 18:23:0455 public ::testing::WithParamInterface<TestCase> {
56 public:
57 FeaturedIntegrationTest() {
58 feature_list_.InitFromCommandLine(GetParam().enabled_features,
59 GetParam().disabled_features);
60 }
61
James Cookb1c49c42023-12-18 20:46:2762 // AshIntegrationTest:
James Cook58683a6c2023-09-19 18:32:4563 void SetUpOnMainThread() override {
James Cookb1c49c42023-12-18 20:46:2764 AshIntegrationTest::SetUpOnMainThread();
James Cook58683a6c2023-09-19 18:32:4565
66 chrome_test_base_chromeos_crosier::TestInfo info;
67 info.set_description(
68 "Verifies features are enabled/disabled as expected and parameters are "
69 "unchanged");
Ian Barkley-Yeungd5e8856a2024-02-14 03:18:3670 info.set_team_email("chromeos-data-eng@google.com");
James Cook58683a6c2023-09-19 18:32:4571 info.add_contacts("jamescook@google.com"); // Ported from Tast to Crosier.
72 info.set_buganizer("1096648");
73 crosier_util::AddTestInfo(info);
74 }
75
James Cook17f18e72023-09-15 18:23:0476 base::test::ScopedFeatureList feature_list_;
77};
78
79constexpr TestCase kTestCases[] = {
80 {.test_name = "experiment_enabled_without_params",
81 .enabled_features =
82 "CrOSLateBootTestDefaultEnabled,CrOSLateBootTestDefaultDisabled",
83 .expected_default_enabled = R"(
84 {
85 "EnabledCallbackEnabledResult": true,
86 "FeatureName": "CrOSLateBootTestDefaultEnabled",
87 "ParamsCallbackEnabledResult": true,
88 "ParamsCallbackFeatureName": "CrOSLateBootTestDefaultEnabled",
89 "ParamsCallbackParamsResult": {}
90 }
91 )",
92 .expected_default_disabled = R"(
93 {
94 "EnabledCallbackEnabledResult": true,
95 "FeatureName": "CrOSLateBootTestDefaultDisabled",
96 "ParamsCallbackEnabledResult": true,
97 "ParamsCallbackFeatureName": "CrOSLateBootTestDefaultDisabled",
98 "ParamsCallbackParamsResult": {}
99 }
100 )"},
101 {.test_name = "experiment_disabled_without_params",
102 .disabled_features =
103 "CrOSLateBootTestDefaultEnabled,CrOSLateBootTestDefaultDisabled",
104 .expected_default_enabled = R"(
105 {
106 "EnabledCallbackEnabledResult": false,
107 "FeatureName": "CrOSLateBootTestDefaultEnabled",
108 "ParamsCallbackEnabledResult": false,
109 "ParamsCallbackFeatureName": "CrOSLateBootTestDefaultEnabled",
110 "ParamsCallbackParamsResult": {}
111 }
112 )",
113 .expected_default_disabled = R"(
114 {
115 "EnabledCallbackEnabledResult": false,
116 "FeatureName": "CrOSLateBootTestDefaultDisabled",
117 "ParamsCallbackEnabledResult": false,
118 "ParamsCallbackFeatureName": "CrOSLateBootTestDefaultDisabled",
119 "ParamsCallbackParamsResult": {}
120 }
121 )"},
122 {.test_name = "experiment_enabled_with_params",
123 .enabled_features = "CrOSLateBootTestDefaultEnabled:k1/v1/k2/v2,"
124 "CrOSLateBootTestDefaultDisabled:k3/v3/k4/v4",
125 .expected_default_enabled = R"(
126 {
127 "EnabledCallbackEnabledResult": true,
128 "FeatureName": "CrOSLateBootTestDefaultEnabled",
129 "ParamsCallbackEnabledResult": true,
130 "ParamsCallbackFeatureName": "CrOSLateBootTestDefaultEnabled",
131 "ParamsCallbackParamsResult": {
132 "k1":"v1",
133 "k2":"v2"
134 }
135 }
136 )",
137 .expected_default_disabled = R"(
138 {
139 "EnabledCallbackEnabledResult": true,
140 "FeatureName": "CrOSLateBootTestDefaultDisabled",
141 "ParamsCallbackEnabledResult": true,
142 "ParamsCallbackFeatureName": "CrOSLateBootTestDefaultDisabled",
143 "ParamsCallbackParamsResult": {
144 "k3":"v3",
145 "k4":"v4"
146 }
147 }
148 )"},
149 {.test_name = "experiment_default",
150 .enabled_features = "",
151 .expected_default_enabled = R"(
152 {
153 "EnabledCallbackEnabledResult": true,
154 "FeatureName": "CrOSLateBootTestDefaultEnabled",
155 "ParamsCallbackEnabledResult": true,
156 "ParamsCallbackFeatureName": "CrOSLateBootTestDefaultEnabled",
157 "ParamsCallbackParamsResult": {}
158 }
159 )",
160 .expected_default_disabled = R"(
161 {
162 "EnabledCallbackEnabledResult": false,
163 "FeatureName": "CrOSLateBootTestDefaultDisabled",
164 "ParamsCallbackEnabledResult": false,
165 "ParamsCallbackFeatureName": "CrOSLateBootTestDefaultDisabled",
166 "ParamsCallbackParamsResult": {}
167 }
168 )"},
169
170};
171
172const char* NameFromTestCase(::testing::TestParamInfo<TestCase> info) {
173 return info.param.test_name;
174}
175
176INSTANTIATE_TEST_SUITE_P(All,
177 FeaturedIntegrationTest,
178 ::testing::ValuesIn(kTestCases),
179 NameFromTestCase);
180
181IN_PROC_BROWSER_TEST_P(FeaturedIntegrationTest, FeatureLibraryLateBoot) {
182 // Collect stdout from the helper binary. The binary will call back via D-Bus
183 // into chromeos_integration_tests, so collect the binary's output off the
184 // main thread.
185 std::string output;
186 base::RunLoop run_loop;
187 base::ThreadPool::PostTask(
188 FROM_HERE, {base::MayBlock()},
189 base::BindOnce(
190 [](std::string* output, base::RunLoop* run_loop) {
191 // Run the helper binary to get its feature state.
192 ASSERT_TRUE(base::GetAppOutput({kHelperBinary}, output));
193 run_loop->Quit();
194 },
195 &output, &run_loop));
196 run_loop.Run();
197
198 // The helper binary returns two lines of output.
199 std::vector<std::string> split_out = base::SplitString(
200 output, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
201 ASSERT_EQ(split_out.size(), 2u);
202
203 // The first line of the output is the state of the default enabled feature.
Arthur Sonzognife132ee2024-01-15 11:01:04204 std::optional<base::Value> default_enabled =
James Cook17f18e72023-09-15 18:23:04205 base::JSONReader::Read(split_out[0]);
206 ASSERT_TRUE(default_enabled.has_value());
207
208 // The test case has the expected JSON for the default enabled feature.
Arthur Sonzognife132ee2024-01-15 11:01:04209 std::optional<base::Value> expected_default_enabled =
James Cook17f18e72023-09-15 18:23:04210 base::JSONReader::Read(GetParam().expected_default_enabled);
211 ASSERT_TRUE(expected_default_enabled.has_value());
212 EXPECT_EQ(default_enabled, expected_default_enabled);
213
214 // The second line of the output is the default disabled feature.
Arthur Sonzognife132ee2024-01-15 11:01:04215 std::optional<base::Value> default_disabled =
James Cook17f18e72023-09-15 18:23:04216 base::JSONReader::Read(split_out[1]);
217 ASSERT_TRUE(default_disabled.has_value());
218
219 // The test case has the expected JSON for the default disabled feature.
Arthur Sonzognife132ee2024-01-15 11:01:04220 std::optional<base::Value> expected_default_disabled =
James Cook17f18e72023-09-15 18:23:04221 base::JSONReader::Read(GetParam().expected_default_disabled);
222 ASSERT_TRUE(expected_default_disabled.has_value());
223 EXPECT_EQ(default_disabled, expected_default_disabled);
224}
225
James Cook75faa8752024-01-23 18:29:06226////////////////////////////////////////////////////////////////////////////////
227// FeaturedLatePlatformIntegrationTest
228////////////////////////////////////////////////////////////////////////////////
229
230// kTestFeature is the name of the test feature in platform-features.json.
231// It should always match the name in that file exactly.
232constexpr char kTestFeature[] = "CrOSLateBootTestFeature";
233
234// kDirPath is the path to the directory this test uses.
235constexpr char kDirPath[] = "/run/featured_test";
236
237// kFilePath is the file whose existence gates the behavior of featured.
238// If it exists and the experiment is enabled, featured should write a string to
239// it. Otherwise, it should do nothing.
240constexpr char kFilePath[] = "/run/featured_test/test_write";
241
242// kExpectedContents is the expected contents of the filePath after featured
243// writes to it.
244constexpr char kExpectedContents[] = "test_featured";
245
246class FeaturedLatePlatformIntegrationTest
247 : public AshIntegrationTest,
248 public testing::WithParamInterface<std::tuple<bool, bool>> {
249 public:
250 // Whether the test file should be created by the test.
251 static bool TestFileExists() { return std::get<0>(GetParam()); }
252
253 // Whether the experimental feature should be enabled in chrome.
254 static bool TestFeatureEnabled() { return std::get<1>(GetParam()); }
255
256 FeaturedLatePlatformIntegrationTest() {
257 // Don't enable or disable the test feature in the PRE_ test stage. Only do
258 // it in the main test stage. Ditto for login.
259 if (base::StartsWith(
260 testing::UnitTest::GetInstance()->current_test_info()->name(),
261 "PRE_")) {
262 return;
263 }
264 if (TestFeatureEnabled()) {
265 feature_list_.InitFromCommandLine(/*enable_features=*/kTestFeature,
266 /*disable_features=*/"");
267 } else {
268 feature_list_.InitFromCommandLine(/*enable_features=*/"",
269 /*disable_features=*/kTestFeature);
270 }
271 set_exit_when_last_browser_closes(false);
272 login_mixin().SetMode(ChromeOSIntegrationLoginMixin::Mode::kTestLogin);
273 }
274
275 void TearDownOnMainThread() override {
276 // Don't clean up files in the PRE_ test stage because they must continue to
277 // exist for the main test stage.
278 if (base::StartsWith(
279 testing::UnitTest::GetInstance()->current_test_info()->name(),
280 "PRE_")) {
281 return;
282 }
283
284 // The directory is owned by root, so use the sudo helper.
285 auto result = TestSudoHelperClient().RunCommand(
286 base::StringPrintf("rm -rf %s", kDirPath));
287 ASSERT_EQ(result.return_code, 0) << result.output;
288
289 AshIntegrationTest::TearDownOnMainThread();
290 }
291
292 base::test::ScopedFeatureList feature_list_;
293};
294
295INSTANTIATE_TEST_SUITE_P(FileFeature,
296 FeaturedLatePlatformIntegrationTest,
297 testing::Combine(testing::Bool(), testing::Bool()));
298
299IN_PROC_BROWSER_TEST_P(FeaturedLatePlatformIntegrationTest,
300 PRE_LatePlatformFeatures) {
301 // Use sudo helper because /run is owned by root.
302 TestSudoHelperClient sudo;
303 auto result = sudo.RunCommand(base::StringPrintf("mkdir -p %s", kDirPath));
304 ASSERT_EQ(result.return_code, 0) << result.output;
305 result = sudo.RunCommand(base::StringPrintf("chmod 755 %s", kDirPath));
306 ASSERT_EQ(result.return_code, 0) << result.output;
307
308 base::FilePath file_path(kFilePath);
309 if (TestFileExists()) {
310 // Create a zero-sized file.
311 result = sudo.RunCommand(base::StringPrintf("truncate -s 0 %s", kFilePath));
312 ASSERT_EQ(result.return_code, 0) << result.output;
313
314 result = sudo.RunCommand(base::StringPrintf("chmod 664 %s", kFilePath));
315 ASSERT_EQ(result.return_code, 0) << result.output;
316 } else {
317 // Ensure the file does not exist.
318 result = sudo.RunCommand("rm -f /run/featured_test/test_write");
319 ASSERT_EQ(result.return_code, 0) << result.output;
320 }
321
322 // Restart featured.
323 ASSERT_TRUE(upstart::RestartJob("featured"));
324 ASSERT_TRUE(upstart::WaitForJobStatus("featured", upstart::Goal::kStart,
325 upstart::State::kRunning,
326 upstart::WrongGoalPolicy::kReject));
327
328 // Restart chrome (by ending this PRE_ test).
329}
330
331IN_PROC_BROWSER_TEST_P(FeaturedLatePlatformIntegrationTest,
332 LatePlatformFeatures) {
333 login_mixin().Login();
334 ash::test::WaitForPrimaryUserSessionStart();
335 EXPECT_TRUE(login_mixin().IsCryptohomeMounted());
336
337 base::ScopedAllowBlockingForTesting allow_blocking;
338
339 // Poll for files for up to 20 seconds.
340 bool passed = false;
341 for (int i = 0; i < 20; ++i) {
342 base::RunLoop run_loop;
343 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
344 FROM_HERE, run_loop.QuitClosure(), base::Seconds(1));
345 run_loop.Run();
346
347 base::FilePath file_path(kFilePath);
348 if (TestFileExists()) {
349 // If testing the file exists case, the file should exist. Featured should
350 // not have removed the file.
351 ASSERT_TRUE(base::PathExists(file_path));
352 std::string contents;
353 ASSERT_TRUE(base::ReadFileToString(file_path, &contents));
354 if (TestFeatureEnabled()) {
355 // If the file contains the expected string, exit the loop. If not, keep
356 // polling.
357 if (contents == kExpectedContents) {
358 passed = true;
359 break;
360 }
361 } else {
362 // In the feature disabled case the File should be empty. If it is,
363 // exit the loop. Otherwise keep polling.
364 if (contents.empty()) {
365 passed = true;
366 break;
367 }
368 }
369 } else {
370 // If testing the file does-not-exist case, featured should not have
371 // created the file.
372 ASSERT_FALSE(base::PathExists(file_path));
373 passed = true;
374 // Continue to poll, to ensure featured doesn't create the file later.
375 }
376 }
377 EXPECT_TRUE(passed) << "Did not pass after 20 iterations.";
378}
379
James Cook17f18e72023-09-15 18:23:04380} // namespace
381} // namespace ash