qrcodegen-batch-test.py
1 # 2 # QR Code generator batch test (Python) 3 # 4 # Runs various versions of the QR Code generator test worker as subprocesses, 5 # feeds each one the same random input, and compares their output for equality. 6 # 7 # Copyright (c) Project Nayuki. (MIT License) 8 # https://www.nayuki.io/page/qr-code-generator-library 9 # 10 # Permission is hereby granted, free of charge, to any person obtaining a copy of 11 # this software and associated documentation files (the "Software"), to deal in 12 # the Software without restriction, including without limitation the rights to 13 # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 14 # the Software, and to permit persons to whom the Software is furnished to do so, 15 # subject to the following conditions: 16 # - The above copyright notice and this permission notice shall be included in 17 # all copies or substantial portions of the Software. 18 # - The Software is provided "as is", without warranty of any kind, express or 19 # implied, including but not limited to the warranties of merchantability, 20 # fitness for a particular purpose and noninfringement. In no event shall the 21 # authors or copyright holders be liable for any claim, damages or other 22 # liability, whether in an action of contract, tort or otherwise, arising from, 23 # out of or in connection with the Software or the use or other dealings in the 24 # Software. 25 # 26 27 import itertools, random, subprocess, sys, time 28 from typing import List, Optional, TypeVar 29 30 31 CHILD_PROGRAMS: List[List[str]] = [ 32 ["python3", "-B", "../python/qrcodegen-worker.py"], # Python program 33 ["java", "-cp", "../java/src/main/java", "-ea:io.nayuki.qrcodegen...", "io/nayuki/qrcodegen/QrCodeGeneratorWorker"], # Java program 34 ["node", "../typescript-javascript/qrcodegen-worker.js"], # TypeScript program 35 ["../c/qrcodegen-worker"], # C program 36 ["../cpp/QrCodeGeneratorWorker"], # C++ program 37 ["../rust/target/debug/examples/qrcodegen-worker"], # Rust program 38 ] 39 40 41 subprocs: List[subprocess.Popen] = [] 42 43 def main() -> None: 44 # Launch workers 45 global subprocs 46 try: 47 for args in CHILD_PROGRAMS: 48 subprocs.append(subprocess.Popen(args, universal_newlines=True, 49 stdin=subprocess.PIPE, stdout=subprocess.PIPE)) 50 except FileNotFoundError: 51 write_all(-1) 52 raise 53 54 # Check if any died 55 time.sleep(0.3) 56 if any(proc.poll() is not None for proc in subprocs): 57 for proc in subprocs: 58 if proc.poll() is None: 59 print(-1, file=proc.stdin) 60 not_none(proc.stdin).flush() 61 sys.exit("Error: One or more workers failed to start") 62 63 # Do tests 64 for i in itertools.count(): 65 print("Trial {}: ".format(i), end="") 66 do_trial() 67 print() 68 69 70 def do_trial() -> None: 71 mode = random.randrange(4) 72 if mode == 0: # Numeric 73 length = round((2 * 7089) ** random.random()) 74 data = random.choices(b"0123456789", k=length) 75 elif mode == 1: # Alphanumeric 76 length = round((2 * 4296) ** random.random()) 77 data = random.choices(b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:", k=length) 78 elif mode == 2: # ASCII 79 length = round((2 * 2953) ** random.random()) 80 data = [random.randrange(128) for _ in range(length)] 81 elif mode == 3: # Byte 82 length = round((2 * 2953) ** random.random()) 83 data = [random.randrange(256) for _ in range(length)] 84 else: 85 raise AssertionError() 86 87 write_all(length) 88 for b in data: 89 write_all(b) 90 91 errcorlvl = random.randrange(4) 92 minversion = random.randint(1, 40) 93 maxversion = random.randint(1, 40) 94 if minversion > maxversion: 95 minversion, maxversion = maxversion, minversion 96 mask = -1 97 if random.random() < 0.5: 98 mask = random.randrange(8) 99 boostecl = int(random.random() < 0.2) 100 print("mode={} len={} ecl={} minv={} maxv={} mask={} boost={}".format(mode, length, errcorlvl, minversion, maxversion, mask, boostecl), end="") 101 102 write_all(errcorlvl) 103 write_all(minversion) 104 write_all(maxversion) 105 write_all(mask) 106 write_all(boostecl) 107 flush_all() 108 109 version = read_verify() 110 print(" version={}".format(version), end="") 111 if version == -1: 112 return 113 size = version * 4 + 17 114 for _ in range(size**2): 115 read_verify() 116 117 118 def write_all(val: int) -> None: 119 for proc in subprocs: 120 print(val, file=proc.stdin) 121 122 def flush_all() -> None: 123 for proc in subprocs: 124 not_none(proc.stdin).flush() 125 126 def read_verify() -> int: 127 val = not_none(subprocs[0].stdout).readline().rstrip("\r\n") 128 for proc in subprocs[1 : ]: 129 if not_none(proc.stdout).readline().rstrip("\r\n") != val: 130 raise ValueError("Mismatch") 131 return int(val) 132 133 134 T = TypeVar("T") 135 def not_none(obj: Optional[T]) -> T: 136 if obj is None: 137 raise TypeError() 138 return obj 139 140 141 if __name__ == "__main__": 142 main()