| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "dev-install/dev_install.h" |
| |
| #include <unistd.h> |
| |
| #include <istream> |
| #include <sstream> |
| #include <string> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| using ::testing::_; |
| using ::testing::Return; |
| |
| namespace dev_install { |
| |
| namespace { |
| |
| class DevInstallMock : public DevInstall { |
| public: |
| MOCK_METHOD(int, Exec, (const std::vector<const char*>&), (override)); |
| MOCK_METHOD(bool, IsDevMode, (), (const, override)); |
| MOCK_METHOD(bool, |
| PromptUser, |
| (std::istream&, const std::string&), |
| (override)); |
| MOCK_METHOD(bool, ClearStateDir, (const base::FilePath&), (override)); |
| MOCK_METHOD(bool, |
| InitializeStateDir, |
| (const base::FilePath& dir), |
| (override)); |
| }; |
| |
| class DevInstallTest : public ::testing::Test { |
| public: |
| void SetUp() override { |
| // Set the default to dev mode enabled. Most tests want that. |
| ON_CALL(dev_install_, IsDevMode()).WillByDefault(Return(true)); |
| |
| // Ignore stateful setup for most tests. |
| ON_CALL(dev_install_, InitializeStateDir(_)).WillByDefault(Return(true)); |
| |
| // Most tests should run with a path that doesn't exist. |
| dev_install_.SetStateDirForTest(base::FilePath("/.path-does-not-exist")); |
| } |
| |
| protected: |
| DevInstallMock dev_install_; |
| }; |
| |
| } // namespace |
| |
| // Check default run through. |
| TEST_F(DevInstallTest, Run) { |
| EXPECT_CALL(dev_install_, Exec(_)).WillOnce(Return(1234)); |
| EXPECT_EQ(1234, dev_install_.Run()); |
| } |
| |
| // Systems not in dev mode should abort. |
| TEST_F(DevInstallTest, NonDevMode) { |
| EXPECT_CALL(dev_install_, IsDevMode()).WillOnce(Return(false)); |
| EXPECT_CALL(dev_install_, ClearStateDir(_)).Times(0); |
| EXPECT_CALL(dev_install_, Exec(_)).Times(0); |
| EXPECT_EQ(2, dev_install_.Run()); |
| } |
| |
| // Check system has been initialized. |
| TEST_F(DevInstallTest, AlreadyInitialized) { |
| dev_install_.SetStateDirForTest(base::FilePath("/")); |
| EXPECT_CALL(dev_install_, Exec(_)).Times(0); |
| ASSERT_EQ(4, dev_install_.Run()); |
| } |
| |
| // Check --reinstall passed. |
| TEST_F(DevInstallTest, RunReinstallWorked) { |
| dev_install_.SetReinstallForTest(true); |
| EXPECT_CALL(dev_install_, ClearStateDir(_)).WillOnce(Return(true)); |
| EXPECT_CALL(dev_install_, Exec(_)).WillOnce(Return(1234)); |
| ASSERT_EQ(1234, dev_install_.Run()); |
| } |
| |
| // Check when --reinstall is requested but clearing fails. |
| TEST_F(DevInstallTest, RunReinstallFails) { |
| dev_install_.SetReinstallForTest(true); |
| EXPECT_CALL(dev_install_, ClearStateDir(_)).WillOnce(Return(false)); |
| EXPECT_CALL(dev_install_, Exec(_)).Times(0); |
| ASSERT_EQ(1, dev_install_.Run()); |
| } |
| |
| // Check --uninstall passed. |
| TEST_F(DevInstallTest, RunUninstall) { |
| dev_install_.SetUninstallForTest(true); |
| EXPECT_CALL(dev_install_, ClearStateDir(_)).WillOnce(Return(true)); |
| EXPECT_CALL(dev_install_, Exec(_)).Times(0); |
| ASSERT_EQ(0, dev_install_.Run()); |
| } |
| |
| // Stateful setup failures. |
| TEST_F(DevInstallTest, StatefulSetupFailure) { |
| EXPECT_CALL(dev_install_, InitializeStateDir(_)).WillOnce(Return(false)); |
| EXPECT_CALL(dev_install_, Exec(_)).Times(0); |
| ASSERT_EQ(5, dev_install_.Run()); |
| } |
| |
| namespace { |
| |
| class PromptUserTest : public ::testing::Test { |
| protected: |
| DevInstall dev_install_; |
| }; |
| |
| } // namespace |
| |
| // The --yes flag should pass w/out prompting the user. |
| TEST_F(PromptUserTest, Forced) { |
| dev_install_.SetYesForTest(true); |
| std::stringstream stream(""); |
| EXPECT_TRUE(dev_install_.PromptUser(stream, "")); |
| } |
| |
| // EOF input should fail. |
| TEST_F(PromptUserTest, Eof) { |
| std::stringstream stream(""); |
| EXPECT_FALSE(dev_install_.PromptUser(stream, "")); |
| } |
| |
| // Default input (hitting enter) should fail. |
| TEST_F(PromptUserTest, Default) { |
| std::stringstream stream("\n"); |
| EXPECT_FALSE(dev_install_.PromptUser(stream, "")); |
| } |
| |
| // Entering "n" should fail. |
| TEST_F(PromptUserTest, No) { |
| std::stringstream stream("n\n"); |
| EXPECT_FALSE(dev_install_.PromptUser(stream, "")); |
| } |
| |
| // Entering "y" should pass. |
| TEST_F(PromptUserTest, Yes) { |
| std::stringstream stream("y\n"); |
| EXPECT_TRUE(dev_install_.PromptUser(stream, "")); |
| } |
| |
| namespace { |
| |
| class DeletePathTest : public ::testing::Test { |
| public: |
| void SetUp() override { |
| ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); |
| test_dir_ = scoped_temp_dir_.GetPath(); |
| dev_install_.SetStateDirForTest(test_dir_); |
| } |
| |
| protected: |
| DevInstall dev_install_; |
| base::FilePath test_dir_; |
| base::ScopedTempDir scoped_temp_dir_; |
| }; |
| |
| } // namespace |
| |
| // Check missing dir. |
| TEST_F(DeletePathTest, Missing) { |
| struct stat st = {}; |
| EXPECT_TRUE(dev_install_.DeletePath(st, test_dir_.Append("foo"))); |
| } |
| |
| // Check deleting dir contents leaves the dir alone. |
| TEST_F(DeletePathTest, Empty) { |
| struct stat st = {}; |
| EXPECT_TRUE(dev_install_.DeletePath(st, test_dir_)); |
| EXPECT_TRUE(base::PathExists(test_dir_)); |
| } |
| |
| // Check mounted deletion. |
| TEST_F(DeletePathTest, Mounted) { |
| struct stat st = {}; |
| const base::FilePath subdir = test_dir_.Append("subdir"); |
| EXPECT_TRUE(base::CreateDirectory(subdir)); |
| EXPECT_FALSE(dev_install_.DeletePath(st, test_dir_)); |
| EXPECT_TRUE(base::PathExists(subdir)); |
| } |
| |
| // Check recursive deletion. |
| TEST_F(DeletePathTest, Works) { |
| struct stat st; |
| EXPECT_EQ(0, stat(test_dir_.value().c_str(), &st)); |
| |
| EXPECT_EQ(3, base::WriteFile(test_dir_.Append("file"), "123", 3)); |
| EXPECT_EQ(0, symlink("x", test_dir_.Append("broken-sym").value().c_str())); |
| EXPECT_EQ(0, symlink("file", test_dir_.Append("file-sym").value().c_str())); |
| EXPECT_EQ(0, symlink(".", test_dir_.Append("dir-sym").value().c_str())); |
| EXPECT_EQ(0, symlink("subdir", test_dir_.Append("dir-sym2").value().c_str())); |
| const base::FilePath subdir = test_dir_.Append("subdir"); |
| EXPECT_TRUE(base::CreateDirectory(subdir)); |
| EXPECT_EQ(3, base::WriteFile(subdir.Append("file"), "123", 3)); |
| const base::FilePath subsubdir = test_dir_.Append("subdir"); |
| EXPECT_TRUE(base::CreateDirectory(subsubdir)); |
| EXPECT_EQ(3, base::WriteFile(subsubdir.Append("file"), "123", 3)); |
| |
| EXPECT_TRUE(dev_install_.DeletePath(st, test_dir_)); |
| EXPECT_TRUE(base::PathExists(test_dir_)); |
| EXPECT_EQ(0, rmdir(test_dir_.value().c_str())); |
| } |
| |
| namespace { |
| |
| // We could mock out DeletePath, but it's easy to lightly validate it. |
| class ClearStateDirMock : public DevInstall { |
| public: |
| MOCK_METHOD(bool, |
| PromptUser, |
| (std::istream&, const std::string&), |
| (override)); |
| }; |
| |
| class ClearStateDirTest : public ::testing::Test { |
| public: |
| void SetUp() { |
| ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); |
| test_dir_ = scoped_temp_dir_.GetPath(); |
| } |
| |
| protected: |
| ClearStateDirMock dev_install_; |
| base::FilePath test_dir_; |
| base::ScopedTempDir scoped_temp_dir_; |
| }; |
| |
| } // namespace |
| |
| // Check user rejecting things. |
| TEST_F(ClearStateDirTest, Cancel) { |
| EXPECT_CALL(dev_install_, PromptUser(_, _)).WillOnce(Return(false)); |
| const base::FilePath subdir = test_dir_.Append("subdir"); |
| ASSERT_TRUE(base::CreateDirectory(subdir)); |
| ASSERT_FALSE(dev_install_.ClearStateDir(test_dir_)); |
| ASSERT_TRUE(base::PathExists(subdir)); |
| } |
| |
| // Check missing dir is handled. |
| TEST_F(ClearStateDirTest, Missing) { |
| EXPECT_CALL(dev_install_, PromptUser(_, _)).WillOnce(Return(true)); |
| ASSERT_TRUE(dev_install_.ClearStateDir(test_dir_.Append("subdir"))); |
| ASSERT_TRUE(base::PathExists(test_dir_)); |
| } |
| |
| // Check empty dir is handled. |
| TEST_F(ClearStateDirTest, Empty) { |
| EXPECT_CALL(dev_install_, PromptUser(_, _)).WillOnce(Return(true)); |
| ASSERT_TRUE(dev_install_.ClearStateDir(test_dir_)); |
| ASSERT_TRUE(base::PathExists(test_dir_)); |
| } |
| |
| // Check dir with contents is cleared. |
| TEST_F(ClearStateDirTest, Works) { |
| EXPECT_CALL(dev_install_, PromptUser(_, _)).WillOnce(Return(true)); |
| const base::FilePath subdir = test_dir_.Append("subdir"); |
| ASSERT_TRUE(base::CreateDirectory(subdir)); |
| ASSERT_TRUE(dev_install_.ClearStateDir(test_dir_)); |
| ASSERT_FALSE(base::PathExists(subdir)); |
| } |
| |
| namespace { |
| |
| class InitializeStateDirTest : public ::testing::Test { |
| public: |
| void SetUp() { |
| ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); |
| test_dir_ = scoped_temp_dir_.GetPath(); |
| } |
| |
| protected: |
| DevInstall dev_install_; |
| base::FilePath test_dir_; |
| base::ScopedTempDir scoped_temp_dir_; |
| }; |
| |
| } // namespace |
| |
| // Check stateful is set up correctly. |
| TEST_F(InitializeStateDirTest, Works) { |
| // Make sure we fully set things up. |
| ASSERT_TRUE(dev_install_.InitializeStateDir(test_dir_)); |
| ASSERT_TRUE(base::IsLink(test_dir_.Append("usr"))); |
| ASSERT_TRUE(base::IsLink(test_dir_.Append("local"))); |
| ASSERT_TRUE(base::IsLink(test_dir_.Append("local"))); |
| const base::FilePath etc = test_dir_.Append("etc"); |
| ASSERT_TRUE(base::PathExists(etc)); |
| ASSERT_TRUE(base::IsLink(etc.Append("passwd"))); |
| ASSERT_TRUE(base::IsLink(etc.Append("group"))); |
| |
| // Calling a second time should be fine. |
| ASSERT_TRUE(dev_install_.InitializeStateDir(test_dir_)); |
| } |
| |
| // Check we handle errors gracefully. |
| TEST_F(InitializeStateDirTest, Fails) { |
| // Create a broken /etc symlink. |
| ASSERT_TRUE( |
| base::CreateSymbolicLink(base::FilePath("foo"), test_dir_.Append("etc"))); |
| ASSERT_FALSE(dev_install_.InitializeStateDir(test_dir_)); |
| } |
| |
| namespace { |
| |
| class LoadRuntimeSettingsTest : public ::testing::Test { |
| public: |
| void SetUp() { |
| ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); |
| test_dir_ = scoped_temp_dir_.GetPath(); |
| } |
| |
| protected: |
| DevInstall dev_install_; |
| base::FilePath test_dir_; |
| base::ScopedTempDir scoped_temp_dir_; |
| }; |
| |
| } // namespace |
| |
| // Check loading state works. |
| TEST_F(LoadRuntimeSettingsTest, Works) { |
| const base::FilePath lsb_release = test_dir_.Append("lsb-release"); |
| std::string data{ |
| "CHROMEOS_DEVSERVER=https://foo\n" |
| "CHROMEOS_RELEASE_BOARD=betty\n" |
| "CHROMEOS_RELEASE_CHROME_MILESTONE=79\n" |
| "CHROMEOS_RELEASE_VERSION=100.10.1\n"}; |
| ASSERT_EQ(base::WriteFile(lsb_release, data.c_str(), data.size()), |
| data.size()); |
| ASSERT_TRUE(dev_install_.LoadRuntimeSettings(lsb_release)); |
| ASSERT_EQ(dev_install_.GetDevserverUrlForTest(), "https://foo"); |
| ASSERT_EQ(dev_install_.GetBoardForTest(), "betty"); |
| ASSERT_EQ(dev_install_.GetBinhostVersionForTest(), "100.10.1"); |
| } |
| |
| // Check loading empty state works. |
| TEST_F(LoadRuntimeSettingsTest, Empty) { |
| const base::FilePath lsb_release = test_dir_.Append("lsb-release"); |
| std::string data{""}; |
| ASSERT_EQ(base::WriteFile(lsb_release, data.c_str(), data.size()), |
| data.size()); |
| ASSERT_TRUE(dev_install_.LoadRuntimeSettings(lsb_release)); |
| } |
| |
| // Check loading state doesn't abort with missing file. |
| TEST_F(LoadRuntimeSettingsTest, Missing) { |
| ASSERT_TRUE(dev_install_.LoadRuntimeSettings(test_dir_.Append("asdf"))); |
| } |
| |
| } // namespace dev_install |