| // 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 <stdint.h> // to get uintptr_t |
| #elif defined HAVE_INTTYPES_H |
| #include <inttypes.h> // another place uintptr_t might be defined |
| #endif |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <string> |
| |
| #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<typename T> 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<char *>(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<void*>(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<ProfileDataSlot> 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<ssize_t>(statbuf.st_size)) |
| return "file impossibly large"; |
| ssize_t filesize = statbuf.st_size; |
| |
| scoped_array<char> 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<ProfileDataSlot*>(filedata.get())[0] != 0) |
| return "error in header: non-zero count"; |
| if (reinterpret_cast<ProfileDataSlot*>(filedata.get())[1] != 3) |
| return "error in header: num_slots != 3"; |
| if (reinterpret_cast<ProfileDataSlot*>(filedata.get())[2] != 0) |
| return "error in header: non-zero format version"; |
| // Period (slot 3) can have any value. |
| if (reinterpret_cast<ProfileDataSlot*>(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<ProfileDataSlot*>(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); |
| 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; |
| } |