• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3#   Copyright 2016 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16import logging
17from builtins import str
18
19import argparse
20import multiprocessing
21import signal
22import sys
23import traceback
24
25from acts import config_parser
26from acts import keys
27from acts import signals
28from acts import test_runner
29from acts.config.config_generator import ConfigGenerator
30
31
32def _run_test(parsed_config, test_identifiers, repeat=1):
33    """Instantiate and runs test_runner.TestRunner.
34
35    This is the function to start separate processes with.
36
37    Args:
38        parsed_config: A dict that is a set of configs for one
39                       test_runner.TestRunner.
40        test_identifiers: A list of tuples, each identifies what test case to
41                          run on what test class.
42        repeat: Number of times to iterate the specified tests.
43
44    Returns:
45        True if all tests passed without any error, False otherwise.
46    """
47    runner = _create_test_runner(parsed_config, test_identifiers)
48    try:
49        for i in range(repeat):
50            runner.run()
51        return runner.results.is_all_pass
52    except signals.TestAbortAll:
53        return True
54    except:
55        print("Exception when executing %s, iteration %s." %
56              (runner.testbed_name, i))
57        print(traceback.format_exc())
58    finally:
59        runner.stop()
60
61
62def _create_test_runner(parsed_config, test_identifiers):
63    """Instantiates one test_runner.TestRunner object and register termination
64    signal handlers that properly shut down the test_runner.TestRunner run.
65
66    Args:
67        parsed_config: A dict that is a set of configs for one
68                       test_runner.TestRunner.
69        test_identifiers: A list of tuples, each identifies what test case to
70                          run on what test class.
71
72    Returns:
73        A test_runner.TestRunner object.
74    """
75    try:
76        t = test_runner.TestRunner(parsed_config, test_identifiers)
77    except:
78        print("Failed to instantiate test runner, abort.")
79        print(traceback.format_exc())
80        sys.exit(1)
81    # Register handler for termination signals.
82    handler = config_parser.gen_term_signal_handler([t])
83    signal.signal(signal.SIGTERM, handler)
84    signal.signal(signal.SIGINT, handler)
85    return t
86
87
88def _run_tests_parallel(parsed_configs, test_identifiers, repeat):
89    """Executes requested tests in parallel.
90
91    Each test run will be in its own process.
92
93    Args:
94        parsed_configs: A list of dicts, each is a set of configs for one
95                        test_runner.TestRunner.
96        test_identifiers: A list of tuples, each identifies what test case to
97                          run on what test class.
98        repeat: Number of times to iterate the specified tests.
99
100    Returns:
101        True if all test runs executed successfully, False otherwise.
102    """
103    print("Executing {} concurrent test runs.".format(len(parsed_configs)))
104    arg_list = [(c, test_identifiers, repeat) for c in parsed_configs]
105    results = []
106    with multiprocessing.Pool(processes=len(parsed_configs)) as pool:
107        # Can't use starmap for py2 compatibility. One day, one day......
108        for args in arg_list:
109            results.append(pool.apply_async(_run_test, args))
110        pool.close()
111        pool.join()
112    for r in results:
113        if r.get() is False or isinstance(r, Exception):
114            return False
115
116
117def _run_tests_sequential(parsed_configs, test_identifiers, repeat):
118    """Executes requested tests sequentially.
119
120    Requested test runs will commence one after another according to the order
121    of their corresponding configs.
122
123    Args:
124        parsed_configs: A list of dicts, each is a set of configs for one
125                        test_runner.TestRunner.
126        test_identifiers: A list of tuples, each identifies what test case to
127                          run on what test class.
128        repeat: Number of times to iterate the specified tests.
129
130    Returns:
131        True if all test runs executed successfully, False otherwise.
132    """
133    ok = True
134    for c in parsed_configs:
135        try:
136            ret = _run_test(c, test_identifiers, repeat)
137            ok = ok and ret
138        except Exception as e:
139            print("Exception occurred when executing test bed %s. %s" %
140                  (c[keys.Config.key_testbed.value], e))
141    return ok
142
143
144def main(argv):
145    """This is a sample implementation of a cli entry point for ACTS test
146    execution.
147
148    Or you could implement your own cli entry point using acts.config_parser
149    functions and acts.test_runner.execute_one_test_class.
150    """
151    parser = argparse.ArgumentParser(
152        description=("Specify tests to run. If nothing specified, "
153                     "run all test cases found."))
154    parser.add_argument(
155        '-c',
156        '--config',
157        nargs=1,
158        type=str,
159        required=True,
160        metavar="<PATH>",
161        help="Path to the test configuration file.")
162    parser.add_argument(
163        '--test_args',
164        nargs='+',
165        type=str,
166        metavar="Arg1 Arg2 ...",
167        help=("Command-line arguments to be passed to every test case in a "
168              "test run. Use with caution."))
169    parser.add_argument(
170        '-p',
171        '--parallel',
172        action="store_true",
173        help=("If set, tests will be executed on all testbeds in parallel. "
174              "Otherwise, tests are executed iteratively testbed by testbed."))
175    parser.add_argument(
176        '-ci',
177        '--campaign_iterations',
178        metavar="<CAMPAIGN_ITERATIONS>",
179        nargs='?',
180        type=int,
181        const=1,
182        default=1,
183        help="Number of times to run the campaign or a group of test cases.")
184    parser.add_argument(
185        '-tb',
186        '--testbed',
187        nargs='+',
188        type=str,
189        metavar="[<TEST BED NAME1> <TEST BED NAME2> ...]",
190        help="Specify which test beds to run tests on.")
191    parser.add_argument(
192        '-lp',
193        '--logpath',
194        type=str,
195        metavar="<PATH>",
196        help="Root path under which all logs will be placed.")
197    parser.add_argument(
198        '-tp',
199        '--testpaths',
200        nargs='*',
201        type=str,
202        metavar="<PATH> <PATH>",
203        help="One or more non-recursive test class search paths.")
204
205    group = parser.add_mutually_exclusive_group(required=True)
206    group.add_argument(
207        '-tc',
208        '--testclass',
209        nargs='+',
210        type=str,
211        metavar="[TestClass1 TestClass2:test_xxx ...]",
212        help="A list of test classes/cases to run.")
213    group.add_argument(
214        '-tf',
215        '--testfile',
216        nargs=1,
217        type=str,
218        metavar="<PATH>",
219        help=("Path to a file containing a comma delimited list of test "
220              "classes to run."))
221    parser.add_argument(
222        '-r',
223        '--random',
224        action="store_true",
225        help="If set, tests will be executed in random order.")
226    parser.add_argument(
227        '-ti',
228        '--test_case_iterations',
229        metavar="<TEST_CASE_ITERATIONS>",
230        nargs='?',
231        type=int,
232        help="Number of times to run every test case.")
233
234    args = parser.parse_args(argv)
235    test_list = None
236    if args.testfile:
237        test_list = config_parser.parse_test_file(args.testfile[0])
238    elif args.testclass:
239        test_list = args.testclass
240    parsed_configs = config_parser.load_test_config_file(
241        args.config[0], args.testbed, args.testpaths, args.logpath,
242        args.test_args, args.random, args.test_case_iterations)
243
244    log = logging.getLogger()
245    try:
246        new_parsed_args = ConfigGenerator().generate_configs()
247
248        for old_config, new_config in zip(parsed_configs, new_parsed_args):
249            if not old_config.items() <= new_config.items():
250                log.warning('Backward compat broken:\n%s\n%s' % (
251                    old_config, new_config))
252    except SystemExit as e:
253        log.warning('Unable to parse command line flags: %s' %
254                        traceback.format_exc(e))
255    except Exception as e:
256        log.warning('Failed to generate configs through the new system: '
257                        '%s' % traceback.format_exc(e))
258
259    # Prepare args for test runs
260    test_identifiers = config_parser.parse_test_list(test_list)
261
262    # Execute test runners.
263    if args.parallel and len(parsed_configs) > 1:
264        print('Running tests in parallel.')
265        exec_result = _run_tests_parallel(parsed_configs, test_identifiers,
266                                          args.campaign_iterations)
267    else:
268        print('Running tests sequentially.')
269        exec_result = _run_tests_sequential(parsed_configs, test_identifiers,
270                                            args.campaign_iterations)
271    if exec_result is False:
272        # return 1 upon test failure.
273        sys.exit(1)
274    sys.exit(0)
275
276
277if __name__ == "__main__":
278    main(sys.argv[1:])
279