aboutsummaryrefslogtreecommitdiffstats
path: root/chromeos/bootstat/log_unit_tests.cc
blob: fce46eea1118574babb4d8c5ad01665f484eaf06 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/bootstat/bootstat.h"
#include "chromeos/bootstat/bootstat_test.h"

#include <errno.h>
#include <stdlib.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <string>
#include <iostream>

#include <gtest/gtest.h>

namespace {

using std::string;

static void RemoveFile(const string& file_path) {
  EXPECT_EQ(0, unlink(file_path.c_str()))
      << "RemoveFile unlink() " << file_path << ": "
      << strerror(errno);
}


// Class to track and test the data associated with a single event.
// The primary function is TestLogEvent():  This method wraps calls
// to bootstat_log() with code to track the expected contents of the
// event files.  After logging, the expected content is tested
// against the actual content.
class EventTracker {
 public:
    EventTracker(const string& name, const string& uptime_prefix,
               const string& disk_prefix);
    void TestLogEvent(const string& uptime, const string& diskstats);
    void TestLogSymlink(const string& dirname, bool create_target);
    void Reset();

 private:
    string event_name_;
    string uptime_file_name_;
    string uptime_content_;
    string diskstats_file_name_;
    string diskstats_content_;
};


EventTracker::EventTracker(const string& name,
                           const string& uptime_prefix,
                           const string& diskstats_prefix)
    : event_name_(name),
      uptime_content_(""),
      diskstats_content_("") {
  string truncated_name =
      event_name_.substr(0, BOOTSTAT_MAX_EVENT_LEN - 1);
  uptime_file_name_ = uptime_prefix + truncated_name;
  diskstats_file_name_ = diskstats_prefix + truncated_name;
}


// Basic helper function to test whether the contents of the
// specified file exactly match the given contents string.
static void ValidateEventFileContents(const string& file_name,
                                      const string& file_contents) {
  int rv = access(file_name.c_str(), W_OK);
  EXPECT_EQ(0, rv) << "ValidateEventFileContents access(): "
      << file_name << " is not writable: "
      << strerror(errno) << ".";

  rv = access(file_name.c_str(), R_OK);
  ASSERT_EQ(0, rv) << "ValidateEventFileContents access(): "
    << file_name << " is not readable: " << strerror(errno) << ".";

  char *buffer = new char[file_contents.length() + 1];
  int fd = open(file_name.c_str(), O_RDONLY);
  rv = read(fd, buffer, file_contents.length());
  EXPECT_EQ(file_contents.length(), rv)
      << "ValidateEventFileContents read() failed.";

  buffer[file_contents.length()] = '\0';
  string actual_contents(buffer);
  EXPECT_EQ(file_contents, actual_contents)
      << "ValidateEventFileContents content mismatch.";

  rv = read(fd, buffer, 1);
  EXPECT_EQ(0, rv)
      << "ValidateEventFileContents found data "
         "in event file past expected EOF";

  (void)close(fd);
  delete [] buffer;
}


// Call bootstat_log() once, and update the expected content for
// this event.  Test that the new content of the event's files
// matches the updated expected content.
void EventTracker::TestLogEvent(const string& uptime,
                                const string& diskstats) {
  bootstat_log(event_name_.c_str());
  uptime_content_ += uptime;
  diskstats_content_ += diskstats;
  ValidateEventFileContents(uptime_file_name_, uptime_content_);
  ValidateEventFileContents(diskstats_file_name_, diskstats_content_);
}


static void MakeSymlink(const string& linkname, const string& filename) {
  int rv = symlink(linkname.c_str(), filename.c_str());
  ASSERT_EQ(0, rv) << "MakeSymlink symlink() failed to make "
      << linkname << " point to  " << filename
      << ": " << strerror(errno) << ".";
}

static void CreateSymlinkTarget(const string& filename) {
    const mode_t kFileCreationMode =
        S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
    int fd = creat(filename.c_str(), kFileCreationMode);
    ASSERT_GE(fd, 0) << "CreateSymlinkTarget creat(): "
        << filename << ": " << strerror(errno) << ".";
    (void)close(fd);
}

static void TestSymlinkTarget(const string& filename, bool expect_exists) {
    int fd = open(filename.c_str(), O_RDONLY);
    if (expect_exists) {
      EXPECT_GE(fd, 0) << "TestSymlinkTarget open(): " << filename
          << ": " << strerror(errno) << ".";
      char buff[8];
      ssize_t nbytes = read(fd, buff, sizeof (buff));
      EXPECT_EQ(0, nbytes) << "TestSymlinkTarget read(): nbytes = "
          << nbytes << ".";
    } else {
      EXPECT_LT(fd, 0) << "TestSymlinkTarget open(): "
          << filename << ": success was not expected";
    }
}

// Test calling bootstat_log() when the event files are symlinks.
// Calls to log events in this case are expected to produce no
// change in the file system.
//
// The test creates the necessary symlinks for the events, and
// optionally creates targets for the files.
void EventTracker::TestLogSymlink(
    const string& dirname, bool create_target) {
  string uptime_linkname("uptime.symlink");
  string diskstats_linkname("disk.symlink");

  MakeSymlink(uptime_linkname, uptime_file_name_);
  MakeSymlink(diskstats_linkname, diskstats_file_name_);
  if (create_target) {
    CreateSymlinkTarget(uptime_file_name_);
    CreateSymlinkTarget(diskstats_file_name_);
  }

  bootstat_log(event_name_.c_str());

  TestSymlinkTarget(uptime_file_name_, create_target);
  TestSymlinkTarget(diskstats_file_name_, create_target);

  if (create_target) {
    RemoveFile(dirname + "/" + uptime_linkname);
    RemoveFile(dirname + "/" + diskstats_linkname);
  }
}


// Reset event state back to initial conditions, by deleting the
// associated event files, and clearing the expected contents.
void EventTracker::Reset() {
  uptime_content_.clear();
  diskstats_content_.clear();
  RemoveFile(diskstats_file_name_);
  RemoveFile(uptime_file_name_);
}


// Bootstat test class.  We use this class to override the
// dependencies in bootstat_log() on the file paths for /proc/uptime
// and /sys/block/<device>/stat.
//
// The class uses test-specific interfaces that change the default
// paths from the kernel statistics psuedo-files to temporary paths
// selected by this test.  This class also redirects the location for
// the event files created by bootstat_log() to a temporary directory.
class BootstatTest : public ::testing::Test {
 protected:
    virtual void SetUp();
    virtual void TearDown();

    EventTracker MakeEvent(const string& event_name) {
      return EventTracker(event_name, uptime_event_prefix_,
                          disk_event_prefix_);
    }

    void SetMockStats(const char* uptime_content,
                         const char* disk_content);
    void ClearMockStats();
    void TestLogEvent(EventTracker& event);

    string stats_output_dir_;

 private:
    string uptime_event_prefix_;
    string disk_event_prefix_;

    string mock_uptime_file_name_;
    string mock_uptime_content_;
    string mock_disk_file_name_;
    string mock_disk_content_;
};


void BootstatTest::SetUp() {
  char dir_template[] = "bootstat_test_XXXXXX";
  stats_output_dir_ = string(mkdtemp(dir_template));
  uptime_event_prefix_ = stats_output_dir_ + "/uptime-";
  disk_event_prefix_ = stats_output_dir_ + "/disk-";
  mock_uptime_file_name_ = stats_output_dir_ + "/proc_uptime";
  mock_disk_file_name_ = stats_output_dir_ + "/block_stats";
  bootstat_set_output_directory_for_test(stats_output_dir_.c_str());
}


void BootstatTest::TearDown() {
  bootstat_set_output_directory_for_test(NULL);
  EXPECT_EQ(0, rmdir(stats_output_dir_.c_str()))
      << "BootstatTest::Teardown rmdir(): " << stats_output_dir_
      << ": " << strerror(errno) << ".";
}


static void WriteMockStats(const string& content, const string& file_path) {
  const int kFileOpenFlags = O_WRONLY | O_TRUNC | O_CREAT;
  const mode_t kFileCreationMode =
      S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
  int fd = open(file_path.c_str(), kFileOpenFlags, kFileCreationMode);
  int nwrite = write(fd, content.c_str(), content.length());
  EXPECT_EQ(content.length(), nwrite)
      << "WriteMockStats write(): " << file_path
      << ": " << strerror(errno) << ".";
  (void)close(fd);
}


// Set the content of the files mocking the contents of the kernel's
// statistics pseudo-files.  The strings provided here will be the
// ones recorded for subsequent calls to bootstat_log() for all
// events.
void BootstatTest::SetMockStats(const char* uptime_data,
                                const char* disk_data) {
  mock_uptime_content_ = string(uptime_data);
  WriteMockStats(mock_uptime_content_, mock_uptime_file_name_);
  mock_disk_content_ = string(disk_data);
  WriteMockStats(mock_disk_content_, mock_disk_file_name_);
  bootstat_set_uptime_file_name_for_test(mock_uptime_file_name_.c_str());
  bootstat_set_disk_file_name_for_test(mock_disk_file_name_.c_str());
}


// Clean up the effects from SetMockStats().
void BootstatTest::ClearMockStats() {
  bootstat_set_uptime_file_name_for_test(NULL);
  bootstat_set_disk_file_name_for_test(NULL);
  RemoveFile(mock_uptime_file_name_);
  RemoveFile(mock_disk_file_name_);
}


void BootstatTest::TestLogEvent(EventTracker& event) {
  event.TestLogEvent(mock_uptime_content_, mock_disk_content_);
}


// Test data to be used as input to SetMockStats().
//
// The structure of this array is pairs of strings, terminated by a
// single NULL.  The first string in the pair is content for
// /proc/uptime, the second for /sys/block/<device>/stat.
//
// This data is taken directly from a development system, and is
// representative of valid stats content, though not typical of what
// would be seen immediately after boot.
static const char* bootstat_data[] = {
/*  0  */
/* uptime */  "691448.42 11020440.26\n",
/*  disk  */  " 1417116    14896 55561564 10935990  4267850 78379879"
                  " 661568738 1635920520      158 17856450 1649520570\n",
/*  1  */
/* uptime */  "691623.71 11021372.99\n",
/*  disk  */  " 1420714    14918 55689988 11006390  4287385 78594261"
                  " 663441564 1651579200      152 17974280 1665255160\n",
/* EOT */     NULL
};


// Tests that event file content matches expectations when an
// event is logged multiple times.
TEST_F(BootstatTest, ContentGeneration) {
  EventTracker ev = MakeEvent(string("test_event"));
  int i = 0;
  while (bootstat_data[i] != NULL) {
    SetMockStats(bootstat_data[i], bootstat_data[i+1]);
    TestLogEvent(ev);
    i += 2;
  }
  ClearMockStats();
  ev.Reset();
}


// Tests that name truncation of logged events works as advertised.
TEST_F(BootstatTest, EventNameTruncation) {
  static const char kMostVoluminousEventName[] =
    //             16              32              48              64
    "event-6789abcdef_123456789ABCDEF.123456789abcdef0123456789abcdef"  //  64
    "=064+56789abcdef_123456789ABCDEF.123456789abcdef0123456789abcdef"  // 128
    "=128+56789abcdef_123456789ABCDEF.123456789abcdef0123456789abcdef"  // 191
    "=191+56789abcdef_123456789ABCDEF.123456789abcdef0123456789abcdef";  // 256

  string very_long(kMostVoluminousEventName);
  SetMockStats(bootstat_data[0], bootstat_data[1]);

  EventTracker ev = MakeEvent(very_long);
  TestLogEvent(ev);
  ev.Reset();
  ev = MakeEvent(very_long.substr(0, 1));
  TestLogEvent(ev);
  ev.Reset();
  ev = MakeEvent(very_long.substr(0, BOOTSTAT_MAX_EVENT_LEN - 1));
  TestLogEvent(ev);
  ev.Reset();
  ev = MakeEvent(very_long.substr(0, BOOTSTAT_MAX_EVENT_LEN));
  TestLogEvent(ev);
  ev.Reset();

  ClearMockStats();
}


// Test that event logging does not follow symbolic links.
TEST_F(BootstatTest, SymlinkFollow) {
  EventTracker ev = MakeEvent("symlink-no-follow");
  ev.TestLogSymlink(stats_output_dir_, true);
  ev.Reset();
  ev.TestLogSymlink(stats_output_dir_, false);
  ev.Reset();
}

}  // namespace

int main(int argc, char** argv) {
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}