core implementation

code bomb implementing the bulk of the osmo-gsm-tester

Change-Id: I53610becbf643ed51b90cfd9debc6992fe211ec9
changes/43/2443/1
Neels Hofmeyr 6 years ago committed by Neels Hofmeyr
parent dae3d3c479
commit 3531a192ae

3
.gitignore vendored

@ -4,3 +4,6 @@ pid
version
_version.py
tags
set_pythonpath
test_work
state

@ -9,7 +9,7 @@ version:
./update_version.sh
check:
$(MAKE) -C test check
$(MAKE) -C selftest check
@echo "make check: success"
# vim: noexpandtab tabstop=8 shiftwidth=8

@ -22,5 +22,6 @@ import tempfile
import time
import traceback
import yaml
import pydbus
print('ok')
print('dependencies ok')

@ -18,9 +18,9 @@ osmo_gsm_tester_host=root@10.9.1.190
osmo_gsm_tester_dir="/var/tmp/osmo-gsm-tester"
tmp_dir="/var/tmp/prep-osmo-gsm-tester"
arch="x86_64"
archive_name="openbsc-$arch-build-$BUILD_NUMBER"
archive_name="openbsc.$arch-build-$BUILD_NUMBER"
archive="$archive_name.tgz"
manifest="manifest.txt"
manifest="checksums.md5"
test_report="test-report.xml"
test_timeout_sec=120
@ -78,7 +78,9 @@ rm -rf "$local_ts_base" || true
mkdir -p "$local_ts_dir"
# create archive of openbsc build
tar czf "$local_ts_dir/$archive" "$prefix_dirname"/*
cd "$prefix_dirname"
tar czf "../$local_ts_dir/$archive" *
cd ..
# move archived bts builds into test session directory
mv $WORKSPACE/osmo-bts-*.tgz "$local_ts_dir"
cd "$local_ts_dir"

@ -91,4 +91,4 @@ main "$octbts_repos"
# build the archive that is going to be copied to the tester and then to the BTS
rm -f $WORKSPACE/osmo-bts-octphy*.tgz
tar czf $WORKSPACE/osmo-bts-octphy-build-$BUILD_NUMBER.tgz inst-osmo-bts-octphy
tar czf $WORKSPACE/osmo-bts-octphy.build-$BUILD_NUMBER.tgz inst-osmo-bts-octphy

@ -60,9 +60,6 @@ for dep in $deps; do
done
# build the archive that is going to be copied to the tester and then to the BTS
tar_name="osmo-bts-sysmo-build-"
if ls "$base/$tar_name"* ; then
rm -f "$base/$tar_name"*
fi
rm -f "$base/osmo-bts-sysmo.*.tgz"
cd "$prefix_base_real"
tar cvzf "$base/$tar_name${BUILD_NUMBER}.tgz" *
tar cvzf "$base/osmo-bts-sysmo.build-${BUILD_NUMBER}.tgz" *

@ -56,6 +56,6 @@ for dep in $deps; do
done
# build the archive that is going to be copied to the tester
rm -f "$base/osmo-bts-trx*.tgz"
cd "$base"
rm -f osmo-bts-trx*.tgz
tar czf "osmo-bts-trx-build-${BUILD_NUMBER}.tgz" "$inst"
tar czf "osmo-bts-trx.build-${BUILD_NUMBER}.tgz" "$inst"

@ -89,4 +89,4 @@ LAUNCHING A TEST RUN
osmo-gsm-tester watches /var/tmp/osmo-gsm-tester for instructions to launch
test runs. A test run is triggered by a subdirectory containing binaries and a
manifest file, typically created by jenkins using the enclosed scripts.
checksums file, typically created by jenkins using the scripts in contrib/.

@ -1,9 +1,12 @@
.PHONY: check update
check:
check: set_pythonpath
./all_tests.py
update:
./all_tests.py -u
set_pythonpath:
echo "export PYTHONPATH=\"$(PWD)/../src\"" > set_pythonpath
# vim: noexpandtab tabstop=8 shiftwidth=8

@ -10,6 +10,7 @@ sys.path.append(src_dir)
from osmo_gsm_tester import log
log.targets = [ log.TestsTarget() ]
log.set_all_levels(log.L_DBG)
if '-v' in sys.argv:
log.style_change(trace=True)

@ -6,6 +6,7 @@ import subprocess
import time
import difflib
import argparse
import re
parser = argparse.ArgumentParser()
parser.add_argument('testdir_or_test', nargs='*',
@ -37,6 +38,21 @@ def udiff(expect, got, expect_path):
def verify_output(got, expect_file, update=False):
if os.path.isfile(expect_file):
ign_file = expect_file + '.ign'
if os.path.isfile(ign_file):
with open(ign_file, 'r') as f:
ign_rules = f.readlines()
for ign_rule in ign_rules:
if not ign_rule:
continue
if '\t' in ign_rule:
ign_rule, repl = ign_rule.split('\t')
repl = repl.strip()
else:
repl = '*'
ir = re.compile(ign_rule)
got = repl.join(ir.split(got))
if update:
with open(expect_file, 'w') as f:
f.write(got)
@ -44,6 +60,7 @@ def verify_output(got, expect_file, update=False):
with open(expect_file, 'r') as f:
expect = f.read()
if expect != got:
udiff(expect, got, expect_file)
sys.stderr.write('output mismatch: %r\n'
@ -93,12 +110,7 @@ for test in sorted(tests):
success = False
if not success:
sys.stderr.write('--- stdout ---\n')
sys.stderr.write(out)
sys.stderr.write('--- stderr ---\n')
sys.stderr.write(err)
sys.stderr.write('---\n')
sys.stderr.write('Test failed: %r\n\n' % os.path.basename(test))
sys.stderr.write('\nTest failed: %r\n\n' % os.path.basename(test))
errors.append(test)
if errors:

@ -0,0 +1,2 @@
state_dir: ./test_work/state_dir
suites_dir: ./suite_test

@ -1,37 +1,55 @@
# all hardware and interfaces available to this osmo-gsm-tester
nitb_iface:
- 10.42.42.1
- 10.42.42.2
- 10.42.42.3
- addr: 10.42.42.1
- addr: 10.42.42.2
- addr: 10.42.42.3
bts:
- label: sysmoBTS 1002
type: sysmo
unit_id: 1
addr: 10.42.42.114
trx:
- band: GSM-1800
band: GSM-1800
- label: octBTS 3000
type: oct
unit_id: 5
addr: 10.42.42.115
band: GSM-1800
trx:
- band: GSM-1800
hwaddr: 00:0c:90:32:b5:8a
- hwaddr: 00:0c:90:32:b5:8a
- label: nanoBTS 1900
type: nanobts
unit_id: 1902
addr: 10.42.42.190
band: GSM-1900
trx:
- band: GSM-1900
hwaddr: 00:02:95:00:41:b3
- hwaddr: 00:02:95:00:41:b3
arfcn:
- GSM-1800: [512, 514, 516, 518, 520]
- GSM-1900: [540, 542, 544, 546, 548]
- arfcn: 512
band: GSM-1800
- arfcn: 514
band: GSM-1800
- arfcn: 516
band: GSM-1800
- arfcn: 518
band: GSM-1800
- arfcn: 520
band: GSM-1800
- arfcn: 540
band: GSM-1900
- arfcn: 542
band: GSM-1900
- arfcn: 544
band: GSM-1900
- arfcn: 546
band: GSM-1900
- arfcn: 548
band: GSM-1900
modem:
- label: m7801

@ -0,0 +1,95 @@
{'addr': ['0.0.0.0',
'255.255.255.255',
'10.11.12.13',
'10.0.99.1',
'192.168.0.14'],
'bts': [{'addr': '10.42.42.114',
'name': 'sysmoBTS 1002',
'trx': [{'band': 'GSM-1800',
'timeslots': ['CCCH+SDCCH4',
'SDCCH8',
'TCH/F_TCH/H_PDCH',
'TCH/F_TCH/H_PDCH',
'TCH/F_TCH/H_PDCH',
'TCH/F_TCH/H_PDCH',
'TCH/F_TCH/H_PDCH',
'TCH/F_TCH/H_PDCH']},
{'band': 'GSM-1900',
'timeslots': ['SDCCH8',
'PDCH',
'PDCH',
'PDCH',
'PDCH',
'PDCH',
'PDCH',
'PDCH']}],
'type': 'sysmobts'}],
'hwaddr': ['ca:ff:ee:ba:aa:be',
'00:00:00:00:00:00',
'CA:FF:EE:BA:AA:BE',
'cA:Ff:eE:Ba:aA:Be',
'ff:ff:ff:ff:ff:ff'],
'imsi': ['012345', '012345678', '012345678912345'],
'ki': ['000102030405060708090a0b0c0d0e0f', '000102030405060708090a0b0c0d0e0f'],
'modems': [{'dbus_path': '/sierra_0',
'imsi': '901700000009001',
'ki': 'D620F48487B1B782DA55DF6717F08FF9',
'msisdn': '7801'},
{'dbus_path': '/sierra_1',
'imsi': '901700000009002',
'ki': 'D620F48487B1B782DA55DF6717F08FF9',
'msisdn': '7802'}]}
- expect validation success:
Validation: OK
- unknown item:
--- -: ERR: ValueError: config item not known: 'bts[].unknown_item'
Validation: Error
- wrong type modems[].imsi:
--- -: ERR: ValueError: config item is dict but should be a leaf node of type 'imsi': 'modems[].imsi'
Validation: Error
- invalid key with space:
--- -: ERR: ValueError: invalid config key: 'imsi '
Validation: Error
- list instead of dict:
--- -: ERR: ValueError: config item not known: 'a_dict[]'
Validation: Error
- unknown band:
--- (item='bts[].trx[].band'): ERR: ValueError: Unknown GSM band: 'what'
Validation: Error
- invalid v4 addrs:
--- (item='addr[]'): ERR: ValueError: Invalid IPv4 address: '1.2.3'
Validation: Error
--- (item='addr[]'): ERR: ValueError: Invalid IPv4 address: '1.2.3 .4'
Validation: Error
--- (item='addr[]'): ERR: ValueError: Invalid IPv4 address: '91.2.3'
Validation: Error
--- (item='addr[]'): ERR: ValueError: Invalid IPv4 address: 'go away'
Validation: Error
--- (item='addr[]'): ERR: ValueError: Invalid IPv4 address: ''
Validation: Error
--- (item='addr[]'): ERR: ValueError: Invalid IPv4 address: None
Validation: Error
- invalid hw addrs:
--- (item='hwaddr[]'): ERR: ValueError: Invalid hardware address: '1.2.3'
Validation: Error
--- (item='hwaddr[]'): ERR: ValueError: Invalid hardware address: '0b:0c:0d:0e:0f:0g'
Validation: Error
--- (item='hwaddr[]'): ERR: ValueError: Invalid hardware address: '0b:0c:0d:0e : 0f:0f'
Validation: Error
--- (item='hwaddr[]'): ERR: ValueError: Invalid hardware address: 'go away'
Validation: Error
--- (item='hwaddr[]'): ERR: ValueError: Invalid hardware address: ''
Validation: Error
--- (item='hwaddr[]'): ERR: ValueError: Invalid hardware address: None
Validation: Error
- invalid imsis:
--- (item='imsi[]'): ERR: ValueError: Invalid IMSI: '99999999x9'
Validation: Error
--- (item='imsi[]'): ERR: ValueError: Invalid IMSI: '123 456 789 123'
Validation: Error
--- (item='imsi[]'): ERR: ValueError: Invalid IMSI: 'go away'
Validation: Error
--- (item='imsi[]'): ERR: ValueError: Invalid IMSI: ''
Validation: Error
--- (item='imsi[]'): ERR: ValueError: Invalid IMSI: None
Validation: Error

@ -0,0 +1,115 @@
#!/usr/bin/env python3
import _prep
import sys
import os
import io
import pprint
import copy
from osmo_gsm_tester import config, log, schema
example_config_file = 'test.cfg'
example_config = os.path.join(_prep.script_dir, 'config_test', example_config_file)
cfg = config.read(example_config)
pprint.pprint(cfg)
test_schema = {
'modems[].dbus_path': schema.STR,
'modems[].msisdn': schema.STR,
'modems[].imsi': schema.IMSI,
'modems[].ki': schema.STR,
'bts[].name' : schema.STR,
'bts[].type' : schema.STR,
'bts[].addr' : schema.STR,
'bts[].trx[].timeslots[]' : schema.STR,
'bts[].trx[].band' : schema.BAND,
'a_dict.foo' : schema.INT,
'addr[]' : schema.IPV4,
'hwaddr[]' : schema.HWADDR,
'imsi[]' : schema.IMSI,
'ki[]' : schema.KI,
}
def val(which):
try:
schema.validate(which, test_schema)
print('Validation: OK')
except ValueError:
log.log_exn()
print('Validation: Error')
print('- expect validation success:')
val(cfg)
print('- unknown item:')
c = copy.deepcopy(cfg)
c['bts'][0]['unknown_item'] = 'no'
val(c)
print('- wrong type modems[].imsi:')
c = copy.deepcopy(cfg)
c['modems'][0]['imsi'] = {'no':'no'}
val(c)
print('- invalid key with space:')
c = copy.deepcopy(cfg)
c['modems'][0]['imsi '] = '12345'
val(c)
print('- list instead of dict:')
c = copy.deepcopy(cfg)
c['a_dict'] = [ 1, 2, 3 ]
val(c)
print('- unknown band:')
c = copy.deepcopy(cfg)
c['bts'][0]['trx'][0]['band'] = 'what'
val(c)
print('- invalid v4 addrs:')
c = copy.deepcopy(cfg)
c['addr'][3] = '1.2.3'
val(c)
c['addr'][3] = '1.2.3 .4'
val(c)
c['addr'][3] = '91.2.3'
val(c)
c['addr'][3] = 'go away'
val(c)
c['addr'][3] = ''
val(c)
c['addr'][3] = None
val(c)
print('- invalid hw addrs:')
c = copy.deepcopy(cfg)
c['hwaddr'][3] = '1.2.3'
val(c)
c['hwaddr'][3] = '0b:0c:0d:0e:0f:0g'
val(c)
c['hwaddr'][3] = '0b:0c:0d:0e : 0f:0f'
val(c)
c['hwaddr'][3] = 'go away'
val(c)
c['hwaddr'][3] = ''
val(c)
c['hwaddr'][3] = None
val(c)
print('- invalid imsis:')
c = copy.deepcopy(cfg)
c['imsi'][2] = '99999999x9'
val(c)
c['imsi'][2] = '123 456 789 123'
val(c)
c['imsi'][2] = 'go away'
val(c)
c['imsi'][2] = ''
val(c)
c['imsi'][2] = None
val(c)
# vim: expandtab tabstop=4 shiftwidth=4

@ -37,3 +37,23 @@ BTS:
- PDCH
- PDCH
band: GSM-1900
addr:
- 0.0.0.0
- 255.255.255.255
- 10.11.12.13
- 10.0.99.1
- 192.168.0.14
hwaddr:
- ca:ff:ee:ba:aa:be
- 00:00:00:00:00:00
- CA:FF:EE:BA:AA:BE
- cA:Ff:eE:Ba:aA:Be
- ff:ff:ff:ff:ff:ff
imsi:
- '012345'
- '012345678'
- '012345678912345'
ki:
- 000102030405060708090a0b0c0d0e0f
- 000102030405060708090a0b0c0d0e0f

@ -0,0 +1,44 @@
#!/usr/bin/env python3
# Based on http://stackoverflow.com/questions/22390064/use-dbus-to-just-send-a-message-in-python
# Python DBUS Test Server
# runs until the Quit() method is called via DBUS
from gi.repository import GLib
from pydbus import SessionBus
loop = GLib.MainLoop()
class MyDBUSService(object):
"""
<node>
<interface name='net.lew21.pydbus.ClientServerExample'>
<method name='Hello'>
<arg type='s' name='response' direction='out'/>
</method>
<method name='EchoString'>
<arg type='s' name='a' direction='in'/>
<arg type='s' name='response' direction='out'/>
</method>
<method name='Quit'/>
</interface>
</node>
"""
def Hello(self):
"""returns the string 'Hello, World!'"""
return "Hello, World!"
def EchoString(self, s):
"""returns whatever is passed to it"""
return s
def Quit(self):
"""removes this object from the DBUS connection and exits"""
loop.quit()
bus = SessionBus()
bus.publish("net.lew21.pydbus.ClientServerExample", MyDBUSService())
loop.run()

@ -0,0 +1,57 @@
#!/usr/bin/env python3
'''
Power on and off some modem on ofono, while running the glib main loop in a
thread and receiving modem state changes by dbus signals.
'''
from pydbus import SystemBus, Variant
import time
import threading
import pprint
from gi.repository import GLib
loop = GLib.MainLoop()
def propchanged(*args, **kwargs):
print('-> PROP CHANGED: %r %r' % (args, kwargs))
class GlibMainloop(threading.Thread):
def run(self):
loop.run()
ml = GlibMainloop()
ml.start()
try:
bus = SystemBus()
print('\n- list modems')
root = bus.get("org.ofono", '/')
print(root.Introspect())
modems = sorted(root.GetModems())
pprint.pprint(modems)
first_modem_path = modems[0][0]
print('\n- first modem %r' % first_modem_path)
modem = bus.get("org.ofono", first_modem_path)
modem.PropertyChanged.connect(propchanged)
print(modem.Introspect())
print(modem.GetProperties())
print('\n- set Powered = True')
modem.SetProperty('Powered', Variant('b', True))
print('call returned')
print(modem.GetProperties())
time.sleep(1)
print('\n- set Powered = False')
modem.SetProperty('Powered', Variant('b', False))
print('call returned')
print(modem.GetProperties())
finally:
loop.quit()
ml.join()

@ -0,0 +1,71 @@
#!/usr/bin/env python3
'''
Power on and off some modem on ofono, while running the glib main loop in a
thread and receiving modem state changes by dbus signals.
'''
from pydbus import SystemBus, Variant
import time
import pprint
from gi.repository import GLib
glib_main_loop = GLib.MainLoop()
glib_main_ctx = glib_main_loop.get_context()
def propchanged(*args, **kwargs):
print('-> PROP CHANGED: %r %r' % (args, kwargs))
def pump():
global glib_main_ctx
print('pump?')
while glib_main_ctx.pending():
print('* pump')
glib_main_ctx.iteration()
def wait(condition):
pump()
while not condition():
time.sleep(.1)
pump()
bus = SystemBus()
print('\n- list modems')
root = bus.get("org.ofono", '/')
print(root.Introspect())
modems = sorted(root.GetModems())
pprint.pprint(modems)
pump()
first_modem_path = modems[0][0]
print('\n- first modem %r' % first_modem_path)
modem = bus.get("org.ofono", first_modem_path)
modem.PropertyChanged.connect(propchanged)
print(modem.Introspect())
print(modem.GetProperties())
print('\n- set Powered = True')
modem.SetProperty('Powered', Variant('b', True))
print('call returned')
print('- pump dbus events')
pump()
pump()
print('sleep 1')
time.sleep(1)
pump()
print('- modem properties:')
print(modem.GetProperties())
print('\n- set Powered = False')
modem.SetProperty('Powered', Variant('b', False))
print('call returned')
print(modem.GetProperties())
# vim: tabstop=4 shiftwidth=4 expandtab

@ -3,7 +3,7 @@ import time
import _prep
from osmo_gsm_tester.utils import FileLock
from osmo_gsm_tester.util import FileLock
fl = FileLock('/tmp/lock_test', '_'.join(sys.argv[1:]))

@ -0,0 +1,41 @@
- Testing global log functions
01:02:03 tst <origin>: from log.log()
01:02:03 tst <origin>: DBG: from log.dbg()
01:02:03 tst <origin>: ERR: from log.err()
- Testing log.Origin functions
01:02:03 tst some-name(some='detail'): hello log
01:02:03 tst some-name(some='detail'): ERR: hello err
01:02:03 tst some-name(some='detail'): message {int=3, none=None, str='str\n', tuple=('foo', 42)}
01:02:03 tst some-name(some='detail'): DBG: hello dbg
- Testing log.style()
01:02:03: only time
tst: only category
DBG: only level
some-name(some='detail'): only origin
only src [log_test.py:70]
- Testing log.style_change()
no log format
01:02:03: add time
but no time format
01:02:03: DBG: add level
01:02:03 tst: DBG: add category
01:02:03 tst: DBG: add src [log_test.py:85]
01:02:03 tst some-name(some='detail'): DBG: add origin [log_test.py:87]
- Testing origin_width
01:02:03 tst shortname: origin str set to 23 chars [log_test.py:94]
01:02:03 tst very long name(and_some=(3, 'things', 'in a tuple'), some='details'): long origin str [log_test.py:96]
01:02:03 tst very long name(and_some=(3, 'things', 'in a tuple'), some='details'): DBG: long origin str dbg [log_test.py:97]
01:02:03 tst very long name(and_some=(3, 'things', 'in a tuple'), some='details'): ERR: long origin str err [log_test.py:98]
- Testing log.Origin with omitted info
01:02:03 tst LogTest: hello log, name implicit from class name [log_test.py:103]
01:02:03 --- explicit_name: hello log, no category set [log_test.py:107]
01:02:03 --- LogTest: hello log, no category nor name set [log_test.py:110]
01:02:03 --- LogTest: DBG: debug message, no category nor name set [log_test.py:113]
- Testing logging of Exceptions, tracing origins
Not throwing an exception in 'with:' works.
nested print just prints
01:02:03 tst level3: nested log() [level1↪level2↪level3] [log_test.py:145]
01:02:03 tst level2: nested l2 log() from within l3 scope [level1↪level2] [log_test.py:146]
01:02:03 tst level3: ERR: ValueError: bork [level1↪level2↪level3] [log_test.py:147: raise ValueError('bork')]
- Enter the same Origin context twice
01:02:03 tst level2: nested log [level1↪level2] [log_test.py:159]

@ -29,6 +29,7 @@ from osmo_gsm_tester import log
#log.targets[0].get_time_str = lambda: '01:02:03'
fake_time = '01:02:03'
log.style_change(time=True, time_fmt=fake_time)
log.set_all_levels(None)
print('- Testing global log functions')
log.log('<origin>', log.C_TST, 'from log.log()')

@ -0,0 +1,7 @@
#!/usr/bin/env python3
msisdn = '0000'
l = len(msisdn)
next_msisdn = ('%%0%dd' % l) % (int(msisdn) + 1)
print(next_msisdn)

@ -0,0 +1,33 @@
run foo: DBG: cd '[TMP]'; PATH=[$PATH] foo.py arg1 arg2 [foo↪foo]
run foo: DBG: [TMP]/stdout [foo↪foo]
run foo: DBG: [TMP]/stderr [foo↪foo]
run foo(pid=[PID]): Launched [foo(pid=[PID])↪foo(pid=[PID])]
stdout:
(launched: [DATETIME])
foo stdout
[[$0], 'arg1', 'arg2']
stderr:
(launched: [DATETIME])
foo stderr
run foo(pid=[PID]): Terminating (SIGINT)
run foo(pid=[PID]): DBG: Cleanup
run foo(pid=[PID]): Terminated {rc=1}
result: 1
stdout:
(launched: [DATETIME])
foo stdout
[[$0], 'arg1', 'arg2']
Exiting (stdout)
stderr:
(launched: [DATETIME])
foo stderr
Traceback (most recent call last):
File [$0], line [LINE], in <module>
time.sleep(1)
KeyboardInterrupt
Exiting (stderr)
done.

@ -0,0 +1,7 @@
PATH='[^']*' PATH=[$PATH]
/tmp/[^/ '"]* [TMP]
pid=[0-9]* pid=[PID]
....-..-.._..:..:.. [DATETIME]
'[^']*/selftest/process_test/foo.py' [$0]
"[^"]*/selftest/process_test/foo.py" [$0]
, line [0-9]* , line [LINE]

@ -0,0 +1,51 @@
#!/usr/bin/env python3
import _prep
import time
import os
from osmo_gsm_tester import process, util, log
tmpdir = util.Dir(util.get_tempdir())
dollar_path = '%s:%s' % (
os.path.join(os.getcwd(), 'process_test'),
os.getenv('PATH'))
p = process.Process('foo', tmpdir, ('foo.py', 'arg1', 'arg2'),
env={'PATH': dollar_path})
p.launch()
time.sleep(.5)
p.poll()
print('stdout:')
print(p.get_stdout())
print('stderr:')
print(p.get_stderr())
assert not p.terminated()
p.terminate()
assert p.terminated()
print('result: %r' % p.result)
print('stdout:')
print(p.get_stdout())
print('stderr:')
print(p.get_stderr())
print('done.')
test_ssh = True
test_ssh = False
if test_ssh:
# this part of the test requires ability to ssh to localhost
p = process.RemoteProcess('localhost', '/tmp', 'ssh-test', tmpdir,
('ls', '-al'))
p.launch()
p.wait()
assert p.terminated()
print('stdout:')
print(p.get_stdout())
print('stderr:')
print(p.get_stderr())
# vim: expandtab tabstop=4 shiftwidth=4

@ -0,0 +1,25 @@
#!/usr/bin/env python3
import sys
import atexit
import time
sys.stdout.write('foo stdout\n')
sys.stderr.write('foo stderr\n')
print(repr(sys.argv))
sys.stdout.flush()
sys.stderr.flush()
def x():
sys.stdout.write('Exiting (stdout)\n')
sys.stdout.flush()
sys.stderr.write('Exiting (stderr)\n')
sys.stderr.flush()
atexit.register(x)
while True:
time.sleep(1)
# vim: expandtab tabstop=4 shiftwidth=4

@ -0,0 +1,24 @@
#!/usr/bin/env python3
import support
import importlib.util
if hasattr(importlib.util, 'module_from_spec'):
def run_test(path):
print('py 3.5+')
spec = importlib.util.spec_from_file_location("tests.script", path)
spec.loader.exec_module( importlib.util.module_from_spec(spec) )
else:
def run_test(path):
print('py 3.4-')
from importlib.machinery import SourceFileLoader
SourceFileLoader("tests.script", path).load_module()
path = './subdir/script.py'
support.config = 'specifics'
run_test(path)
support.config = 'specifics2'
run_test(path)

@ -0,0 +1,9 @@
from support import *
print('hello')
def run(what):
print(what)
print(what)
run(config)

@ -0,0 +1,18 @@
This a real gsm test suite configured and ready to use.
The only thing missing is a trial dir containing binaries.
If you have your trial with binary tar archives in ~/my_trial
you can run the suite for example like this:
. ./env # point your environment at all the right places
run_once.py ~/my_trial -s sms:trx
This combines the suites/sms test suite with the scenarios/trx choice of
osmo-bts-trx and runs all tests in the 'sms' suite.
A ./state dir will be created to store the current osmo-gsm-tester state. If
you prefer not to write to this dir, set up an own configuration pointing at a
different path (see paths.conf: 'state_dir' and the env file). When there is
no OSMO_GSM_TESTER_CONF set (from ./env), osmo-gsm-tester will instead look for
conf files in several locations like ~/.config/osmo-gsm-tester,
/usr/local/etc/osmo-gsm-tester, /etc/osmo-gsm-tester

@ -0,0 +1,31 @@
nitb:
net:
mcc: 1
mnc: 868
short_name: osmo-gsm-tester
long_name: osmo-gsm-tester
auth_policy: closed
encryption: a5 0
nitb_bts:
location_area_code: 23
base_station_id_code: 63
stream_id: 255
trx_list:
- max_power_red: 22
arfcn: 868
timeslot_list:
- phys_chan_config: CCCH+SDCCH4
- phys_chan_config: SDCCH8
- phys_chan_config: TCH_F/TCH_H/PDCH
- phys_chan_config: TCH_F/TCH_H/PDCH
- phys_chan_config: TCH_F/TCH_H/PDCH
- phys_chan_config: TCH_F/TCH_H/PDCH
- phys_chan_config: TCH_F/TCH_H/PDCH
- phys_chan_config: TCH_F/TCH_H/PDCH
osmo_bts_sysmo:
ipa_unit_id: 1123
osmo_bts_trx:
ipa_unit_id: 1124

@ -0,0 +1,4 @@
OSMO_GSM_TESTER_SRC="$(readlink -f ../../src)"
export PYTHONPATH="$OSMO_GSM_TESTER_SRC"
export PATH="$OSMO_GSM_TESTER_SRC:$PATH"
export OSMO_GSM_TESTER_CONF="$PWD"

@ -0,0 +1,3 @@
state_dir: './state'
suites_dir: './suites'
scenarios_dir: './scenarios'

@ -0,0 +1,139 @@
# all hardware and interfaces available to this osmo-gsm-tester
nitb_iface:
- addr: 127.0.0.10
- addr: 127.0.0.11
- addr: 127.0.0.12
bts:
- label: sysmoBTS 1002
type: sysmo
unit_id: 1
addr: 10.42.42.114
band: GSM-1800
- label: octBTS 3000
type: oct
unit_id: 5
addr: 10.42.42.115
band: GSM-1800
trx:
- hwaddr: 00:0c:90:32:b5:8a
- label: Ettus B210
type: osmotrx
unit_id: 6
addr: 10.42.42.116
band: GSM-1800
- label: nanoBTS 1900
type: nanobts
unit_id: 1902
addr: 10.42.42.190
band: GSM-1900
trx:
- hwaddr: 00:02:95:00:41:b3
arfcn:
- arfcn: 512
band: GSM-1800
- arfcn: 514
band: GSM-1800
- arfcn: 516
band: GSM-1800
- arfcn: 518
band: GSM-1800
- arfcn: 520
band: GSM-1800
- arfcn: 540
band: GSM-1900
- arfcn: 542
band: GSM-1900
- arfcn: 544
band: GSM-1900
- arfcn: 546
band: GSM-1900
- arfcn: 548
band: GSM-1900
modem:
- label: m7801
path: '/wavecom_0'
imsi: 901700000007801
ki: D620F48487B1B782DA55DF6717F08FF9
- label: m7802
path: '/wavecom_1'
imsi: 901700000007802
ki: 47FDB2D55CE6A10A85ABDAD034A5B7B3
- label: m7803
path: '/wavecom_2'
imsi: 901700000007803
ki: ABBED4C91417DF710F60675B6EE2C8D2
- label: m7804
path: '/wavecom_3'
imsi: 901700000007804
ki: 8BA541179156F2BF0918CA3CFF9351B0
- label: m7805
path: '/wavecom_4'
imsi: 901700000007805
ki: 82BEC24B5B50C9FAA69D17DEC0883A23
- label: m7806
path: '/wavecom_5'
imsi: 901700000007806
ki: DAF6BD6A188F7A4F09866030BF0F723D
- label: m7807
path: '/wavecom_6'
imsi: 901700000007807
ki: AEB411CFE39681A6352A1EAE4DDC9DBA
- label: m7808
path: '/wavecom_7'
imsi: 901700000007808
ki: F5DEF8692B305D7A65C677CA9EEE09C4
- label: m7809
path: '/wavecom_8'
imsi: 901700000007809
ki: A644F4503E812FD75329B1C8D625DA44
- label: m7810
path: '/wavecom_9'
imsi: 901700000007810
ki: EF663BDF3477DCD18D3D2293A2BAED67
- label: m7811
path: '/wavecom_10'
imsi: 901700000007811
ki: E88F37F048A86A9BC4D652539228C039
- label: m7812
path: '/wavecom_11'
imsi: 901700000007812
ki: E8D940DD66FCF6F1CD2C0F8F8C45633D
- label: m7813
path: '/wavecom_12'
imsi: 901700000007813
ki: DBF534700C10141C49F699B0419107E3
- label: m7814
path: '/wavecom_13'
imsi: 901700000007814
ki: B36021DEB90C4EA607E408A92F3B024D
- label: m7815
path: '/wavecom_14'
imsi: 901700000007815
ki: 1E209F6F839F9195778C4F96BE281A24
- label: m7816
path: '/wavecom_15'
imsi: 901700000007816
ki: BF827D219E739DD189F6F59E60D6455C

@ -0,0 +1,3 @@
resources:
bts:
- type: osmotrx

@ -0,0 +1,26 @@
#!/usr/bin/env python3
from osmo_gsm_tester.test import *
print('use resources...')
nitb = suite.nitb()
bts = suite.bts()
ms_mo = suite.modem()
ms_mt = suite.modem()
print('start nitb and bts...')
nitb.add_bts(bts)
nitb.start()
sleep(.1)
assert nitb.running()
bts.start()
nitb.add_subscriber(ms_mo)
nitb.add_subscriber(ms_mt)
ms_mo.connect(nitb)
ms_mt.connect(nitb)
wait(nitb.subscriber_attached, ms_mo, ms_mt)
sms = ms_mo.sms_send(ms_mt.msisdn)
sleep(3)
wait(nitb.sms_received, sms)

@ -0,0 +1,10 @@
resources:
nitb_iface:
- times: 1
bts:
- times: 1
modem:
- times: 2
defaults:
timeout: 60s

@ -0,0 +1,207 @@
- expect solutions:
[0, 1, 2]
[0, 1, 2]
[1, 0, 2]
[1, 2, 0]
- expect failure to solve:
The requested resource requirements are not solvable [[0, 2], [2], [0, 2]]
- test removing a Resources list from itself
ok, caused exception: RuntimeError('Refusing to drop a list of resources from itself. This is probably a bug where a list of Resources() should have been copied but is passed as-is. use Resources.clear() instead.',)
- test removing a Resources list from one with the same list in it
- test resources config and state dir:
*** all resources:
{'arfcn': [{'_hash': 'e620569450f8259b3f0212ec19c285dd07df063c',
'arfcn': '512',
'band': 'GSM-1800'},
{'_hash': '022621e513c5a5bf33b77430a1e9c886be676fa1',
'arfcn': '514',
'band': 'GSM-1800'},
{'_hash': '3199abf375a1dd899e554e9d63a552e06d7f38bf',
'arfcn': '516',
'band': 'GSM-1800'},
{'_hash': '57aa7bd1da62495f2857ae6b859193dd592a0a02',
'arfcn': '518',
'band': 'GSM-1800'},
{'_hash': '53dd2e2682b736f427abd2ce59a9a50ca8130678',
'arfcn': '520',
'band': 'GSM-1800'},
{'_hash': '31687a5e6d5140a4b3877606ca5f18244f11d706',
'arfcn': '540',
'band': 'GSM-1900'},
{'_hash': '1def43a5c88a83cdb21279eacab0679ea08ffaf3',
'arfcn': '542',
'band': 'GSM-1900'},
{'_hash': '1d6e3b08a3861fd4d748f111295ec5a93ecd3d23',
'arfcn': '544',
'band': 'GSM-1900'},
{'_hash': '8fb36927de15466fcdbee01f7f65704c312cb36c',
'arfcn': '546',
'band': 'GSM-1900'},
{'_hash': 'dc9ce027a257da087f31a5bc1ee6b4abd2637369',
'arfcn': '548',
'band': 'GSM-1900'}],
'bts': [{'_hash': 'a7c6d2ebaeb139e8c2e7d45c3495d046d7439007',
'addr': '10.42.42.114',
'band': 'GSM-1800',
'label': 'sysmoBTS 1002',
'type': 'sysmo',
'unit_id': '1'},
{'_hash': '02540ab9eb556056a0b4d28443bc9f4793f6d549',
'addr': '10.42.42.115',
'band': 'GSM-1800',
'label': 'octBTS 3000',
'trx': [{'hwaddr': '00:0c:90:32:b5:8a'}],
'type': 'oct',
'unit_id': '5'},
{'_hash': '556c954d475d12cf0dc622c0df5743cac5543fa0',
'addr': '10.42.42.190',
'band': 'GSM-1900',
'label': 'nanoBTS 1900',
'trx': [{'hwaddr': '00:02:95:00:41:b3'}],
'type': 'nanobts',
'unit_id': '1902'}],
'modem': [{'_hash': '19c69e45aa090fb511446bd00797690aa82ff52f',
'imsi': '901700000007801',
'ki': 'D620F48487B1B782DA55DF6717F08FF9',
'label': 'm7801',
'path': '/wavecom_0'},
{'_hash': 'e1a46516a1fb493b2617ab14fc1693a9a45ec254',
'imsi': '901700000007802',
'ki': '47FDB2D55CE6A10A85ABDAD034A5B7B3',
'label': 'm7802',
'path': '/wavecom_1'},
{'_hash': '4fe91500a309782bb0fd8ac6fc827834089f8b00',
'imsi': '901700000007803',
'ki': 'ABBED4C91417DF710F60675B6EE2C8D2',
'label': 'm7803',
'path': '/wavecom_2'},
{'_hash': 'c895badf0c2faaa4a997cd9f2313b5ebda7486e4',
'imsi': '901700000007804',
'ki': '8BA541179156F2BF0918CA3CFF9351B0',
'label': 'm7804',
'path': '/wavecom_3'},
{'_hash': '60f182abed05adb530e3d06d88cc47703b65d7d8',
'imsi': '901700000007805',
'ki': '82BEC24B5B50C9FAA69D17DEC0883A23',
'label': 'm7805',
'path': '/wavecom_4'},
{'_hash': 'd1f0fbf089a4bf32dd566af956d23b89e3d60821',
'imsi': '901700000007806',
'ki': 'DAF6BD6A188F7A4F09866030BF0F723D',
'label': 'm7806',
'path': '/wavecom_5'},
{'_hash': '2445e3b5949d15f4351c0db1d3f3f593f9d73aa5',
'imsi': '901700000007807',
'ki': 'AEB411CFE39681A6352A1EAE4DDC9DBA',
'label': 'm7807',
'path': '/wavecom_6'},
{'_hash': '80247388b2ca382382c4aec678102355b7922965',
'imsi': '901700000007808',
'ki': 'F5DEF8692B305D7A65C677CA9EEE09C4',
'label': 'm7808',
'path': '/wavecom_7'},
{'_hash': '5b9e4e117a8889430542d22a9693e7b999362856',
'imsi': '901700000007809',
'ki': 'A644F4503E812FD75329B1C8D625DA44',
'label': 'm7809',
'path': '/wavecom_8'},
{'_hash': '219a7abb057050eef3ce4b99c487f32bbaae9a41',
'imsi': '901700000007810',
'ki': 'EF663BDF3477DCD18D3D2293A2BAED67',
'label': 'm7810',
'path': '/wavecom_9'},
{'_hash': '75d45c2d975b893da34c7cae827c25a2039cecd2',
'imsi': '901700000007811',
'ki': 'E88F37F048A86A9BC4D652539228C039',
'label': 'm7811',
'path': '/wavecom_10'},
{'_hash': '1777362f556b249a5c1d6a83110704dbd037bc20',
'imsi': '901700000007812',
'ki': 'E8D940DD66FCF6F1CD2C0F8F8C45633D',
'label': 'm7812',
'path': '/wavecom_11'},
{'_hash': '21d7eb4b0c782e004821a9f7f778891c93956924',
'imsi': '901700000007813',
'ki': 'DBF534700C10141C49F699B0419107E3',
'label': 'm7813',
'path': '/wavecom_12'},
{'_hash': 'f53e4e79bdbc63eb2845de671007d4f733f28409',
'imsi': '901700000007814',
'ki': 'B36021DEB90C4EA607E408A92F3B024D',
'label': 'm7814',
'path': '/wavecom_13'},
{'_hash': 'df1abec7704ebc89b2c062a69bd299cf3663ed9e',
'imsi': '901700000007815',
'ki': '1E209F6F839F9195778C4F96BE281A24',
'label': 'm7815',
'path': '/wavecom_14'},
{'_hash': '11df1e4c7708157e5b89020c757763f58d6e610b',
'imsi': '901700000007816',
'ki': 'BF827D219E739DD189F6F59E60D6455C',
'label': 'm7816',
'path': '/wavecom_15'}],
'nitb_iface': [{'_hash': 'cde1debf28f07f94f92c761b4b7c6bf35785ced4',
'addr': '10.42.42.1'},
{'_hash': 'fd103b22c7cf2480d609150e06f4bbd92ac78d8c',
'addr': '10.42.42.2'},
{'_hash': '1c614d6210c551d142aadca8f25e1534ebb2a70f',
'addr': '10.42.42.3'}]}
*** end: all resources
- request some resources
--- (want='nitb_iface'): DBG: Looking for 1 x nitb_iface , candidates: 3
--- (want='arfcn'): DBG: Looking for 2 x arfcn , candidates: 10
--- (want='bts'): DBG: Looking for 2 x bts , candidates: 3
--- (want='modem'): DBG: Looking for 2 x modem , candidates: 16
~~~ currently reserved:
arfcn:
- _hash: e620569450f8259b3f0212ec19c285dd07df063c
_reserved_by: testowner-123-1490837279
arfcn: '512'
band: GSM-1800
- _hash: 022621e513c5a5bf33b77430a1e9c886be676fa1
_reserved_by: testowner-123-1490837279
arfcn: '514'
band: GSM-1800
bts:
- _hash: a7c6d2ebaeb139e8c2e7d45c3495d046d7439007
_reserved_by: testowner-123-1490837279
addr: 10.42.42.114
band: GSM-1800
label: sysmoBTS 1002
type: sysmo
unit_id: '1'
- _hash: 02540ab9eb556056a0b4d28443bc9f4793f6d549
_reserved_by: testowner-123-1490837279
addr: 10.42.42.115
band: GSM-1800
label: octBTS 3000
trx:
- hwaddr: 00:0c:90:32:b5:8a
type: oct
unit_id: '5'
modem:
- _hash: 19c69e45aa090fb511446bd00797690aa82ff52f
_reserved_by: testowner-123-1490837279
imsi: '901700000007801'
ki: D620F48487B1B782DA55DF6717F08FF9
label: m7801
path: /wavecom_0
- _hash: e1a46516a1fb493b2617ab14fc1693a9a45ec254
_reserved_by: testowner-123-1490837279
imsi: '901700000007802'
ki: 47FDB2D55CE6A10A85ABDAD034A5B7B3
label: m7802
path: /wavecom_1
nitb_iface:
- _hash: cde1debf28f07f94f92c761b4b7c6bf35785ced4
_reserved_by: testowner-123-1490837279
addr: 10.42.42.1
~~~ end: currently reserved
~~~ currently reserved:
{}
~~~ end: currently reserved

@ -0,0 +1,97 @@
#!/usr/bin/env python3
import tempfile
import os
import pprint
import shutil
import atexit
import _prep
from osmo_gsm_tester import config, log, resource, util
workdir = util.get_tempdir()
# override config locations to make sure we use only the test conf
config.ENV_CONF = './conf'
log.get_process_id = lambda: '123-1490837279'
print('- expect solutions:')
pprint.pprint(
resource.solve([ [0, 1, 2],
[0, 1, 2],
[0, 1, 2] ]) )
pprint.pprint(
resource.solve([ [0, 1, 2],
[0, 1],
[0, 2] ]) ) # == [0, 1, 2]
pprint.pprint(
resource.solve([ [0, 1, 2],
[0],
[0, 2] ]) ) # == [1, 0, 2]
pprint.pprint(
resource.solve([ [0, 1, 2],
[2],
[0, 2] ]) ) # == [1, 2, 0]
print('- expect failure to solve:')
try:
resource.solve([ [0, 2],
[2],
[0, 2] ])
assert False
except resource.NoResourceExn as e:
print(e)
print('- test removing a Resources list from itself')
try:
r = resource.Resources({ 'k': [ {'a': 1, 'b': 2}, {'a': 3, 'b': 4}, ],
'i': [ {'c': 1, 'd': 2}, {'c': 3, 'd': 4}, ] })
r.drop(r)
assert False
except RuntimeError as e:
print('ok, caused exception: %r' % e)
print('- test removing a Resources list from one with the same list in it')
r = resource.Resources({ 'k': [ {'a': 1, 'b': 2}, {'a': 3, 'b': 4}, ],
'i': [ {'c': 1, 'd': 2}, {'c': 3, 'd': 4}, ] })
r.drop({ 'k': r.get('k'), 'i': r.get('i') })
assert not r
print('- test resources config and state dir:')
resources_conf = os.path.join(_prep.script_dir, 'resource_test', 'etc',
'resources.conf')
state_dir = config.get_state_dir()
rrfile = state_dir.child(resource.RESERVED_RESOURCES_FILE)
pool = resource.ResourcesPool()
print('*** all resources:')
pprint.pprint(pool.all_resources)
print('*** end: all resources\n')
print('- request some resources')
want = {
'nitb_iface': [ { 'times': 1 } ],
'bts': [ { 'type': 'sysmo', 'times': 1 }, { 'type': 'oct', 'times': 1 } ],
'arfcn': [ { 'band': 'GSM-1800', 'times': 2 } ],
'modem': [ { 'times': 2 } ],
}
origin = log.Origin('testowner')
resources = pool.reserve(origin, want)
print('~~~ currently reserved:')
with open(rrfile, 'r') as f:
print(f.read())
print('~~~ end: currently reserved\n')
resources.free()
print('~~~ currently reserved:')
with open(rrfile, 'r') as f:
print(f.read())
print('~~~ end: currently reserved\n')
# vim: expandtab tabstop=4 shiftwidth=4

@ -0,0 +1,40 @@
- non-existing suite dir
--- -: ERR: RuntimeError: Suite not found: 'does_not_exist' in ./suite_test
- no suite.conf
cnf empty_dir: DBG: reading suite.conf [empty_dir↪empty_dir]
--- ./suite_test/empty_dir/suite.conf: ERR: FileNotFoundError: [Errno 2] No such file or directory: './suite_test/empty_dir/suite.conf' [empty_dir↪./suite_test/empty_dir/suite.conf]
- valid suite dir
cnf test_suite: DBG: reading suite.conf [test_suite↪test_suite]
defaults:
timeout: 60s
resources:
bts:
- times: '1'
modem:
- times: '2'
nitb_iface:
- times: '1'
- run hello world test
tst test_suite: reserving resources...
--- (want='nitb_iface'): DBG: Looking for 1 x nitb_iface , candidates: 3
--- (want='modem'): DBG: Looking for 2 x modem , candidates: 16
--- (want='bts'): DBG: Looking for 1 x bts , candidates: 3
tst hello_world.py: START [test_suite↪hello_world.py]
tst hello_world.py:3: hello world [test_suite↪hello_world.py:3]
tst hello_world.py:4: I am 'test_suite' / 'hello_world.py:4' [test_suite↪hello_world.py:4]
tst hello_world.py:5: one [test_suite↪hello_world.py:5]