diff --git a/selftest/suite_test/suite_test.py b/selftest/suite_test/suite_test.py index fc5f9df9..a096027e 100755 --- a/selftest/suite_test/suite_test.py +++ b/selftest/suite_test/suite_test.py @@ -3,7 +3,11 @@ import os import sys import _prep import shutil -from osmo_gsm_tester.core import log, config, util, report +from osmo_gsm_tester.core import log +from osmo_gsm_tester.core import config +from osmo_gsm_tester.core import util +from osmo_gsm_tester.core import report +from osmo_gsm_tester.core import scenario from osmo_gsm_tester.core import suite from osmo_gsm_tester.core.schema import generate_schemas, get_all_schema @@ -66,26 +70,26 @@ print(output) print('- test with half empty scenario') trial = FakeTrial() -scenario = config.Scenario('foo', 'bar') -scenario['resources'] = { 'bts': [{'type': 'osmo-bts-trx'}] } -s = suite.SuiteRun(trial, 'test_suite', s_def, [scenario]) +sc = scenario.Scenario('foo', 'bar') +sc['resources'] = { 'bts': [{'type': 'osmo-bts-trx'}] } +s = suite.SuiteRun(trial, 'test_suite', s_def, [sc]) results = s.run_tests('hello_world.py') print(report.suite_to_text(s)) print('- test with scenario') trial = FakeTrial() -scenario = config.Scenario('foo', 'bar') -scenario['resources'] = { 'bts': [{ 'times': '2', 'type': 'osmo-bts-trx', 'trx_list': [{'nominal_power': '10'}, {'nominal_power': '12'}]}, {'type': 'sysmo'}] } -s = suite.SuiteRun(trial, 'test_suite', s_def, [scenario]) +sc = scenario.Scenario('foo', 'bar') +sc['resources'] = { 'bts': [{ 'times': '2', 'type': 'osmo-bts-trx', 'trx_list': [{'nominal_power': '10'}, {'nominal_power': '12'}]}, {'type': 'sysmo'}] } +s = suite.SuiteRun(trial, 'test_suite', s_def, [sc]) results = s.run_tests('hello_world.py') print(report.suite_to_text(s)) print('- test with scenario and modifiers') trial = FakeTrial() -scenario = config.Scenario('foo', 'bar') -scenario['resources'] = { 'bts': [{ 'times': '2', 'type': 'osmo-bts-trx', 'trx_list': [{'nominal_power': '10'}, {'nominal_power': '12'}]}, {'type': 'sysmo'}] } -scenario['modifiers'] = { 'bts': [{ 'times': '2', 'trx_list': [{'nominal_power': '20'}, {'nominal_power': '20'}]}, {'type': 'sysmo'}] } -s = suite.SuiteRun(trial, 'test_suite', s_def, [scenario]) +sc = scenario.Scenario('foo', 'bar') +sc['resources'] = { 'bts': [{ 'times': '2', 'type': 'osmo-bts-trx', 'trx_list': [{'nominal_power': '10'}, {'nominal_power': '12'}]}, {'type': 'sysmo'}] } +sc['modifiers'] = { 'bts': [{ 'times': '2', 'trx_list': [{'nominal_power': '20'}, {'nominal_power': '20'}]}, {'type': 'sysmo'}] } +s = suite.SuiteRun(trial, 'test_suite', s_def, [sc]) s.reserve_resources() print(repr(s.reserved_resources)) results = s.run_tests('hello_world.py') @@ -93,9 +97,9 @@ print(report.suite_to_text(s)) print('- test with suite-specific config') trial = FakeTrial() -scenario = config.Scenario('foo', 'bar') -scenario['config'] = {'suite': {s.name(): { 'some_suite_global_param': 'heyho', 'test_suite_params': {'one_bool_parameter': 'true', 'second_list_parameter': ['23', '45']}}}} -s = suite.SuiteRun(trial, 'test_suite', s_def, [scenario]) +sc = scenario.Scenario('foo', 'bar') +sc['config'] = {'suite': {s.name(): { 'some_suite_global_param': 'heyho', 'test_suite_params': {'one_bool_parameter': 'true', 'second_list_parameter': ['23', '45']}}}} +s = suite.SuiteRun(trial, 'test_suite', s_def, [sc]) s.reserve_resources() print(repr(s.reserved_resources)) results = s.run_tests('test_suite_params.py') diff --git a/src/osmo_gsm_tester/core/config.py b/src/osmo_gsm_tester/core/config.py index 98d422f5..88e522d7 100644 --- a/src/osmo_gsm_tester/core/config.py +++ b/src/osmo_gsm_tester/core/config.py @@ -165,6 +165,13 @@ def write(path, config): with open(path, 'w') as f: f.write(tostr(config)) +def fromstr(config_str, validation_schema=None): + config = yaml.safe_load(config_str) + config = _standardize(config) + if validation_schema is not None: + schema.validate(config, validation_schema) + return config + def tostr(config): return _tostr(_standardize(config)) @@ -188,99 +195,6 @@ def get_defaults(for_kind): defaults = read_config_file(DEFAULTS_CONF, if_missing_return={}) return defaults.get(for_kind, {}) -class Scenario(log.Origin, dict): - def __init__(self, name, path, param_list=[]): - super().__init__(log.C_TST, name) - self.path = path - self.param_list = param_list - - @classmethod - def count_cont_char_backward(cls, str, before_pos, c): - n = 0 - i = before_pos - 1 - while i >= 0: - if str[i] != c: - break - n += 1 - i -= 1 - return n - - @classmethod - def split_scenario_parameters(cls, str): - cur_pos = 0 - param_li = [] - saved = '' - # Split into a list, but we want to escape '\,' to avoid splitting parameters containing commas. - while True: - prev_pos = cur_pos - cur_pos = str.find(',', prev_pos) - if cur_pos == -1: - param_li.append(str[prev_pos:]) - break - if cur_pos == 0: - param_li.append('') - elif cur_pos != 0 and str[cur_pos - 1] == '\\' and cls.count_cont_char_backward(str, cur_pos, '\\') % 2 == 1: - saved += str[prev_pos:cur_pos - 1] + ',' - else: - param_li.append(saved + str[prev_pos:cur_pos]) - saved = '' - cur_pos += 1 - i = 0 - # Also escape '\\' -> '\' - while i < len(param_li): - param_li[i] = param_li[i].replace('\\\\', '\\') - i += 1 - return param_li - - @classmethod - def from_param_list_str(cls, name, path, param_list_str): - param_list = cls.split_scenario_parameters(param_list_str) - return cls(name, path, param_list) - - def read_from_file(self, validation_schema): - with open(self.path, 'r') as f: - config_str = f.read() - if len(self.param_list) != 0: - param_dict = {} - i = 1 - for param in self.param_list: - param_dict['param' + str(i)] = param - i += 1 - self.dbg(param_dict=param_dict) - config_str = template.render_strbuf_inline(config_str, param_dict) - config = yaml.safe_load(config_str) - config = _standardize(config) - if validation_schema: - schema.validate(config, validation_schema) - self.update(config) - -def get_scenario(name, validation_schema=None): - scenarios_dir = get_scenarios_dir() - if not name.endswith('.conf'): - name = name + '.conf' - is_parametrized_file = '@' in name - param_list = [] - path = scenarios_dir.child(name) - if not is_parametrized_file: - if not os.path.isfile(path): - raise RuntimeError('No such scenario file: %r' % path) - sc = Scenario(name, path) - else: # parametrized scenario: - # Allow first matching complete matching names (eg: scenario@param1,param2.conf), - # this allows setting specific content in different files for specific values. - if not os.path.isfile(path): - # get "scenario@.conf" from "scenario@param1,param2.conf": - prefix_name = name[:name.index("@")+1] + '.conf' - path = scenarios_dir.child(prefix_name) - if not os.path.isfile(path): - raise RuntimeError('No such scenario file: %r (nor %s)' % (path, name)) - # At this point, we have existing file path. Let's now scrap the parameter(s): - # get param1,param2 str from scenario@param1,param2.conf - param_list_str = name.split('@', 1)[1][:-len('.conf')] - sc = Scenario.from_param_list_str(name, path, param_list_str) - sc.read_from_file(validation_schema) - return sc - def overlay(dest, src): if is_dict(dest): if not is_dict(src): diff --git a/src/osmo_gsm_tester/core/scenario.py b/src/osmo_gsm_tester/core/scenario.py new file mode 100644 index 00000000..efa045b1 --- /dev/null +++ b/src/osmo_gsm_tester/core/scenario.py @@ -0,0 +1,117 @@ +# osmo_gsm_tester: Suite scenario +# +# Copyright (C) 2016-2020 by sysmocom - s.f.m.c. GmbH +# +# Author: Neels Hofmeyr +# Author: Pau Espin Pedrol +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from . import log +from . import template +from . import config + +class Scenario(log.Origin, dict): + def __init__(self, name, path, param_list=[]): + super().__init__(log.C_TST, name) + self.path = path + self.param_list = param_list + + @classmethod + def count_cont_char_backward(cls, str, before_pos, c): + n = 0 + i = before_pos - 1 + while i >= 0: + if str[i] != c: + break + n += 1 + i -= 1 + return n + + @classmethod + def split_scenario_parameters(cls, str): + cur_pos = 0 + param_li = [] + saved = '' + # Split into a list, but we want to escape '\,' to avoid splitting parameters containing commas. + while True: + prev_pos = cur_pos + cur_pos = str.find(',', prev_pos) + if cur_pos == -1: + param_li.append(str[prev_pos:]) + break + if cur_pos == 0: + param_li.append('') + elif cur_pos != 0 and str[cur_pos - 1] == '\\' and cls.count_cont_char_backward(str, cur_pos, '\\') % 2 == 1: + saved += str[prev_pos:cur_pos - 1] + ',' + else: + param_li.append(saved + str[prev_pos:cur_pos]) + saved = '' + cur_pos += 1 + i = 0 + # Also escape '\\' -> '\' + while i < len(param_li): + param_li[i] = param_li[i].replace('\\\\', '\\') + i += 1 + return param_li + + @classmethod + def from_param_list_str(cls, name, path, param_list_str): + param_list = cls.split_scenario_parameters(param_list_str) + return cls(name, path, param_list) + + def read_from_file(self, validation_schema): + with open(self.path, 'r') as f: + config_str = f.read() + if len(self.param_list) != 0: + param_dict = {} + i = 1 + for param in self.param_list: + param_dict['param' + str(i)] = param + i += 1 + self.dbg(param_dict=param_dict) + config_str = template.render_strbuf_inline(config_str, param_dict) + conf = config.fromstr(config_str, validation_schema) + self.update(conf) + +def get_scenario(name, validation_schema=None): + scenarios_dir = config.get_scenarios_dir() + if not name.endswith('.conf'): + name = name + '.conf' + is_parametrized_file = '@' in name + param_list = [] + path = scenarios_dir.child(name) + if not is_parametrized_file: + if not os.path.isfile(path): + raise RuntimeError('No such scenario file: %r' % path) + sc = Scenario(name, path) + else: # parametrized scenario: + # Allow first matching complete matching names (eg: scenario@param1,param2.conf), + # this allows setting specific content in different files for specific values. + if not os.path.isfile(path): + # get "scenario@.conf" from "scenario@param1,param2.conf": + prefix_name = name[:name.index("@")+1] + '.conf' + path = scenarios_dir.child(prefix_name) + if not os.path.isfile(path): + raise RuntimeError('No such scenario file: %r (nor %s)' % (path, name)) + # At this point, we have existing file path. Let's now scrap the parameter(s): + # get param1,param2 str from scenario@param1,param2.conf + param_list_str = name.split('@', 1)[1][:-len('.conf')] + sc = Scenario.from_param_list_str(name, path, param_list_str) + sc.read_from_file(validation_schema) + return sc + +# vim: expandtab tabstop=4 shiftwidth=4 diff --git a/src/osmo_gsm_tester/core/suite.py b/src/osmo_gsm_tester/core/suite.py index 81aab3e2..a6eaca27 100644 --- a/src/osmo_gsm_tester/core/suite.py +++ b/src/osmo_gsm_tester/core/suite.py @@ -26,6 +26,7 @@ from . import log from . import util from . import schema from . import resource +from . import scenario from . import test class SuiteDefinition(log.Origin): @@ -111,12 +112,12 @@ class SuiteRun(log.Origin): if replicate_times: combination = config.replicate_times(combination) log.dbg(definition_conf=combination) - for scenario in self.scenarios: - log.ctx(combining_scenarios=conf_name, scenario=scenario.name()) - c = scenario.get(conf_name, {}) + for sc in self.scenarios: + log.ctx(combining_scenarios=conf_name, scenario=sc.name()) + c = sc.get(conf_name, {}) if replicate_times: c = config.replicate_times(c) - log.dbg(scenario=scenario.name(), conf=c) + log.dbg(scenario=sc.name(), conf=c) if c is None: continue schema.combine(combination, c) @@ -258,7 +259,7 @@ def parse_suite_scenario_str(suite_scenario_str): def load_suite_scenario_str(suite_scenario_str): suite_name, scenario_names = parse_suite_scenario_str(suite_scenario_str) suite = load(suite_name) - scenarios = [config.get_scenario(scenario_name, schema.get_all_schema()) for scenario_name in scenario_names] + scenarios = [scenario.get_scenario(scenario_name, schema.get_all_schema()) for scenario_name in scenario_names] return (suite_scenario_str, suite, scenarios) # vim: expandtab tabstop=4 shiftwidth=4