/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "boot_event_record_store.h" #include <dirent.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/time.h> #include <unistd.h> #include <cstdint> #include <cstdlib> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/test_utils.h> #include <android-base/unique_fd.h> #include <gtest/gtest.h> #include <gmock/gmock.h> #include "uptime_parser.h" using testing::UnorderedElementsAreArray; namespace { // Creates a fake boot event record file at |record_path| containing the boot // record |value|. This method is necessary as truncating a // BootEventRecordStore-created file would modify the mtime, which would alter // the value of the record. bool CreateEmptyBootEventRecord(const std::string& record_path, int32_t value) { android::base::unique_fd record_fd(creat(record_path.c_str(), S_IRUSR | S_IWUSR)); if (record_fd == -1) { return false; } // Set the |mtime| of the file to store the value of the boot event while // preserving the |atime|. struct timeval atime = {/* tv_sec */ 0, /* tv_usec */ 0}; struct timeval mtime = {/* tv_sec */ value, /* tv_usec */ 0}; const struct timeval times[] = {atime, mtime}; if (utimes(record_path.c_str(), times) != 0) { return false; } return true; } // Returns true if the time difference between |a| and |b| is no larger // than 10 seconds. This allow for a relatively large fuzz when comparing // two timestamps taken back-to-back. bool FuzzUptimeEquals(int32_t a, int32_t b) { const int32_t FUZZ_SECONDS = 10; return (abs(a - b) <= FUZZ_SECONDS); } // Recursively deletes the directory at |path|. void DeleteDirectory(const std::string& path) { typedef std::unique_ptr<DIR, decltype(&closedir)> ScopedDIR; ScopedDIR dir(opendir(path.c_str()), closedir); ASSERT_NE(nullptr, dir.get()); struct dirent* entry; while ((entry = readdir(dir.get())) != NULL) { const std::string entry_name(entry->d_name); if (entry_name == "." || entry_name == "..") { continue; } const std::string entry_path = path + "/" + entry_name; if (entry->d_type == DT_DIR) { DeleteDirectory(entry_path); } else { unlink(entry_path.c_str()); } } rmdir(path.c_str()); } class BootEventRecordStoreTest : public ::testing::Test { public: BootEventRecordStoreTest() { store_path_ = std::string(store_dir_.path) + "/"; } const std::string& GetStorePathForTesting() const { return store_path_; } private: void TearDown() { // This removes the record store temporary directory even though // TemporaryDir should already take care of it, but this method cleans up // the test files added to the directory which prevent TemporaryDir from // being able to remove the directory. DeleteDirectory(store_path_); } // A scoped temporary directory. Using this abstraction provides creation of // the directory and the path to the directory, which is stored in // |store_path_|. TemporaryDir store_dir_; // The path to the temporary directory used by the BootEventRecordStore to // persist records. The directory is created and destroyed for each test. std::string store_path_; DISALLOW_COPY_AND_ASSIGN(BootEventRecordStoreTest); }; } // namespace TEST_F(BootEventRecordStoreTest, AddSingleBootEvent) { BootEventRecordStore store; store.SetStorePath(GetStorePathForTesting()); time_t uptime = bootstat::ParseUptime(); ASSERT_NE(-1, uptime); store.AddBootEvent("cenozoic"); auto events = store.GetAllBootEvents(); ASSERT_EQ(1U, events.size()); EXPECT_EQ("cenozoic", events[0].first); EXPECT_TRUE(FuzzUptimeEquals(uptime, events[0].second)); } TEST_F(BootEventRecordStoreTest, AddMultipleBootEvents) { BootEventRecordStore store; store.SetStorePath(GetStorePathForTesting()); time_t uptime = bootstat::ParseUptime(); ASSERT_NE(-1, uptime); store.AddBootEvent("cretaceous"); store.AddBootEvent("jurassic"); store.AddBootEvent("triassic"); const std::string EXPECTED_NAMES[] = { "cretaceous", "jurassic", "triassic", }; auto events = store.GetAllBootEvents(); ASSERT_EQ(3U, events.size()); std::vector<std::string> names; std::vector<int32_t> timestamps; for (auto i = events.begin(); i != events.end(); ++i) { names.push_back(i->first); timestamps.push_back(i->second); } EXPECT_THAT(names, UnorderedElementsAreArray(EXPECTED_NAMES)); for (auto i = timestamps.cbegin(); i != timestamps.cend(); ++i) { EXPECT_TRUE(FuzzUptimeEquals(uptime, *i)); } } TEST_F(BootEventRecordStoreTest, AddBootEventWithValue) { BootEventRecordStore store; store.SetStorePath(GetStorePathForTesting()); store.AddBootEventWithValue("permian", 42); auto events = store.GetAllBootEvents(); ASSERT_EQ(1U, events.size()); EXPECT_EQ("permian", events[0].first); EXPECT_EQ(42, events[0].second); } TEST_F(BootEventRecordStoreTest, GetBootEvent) { BootEventRecordStore store; store.SetStorePath(GetStorePathForTesting()); // Event does not exist. BootEventRecordStore::BootEventRecord record; bool result = store.GetBootEvent("nonexistent", &record); EXPECT_EQ(false, result); // Empty path. EXPECT_DEATH(store.GetBootEvent(std::string(), &record), std::string()); // Success case. store.AddBootEventWithValue("carboniferous", 314); result = store.GetBootEvent("carboniferous", &record); EXPECT_EQ(true, result); EXPECT_EQ("carboniferous", record.first); EXPECT_EQ(314, record.second); // Null |record|. EXPECT_DEATH(store.GetBootEvent("carboniferous", nullptr), std::string()); } // Tests that the BootEventRecordStore is capable of handling an older record // protocol which does not contain file contents. TEST_F(BootEventRecordStoreTest, GetBootEventNoFileContent) { BootEventRecordStore store; store.SetStorePath(GetStorePathForTesting()); EXPECT_TRUE(CreateEmptyBootEventRecord(store.GetBootEventPath("devonian"), 2718)); BootEventRecordStore::BootEventRecord record; bool result = store.GetBootEvent("devonian", &record); EXPECT_EQ(true, result); EXPECT_EQ("devonian", record.first); EXPECT_EQ(2718, record.second); }