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, errno
31import random
32import sys
33import subprocess
34
35def gen(limit=4):
36	return random.randint(0, 2 ** (8 * limit))
37
38def negative():
39	return random.randint(0, 1) == 1
40
41def zero():
42	return random.randint(0, 2 ** (8) - 1) == 0
43
44def num(op, neg, real, z, limit=4):
45
46	if z:
47		z = zero()
48	else:
49		z = False
50
51	if z:
52		return 0
53
54	if neg:
55		neg = negative()
56
57	g = gen(limit)
58
59	if real and negative():
60		n = str(gen(25))
61		length = gen(7 / 8)
62		if len(n) < length:
63			n = ("0" * (length - len(n))) + n
64	else:
65		n = "0"
66
67	g = str(g)
68	if n != "0":
69		g = g + "." + n
70
71	if neg and g != "0":
72		if op != modexp:
73			g = "-" + g
74		else:
75			g = "_" + g
76
77	return g
78
79
80def add(test, op):
81
82	tests.append(test)
83	gen_ops.append(op)
84
85def compare(exe, options, p, test, halt, expected, op, do_add=True):
86
87	if p.returncode != 0:
88
89		print("    {} returned an error ({})".format(exe, p.returncode))
90
91		if do_add:
92			print("    adding to checklist...")
93			add(test, op)
94
95		return
96
97	actual = p.stdout.decode()
98
99	if actual != expected:
100
101		if op >= exponent:
102
103			indata = "scale += 10; {}; {}".format(test, halt)
104			args = [ exe, options ]
105			p2 = subprocess.run(args, input=indata.encode(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
106			expected = p2.stdout[:-10].decode()
107
108			if actual == expected:
109				print("    failed because of bug in other {}".format(exe))
110				print("    continuing...")
111				return
112
113		if do_add:
114			print("   failed; adding to checklist...")
115			add(test, op)
116		else:
117			print("   failed {}".format(test))
118			print("    expected:")
119			print("        {}".format(expected))
120			print("    actual:")
121			print("        {}".format(actual))
122
123
124def gen_test(op):
125
126	scale = num(op, False, False, True, 5 / 8)
127
128	if op < div:
129		s = fmts[op].format(scale, num(op, True, True, True), num(op, True, True, True))
130	elif op == div or op == mod:
131		s = fmts[op].format(scale, num(op, True, True, True), num(op, True, True, False))
132	elif op == power:
133		s = fmts[op].format(scale, num(op, True, True, True, 7 / 8), num(op, True, False, True, 6 / 8))
134	elif op == modexp:
135		s = fmts[op].format(scale, num(op, True, False, True), num(op, True, False, True),
136		                    num(op, True, False, False))
137	elif op == sqrt:
138		s = "1"
139		while s == "1":
140			s = num(op, False, True, True, 1)
141		s = fmts[op].format(scale, s)
142	else:
143
144		if op == exponent:
145			first = num(op, True, True, True, 6 / 8)
146		elif op == bessel:
147			first = num(op, False, True, True, 6 / 8)
148		else:
149			first = num(op, True, True, True)
150
151		if op != bessel:
152			s = fmts[op].format(scale, first)
153		else:
154			s = fmts[op].format(scale, first, 6 / 8)
155
156	return s
157
158def run_test(t):
159
160	op = random.randrange(bessel + 1)
161
162	if op != modexp:
163		exe = "bc"
164		halt = "halt"
165		options = "-lq"
166	else:
167		exe = "dc"
168		halt = "q"
169		options = ""
170
171	test = gen_test(op)
172
173	if "c(0)" in test or "scale = 4; j(4" in test:
174		return
175
176	bcexe = exedir + "/" + exe
177	indata = test + "\n" + halt
178
179	print("Test {}: {}".format(t, test))
180
181	if exe == "bc":
182		args = [ exe, options ]
183	else:
184		args = [ exe ]
185
186	p = subprocess.run(args, input=indata.encode(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
187
188	output1 = p.stdout.decode()
189
190	if p.returncode != 0 or output1 == "":
191		print("    other {} returned an error ({}); continuing...".format(exe, p.returncode))
192		return
193
194	if output1 == "\n":
195		print("   other {} has a bug; continuing...".format(exe))
196		return
197
198	if output1 == "-0\n":
199		output1 = "0\n"
200	elif output1 == "-0":
201		output1 = "0"
202
203	args = [ bcexe, options ]
204
205	p = subprocess.run(args, input=indata.encode(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
206	compare(exe, options, p, test, halt, output1, op)
207
208
209if __name__ != "__main__":
210	sys.exit(1)
211
212script = sys.argv[0]
213testdir = os.path.dirname(script)
214
215exedir = testdir + "/../bin"
216
217ops = [ '+', '-', '*', '/', '%', '^', '|' ]
218files = [ "add", "subtract", "multiply", "divide", "modulus", "power", "modexp",
219          "sqrt", "exponent", "log", "arctangent", "sine", "cosine", "bessel" ]
220funcs = [ "sqrt", "e", "l", "a", "s", "c", "j" ]
221
222fmts = [ "scale = {}; {} + {}", "scale = {}; {} - {}", "scale = {}; {} * {}",
223         "scale = {}; {} / {}", "scale = {}; {} % {}", "scale = {}; {} ^ {}",
224         "{}k {} {} {}|pR", "scale = {}; sqrt({})", "scale = {}; e({})",
225         "scale = {}; l({})", "scale = {}; a({})", "scale = {}; s({})",
226         "scale = {}; c({})", "scale = {}; j({}, {})" ]
227
228div = 3
229mod = 4
230power = 5
231modexp = 6
232sqrt = 7
233exponent = 8
234bessel = 13
235
236gen_ops = []
237tests = []
238
239try:
240	i = 0
241	while True:
242		run_test(i)
243		i = i + 1
244except KeyboardInterrupt:
245	pass
246
247if len(tests) == 0:
248	print("\nNo items in checklist.")
249	print("Exiting")
250	sys.exit(0)
251
252print("\nGoing through the checklist...\n")
253
254if len(tests) != len(gen_ops):
255	print("Corrupted checklist!")
256	print("Exiting...")
257	sys.exit(1)
258
259for i in range(0, len(tests)):
260
261	print("\n{}".format(tests[i]))
262
263	op = int(gen_ops[i])
264
265	if op != modexp:
266		exe = "bc"
267		halt = "halt"
268		options = "-lq"
269	else:
270		exe = "dc"
271		halt = "q"
272		options = ""
273
274	indata = tests[i] + "\n" + halt
275
276	args = [ exe, options ]
277
278	p = subprocess.run(args, input=indata.encode(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
279
280	expected = p.stdout.decode()
281
282	bcexe = exedir + "/" + exe
283	args = [ bcexe, options ]
284
285	p = subprocess.run(args, input=indata.encode(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
286
287	compare(exe, options, p, tests[i], halt, expected, op, False)
288
289	answer = input("\nAdd test ({}/{}) to test suite? [y/N]: ".format(i + 1, len(tests)))
290
291	if 'Y' in answer or 'y' in answer:
292
293		print("Yes")
294
295		name = testdir + "/" + exe + "/" + files[op]
296
297		with open(name + ".txt", "a") as f:
298			f.write(tests[i] + "\n")
299
300		with open(name + "_results.txt", "a") as f:
301			f.write(expected)
302
303	else:
304		print("No")
305
306print("Done!")
307