blob: 15b192309dc3bed0ce91f916eff6e139750d1347 [file] [log] [blame]
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +02001#!/usr/bin/env python3
2
Pau Espin Pedrol0bd048c2020-05-07 19:32:21 +02003# osmo-gsm-tester.py: main program file
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +02004#
Pau Espin Pedrol0bd048c2020-05-07 19:32:21 +02005# Copyright (C) 2016-2020 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +02006#
7# Author: Neels Hofmeyr <neels@hofmeyr.de>
Pau Espin Pedrol0bd048c2020-05-07 19:32:21 +02008# Author: Pau Espin Pedrol <pespin@sysmocom.de>
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +02009#
10# This program is free software: you can redistribute it and/or modify
Harald Welte27205342017-06-03 09:51:45 +020011# it under the terms of the GNU General Public License as
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020012# published by the Free Software Foundation, either version 3 of the
13# License, or (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte27205342017-06-03 09:51:45 +020018# GNU General Public License for more details.
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020019#
Harald Welte27205342017-06-03 09:51:45 +020020# You should have received a copy of the GNU General Public License
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020021# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
Pau Espin Pedrol0bd048c2020-05-07 19:32:21 +020023'''osmo-gsm-tester.py: invoke a single test run
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020024
Neels Hofmeyr3531a192017-03-28 14:30:28 +020025Examples:
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020026
Pau Espin Pedrole972c9c2020-05-12 15:06:55 +020027./osmo-gsm-tester.py -c doc/examples/2g_osmocom/main.conf ~/my_trial_dir/ -s osmo_trx
28./osmo-gsm-tester.py -c doc/examples/2g_osmocom/main.conf ~/my_trial_dir/ -s sms_tests:dyn_ts+eu_band+bts_sysmo
29./osmo-gsm-tester.py -c sysmocom/main.conf ~/my_trial_dir/ -s sms_tests/mo_mt_sms:bts_trx
Neels Hofmeyr3531a192017-03-28 14:30:28 +020030
Pau Espin Pedrol0bd048c2020-05-07 19:32:21 +020031(The names for test suites and scenarios used in these examples must be defined
32by the osmo-gsm-tester configuration.)
Neels Hofmeyr3531a192017-03-28 14:30:28 +020033
Pau Espin Pedrol0bd048c2020-05-07 19:32:21 +020034A trial package contains binaries (usually built by a jenkins job) of software
35to be run by Osmo-Gsm-Tester, like core network programs as well as binaries for
36the various BTS models on a 2G network.
Neels Hofmeyr3531a192017-03-28 14:30:28 +020037
38A test suite defines specific actions to be taken and verifies their outcome.
39Such a test suite may leave certain aspects of a setup undefined, e.g. it may
40be BTS model agnostic or does not care which voice codecs are chosen.
41
42A test scenario completes the picture in that it defines which specific choices
43shall be made to run a test suite. Any one test suite may thus run on any
44number of different scenarios, e.g. to test various voice codecs.
45
46Test scenarios may be combined. For example, one scenario may define a timeslot
47configuration to use, while another scenario may define the voice codec
48configuration.
49
50There may still be aspects that are neither required by a test suite nor
51strictly defined by a scenario, which will be resolved automatically, e.g. by
52choosing the first available item that matches the other constraints.
53
Pau Espin Pedrol0bd048c2020-05-07 19:32:21 +020054A test run thus needs to define:
55* A trial package containing built binaries
56* A set of test suites, each with its combinations of scenarios
Pau Espin Pedrol6c6c0e82020-05-11 18:30:58 +020057* A main configuration file specifying paths to other files containing sets of
58 resources, default configurations and paths on where to find suites,
59 scenarios, etc.
Neels Hofmeyr3531a192017-03-28 14:30:28 +020060
Pau Espin Pedrol0bd048c2020-05-07 19:32:21 +020061If no combination of suites and scenarios is provided, the default list of
62suites will be run as defined in the osmo-gsm-tester configuration.
Neels Hofmeyr3531a192017-03-28 14:30:28 +020063
64The scenarios and suites run for a given trial will be recorded in a trial
Pau Espin Pedrol0bd048c2020-05-07 19:32:21 +020065package's directory: Upon launch, a '$trial_dir/run.<date>' directory will be
Neels Hofmeyr3531a192017-03-28 14:30:28 +020066created, 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 Pedrol0dd4c0b2020-05-06 14:04:18 +020076from osmo_gsm_tester.core import config
Pau Espin Pedrolea8c3d42020-05-04 12:05:05 +020077from 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')
Pau Espin Pedrol6c6c0e82020-05-11 18:30:58 +0200105 parser.add_argument('-c', '--conf-path', dest='conf_path',
106 help='''Specify main configuration file path''')
Pau Espin Pedrole972c9c2020-05-12 15:06:55 +0200107 parser.add_argument('trial_dir', nargs='?', default=None,
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200108 help='Directory containing binaries to test')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200109 parser.add_argument('-s', '--suite-scenario', dest='suite_scenario', action='append',
Neels Hofmeyrcf0304f2017-05-06 22:07:39 +0200110 help='''A suite-scenarios combination
111like suite:scenario+scenario''')
Neels Hofmeyr1e01a682020-12-06 17:05:15 +0100112 parser.add_argument('-S', '--suites-file', dest='suites_file', action='append',
113 default=[],
114 help='''Read suites to run from a yml listing,
115like default-suites.conf. The path is relative to
116--conf-path.''')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200117 parser.add_argument('-t', '--test', dest='test', action='append',
Neels Hofmeyrcf0304f2017-05-06 22:07:39 +0200118 help='''Run only tests matching this name.
119Any test name that contains the given string is run.
120To get an exact match, prepend a "=" like
121"-t =my_exact_name". The ".py" suffix is always
122optional.''')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200123 parser.add_argument('-l', '--log-level', dest='log_level', choices=log.LEVEL_STRS.keys(),
124 default=None,
125 help='Set logging level for all categories (on stdout)')
126 parser.add_argument('-T', '--traceback', dest='trace', action='store_true',
Neels Hofmeyr42bebfa2017-06-06 19:41:42 +0200127 help='Enable stdout logging of tracebacks')
128 parser.add_argument('-R', '--source', dest='source', action='store_true',
129 help='Enable stdout logging of source file')
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200130 args = parser.parse_args()
131
132 if args.version:
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200133 print(__version__)
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200134 exit(0)
135
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200136 print('combinations:', repr(args.suite_scenario))
Pau Espin Pedrole972c9c2020-05-12 15:06:55 +0200137 print('trial:', repr(args.trial_dir))
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)
Pau Espin Pedrol6c6c0e82020-05-11 18:30:58 +0200149 if args.conf_path:
150 config.override_conf = args.conf_path
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200151
Pau Espin Pedrole972c9c2020-05-12 15:06:55 +0200152 if args.trial_dir is not None:
153 trial_dir = args.trial_dir
154 else:
155 trial_dir = config.get_main_config_value(config.CFG_TRIAL_DIR)
156
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200157 combination_strs = list(args.suite_scenario or [])
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200158
Neels Hofmeyr1e01a682020-12-06 17:05:15 +0100159 for suites_file in args.suites_file:
160 suites_file = config.main_config_path_to_abspath(suites_file)
161 from_this_file = config.read(suites_file)
162 print(('Running suites from %r:\n ' % suites_file) + ('\n '.join(from_this_file)))
163 combination_strs.extend(from_this_file)
164
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200165 if not combination_strs:
Pau Espin Pedrol6c6c0e82020-05-11 18:30:58 +0200166 combination_strs = config.read_config_file(config.CFG_DEFAULT_SUITES_CONF, if_missing_return=[])
Neels Hofmeyrd46ea132017-04-08 15:56:31 +0200167
168 if combination_strs:
169 print('Running default suites:\n ' + ('\n '.join(combination_strs)))
Your Name3c6673a2017-04-08 18:52:39 +0200170 else:
Pau Espin Pedrol6c6c0e82020-05-11 18:30:58 +0200171 print('Failed to load default suites (%r)' % config.get_main_config_value(config.DEFAULT_SUITES_CONF, fail_if_missing=False))
Your Name3c6673a2017-04-08 18:52:39 +0200172
Neels Hofmeyrd46ea132017-04-08 15:56:31 +0200173
174 if not combination_strs:
Pau Espin Pedrol0bd048c2020-05-07 19:32:21 +0200175 raise RuntimeError('Need at least one suite:scenario to run')
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200176
Pau Espin Pedrolea8c3d42020-05-04 12:05:05 +0200177 # Generate supported schemas dynamically from objects:
178 generate_schemas()
179
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200180 # make sure all suite:scenarios exist
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200181 suite_scenarios = []
182 for combination_str in combination_strs:
183 suite_scenarios.append(suite.load_suite_scenario_str(combination_str))
184
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200185 # pick tests and make sure they exist
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200186 test_names = []
187 for test_name in (args.test or []):
188 found = False
Neels Hofmeyrf9de78f2017-05-06 16:04:37 +0200189 if test_name.startswith('=') and not test_name.endswith('.py'):
190 test_name = test_name + '.py'
Neels Hofmeyr930ac952017-05-06 15:49:53 +0200191 for suite_scenario_str, suite_def, scenarios in suite_scenarios:
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200192 for def_test_name in suite_def.test_basenames:
Neels Hofmeyrf9de78f2017-05-06 16:04:37 +0200193 if test_name.startswith('='):
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200194 match = test_name[1:] == def_test_name
Neels Hofmeyrf9de78f2017-05-06 16:04:37 +0200195 else:
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200196 match = test_name in def_test_name
Neels Hofmeyrf9de78f2017-05-06 16:04:37 +0200197 if match:
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200198 found = True
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200199 test_names.append(def_test_name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200200 if not found:
201 raise RuntimeError('No test found for %r' % test_name)
202 if test_names:
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200203 test_names = sorted(set(test_names))
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200204 print(repr(test_names))
205
Pau Espin Pedrole972c9c2020-05-12 15:06:55 +0200206 with trial.Trial(trial_dir) as current_trial:
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200207 current_trial.verify()
208 for suite_scenario_str, suite_def, scenarios in suite_scenarios:
209 current_trial.add_suite_run(suite_scenario_str, suite_def, scenarios)
210 current_trial.run_suites(test_names)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200211
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200212 if current_trial.status != trial.Trial.PASS:
213 return 1
214 return 0
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200215
Neels Hofmeyr21235412017-05-14 03:00:21 +0200216if __name__ == '__main__':
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200217 rc = 2
Neels Hofmeyr21235412017-05-14 03:00:21 +0200218 try:
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200219 rc = main()
Neels Hofmeyr21235412017-05-14 03:00:21 +0200220 except:
221 # Tell the log to show the exception, then terminate the program with the exception anyway.
222 # Since exceptions within test runs should be caught and evaluated, this is basically about
223 # exceptions during command line parsing and such, so it's appropriate to abort immediately.
224 log.log_exn()
225 raise
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200226 exit(rc)
Neels Hofmeyr21235412017-05-14 03:00:21 +0200227
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200228# vim: expandtab tabstop=4 shiftwidth=4