| 
									
										
										
										
											2020-03-19 12:36:30 +01:00
										 |  |  | #!/usr/bin/env python3 | 
					
						
							| 
									
										
										
										
											2017-01-19 22:48:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-28 16:14:21 +02:00
										 |  |  | from __future__ import print_function | 
					
						
							| 
									
										
										
										
											2014-03-08 11:31:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 22:02:31 +02:00
										 |  |  | import io | 
					
						
							| 
									
										
										
										
											2012-11-29 08:41:17 +00:00
										 |  |  | import os | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | import subprocess | 
					
						
							|  |  |  | import re | 
					
						
							| 
									
										
										
										
											2017-01-19 22:48:23 +01:00
										 |  |  | import difflib | 
					
						
							| 
									
										
										
										
											2012-11-29 08:41:17 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  | import scriptCommon | 
					
						
							| 
									
										
										
										
											2019-07-19 18:16:21 +02:00
										 |  |  | from scriptCommon import catchPath | 
					
						
							| 
									
										
										
										
											2013-04-24 18:58:57 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-02 17:25:30 +12:00
										 |  |  | if os.name == 'nt': | 
					
						
							| 
									
										
										
										
											2018-08-28 16:14:21 +02:00
										 |  |  |     # Enable console colours on windows | 
					
						
							|  |  |  |     os.system('') | 
					
						
							| 
									
										
										
										
											2018-05-02 17:25:30 +12:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-05 16:00:20 +01:00
										 |  |  | rootPath = os.path.join(catchPath, 'tests/SelfTest/Baselines') | 
					
						
							| 
									
										
										
										
											2013-09-30 07:39:06 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-19 12:46:06 +02:00
										 |  |  | langFilenameParser = re.compile(r'(.+\.[ch]pp)') | 
					
						
							| 
									
										
										
										
											2017-01-19 23:56:31 +01:00
										 |  |  | filelocParser = re.compile(r'''
 | 
					
						
							|  |  |  |     .*/ | 
					
						
							|  |  |  |     (.+\.[ch]pp)  # filename | 
					
						
							|  |  |  |     (?::|\()      # : is starting separator between filename and line number on Linux, ( on Windows | 
					
						
							|  |  |  |     ([0-9]*)      # line number | 
					
						
							|  |  |  |     \)?           # Windows also has an ending separator, ) | 
					
						
							|  |  |  | ''', re.VERBOSE)
 | 
					
						
							| 
									
										
										
										
											2017-01-19 15:15:06 +01:00
										 |  |  | lineNumberParser = re.compile(r' line="[0-9]*"') | 
					
						
							|  |  |  | hexParser = re.compile(r'\b(0[xX][0-9a-fA-F]+)\b') | 
					
						
							| 
									
										
										
										
											2021-05-22 23:41:58 +02:00
										 |  |  | # Note: junit must serialize time with 3 (or or less) decimal places | 
					
						
							|  |  |  | #       before generalizing this parser, make sure that this is checked | 
					
						
							|  |  |  | #       in other places too. | 
					
						
							|  |  |  | junitDurationsParser = re.compile(r' time="[0-9]+\.[0-9]{3}"') | 
					
						
							|  |  |  | durationParser = re.compile(r''' duration=['"][0-9]+['"]''') | 
					
						
							| 
									
										
										
										
											2017-10-09 12:31:22 +02:00
										 |  |  | timestampsParser = re.compile(r'\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}Z') | 
					
						
							| 
									
										
										
										
											2022-01-27 11:21:43 +01:00
										 |  |  | versionParser = re.compile(r'Catch2 v[0-9]+\.[0-9]+\.[0-9]+(-\w*\.[0-9]+)?') | 
					
						
							| 
									
										
										
										
											2017-01-19 15:15:06 +01:00
										 |  |  | nullParser = re.compile(r'\b(__null|nullptr)\b') | 
					
						
							| 
									
										
										
										
											2017-01-19 23:56:31 +01:00
										 |  |  | exeNameParser = re.compile(r'''
 | 
					
						
							|  |  |  |     \b | 
					
						
							| 
									
										
										
										
											2022-01-26 23:47:40 +01:00
										 |  |  |     SelfTest                  # Expected executable name | 
					
						
							| 
									
										
										
										
											2017-01-19 23:56:31 +01:00
										 |  |  |     (?:.exe)?                 # Executable name contains .exe on Windows. | 
					
						
							|  |  |  |     \b | 
					
						
							|  |  |  | ''', re.VERBOSE)
 | 
					
						
							|  |  |  | # This is a hack until something more reasonable is figured out | 
					
						
							|  |  |  | specialCaseParser = re.compile(r'file\((\d+)\)') | 
					
						
							| 
									
										
										
										
											2012-11-29 08:41:17 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-09 12:31:22 +02:00
										 |  |  | sinceEpochParser = re.compile(r'\d+ .+ since epoch') | 
					
						
							| 
									
										
										
										
											2017-11-10 18:14:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-03 20:29:36 +01:00
										 |  |  | # The weird OR is there to always have at least empty string for group 1 | 
					
						
							| 
									
										
										
										
											2020-02-13 14:22:18 +01:00
										 |  |  | tapTestNumParser = re.compile(r'^((?:not ok)|(?:ok)|(?:warning)|(?:info)) (\d+) -') | 
					
						
							| 
									
										
										
										
											2017-04-29 17:52:12 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-02-02 19:58:04 +00:00
										 |  |  | if len(sys.argv) == 2: | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  |     cmdPath = sys.argv[1] | 
					
						
							| 
									
										
										
										
											2013-02-02 19:58:04 +00:00
										 |  |  | else: | 
					
						
							| 
									
										
										
										
											2017-01-19 22:08:51 +01:00
										 |  |  |     cmdPath = os.path.join(catchPath, scriptCommon.getBuildExecutable()) | 
					
						
							| 
									
										
										
										
											2012-11-29 08:41:17 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-09-27 19:01:14 +01:00
										 |  |  | overallResult = 0 | 
					
						
							| 
									
										
										
										
											2012-11-29 08:41:17 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-06 13:59:08 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-19 22:48:23 +01:00
										 |  |  | def diffFiles(fileA, fileB): | 
					
						
							| 
									
										
										
										
											2018-04-19 22:02:31 +02:00
										 |  |  |     with io.open(fileA, 'r', encoding='utf-8', errors='surrogateescape') as file: | 
					
						
							| 
									
										
										
										
											2017-04-29 18:06:36 +02:00
										 |  |  |         aLines = [line.rstrip() for line in file.readlines()] | 
					
						
							| 
									
										
										
										
											2018-04-19 22:02:31 +02:00
										 |  |  |     with io.open(fileB, 'r', encoding='utf-8', errors='surrogateescape') as file: | 
					
						
							| 
									
										
										
										
											2017-04-29 18:06:36 +02:00
										 |  |  |         bLines = [line.rstrip() for line in file.readlines()] | 
					
						
							| 
									
										
										
										
											2017-01-19 22:48:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     shortenedFilenameA = fileA.rsplit(os.sep, 1)[-1] | 
					
						
							|  |  |  |     shortenedFilenameB = fileB.rsplit(os.sep, 1)[-1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     diff = difflib.unified_diff(aLines, bLines, fromfile=shortenedFilenameA, tofile=shortenedFilenameB, n=0) | 
					
						
							|  |  |  |     return [line for line in diff if line[0] in ('+', '-')] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-19 12:46:06 +02:00
										 |  |  | def normalizeFilepath(line): | 
					
						
							| 
									
										
										
										
											2021-11-12 23:51:51 +01:00
										 |  |  |     # Sometimes the path separators used by compiler and Python can differ, | 
					
						
							|  |  |  |     # so we try to match the path with both forward and backward path | 
					
						
							|  |  |  |     # separators, to make the paths relative to Catch2 repo root. | 
					
						
							|  |  |  |     forwardSlashPath = catchPath.replace('\\', '/') | 
					
						
							|  |  |  |     if forwardSlashPath in line: | 
					
						
							|  |  |  |         line = line.replace(forwardSlashPath + '/', '') | 
					
						
							|  |  |  |     backwardSlashPath = catchPath.replace('/', '\\') | 
					
						
							|  |  |  |     if backwardSlashPath in line: | 
					
						
							|  |  |  |         line = line.replace(backwardSlashPath + '\\', '') | 
					
						
							| 
									
										
										
										
											2018-10-19 12:46:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     m = langFilenameParser.match(line) | 
					
						
							|  |  |  |     if m: | 
					
						
							|  |  |  |         filepath = m.group(0) | 
					
						
							| 
									
										
										
										
											2017-01-19 23:56:31 +01:00
										 |  |  |         # go from \ in windows paths to / | 
					
						
							| 
									
										
										
										
											2018-10-19 12:46:06 +02:00
										 |  |  |         filepath = filepath.replace('\\', '/') | 
					
						
							|  |  |  |         # remove start of relative path | 
					
						
							|  |  |  |         filepath = filepath.replace('../', '') | 
					
						
							|  |  |  |         line = line[:m.start()] + filepath + line[m.end():] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return line | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def filterLine(line, isCompact): | 
					
						
							|  |  |  |     line = normalizeFilepath(line) | 
					
						
							| 
									
										
										
										
											2017-01-19 23:56:31 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-19 15:15:06 +01:00
										 |  |  |     # strip source line numbers | 
					
						
							|  |  |  |     m = filelocParser.match(line) | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  |     if m: | 
					
						
							| 
									
										
										
										
											2017-01-19 15:15:06 +01:00
										 |  |  |         # note that this also strips directories, leaving only the filename | 
					
						
							|  |  |  |         filename, lnum = m.groups() | 
					
						
							| 
									
										
										
										
											2017-01-19 23:56:31 +01:00
										 |  |  |         lnum = ":<line number>" if lnum else "" | 
					
						
							| 
									
										
										
										
											2017-01-19 15:15:06 +01:00
										 |  |  |         line = filename + lnum + line[m.end():] | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2017-01-19 15:15:06 +01:00
										 |  |  |         line = lineNumberParser.sub(" ", line) | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-04 20:28:33 +02:00
										 |  |  |     if isCompact: | 
					
						
							|  |  |  |         line = line.replace(': FAILED', ': failed') | 
					
						
							|  |  |  |         line = line.replace(': PASSED', ': passed') | 
					
						
							| 
									
										
										
										
											2018-08-28 16:14:21 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-03 20:29:36 +01:00
										 |  |  |     # strip out the test order number in TAP to avoid massive diffs for every change | 
					
						
							| 
									
										
										
										
											2020-02-13 14:22:18 +01:00
										 |  |  |     line = tapTestNumParser.sub("\g<1> {test-number} -", line) | 
					
						
							| 
									
										
										
										
											2020-02-03 20:29:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-27 11:21:43 +01:00
										 |  |  |     # strip Catch2 version number | 
					
						
							| 
									
										
										
										
											2017-01-19 15:15:06 +01:00
										 |  |  |     line = versionParser.sub("<version>", line) | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-19 15:15:06 +01:00
										 |  |  |     # replace *null* with 0 | 
					
						
							|  |  |  |     line = nullParser.sub("0", line) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # strip executable name | 
					
						
							|  |  |  |     line = exeNameParser.sub("<exe-name>", line) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # strip hexadecimal numbers (presumably pointer values) | 
					
						
							|  |  |  |     line = hexParser.sub("0x<hex digits>", line) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # strip durations and timestamps | 
					
						
							| 
									
										
										
										
											2021-05-22 23:41:58 +02:00
										 |  |  |     line = junitDurationsParser.sub(' time="{duration}"', line) | 
					
						
							|  |  |  |     line = durationParser.sub(' duration="{duration}"', line) | 
					
						
							| 
									
										
										
										
											2017-10-09 12:31:22 +02:00
										 |  |  |     line = timestampsParser.sub('{iso8601-timestamp}', line) | 
					
						
							| 
									
										
										
										
											2017-01-19 23:56:31 +01:00
										 |  |  |     line = specialCaseParser.sub('file:\g<1>', line) | 
					
						
							| 
									
										
										
										
											2017-10-09 12:31:22 +02:00
										 |  |  |     line = sinceEpochParser.sub('{since-epoch-report}', line) | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  |     return line | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-06 20:12:07 +01:00
										 |  |  | def get_rawResultsPath(baseName): | 
					
						
							|  |  |  |     return os.path.join(rootPath, '_{0}.tmp'.format(baseName)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_baselinesPath(baseName): | 
					
						
							|  |  |  |     return os.path.join(rootPath, '{0}.approved.txt'.format(baseName)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_filteredResultsPath(baseName): | 
					
						
							|  |  |  |     return os.path.join(rootPath, '{0}.unapproved.txt'.format(baseName)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def run_test(baseName, args): | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  |     args[0:0] = [cmdPath] | 
					
						
							|  |  |  |     if not os.path.exists(cmdPath): | 
					
						
							|  |  |  |         raise Exception("Executable doesn't exist at " + cmdPath) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-06 20:12:07 +01:00
										 |  |  |     print(args) | 
					
						
							|  |  |  |     rawResultsPath = get_rawResultsPath(baseName) | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  |     f = open(rawResultsPath, 'w') | 
					
						
							|  |  |  |     subprocess.call(args, stdout=f, stderr=f) | 
					
						
							|  |  |  |     f.close() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-06 20:12:07 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | def check_outputs(baseName): | 
					
						
							|  |  |  |     global overallResult | 
					
						
							|  |  |  |     rawResultsPath = get_rawResultsPath(baseName) | 
					
						
							|  |  |  |     baselinesPath = get_baselinesPath(baseName) | 
					
						
							|  |  |  |     filteredResultsPath = get_filteredResultsPath(baseName) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-19 22:02:31 +02:00
										 |  |  |     rawFile = io.open(rawResultsPath, 'r', encoding='utf-8', errors='surrogateescape') | 
					
						
							|  |  |  |     filteredFile = io.open(filteredResultsPath, 'w', encoding='utf-8', errors='surrogateescape') | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  |     for line in rawFile: | 
					
						
							| 
									
										
										
										
											2018-06-04 20:28:33 +02:00
										 |  |  |         filteredFile.write(filterLine(line, 'compact' in baseName).rstrip() + "\n") | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  |     filteredFile.close() | 
					
						
							|  |  |  |     rawFile.close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     os.remove(rawResultsPath) | 
					
						
							|  |  |  |     print() | 
					
						
							|  |  |  |     print(baseName + ":") | 
					
						
							|  |  |  |     if os.path.exists(baselinesPath): | 
					
						
							| 
									
										
										
										
											2017-01-19 22:48:23 +01:00
										 |  |  |         diffResult = diffFiles(baselinesPath, filteredResultsPath) | 
					
						
							|  |  |  |         if diffResult: | 
					
						
							| 
									
										
										
										
											2017-04-29 18:06:36 +02:00
										 |  |  |             print('\n'.join(diffResult)) | 
					
						
							| 
									
										
										
										
											2017-01-19 22:48:23 +01:00
										 |  |  |             print("  \n****************************\n  \033[91mResults differed") | 
					
						
							|  |  |  |             if len(diffResult) > overallResult: | 
					
						
							|  |  |  |                 overallResult = len(diffResult) | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  |             os.remove(filteredResultsPath) | 
					
						
							|  |  |  |             print("  \033[92mResults matched") | 
					
						
							|  |  |  |         print("\033[0m") | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print("  first approval") | 
					
						
							|  |  |  |         if overallResult == 0: | 
					
						
							|  |  |  |             overallResult = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-06 20:12:07 +01:00
										 |  |  | def approve(baseName, args): | 
					
						
							|  |  |  |     run_test(baseName, args) | 
					
						
							|  |  |  |     check_outputs(baseName) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  | print("Running approvals against executable:") | 
					
						
							|  |  |  | print("  " + cmdPath) | 
					
						
							| 
									
										
										
										
											2013-09-27 19:01:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-06 15:48:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:04:19 +01:00
										 |  |  | ## special cases first: | 
					
						
							| 
									
										
										
										
											2013-09-27 19:01:14 +01:00
										 |  |  | # Standard console reporter | 
					
						
							| 
									
										
										
										
											2019-06-22 20:26:03 +02:00
										 |  |  | approve("console.std", ["~[!nonportable]~[!benchmark]~[approvals] *", "--order", "lex", "--rng-seed", "1"]) | 
					
						
							| 
									
										
										
										
											2013-09-27 19:01:14 +01:00
										 |  |  | # console reporter, include passes, warn about No Assertions, limit failures to first 4 | 
					
						
							| 
									
										
										
										
											2019-06-22 20:26:03 +02:00
										 |  |  | approve("console.swa4", ["~[!nonportable]~[!benchmark]~[approvals] *", "-s", "-w", "NoAssertions", "-x", "4", "--order", "lex", "--rng-seed", "1"]) | 
					
						
							| 
									
										
										
										
											2020-02-02 15:04:19 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | ## Common reporter checks: include passes, warn about No Assertions | 
					
						
							|  |  |  | reporters = ('console', 'junit', 'xml', 'compact', 'sonarqube', 'tap', 'teamcity', 'automake') | 
					
						
							|  |  |  | for reporter in reporters: | 
					
						
							|  |  |  |     filename = '{}.sw'.format(reporter) | 
					
						
							|  |  |  |     common_args = ["~[!nonportable]~[!benchmark]~[approvals] *", "-s", "-w", "NoAssertions", "--order", "lex", "--rng-seed", "1"] | 
					
						
							|  |  |  |     reporter_args = ['-r', reporter] | 
					
						
							|  |  |  |     approve(filename, common_args + reporter_args) | 
					
						
							| 
									
										
										
										
											2019-06-22 20:26:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-06 20:12:07 +01:00
										 |  |  | ## All reporters at the same time | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | common_args = ["~[!nonportable]~[!benchmark]~[approvals] *", "-s", "-w", "NoAssertions", "--order", "lex", "--rng-seed", "1"] | 
					
						
							|  |  |  | filenames = ['{}.sw.multi'.format(reporter) for reporter in reporters] | 
					
						
							|  |  |  | reporter_args = [] | 
					
						
							|  |  |  | for reporter, filename in zip(reporters, filenames): | 
					
						
							|  |  |  |     reporter_args += ['-r', '{}::{}'.format(reporter, get_rawResultsPath(filename))] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | run_test("default.sw.multi", common_args + reporter_args) | 
					
						
							|  |  |  | check_outputs("default.sw.multi") | 
					
						
							|  |  |  | for reporter, filename in zip(reporters, filenames): | 
					
						
							|  |  |  |     check_outputs(filename) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-09-27 19:01:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-08 11:31:11 +01:00
										 |  |  | if overallResult != 0: | 
					
						
							| 
									
										
										
										
											2017-02-24 15:56:26 +01:00
										 |  |  |     print("If these differences are expected, run approve.py to approve new baselines.") | 
					
						
							| 
									
										
										
										
											2017-01-09 14:12:12 +00:00
										 |  |  | exit(overallResult) |