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