blob: dc1e058c1a8d3cf5e87dcc522d9ce115c59feda2 [file] [log] [blame]
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +02001#!/usr/bin/env python3
2
3# osmo_gsm_tester: invoke a single test run
4#
5# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
6#
7# Author: Neels Hofmeyr <neels@hofmeyr.de>
8#
9# This program is free software: you can redistribute it and/or modify
Harald Welte27205342017-06-03 09:51:45 +020010# it under the terms of the GNU General Public License as
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020011# published by the Free Software Foundation, either version 3 of the
12# License, or (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte27205342017-06-03 09:51:45 +020017# GNU General Public License for more details.
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020018#
Harald Welte27205342017-06-03 09:51:45 +020019# You should have received a copy of the GNU General Public License
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020020# along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22'''osmo_gsm_tester: invoke a single test run.
23
Neels Hofmeyr3531a192017-03-28 14:30:28 +020024Examples:
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020025
Neels Hofmeyr3531a192017-03-28 14:30:28 +020026./run_once.py ~/my_trial_package/ -s osmo_trx
27./run_once.py ~/my_trial_package/ -c sms_tests:dyn_ts+eu_band+bts_sysmo
28./run_once.py ~/my_trial_package/ -c sms_tests/mo_mt_sms:bts_trx
29
30(The names for test suite, scenario and series names used in these examples
31must be defined by the osmo-gsm-tester configuration.)
32
33A trial package contains binaries (usually built by a jenkins job) of GSM
34software, including the core network programs as well as binaries for the
35various BTS models.
36
37A test suite defines specific actions to be taken and verifies their outcome.
38Such a test suite may leave certain aspects of a setup undefined, e.g. it may
39be BTS model agnostic or does not care which voice codecs are chosen.
40
41A test scenario completes the picture in that it defines which specific choices
42shall be made to run a test suite. Any one test suite may thus run on any
43number of different scenarios, e.g. to test various voice codecs.
44
45Test scenarios may be combined. For example, one scenario may define a timeslot
46configuration to use, while another scenario may define the voice codec
47configuration.
48
49There may still be aspects that are neither required by a test suite nor
50strictly defined by a scenario, which will be resolved automatically, e.g. by
51choosing the first available item that matches the other constraints.
52
53A test run thus needs to define: a trial package containing built binaries, a
54combination of scenarios to run a suite in, and a test suite to launch in the
55given scenario with the given binaries.
56
57The osmo-gsm-tester configuration may define one or more series as a number of
58suite:scenario combinations. So instead of a specific suite:scenario
59combination, the name of such a series can be passed.
60
61If neither a combination or series is specified, the default series will be run
62as defined in the osmo-gsm-tester configuration.
63
64The scenarios and suites run for a given trial will be recorded in a trial
65package's directory: Upon launch, a 'test_package/run.<date>' directory will be
66created, which will collect logs and reports.
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020067'''
68
Neels Hofmeyre3528442017-04-08 21:18:30 +020069import sys
Neels Hofmeyre60df692017-05-14 03:05:48 +020070import argparse
Pau Espin Pedrol469316f2017-05-17 14:51:31 +020071from signal import *
Neels Hofmeyrd46ea132017-04-08 15:56:31 +020072from osmo_gsm_tester import __version__
Pau Espin Pedrolea8c3d42020-05-04 12:05:05 +020073from osmo_gsm_tester.core import log
Pau Espin Pedrolf574a462020-05-05 12:18:35 +020074from osmo_gsm_tester.core import trial
75from osmo_gsm_tester.core import suite
Pau Espin Pedrolea8c3d42020-05-04 12:05:05 +020076from osmo_gsm_tester.core.config import read_config_file, DEFAULT_SUITES_CONF
77from osmo_gsm_tester.core.schema import generate_schemas
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020078
Pau Espin Pedrol469316f2017-05-17 14:51:31 +020079def sig_handler_cleanup(signum, frame):
80 print("killed by signal %d" % signum)
81 # This sys.exit() will raise a SystemExit base exception at the current
82 # point of execution. Code must be prepared to clean system-wide resources
83 # by using the "finally" section. This allows at the end 'atexit' hooks to
84 # be called before exiting.
85 sys.exit(1)
86
Neels Hofmeyr21235412017-05-14 03:00:21 +020087def main():
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020088
Pau Espin Pedrol469316f2017-05-17 14:51:31 +020089 for sig in (SIGINT, SIGTERM, SIGQUIT, SIGPIPE, SIGHUP):
90 signal(sig, sig_handler_cleanup)
91
Neels Hofmeyr3531a192017-03-28 14:30:28 +020092 parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.RawTextHelpFormatter)
Neels Hofmeyrcf0304f2017-05-06 22:07:39 +020093 # Note: since we're using RawTextHelpFormatter to keep nicely separate
94 # paragraphs in the long help text, we unfortunately also need to take care
95 # of line wraps in the shorter cmdline options help.
96 # The line width here is what remains of screen width after the list of
97 # options placed by ArgumentParser. That's unfortunately subject to change
98 # and undefined, so when things change, just run a local
99 # ./osmo-gsm-tester.py --help and try to keep everything in 80 chars width.
100 # The help text is indented automatically, but line width is manual.
101 # Using multi-line strings here -- doesn't look nice in the python flow but
102 # is easiest to maintain.
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200103 parser.add_argument('-V', '--version', action='store_true',
104 help='Show version')
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200105 parser.add_argument('trial_package',
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200106 help='Directory containing binaries to test')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200107 parser.add_argument('-s', '--suite-scenario', dest='suite_scenario', action='append',
Neels Hofmeyrcf0304f2017-05-06 22:07:39 +0200108 help='''A suite-scenarios combination
109like suite:scenario+scenario''')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200110 parser.add_argument('-S', '--series', dest='series', action='append',
Neels Hofmeyrcf0304f2017-05-06 22:07:39 +0200111 help='''A series of suite-scenarios combinations
112as defined in the osmo-gsm-tester configuration''')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200113 parser.add_argument('-t', '--test', dest='test', action='append',
Neels Hofmeyrcf0304f2017-05-06 22:07:39 +0200114 help='''Run only tests matching this name.
115Any test name that contains the given string is run.
116To get an exact match, prepend a "=" like
117"-t =my_exact_name". The ".py" suffix is always
118optional.''')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200119 parser.add_argument('-l', '--log-level', dest='log_level', choices=log.LEVEL_STRS.keys(),
120 default=None,
121 help='Set logging level for all categories (on stdout)')
122 parser.add_argument('-T', '--traceback', dest='trace', action='store_true',
Neels Hofmeyr42bebfa2017-06-06 19:41:42 +0200123 help='Enable stdout logging of tracebacks')
124 parser.add_argument('-R', '--source', dest='source', action='store_true',
125 help='Enable stdout logging of source file')
Neels Hofmeyrf15eaf92017-06-05 18:03:53 +0200126 parser.add_argument('-c', '--conf-dir', dest='conf_dir',
127 help='''Specify configuration dir (overrides
128OSMO_GSM_TESTER_CONF env and default locations)''')
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200129 args = parser.parse_args()
130
131 if args.version:
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200132 print(__version__)
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200133 exit(0)
134
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200135 print('combinations:', repr(args.suite_scenario))
136 print('series:', repr(args.series))
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200137 print('trial:', repr(args.trial_package))
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200138 print('tests:', repr(args.test))
139
Neels Hofmeyrf8166882017-05-05 19:48:35 +0200140 # create a default log to stdout
Neels Hofmeyr9576f5f2017-05-24 18:31:01 +0200141 log.LogTarget().style(all_origins_on_levels=(log.L_ERR, log.L_TRACEBACK), src=False)
Neels Hofmeyrf8166882017-05-05 19:48:35 +0200142
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200143 if args.log_level:
144 log.set_all_levels(log.LEVEL_STRS.get(args.log_level))
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200145 if args.trace:
146 log.style_change(trace=True)
Neels Hofmeyr42bebfa2017-06-06 19:41:42 +0200147 if args.source:
148 log.style_change(src=True)
Neels Hofmeyrf15eaf92017-06-05 18:03:53 +0200149 if args.conf_dir:
150 config.override_conf = args.conf_dir
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200151
152 combination_strs = list(args.suite_scenario or [])
153 # for series in args.series:
154 # combination_strs.extend(config.get_series(series))
155
156 if not combination_strs:
Neels Hofmeyrd46ea132017-04-08 15:56:31 +0200157 combination_strs = config.read_config_file(config.DEFAULT_SUITES_CONF, if_missing_return=[])
158
159 if combination_strs:
160 print('Running default suites:\n ' + ('\n '.join(combination_strs)))
Your Name3c6673a2017-04-08 18:52:39 +0200161 else:
162 print('No default suites configured (%r)' % config.DEFAULT_SUITES_CONF)
163
Neels Hofmeyrd46ea132017-04-08 15:56:31 +0200164
165 if not combination_strs:
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200166 raise RuntimeError('Need at least one suite:scenario or series to run')
167
Pau Espin Pedrolea8c3d42020-05-04 12:05:05 +0200168 # Generate supported schemas dynamically from objects:
169 generate_schemas()
170
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200171 # make sure all suite:scenarios exist
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200172 suite_scenarios = []
173 for combination_str in combination_strs:
174 suite_scenarios.append(suite.load_suite_scenario_str(combination_str))
175
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200176 # pick tests and make sure they exist
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200177 test_names = []
178 for test_name in (args.test or []):
179 found = False
Neels Hofmeyrf9de78f2017-05-06 16:04:37 +0200180 if test_name.startswith('=') and not test_name.endswith('.py'):
181 test_name = test_name + '.py'
Neels Hofmeyr930ac952017-05-06 15:49:53 +0200182 for suite_scenario_str, suite_def, scenarios in suite_scenarios:
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200183 for def_test_name in suite_def.test_basenames:
Neels Hofmeyrf9de78f2017-05-06 16:04:37 +0200184 if test_name.startswith('='):
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200185 match = test_name[1:] == def_test_name
Neels Hofmeyrf9de78f2017-05-06 16:04:37 +0200186 else:
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200187 match = test_name in def_test_name
Neels Hofmeyrf9de78f2017-05-06 16:04:37 +0200188 if match:
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200189 found = True
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200190 test_names.append(def_test_name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200191 if not found:
192 raise RuntimeError('No test found for %r' % test_name)
193 if test_names:
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200194 test_names = sorted(set(test_names))
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200195 print(repr(test_names))
196
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200197 with trial.Trial(args.trial_package) as current_trial:
198 current_trial.verify()
199 for suite_scenario_str, suite_def, scenarios in suite_scenarios:
200 current_trial.add_suite_run(suite_scenario_str, suite_def, scenarios)
201 current_trial.run_suites(test_names)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200202
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200203 if current_trial.status != trial.Trial.PASS:
204 return 1
205 return 0
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200206
Neels Hofmeyr21235412017-05-14 03:00:21 +0200207if __name__ == '__main__':
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200208 rc = 2
Neels Hofmeyr21235412017-05-14 03:00:21 +0200209 try:
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200210 rc = main()
Neels Hofmeyr21235412017-05-14 03:00:21 +0200211 except:
212 # Tell the log to show the exception, then terminate the program with the exception anyway.
213 # Since exceptions within test runs should be caught and evaluated, this is basically about
214 # exceptions during command line parsing and such, so it's appropriate to abort immediately.
215 log.log_exn()
216 raise
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200217 exit(rc)
Neels Hofmeyr21235412017-05-14 03:00:21 +0200218
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200219# vim: expandtab tabstop=4 shiftwidth=4