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