• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#! /usr/bin/env vpython3
2#
3# Copyright 2023 The ANGLE Project Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6#
7
8import argparse
9import contextlib
10import difflib
11import json
12import logging
13import os
14import pathlib
15import shutil
16import subprocess
17import sys
18import tempfile
19import time
20
21SCRIPT_DIR = str(pathlib.Path(__file__).resolve().parent)
22PY_UTILS = str(pathlib.Path(SCRIPT_DIR) / '..' / 'py_utils')
23if PY_UTILS not in sys.path:
24    os.stat(PY_UTILS) and sys.path.insert(0, PY_UTILS)
25import angle_test_util
26
27
28@contextlib.contextmanager
29def temporary_dir(prefix=''):
30    path = tempfile.mkdtemp(prefix=prefix)
31    try:
32        yield path
33    finally:
34        logging.info("Removing temporary directory: %s" % path)
35        shutil.rmtree(path)
36
37
38def file_content(path):
39    with open(path, 'rb') as f:
40        content = f.read()
41
42    if path.endswith('.json'):
43        info = json.loads(content)
44        info['TraceMetadata']['CaptureRevision'] = '<ignored>'
45        return json.dumps(info, indent=2).encode()
46
47    return content
48
49
50def diff_files(path, expected_path):
51    content = file_content(path)
52    expected_content = file_content(expected_path)
53    fn = os.path.basename(path)
54
55    if content == expected_content:
56        return False
57
58    if fn.endswith('.angledata'):
59        logging.error('Checks failed. Binary file contents mismatch: %s', fn)
60        return True
61
62    # Captured files are expected to have LF line endings.
63    # Note that git's EOL conversion for these files is disabled via .gitattributes
64    assert b'\r\n' not in content
65    assert b'\r\n' not in expected_content
66
67    diff = list(
68        difflib.unified_diff(
69            expected_content.decode().splitlines(),
70            content.decode().splitlines(),
71            fromfile=fn,
72            tofile=fn,
73        ))
74
75    logging.error('Checks failed. Found diff in %s:\n%s\n', fn, '\n'.join(diff))
76    return True
77
78
79def run_test(test_name, overwrite_expected):
80    with temporary_dir() as temp_dir:
81        if angle_test_util.IsAndroid():
82            cmd = [
83                '../../src/tests/angle_android_test_runner.py', 'gtest',
84                '--suite=angle_end2end_tests', '--output-directory=.'
85            ]
86        else:
87            cmd = [angle_test_util.ExecutablePathInCurrentDir('angle_end2end_tests')]
88
89        test_args = ['--gtest_filter=%s' % test_name, '--angle-per-test-capture-label']
90        extra_env = {
91            'ANGLE_CAPTURE_ENABLED': '1',
92            'ANGLE_CAPTURE_FRAME_START': '2',
93            'ANGLE_CAPTURE_FRAME_END': '5',
94            'ANGLE_CAPTURE_OUT_DIR': temp_dir,
95            'ANGLE_CAPTURE_COMPRESSION': '0',
96        }
97        subprocess.check_call(cmd + test_args, env={**os.environ.copy(), **extra_env})
98        logging.info('Capture finished, comparing files')
99        files = sorted(fn for fn in os.listdir(temp_dir))
100        expected_dir = os.path.join(SCRIPT_DIR, 'expected')
101        expected_files = sorted(fn for fn in os.listdir(expected_dir) if not fn.startswith('.'))
102
103        if overwrite_expected:
104            for f in expected_files:
105                os.remove(os.path.join(expected_dir, f))
106            shutil.copytree(temp_dir, expected_dir, dirs_exist_ok=True)
107            return True
108
109        if files != expected_files:
110            logging.error(
111                'Checks failed. Capture produced a different set of files: %s\nDiff:\n%s\n', files,
112                '\n'.join(difflib.unified_diff(expected_files, files)))
113            return False
114
115        has_diffs = False
116        for fn in files:
117            has_diffs |= diff_files(os.path.join(temp_dir, fn), os.path.join(expected_dir, fn))
118
119        return not has_diffs
120
121
122def main():
123    parser = argparse.ArgumentParser()
124    parser.add_argument('--isolated-script-test-output', type=str)
125    parser.add_argument('--log', help='Logging level.', default='info')
126    parser.add_argument(
127        '--overwrite-expected', help='Overwrite contents of expected/', action='store_true')
128    args, extra_flags = parser.parse_known_args()
129
130    logging.basicConfig(level=args.log.upper())
131
132    angle_test_util.Initialize('angle_end2end_tests')
133
134    test_name = 'CapturedTest.MultiFrame/ES3_Vulkan'
135    had_error = False
136    try:
137        if not run_test(test_name, args.overwrite_expected):
138            had_error = True
139            logging.error(
140                'Found capture diffs. If diffs are expected, build angle_end2end_tests and run '
141                '(cd out/<build>; ../../src/tests/capture_tests/capture_tests.py --overwrite-expected)'
142            )
143    except Exception as e:
144        logging.exception(e)
145        had_error = True
146
147    if args.isolated_script_test_output:
148        results = {
149            'tests': {
150                'capture_test': {}
151            },
152            'interrupted': False,
153            'seconds_since_epoch': time.time(),
154            'path_delimiter': '.',
155            'version': 3,
156            'num_failures_by_type': {
157                'FAIL': 0,
158                'PASS': 0,
159                'SKIP': 0,
160            },
161        }
162        result = 'FAIL' if had_error else 'PASS'
163        results['tests']['capture_test'][test_name] = {'expected': 'PASS', 'actual': result}
164        results['num_failures_by_type'][result] += 1
165
166        with open(args.isolated_script_test_output, 'w') as f:
167            f.write(json.dumps(results, indent=2))
168
169    return 1 if had_error else 0
170
171
172if __name__ == '__main__':
173    sys.exit(main())
174