1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "boot_event_record_store.h" 18 19#include <dirent.h> 20#include <fcntl.h> 21#include <sys/stat.h> 22#include <sys/time.h> 23#include <unistd.h> 24#include <cstdint> 25#include <cstdlib> 26#include <android-base/file.h> 27#include <android-base/logging.h> 28#include <android-base/test_utils.h> 29#include <android-base/unique_fd.h> 30#include <gtest/gtest.h> 31#include <gmock/gmock.h> 32#include "uptime_parser.h" 33 34using testing::UnorderedElementsAreArray; 35 36namespace { 37 38// Creates a fake boot event record file at |record_path| containing the boot 39// record |value|. This method is necessary as truncating a 40// BootEventRecordStore-created file would modify the mtime, which would alter 41// the value of the record. 42bool CreateEmptyBootEventRecord(const std::string& record_path, int32_t value) { 43 android::base::unique_fd record_fd(creat(record_path.c_str(), S_IRUSR | S_IWUSR)); 44 if (record_fd == -1) { 45 return false; 46 } 47 48 // Set the |mtime| of the file to store the value of the boot event while 49 // preserving the |atime|. 50 struct timeval atime = {/* tv_sec */ 0, /* tv_usec */ 0}; 51 struct timeval mtime = {/* tv_sec */ value, /* tv_usec */ 0}; 52 const struct timeval times[] = {atime, mtime}; 53 if (utimes(record_path.c_str(), times) != 0) { 54 return false; 55 } 56 57 return true; 58} 59 60// Returns true if the time difference between |a| and |b| is no larger 61// than 10 seconds. This allow for a relatively large fuzz when comparing 62// two timestamps taken back-to-back. 63bool FuzzUptimeEquals(int32_t a, int32_t b) { 64 const int32_t FUZZ_SECONDS = 10; 65 return (abs(a - b) <= FUZZ_SECONDS); 66} 67 68// Recursively deletes the directory at |path|. 69void DeleteDirectory(const std::string& path) { 70 typedef std::unique_ptr<DIR, decltype(&closedir)> ScopedDIR; 71 ScopedDIR dir(opendir(path.c_str()), closedir); 72 ASSERT_NE(nullptr, dir.get()); 73 74 struct dirent* entry; 75 while ((entry = readdir(dir.get())) != NULL) { 76 const std::string entry_name(entry->d_name); 77 if (entry_name == "." || entry_name == "..") { 78 continue; 79 } 80 81 const std::string entry_path = path + "/" + entry_name; 82 if (entry->d_type == DT_DIR) { 83 DeleteDirectory(entry_path); 84 } else { 85 unlink(entry_path.c_str()); 86 } 87 } 88 89 rmdir(path.c_str()); 90} 91 92class BootEventRecordStoreTest : public ::testing::Test { 93 public: 94 BootEventRecordStoreTest() { 95 store_path_ = std::string(store_dir_.path) + "/"; 96 } 97 98 const std::string& GetStorePathForTesting() const { 99 return store_path_; 100 } 101 102 private: 103 void TearDown() { 104 // This removes the record store temporary directory even though 105 // TemporaryDir should already take care of it, but this method cleans up 106 // the test files added to the directory which prevent TemporaryDir from 107 // being able to remove the directory. 108 DeleteDirectory(store_path_); 109 } 110 111 // A scoped temporary directory. Using this abstraction provides creation of 112 // the directory and the path to the directory, which is stored in 113 // |store_path_|. 114 TemporaryDir store_dir_; 115 116 // The path to the temporary directory used by the BootEventRecordStore to 117 // persist records. The directory is created and destroyed for each test. 118 std::string store_path_; 119 120 DISALLOW_COPY_AND_ASSIGN(BootEventRecordStoreTest); 121}; 122 123} // namespace 124 125TEST_F(BootEventRecordStoreTest, AddSingleBootEvent) { 126 BootEventRecordStore store; 127 store.SetStorePath(GetStorePathForTesting()); 128 129 time_t uptime = bootstat::ParseUptime(); 130 ASSERT_NE(-1, uptime); 131 132 store.AddBootEvent("cenozoic"); 133 134 auto events = store.GetAllBootEvents(); 135 ASSERT_EQ(1U, events.size()); 136 EXPECT_EQ("cenozoic", events[0].first); 137 EXPECT_TRUE(FuzzUptimeEquals(uptime, events[0].second)); 138} 139 140TEST_F(BootEventRecordStoreTest, AddMultipleBootEvents) { 141 BootEventRecordStore store; 142 store.SetStorePath(GetStorePathForTesting()); 143 144 time_t uptime = bootstat::ParseUptime(); 145 ASSERT_NE(-1, uptime); 146 147 store.AddBootEvent("cretaceous"); 148 store.AddBootEvent("jurassic"); 149 store.AddBootEvent("triassic"); 150 151 const std::string EXPECTED_NAMES[] = { 152 "cretaceous", 153 "jurassic", 154 "triassic", 155 }; 156 157 auto events = store.GetAllBootEvents(); 158 ASSERT_EQ(3U, events.size()); 159 160 std::vector<std::string> names; 161 std::vector<int32_t> timestamps; 162 for (auto i = events.begin(); i != events.end(); ++i) { 163 names.push_back(i->first); 164 timestamps.push_back(i->second); 165 } 166 167 EXPECT_THAT(names, UnorderedElementsAreArray(EXPECTED_NAMES)); 168 169 for (auto i = timestamps.cbegin(); i != timestamps.cend(); ++i) { 170 EXPECT_TRUE(FuzzUptimeEquals(uptime, *i)); 171 } 172} 173 174TEST_F(BootEventRecordStoreTest, AddBootEventWithValue) { 175 BootEventRecordStore store; 176 store.SetStorePath(GetStorePathForTesting()); 177 178 store.AddBootEventWithValue("permian", 42); 179 180 auto events = store.GetAllBootEvents(); 181 ASSERT_EQ(1U, events.size()); 182 EXPECT_EQ("permian", events[0].first); 183 EXPECT_EQ(42, events[0].second); 184} 185 186TEST_F(BootEventRecordStoreTest, GetBootEvent) { 187 BootEventRecordStore store; 188 store.SetStorePath(GetStorePathForTesting()); 189 190 // Event does not exist. 191 BootEventRecordStore::BootEventRecord record; 192 bool result = store.GetBootEvent("nonexistent", &record); 193 EXPECT_EQ(false, result); 194 195 // Empty path. 196 EXPECT_DEATH(store.GetBootEvent(std::string(), &record), std::string()); 197 198 // Success case. 199 store.AddBootEventWithValue("carboniferous", 314); 200 result = store.GetBootEvent("carboniferous", &record); 201 EXPECT_EQ(true, result); 202 EXPECT_EQ("carboniferous", record.first); 203 EXPECT_EQ(314, record.second); 204 205 // Null |record|. 206 EXPECT_DEATH(store.GetBootEvent("carboniferous", nullptr), std::string()); 207} 208 209// Tests that the BootEventRecordStore is capable of handling an older record 210// protocol which does not contain file contents. 211TEST_F(BootEventRecordStoreTest, GetBootEventNoFileContent) { 212 BootEventRecordStore store; 213 store.SetStorePath(GetStorePathForTesting()); 214 215 EXPECT_TRUE(CreateEmptyBootEventRecord(store.GetBootEventPath("devonian"), 2718)); 216 217 BootEventRecordStore::BootEventRecord record; 218 bool result = store.GetBootEvent("devonian", &record); 219 EXPECT_EQ(true, result); 220 EXPECT_EQ("devonian", record.first); 221 EXPECT_EQ(2718, record.second); 222} 223