1# encoding=utf-8 2# Copyright © 2018 Intel Corporation 3 4# Permission is hereby granted, free of charge, to any person obtaining a copy 5# of this software and associated documentation files (the "Software"), to deal 6# in the Software without restriction, including without limitation the rights 7# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8# copies of the Software, and to permit persons to whom the Software is 9# furnished to do so, subject to the following conditions: 10 11# The above copyright notice and this permission notice shall be included in 12# all copies or substantial portions of the Software. 13 14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20# SOFTWARE. 21 22"""Run glcpp tests with various line endings.""" 23 24import argparse 25import difflib 26import errno 27import io 28import os 29import subprocess 30import sys 31 32# The meson version handles windows paths better, but if it's not available 33# fall back to shlex 34try: 35 from meson.mesonlib import split_args 36except ImportError: 37 from shlex import split as split_args 38 39 40def arg_parser(): 41 parser = argparse.ArgumentParser() 42 parser.add_argument('glcpp', help='Path to the he glcpp binary.') 43 parser.add_argument('testdir', help='Path to tests and expected output.') 44 parser.add_argument('--unix', action='store_true', help='Run tests for Unix style newlines') 45 parser.add_argument('--windows', action='store_true', help='Run tests for Windows/Dos style newlines') 46 parser.add_argument('--oldmac', action='store_true', help='Run tests for Old Mac (pre-OSX) style newlines') 47 parser.add_argument('--bizarro', action='store_true', help='Run tests for Bizarro world style newlines') 48 parser.add_argument('--valgrind', action='store_true', help='Run with valgrind for errors') 49 return parser.parse_args() 50 51 52def parse_test_file(contents, nl_format): 53 """Check for any special arguments and return them as a list.""" 54 # Disable "universal newlines" mode; we can't directly use `nl_format` as 55 # the `newline` argument, because the "bizarro" test uses something Python 56 # considers invalid. 57 for l in contents.decode('utf-8').split(nl_format): 58 if 'glcpp-args:' in l: 59 return l.split('glcpp-args:')[1].strip().split() 60 return [] 61 62 63def test_output(glcpp, contents, expfile, nl_format='\n'): 64 """Test that the output of glcpp is what we expect.""" 65 extra_args = parse_test_file(contents, nl_format) 66 67 proc = subprocess.Popen( 68 glcpp + extra_args, 69 stdout=subprocess.PIPE, 70 stderr=subprocess.STDOUT, 71 stdin=subprocess.PIPE) 72 actual, _ = proc.communicate(contents) 73 actual = actual.decode('utf-8') 74 75 if proc.returncode == 255: 76 print("Test returned general error, possibly missing linker") 77 sys.exit(77) 78 79 with open(expfile, 'r') as f: 80 expected = f.read() 81 82 # Bison 3.6 changed '$end' to 'end of file' in its error messages 83 # See: https://gitlab.freedesktop.org/mesa/mesa/-/issues/3181 84 actual = actual.replace('$end', 'end of file') 85 86 if actual == expected: 87 return (True, []) 88 return (False, difflib.unified_diff(actual.splitlines(), expected.splitlines())) 89 90 91def _valgrind(glcpp, filename): 92 """Run valgrind and report any warnings.""" 93 with open(filename, 'rb') as f: 94 contents = f.read() 95 extra_args = parse_test_file(contents, nl_format='\n') 96 97 proc = subprocess.Popen( 98 ['valgrind', '--error-exitcode=126'] + glcpp + extra_args, 99 stdout=subprocess.DEVNULL, 100 stderr=subprocess.PIPE, 101 stdin=subprocess.PIPE) 102 _, errors = proc.communicate(contents) 103 if proc.returncode != 126: 104 return (True, []) 105 return (False, errors.decode('utf-8')) 106 107 108def test_unix(args): 109 """Test files with unix style (\n) new lines.""" 110 total = 0 111 passed = 0 112 113 print('============= Testing for Correctness (Unix) =============') 114 for filename in os.listdir(args.testdir): 115 if not filename.endswith('.c'): 116 continue 117 118 print( '{}:'.format(os.path.splitext(filename)[0]), end=' ') 119 total += 1 120 121 testfile = os.path.join(args.testdir, filename) 122 with open(testfile, 'rb') as f: 123 contents = f.read() 124 valid, diff = test_output(args.glcpp, contents, testfile + '.expected') 125 if valid: 126 passed += 1 127 print('PASS') 128 else: 129 print('FAIL') 130 for l in diff: 131 print(l, file=sys.stderr) 132 133 if not total: 134 raise Exception('Could not find any tests.') 135 136 print('{}/{}'.format(passed, total), 'tests returned correct results') 137 return total == passed 138 139 140def _replace_test(args, replace): 141 """Test files with non-unix style line endings. Print your own header.""" 142 total = 0 143 passed = 0 144 145 for filename in os.listdir(args.testdir): 146 if not filename.endswith('.c'): 147 continue 148 149 print( '{}:'.format(os.path.splitext(filename)[0]), end=' ') 150 total += 1 151 testfile = os.path.join(args.testdir, filename) 152 153 with open(testfile, 'rt') as f: 154 contents = f.read() 155 contents = contents.replace('\n', replace).encode('utf-8') 156 valid, diff = test_output( 157 args.glcpp, contents, testfile + '.expected', nl_format=replace) 158 159 if valid: 160 passed += 1 161 print('PASS') 162 else: 163 print('FAIL') 164 for l in diff: 165 print(l, file=sys.stderr) 166 167 if not total: 168 raise Exception('Could not find any tests.') 169 170 print('{}/{}'.format(passed, total), 'tests returned correct results') 171 return total == passed 172 173 174def test_windows(args): 175 """Test files with windows/dos style (\r\n) new lines.""" 176 print('============= Testing for Correctness (Windows) =============') 177 return _replace_test(args, '\r\n') 178 179 180def test_oldmac(args): 181 """Test files with Old Mac style (\r) new lines.""" 182 print('============= Testing for Correctness (Old Mac) =============') 183 return _replace_test(args, '\r') 184 185 186def test_bizarro(args): 187 """Test files with Bizarro world style (\n\r) new lines.""" 188 # This is allowed by the spec, but why? 189 print('============= Testing for Correctness (Bizarro) =============') 190 return _replace_test(args, '\n\r') 191 192 193def test_valgrind(args): 194 total = 0 195 passed = 0 196 197 print('============= Testing for Valgrind Warnings =============') 198 for filename in os.listdir(args.testdir): 199 if not filename.endswith('.c'): 200 continue 201 202 print( '{}:'.format(os.path.splitext(filename)[0]), end=' ') 203 total += 1 204 valid, log = _valgrind(args.glcpp, os.path.join(args.testdir, filename)) 205 if valid: 206 passed += 1 207 print('PASS') 208 else: 209 print('FAIL') 210 print(log, file=sys.stderr) 211 212 if not total: 213 raise Exception('Could not find any tests.') 214 215 print('{}/{}'.format(passed, total), 'tests returned correct results') 216 return total == passed 217 218 219def main(): 220 args = arg_parser() 221 222 wrapper = os.environ.get('MESON_EXE_WRAPPER') 223 if wrapper is not None: 224 args.glcpp = split_args(wrapper) + [args.glcpp] 225 else: 226 args.glcpp = [args.glcpp] 227 228 success = True 229 try: 230 if args.unix: 231 success = success and test_unix(args) 232 if args.windows: 233 success = success and test_windows(args) 234 if args.oldmac: 235 success = success and test_oldmac(args) 236 if args.bizarro: 237 success = success and test_bizarro(args) 238 if args.valgrind: 239 success = success and test_valgrind(args) 240 except OSError as e: 241 if e.errno == errno.ENOEXEC: 242 print('Skipping due to inability to run host binaries.', 243 file=sys.stderr) 244 sys.exit(77) 245 raise 246 247 exit(0 if success else 1) 248 249 250if __name__ == '__main__': 251 main() 252