1 // Copyright (c) 2007, Google Inc.
2 // All rights reserved.
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
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
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.
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.
31 // Author: Chris Demetriou
33 // This file contains the unit tests for the ProfileData class.
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
41 #include <sys/types.h>
46 #include "profiledata.h"
48 #include "base/commandlineflags.h"
49 #include "base/logging.h"
53 // Some helpful macros for the test class
54 #define TEST_F(cls, fn) void cls :: fn()
58 template<typename T
> class scoped_array
{
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
]; }
68 // Re-runs fn until it doesn't cause EINTR.
69 #define NO_INTR(fn) do {} while ((fn) < 0 && errno == EINTR)
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.
74 static ssize_t
ReadPersistent(const int fd
, void *buf
, const size_t count
) {
76 char *buf0
= reinterpret_cast<char *>(buf
);
77 ssize_t num_bytes
= 0;
78 while (num_bytes
< count
) {
80 NO_INTR(len
= read(fd
, buf0
+ num_bytes
, count
- num_bytes
));
81 if (len
< 0) { // There was an error other than EINTR.
84 if (len
== 0) { // Reached EOF.
89 CHECK(num_bytes
<= count
);
93 // Thin wrapper around a file descriptor so that the file descriptor
94 // gets closed for sure.
95 struct FileDescriptor
{
97 explicit FileDescriptor(int fd
) : fd_(fd
) {}
103 int get() { return fd_
; }
106 // must be the same as with ProfileData::Slot.
107 typedef uintptr_t ProfileDataSlot
;
109 // Quick and dirty function to make a number into a void* for use in a
111 inline void* V(intptr_t x
) { return reinterpret_cast<void*>(x
); }
113 // String returned by ProfileDataChecker helper functions to indicate success.
114 const char kNoError
[] = "";
116 class ProfileDataChecker
{
118 ProfileDataChecker() {
119 const char* tmpdir
= getenv("TMPDIR");
122 mkdir(tmpdir
, 0755); // if necessary
123 filename_
= string(tmpdir
) + "/profiledata_unittest.tmp";
126 string
filename() const { return filename_
; }
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
132 string
Check(const ProfileDataSlot
* slots
, int num_slots
) {
133 return CheckWithSkips(slots
, num_slots
, NULL
, 0);
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'.
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.
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
);
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.
154 // Returns kNoError if the profile appears valid, otherwise returns
155 // an indication of the problem with the profile.
156 string
ValidateProfile();
162 string
ProfileDataChecker::CheckWithSkips(const ProfileDataSlot
* slots
,
163 int num_slots
, const int* skips
,
165 FileDescriptor
fd(open(filename_
.c_str(), O_RDONLY
));
167 return "file open error";
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";
175 for (int i
= 0; i
< num_slots
; i
++) {
176 if (num_skips
> 0 && *skips
== i
) {
181 if (slots
[i
] != filedata
[i
])
182 return "data mismatch";
187 string
ProfileDataChecker::ValidateProfile() {
188 FileDescriptor
fd(open(filename_
.c_str(), O_RDONLY
));
190 return "file open error";
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
;
199 scoped_array
<char> filedata(new char[filesize
]);
200 if (ReadPersistent(fd
.get(), filedata
.get(), filesize
) != filesize
)
201 return "read of whole file failed";
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";
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
);
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";
232 if (sample
[0] == 0 && sample
[1] == 1 && sample
[2] == 0) {
236 return "error in sample: sample count < 1";
238 return "error in sample: num_pcs < 1";
239 for (int i
= 2; i
< slots_this_sample
; i
++) {
241 return "error in sample: NULL PC";
244 cur_offset
+= size_this_sample
;
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";
255 while (cur_offset
< filesize
) {
256 char* line_start
= filedata
.get() + cur_offset
;
258 // Find the end of the line, and replace it with a NUL for easier
260 char* line_end
= strchr(line_start
, '\n');
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;
272 bool found_match
= false;
274 // Check for build lines.
276 found_match
= (strncmp(line_cur
, "build=", 6) == 0);
277 // Anything may follow "build=", and leading space is allowed.
280 // A line from ProcMapsIterator::FormatLine, of the form:
282 // 40000000-40015000 r-xp 00000000 03:01 12845071 /lib/ld-2.3.2.so
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.
288 int chars_scanned
= -1;
289 sscanf(line_cur
, "%*x-%*x %*c%*c%*c%*c %*x %*x:%*x %*d %n",
291 found_match
= (chars_scanned
> 0 && !has_leading_space
);
294 // A line from DumpAddressMap, of the form:
296 // 40000000-40015000: /lib/ld-2.3.2.so
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.
302 int chars_scanned
= -1;
303 sscanf(line_cur
, "%*x-%*x: %n", &chars_scanned
);
304 found_match
= (chars_scanned
> 0);
308 return "unrecognized line in text section";
310 cur_offset
+= (line_end
- line_start
) + 1;
316 class ProfileDataTest
{
318 void ExpectStopped() {
319 EXPECT_FALSE(collector_
.enabled());
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
);
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
);
337 ProfileData collector_
;
338 ProfileDataChecker checker_
;
342 void OpsWhenStopped();
343 void StartStopEmpty();
344 void StartStopNoOptionsEmpty();
345 void StartWhenStarted();
346 void StartStopEmpty2();
348 void CollectTwoMatching();
349 void CollectTwoFlush();
350 void StartResetRestart();
353 #define RUN(test) do { \
354 printf("Running %s\n", #test); \
355 ProfileDataTest pdt; \
359 static int RUN_ALL_TESTS() {
362 RUN(StartWhenStarted
);
363 RUN(StartStopEmpty2
);
365 RUN(CollectTwoMatching
);
366 RUN(CollectTwoFlush
);
367 RUN(StartResetRestart
);
372 // Check that various operations are safe when stopped.
373 TEST_F(ProfileDataTest
, OpsWhenStopped
) {
375 EXPECT_FALSE(collector_
.enabled());
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
);
385 // Safe to call stop again.
388 // Safe to call FlushTable.
389 collector_
.FlushTable();
392 const void *trace
[] = { V(100), V(101), V(102), V(103), V(104) };
393 collector_
.Add(arraysize(trace
), trace
);
395 ProfileData::State state_after
;
396 collector_
.GetCurrentState(&state_after
);
398 ExpectSameState(state_before
, state_after
);
401 // Start and Stop, collecting no samples. Verify output contents.
402 TEST_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
410 ProfileData::Options options
;
411 options
.set_frequency(frequency
);
412 EXPECT_TRUE(collector_
.Start(checker_
.filename().c_str(), options
));
413 ExpectRunningSamples(0);
416 EXPECT_EQ(kNoError
, checker_
.ValidateProfile());
417 EXPECT_EQ(kNoError
, checker_
.Check(slots
, arraysize(slots
)));
420 // Start and Stop with no options, collecting no samples. Verify
422 TEST_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
429 int slots_to_skip
[] = { 3 };
432 EXPECT_TRUE(collector_
.Start(checker_
.filename().c_str(),
433 ProfileData::Options()));
434 ExpectRunningSamples(0);
437 EXPECT_EQ(kNoError
, checker_
.ValidateProfile());
438 EXPECT_EQ(kNoError
, checker_
.CheckWithSkips(slots
, arraysize(slots
),
440 arraysize(slots_to_skip
)));
443 // Start after already started. Should return false and not impact
444 // collected data or state.
445 TEST_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
452 ProfileData::Options options
;
453 options
.set_frequency(frequency
);
454 EXPECT_TRUE(collector_
.Start(checker_
.filename().c_str(), options
));
456 ProfileData::State state_before
;
457 collector_
.GetCurrentState(&state_before
);
459 options
.set_frequency(frequency
* 2);
460 CHECK(!collector_
.Start("foobar", options
));
462 ProfileData::State state_after
;
463 collector_
.GetCurrentState(&state_after
);
464 ExpectSameState(state_before
, state_after
);
468 EXPECT_EQ(kNoError
, checker_
.ValidateProfile());
469 EXPECT_EQ(kNoError
, checker_
.Check(slots
, arraysize(slots
)));
472 // Like StartStopEmpty, but uses a different file name and frequency.
473 TEST_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
481 ProfileData::Options options
;
482 options
.set_frequency(frequency
);
483 EXPECT_TRUE(collector_
.Start(checker_
.filename().c_str(), options
));
484 ExpectRunningSamples(0);
487 EXPECT_EQ(kNoError
, checker_
.ValidateProfile());
488 EXPECT_EQ(kNoError
, checker_
.Check(slots
, arraysize(slots
)));
491 TEST_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
500 ProfileData::Options options
;
501 options
.set_frequency(frequency
);
502 EXPECT_TRUE(collector_
.Start(checker_
.filename().c_str(), options
));
503 ExpectRunningSamples(0);
505 const void *trace
[] = { V(100), V(101), V(102), V(103), V(104) };
506 collector_
.Add(arraysize(trace
), trace
);
507 ExpectRunningSamples(1);
511 EXPECT_EQ(kNoError
, checker_
.ValidateProfile());
512 EXPECT_EQ(kNoError
, checker_
.Check(slots
, arraysize(slots
)));
515 TEST_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
524 ProfileData::Options options
;
525 options
.set_frequency(frequency
);
526 EXPECT_TRUE(collector_
.Start(checker_
.filename().c_str(), options
));
527 ExpectRunningSamples(0);
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);
537 EXPECT_EQ(kNoError
, checker_
.ValidateProfile());
538 EXPECT_EQ(kNoError
, checker_
.Check(slots
, arraysize(slots
)));
541 TEST_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
551 ProfileData::Options options
;
552 options
.set_frequency(frequency
);
553 EXPECT_TRUE(collector_
.Start(checker_
.filename().c_str(), options
));
554 ExpectRunningSamples(0);
556 const void *trace
[] = { V(100), V(201), V(302), V(403), V(504) };
558 collector_
.Add(arraysize(trace
), trace
);
559 ExpectRunningSamples(1);
560 collector_
.FlushTable();
562 collector_
.Add(arraysize(trace
), trace
);
563 ExpectRunningSamples(2);
567 EXPECT_EQ(kNoError
, checker_
.ValidateProfile());
568 EXPECT_EQ(kNoError
, checker_
.Check(slots
, arraysize(slots
)));
571 // Start then reset, verify that the result is *not* a valid profile.
572 // Then start again and make sure the result is OK.
573 TEST_F(ProfileDataTest
, StartResetRestart
) {
575 ProfileData::Options options
;
576 options
.set_frequency(1);
577 EXPECT_TRUE(collector_
.Start(checker_
.filename().c_str(), options
));
578 ExpectRunningSamples(0);
581 // We expect the resulting file to be empty. This is a minimal test
582 // of ValidateProfile.
583 EXPECT_NE(kNoError
, checker_
.ValidateProfile());
586 EXPECT_EQ(0, stat(checker_
.filename().c_str(), &statbuf
));
587 EXPECT_EQ(0, statbuf
.st_size
);
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
595 options
.set_frequency(frequency
);
596 EXPECT_TRUE(collector_
.Start(checker_
.filename().c_str(), options
));
597 ExpectRunningSamples(0);
600 EXPECT_EQ(kNoError
, checker_
.ValidateProfile());
601 EXPECT_EQ(kNoError
, checker_
.Check(slots
, arraysize(slots
)));
606 int main(int argc
, char** argv
) {
607 int rc
= ProfileDataTest::RUN_ALL_TESTS();
608 printf("%s\n", rc
== 0 ? "PASS" : "FAIL");