| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  | # Regression test helper script | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Copyright (C) 2018  Kevin O'Connor <kevin@koconnor.net> | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # This file may be distributed under the terms of the GNU GPLv3 license. | 
					
						
							|  |  |  | import sys, os, optparse, logging, subprocess | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  | TEMP_GCODE_FILE = "_test_.gcode" | 
					
						
							|  |  |  | TEMP_LOG_FILE = "_test_.log" | 
					
						
							| 
									
										
										
										
											2018-06-29 13:52:23 -04:00
										 |  |  | TEMP_OUTPUT_FILE = "_test_output" | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Test cases | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class error(Exception): | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TestCase: | 
					
						
							| 
									
										
										
										
											2018-06-30 12:20:42 -04:00
										 |  |  |     def __init__(self, fname, dictdir, tempdir, verbose, keepfiles): | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |         self.fname = fname | 
					
						
							|  |  |  |         self.dictdir = dictdir | 
					
						
							|  |  |  |         self.tempdir = tempdir | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  |         self.verbose = verbose | 
					
						
							| 
									
										
										
										
											2018-06-30 12:20:42 -04:00
										 |  |  |         self.keepfiles = keepfiles | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |     def relpath(self, fname, rel='test'): | 
					
						
							|  |  |  |         if rel == 'dict': | 
					
						
							|  |  |  |             reldir = self.dictdir | 
					
						
							|  |  |  |         elif rel == 'temp': | 
					
						
							|  |  |  |             reldir = self.tempdir | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             reldir = os.path.dirname(self.fname) | 
					
						
							|  |  |  |         return os.path.join(reldir, fname) | 
					
						
							|  |  |  |     def parse_test(self): | 
					
						
							|  |  |  |         # Parse file into test cases | 
					
						
							| 
									
										
										
										
											2018-06-29 13:52:23 -04:00
										 |  |  |         config_fname = gcode_fname = dict_fnames = None | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |         should_fail = multi_tests = False | 
					
						
							|  |  |  |         gcode = [] | 
					
						
							| 
									
										
										
										
											2020-06-11 22:48:18 -04:00
										 |  |  |         f = open(self.fname, 'r') | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |         for line in f: | 
					
						
							|  |  |  |             cpos = line.find('#') | 
					
						
							|  |  |  |             if cpos >= 0: | 
					
						
							|  |  |  |                 line = line[:cpos] | 
					
						
							|  |  |  |             parts = line.strip().split() | 
					
						
							|  |  |  |             if not parts: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             if parts[0] == "CONFIG": | 
					
						
							|  |  |  |                 if config_fname is not None: | 
					
						
							|  |  |  |                     # Multiple tests in same file | 
					
						
							|  |  |  |                     if not multi_tests: | 
					
						
							|  |  |  |                         multi_tests = True | 
					
						
							| 
									
										
										
										
											2018-06-29 13:52:23 -04:00
										 |  |  |                         self.launch_test(config_fname, dict_fnames, | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |                                          gcode_fname, gcode, should_fail) | 
					
						
							|  |  |  |                 config_fname = self.relpath(parts[1]) | 
					
						
							|  |  |  |                 if multi_tests: | 
					
						
							| 
									
										
										
										
											2018-06-29 13:52:23 -04:00
										 |  |  |                     self.launch_test(config_fname, dict_fnames, | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |                                      gcode_fname, gcode, should_fail) | 
					
						
							|  |  |  |             elif parts[0] == "DICTIONARY": | 
					
						
							| 
									
										
										
										
											2018-06-29 13:52:23 -04:00
										 |  |  |                 dict_fnames = [self.relpath(parts[1], 'dict')] | 
					
						
							|  |  |  |                 for mcu_dict in parts[2:]: | 
					
						
							|  |  |  |                     mcu, fname = mcu_dict.split('=', 1) | 
					
						
							|  |  |  |                     dict_fnames.append('%s=%s' % ( | 
					
						
							|  |  |  |                         mcu.strip(), self.relpath(fname.strip(), 'dict'))) | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |             elif parts[0] == "GCODE": | 
					
						
							|  |  |  |                 gcode_fname = self.relpath(parts[1]) | 
					
						
							|  |  |  |             elif parts[0] == "SHOULD_FAIL": | 
					
						
							|  |  |  |                 should_fail = True | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2018-06-16 20:42:13 -04:00
										 |  |  |                 gcode.append(line.strip()) | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |         f.close() | 
					
						
							|  |  |  |         if not multi_tests: | 
					
						
							| 
									
										
										
										
											2018-06-29 13:52:23 -04:00
										 |  |  |             self.launch_test(config_fname, dict_fnames, | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |                              gcode_fname, gcode, should_fail) | 
					
						
							| 
									
										
										
										
											2018-06-29 13:52:23 -04:00
										 |  |  |     def launch_test(self, config_fname, dict_fnames, gcode_fname, gcode, | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |                     should_fail): | 
					
						
							|  |  |  |         gcode_is_temp = False | 
					
						
							|  |  |  |         if gcode_fname is None: | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  |             gcode_fname = self.relpath(TEMP_GCODE_FILE, 'temp') | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |             gcode_is_temp = True | 
					
						
							| 
									
										
										
										
											2020-06-11 22:48:18 -04:00
										 |  |  |             f = open(gcode_fname, 'w') | 
					
						
							| 
									
										
										
										
											2018-06-16 20:42:13 -04:00
										 |  |  |             f.write('\n'.join(gcode + [''])) | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |             f.close() | 
					
						
							|  |  |  |         elif gcode: | 
					
						
							|  |  |  |             raise error("Can't specify both a gcode file and gcode commands") | 
					
						
							|  |  |  |         if config_fname is None: | 
					
						
							|  |  |  |             raise error("config file not specified") | 
					
						
							| 
									
										
										
										
											2018-06-29 13:52:23 -04:00
										 |  |  |         if dict_fnames is None: | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |             raise error("data dictionary file not specified") | 
					
						
							|  |  |  |         # Call klippy | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  |         sys.stderr.write("    Starting %s (%s)\n" % ( | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |             self.fname, os.path.basename(config_fname))) | 
					
						
							| 
									
										
										
										
											2018-06-29 13:52:23 -04:00
										 |  |  |         args = [ sys.executable, './klippy/klippy.py', config_fname, | 
					
						
							|  |  |  |                  '-i', gcode_fname, '-o', TEMP_OUTPUT_FILE, '-v' ] | 
					
						
							|  |  |  |         for df in dict_fnames: | 
					
						
							|  |  |  |             args += ['-d', df] | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  |         if not self.verbose: | 
					
						
							|  |  |  |             args += ['-l', TEMP_LOG_FILE] | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |         res = subprocess.call(args) | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  |         is_fail = (should_fail and not res) or (not should_fail and res) | 
					
						
							|  |  |  |         if is_fail: | 
					
						
							|  |  |  |             if not self.verbose: | 
					
						
							|  |  |  |                 self.show_log() | 
					
						
							|  |  |  |             if should_fail: | 
					
						
							| 
									
										
										
										
											2018-06-16 20:42:13 -04:00
										 |  |  |                 raise error("Test failed to raise an error") | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  |             raise error("Error during test") | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |         # Do cleanup | 
					
						
							| 
									
										
										
										
											2018-06-30 12:20:42 -04:00
										 |  |  |         if self.keepfiles: | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2018-06-29 13:52:23 -04:00
										 |  |  |         for fname in os.listdir(self.tempdir): | 
					
						
							|  |  |  |             if fname.startswith(TEMP_OUTPUT_FILE): | 
					
						
							|  |  |  |                 os.unlink(fname) | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  |         if not self.verbose: | 
					
						
							|  |  |  |             os.unlink(TEMP_LOG_FILE) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             sys.stderr.write('\n') | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |         if gcode_is_temp: | 
					
						
							|  |  |  |             os.unlink(gcode_fname) | 
					
						
							|  |  |  |     def run(self): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             self.parse_test() | 
					
						
							|  |  |  |         except error as e: | 
					
						
							|  |  |  |             return str(e) | 
					
						
							|  |  |  |         except Exception: | 
					
						
							|  |  |  |             logging.exception("Unhandled exception during test run") | 
					
						
							|  |  |  |             return "internal error" | 
					
						
							|  |  |  |         return "success" | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  |     def show_log(self): | 
					
						
							| 
									
										
										
										
											2020-06-11 22:48:18 -04:00
										 |  |  |         f = open(TEMP_LOG_FILE, 'r') | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  |         data = f.read() | 
					
						
							|  |  |  |         f.close() | 
					
						
							|  |  |  |         sys.stdout.write(data) | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Startup | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def main(): | 
					
						
							|  |  |  |     # Parse args | 
					
						
							|  |  |  |     usage = "%prog [options] <test cases>" | 
					
						
							|  |  |  |     opts = optparse.OptionParser(usage) | 
					
						
							|  |  |  |     opts.add_option("-d", "--dictdir", dest="dictdir", default=".", | 
					
						
							|  |  |  |                     help="directory for dictionary files") | 
					
						
							|  |  |  |     opts.add_option("-t", "--tempdir", dest="tempdir", default=".", | 
					
						
							|  |  |  |                     help="directory for temporary files") | 
					
						
							| 
									
										
										
										
											2018-06-30 12:20:42 -04:00
										 |  |  |     opts.add_option("-k", action="store_true", dest="keepfiles", | 
					
						
							|  |  |  |                     help="do not remove temporary files") | 
					
						
							| 
									
										
										
										
											2018-06-17 11:48:06 -04:00
										 |  |  |     opts.add_option("-v", action="store_true", dest="verbose", | 
					
						
							|  |  |  |                     help="show all output from tests") | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |     options, args = opts.parse_args() | 
					
						
							|  |  |  |     if len(args) < 1: | 
					
						
							|  |  |  |         opts.error("Incorrect number of arguments") | 
					
						
							|  |  |  |     logging.basicConfig(level=logging.DEBUG) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Run each test | 
					
						
							|  |  |  |     for fname in args: | 
					
						
							| 
									
										
										
										
											2018-06-30 12:20:42 -04:00
										 |  |  |         tc = TestCase(fname, options.dictdir, options.tempdir, options.verbose, | 
					
						
							|  |  |  |                       options.keepfiles) | 
					
						
							| 
									
										
										
										
											2017-09-11 20:53:32 -04:00
										 |  |  |         res = tc.run() | 
					
						
							|  |  |  |         if res != 'success': | 
					
						
							|  |  |  |             sys.stderr.write("\n\nTest case %s FAILED (%s)!\n\n" % (fname, res)) | 
					
						
							|  |  |  |             sys.exit(-1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sys.stderr.write("\n    All %d test cases passed\n" % (len(args),)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == '__main__': | 
					
						
							|  |  |  |     main() |