New upstream release

- Update to current snapshot (commit 5d837d2) to build with gcc10
- Pass full path of Python3 to configure

Signed-off-by: Jan Friesse <jfriesse@redhat.com>
This commit is contained in:
Jan Friesse 2020-05-04 10:50:58 +02:00
parent 7b56628eba
commit c4b1800b74
11 changed files with 10 additions and 1226 deletions

View File

@ -1,158 +0,0 @@
From 9469ffc2d58a5673fffae8778b9c48f5605dda6a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Tue, 10 Jul 2018 18:41:18 +0200
Subject: [PATCH] test: remove superfluous shebangs for import-only modules
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Consequently, there's no reason to have the affected files marked as
executable.
Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
---
test/arbtests.py | 2 --
test/assertions.py | 2 --
test/boothrunner.py | 2 --
test/boothtestenv.py | 2 --
test/clientenv.py | 2 --
test/clienttests.py | 2 --
test/serverenv.py | 2 --
test/servertests.py | 2 --
test/sitetests.py | 2 --
test/utils.py | 2 --
10 files changed, 20 deletions(-)
mode change 100755 => 100644 test/arbtests.py
mode change 100755 => 100644 test/assertions.py
mode change 100755 => 100644 test/boothrunner.py
mode change 100755 => 100644 test/boothtestenv.py
mode change 100755 => 100644 test/clientenv.py
mode change 100755 => 100644 test/clienttests.py
mode change 100755 => 100644 test/serverenv.py
mode change 100755 => 100644 test/servertests.py
mode change 100755 => 100644 test/sitetests.py
mode change 100755 => 100644 test/utils.py
diff --git a/test/arbtests.py b/test/arbtests.py
old mode 100755
new mode 100644
index caba010..ef7b7f9
--- a/test/arbtests.py
+++ b/test/arbtests.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-
from servertests import ServerTests
class ArbitratorConfigTests(ServerTests):
diff --git a/test/assertions.py b/test/assertions.py
old mode 100755
new mode 100644
index 4396ab7..0b7f995
--- a/test/assertions.py
+++ b/test/assertions.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-
import re
class BoothAssertions:
diff --git a/test/boothrunner.py b/test/boothrunner.py
old mode 100755
new mode 100644
index f9154e7..d981183
--- a/test/boothrunner.py
+++ b/test/boothrunner.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-
import os
import subprocess
import time
diff --git a/test/boothtestenv.py b/test/boothtestenv.py
old mode 100755
new mode 100644
index 89a484a..fcd0c4d
--- a/test/boothtestenv.py
+++ b/test/boothtestenv.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-
import os
import subprocess
import time
diff --git a/test/clientenv.py b/test/clientenv.py
old mode 100755
new mode 100644
index fcd40fa..73b2791
--- a/test/clientenv.py
+++ b/test/clientenv.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-
from boothtestenv import BoothTestEnvironment
from boothrunner import BoothRunner
diff --git a/test/clienttests.py b/test/clienttests.py
old mode 100755
new mode 100644
index 61b691b..c4b9d8a
--- a/test/clienttests.py
+++ b/test/clienttests.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-
import string
from clientenv import ClientTestEnvironment
diff --git a/test/serverenv.py b/test/serverenv.py
old mode 100755
new mode 100644
index d0467b9..c6d4e30
--- a/test/serverenv.py
+++ b/test/serverenv.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-
import os
import re
import time
diff --git a/test/servertests.py b/test/servertests.py
old mode 100755
new mode 100644
index f574f26..39a6ffc
--- a/test/servertests.py
+++ b/test/servertests.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-
import copy
from pprint import pprint, pformat
import re
diff --git a/test/sitetests.py b/test/sitetests.py
old mode 100755
new mode 100644
index dfdf6b9..6944ffe
--- a/test/sitetests.py
+++ b/test/sitetests.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-
from servertests import ServerTests
class SiteConfigTests(ServerTests):
diff --git a/test/utils.py b/test/utils.py
old mode 100755
new mode 100644
index ceeef98..5b70cfc
--- a/test/utils.py
+++ b/test/utils.py
@@ -1,5 +1,3 @@
-#!/usr/bin/python
-
import subprocess
import re
--
2.18.0.rc2

View File

@ -1,60 +0,0 @@
From a642a833e31a6bd1e71dc2045a16e494775b35e6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Thu, 12 Jul 2018 18:58:32 +0200
Subject: [PATCH] test: do not mix tabs with spaces in Python code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Beside being matter of a good style, it's also forbidden inside
a single, non-delimited block in Python 3.
Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
---
test/servertests.py | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/test/servertests.py b/test/servertests.py
index 39a6ffc..71e808e 100644
--- a/test/servertests.py
+++ b/test/servertests.py
@@ -51,8 +51,8 @@ class ServerTests(ServerTestEnvironment):
config_text=self.working_config)
def test_missing_quotes(self):
- # quotes no longer required
- return True
+ # quotes no longer required
+ return True
orig_lines = self.working_config.split("\n")
for i in xrange(len(orig_lines)):
new_lines = copy.copy(orig_lines)
@@ -97,8 +97,8 @@ class ServerTests(ServerTestEnvironment):
expected_exitcode=None, expected_daemon=True)
def test_missing_transport(self):
- # UDP is default -- TODO?
- return True
+ # UDP is default -- TODO?
+ return True
config = re.sub('transport=.+\n', '', self.typical_config)
(pid, ret, stdout, stderr, runner) = \
self.run_booth(config_text=config, expected_exitcode=1, expected_daemon=False)
@@ -141,10 +141,10 @@ class ServerTests(ServerTestEnvironment):
self.assertRegexpMatches(stderr, 'ticket name "' + ticket + '" invalid')
def test_unreachable_peer(self):
- # what should this test do? daemon not expected, but no exitcode either?
- # booth would now just run, and try to reach that peer...
- # TCP reachability is not required during startup anymore.
- return True
+ # what should this test do? daemon not expected, but no exitcode either?
+ # booth would now just run, and try to reach that peer...
+ # TCP reachability is not required during startup anymore.
+ return True
config = re.sub('#(.+147.+)', lambda m: m.group(1), self.working_config)
self.run_booth(config_text=config,
expected_exitcode=None, expected_daemon=False)
--
2.18.0.rc2

View File

@ -1,510 +0,0 @@
From ab2229451827f530959d554920619d87daa34586 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Wed, 11 Jul 2018 16:18:25 +0200
Subject: [PATCH] test: make Python files supported _also_ with Python 3.3+
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- use "print" like a function rather than a statement
. where implicit newline is to be suppressed, don't do that
and rather strip it from the string to be printed instead
- use 2+3 compatible convention for parametrizing exceptions
- Python 3 doesn't recognize "basestring" class, and at the place
of use (pre Python 2.7 only), unicode string is really not expected
(also re.UNICODE flag is not used...)
- Python 3 doesn't recognize "xrange" function, but the surrounding
code can be reasonably simplified using "enumerate" function
- arrange dict treatment in a compatible way:
. d.has_key(k) -> k in d
. d.iteritems() -> custom "iter_items", always efficient wrapper
. d.iterkeys(), here incl. lazy mapping and filtering
-> rewrite while retaining laziness
. optimize UT.merge_dicts in script/unit-test.py along
- also in three instances, deal with string/uninterpreted bytes proper
dichotomy introduced in Python 3, and related to that, "string"
module only supports "ascii_lowercase" attribute in Python 3
(as opposed to system-specific plain "lowercase" one)
Note that script/unit-test.py has a pre-existing issue (regardless
of which Python version interpreter is used), so at least document
that in the header for now.
Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
---
script/unit-test.py | 65 ++++++++++++++++++++++++++------------------
test/assertions.py | 4 +--
test/boothrunner.py | 32 ++++++++++++----------
test/boothtestenv.py | 6 ++--
test/clienttests.py | 4 +--
test/runtests.py | 2 +-
test/serverenv.py | 18 ++++++------
test/servertests.py | 10 +++----
test/utils.py | 10 +++++--
9 files changed, 84 insertions(+), 67 deletions(-)
diff --git a/script/unit-test.py b/script/unit-test.py
index 6871930..399528e 100755
--- a/script/unit-test.py
+++ b/script/unit-test.py
@@ -1,6 +1,8 @@
#!/usr/bin/python
# 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
+# FIXME: apparently, pexpect.EOF is not being excepted properly
import os, sys, time, signal, tempfile, socket, posix, time
import re, shutil, pexpect, logging, pprint
@@ -16,6 +18,16 @@ 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():
@@ -28,9 +40,12 @@ class expect_logging():
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
@@ -110,7 +125,7 @@ class UT():
res = re.match(r"^\s*(\w+)\s*:(?:\s*(#.*?\S))?\s*$", line)
if res:
state = res.group(1)
- if not m.has_key(state):
+ if state not in m:
m[state] = dict_plus()
if res.group(2):
m[state].aux["comment"] = res.group(2)
@@ -188,17 +203,15 @@ class UT():
name = re.sub(r".*/", "", bin)
# How to get stderr, too?
expct = pexpect.spawn(bin,
- env = dict( os.environ.items() +
- [('PATH',
- self.test_base + "/bin/:" +
- os.getenv('PATH')),
- ('UNIT_TEST_PATH', self.test_base),
- ('LC_ALL', 'C'),
- ('LANG', 'C')] +
- env_add ),
- timeout = 30,
- maxread = 32768,
- **args)
+ 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)
@@ -361,7 +374,7 @@ class UT():
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 kv.iteritems():
+ for n, v in iter_items(kv):
self.set_val( self.translate_shorthand(n, "ticket"), v)
logging.info("set state")
@@ -372,7 +385,7 @@ class UT():
if not sys.stdin.isatty():
logging.error("Not a terminal, stopping.")
else:
- print "\n\nEntering interactive mode.\n\n"
+ 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.
@@ -415,7 +428,7 @@ class UT():
self.send_cmd("next")
# push message.
- for (n, v) in msg.iteritems():
+ for (n, v) in iter_items(msg):
self.set_val( self.translate_shorthand(n, "message"), v, "htonl")
# set "received" length
@@ -426,7 +439,7 @@ class UT():
def wait_outgoing(self, msg):
self.wait_for_function("booth_udp_send")
ok = True
- for (n, v) in msg.iteritems():
+ 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:
@@ -438,14 +451,12 @@ class UT():
#stopped_at = self.sync()
def merge_dicts(self, base, overlay):
- return dict(base.items() + overlay.items())
+ return dict(base, **overlay)
def loop(self, fn, data):
- matches = map(lambda k: re.match(r"^(outgoing|message)(\d+)$", k), data.iterkeys())
- valid_matches = filter(None, matches)
- nums = map(lambda m: int(m.group(2)), valid_matches)
- loop_max = max(nums)
+ 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
@@ -471,14 +482,14 @@ class UT():
logging.info("ticket change %s (%s:%d) %s" % (ktkt, fn, self.current_nr, comment))
self.set_state(tkt)
if gdb:
- for (k, v) in gdb.iteritems():
+ 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 data.has_key(kgdb) and len(gdb) == 0:
+ if kgdb in data and len(gdb) == 0:
self.user_debug("manual override")
if out:
self.current_nr = out.aux.get("line")
@@ -520,7 +531,7 @@ class UT():
self.let_booth_go_a_bit()
ok = True
- for (n, v) in data.iteritems():
+ 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)
@@ -529,8 +540,8 @@ class UT():
def run(self, start_from="000", end_with="999"):
os.chdir(self.test_base)
# TODO: sorted, random order
- tests = filter( (lambda f: re.match(r"^\d\d\d_.*\.txt$", f)), glob.glob("*"))
- tests.sort()
+ 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:
@@ -561,7 +572,7 @@ class UT():
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_traceback):
+ for frame in traceback.format_tb(sys.exc_info()[2]):
logging.info(" - %s " % frame.rstrip())
finally:
self.stop_processes()
diff --git a/test/assertions.py b/test/assertions.py
index 0b7f995..34333ca 100644
--- a/test/assertions.py
+++ b/test/assertions.py
@@ -21,7 +21,7 @@ class BoothAssertions:
# backported from 2.7 just in case we're running on an older Python
def assertRegexpMatches(self, text, expected_regexp, msg=None):
"""Fail the test unless the text matches the regular expression."""
- if isinstance(expected_regexp, basestring):
+ if isinstance(expected_regexp, str):
expected_regexp = re.compile(expected_regexp)
if not expected_regexp.search(text, MULTILINE):
msg = msg or "Regexp didn't match"
@@ -30,7 +30,7 @@ class BoothAssertions:
def assertNotRegexpMatches(self, text, unexpected_regexp, msg=None):
"""Fail the test if the text matches the regular expression."""
- if isinstance(unexpected_regexp, basestring):
+ if isinstance(unexpected_regexp, str):
unexpected_regexp = re.compile(unexpected_regexp)
match = unexpected_regexp.search(text)
if match:
diff --git a/test/boothrunner.py b/test/boothrunner.py
index d981183..347912b 100644
--- a/test/boothrunner.py
+++ b/test/boothrunner.py
@@ -1,4 +1,5 @@
import os
+import sys
import subprocess
import time
import unittest
@@ -37,14 +38,14 @@ class BoothRunner:
def show_output(self, stdout, stderr):
if stdout:
- print "STDOUT:"
- print "------"
- print stdout,
+ print("STDOUT:")
+ print("------")
+ print(stdout.rstrip('\n'))
if stderr:
- print "STDERR: (N.B. crm_ticket failures indicate daemon started correctly)"
- print "------"
- print stderr,
- print "-" * 70
+ print("STDERR: (N.B. crm_ticket failures indicate daemon started correctly)")
+ print("------")
+ print(stderr.rstrip('\n'))
+ print("-" * 70)
def subproc_completed_within(self, p, timeout):
start = time.time()
@@ -55,7 +56,7 @@ class BoothRunner:
elapsed = time.time() - start
if elapsed + wait > timeout:
wait = timeout - elapsed
- print "Waiting on %d for %.1fs ..." % (p.pid, wait)
+ print("Waiting on %d for %.1fs ..." % (p.pid, wait))
time.sleep(wait)
elapsed = time.time() - start
if elapsed >= timeout:
@@ -83,26 +84,29 @@ class BoothRunner:
return text
def show_args(self):
- print "\n"
- print "-" * 70
- print "Running", ' '.join(self.all_args())
+ print("\n")
+ print("-" * 70)
+ print("Running", ' '.join(self.all_args()))
msg = "with config from %s" % self.config_file_used()
config_text = self.config_text_used()
if config_text is not None:
msg += ": [%s]" % config_text
- print msg
+ print(msg)
def run(self):
p = subprocess.Popen(self.all_args(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if not p:
- raise RuntimeError, "failed to start subprocess"
+ raise RuntimeError("failed to start subprocess")
- print "Started subprocess pid %d" % p.pid
+ print("Started subprocess pid %d" % p.pid)
completed = self.subproc_completed_within(p, 2)
if completed:
(stdout, stderr) = p.communicate()
+ if sys.version_info[0] >= 3:
+ # only expect ASCII/UTF-8 encodings for the obtained input bytes
+ stdout, stderr = str(stdout, 'UTF-8'), str(stderr, 'UTF-8')
self.show_output(stdout, stderr)
return (p.pid, p.returncode, stdout, stderr)
diff --git a/test/boothtestenv.py b/test/boothtestenv.py
index fcd0c4d..59e25c3 100644
--- a/test/boothtestenv.py
+++ b/test/boothtestenv.py
@@ -17,7 +17,7 @@ class BoothTestEnvironment(unittest.TestCase, BoothAssertions):
def setUp(self):
if not self._testMethodName.startswith('test_'):
- raise RuntimeError, "unexpected test method name: " + self._testMethodName
+ raise RuntimeError("unexpected test method name: " + self._testMethodName)
self.test_name = self._testMethodName[5:]
self.test_path = os.path.join(self.test_run_path, self.test_name)
os.makedirs(self.test_path)
@@ -54,11 +54,11 @@ class BoothTestEnvironment(unittest.TestCase, BoothAssertions):
def check_return_code(self, pid, return_code, expected_exitcode):
if return_code is None:
- print "pid %d still running" % pid
+ print("pid %d still running" % pid)
if expected_exitcode is not None:
self.fail("expected exit code %d, not long-running process" % expected_exitcode)
else:
- print "pid %d exited with code %d" % (pid, return_code)
+ print("pid %d exited with code %d" % (pid, return_code))
if expected_exitcode is None:
msg = "should not exit"
else:
diff --git a/test/clienttests.py b/test/clienttests.py
index c4b9d8a..512620e 100644
--- a/test/clienttests.py
+++ b/test/clienttests.py
@@ -7,14 +7,14 @@ class ClientConfigTests(ClientTestEnvironment):
def test_site_buffer_overflow(self):
# https://bugzilla.novell.com/show_bug.cgi?id=750256
- longfile = (string.lowercase * 3)[:63]
+ longfile = (string.ascii_lowercase * 3)[:63]
expected_error = "'%s' exceeds maximum site name length" % longfile
args = [ 'grant', '-s', longfile, '-t', 'ticket' ]
self._test_buffer_overflow(expected_error, args=args)
def test_ticket_buffer_overflow(self):
# https://bugzilla.novell.com/show_bug.cgi?id=750256
- longfile = (string.lowercase * 3)[:63]
+ longfile = (string.ascii_lowercase * 3)[:63]
expected_error = "'%s' exceeds maximum ticket name length" % longfile
args = [ 'grant', '-s', 'site', '-t', longfile ]
self._test_buffer_overflow(expected_error, args=args)
diff --git a/test/runtests.py b/test/runtests.py
index 0532c01..833b1a7 100755
--- a/test/runtests.py
+++ b/test/runtests.py
@@ -53,5 +53,5 @@ if __name__ == '__main__':
shutil.rmtree(test_run_path)
sys.exit(0)
else:
- print "Left %s for debugging" % test_run_path
+ print("Left %s for debugging" % test_run_path)
sys.exit(1)
diff --git a/test/serverenv.py b/test/serverenv.py
index c6d4e30..5d6c6c4 100644
--- a/test/serverenv.py
+++ b/test/serverenv.py
@@ -73,12 +73,10 @@ ticket="ticketB"
where return_code/stdout/stderr are None iff pid is still running.
'''
if expected_daemon and expected_exitcode is not None and expected_exitcode != 0:
- raise RuntimeError, \
- "Shouldn't ever expect daemon to start and then failure"
+ raise RuntimeError("Shouldn't ever expect daemon to start and then failure")
if not expected_daemon and expected_exitcode == 0:
- raise RuntimeError, \
- "Shouldn't ever expect success without starting daemon"
+ raise RuntimeError("Shouldn't ever expect success without starting daemon")
self.init_log()
@@ -122,9 +120,9 @@ ticket="ticketB"
return config_file
def kill_pid(self, pid):
- print "killing %d ..." % pid
+ print("killing %d ..." % pid)
os.kill(pid, 15)
- print "killed"
+ print("killed")
def check_daemon_handling(self, runner, expected_daemon):
'''
@@ -154,7 +152,7 @@ ticket="ticketB"
Returns the pid contained in lock_file, or None if it doesn't exist.
'''
if not os.path.exists(lock_file):
- print "%s does not exist" % lock_file
+ print("%s does not exist" % lock_file)
return None
l = open(lock_file)
@@ -162,7 +160,7 @@ ticket="ticketB"
l.close()
self.assertEqual(len(lines), 1, "Lock file should contain one line")
pid = re.search('\\bbooth_pid="?(\\d+)"?', lines[0]).group(1)
- print "lockfile contains: <%s>" % pid
+ print("lockfile contains: <%s>" % pid)
return pid
def is_pid_running_daemon(self, pid):
@@ -185,11 +183,11 @@ ticket="ticketB"
c = open("/proc/%s/cmdline" % pid)
cmdline = "".join(c.readlines())
- print cmdline
+ print(cmdline)
c.close()
if cmdline.find('boothd') == -1:
- print 'no boothd in cmdline:', cmdline
+ print('no boothd in cmdline:', cmdline)
return False
# self.assertRegexpMatches(
diff --git a/test/servertests.py b/test/servertests.py
index 71e808e..288d19f 100644
--- a/test/servertests.py
+++ b/test/servertests.py
@@ -35,13 +35,13 @@ class ServerTests(ServerTestEnvironment):
def test_config_file_buffer_overflow(self):
# https://bugzilla.novell.com/show_bug.cgi?id=750256
- longfile = (string.lowercase * 5)[:127]
+ longfile = (string.ascii_lowercase * 5)[:127]
expected_error = "'%s' exceeds maximum config name length" % longfile
self._test_buffer_overflow(expected_error, config_file=longfile)
def test_lock_file_buffer_overflow(self):
# https://bugzilla.novell.com/show_bug.cgi?id=750256
- longfile = (string.lowercase * 5)[:127]
+ longfile = (string.ascii_lowercase * 5)[:127]
expected_error = "'%s' exceeds maximum lock file length" % longfile
self._test_buffer_overflow(expected_error, lock_file=longfile)
@@ -54,12 +54,12 @@ class ServerTests(ServerTestEnvironment):
# quotes no longer required
return True
orig_lines = self.working_config.split("\n")
- for i in xrange(len(orig_lines)):
+ for (i, line) in enumerate(orig_lines):
new_lines = copy.copy(orig_lines)
- new_lines[i] = new_lines[i].replace('"', '')
+ new_lines[i] = line.replace('"', '')
new_config = "\n".join(new_lines)
- line_contains_IP = re.search('^\s*(site|arbitrator)=.*[0-9]\.', orig_lines[i])
+ line_contains_IP = re.search('^\s*(site|arbitrator)=.*[0-9]\.', line)
if line_contains_IP:
# IP addresses need to be surrounded by quotes,
# so stripping them should cause it to fail
diff --git a/test/utils.py b/test/utils.py
index 5b70cfc..aca3592 100644
--- a/test/utils.py
+++ b/test/utils.py
@@ -1,5 +1,6 @@
import subprocess
import re
+import sys
def run_cmd(cmd):
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -9,8 +10,11 @@ def run_cmd(cmd):
def get_IP():
(stdout, stderr, returncode) = run_cmd(['hostname', '-i'])
if returncode != 0:
- raise RuntimeError, "Failed to run hostname -i:\n" + stderr
+ raise RuntimeError("Failed to run hostname -i:\n" + stderr)
# in case multiple IP addresses are returned, use only the first
- # and also strip '%<device>' part possibly present with IPv6 address
- ret = re.sub(r'\s.*', '', stdout)
+ # and also strip '%<device>' part possibly present with IPv6 address;
+ # in Python 3 context, only expect ASCII/UTF-8 encodings for the
+ # obtained input bytes
+ ret = re.sub(r'\s.*', '',
+ stdout if sys.version_info[0] < 3 else str(stdout, 'UTF-8'))
return "::1" if '%' in ret else ret
--
2.18.0.rc2

View File

@ -1,89 +0,0 @@
From 31133e8ac07c08b607ee7799c0074c1dce37a952 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Wed, 11 Jul 2018 14:18:50 +0200
Subject: [PATCH] build: parametrize Python invocations in the shebangs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Consequently, there's no reason to have the affected files marked as
executable (processing the files by the means of AC_CONFIG_FILES will
get rid of any such permission bits, anyway), but at the very least,
test/runtests.py needs to be set executable afterwards so as no to
cause failures with the current "make check" arrangement that invokes
TESTS subtargets directly (i.e. no extension-based LOG_COMPILER set).
Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
---
configure.ac | 12 +++++++++++-
script/{unit-test.py => unit-test.py.in} | 2 +-
test/{runtests.py => runtests.py.in} | 2 +-
3 files changed, 13 insertions(+), 3 deletions(-)
rename script/{unit-test.py => unit-test.py.in} (99%)
mode change 100755 => 100644
rename test/{runtests.py => runtests.py.in} (98%)
mode change 100755 => 100644
diff --git a/configure.ac b/configure.ac
index 3bf41b3..a6ad86e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -97,6 +97,14 @@ CPPFLAGS="$CPPFLAGS $XML2HEAD"
PKG_CHECK_MODULES(GLIB, [glib-2.0])
+# Python casing, prefer 3.3+ to 2.{6...}
+AM_PATH_PYTHON([3.3], , [PYTHON=:])
+if test "x$PYTHON" = x:; then
+ AM_PATH_PYTHON([2.6])
+fi
+PYTHON_SHEBANG="$PYTHON ${PYTHON_OPTS--Es}"
+AC_ARG_VAR([PYTHON_SHEBANG], [Python invocation used in shebangs])
+
# Checks for header files.
AC_FUNC_ALLOCA
AC_HEADER_DIRENT
@@ -157,7 +165,9 @@ 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 test/runtests.py],
+ dnl Following required at least for "make check"
+ [chmod +x test/runtests.py])
AC_CONFIG_FILES([script/service-runnable], [chmod +x script/service-runnable])
# ===============================================
diff --git a/script/unit-test.py b/script/unit-test.py.in
old mode 100755
new mode 100644
similarity index 99%
rename from script/unit-test.py
rename to script/unit-test.py.in
index 399528e..4f3cf62
--- a/script/unit-test.py
+++ b/script/unit-test.py.in
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!@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
diff --git a/test/runtests.py b/test/runtests.py.in
old mode 100755
new mode 100644
similarity index 98%
rename from test/runtests.py
rename to test/runtests.py.in
index 833b1a7..ec59159
--- a/test/runtests.py
+++ b/test/runtests.py.in
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!@PYTHON_SHEBANG@
import os
import re
--
2.18.0.rc2

View File

@ -1,36 +0,0 @@
From 541e6184fca60a01ff7e8c1bba794c083ac4245f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Tue, 10 Jul 2018 19:25:34 +0200
Subject: [PATCH] test: drop underqualified identifier in unittest's 2.6 compat
"polyfill"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Specifically, in supplemented unittest.TestCase.assertRegexpMatches
method. In Python 2.7's implementation, there's no re.MULTILINE
modifier at that very place, either.
Not sure what the original purpose of introducing that with c1c47f5 was.
Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
---
test/assertions.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/assertions.py b/test/assertions.py
index 34333ca..fafb291 100644
--- a/test/assertions.py
+++ b/test/assertions.py
@@ -23,7 +23,7 @@ class BoothAssertions:
"""Fail the test unless the text matches the regular expression."""
if isinstance(expected_regexp, str):
expected_regexp = re.compile(expected_regexp)
- if not expected_regexp.search(text, MULTILINE):
+ if not expected_regexp.search(text):
msg = msg or "Regexp didn't match"
msg = '%s: %r not found in %r' % (msg, expected_regexp.pattern, text)
raise self.failureException(msg)
--
2.18.0.rc2

View File

@ -1,90 +0,0 @@
From 6a6834a8110d9e6aff50cd6d6935976af4cbdb8f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Thu, 12 Jul 2018 20:18:07 +0200
Subject: [PATCH] test: drop/comment out superfluous imports
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
---
test/boothrunner.py | 2 --
test/boothtestenv.py | 1 -
test/runtests.py.in | 3 +--
test/serverenv.py | 2 --
test/servertests.py | 1 -
5 files changed, 1 insertion(+), 8 deletions(-)
diff --git a/test/boothrunner.py b/test/boothrunner.py
index 347912b..31c2213 100644
--- a/test/boothrunner.py
+++ b/test/boothrunner.py
@@ -1,8 +1,6 @@
-import os
import sys
import subprocess
import time
-import unittest
class BoothRunner:
default_config_file = '/etc/booth/booth.conf'
diff --git a/test/boothtestenv.py b/test/boothtestenv.py
index 59e25c3..ba54360 100644
--- a/test/boothtestenv.py
+++ b/test/boothtestenv.py
@@ -5,7 +5,6 @@ import tempfile
import unittest
from assertions import BoothAssertions
-from boothrunner import BoothRunner
class BoothTestEnvironment(unittest.TestCase, BoothAssertions):
test_src_path = os.path.abspath(os.path.dirname(__file__))
diff --git a/test/runtests.py.in b/test/runtests.py.in
index ec59159..73d70a3 100644
--- a/test/runtests.py.in
+++ b/test/runtests.py.in
@@ -1,7 +1,6 @@
#!@PYTHON_SHEBANG@
import os
-import re
import shutil
import sys
import tempfile
@@ -10,7 +9,7 @@ import unittest
from clienttests import ClientConfigTests
from sitetests import SiteConfigTests
-from arbtests import ArbitratorConfigTests
+#from arbtests import ArbitratorConfigTests
if __name__ == '__main__':
if os.geteuid() == 0:
diff --git a/test/serverenv.py b/test/serverenv.py
index 5d6c6c4..7b8915d 100644
--- a/test/serverenv.py
+++ b/test/serverenv.py
@@ -1,9 +1,7 @@
import os
import re
import time
-import unittest
-from assertions import BoothAssertions
from boothrunner import BoothRunner
from boothtestenv import BoothTestEnvironment
from utils import get_IP
diff --git a/test/servertests.py b/test/servertests.py
index 288d19f..f72dbed 100644
--- a/test/servertests.py
+++ b/test/servertests.py
@@ -1,5 +1,4 @@
import copy
-from pprint import pprint, pformat
import re
import string
--
2.18.0.rc2

View File

@ -1,178 +0,0 @@
From 34cc2fcda6804d42ee66fa5a417fc42b64fe3806 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Tue, 10 Jul 2018 19:45:56 +0200
Subject: [PATCH] test: avoid dangerous mutable/sticky default value
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Incl. slight refactoring towards more frequent use of tuples where
advantage of lists are dubious.
Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
---
script/unit-test.py.in | 34 ++++++++++++++++------------------
test/assertions.py | 2 +-
test/boothrunner.py | 14 +++++++-------
test/clientenv.py | 4 ++--
test/serverenv.py | 4 ++--
5 files changed, 28 insertions(+), 30 deletions(-)
diff --git a/script/unit-test.py.in b/script/unit-test.py.in
index 4f3cf62..fc98bc1 100644
--- a/script/unit-test.py.in
+++ b/script/unit-test.py.in
@@ -199,7 +199,7 @@ class UT():
self.booth.close( force=self.booth.isalive() )
- def start_a_process(self, bin, env_add=[], **args):
+ def start_a_process(self, bin, env_add=(), **args):
name = re.sub(r".*/", "", bin)
# How to get stderr, too?
expct = pexpect.spawn(bin,
@@ -220,16 +220,15 @@ class UT():
def start_processes(self, test):
self.booth = self.start_a_process(self.binary,
- args = [ "daemon", "-D",
- "-c", self.test_base + "/booth.conf",
- "-s", "127.0.0.1",
- "-l", self.lockfile,
- ],
- env_add=[ ('UNIT_TEST', test),
+ args = ["daemon", "-D",
+ "-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)
@@ -237,16 +236,15 @@ class UT():
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",
- ])
+ "-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")
diff --git a/test/assertions.py b/test/assertions.py
index fafb291..db6fcd8 100644
--- a/test/assertions.py
+++ b/test/assertions.py
@@ -10,7 +10,7 @@ class BoothAssertions:
self.assertRegexpMatches(stderr, expected_error)
def assertLockFileError(self, config_file=None, config_text=None,
- lock_file=True, args=[]):
+ lock_file=True, args=()):
(pid, ret, stdout, stderr, runner) = \
self.run_booth(config_text=config_text, config_file=config_file,
lock_file=lock_file, args=args, expected_exitcode=1)
diff --git a/test/boothrunner.py b/test/boothrunner.py
index 31c2213..0285fe6 100644
--- a/test/boothrunner.py
+++ b/test/boothrunner.py
@@ -8,14 +8,14 @@ class BoothRunner:
def __init__(self, boothd_path, mode, args):
self.boothd_path = boothd_path
- self.args = [ mode ]
- self.final_args = args # will be appended to self.args
+ self.args = (mode, )
+ self.final_args = tuple(args) # will be appended to self.args
self.mode = mode
self.config_file = None
self.lock_file = None
def set_config_file_arg(self):
- self.args += [ '-c', self.config_file ]
+ self.args += ('-c', self.config_file)
def set_config_file(self, config_file):
self.config_file = config_file
@@ -23,16 +23,16 @@ class BoothRunner:
def set_lock_file(self, lock_file):
self.lock_file = lock_file
- self.args += [ '-l', self.lock_file ]
+ self.args += ('-l', self.lock_file)
def set_debug(self):
- self.args += [ '-D' ]
+ self.args += ('-D', )
def set_foreground(self):
- self.args += [ '-S' ]
+ self.args += ('-S', )
def all_args(self):
- return [ self.boothd_path ] + self.args + self.final_args
+ return (self.boothd_path, ) + self.args + self.final_args
def show_output(self, stdout, stderr):
if stdout:
diff --git a/test/clientenv.py b/test/clientenv.py
index 73b2791..141e33c 100644
--- a/test/clientenv.py
+++ b/test/clientenv.py
@@ -4,8 +4,8 @@ from boothrunner import BoothRunner
class ClientTestEnvironment(BoothTestEnvironment):
mode = 'client'
- def run_booth(self, config_text=None, config_file=None, lock_file=True, args=[],
- expected_exitcode=0, debug=False):
+ def run_booth(self, config_text=None, config_file=None, lock_file=True,
+ args=(), expected_exitcode=0, debug=False):
'''
Runs boothd.
diff --git a/test/serverenv.py b/test/serverenv.py
index 7b8915d..62c37d0 100644
--- a/test/serverenv.py
+++ b/test/serverenv.py
@@ -29,7 +29,7 @@ ticket="ticketB"
def run_booth(self, expected_exitcode, expected_daemon,
config_text=None, config_file=None, lock_file=True,
- args=[], debug=False, foreground=False):
+ args=(), debug=False, foreground=False):
'''
Runs boothd. Defaults to using a temporary lock file and the
standard config file path. There are four possible types of
@@ -52,7 +52,7 @@ ticket="ticketB"
True: pass a temporary lockfile parameter to booth via -l
string: pass the given lockfile path to booth via -l
args
- array of extra args to pass to booth
+ iterable of extra args to pass to booth
expected_exitcode
an integer, or False if booth is not expected to terminate
within the timeout
--
2.18.0.rc2

View File

@ -1,41 +0,0 @@
From 0a7b51d1eb6f948724c08e94148e8ff1f448d100 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Fri, 13 Jul 2018 14:10:28 +0200
Subject: [PATCH] test: unit-test.py: daemon will not stay in foreground with
-D anymore
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Commit a66ac33 missed this impact (it may have missed impact on
test/boothrunner.py but it appears to be fine either way).
Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
---
script/unit-test.py.in | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/script/unit-test.py.in b/script/unit-test.py.in
index fc98bc1..74a014b 100644
--- a/script/unit-test.py.in
+++ b/script/unit-test.py.in
@@ -2,7 +2,6 @@
# 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
-# FIXME: apparently, pexpect.EOF is not being excepted properly
import os, sys, time, signal, tempfile, socket, posix, time
import re, shutil, pexpect, logging, pprint
@@ -220,7 +219,7 @@ class UT():
def start_processes(self, test):
self.booth = self.start_a_process(self.binary,
- args = ["daemon", "-D",
+ args = ["daemon", "-DS",
"-c", self.test_base + "/booth.conf",
"-s", "127.0.0.1",
"-l", self.lockfile],
--
2.18.0.rc2

View File

@ -1,50 +0,0 @@
From d3bf9f5ced41ad0f4e8ae87e80c7e44df4157b61 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Pokorn=C3=BD?= <jpokorny@redhat.com>
Date: Fri, 13 Jul 2018 14:40:07 +0200
Subject: [PATCH] Refactor: fix "strncpy may miss trailing null byte" warnings
of GCC 8.1
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Verbatim warning:
> strncpy specified bound 64 equals destination size [-Wstringop-truncation]
Signed-off-by: Jan Pokorný <jpokorny@redhat.com>
---
src/config.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/config.c b/src/config.c
index 9df5767..e4d36ab 100644
--- a/src/config.c
+++ b/src/config.c
@@ -75,7 +75,10 @@ static void hostname_to_ip(char * hostname)
/* Return the first found address */
if (addr_list[0] != NULL) {
- strncpy(hostname, inet_ntoa(*addr_list[0]), BOOTH_NAME_LEN);
+ strncpy(hostname, inet_ntoa(*addr_list[0]), BOOTH_NAME_LEN - 1);
+ /* buffer overflow will not happen (IPv6 notation < 63 chars),
+ but suppress the warnings */
+ hostname[BOOTH_NAME_LEN - 1] = '\0';
}
else {
log_error("no IP addresses found for the host \"%s\"", hostname);
@@ -106,7 +109,12 @@ static int add_site(char *addr_string, int type)
site->family = AF_INET;
site->type = type;
- strncpy(site->addr_string, addr_string, sizeof(site->addr_string));
+ /* buffer overflow will not hapen (we've already checked that
+ addr_string will fit incl. terminating '\0' above), but
+ suppress the warnings with copying everything but the boundary
+ byte, which is valid as-is, since this last byte will be safely
+ pre-zeroed from the struct booth_config initialization */
+ strncpy(site->addr_string, addr_string, sizeof(site->addr_string) - 1);
if (!(inet_pton(AF_INET, site->addr_string, &site->sa4.sin_addr) > 0) &&
!(inet_pton(AF_INET6, site->addr_string, &site->sa6.sin6_addr) > 0)) {
--
2.18.0.rc2

View File

@ -25,11 +25,11 @@
# Disable automatic compilation of Python files in extra directories
%global _python_bytecompile_extra 0
%global specver 3
%global specver 4
%global boothver 1.0
# set following to the actual commit or, for final release, concatenate
# "boothver" macro to "v" (will yield a tag per the convention)
%global commit f2d38ce3d61502bda2a28e79db103737a691faf4
%global commit 5d837d2b5bf1c240a5f1c5efe4e8d79f55727cca
%global lparen (
%global rparen )
%global shortcommit %(c=%{commit}; case ${c} in
@ -57,20 +57,11 @@
Name: booth
Version: %{boothver}
Release: %{boothrel}%{dist}.3
Release: %{boothrel}%{dist}
Summary: Ticket Manager for Multi-site Clusters
License: GPLv2+
Url: https://github.com/%{github_owner}/%{name}
Source0: https://github.com/%{github_owner}/%{name}/archive/%{commit}/%{name}-%{shortcommit}.tar.gz
Patch0: 0000-test-remove-superfluous-shebangs-for-import-only-mod.patch
Patch1: 0001-test-do-not-mix-tabs-with-spaces-in-Python-code.patch
Patch2: 0002-test-make-Python-files-supported-_also_-with-Python-.patch
Patch3: 0003-build-parametrize-Python-invocations-in-the-shebangs.patch
Patch4: 0004-test-drop-underqualified-identifier-in-unittest-s-2..patch
Patch5: 0005-test-drop-comment-out-superfluous-imports.patch
Patch6: 0006-test-avoid-dangerous-mutable-sticky-default-value.patch
Patch7: 0007-test-unit-test.py-daemon-will-not-stay-in-foreground.patch
Patch8: 0008-Refactor-fix-strncpy-may-miss-trailing-null-byte-war.patch
# direct build process dependencies
BuildRequires: autoconf
@ -198,7 +189,8 @@ Automated tests for running Booth, ticket manager for multi-site clusters.
--docdir=%{_pkgdocdir} \
--enable-user-flags \
%{!?with_html_man:--without-html_man} \
%{!?with_glue:--without-glue}
%{!?with_glue:--without-glue} \
PYTHON=%{__python3}
%{make_build}
%install
@ -273,6 +265,10 @@ VERBOSE=1 make check
/usr/lib/ocf/resource.d/booth/sharedrsc
%changelog
* Mon May 4 2020 Jan Friesse <jfriesse@redhat.com> - 1.0-4.5d837d2.git
- Update to current snapshot (commit 5d837d2) to build with gcc10
- Pass full path of Python3 to configure
* Tue Jan 28 2020 Fedora Release Engineering <releng@fedoraproject.org> - 1.0-3.f2d38ce.git.3
- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild

View File

@ -1 +1 @@
SHA512 (booth-f2d38ce.tar.gz) = fb172b06640959e3f33953516fa7609a09a1b16467f1dee20cbd831e07597548f74fa1d6d162cbe2cdcbb8e4b15144beeae71a4d7528f259a995f4170bbc6be6
SHA512 (booth-5d837d2.tar.gz) = 3118dd43796415c603ad200247c7905ccb3bcb71cc705848f0762f3dfeb5dcb7d3b1a935d2cee70da81328f0555d7b776eda7587c4c755fc570496a7a1dd8c49