| // 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 "bootstat/bootstat.h" |
| #include "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 <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(nullptr); |
| 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(nullptr); |
| bootstat_set_disk_file_name_for_test(nullptr); |
| 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 */ nullptr |
| }; |
| |
| |
| // 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] != nullptr) { |
| 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(); |
| } |