2019-04-04 21:04:49 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
from argparse import ArgumentParser
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import subprocess
|
|
|
|
import re
|
|
|
|
import glob
|
2019-04-29 09:14:16 +00:00
|
|
|
import threading
|
|
|
|
import time
|
2019-04-04 21:04:49 +00:00
|
|
|
|
|
|
|
DESCRIPTION = """Regressor is a tool to run regression tests in a CI env."""
|
|
|
|
|
2019-04-29 09:14:16 +00:00
|
|
|
class PrintDotsThread(object):
|
|
|
|
"""Prints a dot every "interval" (default is 300) seconds"""
|
|
|
|
|
|
|
|
def __init__(self, interval=300):
|
|
|
|
self.interval = interval
|
|
|
|
|
|
|
|
thread = threading.Thread(target=self.run, args=())
|
|
|
|
thread.daemon = True
|
|
|
|
thread.start()
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
""" Runs until the main Python thread exits. """
|
|
|
|
## Print a newline at the very beginning.
|
|
|
|
print("")
|
|
|
|
while True:
|
|
|
|
# Print dot
|
|
|
|
print(".")
|
|
|
|
time.sleep(self.interval)
|
2019-04-04 21:04:49 +00:00
|
|
|
|
|
|
|
class regressor():
|
|
|
|
_re_sanitizer_log = re.compile(r"""ERROR: (?P<sanitizer>\w+).*""")
|
2019-04-24 16:46:45 +00:00
|
|
|
_error_blacklist = ["AddressSanitizer", "libFuzzer"]
|
2019-04-04 21:04:49 +00:00
|
|
|
|
|
|
|
def __init__(self, description, args):
|
|
|
|
self._description = description
|
|
|
|
self._args = self.parseCmdLine(description, args)
|
|
|
|
self._repo_root = os.path.dirname(sys.path[0])
|
|
|
|
self._fuzzer_path = os.path.join(self._repo_root,
|
|
|
|
"build/test/tools/ossfuzz")
|
|
|
|
self._logpath = os.path.join(self._repo_root, "test_results")
|
|
|
|
|
|
|
|
def parseCmdLine(self, description, args):
|
|
|
|
argParser = ArgumentParser(description)
|
|
|
|
argParser.add_argument('-o', '--out-dir', required=True, type=str,
|
|
|
|
help="""Directory where test results will be written""")
|
|
|
|
return argParser.parse_args(args)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def run_cmd(command, logfile=None, env=None):
|
2019-04-29 10:38:11 +00:00
|
|
|
"""
|
|
|
|
Args:
|
|
|
|
command (str): command to run
|
|
|
|
logfile (str): log file name
|
|
|
|
env (dict): dictionary holding key-value pairs for bash environment
|
|
|
|
variables
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
int: The exit status of the command. Exit status codes are:
|
|
|
|
0 -> Success
|
|
|
|
1-255 -> Failure
|
|
|
|
"""
|
2019-04-04 21:04:49 +00:00
|
|
|
if not logfile:
|
|
|
|
logfile = os.devnull
|
|
|
|
|
|
|
|
if not env:
|
|
|
|
env = os.environ.copy()
|
|
|
|
|
|
|
|
logfh = open(logfile, 'w')
|
|
|
|
proc = subprocess.Popen(command, shell=True, executable='/bin/bash',
|
|
|
|
env=env, stdout=logfh,
|
|
|
|
stderr=subprocess.STDOUT)
|
|
|
|
ret = proc.wait()
|
|
|
|
logfh.close()
|
2019-04-29 10:38:11 +00:00
|
|
|
return ret
|
2019-04-04 21:04:49 +00:00
|
|
|
|
|
|
|
def process_log(self, logfile):
|
2019-04-29 10:38:11 +00:00
|
|
|
"""
|
|
|
|
Args:
|
|
|
|
logfile (str): log file name
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
bool: Test status.
|
|
|
|
True -> Success
|
|
|
|
False -> Failure
|
|
|
|
int: Number of suppressed memory leaks
|
|
|
|
"""
|
|
|
|
|
2019-04-26 09:47:46 +00:00
|
|
|
## Log may contain non ASCII characters, so we simply stringify them
|
|
|
|
## since they don't matter for regular expression matching
|
|
|
|
rawtext = str(open(logfile, 'rb').read())
|
|
|
|
list = re.findall(self._re_sanitizer_log, rawtext)
|
2019-04-04 21:04:49 +00:00
|
|
|
numSuppressedLeaks = list.count("LeakSanitizer")
|
2019-04-24 16:46:45 +00:00
|
|
|
rv = any(word in list for word in self._error_blacklist)
|
|
|
|
return not rv, numSuppressedLeaks
|
2019-04-04 21:04:49 +00:00
|
|
|
|
|
|
|
def run(self):
|
2019-04-29 10:38:11 +00:00
|
|
|
"""
|
|
|
|
Returns:
|
|
|
|
bool: Test status.
|
|
|
|
True -> All tests succeeded
|
|
|
|
False -> At least one test failed
|
|
|
|
"""
|
|
|
|
|
|
|
|
testStatus = []
|
2019-04-04 21:04:49 +00:00
|
|
|
for fuzzer in glob.iglob("{}/*_ossfuzz".format(self._fuzzer_path)):
|
|
|
|
basename = os.path.basename(fuzzer)
|
|
|
|
logfile = os.path.join(self._logpath, "{}.log".format(basename))
|
|
|
|
corpus_dir = "/tmp/solidity-fuzzing-corpus/{0}_seed_corpus" \
|
|
|
|
.format(basename)
|
2019-04-29 10:38:11 +00:00
|
|
|
cmd = "find {0} -type f | xargs -n1 {1}".format(corpus_dir, fuzzer)
|
|
|
|
cmd_status = self.run_cmd(cmd, logfile=logfile)
|
|
|
|
if cmd_status:
|
2019-04-04 21:04:49 +00:00
|
|
|
ret, numLeaks = self.process_log(logfile)
|
|
|
|
if not ret:
|
|
|
|
print(
|
2019-04-29 10:38:11 +00:00
|
|
|
"\t[-] libFuzzer reported failure for {0}. "
|
2019-04-04 21:04:49 +00:00
|
|
|
"Failure logged to test_results".format(
|
|
|
|
basename))
|
2019-04-29 10:38:11 +00:00
|
|
|
testStatus.append(cmd_status)
|
2019-04-04 21:04:49 +00:00
|
|
|
else:
|
|
|
|
print("\t[+] {0} passed regression tests but leaked "
|
|
|
|
"memory.".format(basename))
|
|
|
|
print("\t\t[+] Suppressed {0} memory leak reports".format(
|
|
|
|
numLeaks))
|
2019-04-29 10:38:11 +00:00
|
|
|
testStatus.append(0)
|
2019-04-04 21:04:49 +00:00
|
|
|
else:
|
|
|
|
print("\t[+] {0} passed regression tests.".format(basename))
|
2019-04-29 10:38:11 +00:00
|
|
|
testStatus.append(cmd_status)
|
|
|
|
return any(testStatus)
|
2019-04-04 21:04:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2019-04-29 09:14:16 +00:00
|
|
|
dotprinter = PrintDotsThread()
|
2019-04-04 21:04:49 +00:00
|
|
|
tool = regressor(DESCRIPTION, sys.argv[1:])
|
2019-04-29 10:38:11 +00:00
|
|
|
sys.exit(tool.run())
|