blob: 6b902001c8f2f99e06ad3b427480efea29c66150 [file] [log] [blame]
Mike Frysinger1dad0972019-02-23 18:36:37 -05001// Copyright 2019 The Chromium OS 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 "dev-install/dev_install.h"
6
Mike Frysinger60260f62019-02-24 02:28:23 -05007#include <unistd.h>
8
9#include <istream>
10#include <sstream>
11#include <string>
12
Mike Frysingerb9c9f6c2019-02-24 02:32:32 -050013#include <base/files/file_path.h>
14#include <base/files/file_util.h>
15#include <base/files/scoped_temp_dir.h>
Mike Frysinger1dad0972019-02-23 18:36:37 -050016#include <gmock/gmock.h>
17#include <gtest/gtest.h>
18
19using ::testing::_;
20using ::testing::Return;
21
22namespace dev_install {
23
24namespace {
25
26class DevInstallMock : public DevInstall {
27 public:
Ben Chan1ea2ba12019-09-19 11:58:03 -070028 MOCK_METHOD(int, Exec, (const std::vector<const char*>&), (override));
29 MOCK_METHOD(bool, IsDevMode, (), (const, override));
30 MOCK_METHOD(bool,
31 PromptUser,
32 (std::istream&, const std::string&),
33 (override));
Mike Frysinger69c167f2019-02-24 05:14:57 -050034 MOCK_METHOD(bool, ClearStateDir, (const base::FilePath&), (override));
Mike Frysinger3d44f782019-02-25 23:17:08 -050035 MOCK_METHOD(bool,
36 InitializeStateDir,
37 (const base::FilePath& dir),
38 (override));
Mike Frysingerbf36e3c2019-11-02 02:47:50 -040039 MOCK_METHOD(bool,
40 DownloadAndInstallBootstrapPackages,
41 (const base::FilePath&),
42 (override));
Mike Frysinger1dad0972019-02-23 18:36:37 -050043};
44
45class DevInstallTest : public ::testing::Test {
Mike Frysingeree5af6e2019-02-23 23:47:03 -050046 public:
47 void SetUp() override {
48 // Set the default to dev mode enabled. Most tests want that.
49 ON_CALL(dev_install_, IsDevMode()).WillByDefault(Return(true));
Mike Frysinger69c167f2019-02-24 05:14:57 -050050
Mike Frysinger3d44f782019-02-25 23:17:08 -050051 // Ignore stateful setup for most tests.
52 ON_CALL(dev_install_, InitializeStateDir(_)).WillByDefault(Return(true));
53
Mike Frysingerbf36e3c2019-11-02 02:47:50 -040054 // Ignore bootstrap for most tests.
55 ON_CALL(dev_install_, DownloadAndInstallBootstrapPackages(_))
56 .WillByDefault(Return(true));
57
Mike Frysinger69c167f2019-02-24 05:14:57 -050058 // Most tests should run with a path that doesn't exist.
59 dev_install_.SetStateDirForTest(base::FilePath("/.path-does-not-exist"));
Mike Frysingeree5af6e2019-02-23 23:47:03 -050060 }
61
Mike Frysinger1dad0972019-02-23 18:36:37 -050062 protected:
63 DevInstallMock dev_install_;
64};
65
66} // namespace
67
68// Check default run through.
69TEST_F(DevInstallTest, Run) {
70 EXPECT_CALL(dev_install_, Exec(_)).WillOnce(Return(1234));
71 EXPECT_EQ(1234, dev_install_.Run());
72}
73
Mike Frysingeree5af6e2019-02-23 23:47:03 -050074// Systems not in dev mode should abort.
75TEST_F(DevInstallTest, NonDevMode) {
76 EXPECT_CALL(dev_install_, IsDevMode()).WillOnce(Return(false));
Mike Frysinger69c167f2019-02-24 05:14:57 -050077 EXPECT_CALL(dev_install_, ClearStateDir(_)).Times(0);
Mike Frysingeree5af6e2019-02-23 23:47:03 -050078 EXPECT_CALL(dev_install_, Exec(_)).Times(0);
79 EXPECT_EQ(2, dev_install_.Run());
80}
81
Mike Frysinger69c167f2019-02-24 05:14:57 -050082// Check system has been initialized.
83TEST_F(DevInstallTest, AlreadyInitialized) {
84 dev_install_.SetStateDirForTest(base::FilePath("/"));
85 EXPECT_CALL(dev_install_, Exec(_)).Times(0);
86 ASSERT_EQ(4, dev_install_.Run());
87}
88
89// Check --reinstall passed.
90TEST_F(DevInstallTest, RunReinstallWorked) {
91 dev_install_.SetReinstallForTest(true);
92 EXPECT_CALL(dev_install_, ClearStateDir(_)).WillOnce(Return(true));
93 EXPECT_CALL(dev_install_, Exec(_)).WillOnce(Return(1234));
94 ASSERT_EQ(1234, dev_install_.Run());
95}
96
97// Check when --reinstall is requested but clearing fails.
98TEST_F(DevInstallTest, RunReinstallFails) {
99 dev_install_.SetReinstallForTest(true);
100 EXPECT_CALL(dev_install_, ClearStateDir(_)).WillOnce(Return(false));
101 EXPECT_CALL(dev_install_, Exec(_)).Times(0);
102 ASSERT_EQ(1, dev_install_.Run());
103}
104
105// Check --uninstall passed.
106TEST_F(DevInstallTest, RunUninstall) {
107 dev_install_.SetUninstallForTest(true);
108 EXPECT_CALL(dev_install_, ClearStateDir(_)).WillOnce(Return(true));
109 EXPECT_CALL(dev_install_, Exec(_)).Times(0);
110 ASSERT_EQ(0, dev_install_.Run());
111}
112
Mike Frysinger3d44f782019-02-25 23:17:08 -0500113// Stateful setup failures.
114TEST_F(DevInstallTest, StatefulSetupFailure) {
115 EXPECT_CALL(dev_install_, InitializeStateDir(_)).WillOnce(Return(false));
116 EXPECT_CALL(dev_install_, Exec(_)).Times(0);
117 ASSERT_EQ(5, dev_install_.Run());
118}
119
Mike Frysingerbf36e3c2019-11-02 02:47:50 -0400120// We only bootstrap before exiting.
121TEST_F(DevInstallTest, BootstrapOnly) {
122 dev_install_.SetBootstrapForTest(true);
123 EXPECT_CALL(dev_install_, Exec(_)).Times(0);
124 ASSERT_EQ(0, dev_install_.Run());
125}
126
127// Bootstrap failures.
128TEST_F(DevInstallTest, BootstrapFailure) {
129 EXPECT_CALL(dev_install_, DownloadAndInstallBootstrapPackages(_))
130 .WillOnce(Return(false));
131 EXPECT_CALL(dev_install_, Exec(_)).Times(0);
132 ASSERT_EQ(7, dev_install_.Run());
133}
134
Mike Frysinger60260f62019-02-24 02:28:23 -0500135namespace {
136
137class PromptUserTest : public ::testing::Test {
138 protected:
139 DevInstall dev_install_;
140};
141
142} // namespace
143
144// The --yes flag should pass w/out prompting the user.
145TEST_F(PromptUserTest, Forced) {
146 dev_install_.SetYesForTest(true);
147 std::stringstream stream("");
148 EXPECT_TRUE(dev_install_.PromptUser(stream, ""));
149}
150
151// EOF input should fail.
152TEST_F(PromptUserTest, Eof) {
153 std::stringstream stream("");
154 EXPECT_FALSE(dev_install_.PromptUser(stream, ""));
155}
156
157// Default input (hitting enter) should fail.
158TEST_F(PromptUserTest, Default) {
159 std::stringstream stream("\n");
160 EXPECT_FALSE(dev_install_.PromptUser(stream, ""));
161}
162
163// Entering "n" should fail.
164TEST_F(PromptUserTest, No) {
165 std::stringstream stream("n\n");
166 EXPECT_FALSE(dev_install_.PromptUser(stream, ""));
167}
168
169// Entering "y" should pass.
170TEST_F(PromptUserTest, Yes) {
171 std::stringstream stream("y\n");
172 EXPECT_TRUE(dev_install_.PromptUser(stream, ""));
173}
174
Mike Frysingerb9c9f6c2019-02-24 02:32:32 -0500175namespace {
176
177class DeletePathTest : public ::testing::Test {
178 public:
179 void SetUp() override {
180 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
181 test_dir_ = scoped_temp_dir_.GetPath();
182 dev_install_.SetStateDirForTest(test_dir_);
183 }
184
185 protected:
186 DevInstall dev_install_;
187 base::FilePath test_dir_;
188 base::ScopedTempDir scoped_temp_dir_;
189};
190
191} // namespace
192
193// Check missing dir.
194TEST_F(DeletePathTest, Missing) {
195 struct stat st = {};
196 EXPECT_TRUE(dev_install_.DeletePath(st, test_dir_.Append("foo")));
197}
198
199// Check deleting dir contents leaves the dir alone.
200TEST_F(DeletePathTest, Empty) {
201 struct stat st = {};
202 EXPECT_TRUE(dev_install_.DeletePath(st, test_dir_));
203 EXPECT_TRUE(base::PathExists(test_dir_));
204}
205
206// Check mounted deletion.
207TEST_F(DeletePathTest, Mounted) {
208 struct stat st = {};
209 const base::FilePath subdir = test_dir_.Append("subdir");
210 EXPECT_TRUE(base::CreateDirectory(subdir));
211 EXPECT_FALSE(dev_install_.DeletePath(st, test_dir_));
212 EXPECT_TRUE(base::PathExists(subdir));
213}
214
215// Check recursive deletion.
216TEST_F(DeletePathTest, Works) {
217 struct stat st;
218 EXPECT_EQ(0, stat(test_dir_.value().c_str(), &st));
219
220 EXPECT_EQ(3, base::WriteFile(test_dir_.Append("file"), "123", 3));
221 EXPECT_EQ(0, symlink("x", test_dir_.Append("broken-sym").value().c_str()));
222 EXPECT_EQ(0, symlink("file", test_dir_.Append("file-sym").value().c_str()));
223 EXPECT_EQ(0, symlink(".", test_dir_.Append("dir-sym").value().c_str()));
224 EXPECT_EQ(0, symlink("subdir", test_dir_.Append("dir-sym2").value().c_str()));
225 const base::FilePath subdir = test_dir_.Append("subdir");
226 EXPECT_TRUE(base::CreateDirectory(subdir));
227 EXPECT_EQ(3, base::WriteFile(subdir.Append("file"), "123", 3));
228 const base::FilePath subsubdir = test_dir_.Append("subdir");
229 EXPECT_TRUE(base::CreateDirectory(subsubdir));
230 EXPECT_EQ(3, base::WriteFile(subsubdir.Append("file"), "123", 3));
231
232 EXPECT_TRUE(dev_install_.DeletePath(st, test_dir_));
233 EXPECT_TRUE(base::PathExists(test_dir_));
234 EXPECT_EQ(0, rmdir(test_dir_.value().c_str()));
235}
236
Mike Frysinger69c167f2019-02-24 05:14:57 -0500237namespace {
238
Mike Frysinger3eaab632019-11-03 20:19:09 -0500239class CreateMissingDirectoryTest : public ::testing::Test {
240 public:
241 void SetUp() {
242 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
243 test_dir_ = scoped_temp_dir_.GetPath();
244 }
245
246 protected:
247 DevInstall dev_install_;
248 base::FilePath test_dir_;
249 base::ScopedTempDir scoped_temp_dir_;
250};
251
252} // namespace
253
254// Create dirs that don't yet exist.
255TEST_F(CreateMissingDirectoryTest, Works) {
256 const base::FilePath dir = test_dir_.Append("test");
257 ASSERT_TRUE(dev_install_.CreateMissingDirectory(dir));
258 int mode;
259 ASSERT_TRUE(base::GetPosixFilePermissions(dir, &mode));
260 ASSERT_EQ(0755, mode);
261 ASSERT_TRUE(dev_install_.CreateMissingDirectory(dir));
262}
263
264// If a dir already exists, should do nothing.
265TEST_F(CreateMissingDirectoryTest, Existing) {
266 ASSERT_TRUE(dev_install_.CreateMissingDirectory(test_dir_));
267 ASSERT_TRUE(dev_install_.CreateMissingDirectory(test_dir_));
268}
269
270namespace {
271
Mike Frysinger69c167f2019-02-24 05:14:57 -0500272// We could mock out DeletePath, but it's easy to lightly validate it.
273class ClearStateDirMock : public DevInstall {
274 public:
275 MOCK_METHOD(bool,
276 PromptUser,
277 (std::istream&, const std::string&),
278 (override));
279};
280
281class ClearStateDirTest : public ::testing::Test {
282 public:
283 void SetUp() {
284 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
285 test_dir_ = scoped_temp_dir_.GetPath();
286 }
287
288 protected:
289 ClearStateDirMock dev_install_;
290 base::FilePath test_dir_;
291 base::ScopedTempDir scoped_temp_dir_;
292};
293
294} // namespace
295
296// Check user rejecting things.
297TEST_F(ClearStateDirTest, Cancel) {
298 EXPECT_CALL(dev_install_, PromptUser(_, _)).WillOnce(Return(false));
299 const base::FilePath subdir = test_dir_.Append("subdir");
300 ASSERT_TRUE(base::CreateDirectory(subdir));
301 ASSERT_FALSE(dev_install_.ClearStateDir(test_dir_));
302 ASSERT_TRUE(base::PathExists(subdir));
303}
304
305// Check missing dir is handled.
306TEST_F(ClearStateDirTest, Missing) {
307 EXPECT_CALL(dev_install_, PromptUser(_, _)).WillOnce(Return(true));
308 ASSERT_TRUE(dev_install_.ClearStateDir(test_dir_.Append("subdir")));
309 ASSERT_TRUE(base::PathExists(test_dir_));
310}
311
312// Check empty dir is handled.
313TEST_F(ClearStateDirTest, Empty) {
314 EXPECT_CALL(dev_install_, PromptUser(_, _)).WillOnce(Return(true));
315 ASSERT_TRUE(dev_install_.ClearStateDir(test_dir_));
316 ASSERT_TRUE(base::PathExists(test_dir_));
317}
318
319// Check dir with contents is cleared.
320TEST_F(ClearStateDirTest, Works) {
321 EXPECT_CALL(dev_install_, PromptUser(_, _)).WillOnce(Return(true));
322 const base::FilePath subdir = test_dir_.Append("subdir");
323 ASSERT_TRUE(base::CreateDirectory(subdir));
324 ASSERT_TRUE(dev_install_.ClearStateDir(test_dir_));
325 ASSERT_FALSE(base::PathExists(subdir));
326}
327
Mike Frysinger3d44f782019-02-25 23:17:08 -0500328namespace {
329
330class InitializeStateDirTest : public ::testing::Test {
331 public:
332 void SetUp() {
333 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
334 test_dir_ = scoped_temp_dir_.GetPath();
335 }
336
337 protected:
338 DevInstall dev_install_;
339 base::FilePath test_dir_;
340 base::ScopedTempDir scoped_temp_dir_;
341};
342
343} // namespace
344
345// Check stateful is set up correctly.
346TEST_F(InitializeStateDirTest, Works) {
347 // Make sure we fully set things up.
348 ASSERT_TRUE(dev_install_.InitializeStateDir(test_dir_));
349 ASSERT_TRUE(base::IsLink(test_dir_.Append("usr")));
350 ASSERT_TRUE(base::IsLink(test_dir_.Append("local")));
351 ASSERT_TRUE(base::IsLink(test_dir_.Append("local")));
352 const base::FilePath etc = test_dir_.Append("etc");
353 ASSERT_TRUE(base::PathExists(etc));
354 ASSERT_TRUE(base::IsLink(etc.Append("passwd")));
355 ASSERT_TRUE(base::IsLink(etc.Append("group")));
356
357 // Calling a second time should be fine.
358 ASSERT_TRUE(dev_install_.InitializeStateDir(test_dir_));
359}
360
361// Check we handle errors gracefully.
362TEST_F(InitializeStateDirTest, Fails) {
363 // Create a broken /etc symlink.
364 ASSERT_TRUE(
365 base::CreateSymbolicLink(base::FilePath("foo"), test_dir_.Append("etc")));
366 ASSERT_FALSE(dev_install_.InitializeStateDir(test_dir_));
367}
368
Mike Frysingerb4aadcd2019-02-25 23:50:05 -0500369namespace {
370
371class LoadRuntimeSettingsTest : public ::testing::Test {
372 public:
373 void SetUp() {
374 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
375 test_dir_ = scoped_temp_dir_.GetPath();
376 }
377
378 protected:
379 DevInstall dev_install_;
380 base::FilePath test_dir_;
381 base::ScopedTempDir scoped_temp_dir_;
382};
383
384} // namespace
385
386// Check loading state works.
387TEST_F(LoadRuntimeSettingsTest, Works) {
388 const base::FilePath lsb_release = test_dir_.Append("lsb-release");
389 std::string data{
390 "CHROMEOS_DEVSERVER=https://foo\n"
391 "CHROMEOS_RELEASE_BOARD=betty\n"
392 "CHROMEOS_RELEASE_CHROME_MILESTONE=79\n"
393 "CHROMEOS_RELEASE_VERSION=100.10.1\n"};
394 ASSERT_EQ(base::WriteFile(lsb_release, data.c_str(), data.size()),
395 data.size());
396 ASSERT_TRUE(dev_install_.LoadRuntimeSettings(lsb_release));
397 ASSERT_EQ(dev_install_.GetDevserverUrlForTest(), "https://foo");
398 ASSERT_EQ(dev_install_.GetBoardForTest(), "betty");
399 ASSERT_EQ(dev_install_.GetBinhostVersionForTest(), "100.10.1");
400}
401
402// Check loading empty state works.
403TEST_F(LoadRuntimeSettingsTest, Empty) {
404 const base::FilePath lsb_release = test_dir_.Append("lsb-release");
405 std::string data{""};
406 ASSERT_EQ(base::WriteFile(lsb_release, data.c_str(), data.size()),
407 data.size());
408 ASSERT_TRUE(dev_install_.LoadRuntimeSettings(lsb_release));
409}
410
411// Check loading state doesn't abort with missing file.
412TEST_F(LoadRuntimeSettingsTest, Missing) {
413 ASSERT_TRUE(dev_install_.LoadRuntimeSettings(test_dir_.Append("asdf")));
414}
415
Mike Frysingerbf36e3c2019-11-02 02:47:50 -0400416namespace {
417
418class BootstrapPackagesMock : public DevInstall {
419 public:
420 MOCK_METHOD(bool,
421 DownloadAndInstallBootstrapPackage,
422 (const std::string&),
423 (override));
424};
425
426class BootstrapPackagesTest : public ::testing::Test {
427 public:
428 void SetUp() {
429 // Have the install step pass by default.
430 ON_CALL(dev_install_, DownloadAndInstallBootstrapPackage(_))
431 .WillByDefault(Return(true));
432
433 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
434 test_dir_ = scoped_temp_dir_.GetPath();
435 dev_install_.SetStateDirForTest(test_dir_);
436 }
437
438 protected:
439 BootstrapPackagesMock dev_install_;
440 base::FilePath test_dir_;
441 base::ScopedTempDir scoped_temp_dir_;
442};
443
444} // namespace
445
446// Check bootstrap works in general.
447TEST_F(BootstrapPackagesTest, Works) {
448 const base::FilePath listing = test_dir_.Append("bootstrap.packages");
449 std::string data{
450 "foo/bar-123\n"
451 "cat/pkg-1.0\n"};
452 ASSERT_EQ(base::WriteFile(listing, data.c_str(), data.size()), data.size());
453
454 ON_CALL(dev_install_, DownloadAndInstallBootstrapPackage(_))
455 .WillByDefault(Return(false));
456 EXPECT_CALL(dev_install_, DownloadAndInstallBootstrapPackage("foo/bar-123"))
457 .WillOnce(Return(true));
458 EXPECT_CALL(dev_install_, DownloadAndInstallBootstrapPackage("cat/pkg-1.0"))
459 .WillOnce(Return(true));
460
461 const base::FilePath bindir = test_dir_.Append("usr/bin");
462 ASSERT_TRUE(base::CreateDirectory(bindir));
463 ASSERT_TRUE(dev_install_.DownloadAndInstallBootstrapPackages(listing));
464
465 // We assert the symlinks exist. We assume the targets are valid for now.
466 base::FilePath target;
467 ASSERT_TRUE(base::ReadSymbolicLink(bindir.Append("python"), &target));
468 ASSERT_TRUE(base::ReadSymbolicLink(bindir.Append("python2"), &target));
469 ASSERT_TRUE(base::ReadSymbolicLink(bindir.Append("python3"), &target));
470}
471
472// Check missing bootstrap list fails.
473TEST_F(BootstrapPackagesTest, Missing) {
474 const base::FilePath listing = test_dir_.Append("bootstrap.packages");
475 ASSERT_FALSE(dev_install_.DownloadAndInstallBootstrapPackages(listing));
476}
477
478// Check empty bootstrap list fails.
479TEST_F(BootstrapPackagesTest, Empty) {
480 const base::FilePath listing = test_dir_.Append("bootstrap.packages");
481 ASSERT_EQ(base::WriteFile(listing, "", 0), 0);
482 ASSERT_FALSE(dev_install_.DownloadAndInstallBootstrapPackages(listing));
483}
484
485// Check mid-bootstrap failure behavior.
486TEST_F(BootstrapPackagesTest, PackageFailed) {
487 const base::FilePath listing = test_dir_.Append("bootstrap.packages");
488 std::string data{"cat/pkg-3"};
489 ASSERT_EQ(base::WriteFile(listing, data.c_str(), data.size()), data.size());
490
491 EXPECT_CALL(dev_install_, DownloadAndInstallBootstrapPackage("cat/pkg-3"))
492 .WillOnce(Return(false));
493
494 const base::FilePath bindir = test_dir_.Append("usr/bin");
495 ASSERT_TRUE(base::CreateDirectory(bindir));
496 ASSERT_FALSE(dev_install_.DownloadAndInstallBootstrapPackages(listing));
497}
498
Mike Frysinger1dad0972019-02-23 18:36:37 -0500499} // namespace dev_install