From abbc4678a6bf94c174f56db14143d43fddb12975 Mon Sep 17 00:00:00 2001 From: Chris Lumens Date: Mon, 22 Apr 2024 15:34:20 -0400 Subject: [PATCH] tests: Remove the unit-tests directory. This code doesn't work and hasn't been touched in several years. There are other ways to test booth that do work, and the fact that no one has fixed these tests means that no one thinks they are important enough to get working. So, remove them. Signed-off-by: Chris Lumens --- .gitignore | 1 - Makefile.am | 3 +- README-testing | 87 ---- booth.spec.in | 2 +- configure.ac | 1 - script/unit-test.py.in | 650 ------------------------- unit-tests/001_init-get-heartbeat.txt | 25 - unit-tests/002_bad_packets.txt | 70 --- unit-tests/003_pacemaker.txt | 22 - unit-tests/010_retries.txt | 62 --- unit-tests/020_ext-verifier.txt | 53 -- unit-tests/060_catchup_same_owner.txt | 38 -- unit-tests/100_abort-after-retries.txt | 40 -- unit-tests/_defaults.txt | 55 --- unit-tests/bin/crm_ticket | 52 -- unit-tests/booth.conf | 18 - unit-tests/init-catchup.txt | 48 -- 17 files changed, 2 insertions(+), 1225 deletions(-) delete mode 100644 script/unit-test.py.in delete mode 100644 unit-tests/001_init-get-heartbeat.txt delete mode 100644 unit-tests/002_bad_packets.txt delete mode 100644 unit-tests/003_pacemaker.txt delete mode 100644 unit-tests/010_retries.txt delete mode 100644 unit-tests/020_ext-verifier.txt delete mode 100644 unit-tests/060_catchup_same_owner.txt delete mode 100644 unit-tests/100_abort-after-retries.txt delete mode 100644 unit-tests/_defaults.txt delete mode 100755 unit-tests/bin/crm_ticket delete mode 100644 unit-tests/booth.conf delete mode 100644 unit-tests/init-catchup.txt diff --git a/.gitignore b/.gitignore index 6e7ceb83..72b81d55 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ conf/booth*.service docs/*.8 docs/*.8.html script/service-runnable -script/unit-test.py src/b_config.h.in src/b_config.h src/booth_config.h diff --git a/Makefile.am b/Makefile.am index acd4c1e2..b6466580 100644 --- a/Makefile.am +++ b/Makefile.am @@ -35,12 +35,11 @@ TARFILE = $(PACKAGE_NAME)-$(VERSION).tar.gz EXTRA_DIST = autogen.sh conf/booth.conf.example \ script/booth-keygen script/lsb script/ocf script/service-runnable.in \ - script/unit-test.py.in script/wireshark-dissector.lua \ + script/wireshark-dissector.lua \ test/arbtests.py test/assertions.py test/booth_path test/boothrunner.py \ test/boothtestenv.py.in test/clientenv.py test/clienttests.py test/live_test.sh \ test/runtests.py.in test/serverenv.py test/servertests.py test/sitetests.py \ test/utils.py \ - unit-tests \ contrib \ icons \ $(SPEC).in booth-rpmlintrc \ diff --git a/README-testing b/README-testing index c5aadfee..8a97da29 100644 --- a/README-testing +++ b/README-testing @@ -141,91 +141,4 @@ BOOTH_RUNTESTS_SINGLE_INSTANCE environment variable to make tests use only single port (9929), but parallel instances will fail. - -=== Unit tests - -These use gdb and pexpect to set boothd state to some configured value, -injecting some input and looking at the output. - - # python script/unit-test.py src/boothd unit-tests/ - -Or, if using the 'booth-test' RPM, - - # python unit-test.py src/boothd unit-tests/ - -This must (currently?) be run as a non-root user; another optional argument is -the test to start from, eg. '003'. - - -Basically, boothd is started with the config file `unit-tests/booth.conf`, and -gdb gets attached to it. - -Then, some ticket state is set, incoming messages are delivered, and outgoing -messages and the state is compared to expected values. - -`unit-tests/_defaults.txt` has default values for the initial state and -message data. - - -Each test file consists of headers and key/value pairs: - --------------------- -ticket: - state ST_STABLE - -message0: # optional comment for the log file - header.cmd OP_ACCEPTING - ticket.id "asdga" - -outgoing0: - header.cmd OP_PREPARING - last_ack_ballot 42 - -finally: - new_ballot 1234 --------------------- - - -A few details to the the above example: - -* Ticket states in RAM (`ticket`, `finally`) are written in host-endianness. - -* Message data (`messageN`, `outgoingN`) are automatically converted via `htonl` resp. `ntohl`. They are delivered/checked in the order defined by the integer `N` component. - -* Strings are done via `strcpy()` - -* `ticket` and `messageN` are assignment chunks - -* `finally` and `outgoingN` are compare chunks - -* In `outgoingN` you can check _both_ message data (keys with a `.` in them) and ticket state - -* Symbolic names are useable, GDB translates them for us - -* The test scripts in `unit-tests/` need to be named with 3 digits, an underscore, some text, and `.txt` - -* The "fake" `crm_ticket` script gets the current test via `UNIT_TEST`; test scripts can pass additional information via `UNIT_TEST_AUX`. - - - -==== Tips and Hints - -There's another special header: `gdb__N__`. These lines are sent to GDB after -injecting a message, but before waiting for an outgoing line. Values that -contain `§` are sent as multiple lines to GDB. - -This means that a stanza like - --------------------- -gdb0: - watch booth_conf->ticket[0].owner § commands § bt § c § end --------------------- - -will cause a watchpoint to be set, and when it is triggered a backtrace (`bt`) -is written to the log file. - -This makes it easy to ask for additional data or check for a call-chain when -hitting bugs that can be reproduced via such a unit-test. - - # vim: set ft=asciidoc : diff --git a/booth.spec.in b/booth.spec.in index 03192454..9f67ffff 100644 --- a/booth.spec.in +++ b/booth.spec.in @@ -160,7 +160,7 @@ ln -s ../../%{_initddir}/booth-arbitrator %{buildroot}%{_sbindir}/rcbooth-arbitr #install test-parts mkdir -p %{buildroot}/%{test_path}/conf -cp -a unit-tests/ script/unit-test.py test %{buildroot}/%{test_path}/ +cp -a test %{buildroot}/%{test_path}/ cp -a conf/booth.conf.example %{buildroot}/%{test_path}/conf/ chmod +x %{buildroot}/%{test_path}/test/booth_path chmod +x %{buildroot}/%{test_path}/test/live_test.sh diff --git a/configure.ac b/configure.ac index c2ea2d76..eb1d1e19 100644 --- a/configure.ac +++ b/configure.ac @@ -161,7 +161,6 @@ AC_CONFIG_FILES([Makefile docs/Makefile conf/Makefile]) AC_CONFIG_FILES([conf/booth-arbitrator.service conf/booth@.service]) -AC_CONFIG_FILES([script/unit-test.py]) AC_CONFIG_FILES([script/service-runnable], [chmod +x script/service-runnable]) # =============================================== diff --git a/script/unit-test.py.in b/script/unit-test.py.in deleted file mode 100644 index ea071c78..00000000 --- a/script/unit-test.py.in +++ /dev/null @@ -1,650 +0,0 @@ -#!@PYTHON_SHEBANG@ -# vim: fileencoding=utf-8 -# see http://stackoverflow.com/questions/728891/correct-way-to-define-python-source-code-encoding -# NOTE: setting the encoding is needed as non-ASCII characters are contained - -import os, sys, time, signal, tempfile, socket, posix, time -import re, shutil, pexpect, logging, pprint -import random, copy, glob, traceback - - -# Don't make that much sense - function/line is write(). -# Would have to use traceback.extract_stack() manually. -# %(funcName)10.10s:%(lineno)3d %(levelname)8s -# The second ":" is to get correct syntax highlightning, -# eg. messages with ERROR etc. are red in vim. -default_log_format = '%(asctime)s: : %(message)s' -default_log_datefmt = '%b %d %H:%M:%S' - - -# Compatibility with dictionary methods not present in Python 3; -# https://www.python.org/dev/peps/pep-0469/#migrating-to-the-common-subset-of-python-2-and-3 -try: - dict.iteritems -except AttributeError: # Python 3 - iter_items = lambda d: iter(d.items()) -else: # Python 2 - iter_items = lambda d: d.iteritems() - - -# {{{ pexpect-logging glue -# needed for use as pexpect.logfile, to relay into existing logfiles -class expect_logging(): - prefix = "" - test = None - - def __init__(self, pre, inst): - self.prefix = pre - self.test = inst - - def flush(self, *arg): - pass - - def write(self, stg): - if self.test.dont_log_expect == 0: - # TODO: split by input/output, give program - if sys.version_info[0] >= 3: - stg = str(stg, 'UTF-8') - for line in re.split(r"[\r\n]+", stg): - if line == self.test.prompt: - continue - if line == "": - continue - logging.debug(" " + self.prefix + " " + line) -# }}} - - -# {{{ dictionary plus second hash -class dict_plus(dict): - def __init__(self): - self.aux = dict() - -# def aux(self): -# return self.aux -# }}} - - -class UT(): -# {{{ Members - binary = None - test_base = None - lockfile = None - - defaults = None - - this_port = None - this_site = "127.0.0.1" - this_site_id = None - running = False - - gdb = None - booth = None - prompt = "CUSTOM-GDB-PROMPT-%d-%d" % (os.getpid(), time.time()) - - dont_log_expect = 0 - current_nr = None - - udp_sock = None - - # http://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output - BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) -# }}} - - -# {{{ setup functions - @classmethod - def _filename(cls, desc): - return "/tmp/booth-unittest.%d.%s" % (os.getpid(), desc) - - - def __init__(self, bin, dir): - self.binary = os.path.realpath(bin) - self.test_base = os.path.realpath(dir) + "/" - self.defaults = self.read_test_input(self.test_base + "_defaults.txt", state="ticket") - self.lockfile = UT._filename("lock") - self.udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - - def read_test_input(self, file, state=None, m = dict()): - fo = open(file, "r") - state = None - line_nr = 0 - for line in fo.readlines(): - line_nr += 1 - - # comment? - if re.match(r"^\s*#", line): - continue - # empty line - if re.match(r"^\s*$", line): - continue - - # message resp. ticket - # We allow a comment to have something to write out to screen - res = re.match(r"^\s*(\w+)\s*:(?:\s*(#.*?\S))?\s*$", line) - if res: - state = res.group(1) - if state not in m: - m[state] = dict_plus() - if res.group(2): - m[state].aux["comment"] = res.group(2) - m[state].aux["line"] = line_nr - continue - - assert(state) - - res = re.match(r"^\s*(\S+)\s*(.*)\s*$", line) - if res: - m[state][ res.group(1) ] = res.group(2) - return m - - - def setup_log(self, **args): - global default_log_format - global default_log_datefmt - - this_test_log = logging.FileHandler( mode = "w", **args ) - this_test_log.setFormatter( - logging.Formatter(fmt = default_log_format, - datefmt = default_log_datefmt) ) - - this_test_log.emit( - logging.makeLogRecord( { - "msg": "## vim: set ft=messages : ##", - "lineno": 0, - "levelname": "None", - "level": None,} ) ) - - # in the specific files we want ALL information - this_test_log.setLevel(logging.DEBUG) - - logging.getLogger('').addHandler(this_test_log) - return this_test_log - - - def running_on_console(self): - return sys.stdout.isatty() - - - def colored_string(self, stg, color): - if self.running_on_console(): - return "\033[%dm%s\033[0m" % (30+color, stg) - return stg - - - # We want shorthand in descriptions, ie. "state" - # instead of "booth_conf->ticket[0].state". - def translate_shorthand(self, name, context): - if context == 'ticket': - return "booth_conf->ticket[0]." + name - if context == 'message': - return "msg->" + name - if context == 'inject': - return "ntohl(((struct boothc_ticket_msg *)buf)->" + name + ")" - assert(False) - - - def stop_processes(self): - if os.access(self.lockfile, os.F_OK): - os.unlink(self.lockfile) - # In case the boothd process is already dead, isalive() would still return True - # (because GDB still has it), but terminate() does fail. - # So we just quit GDB, and that might take the boothd with it - - # if not, we terminate it ourselves. - if self.gdb: - self.gdb.close( force=True ); - self.drain_booth_log() - if self.booth: - self.booth.close( force=self.booth.isalive() ) - - - def start_a_process(self, bin, env_add=(), **args): - name = re.sub(r".*/", "", bin) - # How to get stderr, too? - expct = pexpect.spawn(bin, - env=dict(os.environ, **dict({ - 'PATH': ':'.join((self.test_base + "/bin/", - os.getenv('PATH'))), - 'UNIT_TEST_PATH': self.test_base, - 'LC_ALL': 'C', - 'LANG': 'C'}, **dict(env_add))), - timeout=30, - maxread=32768, - **args) - expct.setecho(False) - expct.logfile_read = expect_logging("<- %s" % name, self) - expct.logfile_send = expect_logging(" -> %s" % name, self) - return expct - - - def start_processes(self, test): - self.booth = self.start_a_process(self.binary, - args = ["daemon", "-DS", - "-c", self.test_base + "/booth.conf", - "-s", "127.0.0.1", - "-l", self.lockfile], - env_add=( ('UNIT_TEST', test), - ('UNIT_TEST_FILE', os.path.realpath(test)), - # provide some space, so that strcpy(getenv()) works - ('UNIT_TEST_AUX', "".zfill(1024)), - )); - - logging.info("started booth with PID %d, lockfile %s" % (self.booth.pid, self.lockfile)) - self.booth.expect("BOOTH site \S+ \(build \S+\) daemon is starting", timeout=2) - #print self.booth.before; exit - - self.gdb = self.start_a_process("gdb", - args=["-quiet", - "-p", str(self.booth.pid), - # Don't use .gdbinit - "-nx", "-nh", - # Run until the defined point. - # This is necessary so that ticket state setting doesn't - # happen _before_ the call to pcmk_load_ticket() - # (which would overwrite our data) - "-ex", "break ticket_cron", - "-ex", "continue"]) - logging.info("started GDB with PID %d" % self.gdb.pid) - self.gdb.expect("(gdb)") - self.gdb.sendline("set pagination off\n") - self.gdb.sendline("set interactive-mode off\n") - self.gdb.sendline("set verbose off\n") ## sadly to late for the initial "symbol not found" messages - self.gdb.sendline("set prompt " + self.prompt + "\\n\n"); - self.sync(2000) - - # Only stop for this recipient, so that broadcasts are not seen multiple times - self.send_cmd("break booth_udp_send if to == &(booth_conf->site[1])") - self.send_cmd("break recvfrom") - # ticket_cron is still a breakpoint - - # Now we're set up. - self.this_site_id = self.query_value("local->site_id") - self.this_port = int(self.query_value("booth_conf->port")) - - # do a self-test - assert(self.check_value("local->site_id", self.this_site_id)) - - self.running = False -# }}} - - -# {{{ GDB communication - def sync(self, timeout=-1): - self.gdb.expect(self.prompt, timeout) - - answer = self.gdb.before - - self.dont_log_expect += 1 - # be careful not to use RE characters like +*.[] etc. - r = str(random.randint(2**19, 2**20)) - self.gdb.sendline("print " + r) - self.gdb.expect(r, timeout) - self.gdb.expect(self.prompt, timeout) - self.dont_log_expect -= 1 - return answer # send a command to GDB, returning the GDB answer as string. - - def drain_booth_log(self): - try: - self.booth.read_nonblocking(64*1024, 0) - except pexpect.EOF: - pass - except pexpect.TIMEOUT: - pass - finally: - pass - - def send_cmd(self, stg, timeout=-1): - # give booth a chance to get its messages out - self.drain_booth_log() - - self.gdb.sendline(stg) - return self.sync(timeout=timeout) - - def _query_value(self, which): - val = self.send_cmd("print " + which) - cleaned = re.search(r"^\$\d+ = (.*\S)\s*$", val, re.MULTILINE) - if not cleaned: - self.user_debug("query failed") - return cleaned.group(1) - - def query_value(self, which): - res = self._query_value(which) - logging.debug("query_value: «%s» evaluates to «%s»" % (which, res)) - return res - - def check_value(self, which, value): - val = self._query_value("(" + which + ") == (" + value + ")") - logging.debug("check_value: «%s» is «%s»: %s" % (which, value, val)) - if val == "1": - return True - # for easier (test) debugging we'll show the _real_ value, too. - want = self._query_value(value) - # Order is important, so that next query works!! - has = self._query_value(which) - # for informational purposes - self._query_value('state_to_string($$)') - logging.error("«%s»: got «%s», expected «%s». ERROR." % (which, has, want)) - return False - - # Send data to GDB, to inject them into the binary. - # Handles different data types - def set_val(self, name, value, numeric_conv=None): - logging.debug("setting value «%s» to «%s» (num_conv %s)" %(name, value, numeric_conv)) - res = None - # string value? - if re.match(r'^"', value): - res = self.send_cmd("print strcpy(" + name + ", " + value + ")") - elif re.match(r"^'", value): - # single-quoted; GDB only understands double quotes. - v1 = re.sub(r"^'", '', value) - v2 = re.sub(r"'$", '', v1) - # TODO: replace \\\\" etc. - v3 = re.sub(r'"', '\\"', v2) - res = self.send_cmd("print strcpy(" + name + ', "' + v3 + '")') - # numeric - elif numeric_conv: - res = self.send_cmd("set variable " + name + " = " + numeric_conv + "(" + value + ")") - else: - res = self.send_cmd("set variable " + name + " = " + value) - for r in [r"There is no member named", - r"Structure has no component named", - r"No symbol .* in current context", ]: - assert(not re.search(r, res, re.MULTILINE)) - logging.debug("set_val %s done" % name) -# }}} GDB communication - - - # there has to be some event waiting, so that boothd stops again. - def continue_debuggee(self, timeout=30): - res = None - if not self.running: - res = self.send_cmd("continue", timeout) - self.drain_booth_log() - return res - - -# {{{ High-level functions. -# Generally, GDB is attached to BOOTHD, and has it stopped. - def set_state(self, kv): - if not kv: - return - - self.current_nr = kv.aux.get("line") - #os.system("strace -f -tt -s 2000 -e write -p" + str(self.gdb.pid) + " &") - for n, v in iter_items(kv): - self.set_val( self.translate_shorthand(n, "ticket"), v) - logging.info("set state") - - - def user_debug(self, txt): - logging.error("Problem detected: %s", txt) - logging.info(self.gdb.buffer) - if not sys.stdin.isatty(): - logging.error("Not a terminal, stopping.") - else: - print("\n\nEntering interactive mode.\n\n") - self.gdb.sendline("set prompt GDB> \n") - self.gdb.setecho(True) - # can't use send_cmd, doesn't reply with expected prompt anymore. - self.gdb.interact() - #while True: - # sys.stdout.write("GDB> ") - # sys.stdout.flush() - # x = sys.stdin.readline() - # if not x: - # break - # self.send_cmd(x) - self.stop_processes() - sys.exit(1) - - - def wait_for_function(self, fn, timeout=20): - until = time.time() + timeout - while True: - stopped_at = self.continue_debuggee(timeout=3) - if not stopped_at: - self.user_debug("Not stopped at any breakpoint?") - if re.search(r"^Program received signal SIGABRT,", stopped_at, re.MULTILINE): - self.user_debug("assert() failed") - if re.search(r"^Program received signal SIGSEGV,", stopped_at, re.MULTILINE): - self.user_debug("Segfault") - if re.search(r"^Breakpoint \d+, (0x\w+ in )?%s " % fn, stopped_at, re.MULTILINE): - break - if time.time() > until: - self.user_debug("Didn't stop in function %s" % fn) - logging.info("Now in %s" % fn) - - # We break, change the data, and return the correct size. - def send_message(self, msg): - self.udp_sock.sendto('a', (socket.gethostbyname(self.this_site), self.this_port)) - - self.wait_for_function("recvfrom") - # drain input, but stop afterwards for changing data - self.send_cmd("finish") - # step over length assignment - self.send_cmd("next") - - # push message. - for (n, v) in iter_items(msg): - self.set_val( self.translate_shorthand(n, "message"), v, "htonl") - - # set "received" length - self.set_val("rv", "msg->header.length", "ntohl") - - # the next thing should run continue via wait_for_function - - def wait_outgoing(self, msg): - self.wait_for_function("booth_udp_send") - ok = True - for (n, v) in iter_items(msg): - if re.search(r"\.", n): - ok = self.check_value( self.translate_shorthand(n, "inject"), v) and ok - else: - ok = self.check_value( self.translate_shorthand(n, "ticket"), v) and ok - - if not ok: - sys.exit(1) - logging.info("out gone") - #stopped_at = self.sync() - - def merge_dicts(self, base, overlay): - return dict(base, **overlay) - - - def loop(self, fn, data): - matches = (re.match(r"^(outgoing|message)(\d+)$", k) for k in data) - loop_max = max(int(m.group(2)) for m in matches if m) - for counter in range(0, loop_max+1): # incl. last message - - kmsg = 'message%d' % counter - msg = data.get(kmsg) - - ktkt = 'ticket%d' % counter - tkt = data.get(ktkt) - - kout = 'outgoing%d' % counter - out = data.get(kout) - - kgdb = 'gdb%d' % counter - gdb = data.get(kgdb) - - - if not any([msg, out, tkt]): - continue - - logging.info("Part %d" % counter) - if tkt: - self.current_nr = tkt.aux.get("line") - comment = tkt.aux.get("comment", "") - logging.info("ticket change %s (%s:%d) %s" % (ktkt, fn, self.current_nr, comment)) - self.set_state(tkt) - if gdb: - for (k, v) in iter_items(gdb): - self.send_cmd(k + " " + v.replace("§", "\n")) - if msg: - self.current_nr = msg.aux.get("line") - comment = msg.aux.get("comment", "") - logging.info("sending %s (%s:%d) %s" % (kmsg, fn, self.current_nr, comment)) - self.send_message(self.merge_dicts(data["message"], msg)) - if kgdb in data and len(gdb) == 0: - self.user_debug("manual override") - if out: - self.current_nr = out.aux.get("line") - comment = out.aux.get("comment", "") - logging.info("waiting for %s (%s:%d) %s" % (kout, fn, self.current_nr, comment)) - self.wait_outgoing(out) - logging.info("loop ends") - - - def let_booth_go_a_bit(self): - self.drain_booth_log() - logging.debug("running: %d" % self.running) - - if not self.running: - self.gdb.sendline("continue") - time.sleep(1) - self.drain_booth_log() - # stop it - via GDB! - self.gdb.sendintr() - # If we sent the signal to booth, the next - # "print state_to_string()" or "continue" - # might catch the signal - and fail to do - # what we want/need. - # - # This additional signal seems to be unnecessary. - #posix.kill(self.gdb.pid, signal.SIGINT) - # In case it's really needed we should drain booth's signals queue, - # eg. by sending "print getpid()" twice, before the sync() call. - self.running = False - self.sync(2000) - - - def do_finally(self, data): - if not data: - return - - self.current_nr = data.aux.get("line") - # Allow debuggee to reach a stable state - self.let_booth_go_a_bit() - - ok = True - for (n, v) in iter_items(data): - ok = self.check_value( self.translate_shorthand(n, "ticket"), v) and ok - if not ok: - sys.exit(1) - - - def run(self, start_from="000", end_with="999"): - os.chdir(self.test_base) - # TODO: sorted, random order - tests = sorted(f for f in glob.glob("*") - if re.match(r"^\d\d\d_.*\.txt$", f)) - failed = 0 - for f in tests: - if f[0:3] < start_from: - continue - if f[0:3] > end_with: - continue - log = None - logfn = UT._filename(f) - if self.running_on_console(): - sys.stdout.write("\n") - self.current_nr = "setup" - try: - log = self.setup_log(filename = logfn) - - log.setLevel(logging.DEBUG) - logging.error(self.colored_string("Starting test '%s'" % f, self.BLUE) + ", logfile " + logfn) - self.start_processes(f) - - test = self.read_test_input(f, m=copy.deepcopy(self.defaults)) - logging.debug("data: %s" % pprint.pformat(test, width = 200)) - - self.set_state(test.get("ticket")) - self.loop(f, test) - self.do_finally(test.get("finally")) - - self.current_nr = "teardown" - logging.warn(self.colored_string("Finished test '%s' - OK" % f, self.GREEN)) - except: - failed += 1 - logging.error(self.colored_string("Broke in %s:%s %s" % (f, self.current_nr, sys.exc_info()), self.RED)) - for frame in traceback.format_tb(sys.exc_info()[2]): - logging.info(" - %s " % frame.rstrip()) - finally: - self.stop_processes() - if log: - log.close() - logging.getLogger("").removeHandler(log) - if self.running_on_console(): - sys.stdout.write("\n") - return failed -# }}} - - -#def traceit(frame, event, arg): -# if event == "line": -# lineno = frame.f_lineno -# print frame.f_code.co_filename, ":", "line", lineno -# return traceit - - -# {{{ main -if __name__ == '__main__': - # Likely assumption for the root exclusion is the amount of risk - # associated with what naturally accompanies root privileges: - # - accidental overwrite (eventually also deletion) of unrelated, - # legitimate and perhaps vital files - # - accidental termination of unrelated, legitimate and perhaps - # vital processes - # - and so forth, possibly amplified with awkward parallel test - # suite run scenarios (containers partly sharing state, etc.) - # - # Nonetheless, there are cases like self-contained CI runs where - # all these concerns are absent, so allow opt-in relaxing of this. - # Alternatively, the config generator could inject particular - # credentials for a booth proces to use, but that might come too - # late to address the above concerns reliably. - if (os.geteuid() == 0 - and "--allow-root-user" not in sys.argv - and not(os.environ.get("BOOTH_UNITTEST_ROOT_USER"))): - sys.stderr.write("Must be run non-root; aborting.\n") - sys.exit(1) - - - ut = UT(sys.argv[1], sys.argv[2] + "/") - - # "master" log object needs max level - logging.basicConfig(level = logging.DEBUG, - filename = "/dev/null", - filemode = "a", - format = default_log_format, - datefmt = default_log_datefmt) - - - # make sure no old processes are active anymore - os.system("killall boothd > /dev/null 2> /dev/null") - - overview_log = ut.setup_log( filename = UT._filename('seq') ) - overview_log.setLevel(logging.WARN) - - # http://stackoverflow.com/questions/9321741/printing-to-screen-and-writing-to-a-file-at-the-same-time - console = logging.StreamHandler() - console.setFormatter(logging.Formatter(' # %(message)s')) - console.setLevel(logging.WARN) - logging.getLogger('').addHandler(console) - - - logging.info("Starting boothd unit tests.") - - #sys.settrace(traceit) - - starting = "0" - if len(sys.argv) > 3: - starting = sys.argv[3] - ending = "999" - if len(sys.argv) > 4: - ending = sys.argv[4] - ret = ut.run(starting, ending) - sys.exit(ret) -# }}} diff --git a/unit-tests/001_init-get-heartbeat.txt b/unit-tests/001_init-get-heartbeat.txt deleted file mode 100644 index fa79a066..00000000 --- a/unit-tests/001_init-get-heartbeat.txt +++ /dev/null @@ -1,25 +0,0 @@ -# vim: ft=sh et : - - -ticket: - state ST_FOLLOWER - current_term 1 - leader 0 - -# should be accepted -message0: # valid heartbeat - header.cmd OP_HEARTBEAT - header.result RLT_SUCCESS - header.from booth_conf->site[2].site_id - ticket.leader booth_conf->site[2].site_id - ticket.term_valid_for 3 - ticket.term 20 - -# nothing goes out - - -# after a delay, check final state -finally: -# should be overwritten - current_term 20 - leader booth_conf->site+2 diff --git a/unit-tests/002_bad_packets.txt b/unit-tests/002_bad_packets.txt deleted file mode 100644 index 8a708055..00000000 --- a/unit-tests/002_bad_packets.txt +++ /dev/null @@ -1,70 +0,0 @@ -# vim: ft=sh et : -# -# This test is mostly concerned with ignoring invalid packets. -# We're expecting heartbeat packets. - - -ticket: - state ST_LEADER - leader local - current_term 500 - - -# defaults -message: - header.cmd OP_HEARTBEAT - ticket.term 500 - #header.from booth_conf->site[2].site_id - header.from local->site_id - header.result 0 - - -message0: # bad result code - header.result 243521741 - -outgoing0: - state ST_LEADER - - -message1: # bad sender - header.from 71 - -outgoing1: - state ST_LEADER - - - -message2: # bad version - header.version 512 - -outgoing2: - state ST_LEADER - - -message3: # bad magic - header.version 31 - -outgoing3: - state ST_LEADER - -message4: # bad length - header.length 16 - -outgoing4: - state ST_LEADER - - -message5: # bad ticket ID - ticket.id "gibtsnich" - -outgoing5: - state ST_LEADER - - -message100: # should work - ticket.term 510 - -# no outgoing message -finally: - state ST_FOLLOWER - current_term 510 diff --git a/unit-tests/003_pacemaker.txt b/unit-tests/003_pacemaker.txt deleted file mode 100644 index 2b039a7b..00000000 --- a/unit-tests/003_pacemaker.txt +++ /dev/null @@ -1,22 +0,0 @@ -# vim: ft=sh et : -# -# Checks whether Pacemaker gets correct command lines. - - -ticket: - state ST_FOLLOWER - current_term 40 - term_expires time(0) + 30 - - -message0: - header.cmd OP_HEARTBEAT - header.from booth_conf->site[2].site_id - header.result 0 - ticket.term 44 - ticket.leader booth_conf->site[2].site_id - #getenv("UNIT_TEST_AUX") "ballot 99 owner +" - - -finally: - current_term 44 diff --git a/unit-tests/010_retries.txt b/unit-tests/010_retries.txt deleted file mode 100644 index 9eaeabf6..00000000 --- a/unit-tests/010_retries.txt +++ /dev/null @@ -1,62 +0,0 @@ -# vim: ft=sh et : -# -# Testing whether retries are sent, and if they're stopped again. - - -ticket: - state ST_LEADER - current_term 40 - leader local - retries 10000 # needed so that heartbeats are sent _now_ - timeout 1 - # may keep ticket all the time - term_duration 3000 - # but shall start renewal now - term_expires time(0) + 1000 - - - -outgoing0: - header.cmd OP_HEARTBEAT - ticket.term 40 -outgoing1: - header.cmd OP_HEARTBEAT - ticket.term 40 -outgoing2: - header.cmd OP_HEARTBEAT - ticket.term 40 -outgoing3: - header.cmd OP_HEARTBEAT - ticket.term 40 - -# yes, you're the leader. -message4: - header.cmd OP_HEARTBEAT - header.from booth_conf->site[2].site_id - header.result 0 - ticket.term 40 - ticket.leader local->site_id - -# doesn't stop ... there is no retry limit - -outgoing5: - header.cmd OP_HEARTBEAT -outgoing6: - header.cmd OP_HEARTBEAT -outgoing7: - header.cmd OP_HEARTBEAT -outgoing8: - header.cmd OP_HEARTBEAT -outgoing9: - header.cmd OP_HEARTBEAT -outgoing10: - header.cmd OP_HEARTBEAT - - -# Now term expires -ticket11: - term_expires time(0) - 1 - -# no outgoing message, gets to be follower -finally: - state ST_FOLLOWER diff --git a/unit-tests/020_ext-verifier.txt b/unit-tests/020_ext-verifier.txt deleted file mode 100644 index b1a5e9d4..00000000 --- a/unit-tests/020_ext-verifier.txt +++ /dev/null @@ -1,53 +0,0 @@ -# vim: ft=sh et : -# -# Testing whether the external verifier (before-acquire-handler) -# is obeyed. - - -ticket: - name "tick1" - state ST_LEADER - current_term 40 - leader local - # may keep ticket all the time - term_duration 3000 - # but shall start renewal now - term_expires time(0) + 1000 - req_sent_at time(0) - 10 - - -gdb0: - call parse_extprog("test `set|grep ^BOOTH|wc -l` -ge 5", booth_conf->ticket+0) - -outgoing0: - header.cmd OP_HEARTBEAT - - -ticket1: - ext_verifier 'test "$BOOTH_TICKET" == "tick1"' - # cause re-query of the verifier - req_sent_at time(0) - 10 - -# -#gdb1: -# break ticket_broadcast_proposed_state § commands § bt § c § end - - -outgoing1: - header.cmd OP_HEARTBEAT - - -# now say that we may not have it anymore. -ticket2: - ext_verifier 'test "$BOOTH_TICKET" == "tick2FOO"' - # cause re-query of the verifier - req_sent_at time(0) - 10 - -# We just tell the others we don't have it anymore. -outgoing2: - header.cmd OP_REQ_VOTE - ticket.leader -1 - -finally: - state ST_FOLLOWER - leader NULL diff --git a/unit-tests/060_catchup_same_owner.txt b/unit-tests/060_catchup_same_owner.txt deleted file mode 100644 index 73899cda..00000000 --- a/unit-tests/060_catchup_same_owner.txt +++ /dev/null @@ -1,38 +0,0 @@ -# vim: ft=sh et : -# We've got the ticket; a peer agrees with us re. owner, but has a -# higher term number. -# We must not lose the ticket. - -ticket: - state ST_LEADER - current_term 100 - leader local - term_expires time(0) + 35 - term_duration 3000 - retries 6 - timeout 1 - hb_sent_at time(0) - 2000 - - -gdb0: - watch booth_conf->ticket[0].leader § commands § bt § c § end - -# No message0 - -outgoing0: - header.cmd OP_HEARTBEAT - header.result RLT_SUCCESS - ticket.term 100 - ticket.leader local->site_id - - -message1: # same owner - header.cmd OP_HEARTBEAT - header.result RLT_SUCCESS - header.from booth_conf->site[2].site_id - ticket.term 110 - ticket.leader local->site_id - -finally: - leader local - diff --git a/unit-tests/100_abort-after-retries.txt b/unit-tests/100_abort-after-retries.txt deleted file mode 100644 index 881d7858..00000000 --- a/unit-tests/100_abort-after-retries.txt +++ /dev/null @@ -1,40 +0,0 @@ -# vim: ft=sh et : -# -# Testing whether retries are aborted at some time. - - -ticket: - state ST_STABLE - last_ack_ballot 40 - new_ballot 50 - retries 6 - timeout 1 - owner local - expiry 3000 - # but renewing is necessary - expires time(0) + 100 - next_cron time(0) + 1 - - -outgoing0: - header.cmd OP_PREPARING -outgoing1: - header.cmd OP_PREPARING -outgoing2: - header.cmd OP_PREPARING -outgoing3: - header.cmd OP_PREPARING - -# Now give cause to abort. -ticket4: - expires time(0) - 2 - retry_number 10 - timeout 2 -outgoing4: - header.cmd CMD_CATCHUP - - -# ticket must be lost -finally: - owner 0 - state ST_INIT diff --git a/unit-tests/_defaults.txt b/unit-tests/_defaults.txt deleted file mode 100644 index 46feb969..00000000 --- a/unit-tests/_defaults.txt +++ /dev/null @@ -1,55 +0,0 @@ -# vim: ft=sh et : - - -# ticket defaults, mostly set via config file. -ticket: - - name "ticket" - - ## these would matter if testing via GDB had high latencies - #expiry 60 - #timeout 10 - acquire_after 0 - - - - # defaults for all tests - state ST_INIT - next_cron.tv_sec 0 - -# time(0)+1 - # local is site[0] per convention - - leader booth_conf->site+1 - #owner booth_conf->site+1 - #expires time(0)+1 - term_expires.tv_sec time(0)+1 - #last_ack_ballot 242 - - leader 0 - #proposer 0 - #proposed_owner 0 - #new_ballot 0 - #proposal_acknowledges 0 - #retry_number 0 - - - -# defaults for input message. -# sender is a peer, and it wants something. -message: - - ticket.id "ticket" - # invalid by default - header.cmd -1 - # invalid by default - header.result 1 - # invalid by default - header.from -1 - header.version BOOTHC_VERSION - header.magic BOOTHC_MAGIC - header.length sizeof(struct boothc_ticket_msg) - ticket.leader -1 - ticket.term 0 - ticket.term_valid_for 0 - diff --git a/unit-tests/bin/crm_ticket b/unit-tests/bin/crm_ticket deleted file mode 100755 index cf697616..00000000 --- a/unit-tests/bin/crm_ticket +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -# -# "fake" crm_ticket for unit tests. -# * Ignores set,grant,revoke actions -# * returns values it gets asked for from UNIT_TEST_AUX - - - -function word_after() -{ -# Per default $1 is printed - perl -e ' - $search=shift(); - $stg=shift(); - print $'${3:-1}' if $stg =~ /$search/;' "$1" "$2" -} - - -function Get() -{ - which=$(word_after " -G ('?)(\\w+)\\1" "$1" 2) - if [[ -z "$which" ]] ; then - exit 1 - fi - - word_after "\\b$which\\b (\\S+)" "$UNIT_TEST_AUX" - # provide a newline - echo "" - exit 0 -} - - - -# include resp. supersede with per-test functions -inc="${UNIT_TEST_FILE%.txt}.sh" -if [[ -e "$inc" ]] ; then - . "$inc" -fi - - -if [[ "$*" == *" -G "* ]] ; then - Get "$*" -elif [[ "$*" == *" -g "* ]] ; then - : # grant -elif [[ "$*" == *" -r "* ]] ; then - : # revoke -elif [[ "$*" == *" -S "* ]] ; then - : # set -else - echo "not understood" >&2 - exit 1 -fi diff --git a/unit-tests/booth.conf b/unit-tests/booth.conf deleted file mode 100644 index 5a69acb4..00000000 --- a/unit-tests/booth.conf +++ /dev/null @@ -1,18 +0,0 @@ -# "local" -site=127.0.0.1 - -# these should not exist, although it shouldn't matter much -# because no packets should be sent anyway -arbitrator="127.0.0.243" -site="127.0.0.244" - -# The ticket name, which corresponds to a set of resources which can be -# fail-overed among different sites. -ticket="ticket" - expire = 60 - timeout = 1 - acquire-after = 30 - weights = 1,2,3 - - # make it non-critical, put provide enough space to override if necessary. - before-acquire-handler = "//////////////////////////////////bin/true" diff --git a/unit-tests/init-catchup.txt b/unit-tests/init-catchup.txt deleted file mode 100644 index c681e89d..00000000 --- a/unit-tests/init-catchup.txt +++ /dev/null @@ -1,48 +0,0 @@ -# vim: ft=sh et : - - -ticket: - state ST_INIT - last_ack_ballot 1 - new_ballot 2012 - -# No message0 -# expecting catchup query - -# outgoing packet: expect this data -outgoing0: - header.cmd CMD_CATCHUP - header.result RLT_SUCCESS - - -# ignore "bad" catchup data -message1: - header.from booth_conf->site[2].site_id - header.cmd CMR_CATCHUP - header.result 243521741 - -outgoing1: - header.cmd CMD_CATCHUP - header.result RLT_SUCCESS - - -# accept good CMR_CATCHUP -message2: - header.cmd CMR_CATCHUP - header.result RLT_SUCCESS - header.from booth_conf->site[2].site_id - ticket.ballot 2062 - ticket.prev_ballot 2052 - ticket.owner -1 - -# nothing goes out - - -# after a delay, check final state -finally: -# should be overwritten - last_ack_ballot 2052 -# should not be - a OP_PREPARING would fetch the new value - new_ballot 2012 -# too low-level -# proposal_acknowledges 5