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