1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/basictypes.h"
6 #include "base/command_line.h"
7 #include "base/file_path.h"
8 #include "base/file_util.h"
9 #include "base/path_service.h"
10 #include "base/process_util.h"
11 #include "base/string_number_conversions.h"
12 #include "base/string_piece.h"
13 #include "base/sys_string_conversions.h"
14 #include "base/test/test_timeouts.h"
15 #include "base/utf_string_conversions.h"
16 #include "chrome/browser/net/url_fixer_upper.h"
17 #include "chrome/common/chrome_constants.h"
18 #include "chrome/common/chrome_paths.h"
19 #include "chrome/common/chrome_switches.h"
20 #include "chrome/test/automation/tab_proxy.h"
21 #include "chrome/test/automation/window_proxy.h"
22 #include "chrome/test/base/chrome_process_util.h"
23 #include "chrome/test/base/test_switches.h"
24 #include "chrome/test/perf/perf_test.h"
25 #include "chrome/test/ui/ui_perf_test.h"
26 #include "googleurl/src/gurl.h"
27 #include "net/base/net_util.h"
30 static const int kTestIterations
= 2;
31 static const int kDatabaseTestIterations
= 2;
32 static const int kWebPageReplayIterations
= 2;
34 static const int kTestIterations
= 10;
35 // For some unknown reason, the DB perf tests are much much slower on the
36 // Vista perf bot, so we have to cut down the number of iterations to 5
37 // to make sure each test finishes in less than 10 minutes.
38 static const int kDatabaseTestIterations
= 5;
39 static const int kWebPageReplayIterations
= 5;
41 static const int kIDBTestIterations
= 5;
43 // URL at which data files may be found for HTTP tests. The document root of
44 // this URL's server should point to data/page_cycler/.
45 static const char kBaseUrl
[] = "http://localhost:8000/";
47 // The following port numbers must match those in
48 // src/tools/python/google/webpagereplay_utils.py.
49 static const char kWebPageReplayHttpPort
[] = "8080";
50 static const char kWebPageReplayHttpsPort
[] = "8413";
54 void PopulateBufferCache(const FilePath
& test_dir
) {
55 // This will recursively walk the directory given and read all the
56 // files it finds. This is done so the system file cache is likely
57 // to have as much loaded as possible. Without this, the tests of
58 // this build gets one set of timings and then the reference build
59 // test, gets slightly faster ones (even if the reference build is
60 // the same binary). The hope is by forcing all the possible data
61 // into the cache we equalize the tests for comparing timing data.
63 // We don't want to walk into .svn dirs, so we have to do the tree walk
66 std::vector
<FilePath
> dirs
;
67 dirs
.push_back(test_dir
);
68 const FilePath
svn_dir(FILE_PATH_LITERAL(".svn"));
70 for (size_t idx
= 0; idx
< dirs
.size(); ++idx
) {
71 file_util::FileEnumerator
dir_enumerator(dirs
[idx
], false,
72 file_util::FileEnumerator::DIRECTORIES
);
74 for (path
= dir_enumerator
.Next();
76 path
= dir_enumerator
.Next()) {
77 if (path
.BaseName() != svn_dir
)
82 unsigned int loaded
= 0;
84 // We seem to have some files in the data dirs that are just there for
85 // reference, make a quick attempt to skip them by matching suffixes.
86 std::vector
<FilePath::StringType
> ignore_suffixes
;
87 ignore_suffixes
.push_back(FILE_PATH_LITERAL(".orig.html"));
88 ignore_suffixes
.push_back(FILE_PATH_LITERAL(".html-original"));
90 std::vector
<FilePath
>::const_iterator iter
;
91 for (iter
= dirs
.begin(); iter
!= dirs
.end(); ++iter
) {
92 file_util::FileEnumerator
file_enumerator(*iter
, false,
93 file_util::FileEnumerator::FILES
);
95 for (path
= file_enumerator
.Next();
97 path
= file_enumerator
.Next()) {
98 const FilePath base_name
= path
.BaseName();
99 const size_t base_name_size
= base_name
.value().size();
101 bool should_skip
= false;
102 std::vector
<FilePath::StringType
>::const_iterator ignore_iter
;
103 for (ignore_iter
= ignore_suffixes
.begin();
104 ignore_iter
!= ignore_suffixes
.end();
106 const FilePath::StringType
&suffix
= *ignore_iter
;
108 if ((base_name_size
> suffix
.size()) &&
109 (base_name
.value().compare(base_name_size
- suffix
.size(),
110 suffix
.size(), suffix
) == 0)) {
118 // Read the file fully to get it into the cache.
119 // We don't care what the contents are.
120 if (file_util::ReadFileToString(path
, NULL
))
124 VLOG(1) << "Buffer cache should be primed with " << loaded
<< " files.";
127 class PageCyclerTest
: public UIPerfTest
{
129 PageCyclerTest() : print_times_only_(false),
130 num_test_iterations_(kTestIterations
) {
132 dom_automation_enabled_
= true;
134 const CommandLine
& parsed_command_line
= *CommandLine::ForCurrentProcess();
135 if (parsed_command_line
.HasSwitch(switches::kPageCyclerIterations
)) {
136 std::string str
= parsed_command_line
.GetSwitchValueASCII(
137 switches::kPageCyclerIterations
);
138 base::StringToInt(str
, &num_test_iterations_
);
142 virtual void SetLaunchSwitches() OVERRIDE
{
143 UIPerfTest::SetLaunchSwitches();
145 // Expose garbage collection for the page cycler tests.
146 launch_arguments_
.AppendSwitchASCII(switches::kJavaScriptFlags
,
150 virtual FilePath
GetDataPath(const char* name
) {
151 // Make sure the test data is checked out
153 PathService::Get(base::DIR_SOURCE_ROOT
, &test_path
);
154 test_path
= test_path
.Append(FILE_PATH_LITERAL("data"));
155 test_path
= test_path
.Append(FILE_PATH_LITERAL("page_cycler"));
156 test_path
= test_path
.AppendASCII(name
);
160 virtual bool HasErrors(const std::string
/*timings*/) {
164 virtual int GetTestIterations() {
165 return num_test_iterations_
;
168 virtual void GetTestUrl(const char* name
, bool use_http
, GURL
*test_url
) {
169 FilePath test_path
= GetDataPath(name
);
170 ASSERT_TRUE(file_util::DirectoryExists(test_path
))
171 << "Missing test directory " << test_path
.value();
172 PopulateBufferCache(test_path
);
175 *test_url
= GURL(std::string(kBaseUrl
) + name
+ "/start.html");
177 test_path
= test_path
.Append(FILE_PATH_LITERAL("start.html"));
178 *test_url
= net::FilePathToFileURL(test_path
);
182 GURL::Replacements replacements
;
183 const std::string query_string
=
184 "iterations=" + base::IntToString(GetTestIterations()) + "&auto=1";
185 replacements
.SetQuery(
186 query_string
.c_str(),
187 url_parse::Component(0, query_string
.length()));
188 *test_url
= test_url
->ReplaceComponents(replacements
);
191 // For HTTP tests, the name must be safe for use in a URL without escaping.
192 void RunPageCycler(const char* name
, std::wstring
* pages
,
193 std::string
* timings
, bool use_http
) {
195 GetTestUrl(name
, use_http
, &test_url
);
197 scoped_refptr
<TabProxy
> tab(GetActiveTab());
198 ASSERT_TRUE(tab
.get());
199 ASSERT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS
, tab
->NavigateToURL(test_url
));
201 // Wait for the test to finish.
202 ASSERT_TRUE(WaitUntilCookieValue(
203 tab
.get(), test_url
, "__pc_done",
204 TestTimeouts::large_test_timeout(), "1"));
207 ASSERT_TRUE(tab
->GetCookieByName(test_url
, "__pc_pages", &cookie
));
208 pages
->assign(UTF8ToWide(cookie
));
209 ASSERT_FALSE(pages
->empty());
211 // Wait for the report.html to be loaded.
212 ASSERT_TRUE(WaitUntilCookieValue(
213 tab
.get(), test_url
, "__navigated_to_report",
214 TestTimeouts::action_max_timeout(), "1"));
216 // Get the timing cookie value from the DOM automation.
217 std::wstring wcookie
;
218 ASSERT_TRUE(tab
->ExecuteAndExtractString(L
"",
219 L
"window.domAutomationController.send("
220 L
"JSON.stringify(__get_timings()));",
222 cookie
= base::SysWideToNativeMB(wcookie
);
224 // JSON.stringify() encapsulates the returned string in quotes, strip them.
225 std::string::size_type start_idx
= cookie
.find("\"");
226 std::string::size_type end_idx
= cookie
.find_last_of("\"");
227 if (start_idx
!= std::string::npos
&&
228 end_idx
!= std::string::npos
&&
229 start_idx
< end_idx
) {
230 cookie
= cookie
.substr(start_idx
+1, end_idx
-start_idx
-1);
233 timings
->assign(cookie
);
234 ASSERT_FALSE(timings
->empty());
237 // When use_http is true, the test name passed here will be used directly in
238 // the path to the test data, so it must be safe for use in a URL without
239 // escaping. (No pound (#), question mark (?), semicolon (;), non-ASCII, or
240 // other funny stuff.)
241 void RunTestWithSuffix(const char* graph
, const char* name
, bool use_http
,
242 const char* suffix
) {
245 size_t start_size
= base::GetSystemCommitCharge();
246 RunPageCycler(name
, &pages
, &timings
, use_http
);
247 if (timings
.empty() || HasErrors(timings
))
249 size_t stop_size
= base::GetSystemCommitCharge();
251 if (!print_times_only_
) {
252 PrintMemoryUsageInfo(suffix
);
253 PrintIOPerfInfo(suffix
);
254 perf_test::PrintSystemCommitCharge(suffix
, stop_size
- start_size
,
255 false /* not important */);
256 ASSERT_NO_FATAL_FAILURE(PrintMemoryHistograms());
259 std::string trace_name
= "t" + std::string(suffix
);
261 printf("Pages: [%s]\n", base::SysWideToNativeMB(pages
).c_str());
263 perf_test::PrintResultList(graph
, "", trace_name
, timings
, "ms",
264 true /* important */);
267 void RunTest(const char* graph
, const char* name
, bool use_http
) {
268 RunTestWithSuffix(graph
, name
, use_http
, "");
272 void PrintMemoryHistogram(const std::string
& name
,
273 const std::string
& units
) {
274 scoped_refptr
<TabProxy
> tab(GetActiveTab());
275 ASSERT_TRUE(tab
.get());
276 std::wstring whistogram
;
277 ASSERT_TRUE(tab
->ExecuteAndExtractString(
279 L
"window.domAutomationController.send("
280 L
"window.domAutomationController.getHistogram ? "
281 L
"window.domAutomationController.getHistogram(\"" +
282 base::SysUTF8ToWide(name
) + L
"\") : '')",
284 std::string histogram
= base::SysWideToNativeMB(whistogram
);
285 printf("HISTOGRAM %s: %s = %s %s\n",
286 name
.c_str(), name
.c_str(), histogram
.c_str(), units
.c_str());
289 void PrintMemoryHistograms() {
290 ASSERT_NO_FATAL_FAILURE(PrintMemoryHistogram(
291 "V8.MemoryExternalFragmentationTotal", "percent"));
292 ASSERT_NO_FATAL_FAILURE(PrintMemoryHistogram(
293 "V8.MemoryHeapSampleTotalCommitted", "kb"));
294 ASSERT_NO_FATAL_FAILURE(PrintMemoryHistogram(
295 "V8.MemoryHeapSampleTotalUsed", "kb"));
299 bool print_times_only_
;
302 int num_test_iterations_
;
305 class PageCyclerReferenceTest
: public PageCyclerTest
{
309 PageCyclerTest::SetUp();
312 void RunTest(const char* graph
, const char* name
, bool use_http
) {
314 PageCyclerTest::RunTestWithSuffix(graph
, name
, use_http
, "_ref");
318 class PageCyclerExtensionTest
: public PageCyclerTest
{
320 // Note: we delay the SetUp until RunTest is called so that we can set
321 // the user_data_dir based on the test name.
322 virtual void SetUp() {}
323 void RunTest(const char* graph
, const char* extension_profile
,
324 const char* output_suffix
, const char* name
, bool use_http
) {
325 // Set up the extension profile directory.
326 ASSERT_TRUE(extension_profile
!= NULL
);
328 PathService::Get(chrome::DIR_TEST_DATA
, &data_dir
);
329 data_dir
= data_dir
.AppendASCII("extensions").AppendASCII("profiles").
330 AppendASCII(extension_profile
);
331 ASSERT_TRUE(file_util::DirectoryExists(data_dir
));
332 set_template_user_data(data_dir
);
335 PageCyclerTest::SetUp();
336 PageCyclerTest::RunTestWithSuffix(graph
, name
, use_http
, output_suffix
);
340 class PageCyclerExtensionWebRequestTest
: public PageCyclerExtensionTest
{
342 PageCyclerExtensionWebRequestTest()
343 : PageCyclerExtensionTest()
345 // Enable experimental extension APIs for webrequest tests.
346 launch_arguments_
.AppendSwitch(switches::kEnableExperimentalExtensionApis
);
350 static FilePath
GetDatabaseDataPath(const char* name
) {
352 PathService::Get(base::DIR_SOURCE_ROOT
, &test_path
);
353 test_path
= test_path
.Append(FILE_PATH_LITERAL("tools"));
354 test_path
= test_path
.Append(FILE_PATH_LITERAL("page_cycler"));
355 test_path
= test_path
.Append(FILE_PATH_LITERAL("database"));
356 test_path
= test_path
.AppendASCII(name
);
360 static FilePath
GetIndexedDatabaseDataPath(const char* name
) {
362 PathService::Get(base::DIR_SOURCE_ROOT
, &test_path
);
363 test_path
= test_path
.Append(FILE_PATH_LITERAL("tools"));
364 test_path
= test_path
.Append(FILE_PATH_LITERAL("page_cycler"));
365 test_path
= test_path
.Append(FILE_PATH_LITERAL("indexed_db"));
366 test_path
= test_path
.AppendASCII(name
);
370 static bool HasDatabaseErrors(const std::string timings
) {
373 std::string time_str
;
376 new_pos
= timings
.find(',', pos
);
377 if (new_pos
== std::string::npos
)
378 new_pos
= timings
.length();
379 if (!base::StringToInt(base::StringPiece(timings
.begin() + pos
,
380 timings
.begin() + new_pos
),
382 LOG(ERROR
) << "Invalid time reported: " << time_str
;
388 LOG(ERROR
) << "Error while opening the database.";
391 LOG(ERROR
) << "Error while setting up the database.";
394 LOG(ERROR
) << "Error while running the transactions.";
397 LOG(ERROR
) << "Unknown error: " << time
;
403 } while (pos
< timings
.length());
408 class PageCyclerDatabaseTest
: public PageCyclerTest
{
410 PageCyclerDatabaseTest() {
411 print_times_only_
= true;
414 virtual FilePath
GetDataPath(const char* name
) {
415 return GetDatabaseDataPath(name
);
418 virtual bool HasErrors(const std::string timings
) {
419 return HasDatabaseErrors(timings
);
422 virtual int GetTestIterations() {
423 return kDatabaseTestIterations
;
427 class PageCyclerDatabaseReferenceTest
: public PageCyclerReferenceTest
{
429 PageCyclerDatabaseReferenceTest() {
430 print_times_only_
= true;
433 virtual FilePath
GetDataPath(const char* name
) {
434 return GetDatabaseDataPath(name
);
437 virtual bool HasErrors(const std::string timings
) {
438 return HasDatabaseErrors(timings
);
441 virtual int GetTestIterations() {
442 return kDatabaseTestIterations
;
446 class PageCyclerIndexedDatabaseTest
: public PageCyclerTest
{
448 PageCyclerIndexedDatabaseTest() {
449 print_times_only_
= true;
452 virtual FilePath
GetDataPath(const char* name
) {
453 return GetIndexedDatabaseDataPath(name
);
456 virtual bool HasErrors(const std::string timings
) {
457 return HasDatabaseErrors(timings
);
460 virtual int GetTestIterations() {
461 return kIDBTestIterations
;
465 class PageCyclerIndexedDatabaseReferenceTest
: public PageCyclerReferenceTest
{
467 PageCyclerIndexedDatabaseReferenceTest() {
468 print_times_only_
= true;
471 virtual FilePath
GetDataPath(const char* name
) {
472 return GetIndexedDatabaseDataPath(name
);
475 virtual bool HasErrors(const std::string timings
) {
476 return HasDatabaseErrors(timings
);
479 virtual int GetTestIterations() {
480 return kIDBTestIterations
;
484 // Web Page Replay is a proxy server to record and serve pages
485 // with realistic network delays and bandwidth throttling.
486 // runtest.py launches replay.py to support these tests.
487 class PageCyclerWebPageReplayTest
: public PageCyclerTest
{
489 PageCyclerWebPageReplayTest() {
490 // These Chrome command-line arguments need to be kept in sync
491 // with src/tools/python/google/webpagereplay_utils.py.
492 FilePath extension_path
= GetPageCyclerWprPath("extension");
493 launch_arguments_
.AppendSwitchPath(
494 switches::kLoadExtension
, extension_path
);
495 // TODO(slamm): Instead of kHostResolverRules, add a new switch,
496 // kTestingFixedDnsPort, and configure Web Page Replay to run
497 // a DNS proxy on that port to test Chrome's DNS code.
498 launch_arguments_
.AppendSwitchASCII(
499 switches::kHostResolverRules
, "MAP * 127.0.0.1");
500 launch_arguments_
.AppendSwitchASCII(
501 switches::kTestingFixedHttpPort
, kWebPageReplayHttpPort
);
502 launch_arguments_
.AppendSwitchASCII(
503 switches::kTestingFixedHttpsPort
, kWebPageReplayHttpsPort
);
504 launch_arguments_
.AppendSwitch(switches::kEnableExperimentalExtensionApis
);
505 launch_arguments_
.AppendSwitch(switches::kEnableStatsTable
);
506 launch_arguments_
.AppendSwitch(switches::kEnableBenchmarking
);
507 launch_arguments_
.AppendSwitch(switches::kIgnoreCertificateErrors
);
508 launch_arguments_
.AppendSwitch(switches::kNoProxyServer
);
511 FilePath
GetPageCyclerWprPath(const char* name
) {
513 PathService::Get(base::DIR_SOURCE_ROOT
, &wpr_path
);
514 wpr_path
= wpr_path
.AppendASCII("tools");
515 wpr_path
= wpr_path
.AppendASCII("page_cycler");
516 wpr_path
= wpr_path
.AppendASCII("webpagereplay");
517 wpr_path
= wpr_path
.AppendASCII(name
);
521 virtual int GetTestIterations() OVERRIDE
{
522 return kWebPageReplayIterations
;
525 virtual void GetTestUrl(const char* name
, bool use_http
,
526 GURL
*test_url
) OVERRIDE
{
527 FilePath start_path
= GetPageCyclerWprPath("start.html");
529 // Add query parameters for iterations and test name.
530 const std::string query_string
=
531 "iterations=" + base::IntToString(GetTestIterations()) +
534 GURL::Replacements replacements
;
535 replacements
.SetQuery(
536 query_string
.c_str(),
537 url_parse::Component(0, query_string
.length()));
539 *test_url
= net::FilePathToFileURL(start_path
);
540 *test_url
= test_url
->ReplaceComponents(replacements
);
543 void RunTest(const char* graph
, const char* name
) {
544 FilePath test_path
= GetPageCyclerWprPath("tests");
545 test_path
= test_path
.AppendASCII(name
);
546 test_path
= test_path
.ReplaceExtension(FILE_PATH_LITERAL(".js"));
547 ASSERT_TRUE(file_util::PathExists(test_path
))
548 << "Missing test file " << test_path
.value();
550 const bool use_http
= false; // always use a file
551 PageCyclerTest::RunTestWithSuffix(graph
, name
, use_http
, "");
555 // This macro simplifies setting up regular and reference build tests.
556 #define PAGE_CYCLER_TESTS(test, name, use_http) \
557 TEST_F(PageCyclerTest, name) { \
558 RunTest("times", test, use_http); \
560 TEST_F(PageCyclerReferenceTest, name) { \
561 RunTest("times", test, use_http); \
564 // This macro simplifies setting up regular and reference build tests
565 // for HTML5 database tests.
566 #define PAGE_CYCLER_DATABASE_TESTS(test, name) \
567 TEST_F(PageCyclerDatabaseTest, Database##name##File) { \
568 RunTest(test, test, false); \
570 TEST_F(PageCyclerDatabaseReferenceTest, Database##name##File) { \
571 RunTest(test, test, false); \
574 // This macro simplifies setting up regular and reference build tests
575 // for HTML5 Indexed DB tests.
576 #define PAGE_CYCLER_IDB_TESTS(test, name) \
577 TEST_F(PageCyclerIndexedDatabaseTest, IndexedDB##name##File) { \
578 RunTest(test, test, false); \
580 TEST_F(PageCyclerIndexedDatabaseReferenceTest, IndexedDB##name##File) { \
581 RunTest(test, test, false); \
584 // These are shorthand for File vs. Http tests.
585 #define PAGE_CYCLER_FILE_TESTS(test, name) \
586 PAGE_CYCLER_TESTS(test, name, false)
587 #define PAGE_CYCLER_HTTP_TESTS(test, name) \
588 PAGE_CYCLER_TESTS(test, name, true)
590 // This macro lets us define tests with 1 and 10 extensions with 1 content
591 // script each. The name for the 10-extension case is changed so as not
592 // to run by default on the buildbots.
593 #define PAGE_CYCLER_EXTENSIONS_FILE_TESTS(test, name) \
594 TEST_F(PageCyclerExtensionTest, name) { \
595 RunTest("times", "content_scripts1", "_extcs1", test, false); \
597 TEST_F(PageCyclerExtensionTest, name##10) { \
598 RunTest("times", "content_scripts10", "_extcs10", test, false); \
601 // This macro lets us define tests with an extension that listens to the
602 // webrequest.onBeforeRequest. It measures the effect that a blocking event
603 // for every request has on page cycle time.
604 #define PAGE_CYCLER_EXTENSIONS_WEBREQUEST_FILE_TESTS(test, name) \
605 TEST_F(PageCyclerExtensionWebRequestTest, name) { \
606 RunTest("times", "extension_webrequest", "_extwr", test, false); \
609 #define PAGE_CYCLER_WEBPAGEREPLAY_TESTS(test, name) \
610 TEST_F(PageCyclerWebPageReplayTest, name) { \
611 RunTest("times", test); \
615 PAGE_CYCLER_FILE_TESTS("moz", MozFile
);
616 PAGE_CYCLER_EXTENSIONS_FILE_TESTS("moz", MozFile
);
617 PAGE_CYCLER_EXTENSIONS_WEBREQUEST_FILE_TESTS("moz", MozFile
)
618 PAGE_CYCLER_FILE_TESTS("intl1", Intl1File
);
619 PAGE_CYCLER_FILE_TESTS("intl2", Intl2File
);
620 PAGE_CYCLER_EXTENSIONS_WEBREQUEST_FILE_TESTS("intl2", Intl2File
);
621 PAGE_CYCLER_FILE_TESTS("dom", DomFile
);
622 PAGE_CYCLER_FILE_TESTS("dhtml", DhtmlFile
);
623 PAGE_CYCLER_FILE_TESTS("morejs", MorejsFile
);
624 PAGE_CYCLER_EXTENSIONS_FILE_TESTS("morejs", MorejsFile
);
625 // added more tests here:
626 PAGE_CYCLER_FILE_TESTS("alexa_us", Alexa_usFile
);
627 PAGE_CYCLER_FILE_TESTS("moz2", Moz2File
);
628 PAGE_CYCLER_FILE_TESTS("morejsnp", MorejsnpFile
);
629 PAGE_CYCLER_FILE_TESTS("bloat", BloatFile
);
631 // http (localhost) tests
632 PAGE_CYCLER_HTTP_TESTS("moz", MozHttp
);
633 PAGE_CYCLER_HTTP_TESTS("intl1", Intl1Http
);
634 PAGE_CYCLER_HTTP_TESTS("intl2", Intl2Http
);
635 PAGE_CYCLER_HTTP_TESTS("dom", DomHttp
);
636 PAGE_CYCLER_HTTP_TESTS("bloat", BloatHttp
);
638 // Web Page Replay (simulated network) tests.
639 // Windows is unsupported because of issues with loopback adapter and
640 // dummynet is unavailable on Vista and above.
642 PAGE_CYCLER_WEBPAGEREPLAY_TESTS("2012Q2", 2012Q2
);
645 // HTML5 database tests
646 // These tests are _really_ slow on XP/Vista.
648 PAGE_CYCLER_DATABASE_TESTS("select-transactions",
650 PAGE_CYCLER_DATABASE_TESTS("select-readtransactions",
651 SelectReadTransactions
);
652 PAGE_CYCLER_DATABASE_TESTS("select-readtransactions-read-results",
653 SelectReadTransactionsReadResults
);
654 PAGE_CYCLER_DATABASE_TESTS("insert-transactions",
656 PAGE_CYCLER_DATABASE_TESTS("update-transactions",
658 PAGE_CYCLER_DATABASE_TESTS("delete-transactions",
660 PAGE_CYCLER_DATABASE_TESTS("pseudo-random-transactions",
661 PseudoRandomTransactions
);
665 PAGE_CYCLER_IDB_TESTS("basic_insert", BasicInsert
);