source: tags/svn.1.5.4/UNIT_TESTER/TestEnvironment.cxx

Last change on this file was 8309, checked in by westram, 14 years ago
  • moved much code into static scope

(partly reverted by [8310])

File size: 19.4 KB
Line 
1// ================================================================= //
2//                                                                   //
3//   File      : TestEnvironment.cxx                                 //
4//   Purpose   : Global! setup/cleanup for tests                     //
5//                                                                   //
6//   Coded by Ralf Westram (coder@reallysoft.de) in September 2010   //
7//   Institute of Microbiology (Technical University Munich)         //
8//   http://www.arb-home.de/                                         //
9//                                                                   //
10// ================================================================= //
11
12#define SIMPLE_ARB_ASSERT
13
14#include <arb_defs.h>
15#include <arb_str.h>
16#include <arb_file.h>
17#include <arbdb.h>
18#include <PT_com.h>
19#include <client.h>
20#include <servercntrl.h>
21
22#include "test_unit.h"
23#include "UnitTester.hxx"
24
25#include <string>
26#include <set>
27#include <unistd.h>
28
29#include <sys/stat.h>
30#include <sys/time.h>
31
32
33#define env_assert(cond) arb_assert(cond)
34
35// #define SIMULATE_ENVSETUP_TIMEOUT // makes tests fail
36// #define SIMULATE_ENVSETUP_SLOW_OVERTIME // should not make tests fail
37
38using namespace arb_test;
39using namespace std;
40
41enum Mode { UNKNOWN, SETUP, CLEAN };
42static const char *mode_command[] = { NULL, "setup", "clean" }; // same order as in enum Mode
43static Mode other_mode[] = { UNKNOWN, CLEAN, SETUP }; 
44
45typedef SmartCharPtr Error;
46
47static const char *upcase(const char *str) {
48    char *upstr = strdup(str);
49    ARB_strupper(upstr);
50    RETURN_LOCAL_ALLOC(upstr);
51}
52
53#define WARN_NOT_IMPLEMENTED(fun,mode) TEST_WARNING2("%s(%s) does nothing", #fun, upcase(mode_command[mode]));
54
55static const char *unitTesterDir() {
56    RETURN_ONETIME_ALLOC(strdup(GB_concat_full_path(GB_getenvARBHOME(), "UNIT_TESTER")));
57}
58static const char *flagDir() {
59    RETURN_ONETIME_ALLOC(strdup(GB_concat_full_path(unitTesterDir(), FLAGS_DIR)));
60}
61static const char *runDir() {
62    RETURN_ONETIME_ALLOC(strdup(GB_concat_full_path(unitTesterDir(), "run")));
63}
64static char *mutexDir(const char *name) {
65    return strdup(GB_concat_full_path(flagDir(), GBS_global_string("mutex_%s", name)));
66}
67
68#if defined(DEBUG)
69// #define DUMP_MUTEX_ACCESS
70#endif
71
72
73class Mutex : virtual Noncopyable {
74    string name;
75    string mutexdir;
76
77    bool mutexdir_exists() const { return GB_is_directory(mutexdir.c_str()); }
78
79    static const char *now() {
80        static char buf[100];
81        timeval t;
82        gettimeofday(&t, NULL);
83        sprintf(buf, "%li.%li", t.tv_sec, t.tv_usec);
84        return buf;
85    }
86
87    static set<string> known_mutexes;
88
89public:
90    Mutex(const char *mutex_name)
91        : name(mutex_name)
92    {
93        mutexdir = mutexDir(mutex_name);
94
95        bool gotMutex = false;
96        int  maxwait  = MAX_EXEC_MS_SLOW-5; // seconds
97        int  wait     = 0;
98
99        arb_assert(maxwait>0);
100
101        test_data().entered_mutex_loop = true; // avoid race-condition
102        while (!gotMutex) {
103            if (mutexdir_exists()) {
104                if (wait>maxwait && mutexdir_exists()) {
105                    GBK_terminatef("[%s] Failed to get mutex for more than %i seconds", now(), maxwait);
106                }
107                printf("[%s] mutex '%s' exists.. sleeping\n", now(), name.c_str());
108                sleep(1);
109                wait++;
110            }
111            else {
112                int res = mkdir(mutexdir.c_str(), S_IRWXU);
113                if (res == 0) {
114#if defined(DUMP_MUTEX_ACCESS)
115                    printf("[%s] allocated mutex '%s'\n", now(), name.c_str());
116#endif
117                    gotMutex = true;
118                }
119                else {
120                    wait = 0; // reset timeout
121                    printf("[%s] lost race for mutex '%s'\n", now(), name.c_str());
122                }
123            }
124        }
125        known_mutexes.insert(name);
126    }
127    ~Mutex() {
128        if (!mutexdir_exists()) {
129            printf("[%s] Strange - mutex '%s' has vanished\n", now(), name.c_str());
130        }
131        else {
132            if (rmdir(mutexdir.c_str()) != 0) {
133                const char *error = GB_IO_error("remove", mutexdir.c_str());
134                GBK_terminatef("[%s] Failed to release mutex dir (%s)", now(), error);
135            }
136            else {
137#if defined(DUMP_MUTEX_ACCESS)
138                printf("[%s] released mutex '%s'\n", now(), mutexdir.c_str());
139#endif
140            }
141        }
142        known_mutexes.erase(name);
143    }
144
145    static bool owned(const char *mutex_name) {
146        return known_mutexes.find(mutex_name) != known_mutexes.end();
147    }
148
149    static void own_or_terminate(const char *mutex_name) {
150        if (!Mutex::owned(mutex_name)) {
151            GBK_terminatef("Expected to own mutex '%s'", mutex_name);
152        }
153    }
154};
155
156set<string> Mutex::known_mutexes;
157
158// -----------------------
159//      PersistantFlag
160
161static const char *FLAG_MUTEX = "flag_access";
162
163class PersistantFlag {
164    // persistant flags keep their value even if the program is restarted(!)
165
166    string       name;
167    mutable bool value; // has to be mutable to support volatile behavior
168    bool         is_volatile;
169
170    const char *flagFileName() const {
171        return GB_concat_full_path(flagDir(), GBS_global_string("%s." FLAGS_EXT, name.c_str()));
172    }
173
174    bool flagFileExists() const {
175        Mutex::own_or_terminate(FLAG_MUTEX);
176        return GB_is_regularfile(flagFileName());
177    }
178
179    void createFlagFile() const {
180        Mutex::own_or_terminate(FLAG_MUTEX);
181        const char *flagfile = flagFileName();
182        FILE       *fp       = fopen(flagfile, "w");
183        if (!fp) {
184            GB_ERROR error = GB_IO_error("creating flag", flagfile);
185            HERE.errorf(true, "%s\n", error);
186        }
187        fclose(fp);
188        env_assert(flagFileExists());
189    }
190    void removeFlagFile() const {
191        const char *flagfile = flagFileName();
192        StaticCode::printf("flagfile='%s'\n", flagfile);
193        if (!flagFileExists()) {
194            GBK_dump_backtrace(stderr, "tried to remove non-existing flagfile");
195        }
196        int res = unlink(flagfile);
197        if (res != 0) {
198            GB_ERROR error = GB_IO_error("unlinking", flagfile);
199            HERE.errorf(true, "%s\n", error);
200        }
201        env_assert(!flagFileExists());
202    }
203    void updateFlagFile() const {
204        if (value) createFlagFile();
205        else removeFlagFile();
206    }
207
208public:
209    PersistantFlag(const string& name_)
210        : name(name_),
211          value(flagFileExists()), 
212          is_volatile(false)
213    {}
214    PersistantFlag(const string& name_, bool is_volatile_)
215        : name(name_),
216          value(flagFileExists()),
217          is_volatile(is_volatile_)
218    {}
219    ~PersistantFlag() {
220        if (flagFileExists() != bool(*this)) {
221            StaticCode::printf("Mismatch between internal value(=%i) and flagfile='%s'\n", int(value), name.c_str());
222        }
223        env_assert(flagFileExists() == value);
224    }
225    operator bool() const {
226        if (is_volatile) value = flagFileExists();
227        return value;
228    }
229
230    PersistantFlag& operator = (bool b) {
231        if (b != bool(*this)) {
232            StaticCode::printf("Changing flag '%s' to %i\n", name.c_str(), int(b));
233            value = b;
234            updateFlagFile();
235        }
236        return *this;
237    }
238};
239
240class LazyPersistantFlag {
241    // wrapper for PersistantFlag - instanciates on r/w access
242
243    SmartPtr<PersistantFlag> flag;
244
245    string name;
246    bool   will_be_volatile;
247
248    void instanciate() {
249        flag = new PersistantFlag(name, will_be_volatile);
250    }
251    void instanciate_on_demand() const {
252        if (flag.isNull()) {
253            const_cast<LazyPersistantFlag*>(this)->instanciate();
254        }
255    }
256
257public:
258    LazyPersistantFlag(const char *name_) : name(strdup(name_)), will_be_volatile(false) {}
259    LazyPersistantFlag(const char *name_, bool is_volatile_) : name(strdup(name_)), will_be_volatile(is_volatile_) {}
260
261    operator bool() const {
262        instanciate_on_demand();
263        return *flag;
264    }
265
266    LazyPersistantFlag& operator = (bool b) {
267        instanciate_on_demand();
268        *flag = b;
269        return *this;
270    }
271
272    void get_lazy_again() { flag.SetNull(); }
273};
274
275
276// --------------------------------------------------------------------------------
277// add environment setup/cleanup functions here (and add name to environments[] below)
278//
279// each environment
280// - is setup on client-request (using TEST_SETUP_GLOBAL_ENVIRONMENT)
281// - is cleaned up after ALL unit tests were executed (only if it has been setup)
282//
283// Functions start in same directory where tests starts.
284//
285// Note: you cannot simply share data between different modes since they are
286//       executed in different instances of the executable 'test_environment'!
287//
288//       Use 'PersistantFlag' to share bools between instances.
289// --------------------------------------------------------------------------------
290
291// -----------------
292//      ptserver
293
294static void test_ptserver_activate(bool start, int serverid) {
295    const char *server_tag = GBS_ptserver_tag(serverid);
296    if (start) {
297        TEST_ASSERT_NO_ERROR(arb_look_and_start_server(AISC_MAGIC_NUMBER, server_tag));
298    }
299    else { // stop
300        GB_ERROR kill_error = arb_look_and_kill_server(AISC_MAGIC_NUMBER, server_tag);
301        if (kill_error) TEST_ASSERT_EQUAL(kill_error, "Server is not running");
302    }
303}
304
305static Error ptserver(Mode mode) {
306    // test-ptserver is restarted and rebuild.
307    // This is done only once in the complete test suite.
308    //
309    // every unit-test using the test-ptserver should simply call
310    // TEST_SETUP_GLOBAL_ENVIRONMENT("ptserver");
311
312    Error error;
313    switch (mode) {
314        case SETUP: {
315            test_ptserver_activate(false, TEST_SERVER_ID);                     // first kill pt-server (otherwise we may test an outdated pt-server)
316            TEST_ASSERT_NO_ERROR(GBK_system("cp TEST_pt_src.arb TEST_pt.arb")); // force rebuild
317            test_ptserver_activate(true, TEST_SERVER_ID);
318            TEST_ASSERT_FILES_EQUAL("TEST_pt.arb.pt.expected", "TEST_pt.arb.pt");
319            TEST_ASSERT(GB_time_of_file("TEST_pt.arb.pt") >= GB_time_of_file("TEST_pt.arb"));
320            break;
321        }
322        case CLEAN: {
323            test_ptserver_activate(false, TEST_SERVER_ID);
324            TEST_ASSERT_ZERO_OR_SHOW_ERRNO(unlink("TEST_pt.arb.pt"));
325            break;
326        }
327        case UNKNOWN:
328            env_assert(0);
329            break;
330    }
331
332    return error;
333}
334
335static Error ptserver_gene(Mode mode) {
336    // test-gene-ptserver is restarted and rebuild.
337    // This is done only once in the complete test suite.
338    //
339    // every unit-test using the test-gene-ptserver should simply call
340    // TEST_SETUP_GLOBAL_ENVIRONMENT("ptserver_gene");
341
342// #define TEST_AUTO_UPDATE
343
344    Error error;
345    switch (mode) {
346        case SETUP: {
347            test_ptserver_activate(false, TEST_GENESERVER_ID);                     // first kill pt-server (otherwise we may test an outdated pt-server)
348            TEST_ASSERT_NO_ERROR(GBK_system("arb_gene_probe TEST_gpt_src.arb TEST_gpt.arb")); // prepare gene-ptserver-db (forcing rebuild)
349
350#if defined(TEST_AUTO_UPDATE)
351            TEST_COPY_FILE("TEST_gpt.arb", "TEST_gpt.arb.expected");
352#else // !defined(TEST_AUTO_UPDATE)
353            TEST_ASSERT_FILES_EQUAL("TEST_gpt.arb.expected", "TEST_gpt.arb");
354#endif
355
356            test_ptserver_activate(true, TEST_GENESERVER_ID);
357
358#if defined(TEST_AUTO_UPDATE)
359            TEST_COPY_FILE("TEST_gpt.arb.pt", "TEST_gpt.arb.pt.expected");
360#else // !defined(TEST_AUTO_UPDATE)
361            TEST_ASSERT_FILES_EQUAL("TEST_gpt.arb.pt.expected", "TEST_gpt.arb.pt");
362#endif
363
364            TEST_ASSERT(GB_time_of_file("TEST_gpt.arb.pt") >= GB_time_of_file("TEST_gpt.arb"));
365            break;
366        }
367        case CLEAN: {
368            test_ptserver_activate(false, TEST_GENESERVER_ID);
369            TEST_ASSERT_ZERO_OR_SHOW_ERRNO(unlink("TEST_gpt.arb.pt"));
370            break;
371        }
372        case UNKNOWN:
373            env_assert(0);
374            break;
375    }
376
377#undef TEST_AUTO_UPDATE
378   
379    return error;
380}
381
382// --------------------------------------------------------------------------------
383
384typedef Error (*Environment_cb)(Mode mode);
385
386static Environment_cb wrapped_cb   = NULL;
387static Mode           wrapped_mode = UNKNOWN;
388static SmartCharPtr   wrapped_error;
389
390static void wrapped() { wrapped_error = wrapped_cb(wrapped_mode); }
391
392class FunInfo {
393    Environment_cb     cb;
394    string             name;
395    LazyPersistantFlag is_setup;
396    LazyPersistantFlag changing; // some process is currently setting up/cleaning the environment
397
398    void all_get_lazy_again() {
399        changing.get_lazy_again();
400        is_setup.get_lazy_again();
401    }
402
403    Error set_to(Mode mode) {
404        StaticCode::printf("[%s environment '%s' START]\n", mode_command[mode], get_name());
405
406        wrapped_cb   = cb;
407        wrapped_mode = mode;
408        wrapped_error.SetNull();
409
410        chdir(runDir());
411
412        long           duration;
413        UnitTestResult guard_says = execute_guarded(wrapped, &duration, MAX_EXEC_MS_ENV, false);
414        Error          error;
415
416        switch (guard_says) {
417            case TEST_OK:
418                if (wrapped_error.isSet()) {
419                    error = GBS_global_string_copy("returns error: %s", &*wrapped_error);
420                }
421                break;
422
423            case TEST_TRAPPED:
424            case TEST_INTERRUPTED:  {
425                const char *what_happened = guard_says == TEST_TRAPPED
426                    ? "trapped"
427                    : "has been interrupted (might be a deaklock)";
428
429                error = GBS_global_string_copy("%s%s",
430                                               what_happened,
431                                               wrapped_error.isSet() ? GBS_global_string(" (wrapped_error='%s')", &*wrapped_error) : "");
432                break;
433            }
434        }
435        if (error.isSet()) {
436            error = GBS_global_string_copy("%s(%s) %s", name.c_str(), upcase(mode_command[mode]), &*error);
437        }
438        else {
439            Mutex m(FLAG_MUTEX);
440
441            env_assert(changing);
442            is_setup = (mode == SETUP);
443#if defined(SIMULATE_ENVSETUP_TIMEOUT)
444            if (mode == SETUP) {
445                StaticCode::printf("[simulating a timeout during SETUP]\n");
446                sleepms(MAX_EXEC_MS_ENV+MAX_EXEC_MS_SLOW+100); // simulate a timeout
447            }
448#endif
449#if defined(SIMULATE_ENVSETUP_SLOW_OVERTIME)
450            if (mode == SETUP) {
451                StaticCode::printf("[simulating overtime during SETUP]\n");
452                sleepms(MAX_EXEC_MS_SLOW+100); // simulate overtime
453            }
454#endif
455            changing = false;
456            all_get_lazy_again();
457        }
458
459        StaticCode::printf("[%s environment '%s' END]\n", mode_command[mode], get_name());
460        return error;
461    }
462
463public:
464
465    FunInfo(Environment_cb cb_, const char *name_)
466        : cb(cb_),
467          name(name_),
468          is_setup(name_),
469          changing(GBS_global_string("changing_%s", name_))
470    {}
471
472    Error switch_to(Mode mode) { // @@@ need to return allocated msg (it gets overwritten)
473        Error error;
474        bool  want_setup = (mode == SETUP);
475
476        bool perform_change  = false;
477        bool wait_for_change = false;
478        {
479            Mutex m(FLAG_MUTEX);
480
481            if (changing) { // somebody is changing the environment state
482                if (is_setup == want_setup) { // wanted state was reached, but somebody is altering it
483                    error = GBS_global_string_copy("[environment '%s' was %s, but somebody is changing it to %s]",
484                                                   get_name(), mode_command[mode], mode_command[other_mode[mode]]);
485                }
486                else { // the somebody is changing to my wanted state
487                    wait_for_change = true;
488                }
489            }
490            else {
491                if (is_setup == want_setup) {
492                    StaticCode::printf("[environment '%s' already was %s]\n", get_name(), mode_command[mode]);
493                }
494                else {
495                    changing = perform_change = true;
496                }
497            }
498
499            all_get_lazy_again();
500        }
501
502        env_assert(!(perform_change && wait_for_change));
503
504        if (perform_change) set_to(mode);
505
506        if (wait_for_change) {
507            bool reached = false;
508            while (!reached) {
509                StaticCode::printf("[waiting until environment '%s' reaches '%s']\n", get_name(), mode_command[mode]);
510                sleep(1);
511                {
512                    Mutex m(FLAG_MUTEX);
513                    if (!changing && is_setup == want_setup) reached = true;
514                    all_get_lazy_again();
515                }
516            }
517            StaticCode::printf("[environment '%s' has reached '%s']\n", get_name(), mode_command[mode]);
518        }
519
520        return error;
521    }
522
523    const char *get_name() const { return name.c_str(); }
524    bool has_name(const char *oname) const { return name == oname; }
525};
526
527#define FUNINFO(fun) FunInfo(fun,#fun)
528
529static FunInfo modules[] = {
530    FUNINFO(ptserver),
531    FUNINFO(ptserver_gene),
532};
533
534const size_t MODULES = ARRAY_ELEMS(modules);
535
536static FunInfo *find_module(const char *moduleName) {
537    for (size_t e = 0; e<MODULES; ++e) {
538        if (modules[e].has_name(moduleName)) {
539            return &modules[e];
540        }
541    }
542    return NULL;
543}
544
545static Error set_all_modules_to(Mode mode) {
546    Error error;
547    for (size_t e = 0; error.isNull() && e<MODULES; ++e) {
548        error = modules[e].switch_to(mode);
549    }
550    return error;
551}
552
553static const char *known_modules(char separator) {
554    const char *mods = NULL;
555    for (size_t i = 0; i<MODULES; ++i) {
556        mods = mods
557            ? GBS_global_string("%s%c%s", mods, separator, modules[i].get_name())
558            : modules[i].get_name();
559    }
560    return mods;
561}
562static const char *known_modes(char separator) {
563    const char *modes = NULL;
564    for (size_t i = 0; i<ARRAY_ELEMS(mode_command); ++i) {
565        if (mode_command[i]) {
566            modes = modes
567                ? GBS_global_string("%s%c%s", modes, separator, mode_command[i])
568                : mode_command[i];
569        }
570    }
571    return modes;
572}
573
574
575int main(int argc, char* argv[]) {
576    Error error;
577    bool  showUsage = true;
578
579    if (argc<2 || argc>3) error = strdup("Wrong number of arguments");
580    else {
581        const char *modearg = argv[1];
582        Mode        mode    = UNKNOWN;
583
584        for (size_t i = 0; i<ARRAY_ELEMS(mode_command); ++i) {
585            if (mode_command[i] && strcmp(modearg, mode_command[i]) == 0) mode = (Mode)i;
586        }
587
588        if (mode == UNKNOWN) {
589            error = GBS_global_string_copy("unknown argument '%s' (known modes are: %s)", modearg, known_modes(' '));
590        }
591        else {
592            if (argc == 2) {
593                error     = set_all_modules_to(mode);
594                showUsage = false;
595            }
596            else {
597                const char *modulearg = argv[2];
598                FunInfo    *module    = find_module(modulearg);
599
600                if (!module) {
601                    error = GBS_global_string_copy("unknown argument '%s' (known modules are: %s)", modulearg, known_modules(' '));
602                }
603                else {
604                    error     = module->switch_to(mode);
605                    showUsage = false;
606                }
607            }
608        }
609    }
610
611    if (error.isSet()) {
612        const char *exename = argv[0];
613        StaticCode::printf("Error in %s: %s\n", exename, &*error);
614        if (showUsage) StaticCode::printf("Usage: %s [%s] [%s]\n", exename, known_modes('|'), known_modules('|'));
615        return EXIT_FAILURE;
616    }
617
618    return EXIT_SUCCESS;
619}
620
Note: See TracBrowser for help on using the repository browser.