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 24from __future__ import print_function 25import argparse 26import difflib 27import errno 28import io 29import os 30import subprocess 31import sys 32import tempfile 33 34# The meson version handles windows paths better, but if it's not available 35# fall back to shlex 36try: 37 from meson.mesonlib import split_args 38except ImportError: 39 from shlex import split as split_args 40 41 42def arg_parser(): 43 parser = argparse.ArgumentParser() 44 parser.add_argument('glcpp', help='Path to the he glcpp binary.') 45 parser.add_argument('testdir', help='Path to tests and expected output.') 46 parser.add_argument('--unix', action='store_true', help='Run tests for Unix style newlines') 47 parser.add_argument('--windows', action='store_true', help='Run tests for Windows/Dos style newlines') 48 parser.add_argument('--oldmac', action='store_true', help='Run tests for Old Mac (pre-OSX) style newlines') 49 parser.add_argument('--bizarro', action='store_true', help='Run tests for Bizarro world style newlines') 50 parser.add_argument('--valgrind', action='store_true', help='Run with valgrind for errors') 51 return parser.parse_args() 52 53 54def parse_test_file(filename, nl_format): 55 """Check for any special arguments and return them as a list.""" 56 # Disable "universal newlines" mode; we can't directly use `nl_format` as 57 # the `newline` argument, because the "bizarro" test uses something Python 58 # considers invalid. 59 with io.open(filename, newline='') as f: 60 for l in f.read().split(nl_format): 61 if 'glcpp-args:' in l: 62 return l.split('glcpp-args:')[1].strip().split() 63 return [] 64 65 66def test_output(glcpp, filename, expfile, nl_format='\n'): 67 """Test that the output of glcpp is what we expect.""" 68 extra_args = parse_test_file(filename, nl_format) 69 70 with open(filename, 'rb') as f: 71 proc = subprocess.Popen( 72 glcpp + extra_args, 73 stdout=subprocess.PIPE, 74 stderr=subprocess.STDOUT, 75 stdin=subprocess.PIPE) 76 actual, _ = proc.communicate(f.read()) 77 actual = actual.decode('utf-8') 78 79 if proc.returncode == 255: 80 print("Test returned general error, possibly missing linker") 81 sys.exit(77) 82 83 with open(expfile, 'r') as f: 84 expected = f.read() 85 86 # Bison 3.6 changed '$end' to 'end of file' in its error messages 87 # See: https://gitlab.freedesktop.org/mesa/mesa/-/issues/3181 88 actual = actual.replace('$end', 'end of file') 89 90 if actual == expected: 91 return (True, []) 92 return (False, difflib.unified_diff(actual.splitlines(), expected.splitlines())) 93 94 95def _valgrind(glcpp, filename): 96 """Run valgrind and report any warnings.""" 97 extra_args = parse_test_file(filename, nl_format='\n') 98 99 try: 100 fd, tmpfile = tempfile.mkstemp() 101 os.close(fd) 102 with open(filename, 'rb') as f: 103 proc = subprocess.Popen( 104 ['valgrind', '--error-exitcode=31', '--log-file', tmpfile] + glcpp + extra_args, 105 stdout=subprocess.PIPE, 106 stderr=subprocess.STDOUT, 107 stdin=subprocess.PIPE) 108 proc.communicate(f.read()) 109 if proc.returncode != 31: 110 return (True, []) 111 with open(tmpfile, 'rb') as f: 112 contents = f.read() 113 return (False, contents) 114 finally: 115 os.unlink(tmpfile) 116 117 118def test_unix(args): 119 """Test files with unix style (\n) new lines.""" 120 total = 0 121 passed = 0 122 123 print('============= Testing for Correctness (Unix) =============') 124 for filename in os.listdir(args.testdir): 125 if not filename.endswith('.c'): 126 continue 127 128 print( '{}:'.format(os.path.splitext(filename)[0]), end=' ') 129 total += 1 130 131 testfile = os.path.join(args.testdir, filename) 132 valid, diff = test_output(args.glcpp, testfile, testfile + '.expected') 133 if valid: 134 passed += 1 135 print('PASS') 136 else: 137 print('FAIL') 138 for l in diff: 139 print(l, file=sys.stderr) 140 141 if not total: 142 raise Exception('Could not find any tests.') 143 144 print('{}/{}'.format(passed, total), 'tests returned correct results') 145 return total == passed 146 147 148def _replace_test(args, replace): 149 """Test files with non-unix style line endings. Print your own header.""" 150 total = 0 151 passed = 0 152 153 for filename in os.listdir(args.testdir): 154 if not filename.endswith('.c'): 155 continue 156 157 print( '{}:'.format(os.path.splitext(filename)[0]), end=' ') 158 total += 1 159 testfile = os.path.join(args.testdir, filename) 160 try: 161 fd, tmpfile = tempfile.mkstemp() 162 os.close(fd) 163 with io.open(testfile, 'rt') as f: 164 contents = f.read() 165 with io.open(tmpfile, 'wt') as f: 166 f.write(contents.replace('\n', replace)) 167 valid, diff = test_output( 168 args.glcpp, tmpfile, testfile + '.expected', nl_format=replace) 169 finally: 170 os.unlink(tmpfile) 171 172 if valid: 173 passed += 1 174 print('PASS') 175 else: 176 print('FAIL') 177 for l in diff: 178 print(l, file=sys.stderr) 179 180 if not total: 181 raise Exception('Could not find any tests.') 182 183 print('{}/{}'.format(passed, total), 'tests returned correct results') 184 return total == passed 185 186 187def test_windows(args): 188 """Test files with windows/dos style (\r\n) new lines.""" 189 print('============= Testing for Correctness (Windows) =============') 190 return _replace_test(args, '\r\n') 191 192 193def test_oldmac(args): 194 """Test files with Old Mac style (\r) new lines.""" 195 print('============= Testing for Correctness (Old Mac) =============') 196 return _replace_test(args, '\r') 197 198 199def test_bizarro(args): 200 """Test files with Bizarro world style (\n\r) new lines.""" 201 # This is allowed by the spec, but why? 202 print('============= Testing for Correctness (Bizarro) =============') 203 return _replace_test(args, '\n\r') 204 205 206def test_valgrind(args): 207 total = 0 208 passed = 0 209 210 print('============= Testing for Valgrind Warnings =============') 211 for filename in os.listdir(args.testdir): 212 if not filename.endswith('.c'): 213 continue 214 215 print( '{}:'.format(os.path.splitext(filename)[0]), end=' ') 216 total += 1 217 valid, log = _valgrind(args.glcpp, os.path.join(args.testdir, filename)) 218 if valid: 219 passed += 1 220 print('PASS') 221 else: 222 print('FAIL') 223 print(log, file=sys.stderr) 224 225 if not total: 226 raise Exception('Could not find any tests.') 227 228 print('{}/{}'.format(passed, total), 'tests returned correct results') 229 return total == passed 230 231 232def main(): 233 args = arg_parser() 234 235 wrapper = os.environ.get('MESON_EXE_WRAPPER') 236 if wrapper is not None: 237 args.glcpp = split_args(wrapper) + [args.glcpp] 238 else: 239 args.glcpp = [args.glcpp] 240 241 success = True 242 try: 243 if args.unix: 244 success = success and test_unix(args) 245 if args.windows: 246 success = success and test_windows(args) 247 if args.oldmac: 248 success = success and test_oldmac(args) 249 if args.bizarro: 250 success = success and test_bizarro(args) 251 if args.valgrind: 252 success = success and test_valgrind(args) 253 except OSError as e: 254 if e.errno == errno.ENOEXEC: 255 print('Skipping due to inability to run host binaries.', 256 file=sys.stderr) 257 sys.exit(77) 258 raise 259 260 exit(0 if success else 1) 261 262 263if __name__ == '__main__': 264 main() 265