Browse Source

big: Improved test driver.

Jeroen van Rijn 4 years ago
parent
commit
2179cc2bc7
3 changed files with 100 additions and 85 deletions
  1. 5 3
      core/math/big/build.bat
  2. 1 1
      core/math/big/common.odin
  3. 94 81
      core/math/big/test.py

+ 5 - 3
core/math/big/build.bat

@@ -1,6 +1,8 @@
 @echo off
 :odin run   . -vet
-odin build . -build-mode:shared -show-timings -o:speed
-:odin build . -build-mode:shared -show-timings
+:odin build . -build-mode:shared -show-timings -o:minimal -use-separate-modules
+odin build . -build-mode:shared -show-timings -o:size -use-separate-modules
+:odin build . -build-mode:shared -show-timings -o:speed -use-separate-modules
 
-python test.py
+python test.py
+:del big.*

+ 1 - 1
core/math/big/common.odin

@@ -49,7 +49,7 @@ Int :: struct {
 /*
 	Errors are a strict superset of runtime.Allocation_Error.
 */
-Error :: enum byte {
+Error :: enum int {
 	None                   = 0,
 	Out_Of_Memory          = 1,
 	Invalid_Pointer        = 2,

+ 94 - 81
core/math/big/test.py

@@ -4,24 +4,7 @@ from random import *
 import os
 import platform
 import time
-
-#
-# Fast tests?
-#
-FAST_TESTS = True
-
-#
-# Where is the DLL? If missing, build using: `odin build . -build-mode:shared`
-#
-if platform.system() == "Windows":
-	LIB_PATH = os.getcwd() + os.sep + "big.dll"
-elif platform.system() == "Linux":
-	LIB_PATH = os.getcwd() + os.sep + "big.so"
-elif platform.system() == "Darwin":
-	LIB_PATH = os.getcwd() + os.sep + "big.dylib"
-else:
-	print("Platform is unsupported.")
-	exit(1)
+from enum import Enum
 
 #
 # How many iterations of each random test do we want to run?
@@ -34,33 +17,64 @@ BITS_AND_ITERATIONS = [
 ]
 
 #
-# Fast tests?
+# For timed tests we budget a second per `n` bits and iterate until we hit that time.
+# Otherwise, we specify the number of iterations per bit depth in BITS_AND_ITERATIONS.
+#
+TIMED_TESTS = False
+TIMED_BITS_PER_SECOND = 20_000
+
+#
+# If TIMED_TESTS == False and FAST_TESTS == True, we cut down the number of iterations.
+# See below.
 #
+FAST_TESTS = True
+
 if FAST_TESTS:
 	for k in range(len(BITS_AND_ITERATIONS)):
 		b, i = BITS_AND_ITERATIONS[k]
 		BITS_AND_ITERATIONS[k] = (b, i // 10 if i >= 100 else 5)
 
 #
-# Result values will be passed in a struct { res: cstring, err: Error }
+# Where is the DLL? If missing, build using: `odin build . -build-mode:shared`
 #
-class Res(Structure):
-	_fields_ = [("res", c_char_p), ("err", c_byte)]
+if platform.system() == "Windows":
+	LIB_PATH = os.getcwd() + os.sep + "big.dll"
+elif platform.system() == "Linux":
+	LIB_PATH = os.getcwd() + os.sep + "big.so"
+elif platform.system() == "Darwin":
+	LIB_PATH = os.getcwd() + os.sep + "big.dylib"
+else:
+	print("Platform is unsupported.")
+	exit(1)
+
+
+TOTAL_TIME  = 0
+UNTIL_TIME  = 0
+UNTIL_ITERS = 0
+
+def we_iterate():
+	if TIMED_TESTS:
+		return TOTAL_TIME < UNTIL_TIME
+	else:
+		global UNTIL_ITERS
+		UNTIL_ITERS -= 1
+		return UNTIL_ITERS != -1
 
 #
 # Error enum values
 #
-E_None                   = 0
-E_Out_Of_Memory          = 1
-E_Invalid_Pointer        = 2
-E_Invalid_Argument       = 3
-E_Unknown_Error          = 4
-E_Max_Iterations_Reached = 5
-E_Buffer_Overflow        = 6
-E_Integer_Overflow       = 7
-E_Division_by_Zero       = 8
-E_Math_Domain_Error      = 9
-E_Unimplemented          = 127
+class Error(Enum):
+	Okay                   = 0
+	Out_Of_Memory          = 1
+	Invalid_Pointer        = 2
+	Invalid_Argument       = 3
+	Unknown_Error          = 4
+	Max_Iterations_Reached = 5
+	Buffer_Overflow        = 6
+	Integer_Overflow       = 7
+	Division_by_Zero       = 8
+	Math_Domain_Error      = 9
+	Unimplemented          = 127
 
 #
 # Set up exported procedures
@@ -77,56 +91,44 @@ def load(export_name, args, res):
 	export_name.restype  = res
 	return export_name
 
-error_string = load(l.test_error_string, [c_byte], c_char_p)
-
 #
-# res = a + b, err
+# Result values will be passed in a struct { res: cstring, err: Error }
 #
-add_two = load(l.test_add_two, [c_char_p, c_char_p, c_longlong], Res)
+class Res(Structure):
+	_fields_ = [("res", c_char_p), ("err", c_uint64)]
 
-#
-# res = a - b, err
-#
-sub_two = load(l.test_sub_two, [c_char_p, c_char_p, c_longlong], Res)
+error_string = load(l.test_error_string, [c_byte], c_char_p)
 
-#
-# res = a * b, err
-#
+add_two = load(l.test_add_two, [c_char_p, c_char_p, c_longlong], Res)
+sub_two = load(l.test_sub_two, [c_char_p, c_char_p, c_longlong], Res)
 mul_two = load(l.test_mul_two, [c_char_p, c_char_p, c_longlong], Res)
-
-#
-# res = a / b, err
-#
 div_two = load(l.test_div_two, [c_char_p, c_char_p, c_longlong], Res)
 
-
-#
-# res = log(a, base)
-#
 int_log = load(l.test_log, [c_char_p, c_longlong, c_longlong], Res)
 
 
-def test(test_name: "", res: Res, param=[], expected_error = E_None, expected_result = ""):
+def test(test_name: "", res: Res, param=[], expected_error = Error.Okay, expected_result = ""):
 	passed = True
 	r = None
+	err = Error(res.err)
 
-	if res.err != expected_error:
-		error_type = error_string(res.err).decode('utf-8')
+	if err != expected_error:
 		error_loc  = res.res.decode('utf-8')
+		error = "{}: {} in '{}'".format(test_name, err, error_loc)
 
-		error = "{}: '{}' error in '{}'".format(test_name, error_type, error_loc)
 		if len(param):
 			error += " with params {}".format(param)
 
 		print(error, flush=True)
 		passed = False
-	elif res.err == E_None:
+	elif err == Error.Okay:
+		r = None
 		try:
 			r = res.res.decode('utf-8')
+			r = int(res.res, 10)
 		except:
 			pass
 
-		r = eval(res.res)
 		if r != expected_result:
 			error = "{}: Result was '{}', expected '{}'".format(test_name, r, expected_result)
 			if len(param):
@@ -135,34 +137,40 @@ def test(test_name: "", res: Res, param=[], expected_error = E_None, expected_re
 			print(error, flush=True)
 			passed = False
 
+	if not passed:
+		exit()
+
 	return passed
 
 
-def test_add_two(a = 0, b = 0, radix = 10, expected_error = E_None, expected_result = None):
+def test_add_two(a = 0, b = 0, radix = 10, expected_error = Error.Okay):
 	args = [str(a), str(b), radix]
 	sa_c, sb_c = args[0].encode('utf-8'), args[1].encode('utf-8')
 	res  = add_two(sa_c, sb_c, radix)
-	if expected_result == None:
+	expected_result = None
+	if expected_error == Error.Okay:
 		expected_result = a + b
 	return test("test_add_two", res, args, expected_error, expected_result)
 
-def test_sub_two(a = 0, b = 0, radix = 10, expected_error = E_None, expected_result = None):
+def test_sub_two(a = 0, b = 0, radix = 10, expected_error = Error.Okay):
 	sa,     sb = str(a), str(b)
 	sa_c, sb_c = sa.encode('utf-8'), sb.encode('utf-8')
 	res  = sub_two(sa_c, sb_c, radix)
-	if expected_result == None:
+	expected_result = None
+	if expected_error == Error.Okay:
 		expected_result = a - b
 	return test("test_sub_two", res, [sa_c, sb_c, radix], expected_error, expected_result)
 
-def test_mul_two(a = 0, b = 0, radix = 10, expected_error = E_None, expected_result = None):
+def test_mul_two(a = 0, b = 0, radix = 10, expected_error = Error.Okay):
 	sa,     sb = str(a), str(b)
 	sa_c, sb_c = sa.encode('utf-8'), sb.encode('utf-8')
 	res  = mul_two(sa_c, sb_c, radix)
-	if expected_result == None:
+	expected_result = None
+	if expected_error == Error.Okay:
 		expected_result = a * b
 	return test("test_mul_two", res, [sa_c, sb_c, radix], expected_error, expected_result)
 
-def test_div_two(a = 0, b = 0, radix = 10, expected_error = E_None, expected_result = None):
+def test_div_two(a = 0, b = 0, radix = 10, expected_error = Error.Okay):
 	sa,     sb = str(a), str(b)
 	sa_c, sb_c = sa.encode('utf-8'), sb.encode('utf-8')
 	try:
@@ -170,7 +178,8 @@ def test_div_two(a = 0, b = 0, radix = 10, expected_error = E_None, expected_res
 	except:
 		print("Exception with arguments:", a, b, radix)
 		return False
-	if expected_result == None:
+	expected_result = None
+	if expected_error == Error.Okay:
 		#
 		# We don't round the division results, so if one component is negative, we're off by one.
 		#
@@ -183,12 +192,13 @@ def test_div_two(a = 0, b = 0, radix = 10, expected_error = E_None, expected_res
 	return test("test_div_two", res, [sa_c, sb_c, radix], expected_error, expected_result)
 
 
-def test_log(a = 0, base = 0, radix = 10, expected_error = E_None, expected_result = None):
+def test_log(a = 0, base = 0, radix = 10, expected_error = Error.Okay):
 	args  = [str(a), base, radix]
 	sa_c  = args[0].encode('utf-8')
 	res   = int_log(sa_c, base, radix)
 
-	if expected_result == None:
+	expected_result = None
+	if expected_error == Error.Okay:
 		expected_result = int(log(a, base))
 	return test("test_log", res, args, expected_error, expected_result)
 
@@ -204,28 +214,29 @@ def test_log(a = 0, base = 0, radix = 10, expected_error = E_None, expected_resu
 TESTS = {
 	test_add_two: [
 		[ 1234,   5432,    10, ],
-		[ 1234,   5432,   110, E_Invalid_Argument, ],
+		[ 1234,   5432,   110, Error.Invalid_Argument],
 	],
 	test_sub_two: [
 		[ 1234,   5432,    10, ],
 	],
 	test_mul_two: [
 		[ 1234,   5432,    10, ],
-		[ 1099243943008198766717263669950239669, 137638828577110581150675834234248871, 10, ]
+		[ 0xd3b4e926aaba3040e1c12b5ea553b5, 0x1a821e41257ed9281bee5bc7789ea7, 10, ]
 	],
 	test_div_two: [
 		[ 54321,	12345,		10, ],
-		[ 55431,		0,		10,		E_Division_by_Zero, ],
+		[ 55431,		0,		10,		Error.Division_by_Zero],
 	],
 	test_log: [
-		[ 3192,			1,		10,		E_Invalid_Argument,		":log:log(a, base):"],
-		[ -1234,		2,		10,		E_Math_Domain_Error,	":log:log(a, base):"],
-		[ 0,			2,		10,		E_Math_Domain_Error, 	":log:log(a, base):"],
+		[ 3192,			1,		10,		Error.Invalid_Argument],
+		[ -1234,		2,		10,		Error.Math_Domain_Error],
+		[ 0,			2,		10,		Error.Math_Domain_Error],
 		[ 1024,			2,		10, ],
 	],
 }
 
-TOTAL_TIME     = 0
+RANDOM_TESTS = [test_add_two, test_sub_two, test_mul_two, test_div_two, test_log]
+
 total_passes   = 0
 total_failures = 0
 
@@ -266,12 +277,16 @@ if __name__ == '__main__':
 		print("---- core:math/big with two random {bits:,} bit numbers ----".format(bits=BITS))
 		print()
 
-		for test_proc in [test_add_two, test_sub_two, test_mul_two, test_div_two, test_log]:
+		for test_proc in RANDOM_TESTS:
 			count_pass = 0
 			count_fail = 0
 			TIMINGS    = {}
 
-			for i in range(ITERATIONS):
+			UNTIL_ITERS = ITERATIONS
+			UNTIL_TIME  = TOTAL_TIME + BITS / TIMED_BITS_PER_SECOND
+			# We run each test for a second per 20k bits
+
+			while we_iterate():
 				a = randint(-(1 << BITS), 1 << BITS)
 				b = randint(-(1 << BITS), 1 << BITS)
 
@@ -299,11 +314,9 @@ if __name__ == '__main__':
 					TIMINGS[test_proc] += diff
 
 				if res:
-					count_pass     += 1
-					total_passes   += 1
+					count_pass     += 1; total_passes   += 1
 				else:
-					count_fail     += 1
-					total_failures += 1
+					count_fail     += 1; total_failures += 1
 
 			print("{name}: {count_pass:,} passes and {count_fail:,} failures in {timing:.3f} ms.".format(name=test_proc.__name__, count_pass=count_pass, count_fail=count_fail, timing=TIMINGS[test_proc] * 1_000))