blob: 789e2913ba9adf8cc9cf4dc3b034f6cc9d22eaac [file] [log] [blame]
Neels Hofmeyr3531a192017-03-28 14:30:28 +02001# osmo_gsm_tester: context for individual test runs
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +02002#
3# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
4#
5# Author: Neels Hofmeyr <neels@hofmeyr.de>
6#
7# This program is free software: you can redistribute it and/or modify
Harald Welte27205342017-06-03 09:51:45 +02008# it under the terms of the GNU General Public License as
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +02009# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte27205342017-06-03 09:51:45 +020015# GNU General Public License for more details.
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020016#
Harald Welte27205342017-06-03 09:51:45 +020017# You should have received a copy of the GNU General Public License
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020018# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
Neels Hofmeyr3531a192017-03-28 14:30:28 +020020# These will be initialized before each test run.
21# A test script can thus establish its context by doing:
Pau Espin Pedroldfe38ad2017-11-09 13:57:39 +010022# from osmo_gsm_tester.testenv import *
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020023
24import sys
25
26from .core import process
27from .core import log as log_module
28from .core import process as process_module
29from .core import resource
30from .core.event_loop import MainLoop
31
Neels Hofmeyr3531a192017-03-28 14:30:28 +020032suite = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020033resources = None
34log = None
35dbg = None
36err = None
37wait = None
Pau Espin Pedrol927344b2017-05-22 16:38:49 +020038wait_no_raise = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020039sleep = None
40poll = None
41prompt = None
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020042Sms = None
Pau Espin Pedrol878b2c62018-05-18 15:57:06 +020043process = None
Pau Espin Pedrol40c7bc72020-05-05 13:41:42 +020044tenv = None
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020045
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020046class Timeout(Exception):
47 pass
48
49class TestEnv(log_module.Origin):
50 def __init__(self, suite_run, test):
51 super().__init__(log_module.C_TST, test.name())
52 self.suite_run = suite_run
53 self._test = test
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020054 self._processes = []
55 self.test_import_modules_to_clean_up = []
56 self.objects_to_clean_up = None
57 MainLoop.register_poll_func(self.poll)
58
Pau Espin Pedrola442cb82020-05-05 12:54:37 +020059 def test(self):
60 return self._test
61
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020062 def suite(self):
63 return self.suite_run
64
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020065 def remember_to_stop(self, process, respawn=False):
66 '''Ask suite to monitor and manage lifecycle of the Process object. If a
67 process managed by suite finishes before cleanup time, the current test
68 will be marked as FAIL and end immediatelly. If respwan=True, then suite
69 will respawn() the process instead.'''
70 self._processes.insert(0, (process, respawn))
71
72 def stop_processes(self):
73 if len(self._processes) == 0:
74 return
75 strategy = process_module.ParallelTerminationStrategy()
76 while self._processes:
77 proc, _ = self._processes.pop()
78 strategy.add_process(proc)
79 strategy.terminate_all()
80
81 def stop_process(self, process):
82 'Remove process from monitored list and stop it'
83 for proc_respawn in self._processes:
84 proc, respawn = proc_respawn
85 if proc == process:
86 self._processes.remove(proc_respawn)
87 proc.terminate()
88
89 def register_for_cleanup(self, *obj):
90 assert all([hasattr(o, 'cleanup') for o in obj])
91 self.objects_to_clean_up = self.objects_to_clean_up or []
92 self.objects_to_clean_up.extend(obj)
93
94 def objects_cleanup(self):
95 while self.objects_to_clean_up:
96 obj = self.objects_to_clean_up.pop()
97 try:
98 obj.cleanup()
99 except Exception:
100 log_module.log_exn()
101
102 def test_import_modules_register_for_cleanup(self, mod):
103 '''
104 Tests are required to call this API for any module loaded from its own
105 lib subdir, because they are loaded in the global namespace. Otherwise
106 later tests importing modules with the same name will re-use an already
107 loaded module.
108 '''
109 if mod not in self.test_import_modules_to_clean_up:
110 self.dbg('registering module %r for cleanup' % mod)
111 self.test_import_modules_to_clean_up.append(mod)
112
113 def test_import_modules_cleanup(self):
114 while self.test_import_modules_to_clean_up:
115 mod = self.test_import_modules_to_clean_up.pop()
116 try:
117 self.dbg('Cleaning up module %r' % mod)
118 del sys.modules[mod.__name__]
119 del mod
120 except Exception:
121 log_module.log_exn()
122
123 def poll(self):
124 for proc, respawn in self._processes:
125 if proc.terminated():
126 if respawn == True:
127 proc.respawn()
128 else:
129 proc.log_stdout_tail()
130 proc.log_stderr_tail()
131 log_module.ctx(proc)
132 raise log_module.Error('Process ended prematurely: %s' % proc.name())
133
134 def stop(self):
135 # if sys.exit() called from signal handler (e.g. SIGINT), SystemExit
136 # base exception is raised. Make sure to stop processes in this
137 # finally section. Resources are automatically freed with 'atexit'.
138 self.stop_processes()
139 self.objects_cleanup()
140 self.suite_run.reserved_resources.put_all()
141 MainLoop.unregister_poll_func(self.poll)
142 self.test_import_modules_cleanup()
143
Pau Espin Pedrol30637302020-05-06 21:11:02 +0200144 def config_suite_specific(self):
145 return self.suite_run.config_suite_specific()
146
147 def config_test_specific(self):
148 return self.suite_run.config_suite_specific().get(self._test.module_name(), {})
149
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200150 def prompt(self, *msgs, **msg_details):
151 'ask for user interaction. Do not use in tests that should run automatically!'
152 if msg_details:
153 msgs = list(msgs)
154 msgs.append('{%s}' %
155 (', '.join(['%s=%r' % (k,v)
156 for k,v in sorted(msg_details.items())])))
157 msg = ' '.join(msgs) or 'Hit Enter to continue'
158 self.log('prompt:', msg)
159 sys.__stdout__.write('\n\n--- PROMPT ---\n')
160 sys.__stdout__.write(msg)
161 sys.__stdout__.write('\n')
162 sys.__stdout__.flush()
163 entered = util.input_polling('> ', MainLoop.poll)
164 self.log('prompt entered:', repr(entered))
165 return entered
166
167 def get_reserved_resource(self, resource_class_str, specifics=None):
168 return self.suite_run.get_reserved_resource(resource_class_str, specifics)
169
170 def ip_address(self, specifics=None):
171 return self.get_reserved_resource(resource.R_IP_ADDRESS, specifics)
172
173 def nitb(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200174 from .obj.nitb_osmo import OsmoNitb
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200175 if ip_address is None:
176 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200177 return OsmoNitb(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200178
179 def hlr(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200180 from .obj.hlr_osmo import OsmoHlr
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200181 if ip_address is None:
182 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200183 return OsmoHlr(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200184
185 def ggsn(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200186 from .obj.ggsn_osmo import OsmoGgsn
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200187 if ip_address is None:
188 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200189 return OsmoGgsn(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200190
191 def sgsn(self, hlr, ggsn, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200192 from .obj import sgsn_osmo
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200193 if ip_address is None:
194 ip_address = self.ip_address()
195 return sgsn_osmo.OsmoSgsn(self, hlr, ggsn, ip_address)
196
197 def mgcpgw(self, ip_address=None, bts_ip=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200198 from .obj.mgcpgw_osmo import OsmoMgcpgw
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200199 if ip_address is None:
200 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200201 return OsmoMgcpgw(self, ip_address, bts_ip)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200202
203 def mgw(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200204 from .obj.mgw_osmo import OsmoMgw
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200205 if ip_address is None:
206 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200207 return OsmoMgw(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200208
209 def msc(self, hlr, mgcpgw, stp, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200210 from .obj import msc_osmo
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200211 if ip_address is None:
212 ip_address = self.ip_address()
213 return msc_osmo.OsmoMsc(self, hlr, mgcpgw, stp, ip_address)
214
215 def bsc(self, msc, mgw, stp, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200216 from .obj.bsc_osmo import OsmoBsc
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200217 if ip_address is None:
218 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200219 return OsmoBsc(self, msc, mgw, stp, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200220
221 def stp(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200222 from .obj.stp_osmo import OsmoStp
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200223 if ip_address is None:
224 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200225 return OsmoStp(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200226
227 def ms_driver(self):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200228 from .obj.ms_driver import MsDriver
229 ms = MsDriver(self)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200230 self.register_for_cleanup(ms)
231 return ms
232
233 def bts(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200234 from .obj.bts import Bts
235 bts_obj = Bts.get_instance_by_type(self, self.get_reserved_resource(resource.R_BTS, specifics=specifics))
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200236 bts_obj.set_lac(self.lac())
237 bts_obj.set_rac(self.rac())
238 bts_obj.set_cellid(self.cellid())
239 bts_obj.set_bvci(self.bvci())
240 self.register_for_cleanup(bts_obj)
241 return bts_obj
242
243 def modem(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200244 from .obj.ms import MS
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200245 conf = self.get_reserved_resource(resource.R_MODEM, specifics=specifics)
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200246 ms_obj = MS.get_instance_by_type(self, conf)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200247 self.register_for_cleanup(ms_obj)
248 return ms_obj
249
250 def modems(self, count):
251 l = []
252 for i in range(count):
253 l.append(self.modem())
254 return l
255
256 def all_resources(self, resource_func):
257 """Returns all yielded resource."""
258 l = []
259 while True:
260 try:
261 l.append(resource_func())
262 except resource.NoResourceExn:
263 return l
264
265 def esme(self):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200266 from .obj.esme import Esme
267 esme_obj = Esme(self.msisdn())
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200268 self.register_for_cleanup(esme_obj)
269 return esme_obj
270
271 def run_node(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200272 from .obj.run_node import RunNode
273 return RunNode.from_conf(self.get_reserved_resource(resource.R_RUN_NODE, specifics=specifics))
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200274
275 def enb(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200276 from .obj.enb import eNodeB
277 enb_obj = eNodeB.get_instance_by_type(self, self.get_reserved_resource(resource.R_ENB, specifics=specifics))
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200278 self.register_for_cleanup(enb_obj)
279 return enb_obj
280
281 def epc(self, run_node=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200282 from .obj.epc import EPC
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200283 if run_node is None:
284 run_node = self.run_node()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200285 epc_obj = EPC.get_instance_by_type(self, run_node)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200286 self.register_for_cleanup(epc_obj)
287 return epc_obj
288
289 def osmocon(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200290 from .obj.osmocon import Osmocon
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200291 conf = self.get_reserved_resource(resource.R_OSMOCON, specifics=specifics)
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200292 osmocon_obj = Osmocon(self, conf=conf)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200293 self.register_for_cleanup(osmocon_obj)
294 return osmocon_obj
295
296 def iperf3srv(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200297 from .obj.iperf3 import IPerf3Server
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200298 if ip_address is None:
299 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200300 iperf3srv_obj = IPerf3Server(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200301 return iperf3srv_obj
302
303 def msisdn(self):
304 msisdn = self.suite_run.resource_pool().next_msisdn(self)
305 self.log('using MSISDN', msisdn)
306 return msisdn
307
308 def lac(self):
309 lac = self.suite_run.resource_pool().next_lac(self)
310 self.log('using LAC', lac)
311 return lac
312
313 def rac(self):
314 rac = self.suite_run.resource_pool().next_rac(self)
315 self.log('using RAC', rac)
316 return rac
317
318 def cellid(self):
319 cellid = self.suite_run.resource_pool().next_cellid(self)
320 self.log('using CellId', cellid)
321 return cellid
322
323 def bvci(self):
324 bvci = self.suite_run.resource_pool().next_bvci(self)
325 self.log('using BVCI', bvci)
326 return bvci
327
328
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200329def setup(suite_run, _test):
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200330 from .core.event_loop import MainLoop
331 from .obj.sms import Sms as Sms_class
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200332
Pau Espin Pedrol40c7bc72020-05-05 13:41:42 +0200333 global test, resources, log, dbg, err, wait, wait_no_raise, sleep, poll, prompt, Sms, process, tenv
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200334
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200335 test = _test
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200336 resources = suite_run.reserved_resources # TODO: remove this global, only used in selftest
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200337 log = test.log
338 dbg = test.dbg
339 err = test.err
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200340 wait = lambda *args, **kwargs: MainLoop.wait(suite_run, *args, **kwargs)
341 wait_no_raise = lambda *args, **kwargs: MainLoop.wait_no_raise(suite_run, *args, **kwargs)
342 sleep = lambda *args, **kwargs: MainLoop.sleep(suite_run, *args, **kwargs)
343 poll = MainLoop.poll
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200344 Sms = Sms_class
Pau Espin Pedrol878b2c62018-05-18 15:57:06 +0200345 process = process_module
Pau Espin Pedrol40c7bc72020-05-05 13:41:42 +0200346 tenv = TestEnv(suite_run, _test)
347 prompt = tenv.prompt
348 return tenv
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200349
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200350# vim: expandtab tabstop=4 shiftwidth=4