Home | History | Annotate | Download | only in bootstat
      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() {
    105     store_path_ = std::string(store_dir_.path) + "/";
    106   }
    107 
    108   const std::string& GetStorePathForTesting() const {
    109     return store_path_;
    110   }
    111 
    112  private:
    113   void TearDown() {
    114     // This removes the record store temporary directory even though
    115     // TemporaryDir should already take care of it, but this method cleans up
    116     // the test files added to the directory which prevent TemporaryDir from
    117     // being able to remove the directory.
    118     DeleteDirectory(store_path_);
    119   }
    120 
    121   // A scoped temporary directory. Using this abstraction provides creation of
    122   // the directory and the path to the directory, which is stored in
    123   // |store_path_|.
    124   TemporaryDir store_dir_;
    125 
    126   // The path to the temporary directory used by the BootEventRecordStore to
    127   // persist records.  The directory is created and destroyed for each test.
    128   std::string store_path_;
    129 
    130   DISALLOW_COPY_AND_ASSIGN(BootEventRecordStoreTest);
    131 };
    132 
    133 }  // namespace
    134 
    135 TEST_F(BootEventRecordStoreTest, AddSingleBootEvent) {
    136   BootEventRecordStore store;
    137   store.SetStorePath(GetStorePathForTesting());
    138 
    139   time_t uptime = GetUptimeSeconds();
    140   ASSERT_NE(-1, uptime);
    141 
    142   store.AddBootEvent("cenozoic");
    143 
    144   auto events = store.GetAllBootEvents();
    145   ASSERT_EQ(1U, events.size());
    146   EXPECT_EQ("cenozoic", events[0].first);
    147   EXPECT_TRUE(FuzzUptimeEquals(uptime, events[0].second));
    148 }
    149 
    150 TEST_F(BootEventRecordStoreTest, AddMultipleBootEvents) {
    151   BootEventRecordStore store;
    152   store.SetStorePath(GetStorePathForTesting());
    153 
    154   time_t uptime = GetUptimeSeconds();
    155   ASSERT_NE(-1, uptime);
    156 
    157   store.AddBootEvent("cretaceous");
    158   store.AddBootEvent("jurassic");
    159   store.AddBootEvent("triassic");
    160 
    161   const std::string EXPECTED_NAMES[] = {
    162     "cretaceous",
    163     "jurassic",
    164     "triassic",
    165   };
    166 
    167   auto events = store.GetAllBootEvents();
    168   ASSERT_EQ(3U, events.size());
    169 
    170   std::vector<std::string> names;
    171   std::vector<int32_t> timestamps;
    172   for (auto i = events.begin(); i != events.end(); ++i) {
    173     names.push_back(i->first);
    174     timestamps.push_back(i->second);
    175   }
    176 
    177   EXPECT_THAT(names, UnorderedElementsAreArray(EXPECTED_NAMES));
    178 
    179   for (auto i = timestamps.cbegin(); i != timestamps.cend(); ++i) {
    180     EXPECT_TRUE(FuzzUptimeEquals(uptime, *i));
    181   }
    182 }
    183 
    184 TEST_F(BootEventRecordStoreTest, AddBootEventWithValue) {
    185   BootEventRecordStore store;
    186   store.SetStorePath(GetStorePathForTesting());
    187 
    188   store.AddBootEventWithValue("permian", 42);
    189 
    190   auto events = store.GetAllBootEvents();
    191   ASSERT_EQ(1U, events.size());
    192   EXPECT_EQ("permian", events[0].first);
    193   EXPECT_EQ(42, events[0].second);
    194 }
    195 
    196 TEST_F(BootEventRecordStoreTest, GetBootEvent) {
    197   BootEventRecordStore store;
    198   store.SetStorePath(GetStorePathForTesting());
    199 
    200   // Event does not exist.
    201   BootEventRecordStore::BootEventRecord record;
    202   bool result = store.GetBootEvent("nonexistent", &record);
    203   EXPECT_EQ(false, result);
    204 
    205   // Empty path.
    206   EXPECT_DEATH(store.GetBootEvent(std::string(), &record), std::string());
    207 
    208   // Success case.
    209   store.AddBootEventWithValue("carboniferous", 314);
    210   result = store.GetBootEvent("carboniferous", &record);
    211   EXPECT_EQ(true, result);
    212   EXPECT_EQ("carboniferous", record.first);
    213   EXPECT_EQ(314, record.second);
    214 
    215   // Null |record|.
    216   EXPECT_DEATH(store.GetBootEvent("carboniferous", nullptr), std::string());
    217 }
    218 
    219 // Tests that the BootEventRecordStore is capable of handling an older record
    220 // protocol which does not contain file contents.
    221 TEST_F(BootEventRecordStoreTest, GetBootEventNoFileContent) {
    222   BootEventRecordStore store;
    223   store.SetStorePath(GetStorePathForTesting());
    224 
    225   EXPECT_TRUE(CreateEmptyBootEventRecord(store.GetBootEventPath("devonian"), 2718));
    226 
    227   BootEventRecordStore::BootEventRecord record;
    228   bool result = store.GetBootEvent("devonian", &record);
    229   EXPECT_EQ(true, result);
    230   EXPECT_EQ("devonian", record.first);
    231   EXPECT_EQ(2718, record.second);
    232 }
    233