<p>pespin <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/18088">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  pespin: Looks good to me, approved
  Jenkins Builder: Verified

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

<div style="display:none"> Gerrit-Project: osmo-gsm-tester </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: Idbe99a35993d193cd97059feb980e61ff14c67ad </div>
<div style="display:none"> Gerrit-Change-Number: 18088 </div>
<div style="display:none"> Gerrit-PatchSet: 3 </div>
<div style="display:none"> Gerrit-Owner: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>
<div style="display:none"> Gerrit-Reviewer: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>