8bc99fa3e0adc61885dbe5dc9ba662c3e60fea46
[libcds.git] / tests / cppunit / test_main.cpp
1 //$$CDS-header$$
2
3 /*
4  * Copyright (c) 2003, 2004
5  * Zdenek Nemec
6  *
7  * This material is provided "as is", with absolutely no warranty expressed
8  * or implied. Any use is at your own risk.
9  *
10  * Permission to use or copy this software for any purpose is hereby granted
11  * without fee, provided the above notices are retained on all copies.
12  * Permission to modify the code and to distribute modified code is granted,
13  * provided the above notices are retained, and a notice that the code was
14  * modified is included with the above copyright notice.
15  *
16  */
17
18 /*
19 #if defined(_DEBUG) && _MSC_VER == 1500
20 #    define _CRTDBG_MAP_ALLOC
21 #    include <stdlib.h>
22 #    include <crtdbg.h>
23 #endif
24 */
25
26 #include "cppunit/cppunit_proxy.h"
27 #include "cppunit/file_reporter.h"
28
29 #include <cds/init.h>
30 #include <cds/gc/hp.h>
31 #include <cds/gc/dhp.h>
32 #include <cds/urcu/general_instant.h>
33 #include <cds/urcu/general_buffered.h>
34 #include <cds/urcu/general_threaded.h>
35 #include <cds/urcu/signal_buffered.h>
36 #include <cds/urcu/signal_threaded.h>
37 #include <cds/os/topology.h>
38
39 #include "stdio.h"
40 #include <fstream>
41 #include <iostream>
42 #include <set>
43 #include <boost/date_time/posix_time/posix_time.hpp>
44 #include <boost/thread/mutex.hpp>
45
46 // Visual leak detector (see http://vld.codeplex.com/)
47 #if defined(CDS_USE_VLD) && CDS_COMPILER == CDS_COMPILER_MSVC
48 #   ifdef _DEBUG
49 #       include <vld.h>
50 #   endif
51 #endif
52
53
54 std::ostream& operator << (std::ostream& s, const cds::gc::hp::GarbageCollector::InternalState& stat)
55 {
56     s << "\nHZP GC internal state:"
57         << "\n\t            HP record allocated=" << stat.nHPRecAllocated
58         << "\n\t                HP records used=" << stat.nHPRecUsed
59         << "\n\t        Total retired ptr count=" << stat.nTotalRetiredPtrCount
60         << "\n\t Retired ptr in free HP records=" << stat.nRetiredPtrInFreeHPRecs
61         << "\n\tEvents:"
62         << "\n\t              HPRec allocations=" << stat.evcAllocHPRec
63         << "\n\t            HPRec retire events=" << stat.evcRetireHPRec
64         << "\n\tnew HPRec allocations from heap=" << stat.evcAllocNewHPRec
65         << "\n\t                HPRec deletions=" << stat.evcDeleteHPRec
66         << "\n\t                Scan call count=" << stat.evcScanCall
67         << "\n\t            HelpScan call count=" << stat.evcHelpScanCall
68         << "\n\t       Scan calls from HelpScan=" << stat.evcScanFromHelpScan
69         << "\n\t       retired object deleting=" << stat.evcDeletedNode
70         << "\n\t        guarded object on Scan=" << stat.evcDeferredNode
71         << std::endl;
72
73     return s;
74 }
75
76 namespace CppUnitMini
77 {
78     int TestCase::m_numErrors = 0;
79     int TestCase::m_numTests = 0;
80     std::vector<std::string>  TestCase::m_arrStrings;
81     bool TestCase::m_bPrintGCState = false;
82     std::string TestCase::m_strTestDataDir(".");
83     Config TestCase::m_Cfg;
84     bool TestCase::m_bExactMatch = false;
85
86     // random shuffle support
87     /*static*/ std::random_device TestCase::m_RandomDevice;
88     /*static*/ std::mt19937       TestCase::m_RandomGen( TestCase::m_RandomDevice() );
89
90     TestCase * TestCase::m_pCurTestCase = nullptr;
91
92     TestCase *TestCase::m_root = 0;
93     Reporter *TestCase::m_reporter = 0;
94
95   void TestCase::registerTestCase(TestCase *in_testCase) {
96     in_testCase->m_next = m_root;
97     m_root = in_testCase;
98   }
99
100   int TestCase::run(Reporter *in_reporter, const char *in_testName, bool invert)
101   {
102     TestCase::m_reporter = in_reporter;
103
104     m_numErrors = 0;
105     m_numTests = 0;
106
107     TestCase *tmp = m_root;
108     while (tmp != 0) {
109       m_pCurTestCase = tmp;
110       try {
111         tmp->myRun(in_testName, invert);
112       } catch ( std::exception& ex ) {
113         in_reporter->message( "EXCEPTION: ");
114         in_reporter->message( ex.what() );
115       }
116       tmp = tmp->m_next;
117     }
118
119     return m_numErrors;
120   }
121
122   bool TestCase::shouldRunThis(const char *in_desiredTest, const char *in_className, const char *in_functionName,
123                        bool invert, bool explicit_test, bool &do_progress)
124   {
125       if ((in_desiredTest) && (in_desiredTest[0] != '\0')) {
126         do_progress = false;
127         const char *ptr = strstr(in_desiredTest, "::");
128         if (ptr) {
129             bool match;
130             if ( m_bExactMatch ) {
131                 match = (strncmp( in_desiredTest, in_className, strlen( in_className )) == 0 && in_desiredTest[strlen( in_className )] == ':')
132                      && (strcmp( ptr + 2, in_functionName ) == 0);
133             }
134             else {
135                 match = (strncmp( in_desiredTest, in_className, strlen( in_className )) == 0 && in_desiredTest[strlen( in_className )] == ':')
136                      && (strncmp( ptr + 2, in_functionName, strlen( ptr + 2 ) ) == 0);
137             }
138             // Invert shall not make explicit test run:
139             return invert ? (match ? !match : !explicit_test)
140                           : match;
141         }
142         bool match = (strcmp(in_desiredTest, in_className) == 0);
143         do_progress = match;
144         return !explicit_test && (match == !invert);
145       }
146       do_progress = true;
147
148       return !explicit_test;
149   }
150
151
152   void TestCase::print_gc_state()
153   {
154       if ( m_bPrintGCState ) {
155           {
156               cds::gc::hp::GarbageCollector::InternalState stat;
157               std::cout << cds::gc::hp::GarbageCollector::instance().getInternalState( stat ) << std::endl;
158           }
159       }
160   }
161
162   void TestCase::print_test_list()
163   {
164       TestCase *tmp = m_root;
165       std::cout << "Test list:\n";
166       while (tmp != 0) {
167           std::cout << "\t" << tmp->test_name() << "\n";
168           tmp = tmp->m_next;
169       }
170   }
171
172   void Config::load( const char * fileName )
173   {
174       std::ifstream s;
175       s.open( fileName );
176       if ( !s.is_open() ) {
177           std::cerr << "WARNING: Cannot open test cfg file " << fileName
178               << "\n\tUse default settings"
179               << std::endl;
180           return;
181       }
182
183       std::cout << "Using test config file: " << fileName << std::endl;
184
185       char buf[ 4096 ];
186
187       TestCfg * pMap = nullptr;
188       while ( !s.eof() ) {
189           s.getline( buf, sizeof(buf)/sizeof(buf[0]) );
190           char * pszStr = buf;
191           // trim left
192           while ( *pszStr != 0 && (*pszStr == ' ' || *pszStr == '\t' )) ++pszStr;
193           // trim right
194           char * pszEnd = strchr( pszStr, 0 );
195           if ( pszEnd == pszStr )    // empty srtring
196               continue;
197           --pszEnd;
198           while ( pszEnd != pszStr && (*pszEnd ==' ' || *pszEnd=='\t' || *pszEnd=='\n' || *pszEnd=='\r' )) --pszEnd;
199
200           if ( pszStr == pszEnd  )    // empty string
201               continue;
202
203           pszEnd[1] = 0;
204
205           if ( *pszStr == '#' )    // comment
206               continue;
207
208           if ( *pszStr == '[' && *pszEnd == ']' ) {    // chapter header
209               *pszEnd = 0;
210               pMap = &( m_Cfg[ pszStr + 1 ] );
211               continue;
212           }
213
214           if ( !pMap )
215               continue;
216
217           char * pszEq = strchr( pszStr, '=' );
218           if ( !pszEq )
219               continue;
220           if ( pszEq == pszStr )
221               continue;
222
223           pszEnd = pszEq;
224           while ( pszStr <= --pszEnd && (*pszEnd ==' ' || *pszEnd=='\t' || *pszEnd=='\n' || *pszEnd=='\r') );
225
226           if ( pszEnd <= pszStr )
227               continue;
228           pszEnd[1] = 0;
229           pMap->m_Cfg[ pszStr ] = pszEq + 1;
230       }
231       s.close();
232   }
233
234   std::vector<std::string> const & TestCase::getTestStrings()
235   {
236       if ( m_arrStrings.empty() ) {
237           std::string strTestDir = m_strTestDataDir;
238
239           std::ifstream fDict;
240           char bufLine[1024];
241           std::cout << "Loading test data " << strTestDir << "/dictionary.txt..." << std::endl;
242           fDict.open( (strTestDir + "/dictionary.txt").c_str() );
243           if ( fDict.is_open() ) {
244               cds::OS::Timer timer;
245               std::string str;
246               fDict >> str    ;   // number of lines in file
247
248               // Assume that dictionary.txt does not contain doubles.
249               CppUnitMini::TestCase::m_arrStrings.reserve( atol(str.c_str()) );
250               while ( !fDict.eof() ) {
251                   fDict.getline( bufLine, sizeof(bufLine)/sizeof(bufLine[0]) );
252                   if ( bufLine[0] )
253                     m_arrStrings.push_back( bufLine );
254               }
255               fDict.close();
256
257               std::cout << "  Duration=" << timer.duration() << " String count " << CppUnitMini::TestCase::m_arrStrings.size() << std::endl;
258           }
259           else
260               std::cout << "  Failed, file not found" << std::endl;
261
262       }
263       return m_arrStrings;
264   }
265 }
266
267 static void usage(const char* name)
268 {
269     std::cout << "Usage: " << name << " [options] [-t=<class>[::<test>]] [-x=<class>[::<test>]] [-f=<file>]\n"
270                  "\t[-t=<class>[::<test>]] : test class or class::test to execute\n"
271                  "\t[-d=dir] : test data directory (default is .)\n"
272                  "\t[-f=<file>] : output file\n"
273                  "Options:\n"
274                  "\t-exact-match - class::test should be exactly matched to existing test\n"
275                  "\t-gc_state    - print gc state after each test\n"
276                  "\t-cfg=<file>  - config file name for tests\n"
277                  "\t-list        - list all tests\n"
278                  "\t"
279               << std::endl;
280 }
281
282 int main(int argc, char** argv)
283 {
284
285 #ifdef CDS_MSVC_MEMORY_LEAKS_DETECTING_ENABLED
286     _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
287 #endif
288
289   // CppUnit(mini) test launcher
290   // command line option syntax:
291   // test [Options]
292   // where Options are
293   //  -t=CLASS[::TEST]    run the test class CLASS or member test CLASS::TEST
294   //  -x=CLASS[::TEST]    run all except the test class CLASS or member test CLASS::TEST
295   //  -d=dir              test data directory (default is .)
296   //  -f=FILE             save output in file FILE instead of stdout
297   //  -m                  monitor test(s) execution
298   //  -gc_state           print GC state after test
299   const char *fileName = 0;
300   const char *testName = "";
301   const char *xtestName = "";
302   const char *testDataDir = ".";
303   const char *cfgFileName =
304 #ifdef _DEBUG
305       "test-debug.conf"
306 #else
307       "test.conf"
308 #endif
309 ;
310   bool doMonitoring = false;
311
312   for (int i = 1; i < argc; ++i) {
313     if (argv[i][0] == '-') {
314       if ( strncmp(argv[i], "-t=", 3) == 0 ) {
315         testName = argv[i]+3;
316         continue;
317       }
318       else if ( strncmp(argv[i], "-f=", 3) == 0 ) {
319         fileName = argv[i]+3;
320         continue;
321       }
322       else if ( strncmp(argv[i], "-x=", 3) == 0 ) {
323         xtestName = argv[i]+3;
324         continue;
325       }
326       else if ( strncmp(argv[i], "-d=", 3) == 0 ) {
327           testDataDir = argv[i] + 3;
328           continue;
329       }
330       else if ( strncmp( argv[i], "-m", 2 ) == 0 ) {
331           doMonitoring = true;
332           continue;
333       }
334       else if ( strncmp( argv[i], "-exact-match", 12 ) == 0 ) {
335           CppUnitMini::TestCase::m_bExactMatch = true;
336           continue;
337       }
338       else if (strncmp(argv[i], "-list", 5) == 0) {
339           CppUnitMini::TestCase::print_test_list();
340           return 0;
341       }
342       else if ( strncmp(argv[i], "-gc_state", 9) == 0 ) {
343           CppUnitMini::TestCase::m_bPrintGCState = true;
344           continue;
345       }
346       else if( strncmp(argv[i], "-cfg=", 5) == 0 ) {
347           cfgFileName = argv[i] + 5;
348           continue;
349       }
350     }
351
352     // invalid option, we display normal usage.
353     usage(argv[0]);
354     return 1;
355
356   }
357
358   {
359     boost::posix_time::ptime cur( boost::posix_time::second_clock::local_time());
360
361     std::cout << "libcds version " << CDS_VERSION_STRING << "\n";
362     std::cout << "Test started " << cur << std::endl;
363   }
364
365
366   CppUnitMini::TestCase::m_strTestDataDir = testDataDir;
367
368   CppUnitMini::Reporter* reporter;
369   if (fileName != 0)
370     reporter = new CppUnitMini::FileReporter(fileName, doMonitoring);
371   else
372     reporter = new CppUnitMini::FileReporter(stdout, doMonitoring);
373
374   // Load config params
375   CppUnitMini::TestCase::m_Cfg.load( cfgFileName );
376
377   // Init CDS runtime
378   cds::Initialize();
379
380   int num_errors;
381   {
382       size_t nHazardPtrCount = 0;
383       size_t dhp_liberateThreshold;
384       size_t dhp_initialThreadGuardCount;
385       size_t dhp_epochCount;
386       {
387         CppUnitMini::TestCfg& cfg = CppUnitMini::TestCase::m_Cfg.get( "General" );
388         nHazardPtrCount = cfg.getULong( "hazard_pointer_count", 0 );
389
390         dhp_liberateThreshold = cfg.getSizeT( "dhp_liberate_threshold", 1024 );
391         dhp_initialThreadGuardCount = cfg.getSizeT( "dhp_init_guard_count", 8 );
392         dhp_epochCount = cfg.getSizeT( "dhp_epoch_count", 16 );
393       }
394
395       // Safe reclamation schemes
396       cds::gc::HP hzpGC( nHazardPtrCount );
397       cds::gc::DHP dhpGC( dhp_liberateThreshold, dhp_initialThreadGuardCount, dhp_epochCount );
398
399       // RCU varieties
400       typedef cds::urcu::gc< cds::urcu::general_instant<> >    rcu_gpi;
401       rcu_gpi   gpiRCU;
402
403       typedef cds::urcu::gc< cds::urcu::general_buffered<> >    rcu_gpb;
404       rcu_gpb   gpbRCU;
405
406       typedef cds::urcu::gc< cds::urcu::general_threaded<> >    rcu_gpt;
407       rcu_gpt   gptRCU;
408
409 #ifdef CDS_URCU_SIGNAL_HANDLING_ENABLED
410       typedef cds::urcu::gc< cds::urcu::signal_buffered<> >    rcu_shb;
411       rcu_shb   shbRCU( 256, SIGUSR1 );
412
413       typedef cds::urcu::gc< cds::urcu::signal_threaded<> >    rcu_sht;
414       rcu_sht   shtRCU( 256, SIGUSR2 );
415 #endif
416
417       // System topology
418       {
419           std::cout
420               << "System topology:\n"
421               << "    Logical processor count: " << std::thread::hardware_concurrency() << "\n";
422           std::cout << std::endl;
423       }
424
425       {
426         CppUnitMini::TestCfg& cfg = CppUnitMini::TestCase::m_Cfg.get( "General" );
427         std::string strHZPScanStrategy = cfg.get( "HZP_scan_strategy", std::string("classic") );
428         if ( strHZPScanStrategy == "inplace" )
429             hzpGC.setScanType( cds::gc::HP::scan_type::inplace );
430         else if ( strHZPScanStrategy == "classic" )
431             hzpGC.setScanType( cds::gc::HP::scan_type::classic );
432         else {
433             std::cout << "Error value of HZP_scan_strategy in General section of test config\n";
434         }
435
436         switch (hzpGC.getScanType()) {
437         case cds::gc::HP::scan_type::inplace:
438             std::cout << "Use in-place scan strategy for Hazard Pointer memory reclamation algorithm\n";
439             break;
440         case cds::gc::HP::scan_type::classic:
441             std::cout << "Use classic scan strategy for Hazard Pointer memory reclamation algorithm\n";
442             break;
443         default:
444             std::cout << "ERROR: use unknown scan strategy for Hazard Pointer memory reclamation algorithm\n";
445             break;
446         }
447
448         std::cout << "     Hazard Pointer count: " << hzpGC.max_hazard_count() << "\n"
449                   << "  Max thread count for HP: " << hzpGC.max_thread_count() << "\n"
450                   << "Retired HP array capacity: " << hzpGC.retired_array_capacity() << "\n";
451       }
452
453       if ( CppUnitMini::TestCase::m_bPrintGCState ) {
454         cds::gc::hp::GarbageCollector::InternalState stat;
455         cds::gc::hp::GarbageCollector::instance().getInternalState( stat );
456
457         std::cout << "HP constants:"
458             << "\n\tHP count per thread=" << stat.nHPCount
459             << "\n\tMax thread count=" << stat.nMaxThreadCount
460             << "\n\tMax retired pointer count per thread=" << stat.nMaxRetiredPtrCount
461             << "\n\tHP record size in bytes=" << stat.nHPRecSize
462             << "\n" << std::endl;
463       }
464
465     // Attach main thread to CDS GC
466     cds::threading::Manager::attachThread();
467
468     if (xtestName[0] != 0)
469         num_errors = CppUnitMini::TestCase::run(reporter, xtestName, true);
470     else
471         num_errors = CppUnitMini::TestCase::run(reporter, testName);
472
473     // Detach main thread from CDS GC
474     cds::threading::Manager::detachThread();
475
476   }
477
478   // Finalize CDS runtime
479   cds::Terminate();
480
481   reporter->printSummary();
482   delete reporter;
483
484   return num_errors;
485 }