Pau Espin Pedrol | 4e6b507 | 2020-05-11 15:12:07 +0200 | [diff] [blame] | 1 | # osmo_gsm_tester: Suite scenario |
| 2 | # |
| 3 | # Copyright (C) 2016-2020 by sysmocom - s.f.m.c. GmbH |
| 4 | # |
| 5 | # Author: Neels Hofmeyr <neels@hofmeyr.de> |
| 6 | # Author: Pau Espin Pedrol <pespin@sysmocom.de> |
| 7 | # |
| 8 | # This program is free software: you can redistribute it and/or modify |
| 9 | # it under the terms of the GNU General Public License as |
| 10 | # published by the Free Software Foundation, either version 3 of the |
| 11 | # License, or (at your option) any later version. |
| 12 | # |
| 13 | # This program is distributed in the hope that it will be useful, |
| 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | # GNU General Public License for more details. |
| 17 | # |
| 18 | # You should have received a copy of the GNU General Public License |
| 19 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 20 | |
| 21 | import os |
| 22 | |
| 23 | from . import log |
| 24 | from . import template |
| 25 | from . import config |
| 26 | |
| 27 | class Scenario(log.Origin, dict): |
| 28 | def __init__(self, name, path, param_list=[]): |
| 29 | super().__init__(log.C_TST, name) |
| 30 | self.path = path |
| 31 | self.param_list = param_list |
| 32 | |
| 33 | @classmethod |
| 34 | def count_cont_char_backward(cls, str, before_pos, c): |
| 35 | n = 0 |
| 36 | i = before_pos - 1 |
| 37 | while i >= 0: |
| 38 | if str[i] != c: |
| 39 | break |
| 40 | n += 1 |
| 41 | i -= 1 |
| 42 | return n |
| 43 | |
| 44 | @classmethod |
| 45 | def split_scenario_parameters(cls, str): |
| 46 | cur_pos = 0 |
| 47 | param_li = [] |
| 48 | saved = '' |
| 49 | # Split into a list, but we want to escape '\,' to avoid splitting parameters containing commas. |
| 50 | while True: |
| 51 | prev_pos = cur_pos |
| 52 | cur_pos = str.find(',', prev_pos) |
| 53 | if cur_pos == -1: |
| 54 | param_li.append(str[prev_pos:]) |
| 55 | break |
| 56 | if cur_pos == 0: |
| 57 | param_li.append('') |
| 58 | elif cur_pos != 0 and str[cur_pos - 1] == '\\' and cls.count_cont_char_backward(str, cur_pos, '\\') % 2 == 1: |
| 59 | saved += str[prev_pos:cur_pos - 1] + ',' |
| 60 | else: |
| 61 | param_li.append(saved + str[prev_pos:cur_pos]) |
| 62 | saved = '' |
| 63 | cur_pos += 1 |
| 64 | i = 0 |
| 65 | # Also escape '\\' -> '\' |
| 66 | while i < len(param_li): |
| 67 | param_li[i] = param_li[i].replace('\\\\', '\\') |
| 68 | i += 1 |
| 69 | return param_li |
| 70 | |
| 71 | @classmethod |
| 72 | def from_param_list_str(cls, name, path, param_list_str): |
| 73 | param_list = cls.split_scenario_parameters(param_list_str) |
| 74 | return cls(name, path, param_list) |
| 75 | |
| 76 | def read_from_file(self, validation_schema): |
| 77 | with open(self.path, 'r') as f: |
| 78 | config_str = f.read() |
| 79 | if len(self.param_list) != 0: |
| 80 | param_dict = {} |
| 81 | i = 1 |
| 82 | for param in self.param_list: |
| 83 | param_dict['param' + str(i)] = param |
| 84 | i += 1 |
| 85 | self.dbg(param_dict=param_dict) |
| 86 | config_str = template.render_strbuf_inline(config_str, param_dict) |
| 87 | conf = config.fromstr(config_str, validation_schema) |
| 88 | self.update(conf) |
| 89 | |
| 90 | def get_scenario(name, validation_schema=None): |
Pau Espin Pedrol | 66ef945 | 2020-05-25 13:26:41 +0200 | [diff] [blame] | 91 | found = False |
| 92 | path = None |
| 93 | param_list = [] |
Pau Espin Pedrol | 4e6b507 | 2020-05-11 15:12:07 +0200 | [diff] [blame] | 94 | if not name.endswith('.conf'): |
| 95 | name = name + '.conf' |
| 96 | is_parametrized_file = '@' in name |
Pau Espin Pedrol | 4e6b507 | 2020-05-11 15:12:07 +0200 | [diff] [blame] | 97 | if not is_parametrized_file: |
Pau Espin Pedrol | 66ef945 | 2020-05-25 13:26:41 +0200 | [diff] [blame] | 98 | scenarios_dirs = config.get_scenarios_dirs() |
| 99 | for d in scenarios_dirs: |
| 100 | path = d.child(name) |
| 101 | if os.path.isfile(path): |
| 102 | found = True |
| 103 | break |
| 104 | if not found: |
| 105 | raise RuntimeError('No such scenario file %s in %r' % (name, scenarios_dirs)) |
Pau Espin Pedrol | 4e6b507 | 2020-05-11 15:12:07 +0200 | [diff] [blame] | 106 | sc = Scenario(name, path) |
| 107 | else: # parametrized scenario: |
| 108 | # Allow first matching complete matching names (eg: scenario@param1,param2.conf), |
| 109 | # this allows setting specific content in different files for specific values. |
Pau Espin Pedrol | 66ef945 | 2020-05-25 13:26:41 +0200 | [diff] [blame] | 110 | scenarios_dirs = config.get_scenarios_dirs() |
| 111 | for d in scenarios_dirs: |
| 112 | path = d.child(name) |
| 113 | if os.path.isfile(path): |
| 114 | found = True |
| 115 | break |
| 116 | if not found: |
Pau Espin Pedrol | 4e6b507 | 2020-05-11 15:12:07 +0200 | [diff] [blame] | 117 | # get "scenario@.conf" from "scenario@param1,param2.conf": |
Pau Espin Pedrol | 66ef945 | 2020-05-25 13:26:41 +0200 | [diff] [blame] | 118 | for d in scenarios_dirs: |
| 119 | prefix_name = name[:name.index("@")+1] + '.conf' |
| 120 | path = d.child(prefix_name) |
| 121 | if os.path.isfile(path): |
| 122 | found = True |
| 123 | break |
| 124 | if not found: |
| 125 | raise RuntimeError('No such scenario file %r (nor %s) in %r' % (name, prefix_name, scenarios_dirs)) |
Pau Espin Pedrol | 4e6b507 | 2020-05-11 15:12:07 +0200 | [diff] [blame] | 126 | # At this point, we have existing file path. Let's now scrap the parameter(s): |
| 127 | # get param1,param2 str from scenario@param1,param2.conf |
| 128 | param_list_str = name.split('@', 1)[1][:-len('.conf')] |
| 129 | sc = Scenario.from_param_list_str(name, path, param_list_str) |
| 130 | sc.read_from_file(validation_schema) |
| 131 | return sc |
| 132 | |
| 133 | # vim: expandtab tabstop=4 shiftwidth=4 |