Replicate resources based on times attr before combine time

As suite.conf and scenarios need to match 1-to-1 in lists, it's important to
extend the dictionaries by replicating the objects with a 'times' values
higher than 1 in order to match the objects correctly.

Since dictionanries are expanded at combine time, there's no need to
expand them during reserve() time because they are already expanded.
As a result, this commit reworks the kind of schema applied in each
place (and takes the change to start validating scenario files, which
were neglected previously).

Two unit tests are added as a show case. Unfortunately output showing
scenario dictionaries needs to be ignored while verifying because it was
encountered that different versions of python print dictionary elements
in different order.

Change-Id: I25eb639c7e3cf3b4c67a205422808bffbdd791e6
diff --git a/selftest/suite_test.ok b/selftest/suite_test.ok
index 049f7b4..365fcaa 100644
--- a/selftest/suite_test.ok
+++ b/selftest/suite_test.ok
@@ -16,6 +16,7 @@
 resources:
   bts:
   - times: '1'
+  - times: '2'
   ip_address:
   - times: '1'
   modem:
@@ -30,14 +31,30 @@
 ---------------------------------------------------------------------
 tst test_suite: reserving resources in [PATH]/selftest/suite_test/test_work/state_dir ...
 tst test_suite: DBG: {combining='resources'}
-tst {combining_scenarios='resources'}: DBG: {definition_conf={bts=[{'times': '1'}], ip_address=[{'times': '1'}], modem=[{'times': '2'}]}}  [test_suite↪{combining_scenarios='resources'}]
-tst test_suite: Reserving 1 x bts (candidates: 6)
+tst {combining_scenarios='resources'}: DBG: {definition_conf={bts=[{}, {}, {}], ip_address=[{}], modem=[{}, {}]}}  [test_suite↪{combining_scenarios='resources'}]
+tst test_suite: Reserving 3 x bts (candidates: 6)
 tst test_suite: DBG: Picked - _hash: 07d9c8aaa940b674efcbbabdd69f58a6ce4e94f9
   addr: 10.42.42.114
   band: GSM-1800
   ipa_unit_id: '1'
   label: sysmoBTS 1002
   type: sysmo
+- _hash: 76c8d2f459113cd6c99ed62d1a94bbe9a291ba94
+  addr: 10.42.42.115
+  band: GSM-1800
+  ipa_unit_id: '5'
+  label: octBTS 3000
+  trx_list:
+  - hw_addr: 00:0c:90:32:b5:8a
+  type: oct
+- _hash: 0b7fabd512b36aec43d7d496abd00af4e193b0f8
+  addr: 10.42.42.190
+  band: GSM-1900
+  ipa_unit_id: '1902'
+  label: nanoBTS 1900
+  trx_list:
+  - hw_addr: 00:02:95:00:41:b3
+  type: nanobts
 tst test_suite: Reserving 1 x ip_address (candidates: 3)
 tst test_suite: DBG: Picked - _hash: cde1debf28f07f94f92c761b4b7c6bf35785ced4
   addr: 10.42.42.1
@@ -140,5 +157,151 @@
     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
+- 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 path state_dir as [PATH]/selftest/suite_test/test_work/state_dir  [config.py:[LINENR]]
+
+---------------------------------------------------------------------
+trial test_suite
+---------------------------------------------------------------------
+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=[{}, {}, {}], ip_address=[{}], modem=[{}, {}]}}  [test_suite↪{combining_scenarios='resources'}]  [suite.py:[LINENR]]
+tst {combining_scenarios='resources', scenario='foo'}: [RESOURCE_DICT]
+tst test_suite: Reserving 3 x bts (candidates: 6)  [resource.py:[LINENR]]
+tst test_suite: DBG: Picked - _hash: 07d9c8aaa940b674efcbbabdd69f58a6ce4e94f9
+  addr: 10.42.42.114
+  band: GSM-1800
+  ipa_unit_id: '1'
+  label: sysmoBTS 1002
+  type: sysmo
+- _hash: 76c8d2f459113cd6c99ed62d1a94bbe9a291ba94
+  addr: 10.42.42.115
+  band: GSM-1800
+  ipa_unit_id: '5'
+  label: octBTS 3000
+  trx_list:
+  - hw_addr: 00:0c:90:32:b5:8a
+  type: oct
+- _hash: 0b7fabd512b36aec43d7d496abd00af4e193b0f8
+  addr: 10.42.42.190
+  band: GSM-1900
+  ipa_unit_id: '1902'
+  label: nanoBTS 1900
+  trx_list:
+  - hw_addr: 00:02:95:00:41:b3
+  type: nanobts
+  [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]]
+
+----------------------------------------------
+trial test_suite hello_world.py
+----------------------------------------------
+tst hello_world.py:[LINENR]: hello world  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: I am 'test_suite' / 'hello_world.py:[LINENR]'  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: one  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: two  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: three  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR] Test passed (N.N sec)  [test_suite↪hello_world.py]  [suite.py:[LINENR]]
+---------------------------------------------------------------------
+trial test_suite PASS
+---------------------------------------------------------------------
+PASS: test_suite (pass: 1, skip: 5)
+    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
+- 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 path state_dir as [PATH]/selftest/suite_test/test_work/state_dir  [config.py:[LINENR]]
+
+---------------------------------------------------------------------
+trial test_suite
+---------------------------------------------------------------------
+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=[{}, {}, {}], ip_address=[{}], modem=[{}, {}]}}  [test_suite↪{combining_scenarios='resources'}]  [suite.py:[LINENR]]
+tst {combining_scenarios='resources', scenario='foo'}: [RESOURCE_DICT]
+tst test_suite: Reserving 3 x bts (candidates: 6)  [resource.py:[LINENR]]
+tst test_suite: DBG: Picked - _hash: f1cab48db5b9004986e2030cb71730a5c55e823e
+  addr: 10.42.42.52
+  band: GSM-1800
+  ipa_unit_id: '6'
+  label: Ettus B200
+  launch_trx: 'True'
+  trx_list:
+  - nominal_power: '10'
+  - nominal_power: '12'
+  type: osmo-bts-trx
+- _hash: 1d00bd0d6643db5590cdbefff3152e70500abefc
+  addr: 10.42.42.53
+  band: GSM-1800
+  ipa_unit_id: '7'
+  label: sysmoCell 5000
+  trx_list:
+  - nominal_power: '10'
+  - nominal_power: '12'
+  trx_remote_ip: 10.42.42.112
+  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]]
+
+----------------------------------------------
+trial test_suite hello_world.py
+----------------------------------------------
+tst hello_world.py:[LINENR]: hello world  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: I am 'test_suite' / 'hello_world.py:[LINENR]'  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: one  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: two  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: three  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR] Test passed (N.N sec)  [test_suite↪hello_world.py]  [suite.py:[LINENR]]
+---------------------------------------------------------------------
+trial test_suite PASS
+---------------------------------------------------------------------
+PASS: test_suite (pass: 1, skip: 5)
+    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
 
 - graceful exit.