• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env vpython3
2# Copyright 2021 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import argparse
7import os
8import stat
9import sys
10
11sys.path.append(
12    os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
13                 'build'))
14# //build imports.
15import action_helpers
16
17
18def _parse_args(args):
19    description = 'Generator of bash scripts that can invoke the Python ' \
20                  'library for running Rust unit tests with support for ' \
21                  'Chromium test filters, sharding, and test output.'
22    parser = argparse.ArgumentParser(description=description)
23    parser.add_argument('--script-path',
24                        dest='script_path',
25                        help='Where to write the bash script.',
26                        metavar='FILEPATH',
27                        required=True)
28    parser.add_argument('--exe-dir',
29                        dest='exe_dir',
30                        help='Directory where the wrapped executables are',
31                        metavar='PATH',
32                        required=True)
33    parser.add_argument('--rust-test-executables',
34                        dest='rust_test_executables',
35                        help='File listing one or more executables to wrap. ' \
36                             '(basenames - no .exe extension or directory)',
37                        metavar='FILEPATH',
38                        required=True)
39    parser.add_argument('--make-bat',
40                        action='store_true',
41                        help='Generate a .bat file instead of a bash script')
42    return parser.parse_args(args=args)
43
44
45def _find_test_executables(args):
46    exes = set()
47    input_filepath = args.rust_test_executables
48    with open(input_filepath) as f:
49        for line in f:
50            exe_name = line.strip()
51            # Append ".exe" extension when *targeting* Windows, not when this
52            # script is running on windows. We do that by using `args.make_bat`
53            # as a signal.
54            if exe_name in exes:
55                raise ValueError(
56                    f'Duplicate entry "{exe_name}" in {input_filepath}')
57            if args.make_bat:
58                suffix = '.exe'
59            else:
60                suffix = ''
61            exes.add(f'{exe_name}{suffix}')
62    if not exes:
63        raise ValueError(f'Unexpectedly empty file: {input_filepath}')
64    exes = sorted(exes)  # For stable results in unit tests.
65    return exes
66
67
68def _validate_if_test_executables_exist(exes):
69    for exe in exes:
70        if not os.path.isfile(exe):
71            raise ValueError(f'File not found: {exe}')
72
73
74def _generate_script(args, should_validate_if_exes_exist=True):
75    THIS_DIR = os.path.abspath(os.path.dirname(__file__))
76    GEN_SCRIPT_DIR = os.path.dirname(args.script_path)
77
78    # Path from the .bat or bash script to the test exes.
79    exe_dir = os.path.relpath(args.exe_dir, start=GEN_SCRIPT_DIR)
80    exe_dir = os.path.normpath(exe_dir)
81
82    # Path from the .bat or bash script to the python main.
83    main_dir = os.path.relpath(THIS_DIR, start=GEN_SCRIPT_DIR)
84    main_dir = os.path.normpath(main_dir)
85
86    exes = _find_test_executables(args)
87    if should_validate_if_exes_exist:
88        _validate_if_test_executables_exist(exes)
89
90    if args.make_bat:
91        res = '@echo off\n'
92        res += f'vpython3 "%~dp0\\{main_dir}\\rust_main_program.py" ^\n'
93        for exe in exes:
94            res += f'    "--rust-test-executable=%~dp0\\{exe_dir}\\{exe}" ^\n'
95        res += '    %*'
96    else:
97        res = '#!/bin/bash\n'
98        res += (f'env vpython3 '
99                f'"$(dirname $0)/{main_dir}/rust_main_program.py" \\\n')
100        for exe in exes:
101            res += (
102                f'    '
103                f'"--rust-test-executable=$(dirname $0)/{exe_dir}/{exe}" \\\n')
104        res += '    "$@"'
105    return res
106
107
108def _main():
109    args = _parse_args(sys.argv[1:])
110
111    # atomic_output will ensure we only write to the file on disk if what we
112    # give to write() is different than what's currently on disk.
113    with action_helpers.atomic_output(args.script_path) as f:
114        f.write(_generate_script(args).encode())
115
116    # chmod a+x
117    st = os.stat(args.script_path)
118    if (not st.st_mode & stat.S_IXUSR) or (not st.st_mode & stat.S_IXGRP) or \
119       (not st.st_mode & stat.S_IXOTH):
120        os.chmod(args.script_path,
121                 st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
122
123
124if __name__ == '__main__':
125    _main()
126