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