Change in osmo-gsm-tester[master]: Allow suites to dynamically register schemas so tests can receive par...

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

pespin gerrit-no-reply at lists.osmocom.org
Thu May 7 15:06:01 UTC 2020


pespin has submitted this change. ( https://gerrit.osmocom.org/c/osmo-gsm-tester/+/18088 )

Change subject: Allow suites to dynamically register schemas so tests can receive parameters
......................................................................

Allow suites to dynamically register schemas so tests can receive parameters

Change-Id: Idbe99a35993d193cd97059feb980e61ff14c67ad
---
M doc/manuals/chapters/config.adoc
A selftest/schema_test/_prep.py
A selftest/schema_test/schema_case_01.conf
A selftest/schema_test/schema_case_02.conf
A selftest/schema_test/schema_case_03.conf
A selftest/schema_test/schema_case_04.conf
A selftest/schema_test/schema_case_05.conf
A selftest/schema_test/schema_test.err
A selftest/schema_test/schema_test.ok
A selftest/schema_test/schema_test.py
M selftest/suite_test/suite_test.ok
M selftest/suite_test/suite_test.py
M selftest/suite_test/test_suite/suite.conf
A selftest/suite_test/test_suite/test_suite_params.py
M src/osmo_gsm_tester/core/schema.py
M src/osmo_gsm_tester/core/suite.py
M src/osmo_gsm_tester/core/test.py
M src/osmo_gsm_tester/testenv.py
18 files changed, 543 insertions(+), 22 deletions(-)

Approvals:
  pespin: Looks good to me, approved
  Jenkins Builder: Verified



diff --git a/doc/manuals/chapters/config.adoc b/doc/manuals/chapters/config.adoc
index 4dd90ff..483fd8c 100644
--- a/doc/manuals/chapters/config.adoc
+++ b/doc/manuals/chapters/config.adoc
@@ -305,16 +305,26 @@
 
 This file content is parsed using the <<schema_want,Want>> schema.
 
-It provides
-{app-name} with the base restrictions (later to be further filtered by
-<<scenario_conf,scenario>> files) to apply when allocating resources.
+On the <<schema_want,resources>> section, it provides {app-name} with the base restrictions
+(later to be further filtered by <<scenario_conf,scenario>> files) to apply when
+allocating resources.
 
 It can also override attributes for the allocated resources through the
 <<schema_want,modifiers>> section (to be further modified by
-<<scenario_conf,scenario>> files later on). Similary it can do the same for
+<<scenario_conf,scenario>> files later on). Similarly it can do the same for
 general configuration options (no per-resource) through the
 <<schema_want,config>> section.
 
+The _schema_ section allows defining a suite's own schema used to validate
+parameters passed to it later on through <<scenario_conf,scenario>> files (See
+<<scenario_suite_params>>), and which can be retrieved by tests using the
+_tenv.config_suite_specific()_ and _tenv.config_test_specific()_ APIs. The first
+one will provide the whole dictionary under schema, while the later will return
+the dictionary immediatelly inside the former and matching the test name being
+run. For instance, if _tenv.config_test_specific()_ is called from test
+_a_suite_test_foo.py_, the method will return the contents under dictionary with
+key _a_suite_test_foo_.
+
 .Sample 'suite.conf' file:
 ----
 resources:
@@ -337,6 +347,12 @@
       codec_list:
       - fr1
 
+schema:
+  some_suite_parameter: 'uint'
+  a_suite_test_foo:
+    one_test_parameter_for_test_foo: 'str'
+    another_test_parameter_for_test_foo: ['bool_str']
+
 defaults:
   timeout: 50s
 ----
@@ -431,6 +447,37 @@
 . Generate the final
   scenario content from the template available in the matched '.conf' file.
 
+[[scenario_suite_params]]
+*_Scenario to set suite/test parameters_*:
+
+First, the suite needs to define its schema in its <<suite_conf,suite.conf>>
+file. Check <<suite_conf>> on how to do so.
+
+For instance, for a suite named 'mysuite' containing a test 'a_suite_test_foo.py', and containing this schema in its <<suite_conf,suite.conf>> file:
+----
+schema:
+  some_suite_parameter: 'uint'
+  a_suite_test_foo:
+    one_test_parameter_for_test_foo: 'str'
+    another_test_parameter_for_test_foo: ['bool_str']
+----
+
+One could define a parametrized scenario 'myparamscenario at .conf' like this:
+----
+config:
+  suite:
+    mysuite:
+      some_suite_parameter: ${param1}
+      a_suite_test_foo:
+        one_test_parameter_for_test_foo: ${param2}
+        another_test_parameter_for_test_foo: ['true', 'false', 'false', 'true']
+----
+
+And use it in {app-name} this way:
+----
+mysuite:myparamscenario at 4,hello.conf
+----
+
 [[resources_conf]]
 ==== 'resources.conf'
 
diff --git a/selftest/schema_test/_prep.py b/selftest/schema_test/_prep.py
new file mode 120000
index 0000000..9cea3fe
--- /dev/null
+++ b/selftest/schema_test/_prep.py
@@ -0,0 +1 @@
+../_prep.py
\ No newline at end of file
diff --git a/selftest/schema_test/schema_case_01.conf b/selftest/schema_test/schema_case_01.conf
new file mode 100644
index 0000000..dacf18a
--- /dev/null
+++ b/selftest/schema_test/schema_case_01.conf
@@ -0,0 +1,43 @@
+schema:
+  handover:
+    duration: 'duration'
+    threshold: 'uint'
+
+tests:
+   - foobar:
+       prefix:
+         handover:
+           duration: 3
+           threshold: 2
+   - foobar:
+       prefix:
+         handover:
+           duration: 22kkk
+   - foobar:
+       prefix:
+         handover:
+           duration: 22h
+   - foobar:
+       wrongprefix:
+         handover:
+           duration: 22h
+   - foobar:
+       wrongprefix:
+         handover:
+   - foobar:
+       prefix:
+         handover:
+           threshold: 1
+   - foobar:
+       prefix:
+         handover:
+           threshold: -2
+   - foobar:
+       prefix:
+         handover:
+         - threshold: 1
+   - foobar:
+       prefix:
+         handover:
+           threshold:
+           - 1
diff --git a/selftest/schema_test/schema_case_02.conf b/selftest/schema_test/schema_case_02.conf
new file mode 100644
index 0000000..ddc02df
--- /dev/null
+++ b/selftest/schema_test/schema_case_02.conf
@@ -0,0 +1,12 @@
+schema:
+  hey:
+    ho:
+      letsgo: ['wrongtype']
+
+tests:
+   - foobar:
+       prefix:
+         hey:
+           ho:
+             letsgo:
+             - nanana
diff --git a/selftest/schema_test/schema_case_03.conf b/selftest/schema_test/schema_case_03.conf
new file mode 100644
index 0000000..e06fa24
--- /dev/null
+++ b/selftest/schema_test/schema_case_03.conf
@@ -0,0 +1,12 @@
+schema:
+  hey:
+    ho:
+      letsgo: ['str', 'str']
+
+tests:
+   - foobar:
+       prefix:
+         hey:
+           ho:
+             letsgo:
+             - nanana
diff --git a/selftest/schema_test/schema_case_04.conf b/selftest/schema_test/schema_case_04.conf
new file mode 100644
index 0000000..4148310
--- /dev/null
+++ b/selftest/schema_test/schema_case_04.conf
@@ -0,0 +1,12 @@
+schema:
+  hey:
+    ho:
+      letsgo: []
+
+tests:
+   - foobar:
+       prefix:
+         hey:
+           ho:
+             letsgo:
+             - nanana
diff --git a/selftest/schema_test/schema_case_05.conf b/selftest/schema_test/schema_case_05.conf
new file mode 100644
index 0000000..ee3d5db
--- /dev/null
+++ b/selftest/schema_test/schema_case_05.conf
@@ -0,0 +1,44 @@
+schema:
+  hey:
+    ho:
+      letsgo: ['str']
+
+tests:
+   - foobar:
+       prefix:
+         hey:
+           ho:
+             letsgo:
+             - nanana
+   - foobar:
+       prefix:
+         hey:
+           ho:
+             letsgo: []
+   - foobar:
+       prefix:
+         hey:
+           ho:
+             letsgo:
+             - nanana
+             - nunu
+   - foobar:
+       prefix:
+         hey:
+           ho:
+             letsgo: nanana
+   - foobar:
+       prefix:
+         hey:
+           ho:
+             letsgo: ['nana', 'nana', 'nana']
+   - foobar:
+       prefix:
+         hey:
+           ho:
+             letsgo: ['nana', {}, 'nana']
+   - foobar:
+       prefix:
+         hey:
+           ho:
+             letsgo: ['nana', [], 'nana']
diff --git a/selftest/schema_test/schema_test.err b/selftest/schema_test/schema_test.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/selftest/schema_test/schema_test.err
diff --git a/selftest/schema_test/schema_test.ok b/selftest/schema_test/schema_test.ok
new file mode 100644
index 0000000..2c4cd6a
--- /dev/null
+++ b/selftest/schema_test/schema_test.ok
@@ -0,0 +1,63 @@
+==== Testing dynamically generated schemas ====
+schema_case_01.conf:
+{'foobar.prefix.handover.duration': 'duration',
+ 'foobar.prefix.handover.threshold': 'uint'}
+validating tests[0]
+Validation: OK
+validating tests[1]
+--- foobar.prefix.handover.duration: ERR: ValueError: Invalid duration value: '22kkk'
+Validation: Error
+validating tests[2]
+Validation: OK
+validating tests[3]
+--- -: ERR: ValueError: config item not known: 'foobar.wrongprefix.handover.duration'
+Validation: Error
+validating tests[4]
+--- -: ERR: ValueError: config item not known: 'foobar.wrongprefix.handover'
+Validation: Error
+validating tests[5]
+Validation: OK
+validating tests[6]
+--- foobar.prefix.handover.threshold: ERR: ValueError: Positive value expected instead of -2
+Validation: Error
+validating tests[7]
+--- -: ERR: ValueError: config item not known: 'foobar.prefix.handover[].threshold'
+Validation: Error
+validating tests[8]
+--- -: ERR: ValueError: config item is a list, should be 'uint': 'foobar.prefix.handover.threshold'
+Validation: Error
+----------------------
+schema_case_02.conf:
+{'foobar.prefix.hey.ho.letsgo[]': 'wrongtype'}
+validating tests[0]
+--- -: ERR: ValueError: unknown type 'wrongtype' at 'foobar.prefix.hey.ho.letsgo[]'
+Validation: Error
+----------------------
+schema_case_03.conf:
+--- -: ERR: AssertionError: 
+config2schema: Error
+----------------------
+schema_case_04.conf:
+--- -: ERR: AssertionError: 
+config2schema: Error
+----------------------
+schema_case_05.conf:
+{'foobar.prefix.hey.ho.letsgo[]': 'str'}
+validating tests[0]
+Validation: OK
+validating tests[1]
+Validation: OK
+validating tests[2]
+Validation: OK
+validating tests[3]
+--- -: ERR: ValueError: config item not known: 'foobar.prefix.hey.ho.letsgo'
+Validation: Error
+validating tests[4]
+Validation: OK
+validating tests[5]
+--- -: ERR: ValueError: config item is dict but should be a leaf node of type 'str': 'foobar.prefix.hey.ho.letsgo[]'
+Validation: Error
+validating tests[6]
+--- -: ERR: ValueError: config item is a list, should be 'str': 'foobar.prefix.hey.ho.letsgo[]'
+Validation: Error
+----------------------
diff --git a/selftest/schema_test/schema_test.py b/selftest/schema_test/schema_test.py
new file mode 100755
index 0000000..3cf2799
--- /dev/null
+++ b/selftest/schema_test/schema_test.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+
+import _prep
+
+import sys
+import os
+import io
+import pprint
+import copy
+
+from osmo_gsm_tester.core import config, log, schema
+
+def val(which, test_schema):
+    try:
+        schema.validate(which, test_schema)
+        print('Validation: OK')
+    except ValueError:
+        log.log_exn()
+        print('Validation: Error')
+
+def get_case_list(dir):
+    li = []
+    for f in os.listdir(dir):
+        if f.startswith('schema_case'):
+            li.append(f)
+    return sorted(li)
+
+print('==== Testing dynamically generated schemas ====')
+for f in get_case_list(_prep.script_dir):
+    print('%s:' % f)
+    example_config = os.path.join(_prep.script_dir, f)
+    cfg = config.read(example_config)
+    try:
+        schema_def = schema.config_to_schema_def(cfg['schema'], 'foobar.prefix.')
+    except AssertionError:
+        schema_def = None
+        log.log_exn()
+        print('config2schema: Error')
+
+    if schema_def is not None:
+        pprint.pprint(schema_def)
+        i = 0
+        for t in cfg['tests']:
+            print('validating tests[%d]' % i)
+            val(t, schema_def)
+            i += 1
+    print('----------------------')
+
+
+
+
+
+# vim: expandtab tabstop=4 shiftwidth=4
diff --git a/selftest/suite_test/suite_test.ok b/selftest/suite_test/suite_test.ok
index 908f24f..fa38605 100644
--- a/selftest/suite_test/suite_test.ok
+++ b/selftest/suite_test/suite_test.ok
@@ -1,5 +1,5 @@
 - non-existing suite dir
-cnf -: DBG: Found config file paths.conf as [PATH]/selftest/suite_test/paths.conf in ./suite_test which is [PATH]/selftest/suite_test
+cnf -: DBG: Found config file paths.conf as [PATH]/selftest/suite_test/paths.conf in [PATH]/selftest/suite_test which is [PATH]/selftest/suite_test
 cnf -: DBG: [PATH]/selftest/suite_test/paths.conf: relative path ./test_work/state_dir is [PATH]/selftest/suite_test/test_work/state_dir
 cnf -: DBG: [PATH]/selftest/suite_test/paths.conf: relative path . is [PATH]/selftest/suite_test
 cnf -: DBG: Found path suites_dir as [PATH]/selftest/suite_test
@@ -25,7 +25,7 @@
   - times: '2'
 
 - run hello world test
-cnf ResourcesPool: DBG: Found config file resources.conf as [PATH]/selftest/suite_test/resources.conf in ./suite_test which is [PATH]/selftest/suite_test
+cnf ResourcesPool: DBG: Found config file resources.conf as [PATH]/selftest/suite_test/resources.conf in [PATH]/selftest/suite_test which is [PATH]/selftest/suite_test
 cnf ResourcesPool: DBG: Found path state_dir as [PATH]/selftest/suite_test/test_work/state_dir
 
 ---------------------------------------------------------------------
@@ -99,13 +99,14 @@
 ---------------------------------------------------------------------
 trial test_suite PASS
 ---------------------------------------------------------------------
-PASS: test_suite (pass: 1, skip: 5)
+PASS: test_suite (pass: 1, skip: 6)
     pass: hello_world.py (N.N sec)
     skip: mo_mt_sms.py
     skip: mo_sms.py
     skip: test_error.py
     skip: test_fail.py
     skip: test_fail_raise.py
+    skip: test_suite_params.py
 
 - a test with an error
 
@@ -122,13 +123,14 @@
 ---------------------------------------------------------------------
 trial test_suite FAIL
 ---------------------------------------------------------------------
-FAIL: test_suite (fail: 1, skip: 5)
+FAIL: test_suite (fail: 1, skip: 6)
     skip: hello_world.py (N.N sec)
     skip: mo_mt_sms.py
     skip: mo_sms.py
     FAIL: test_error.py (N.N sec) AssertionError: test_error.py:[LINENR]: assert False
     skip: test_fail.py
     skip: test_fail_raise.py
+    skip: test_suite_params.py
 
 - a test with a failure
 
@@ -145,13 +147,14 @@
 ---------------------------------------------------------------------
 trial test_suite FAIL
 ---------------------------------------------------------------------
-FAIL: test_suite (fail: 1, skip: 5)
+FAIL: test_suite (fail: 1, skip: 6)
     skip: hello_world.py (N.N sec)
     skip: mo_mt_sms.py
     skip: mo_sms.py
     skip: test_error.py (N.N sec)
     FAIL: test_fail.py (N.N sec) EpicFail: This failure is expected
     skip: test_fail_raise.py
+    skip: test_suite_params.py
 
 - a test with a raised failure
 
@@ -167,15 +170,16 @@
 ---------------------------------------------------------------------
 trial test_suite FAIL
 ---------------------------------------------------------------------
-FAIL: test_suite (fail: 1, skip: 5)
+FAIL: test_suite (fail: 1, skip: 6)
     skip: hello_world.py (N.N sec)
     skip: mo_mt_sms.py
     skip: mo_sms.py
     skip: test_error.py (N.N sec)
     skip: test_fail.py (N.N sec)
     FAIL: test_fail_raise.py (N.N sec) ExpectedFail: This failure is expected
+    skip: test_suite_params.py
 - test with half empty scenario
-cnf ResourcesPool: DBG: Found config file resources.conf as [PATH]/selftest/suite_test/resources.conf in ./suite_test which is [PATH]/selftest/suite_test  [config.py:[LINENR]]
+cnf ResourcesPool: DBG: Found config file resources.conf as [PATH]/selftest/suite_test/resources.conf in [PATH]/selftest/suite_test which is [PATH]/selftest/suite_test  [config.py:[LINENR]]
 cnf ResourcesPool: DBG: Found path state_dir as [PATH]/selftest/suite_test/test_work/state_dir  [config.py:[LINENR]]
 
 ---------------------------------------------------------------------
@@ -254,15 +258,16 @@
 ---------------------------------------------------------------------
 trial test_suite PASS
 ---------------------------------------------------------------------
-PASS: test_suite (pass: 1, skip: 5)
+PASS: test_suite (pass: 1, skip: 6)
     pass: hello_world.py (N.N sec)
     skip: mo_mt_sms.py
     skip: mo_sms.py
     skip: test_error.py
     skip: test_fail.py
     skip: test_fail_raise.py
+    skip: test_suite_params.py
 - test with scenario
-cnf ResourcesPool: DBG: Found config file resources.conf as [PATH]/selftest/suite_test/resources.conf in ./suite_test which is [PATH]/selftest/suite_test  [config.py:[LINENR]]
+cnf ResourcesPool: DBG: Found config file resources.conf as [PATH]/selftest/suite_test/resources.conf in [PATH]/selftest/suite_test which is [PATH]/selftest/suite_test  [config.py:[LINENR]]
 cnf ResourcesPool: DBG: Found path state_dir as [PATH]/selftest/suite_test/test_work/state_dir  [config.py:[LINENR]]
 
 ---------------------------------------------------------------------
@@ -341,15 +346,16 @@
 ---------------------------------------------------------------------
 trial test_suite PASS
 ---------------------------------------------------------------------
-PASS: test_suite (pass: 1, skip: 5)
+PASS: test_suite (pass: 1, skip: 6)
     pass: hello_world.py (N.N sec)
     skip: mo_mt_sms.py
     skip: mo_sms.py
     skip: test_error.py
     skip: test_fail.py
     skip: test_fail_raise.py
+    skip: test_suite_params.py
 - test with scenario and modifiers
-cnf ResourcesPool: DBG: Found config file resources.conf as [PATH]/selftest/suite_test/resources.conf in ./suite_test which is [PATH]/selftest/suite_test  [config.py:[LINENR]]
+cnf ResourcesPool: DBG: Found config file resources.conf as [PATH]/selftest/suite_test/resources.conf in [PATH]/selftest/suite_test which is [PATH]/selftest/suite_test  [config.py:[LINENR]]
 cnf ResourcesPool: DBG: Found path state_dir as [PATH]/selftest/suite_test/test_work/state_dir  [config.py:[LINENR]]
 tst test_suite: reserving resources in [PATH]/selftest/suite_test/test_work/state_dir ...  [suite.py:[LINENR]]
 tst test_suite: DBG: {combining='resources'}  [suite.py:[LINENR]]
@@ -474,12 +480,150 @@
 ---------------------------------------------------------------------
 trial test_suite PASS
 ---------------------------------------------------------------------
-PASS: test_suite (pass: 1, skip: 5)
+PASS: test_suite (pass: 1, skip: 6)
     pass: hello_world.py (N.N sec)
     skip: mo_mt_sms.py
     skip: mo_sms.py
     skip: test_error.py
     skip: test_fail.py
     skip: test_fail_raise.py
+    skip: test_suite_params.py
+- test with suite-specific config
+cnf ResourcesPool: DBG: Found config file resources.conf as [PATH]/selftest/suite_test/resources.conf in [PATH]/selftest/suite_test which is [PATH]/selftest/suite_test  [config.py:[LINENR]]
+cnf ResourcesPool: DBG: Found path state_dir as [PATH]/selftest/suite_test/test_work/state_dir  [config.py:[LINENR]]
+tst test_suite: reserving resources in [PATH]/selftest/suite_test/test_work/state_dir ...  [suite.py:[LINENR]]
+tst test_suite: DBG: {combining='resources'}  [suite.py:[LINENR]]
+tst {combining_scenarios='resources'}: DBG: {definition_conf={bts=[{'label': 'sysmoCell 5000'}, {'label': 'sysmoCell 5000'}, {'type': 'sysmo'}], ip_address=[{}], modem=[{}, {}]}}  [test_suite↪{combining_scenarios='resources'}]  [suite.py:[LINENR]]
+tst {combining_scenarios='resources', scenario='foo'}: [RESOURCE_DICT]
+tst test_suite: DBG: {combining='modifiers'}  [suite.py:[LINENR]]
+tst {combining_scenarios='modifiers'}: DBG: {definition_conf={}}  [test_suite↪{combining_scenarios='modifiers'}]  [suite.py:[LINENR]]
+tst {combining_scenarios='modifiers', scenario='foo'}: DBG: {conf={}, scenario='foo'}  [test_suite↪{combining_scenarios='modifiers', scenario='foo'}]  [suite.py:[LINENR]]
+tst test_suite: Reserving 3 x bts (candidates: 6)  [resource.py:[LINENR]]
+tst test_suite: DBG: Picked - _hash: a59640b8ba6a373552b24a6f9f65cadd2347bace
+  addr: 10.42.42.53
+  band: GSM-1800
+  ipa_unit_id: '7'
+  label: sysmoCell 5000
+  osmo_trx:
+    clock_reference: external
+    launch_trx: 'False'
+    trx_ip: 10.42.42.112
+  trx_list:
+  - max_power_red: '3'
+    nominal_power: '10'
+  - max_power_red: '0'
+    nominal_power: '12'
+  type: osmo-bts-trx
+- _hash: c2feabd082c36a1cdeccb9a5237dfff7dbadb009
+  addr: 10.42.42.53
+  band: GSM-1800
+  ipa_unit_id: '7'
+  label: sysmoCell 5000
+  osmo_trx:
+    clock_reference: external
+    launch_trx: 'False'
+    trx_ip: 10.42.42.112
+  trx_list:
+  - nominal_power: '10'
+  - max_power_red: '1'
+    nominal_power: '12'
+  type: osmo-bts-trx
+- _hash: 07d9c8aaa940b674efcbbabdd69f58a6ce4e94f9
+  addr: 10.42.42.114
+  band: GSM-1800
+  ipa_unit_id: '1'
+  label: sysmoBTS 1002
+  type: sysmo
+  [resource.py:[LINENR]]
+tst test_suite: Reserving 1 x ip_address (candidates: 3)  [resource.py:[LINENR]]
+tst test_suite: DBG: Picked - _hash: cde1debf28f07f94f92c761b4b7c6bf35785ced4
+  addr: 10.42.42.1
+  [resource.py:[LINENR]]
+tst test_suite: Reserving 2 x modem (candidates: 16)  [resource.py:[LINENR]]
+tst test_suite: DBG: Picked - _hash: 19c69e45aa090fb511446bd00797690aa82ff52f
+  imsi: '901700000007801'
+  ki: D620F48487B1B782DA55DF6717F08FF9
+  label: m7801
+  path: /wavecom_0
+- _hash: e1a46516a1fb493b2617ab14fc1693a9a45ec254
+  imsi: '901700000007802'
+  ki: 47FDB2D55CE6A10A85ABDAD034A5B7B3
+  label: m7802
+  path: /wavecom_1
+  [resource.py:[LINENR]]
+resources(test_suite)={'bts': [{'_hash': 'a59640b8ba6a373552b24a6f9f65cadd2347bace',
+          '_reserved_by': 'test_suite-[ID_NUM]-[ID_NUM]',
+          'addr': '10.42.42.53',
+          'band': 'GSM-1800',
+          'ipa_unit_id': '7',
+          'label': 'sysmoCell 5000',
+          'osmo_trx': {'clock_reference': 'external',
+                       'launch_trx': 'False',
+                       'trx_ip': '10.42.42.112'},
+          'trx_list': [{'max_power_red': '3', 'nominal_power': '10'},
+                       {'max_power_red': '0', 'nominal_power': '12'}],
+          'type': 'osmo-bts-trx'},
+         {'_hash': 'c2feabd082c36a1cdeccb9a5237dfff7dbadb009',
+          '_reserved_by': 'test_suite-[ID_NUM]-[ID_NUM]',
+          'addr': '10.42.42.53',
+          'band': 'GSM-1800',
+          'ipa_unit_id': '7',
+          'label': 'sysmoCell 5000',
+          'osmo_trx': {'clock_reference': 'external',
+                       'launch_trx': 'False',
+                       'trx_ip': '10.42.42.112'},
+          'trx_list': [{'nominal_power': '10'},
+                       {'max_power_red': '1', 'nominal_power': '12'}],
+          'type': 'osmo-bts-trx'},
+         {'_hash': '07d9c8aaa940b674efcbbabdd69f58a6ce4e94f9',
+          '_reserved_by': 'test_suite-[ID_NUM]-[ID_NUM]',
+          'addr': '10.42.42.114',
+          'band': 'GSM-1800',
+          'ipa_unit_id': '1',
+          'label': 'sysmoBTS 1002',
+          'type': 'sysmo'}],
+ 'ip_address': [{'_hash': 'cde1debf28f07f94f92c761b4b7c6bf35785ced4',
+                 '_reserved_by': 'test_suite-[ID_NUM]-[ID_NUM]',
+                 'addr': '10.42.42.1'}],
+ 'modem': [{'_hash': '19c69e45aa090fb511446bd00797690aa82ff52f',
+            '_reserved_by': 'test_suite-[ID_NUM]-[ID_NUM]',
+            'imsi': '901700000007801',
+            'ki': 'D620F48487B1B782DA55DF6717F08FF9',
+            'label': 'm7801',
+            'path': '/wavecom_0'},
+           {'_hash': 'e1a46516a1fb493b2617ab14fc1693a9a45ec254',
+            '_reserved_by': 'test_suite-[ID_NUM]-[ID_NUM]',
+            'imsi': '901700000007802',
+            'ki': '47FDB2D55CE6A10A85ABDAD034A5B7B3',
+            'label': 'm7802',
+            'path': '/wavecom_1'}]}
+
+---------------------------------------------------------------------
+trial test_suite
+---------------------------------------------------------------------
+
+----------------------------------------------
+trial test_suite test_suite_params.py
+----------------------------------------------
+tst test_suite_params.py:[LINENR]: starting test  [test_suite↪test_suite_params.py:[LINENR]]  [test_suite_params.py:[LINENR]]
+tst test_suite: DBG: {combining='config'}  [suite.py:[LINENR]]
+tst {combining_scenarios='config'}: DBG: {definition_conf={}}  [test_suite↪{combining_scenarios='config'}]  [suite.py:[LINENR]]
+tst {combining_scenarios='config', scenario='foo'}: DBG: {conf={suite={test_suite={some_suite_global_param='heyho', test_suite_params={one_bool_parameter='true', second_list_parameter=['23', '45']}}}}, scenario='foo'}  [test_suite↪{combining_scenarios='config', scenario='foo'}]  [suite.py:[LINENR]]
+tst test_suite_params.py:[LINENR]: SPECIFIC SUITE CONFIG: {'some_suite_global_param': 'heyho',  [test_suite↪test_suite_params.py:[LINENR]]  [test_suite_params.py:[LINENR]]
+tst test_suite_params.py:[LINENR]:  'test_suite_params': {'one_bool_parameter': 'true',  [test_suite↪test_suite_params.py:[LINENR]]  [test_suite_params.py:[LINENR]]
+tst test_suite_params.py:[LINENR]:                        'second_list_parameter': ['23', '45']}}  [test_suite↪test_suite_params.py:[LINENR]]  [test_suite_params.py:[LINENR]]
+tst test_suite_params.py:[LINENR]: SPECIFIC TEST CONFIG: {'one_bool_parameter': 'true', 'second_list_parameter': ['23', '45']}  [test_suite↪test_suite_params.py:[LINENR]]  [test_suite_params.py:[LINENR]]
+tst test_suite_params.py:[LINENR] Test passed (N.N sec)  [test_suite↪test_suite_params.py]  [test.py:[LINENR]]
+---------------------------------------------------------------------
+trial test_suite PASS
+---------------------------------------------------------------------
+PASS: test_suite (pass: 1, skip: 6)
+    skip: hello_world.py
+    skip: mo_mt_sms.py
+    skip: mo_sms.py
+    skip: test_error.py
+    skip: test_fail.py
+    skip: test_fail_raise.py
+    pass: test_suite_params.py (N.N sec)
 
 - graceful exit.
diff --git a/selftest/suite_test/suite_test.py b/selftest/suite_test/suite_test.py
index de5c6df..99671c6 100755
--- a/selftest/suite_test/suite_test.py
+++ b/selftest/suite_test/suite_test.py
@@ -1,12 +1,13 @@
 #!/usr/bin/env python3
 import os
+import sys
 import _prep
 import shutil
 from osmo_gsm_tester.core import log, config, util, report
 from osmo_gsm_tester.core import suite
-from osmo_gsm_tester.core.schema import generate_schemas
+from osmo_gsm_tester.core.schema import generate_schemas, get_all_schema
 
-config.ENV_CONF = './suite_test'
+config.ENV_CONF = os.path.join(os.path.dirname(sys.argv[0]))
 
 example_trial_dir = os.path.join('test_trial_tmp')
 
@@ -90,6 +91,16 @@
 results = s.run_tests('hello_world.py')
 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])
+s.reserve_resources()
+print(repr(s.reserved_resources))
+results = s.run_tests('test_suite_params.py')
+print(report.suite_to_text(s))
+
 print('\n- graceful exit.')
 #deleting generated tmp trial dir:
 shutil.rmtree(example_trial_dir, ignore_errors=True)
diff --git a/selftest/suite_test/test_suite/suite.conf b/selftest/suite_test/test_suite/suite.conf
index 925dedb..4b70be8 100644
--- a/selftest/suite_test/test_suite/suite.conf
+++ b/selftest/suite_test/test_suite/suite.conf
@@ -9,5 +9,11 @@
   modem:
   - times: 2
 
+schema:
+    some_suite_global_param: 'str'
+    test_suite_params:
+      one_bool_parameter: 'bool_str'
+      second_list_parameter: ['uint']
+
 defaults:
   timeout: 60s
diff --git a/selftest/suite_test/test_suite/test_suite_params.py b/selftest/suite_test/test_suite/test_suite_params.py
new file mode 100644
index 0000000..2cb89d7
--- /dev/null
+++ b/selftest/suite_test/test_suite/test_suite_params.py
@@ -0,0 +1,25 @@
+from osmo_gsm_tester.testenv import *
+import pprint
+
+print('starting test')
+
+suite_config = tenv.config_suite_specific()
+print('SPECIFIC SUITE CONFIG: ' + pprint.pformat(suite_config))
+
+test_config = tenv.config_test_specific()
+print('SPECIFIC TEST CONFIG: ' + pprint.pformat(test_config))
+
+some_suite_global_param = suite_config.get('some_suite_global_param', '')
+assert some_suite_global_param == 'heyho'
+
+assert suite_config[tenv.test().module_name()] == test_config
+
+one_bool_parameter = test_config.get('one_bool_parameter', '')
+assert one_bool_parameter == 'true'
+
+second_list_parameter = test_config.get('second_list_parameter', [])
+assert len(second_list_parameter) == 2
+assert int(second_list_parameter[0]) == 23
+assert int(second_list_parameter[1]) == 45
+
+#print('checks successful')
diff --git a/src/osmo_gsm_tester/core/schema.py b/src/osmo_gsm_tester/core/schema.py
index 0b21e70..9055c5b 100644
--- a/src/osmo_gsm_tester/core/schema.py
+++ b/src/osmo_gsm_tester/core/schema.py
@@ -325,6 +325,27 @@
 
     nest(None, config, schema)
 
+def config_to_schema_def(src, key_prefix):
+    'Converts a yaml parsed config into a schema dictionary used by validate()'
+    if util.is_dict(src):
+        out_dict = {}
+        for key, val in src.items():
+            list_token = ''
+            dict_token = ''
+            if util.is_list(val):
+                list_token = '[]'
+                assert len(val) == 1
+                val = val[0]
+            if util.is_dict(val):
+                dict_token = '.'
+            tmp_out = config_to_schema_def(val, "%s%s%s%s" %(key_prefix, key, list_token, dict_token))
+            out_dict = {**out_dict, **tmp_out}
+        return out_dict
+
+    # base case: string
+    return {key_prefix: str(src)}
+
+
 def generate_schemas():
     "Generate supported schemas dynamically from objects"
     obj_dir = '%s/../obj/' % os.path.dirname(os.path.abspath(__file__))
@@ -366,12 +387,13 @@
     """Register schema attributes to configure all instances of an object class.
        For instance: register_resource_schema_attributes('bsc', {'net.codec_list[]': schema.CODEC})
     """
-    global _CONFIG_SCHEMA
+    global _CONFIG_SCHEMA, _ALL_SCHEMA
     tmpdict = {}
     for key, val in obj_attr_dict.items():
         new_key = '%s.%s' % (obj_class_str, key)
         tmpdict[new_key] = val
     combine(_CONFIG_SCHEMA, tmpdict)
+    _ALL_SCHEMA = None # reset _ALL_SCHEMA so it is re-generated next time it's requested.
 
 def get_resources_schema():
     return _RESOURCES_SCHEMA;
diff --git a/src/osmo_gsm_tester/core/suite.py b/src/osmo_gsm_tester/core/suite.py
index 1bd6a63..81aab3e 100644
--- a/src/osmo_gsm_tester/core/suite.py
+++ b/src/osmo_gsm_tester/core/suite.py
@@ -38,8 +38,11 @@
     CONF_FILENAME = 'suite.conf'
 
     def __init__(self, suite_dir):
+        self._suite_name = os.path.basename(suite_dir)
+        super().__init__(log.C_CNF, self._suite_name)
         self.suite_dir = suite_dir
-        super().__init__(log.C_CNF, os.path.basename(self.suite_dir))
+        self.conf = None
+        self._schema = None
         self.read_conf()
 
     def read_conf(self):
@@ -47,8 +50,12 @@
         if not os.path.isdir(self.suite_dir):
             raise RuntimeError('No such directory: %r' % self.suite_dir)
         self.conf = config.read(os.path.join(self.suite_dir,
-                                             SuiteDefinition.CONF_FILENAME),
-                                schema.get_all_schema())
+                                             SuiteDefinition.CONF_FILENAME))
+        # Drop schema part since it's dynamically defining content, makes no sense to validate it.
+        self._schema = self.conf.pop('schema', {})
+        sdef = schema.config_to_schema_def(self._schema, "%s." % self._suite_name)
+        schema.register_config_schema('suite', sdef)
+        schema.validate(self.conf, schema.get_all_schema())
         self.load_test_basenames()
 
     def load_test_basenames(self):
@@ -58,6 +65,7 @@
                 continue
             self.test_basenames.append(basename)
 
+
 class SuiteRun(log.Origin):
     UNKNOWN = 'UNKNOWN'
     PASS = 'PASS'
@@ -79,6 +87,10 @@
         self.status = SuiteRun.UNKNOWN
         self.load_tests()
 
+    def suite_name(self):
+        'Return name of suite without scenarios'
+        return self.definition.name()
+
     def trial(self):
         return self._trial
 
@@ -130,6 +142,9 @@
             self._config = self.combined('config', False)
         return self._config
 
+    def config_suite_specific(self):
+        return self.config().get('suite', {}).get(self.suite_name(), {})
+
     def resource_pool(self):
         return self.resources_pool
 
diff --git a/src/osmo_gsm_tester/core/test.py b/src/osmo_gsm_tester/core/test.py
index 8ab124b..7e03b6c 100644
--- a/src/osmo_gsm_tester/core/test.py
+++ b/src/osmo_gsm_tester/core/test.py
@@ -49,6 +49,11 @@
         self.log_target = None
         self._report_stdout = None
 
+    def module_name(self):
+        'Return test name without trailing .py'
+        assert self.basename.endswith('.py')
+        return self.basename[:-3]
+
     def get_run_dir(self):
         if self._run_dir is None:
             self._run_dir = util.Dir(self.suite_run.get_run_dir().new_dir(self._name))
diff --git a/src/osmo_gsm_tester/testenv.py b/src/osmo_gsm_tester/testenv.py
index 42288aa..789e291 100644
--- a/src/osmo_gsm_tester/testenv.py
+++ b/src/osmo_gsm_tester/testenv.py
@@ -141,6 +141,12 @@
         MainLoop.unregister_poll_func(self.poll)
         self.test_import_modules_cleanup()
 
+    def config_suite_specific(self):
+        return self.suite_run.config_suite_specific()
+
+    def config_test_specific(self):
+        return self.suite_run.config_suite_specific().get(self._test.module_name(), {})
+
     def prompt(self, *msgs, **msg_details):
         'ask for user interaction. Do not use in tests that should run automatically!'
         if msg_details:

-- 
To view, visit https://gerrit.osmocom.org/c/osmo-gsm-tester/+/18088
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-gsm-tester
Gerrit-Branch: master
Gerrit-Change-Id: Idbe99a35993d193cd97059feb980e61ff14c67ad
Gerrit-Change-Number: 18088
Gerrit-PatchSet: 3
Gerrit-Owner: pespin <pespin at sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: pespin <pespin at sysmocom.de>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20200507/004ed7c7/attachment.htm>


More information about the gerrit-log mailing list