• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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