// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- // Copyright (c) 2007, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // --- // Author: Chris Demetriou // // This file contains the unit tests for the ProfileData class. #if defined HAVE_STDINT_H #include // to get uintptr_t #elif defined HAVE_INTTYPES_H #include // another place uintptr_t might be defined #endif #include #include #include #include #include #include "profiledata.h" #include "base/commandlineflags.h" #include "base/logging.h" using std::string; // Some helpful macros for the test class #define TEST_F(cls, fn) void cls :: fn() namespace { template class scoped_array { public: scoped_array(T* data) : data_(data) { } ~scoped_array() { delete[] data_; } T* get() { return data_; } T& operator[](int i) { return data_[i]; } private: T* const data_; }; // Re-runs fn until it doesn't cause EINTR. #define NO_INTR(fn) do {} while ((fn) < 0 && errno == EINTR) // Read up to "count" bytes from file descriptor "fd" into the buffer // starting at "buf" while handling short reads and EINTR. On // success, return the number of bytes read. Otherwise, return -1. static ssize_t ReadPersistent(const int fd, void *buf, const size_t count) { CHECK_GE(fd, 0); char *buf0 = reinterpret_cast(buf); ssize_t num_bytes = 0; while (num_bytes < count) { ssize_t len; NO_INTR(len = read(fd, buf0 + num_bytes, count - num_bytes)); if (len < 0) { // There was an error other than EINTR. return -1; } if (len == 0) { // Reached EOF. break; } num_bytes += len; } CHECK(num_bytes <= count); return num_bytes; } // Thin wrapper around a file descriptor so that the file descriptor // gets closed for sure. struct FileDescriptor { const int fd_; explicit FileDescriptor(int fd) : fd_(fd) {} ~FileDescriptor() { if (fd_ >= 0) { NO_INTR(close(fd_)); } } int get() { return fd_; } }; // must be the same as with ProfileData::Slot. typedef uintptr_t ProfileDataSlot; // Quick and dirty function to make a number into a void* for use in a // sample. inline void* V(intptr_t x) { return reinterpret_cast(x); } // String returned by ProfileDataChecker helper functions to indicate success. const char kNoError[] = ""; class ProfileDataChecker { public: ProfileDataChecker() { const char* tmpdir = getenv("TMPDIR"); if (tmpdir == NULL) tmpdir = "/tmp"; mkdir(tmpdir, 0755); // if necessary filename_ = string(tmpdir) + "/profiledata_unittest.tmp"; } string filename() const { return filename_; } // Checks the first 'num_slots' profile data slots in the file // against the data pointed to by 'slots'. Returns kNoError if the // data matched, otherwise returns an indication of the cause of the // mismatch. string Check(const ProfileDataSlot* slots, int num_slots) { return CheckWithSkips(slots, num_slots, NULL, 0); } // Checks the first 'num_slots' profile data slots in the file // against the data pointed to by 'slots', skipping over entries // described by 'skips' and 'num_skips'. // // 'skips' must be a sorted list of (0-based) slot numbers to be // skipped, of length 'num_skips'. Note that 'num_slots' includes // any skipped slots, i.e., the first 'num_slots' profile data slots // will be considered, but some may be skipped. // // Returns kNoError if the data matched, otherwise returns an // indication of the cause of the mismatch. string CheckWithSkips(const ProfileDataSlot* slots, int num_slots, const int* skips, int num_skips); // Validate that a profile is correctly formed. The profile is // assumed to have been created by the same kind of binary (e.g., // same slot size, same endian, etc.) as is validating the profile. // // Returns kNoError if the profile appears valid, otherwise returns // an indication of the problem with the profile. string ValidateProfile(); private: string filename_; }; string ProfileDataChecker::CheckWithSkips(const ProfileDataSlot* slots, int num_slots, const int* skips, int num_skips) { FileDescriptor fd(open(filename_.c_str(), O_RDONLY)); if (fd.get() < 0) return "file open error"; scoped_array filedata(new ProfileDataSlot[num_slots]); size_t expected_bytes = num_slots * sizeof filedata[0]; ssize_t bytes_read = ReadPersistent(fd.get(), filedata.get(), expected_bytes); if (expected_bytes != bytes_read) return "file too small"; for (int i = 0; i < num_slots; i++) { if (num_skips > 0 && *skips == i) { num_skips--; skips++; continue; } if (slots[i] != filedata[i]) return "data mismatch"; } return kNoError; } string ProfileDataChecker::ValidateProfile() { FileDescriptor fd(open(filename_.c_str(), O_RDONLY)); if (fd.get() < 0) return "file open error"; struct stat statbuf; if (fstat(fd.get(), &statbuf) != 0) return "fstat error"; if (statbuf.st_size != static_cast(statbuf.st_size)) return "file impossibly large"; ssize_t filesize = statbuf.st_size; scoped_array filedata(new char[filesize]); if (ReadPersistent(fd.get(), filedata.get(), filesize) != filesize) return "read of whole file failed"; // Must have enough data for the header and the trailer. if (filesize < (5 + 3) * sizeof(ProfileDataSlot)) return "not enough data in profile for header + trailer"; // Check the header if (reinterpret_cast(filedata.get())[0] != 0) return "error in header: non-zero count"; if (reinterpret_cast(filedata.get())[1] != 3) return "error in header: num_slots != 3"; if (reinterpret_cast(filedata.get())[2] != 0) return "error in header: non-zero format version"; // Period (slot 3) can have any value. if (reinterpret_cast(filedata.get())[4] != 0) return "error in header: non-zero padding value"; ssize_t cur_offset = 5 * sizeof(ProfileDataSlot); // While there are samples, skip them. Each sample consists of // at least three slots. bool seen_trailer = false; while (!seen_trailer) { if (cur_offset > filesize - 3 * sizeof(ProfileDataSlot)) return "truncated sample header"; ProfileDataSlot* sample = reinterpret_cast(filedata.get() + cur_offset); ProfileDataSlot slots_this_sample = 2 + sample[1]; ssize_t size_this_sample = slots_this_sample * sizeof(ProfileDataSlot); if (cur_offset > filesize - size_this_sample) return "truncated sample"; if (sample[0] == 0 && sample[1] == 1 && sample[2] == 0) { seen_trailer = true; } else { if (sample[0] < 1) return "error in sample: sample count < 1"; if (sample[1] < 1) return "error in sample: num_pcs < 1"; for (int i = 2; i < slots_this_sample; i++) { if (sample[i] == 0) return "error in sample: NULL PC"; } } cur_offset += size_this_sample; } // There must be at least one line in the (text) list of mapped objects, // and it must be terminated by a newline. Note, the use of newline // here and below Might not be reasonable on non-UNIX systems. if (cur_offset >= filesize) return "no list of mapped objects"; if (filedata[filesize - 1] != '\n') return "profile did not end with a complete line"; while (cur_offset < filesize) { char* line_start = filedata.get() + cur_offset; // Find the end of the line, and replace it with a NUL for easier // scanning. char* line_end = strchr(line_start, '\n'); *line_end = '\0'; // Advance past any leading space. It's allowed in some lines, // but not in others. bool has_leading_space = false; char* line_cur = line_start; while (*line_cur == ' ') { has_leading_space = true; line_cur++; } bool found_match = false; // Check for build lines. if (!found_match) { found_match = (strncmp(line_cur, "build=", 6) == 0); // Anything may follow "build=", and leading space is allowed. } // A line from ProcMapsIterator::FormatLine, of the form: // // 40000000-40015000 r-xp 00000000 03:01 12845071 /lib/ld-2.3.2.so // // Leading space is not allowed. The filename may be omitted or // may consist of multiple words, so we scan only up to the // space before the filename. if (!found_match) { int chars_scanned = -1; sscanf(line_cur, "%*x-%*x %*c%*c%*c%*c %*x %*x:%*x %*d %n", &chars_scanned); found_match = (chars_scanned > 0 && !has_leading_space); } // A line from DumpAddressMap, of the form: // // 40000000-40015000: /lib/ld-2.3.2.so // // Leading space is allowed. The filename may be omitted or may // consist of multiple words, so we scan only up to the space // before the filename. if (!found_match) { int chars_scanned = -1; sscanf(line_cur, "%*x-%*x: %n", &chars_scanned); found_match = (chars_scanned > 0); } if (!found_match) return "unrecognized line in text section"; cur_offset += (line_end - line_start) + 1; } return kNoError; } class ProfileDataTest { protected: void ExpectStopped() { EXPECT_FALSE(collector_.enabled()); } void ExpectRunningSamples(int samples) { ProfileData::State state; collector_.GetCurrentState(&state); EXPECT_TRUE(state.enabled); EXPECT_EQ(samples, state.samples_gathered); } void ExpectSameState(const ProfileData::State& before, const ProfileData::State& after) { EXPECT_EQ(before.enabled, after.enabled); EXPECT_EQ(before.samples_gathered, after.samples_gathered); EXPECT_EQ(before.start_time, after.start_time); EXPECT_STREQ(before.profile_name, after.profile_name); } ProfileData collector_; ProfileDataChecker checker_; private: // The tests to run void OpsWhenStopped(); void StartStopEmpty(); void StartStopNoOptionsEmpty(); void StartWhenStarted(); void StartStopEmpty2(); void CollectOne(); void CollectTwoMatching(); void CollectTwoFlush(); void StartResetRestart(); public: #define RUN(test) do { \ printf("Running %s\n", #test); \ ProfileDataTest pdt; \ pdt.test(); \ } while (0) static int RUN_ALL_TESTS() { RUN(OpsWhenStopped); RUN(StartStopEmpty); RUN(StartWhenStarted); RUN(StartStopEmpty2); RUN(CollectOne); RUN(CollectTwoMatching); RUN(CollectTwoFlush); RUN(StartResetRestart); RUN(StartStopNoOptionsEmpty); return 0; } }; // Check that various operations are safe when stopped. TEST_F(ProfileDataTest, OpsWhenStopped) { ExpectStopped(); EXPECT_FALSE(collector_.enabled()); // Verify that state is disabled, all-empty/all-0 ProfileData::State state_before; collector_.GetCurrentState(&state_before); EXPECT_FALSE(state_before.enabled); EXPECT_EQ(0, state_before.samples_gathered); EXPECT_EQ(0, state_before.start_time); EXPECT_STREQ("", state_before.profile_name); // Safe to call stop again. collector_.Stop(); // Safe to call FlushTable. collector_.FlushTable(); // Safe to call Add. const void *trace[] = { V(100), V(101), V(102), V(103), V(104) }; collector_.Add(arraysize(trace), trace); ProfileData::State state_after; collector_.GetCurrentState(&state_after); ExpectSameState(state_before, state_after); } // Start and Stop, collecting no samples. Verify output contents. TEST_F(ProfileDataTest, StartStopEmpty) { const int frequency = 1; ProfileDataSlot slots[] = { 0, 3, 0, 1000000 / frequency, 0, // binary header 0, 1, 0 // binary trailer }; ExpectStopped(); ProfileData::Options options; options.set_frequency(frequency); EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); ExpectRunningSamples(0); collector_.Stop(); ExpectStopped(); EXPECT_EQ(kNoError, checker_.ValidateProfile()); EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); } // Start and Stop with no options, collecting no samples. Verify // output contents. TEST_F(ProfileDataTest, StartStopNoOptionsEmpty) { // We're not requesting a specific period, implementation can do // whatever it likes. ProfileDataSlot slots[] = { 0, 3, 0, 0 /* skipped */, 0, // binary header 0, 1, 0 // binary trailer }; int slots_to_skip[] = { 3 }; ExpectStopped(); EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), ProfileData::Options())); ExpectRunningSamples(0); collector_.Stop(); ExpectStopped(); EXPECT_EQ(kNoError, checker_.ValidateProfile()); EXPECT_EQ(kNoError, checker_.CheckWithSkips(slots, arraysize(slots), slots_to_skip, arraysize(slots_to_skip))); } // Start after already started. Should return false and not impact // collected data or state. TEST_F(ProfileDataTest, StartWhenStarted) { const int frequency = 1; ProfileDataSlot slots[] = { 0, 3, 0, 1000000 / frequency, 0, // binary header 0, 1, 0 // binary trailer }; ProfileData::Options options; options.set_frequency(frequency); EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); ProfileData::State state_before; collector_.GetCurrentState(&state_before); options.set_frequency(frequency * 2); CHECK(!collector_.Start("foobar", options)); ProfileData::State state_after; collector_.GetCurrentState(&state_after); ExpectSameState(state_before, state_after); collector_.Stop(); ExpectStopped(); EXPECT_EQ(kNoError, checker_.ValidateProfile()); EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); } // Like StartStopEmpty, but uses a different file name and frequency. TEST_F(ProfileDataTest, StartStopEmpty2) { const int frequency = 2; ProfileDataSlot slots[] = { 0, 3, 0, 1000000 / frequency, 0, // binary header 0, 1, 0 // binary trailer }; ExpectStopped(); ProfileData::Options options; options.set_frequency(frequency); EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); ExpectRunningSamples(0); collector_.Stop(); ExpectStopped(); EXPECT_EQ(kNoError, checker_.ValidateProfile()); EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); } TEST_F(ProfileDataTest, CollectOne) { const int frequency = 2; ProfileDataSlot slots[] = { 0, 3, 0, 1000000 / frequency, 0, // binary header 1, 5, 100, 101, 102, 103, 104, // our sample 0, 1, 0 // binary trailer }; ExpectStopped(); ProfileData::Options options; options.set_frequency(frequency); EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); ExpectRunningSamples(0); const void *trace[] = { V(100), V(101), V(102), V(103), V(104) }; collector_.Add(arraysize(trace), trace); ExpectRunningSamples(1); collector_.Stop(); ExpectStopped(); EXPECT_EQ(kNoError, checker_.ValidateProfile()); EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); } TEST_F(ProfileDataTest, CollectTwoMatching) { const int frequency = 2; ProfileDataSlot slots[] = { 0, 3, 0, 1000000 / frequency, 0, // binary header 2, 5, 100, 201, 302, 403, 504, // our two samples 0, 1, 0 // binary trailer }; ExpectStopped(); ProfileData::Options options; options.set_frequency(frequency); EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); ExpectRunningSamples(0); for (int i = 0; i < 2; ++i) { const void *trace[] = { V(100), V(201), V(302), V(403), V(504) }; collector_.Add(arraysize(trace), trace); ExpectRunningSamples(i + 1); } collector_.Stop(); ExpectStopped(); EXPECT_EQ(kNoError, checker_.ValidateProfile()); EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); } TEST_F(ProfileDataTest, CollectTwoFlush) { const int frequency = 2; ProfileDataSlot slots[] = { 0, 3, 0, 1000000 / frequency, 0, // binary header 1, 5, 100, 201, 302, 403, 504, // first sample (flushed) 1, 5, 100, 201, 302, 403, 504, // second identical sample 0, 1, 0 // binary trailer }; ExpectStopped(); ProfileData::Options options; options.set_frequency(frequency); EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); ExpectRunningSamples(0); const void *trace[] = { V(100), V(201), V(302), V(403), V(504) }; collector_.Add(arraysize(trace), trace); ExpectRunningSamples(1); collector_.FlushTable(); collector_.Add(arraysize(trace), trace); ExpectRunningSamples(2); collector_.Stop(); ExpectStopped(); EXPECT_EQ(kNoError, checker_.ValidateProfile()); EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); } // Start then reset, verify that the result is *not* a valid profile. // Then start again and make sure the result is OK. TEST_F(ProfileDataTest, StartResetRestart) { ExpectStopped(); ProfileData::Options options; options.set_frequency(1); EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); ExpectRunningSamples(0); collector_.Reset(); ExpectStopped(); // We expect the resulting file to be empty. This is a minimal test // of ValidateProfile. EXPECT_NE(kNoError, checker_.ValidateProfile()); struct stat statbuf; EXPECT_EQ(0, stat(checker_.filename().c_str(), &statbuf)); EXPECT_EQ(0, statbuf.st_size); const int frequency = 2; // Different frequency than used above. ProfileDataSlot slots[] = { 0, 3, 0, 1000000 / frequency, 0, // binary header 0, 1, 0 // binary trailer }; options.set_frequency(frequency); EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); ExpectRunningSamples(0); collector_.Stop(); ExpectStopped(); EXPECT_EQ(kNoError, checker_.ValidateProfile()); EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); } } // namespace int main(int argc, char** argv) { int rc = ProfileDataTest::RUN_ALL_TESTS(); printf("%s\n", rc == 0 ? "PASS" : "FAIL"); return rc; }