review jenkins scripts

Change-Id: I420910bd2d30e96605ecf3acb779ce726c75d912
diff --git a/src/osmo-gsm-tester.py b/src/osmo-gsm-tester.py
new file mode 100755
index 0000000..80fec4f
--- /dev/null
+++ b/src/osmo-gsm-tester.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+
+# osmo_gsm_tester: invoke a single test run
+#
+# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
+#
+# Author: Neels Hofmeyr <neels@hofmeyr.de>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+'''osmo_gsm_tester: invoke a single test run.
+
+Examples:
+
+./run_once.py ~/my_trial_package/ -s osmo_trx
+./run_once.py ~/my_trial_package/ -c sms_tests:dyn_ts+eu_band+bts_sysmo
+./run_once.py ~/my_trial_package/ -c sms_tests/mo_mt_sms:bts_trx
+
+(The names for test suite, scenario and series names used in these examples
+must be defined by the osmo-gsm-tester configuration.)
+
+A trial package contains binaries (usually built by a jenkins job) of GSM
+software, including the core network programs as well as binaries for the
+various BTS models.
+
+A test suite defines specific actions to be taken and verifies their outcome.
+Such a test suite may leave certain aspects of a setup undefined, e.g. it may
+be BTS model agnostic or does not care which voice codecs are chosen.
+
+A test scenario completes the picture in that it defines which specific choices
+shall be made to run a test suite. Any one test suite may thus run on any
+number of different scenarios, e.g. to test various voice codecs.
+
+Test scenarios may be combined. For example, one scenario may define a timeslot
+configuration to use, while another scenario may define the voice codec
+configuration.
+
+There may still be aspects that are neither required by a test suite nor
+strictly defined by a scenario, which will be resolved automatically, e.g. by
+choosing the first available item that matches the other constraints.
+
+A test run thus needs to define: a trial package containing built binaries, a
+combination of scenarios to run a suite in, and a test suite to launch in the
+given scenario with the given binaries.
+
+The osmo-gsm-tester configuration may define one or more series as a number of
+suite:scenario combinations. So instead of a specific suite:scenario
+combination, the name of such a series can be passed.
+
+If neither a combination or series is specified, the default series will be run
+as defined in the osmo-gsm-tester configuration.
+
+The scenarios and suites run for a given trial will be recorded in a trial
+package's directory: Upon launch, a 'test_package/run.<date>' directory will be
+created, which will collect logs and reports.
+'''
+
+from osmo_gsm_tester import __version__
+from osmo_gsm_tester import trial, suite, log, config
+
+if __name__ == '__main__':
+    import argparse
+
+    parser = argparse.ArgumentParser(epilog=__doc__, formatter_class=argparse.RawTextHelpFormatter)
+    parser.add_argument('-V', '--version', action='store_true',
+            help='Show version')
+    parser.add_argument('trial_package', nargs='+',
+            help='Directory containing binaries to test')
+    parser.add_argument('-s', '--suite-scenario', dest='suite_scenario', action='append',
+            help='A suite-scenarios combination like suite:scenario+scenario')
+    parser.add_argument('-S', '--series', dest='series', action='append',
+            help='A series of suite-scenarios combinations as defined in the'
+                 ' osmo-gsm-tester configuration')
+    parser.add_argument('-t', '--test', dest='test', action='append',
+            help='Run only tests matching this name')
+    parser.add_argument('-l', '--log-level', dest='log_level', choices=log.LEVEL_STRS.keys(),
+            default=None,
+            help='Set logging level for all categories (on stdout)')
+    parser.add_argument('-T', '--traceback', dest='trace', action='store_true',
+            help='Enable logging of tracebacks')
+    args = parser.parse_args()
+
+    if args.version:
+        print(__version__)
+        exit(0)
+
+    print('combinations:', repr(args.suite_scenario))
+    print('series:', repr(args.series))
+    print('trials:', repr(args.trial_package))
+    print('tests:', repr(args.test))
+
+    if args.log_level:
+        log.set_all_levels(log.LEVEL_STRS.get(args.log_level))
+    log.style_change(origin_width=32)
+    if args.trace:
+        log.style_change(trace=True)
+
+    combination_strs = list(args.suite_scenario or [])
+    # for series in args.series:
+    #     combination_strs.extend(config.get_series(series))
+
+    if not combination_strs:
+        combination_strs = config.read_config_file(config.DEFAULT_SUITES_CONF, if_missing_return=[])
+
+        if combination_strs:
+            print('Running default suites:\n  ' + ('\n  '.join(combination_strs)))
+
+    if not combination_strs:
+        raise RuntimeError('Need at least one suite:scenario or series to run')
+
+    suite_scenarios = []
+    for combination_str in combination_strs:
+        suite_scenarios.append(suite.load_suite_scenario_str(combination_str))
+
+    test_names = []
+    for test_name in (args.test or []):
+        found = False
+        for suite_run in suite_runs:
+            for test in suite_run.definition.tests:
+                if test_name in test.name():
+                    found = True
+                    test_names.append(test.name())
+        if not found:
+            raise RuntimeError('No test found for %r' % test_name)
+    if test_names:
+        print(repr(test_names))
+
+    trials = []
+    for trial_package in args.trial_package:
+        t = trial.Trial(trial_package)
+        t.verify()
+        trials.append(t)
+
+    for current_trial in trials:
+        with current_trial:
+            for suite_def, scenarios in suite_scenarios:
+                suite_run = suite.SuiteRun(current_trial, suite_def, scenarios)
+                suite_run.run_tests(test_names)
+
+# vim: expandtab tabstop=4 shiftwidth=4