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() { 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