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 // Writing the value as content in the record file is a debug measure to 49 // ensure the validity of the file mtime value, i.e., to check that the record 50 // file mtime values are not changed once set. 51 // TODO(jhawkins): Remove this block. 52 if (!android::base::WriteStringToFd(std::to_string(value), record_fd)) { 53 return false; 54 } 55 56 // Set the |mtime| of the file to store the value of the boot event while 57 // preserving the |atime|. 58 struct timeval atime = {/* tv_sec */ 0, /* tv_usec */ 0}; 59 struct timeval mtime = {/* tv_sec */ value, /* tv_usec */ 0}; 60 const struct timeval times[] = {atime, mtime}; 61 if (utimes(record_path.c_str(), times) != 0) { 62 return false; 63 } 64 65 return true; 66} 67 68// Returns true if the time difference between |a| and |b| is no larger 69// than 10 seconds. This allow for a relatively large fuzz when comparing 70// two timestamps taken back-to-back. 71bool FuzzUptimeEquals(int32_t a, int32_t b) { 72 const int32_t FUZZ_SECONDS = 10; 73 return (abs(a - b) <= FUZZ_SECONDS); 74} 75 76// Recursively deletes the directory at |path|. 77void DeleteDirectory(const std::string& path) { 78 typedef std::unique_ptr<DIR, decltype(&closedir)> ScopedDIR; 79 ScopedDIR dir(opendir(path.c_str()), closedir); 80 ASSERT_NE(nullptr, dir.get()); 81 82 struct dirent* entry; 83 while ((entry = readdir(dir.get())) != NULL) { 84 const std::string entry_name(entry->d_name); 85 if (entry_name == "." || entry_name == "..") { 86 continue; 87 } 88 89 const std::string entry_path = path + "/" + entry_name; 90 if (entry->d_type == DT_DIR) { 91 DeleteDirectory(entry_path); 92 } else { 93 unlink(entry_path.c_str()); 94 } 95 } 96 97 rmdir(path.c_str()); 98} 99 100class BootEventRecordStoreTest : public ::testing::Test { 101 public: 102 BootEventRecordStoreTest() { 103 store_path_ = std::string(store_dir_.path) + "/"; 104 } 105 106 const std::string& GetStorePathForTesting() const { 107 return store_path_; 108 } 109 110 private: 111 void TearDown() { 112 // This removes the record store temporary directory even though 113 // TemporaryDir should already take care of it, but this method cleans up 114 // the test files added to the directory which prevent TemporaryDir from 115 // being able to remove the directory. 116 DeleteDirectory(store_path_); 117 } 118 119 // A scoped temporary directory. Using this abstraction provides creation of 120 // the directory and the path to the directory, which is stored in 121 // |store_path_|. 122 TemporaryDir store_dir_; 123 124 // The path to the temporary directory used by the BootEventRecordStore to 125 // persist records. The directory is created and destroyed for each test. 126 std::string store_path_; 127 128 DISALLOW_COPY_AND_ASSIGN(BootEventRecordStoreTest); 129}; 130 131} // namespace 132 133TEST_F(BootEventRecordStoreTest, AddSingleBootEvent) { 134 BootEventRecordStore store; 135 store.SetStorePath(GetStorePathForTesting()); 136 137 time_t uptime = bootstat::ParseUptime(); 138 ASSERT_NE(-1, uptime); 139 140 store.AddBootEvent("cenozoic"); 141 142 auto events = store.GetAllBootEvents(); 143 ASSERT_EQ(1U, events.size()); 144 EXPECT_EQ("cenozoic", events[0].first); 145 EXPECT_TRUE(FuzzUptimeEquals(uptime, events[0].second)); 146} 147 148TEST_F(BootEventRecordStoreTest, AddMultipleBootEvents) { 149 BootEventRecordStore store; 150 store.SetStorePath(GetStorePathForTesting()); 151 152 time_t uptime = bootstat::ParseUptime(); 153 ASSERT_NE(-1, uptime); 154 155 store.AddBootEvent("cretaceous"); 156 store.AddBootEvent("jurassic"); 157 store.AddBootEvent("triassic"); 158 159 const std::string EXPECTED_NAMES[] = { 160 "cretaceous", 161 "jurassic", 162 "triassic", 163 }; 164 165 auto events = store.GetAllBootEvents(); 166 ASSERT_EQ(3U, events.size()); 167 168 std::vector<std::string> names; 169 std::vector<int32_t> timestamps; 170 for (auto i = events.begin(); i != events.end(); ++i) { 171 names.push_back(i->first); 172 timestamps.push_back(i->second); 173 } 174 175 EXPECT_THAT(names, UnorderedElementsAreArray(EXPECTED_NAMES)); 176 177 for (auto i = timestamps.cbegin(); i != timestamps.cend(); ++i) { 178 EXPECT_TRUE(FuzzUptimeEquals(uptime, *i)); 179 } 180} 181 182TEST_F(BootEventRecordStoreTest, AddBootEventWithValue) { 183 BootEventRecordStore store; 184 store.SetStorePath(GetStorePathForTesting()); 185 186 store.AddBootEventWithValue("permian", 42); 187 188 auto events = store.GetAllBootEvents(); 189 ASSERT_EQ(1U, events.size()); 190 EXPECT_EQ("permian", events[0].first); 191 EXPECT_EQ(42, events[0].second); 192} 193 194TEST_F(BootEventRecordStoreTest, GetBootEvent) { 195 BootEventRecordStore store; 196 store.SetStorePath(GetStorePathForTesting()); 197 198 // Event does not exist. 199 BootEventRecordStore::BootEventRecord record; 200 bool result = store.GetBootEvent("nonexistent", &record); 201 EXPECT_EQ(false, result); 202 203 // Empty path. 204 EXPECT_DEATH(store.GetBootEvent(std::string(), &record), std::string()); 205 206 // Success case. 207 store.AddBootEventWithValue("carboniferous", 314); 208 result = store.GetBootEvent("carboniferous", &record); 209 EXPECT_EQ(true, result); 210 EXPECT_EQ("carboniferous", record.first); 211 EXPECT_EQ(314, record.second); 212 213 // Null |record|. 214 EXPECT_DEATH(store.GetBootEvent("carboniferous", nullptr), std::string()); 215} 216 217// Tests that the BootEventRecordStore is capable of handling an older record 218// protocol which does not contain file contents. 219TEST_F(BootEventRecordStoreTest, GetBootEventNoFileContent) { 220 BootEventRecordStore store; 221 store.SetStorePath(GetStorePathForTesting()); 222 223 EXPECT_TRUE(CreateEmptyBootEventRecord(store.GetBootEventPath("devonian"), 2718)); 224 225 BootEventRecordStore::BootEventRecord record; 226 bool result = store.GetBootEvent("devonian", &record); 227 EXPECT_EQ(true, result); 228 EXPECT_EQ("devonian", record.first); 229 EXPECT_EQ(2718, record.second); 230} 231