/ python / qrcodegen-batch-test.py
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()