• 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
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