1#! /usr/bin/python3 -B
2#
3# SPDX-License-Identifier: BSD-2-Clause
4#
5# Copyright (c) 2018-2021 Gavin D. Howard and contributors.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are met:
9#
10# * Redistributions of source code must retain the above copyright notice, this
11#   list of conditions and the following disclaimer.
12#
13# * Redistributions in binary form must reproduce the above copyright notice,
14#   this list of conditions and the following disclaimer in the documentation
15#   and/or other materials provided with the distribution.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27# POSSIBILITY OF SUCH DAMAGE.
28#
29
30import os
31import sys
32import shutil
33import subprocess
34
35def usage():
36	print("usage: {} [--asan] dir [results_dir [exe options...]]".format(script))
37	sys.exit(1)
38
39def check_crash(exebase, out, error, file, type, test):
40	if error < 0:
41		print("\n{} crashed ({}) on {}:\n".format(exebase, -error, type))
42		print("    {}".format(test))
43		print("\nCopying to \"{}\"".format(out))
44		shutil.copy2(file, out)
45		print("\nexiting...")
46		sys.exit(error)
47
48def run_test(cmd, exebase, tout, indata, out, file, type, test, environ=None):
49	try:
50		p = subprocess.run(cmd, timeout=tout, input=indata, stdout=subprocess.PIPE,
51		                   stderr=subprocess.PIPE, env=environ)
52		check_crash(exebase, out, p.returncode, file, type, test)
53	except subprocess.TimeoutExpired:
54		print("\n    {} timed out. Continuing...\n".format(exebase))
55
56def create_test(file, tout, environ=None):
57
58	print("    {}".format(file))
59
60	base = os.path.basename(file)
61
62	if base == "README.txt":
63		return
64
65	with open(file, "rb") as f:
66		lines = f.readlines()
67
68	print("        Running whole file...")
69
70	run_test(exe + [ file ], exebase, tout, halt.encode(), out, file, "file", file, environ)
71
72	print("        Running file through stdin...")
73
74	with open(file, "rb") as f:
75		content = f.read()
76
77	run_test(exe, exebase, tout, content, out, file,
78	         "running {} through stdin".format(file), file, environ)
79
80
81def get_children(dir, get_files):
82	dirs = []
83	with os.scandir(dir) as it:
84		for entry in it:
85			if not entry.name.startswith('.') and     \
86			   ((entry.is_dir() and not get_files) or \
87			    (entry.is_file() and get_files)):
88				dirs.append(entry.name)
89	dirs.sort()
90	return dirs
91
92
93def exe_name(d):
94	return "bc" if d == "bc1" or d == "bc2" or d == "bc3" else "dc"
95
96script = sys.argv[0]
97testdir = os.path.dirname(script)
98
99if __name__ != "__main__":
100	usage()
101
102timeout = 2.5
103
104if len(sys.argv) < 2:
105	usage()
106
107idx = 1
108
109exedir = sys.argv[idx]
110
111asan = (exedir == "--asan")
112
113if asan:
114	idx += 1
115	if len(sys.argv) < idx + 1:
116		usage()
117	exedir = sys.argv[idx]
118
119print("exedir: {}".format(exedir))
120
121if len(sys.argv) >= idx + 2:
122	resultsdir = sys.argv[idx + 1]
123else:
124	if exedir == "bc1":
125		resultsdir = testdir + "/fuzzing/bc_outputs1"
126	elif exedir == "bc2":
127		resultsdir = testdir + "/fuzzing/bc_outputs2"
128	elif exedir == "bc3":
129		resultsdir = testdir + "/fuzzing/bc_outputs3"
130	else:
131		resultsdir = testdir + "/fuzzing/dc_outputs"
132
133print("resultsdir: {}".format(resultsdir))
134
135if len(sys.argv) >= idx + 3:
136	exe = sys.argv[idx + 2]
137else:
138	exe = testdir + "/../bin/" + exe_name(exedir)
139
140exebase = os.path.basename(exe)
141
142if exebase == "bc":
143	halt = "halt\n"
144	options = "-lq"
145else:
146	halt = "q\n"
147	options = "-x"
148
149if len(sys.argv) >= idx + 4:
150	exe = [ exe, sys.argv[idx + 3:], options ]
151else:
152	exe = [ exe, options ]
153for i in range(4, len(sys.argv)):
154	exe.append(sys.argv[i])
155
156out = testdir + "/../.test.txt"
157
158print(os.path.realpath(os.getcwd()))
159
160dirs = get_children(resultsdir, False)
161
162if asan:
163	env = os.environ.copy()
164	env['ASAN_OPTIONS'] = 'abort_on_error=1:allocator_may_return_null=1'
165
166for d in dirs:
167
168	d = resultsdir + "/" + d
169
170	print(d)
171
172	files = get_children(d + "/crashes/", True)
173
174	for file in files:
175		file = d + "/crashes/" + file
176		create_test(file, timeout)
177
178	if not asan:
179		continue
180
181	files = get_children(d + "/queue/", True)
182
183	for file in files:
184		file = d + "/queue/" + file
185		create_test(file, timeout * 2, env)
186
187print("Done")
188
189