1// Copyright (c) 2007, Google Inc. 2// All rights reserved. 3// 4// Redistribution and use in source and binary forms, with or without 5// modification, are permitted provided that the following conditions are 6// met: 7// 8// * Redistributions of source code must retain the above copyright 9// notice, this list of conditions and the following disclaimer. 10// * Redistributions in binary form must reproduce the above 11// copyright notice, this list of conditions and the following disclaimer 12// in the documentation and/or other materials provided with the 13// distribution. 14// * Neither the name of Google Inc. nor the names of its 15// contributors may be used to endorse or promote products derived from 16// this software without specific prior written permission. 17// 18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29// 30// --- 31// Author: Chris Demetriou 32// 33// This file contains the unit tests for the ProfileData class. 34 35#if defined HAVE_STDINT_H 36#include <stdint.h> // to get uintptr_t 37#elif defined HAVE_INTTYPES_H 38#include <inttypes.h> // another place uintptr_t might be defined 39#endif 40#include <sys/stat.h> 41#include <sys/types.h> 42#include <fcntl.h> 43#include <string.h> 44#include <string> 45 46#include "profiledata.h" 47 48#include "base/commandlineflags.h" 49#include "base/logging.h" 50 51using std::string; 52 53// Some helpful macros for the test class 54#define TEST_F(cls, fn) void cls :: fn() 55 56namespace { 57 58template<typename T> class scoped_array { 59 public: 60 scoped_array(T* data) : data_(data) { } 61 ~scoped_array() { delete[] data_; } 62 T* get() { return data_; } 63 T& operator[](int i) { return data_[i]; } 64 private: 65 T* const data_; 66}; 67 68// Re-runs fn until it doesn't cause EINTR. 69#define NO_INTR(fn) do {} while ((fn) < 0 && errno == EINTR) 70 71// Read up to "count" bytes from file descriptor "fd" into the buffer 72// starting at "buf" while handling short reads and EINTR. On 73// success, return the number of bytes read. Otherwise, return -1. 74static ssize_t ReadPersistent(const int fd, void *buf, const size_t count) { 75 CHECK_GE(fd, 0); 76 char *buf0 = reinterpret_cast<char *>(buf); 77 ssize_t num_bytes = 0; 78 while (num_bytes < count) { 79 ssize_t len; 80 NO_INTR(len = read(fd, buf0 + num_bytes, count - num_bytes)); 81 if (len < 0) { // There was an error other than EINTR. 82 return -1; 83 } 84 if (len == 0) { // Reached EOF. 85 break; 86 } 87 num_bytes += len; 88 } 89 CHECK(num_bytes <= count); 90 return num_bytes; 91} 92 93// Thin wrapper around a file descriptor so that the file descriptor 94// gets closed for sure. 95struct FileDescriptor { 96 const int fd_; 97 explicit FileDescriptor(int fd) : fd_(fd) {} 98 ~FileDescriptor() { 99 if (fd_ >= 0) { 100 NO_INTR(close(fd_)); 101 } 102 } 103 int get() { return fd_; } 104}; 105 106// must be the same as with ProfileData::Slot. 107typedef uintptr_t ProfileDataSlot; 108 109// Quick and dirty function to make a number into a void* for use in a 110// sample. 111inline void* V(intptr_t x) { return reinterpret_cast<void*>(x); } 112 113// String returned by ProfileDataChecker helper functions to indicate success. 114const char kNoError[] = ""; 115 116class ProfileDataChecker { 117 public: 118 ProfileDataChecker() { 119 const char* tmpdir = getenv("TMPDIR"); 120 if (tmpdir == NULL) 121 tmpdir = "/tmp"; 122 mkdir(tmpdir, 0755); // if necessary 123 filename_ = string(tmpdir) + "/profiledata_unittest.tmp"; 124 } 125 126 string filename() const { return filename_; } 127 128 // Checks the first 'num_slots' profile data slots in the file 129 // against the data pointed to by 'slots'. Returns kNoError if the 130 // data matched, otherwise returns an indication of the cause of the 131 // mismatch. 132 string Check(const ProfileDataSlot* slots, int num_slots) { 133 return CheckWithSkips(slots, num_slots, NULL, 0); 134 } 135 136 // Checks the first 'num_slots' profile data slots in the file 137 // against the data pointed to by 'slots', skipping over entries 138 // described by 'skips' and 'num_skips'. 139 // 140 // 'skips' must be a sorted list of (0-based) slot numbers to be 141 // skipped, of length 'num_skips'. Note that 'num_slots' includes 142 // any skipped slots, i.e., the first 'num_slots' profile data slots 143 // will be considered, but some may be skipped. 144 // 145 // Returns kNoError if the data matched, otherwise returns an 146 // indication of the cause of the mismatch. 147 string CheckWithSkips(const ProfileDataSlot* slots, int num_slots, 148 const int* skips, int num_skips); 149 150 // Validate that a profile is correctly formed. The profile is 151 // assumed to have been created by the same kind of binary (e.g., 152 // same slot size, same endian, etc.) as is validating the profile. 153 // 154 // Returns kNoError if the profile appears valid, otherwise returns 155 // an indication of the problem with the profile. 156 string ValidateProfile(); 157 158 private: 159 string filename_; 160}; 161 162string ProfileDataChecker::CheckWithSkips(const ProfileDataSlot* slots, 163 int num_slots, const int* skips, 164 int num_skips) { 165 FileDescriptor fd(open(filename_.c_str(), O_RDONLY)); 166 if (fd.get() < 0) 167 return "file open error"; 168 169 scoped_array<ProfileDataSlot> filedata(new ProfileDataSlot[num_slots]); 170 size_t expected_bytes = num_slots * sizeof filedata[0]; 171 ssize_t bytes_read = ReadPersistent(fd.get(), filedata.get(), expected_bytes); 172 if (expected_bytes != bytes_read) 173 return "file too small"; 174 175 for (int i = 0; i < num_slots; i++) { 176 if (num_skips > 0 && *skips == i) { 177 num_skips--; 178 skips++; 179 continue; 180 } 181 if (slots[i] != filedata[i]) 182 return "data mismatch"; 183 } 184 return kNoError; 185} 186 187string ProfileDataChecker::ValidateProfile() { 188 FileDescriptor fd(open(filename_.c_str(), O_RDONLY)); 189 if (fd.get() < 0) 190 return "file open error"; 191 192 struct stat statbuf; 193 if (fstat(fd.get(), &statbuf) != 0) 194 return "fstat error"; 195 if (statbuf.st_size != static_cast<ssize_t>(statbuf.st_size)) 196 return "file impossibly large"; 197 ssize_t filesize = statbuf.st_size; 198 199 scoped_array<char> filedata(new char[filesize]); 200 if (ReadPersistent(fd.get(), filedata.get(), filesize) != filesize) 201 return "read of whole file failed"; 202 203 // Must have enough data for the header and the trailer. 204 if (filesize < (5 + 3) * sizeof(ProfileDataSlot)) 205 return "not enough data in profile for header + trailer"; 206 207 // Check the header 208 if (reinterpret_cast<ProfileDataSlot*>(filedata.get())[0] != 0) 209 return "error in header: non-zero count"; 210 if (reinterpret_cast<ProfileDataSlot*>(filedata.get())[1] != 3) 211 return "error in header: num_slots != 3"; 212 if (reinterpret_cast<ProfileDataSlot*>(filedata.get())[2] != 0) 213 return "error in header: non-zero format version"; 214 // Period (slot 3) can have any value. 215 if (reinterpret_cast<ProfileDataSlot*>(filedata.get())[4] != 0) 216 return "error in header: non-zero padding value"; 217 ssize_t cur_offset = 5 * sizeof(ProfileDataSlot); 218 219 // While there are samples, skip them. Each sample consists of 220 // at least three slots. 221 bool seen_trailer = false; 222 while (!seen_trailer) { 223 if (cur_offset > filesize - 3 * sizeof(ProfileDataSlot)) 224 return "truncated sample header"; 225 ProfileDataSlot* sample = 226 reinterpret_cast<ProfileDataSlot*>(filedata.get() + cur_offset); 227 ProfileDataSlot slots_this_sample = 2 + sample[1]; 228 ssize_t size_this_sample = slots_this_sample * sizeof(ProfileDataSlot); 229 if (cur_offset > filesize - size_this_sample) 230 return "truncated sample"; 231 232 if (sample[0] == 0 && sample[1] == 1 && sample[2] == 0) { 233 seen_trailer = true; 234 } else { 235 if (sample[0] < 1) 236 return "error in sample: sample count < 1"; 237 if (sample[1] < 1) 238 return "error in sample: num_pcs < 1"; 239 for (int i = 2; i < slots_this_sample; i++) { 240 if (sample[i] == 0) 241 return "error in sample: NULL PC"; 242 } 243 } 244 cur_offset += size_this_sample; 245 } 246 247 // There must be at least one line in the (text) list of mapped objects, 248 // and it must be terminated by a newline. Note, the use of newline 249 // here and below Might not be reasonable on non-UNIX systems. 250 if (cur_offset >= filesize) 251 return "no list of mapped objects"; 252 if (filedata[filesize - 1] != '\n') 253 return "profile did not end with a complete line"; 254 255 while (cur_offset < filesize) { 256 char* line_start = filedata.get() + cur_offset; 257 258 // Find the end of the line, and replace it with a NUL for easier 259 // scanning. 260 char* line_end = strchr(line_start, '\n'); 261 *line_end = '\0'; 262 263 // Advance past any leading space. It's allowed in some lines, 264 // but not in others. 265 bool has_leading_space = false; 266 char* line_cur = line_start; 267 while (*line_cur == ' ') { 268 has_leading_space = true; 269 line_cur++; 270 } 271 272 bool found_match = false; 273 274 // Check for build lines. 275 if (!found_match) { 276 found_match = (strncmp(line_cur, "build=", 6) == 0); 277 // Anything may follow "build=", and leading space is allowed. 278 } 279 280 // A line from ProcMapsIterator::FormatLine, of the form: 281 // 282 // 40000000-40015000 r-xp 00000000 03:01 12845071 /lib/ld-2.3.2.so 283 // 284 // Leading space is not allowed. The filename may be omitted or 285 // may consist of multiple words, so we scan only up to the 286 // space before the filename. 287 if (!found_match) { 288 int chars_scanned = -1; 289 sscanf(line_cur, "%*x-%*x %*c%*c%*c%*c %*x %*x:%*x %*d %n", 290 &chars_scanned); 291 found_match = (chars_scanned > 0 && !has_leading_space); 292 } 293 294 // A line from DumpAddressMap, of the form: 295 // 296 // 40000000-40015000: /lib/ld-2.3.2.so 297 // 298 // Leading space is allowed. The filename may be omitted or may 299 // consist of multiple words, so we scan only up to the space 300 // before the filename. 301 if (!found_match) { 302 int chars_scanned = -1; 303 sscanf(line_cur, "%*x-%*x: %n", &chars_scanned); 304 found_match = (chars_scanned > 0); 305 } 306 307 if (!found_match) 308 return "unrecognized line in text section"; 309 310 cur_offset += (line_end - line_start) + 1; 311 } 312 313 return kNoError; 314} 315 316class ProfileDataTest { 317 protected: 318 void ExpectStopped() { 319 EXPECT_FALSE(collector_.enabled()); 320 } 321 322 void ExpectRunningSamples(int samples) { 323 ProfileData::State state; 324 collector_.GetCurrentState(&state); 325 EXPECT_TRUE(state.enabled); 326 EXPECT_EQ(samples, state.samples_gathered); 327 } 328 329 void ExpectSameState(const ProfileData::State& before, 330 const ProfileData::State& after) { 331 EXPECT_EQ(before.enabled, after.enabled); 332 EXPECT_EQ(before.samples_gathered, after.samples_gathered); 333 EXPECT_EQ(before.start_time, after.start_time); 334 EXPECT_STREQ(before.profile_name, after.profile_name); 335 } 336 337 ProfileData collector_; 338 ProfileDataChecker checker_; 339 340 private: 341 // The tests to run 342 void OpsWhenStopped(); 343 void StartStopEmpty(); 344 void StartStopNoOptionsEmpty(); 345 void StartWhenStarted(); 346 void StartStopEmpty2(); 347 void CollectOne(); 348 void CollectTwoMatching(); 349 void CollectTwoFlush(); 350 void StartResetRestart(); 351 352 public: 353#define RUN(test) do { \ 354 printf("Running %s\n", #test); \ 355 ProfileDataTest pdt; \ 356 pdt.test(); \ 357} while (0) 358 359 static int RUN_ALL_TESTS() { 360 RUN(OpsWhenStopped); 361 RUN(StartStopEmpty); 362 RUN(StartWhenStarted); 363 RUN(StartStopEmpty2); 364 RUN(CollectOne); 365 RUN(CollectTwoMatching); 366 RUN(CollectTwoFlush); 367 RUN(StartResetRestart); 368 return 0; 369 } 370}; 371 372// Check that various operations are safe when stopped. 373TEST_F(ProfileDataTest, OpsWhenStopped) { 374 ExpectStopped(); 375 EXPECT_FALSE(collector_.enabled()); 376 377 // Verify that state is disabled, all-empty/all-0 378 ProfileData::State state_before; 379 collector_.GetCurrentState(&state_before); 380 EXPECT_FALSE(state_before.enabled); 381 EXPECT_EQ(0, state_before.samples_gathered); 382 EXPECT_EQ(0, state_before.start_time); 383 EXPECT_STREQ("", state_before.profile_name); 384 385 // Safe to call stop again. 386 collector_.Stop(); 387 388 // Safe to call FlushTable. 389 collector_.FlushTable(); 390 391 // Safe to call Add. 392 const void *trace[] = { V(100), V(101), V(102), V(103), V(104) }; 393 collector_.Add(arraysize(trace), trace); 394 395 ProfileData::State state_after; 396 collector_.GetCurrentState(&state_after); 397 398 ExpectSameState(state_before, state_after); 399} 400 401// Start and Stop, collecting no samples. Verify output contents. 402TEST_F(ProfileDataTest, StartStopEmpty) { 403 const int frequency = 1; 404 ProfileDataSlot slots[] = { 405 0, 3, 0, 1000000 / frequency, 0, // binary header 406 0, 1, 0 // binary trailer 407 }; 408 409 ExpectStopped(); 410 ProfileData::Options options; 411 options.set_frequency(frequency); 412 EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); 413 ExpectRunningSamples(0); 414 collector_.Stop(); 415 ExpectStopped(); 416 EXPECT_EQ(kNoError, checker_.ValidateProfile()); 417 EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); 418} 419 420// Start and Stop with no options, collecting no samples. Verify 421// output contents. 422TEST_F(ProfileDataTest, StartStopNoOptionsEmpty) { 423 // We're not requesting a specific period, implementation can do 424 // whatever it likes. 425 ProfileDataSlot slots[] = { 426 0, 3, 0, 0 /* skipped */, 0, // binary header 427 0, 1, 0 // binary trailer 428 }; 429 int slots_to_skip[] = { 3 }; 430 431 ExpectStopped(); 432 EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), 433 ProfileData::Options())); 434 ExpectRunningSamples(0); 435 collector_.Stop(); 436 ExpectStopped(); 437 EXPECT_EQ(kNoError, checker_.ValidateProfile()); 438 EXPECT_EQ(kNoError, checker_.CheckWithSkips(slots, arraysize(slots), 439 slots_to_skip, 440 arraysize(slots_to_skip))); 441} 442 443// Start after already started. Should return false and not impact 444// collected data or state. 445TEST_F(ProfileDataTest, StartWhenStarted) { 446 const int frequency = 1; 447 ProfileDataSlot slots[] = { 448 0, 3, 0, 1000000 / frequency, 0, // binary header 449 0, 1, 0 // binary trailer 450 }; 451 452 ProfileData::Options options; 453 options.set_frequency(frequency); 454 EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); 455 456 ProfileData::State state_before; 457 collector_.GetCurrentState(&state_before); 458 459 options.set_frequency(frequency * 2); 460 CHECK(!collector_.Start("foobar", options)); 461 462 ProfileData::State state_after; 463 collector_.GetCurrentState(&state_after); 464 ExpectSameState(state_before, state_after); 465 466 collector_.Stop(); 467 ExpectStopped(); 468 EXPECT_EQ(kNoError, checker_.ValidateProfile()); 469 EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); 470} 471 472// Like StartStopEmpty, but uses a different file name and frequency. 473TEST_F(ProfileDataTest, StartStopEmpty2) { 474 const int frequency = 2; 475 ProfileDataSlot slots[] = { 476 0, 3, 0, 1000000 / frequency, 0, // binary header 477 0, 1, 0 // binary trailer 478 }; 479 480 ExpectStopped(); 481 ProfileData::Options options; 482 options.set_frequency(frequency); 483 EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); 484 ExpectRunningSamples(0); 485 collector_.Stop(); 486 ExpectStopped(); 487 EXPECT_EQ(kNoError, checker_.ValidateProfile()); 488 EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); 489} 490 491TEST_F(ProfileDataTest, CollectOne) { 492 const int frequency = 2; 493 ProfileDataSlot slots[] = { 494 0, 3, 0, 1000000 / frequency, 0, // binary header 495 1, 5, 100, 101, 102, 103, 104, // our sample 496 0, 1, 0 // binary trailer 497 }; 498 499 ExpectStopped(); 500 ProfileData::Options options; 501 options.set_frequency(frequency); 502 EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); 503 ExpectRunningSamples(0); 504 505 const void *trace[] = { V(100), V(101), V(102), V(103), V(104) }; 506 collector_.Add(arraysize(trace), trace); 507 ExpectRunningSamples(1); 508 509 collector_.Stop(); 510 ExpectStopped(); 511 EXPECT_EQ(kNoError, checker_.ValidateProfile()); 512 EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); 513} 514 515TEST_F(ProfileDataTest, CollectTwoMatching) { 516 const int frequency = 2; 517 ProfileDataSlot slots[] = { 518 0, 3, 0, 1000000 / frequency, 0, // binary header 519 2, 5, 100, 201, 302, 403, 504, // our two samples 520 0, 1, 0 // binary trailer 521 }; 522 523 ExpectStopped(); 524 ProfileData::Options options; 525 options.set_frequency(frequency); 526 EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); 527 ExpectRunningSamples(0); 528 529 for (int i = 0; i < 2; ++i) { 530 const void *trace[] = { V(100), V(201), V(302), V(403), V(504) }; 531 collector_.Add(arraysize(trace), trace); 532 ExpectRunningSamples(i + 1); 533 } 534 535 collector_.Stop(); 536 ExpectStopped(); 537 EXPECT_EQ(kNoError, checker_.ValidateProfile()); 538 EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); 539} 540 541TEST_F(ProfileDataTest, CollectTwoFlush) { 542 const int frequency = 2; 543 ProfileDataSlot slots[] = { 544 0, 3, 0, 1000000 / frequency, 0, // binary header 545 1, 5, 100, 201, 302, 403, 504, // first sample (flushed) 546 1, 5, 100, 201, 302, 403, 504, // second identical sample 547 0, 1, 0 // binary trailer 548 }; 549 550 ExpectStopped(); 551 ProfileData::Options options; 552 options.set_frequency(frequency); 553 EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); 554 ExpectRunningSamples(0); 555 556 const void *trace[] = { V(100), V(201), V(302), V(403), V(504) }; 557 558 collector_.Add(arraysize(trace), trace); 559 ExpectRunningSamples(1); 560 collector_.FlushTable(); 561 562 collector_.Add(arraysize(trace), trace); 563 ExpectRunningSamples(2); 564 565 collector_.Stop(); 566 ExpectStopped(); 567 EXPECT_EQ(kNoError, checker_.ValidateProfile()); 568 EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); 569} 570 571// Start then reset, verify that the result is *not* a valid profile. 572// Then start again and make sure the result is OK. 573TEST_F(ProfileDataTest, StartResetRestart) { 574 ExpectStopped(); 575 ProfileData::Options options; 576 options.set_frequency(1); 577 EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); 578 ExpectRunningSamples(0); 579 collector_.Reset(); 580 ExpectStopped(); 581 // We expect the resulting file to be empty. This is a minimal test 582 // of ValidateProfile. 583 EXPECT_NE(kNoError, checker_.ValidateProfile()); 584 585 struct stat statbuf; 586 EXPECT_EQ(0, stat(checker_.filename().c_str(), &statbuf)); 587 EXPECT_EQ(0, statbuf.st_size); 588 589 const int frequency = 2; // Different frequency than used above. 590 ProfileDataSlot slots[] = { 591 0, 3, 0, 1000000 / frequency, 0, // binary header 592 0, 1, 0 // binary trailer 593 }; 594 595 options.set_frequency(frequency); 596 EXPECT_TRUE(collector_.Start(checker_.filename().c_str(), options)); 597 ExpectRunningSamples(0); 598 collector_.Stop(); 599 ExpectStopped(); 600 EXPECT_EQ(kNoError, checker_.ValidateProfile()); 601 EXPECT_EQ(kNoError, checker_.Check(slots, arraysize(slots))); 602} 603 604} // namespace 605 606int main(int argc, char** argv) { 607 int rc = ProfileDataTest::RUN_ALL_TESTS(); 608 printf("%s\n", rc == 0 ? "PASS" : "FAIL"); 609 return rc; 610} 611