baldertest 0.1.0b14__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
_balder/_version.py CHANGED
@@ -1,7 +1,14 @@
1
1
  # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
3
 
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
5
12
 
6
13
  TYPE_CHECKING = False
7
14
  if TYPE_CHECKING:
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
9
16
  from typing import Union
10
17
 
11
18
  VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
12
20
  else:
13
21
  VERSION_TUPLE = object
22
+ COMMIT_ID = object
14
23
 
15
24
  version: str
16
25
  __version__: str
17
26
  __version_tuple__: VERSION_TUPLE
18
27
  version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
19
30
 
20
- __version__ = version = '0.1.0b14'
21
- __version_tuple__ = version_tuple = (0, 1, 0, 'b14')
31
+ __version__ = version = '0.1.2'
32
+ __version_tuple__ = version_tuple = (0, 1, 2)
33
+
34
+ __commit_id__ = commit_id = None
@@ -62,9 +62,10 @@ class ExecutorTree(BasicExecutableExecutor):
62
62
  self.update_inner_feature_reference_in_all_setups()
63
63
 
64
64
  def _body_execution(self, show_discarded):
65
- for cur_setup_executor in self.get_setup_executors():
65
+ for cur_setup_executor in self.get_setup_executors(return_discarded=show_discarded):
66
66
  prev_mark = cur_setup_executor.prev_mark
67
- if cur_setup_executor.has_runnable_tests(show_discarded) or cur_setup_executor.has_skipped_tests():
67
+ if cur_setup_executor.has_runnable_tests(consider_discarded_too=show_discarded) \
68
+ or cur_setup_executor.has_skipped_tests():
68
69
  cur_setup_executor.execute(show_discarded=show_discarded)
69
70
  elif prev_mark == PreviousExecutorMark.SKIP:
70
71
  cur_setup_executor.set_result_for_whole_branch(ResultState.SKIP)
@@ -78,17 +79,30 @@ class ExecutorTree(BasicExecutableExecutor):
78
79
 
79
80
  # ---------------------------------- METHODS -----------------------------------------------------------------------
80
81
 
81
- def get_setup_executors(self) -> List[SetupExecutor]:
82
- """returns all setup executors of this tree"""
83
- return self._setup_executors
82
+ def get_setup_executors(self, return_discarded=False) -> List[SetupExecutor]:
83
+ """
84
+ returns all setup executors of this tree
84
85
 
85
- def get_all_scenario_executors(self) -> List[ScenarioExecutor]:
86
+ :param return_discarded: True if the method should return discarded variations too
87
+
88
+ :return: a list of relevant :class:`SetupExecutor`
89
+ """
90
+ if return_discarded:
91
+ return self._setup_executors
92
+
93
+ return [
94
+ cur_setup_executor
95
+ for cur_setup_executor in self._setup_executors
96
+ if len(cur_setup_executor.get_scenario_executors(return_discarded=False)) > 0
97
+ ]
98
+
99
+ def get_all_scenario_executors(self, return_discarded=False) -> List[ScenarioExecutor]:
86
100
  """
87
101
  returns a list with all scenario executors
88
102
  """
89
103
  all_scenario_executor = []
90
- for cur_setup_executor in self.get_setup_executors():
91
- all_scenario_executor += cur_setup_executor.get_scenario_executors()
104
+ for cur_setup_executor in self.get_setup_executors(return_discarded=return_discarded):
105
+ all_scenario_executor += cur_setup_executor.get_scenario_executors(return_discarded=return_discarded)
92
106
  return all_scenario_executor
93
107
 
94
108
  def get_all_variation_executors(self, return_discarded=False) -> List[VariationExecutor]:
@@ -96,7 +110,7 @@ class ExecutorTree(BasicExecutableExecutor):
96
110
  returns a list with all variation executors
97
111
  """
98
112
  all_variation_executor = []
99
- for cur_scenario_executor in self.get_all_scenario_executors():
113
+ for cur_scenario_executor in self.get_all_scenario_executors(return_discarded=return_discarded):
100
114
  all_variation_executor += cur_scenario_executor.get_variation_executors(return_discarded=return_discarded)
101
115
  return all_variation_executor
102
116
 
@@ -136,9 +150,9 @@ class ExecutorTree(BasicExecutableExecutor):
136
150
 
137
151
  def cleanup_empty_executor_branches(self, consider_discarded=False):
138
152
  to_remove_executor = []
139
- for cur_setup_executor in self.get_setup_executors():
153
+ for cur_setup_executor in self.get_setup_executors(return_discarded=consider_discarded):
140
154
  cur_setup_executor.cleanup_empty_executor_branches(consider_discarded=consider_discarded)
141
- if len(cur_setup_executor.get_scenario_executors()) == 0:
155
+ if len(cur_setup_executor.get_scenario_executors(return_discarded=consider_discarded)) == 0:
142
156
  # remove this whole executor because it has no children anymore
143
157
  to_remove_executor.append(cur_setup_executor)
144
158
  for cur_setup_executor in to_remove_executor:
@@ -169,7 +183,7 @@ class ExecutorTree(BasicExecutableExecutor):
169
183
  print_line(start_text)
170
184
  # check if there exists runnable elements
171
185
  runnables = [cur_exec.has_runnable_tests(consider_discarded_too=show_discarded)
172
- for cur_exec in self.get_setup_executors()]
186
+ for cur_exec in self.get_setup_executors(return_discarded=show_discarded)]
173
187
  one_or_more_runnable_setups = None if len(runnables) == 0 else max(runnables)
174
188
  if one_or_more_runnable_setups:
175
189
  super().execute(show_discarded=show_discarded)
@@ -189,8 +203,8 @@ class ExecutorTree(BasicExecutableExecutor):
189
203
  def print_tree(self, show_discarded=False) -> None:
190
204
  """this method is an auxiliary method which outputs the entire tree"""
191
205
  print("RESOLVING OVERVIEW", end="\n\n")
192
- for cur_setup_executor in self.get_setup_executors():
193
- for cur_scenario_executor in cur_setup_executor.get_scenario_executors():
206
+ for cur_setup_executor in self.get_setup_executors(return_discarded=show_discarded):
207
+ for cur_scenario_executor in cur_setup_executor.get_scenario_executors(return_discarded=show_discarded):
194
208
  for cur_variation_executor in cur_scenario_executor.get_variation_executors(
195
209
  return_discarded=show_discarded):
196
210
  applicable = cur_variation_executor.prev_mark != PreviousExecutorMark.DISCARDED
@@ -99,7 +99,8 @@ class ScenarioExecutor(BasicExecutableExecutor):
99
99
  def _body_execution(self, show_discarded):
100
100
  for cur_variation_executor in self.get_variation_executors(return_discarded=show_discarded):
101
101
  prev_mark = cur_variation_executor.prev_mark
102
- if cur_variation_executor.has_runnable_tests() or cur_variation_executor.has_skipped_tests():
102
+ if cur_variation_executor.has_runnable_tests(consider_discarded_too=show_discarded) \
103
+ or cur_variation_executor.has_skipped_tests():
103
104
  cur_variation_executor.execute(show_discarded=show_discarded)
104
105
  elif prev_mark == PreviousExecutorMark.SKIP:
105
106
  cur_variation_executor.set_result_for_whole_branch(ResultState.SKIP)
@@ -79,9 +79,10 @@ class SetupExecutor(BasicExecutableExecutor):
79
79
  print(f"SETUP {self.base_setup_class.__class__.__name__}")
80
80
 
81
81
  def _body_execution(self, show_discarded):
82
- for cur_scenario_executor in self.get_scenario_executors():
82
+ for cur_scenario_executor in self.get_scenario_executors(return_discarded=show_discarded):
83
83
  prev_mark = cur_scenario_executor.prev_mark
84
- if cur_scenario_executor.has_runnable_tests(show_discarded) or cur_scenario_executor.has_skipped_tests():
84
+ if cur_scenario_executor.has_runnable_tests(consider_discarded_too=show_discarded) \
85
+ or cur_scenario_executor.has_skipped_tests():
85
86
  cur_scenario_executor.execute(show_discarded=show_discarded)
86
87
  elif prev_mark == PreviousExecutorMark.SKIP:
87
88
  cur_scenario_executor.set_result_for_whole_branch(ResultState.SKIP)
@@ -123,13 +124,26 @@ class SetupExecutor(BasicExecutableExecutor):
123
124
  # object
124
125
  setattr(cur_feature, cur_ref_feature_name, replacing_candidate)
125
126
 
126
- def get_scenario_executors(self) -> List[ScenarioExecutor]:
127
- """returns a list with all scenario executors that belongs to this setup executor"""
128
- return self._scenario_executors
127
+ def get_scenario_executors(self, return_discarded=False) -> List[ScenarioExecutor]:
128
+ """
129
+ returns a list with all scenario executors that belongs to this setup executor
130
+
131
+ :param return_discarded: True if the method should return discarded variations too
132
+
133
+ :return: a list of relevant :class:`ScenarioExecutor`
134
+ """
135
+ if return_discarded:
136
+ return self._scenario_executors
137
+
138
+ return [
139
+ cur_scenario_executor
140
+ for cur_scenario_executor in self._scenario_executors
141
+ if len(cur_scenario_executor.get_variation_executors(return_discarded=False)) > 0
142
+ ]
129
143
 
130
144
  def cleanup_empty_executor_branches(self, consider_discarded=False):
131
145
  to_remove_executor = []
132
- for cur_scenario_executor in self.get_scenario_executors():
146
+ for cur_scenario_executor in self.get_scenario_executors(return_discarded=consider_discarded):
133
147
  cur_scenario_executor.cleanup_empty_executor_branches(consider_discarded=consider_discarded)
134
148
  if len(cur_scenario_executor.get_variation_executors(return_discarded=consider_discarded)) == 0:
135
149
  # remove this whole executor because it has no children anymore
@@ -191,6 +191,7 @@ class VariationExecutor(BasicExecutableExecutor):
191
191
  # do nothing if this variation can not be applied (is discarded)
192
192
  return
193
193
 
194
+ self.restore_vdevice_feature_instances()
194
195
  self.restore_original_vdevice_references()
195
196
  self.revert_active_vdevice_device_mappings_in_all_features()
196
197
  self.revert_scenario_device_feature_instances()
@@ -614,6 +615,8 @@ class VariationExecutor(BasicExecutableExecutor):
614
615
  def restore_original_vdevice_references(self):
615
616
  """
616
617
  This method restores all previously exchanged :class:`VDevice` references to the original ones.
618
+
619
+ This method is the cleanup method for :meth:`VariationExecutor.exchange_unmapped_vdevice_references`.
617
620
  """
618
621
  all_devices = NormalScenarioSetupController.get_for(
619
622
  self.cur_scenario_class.__class__).get_all_abs_inner_device_classes()
@@ -633,10 +636,10 @@ class VariationExecutor(BasicExecutableExecutor):
633
636
  objects, will be replaced with the related feature instances of the mapped device object.
634
637
 
635
638
  .. note::
636
- Note that this method expects that the true defined scenario features are already replaced with the real
637
- setup features. In addition to that, the method expects, that the vDevice-Device mapping of every feature
638
- was set to the resolved setup device! So the method requires that the method
639
- `update_scenario_device_feature_instances()` was called before.
639
+ Note that this method expects that the defined scenario features instances are already replaced with the
640
+ current active setup features. In addition to that, the method expects, that the vDevice-Device mapping of
641
+ every feature was set to the resolved setup device! This means, the method
642
+ `update_scenario_device_feature_instances()` needs to be called before.
640
643
  """
641
644
  for scenario_device, _ in self._base_device_mapping.items():
642
645
  # these features are subclasses of the real defined one (because they are already the replaced ones)
@@ -662,6 +665,31 @@ class VariationExecutor(BasicExecutableExecutor):
662
665
  # object
663
666
  setattr(cur_vdevice, cur_vdevice_attr_name, replacing_candidate)
664
667
 
668
+ def restore_vdevice_feature_instances(self):
669
+ """
670
+ This method restores all previously exchanged :class:`VDevice` feature references to the original ones.
671
+
672
+ This method is the cleanup method for :meth:`VariationExecutor.update_vdevice_referenced_feature_instances`.
673
+
674
+ .. note::
675
+ Note that this method expects that the defined scenario features are still replaced with the active
676
+ setup features. In addition to that, the method expects, that the vDevice-Device mapping of every feature
677
+ is still set!
678
+
679
+ """
680
+ for scenario_device, _ in self._base_device_mapping.items():
681
+ # these features are subclasses of the real defined one (because they are already the replaced ones)
682
+ all_device_features = DeviceController.get_for(scenario_device).get_all_instantiated_feature_objects()
683
+ for _, cur_feature in all_device_features.items():
684
+ # now get the related vDevice class and update its attributes
685
+ cur_vdevice, cur_device = cur_feature.active_vdevice_device_mapping
686
+ if cur_vdevice is not None and cur_device is not None:
687
+ cur_vdevice_controller = VDeviceController.get_for(cur_vdevice)
688
+ original_features_of_cur_vdev = cur_vdevice_controller.get_original_instanced_feature_objects()
689
+
690
+ for cur_vdev_attr_name, cur_vdev_orig_feature in original_features_of_cur_vdev.items():
691
+ setattr(cur_vdevice, cur_vdev_attr_name, cur_vdev_orig_feature)
692
+
665
693
  def verify_applicability(self) -> None:
666
694
  """
667
695
  This method verifies if this variation is executable. First the method checks if all defined
_balder/solver.py CHANGED
@@ -219,9 +219,6 @@ class Solver:
219
219
  variation_executor = VariationExecutor(device_mapping=cur_device_mapping, parent=scenario_executor)
220
220
  variation_executor.verify_applicability()
221
221
 
222
- if not variation_executor.can_be_applied():
223
- continue
224
-
225
222
  scenario_executor.add_variation_executor(variation_executor)
226
223
 
227
224
  for cur_testcase in scenario_executor.base_scenario_controller.get_all_test_methods():
@@ -0,0 +1,360 @@
1
+ Metadata-Version: 2.4
2
+ Name: baldertest
3
+ Version: 0.1.2
4
+ Summary: balder: reusable scenario based test framework
5
+ Home-page: https://docs.balder.dev
6
+ Author: Max Stahlschmidt and others
7
+ License: MIT
8
+ Project-URL: Source, https://github.com/balder-dev/balder
9
+ Project-URL: Tracker, https://github.com/balder-dev/balder/issues
10
+ Keywords: test,systemtest,reusable,scenario
11
+ Platform: unix
12
+ Platform: linux
13
+ Platform: osx
14
+ Platform: cygwin
15
+ Platform: win32
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: MacOS :: MacOS X
18
+ Classifier: Operating System :: Microsoft :: Windows
19
+ Classifier: Operating System :: POSIX
20
+ Classifier: Intended Audience :: Developers
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Programming Language :: Python :: 3.9
24
+ Classifier: Programming Language :: Python :: 3.10
25
+ Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Programming Language :: Python :: 3.12
27
+ Classifier: Programming Language :: Python :: 3.13
28
+ Classifier: Programming Language :: Python :: 3.14
29
+ Classifier: Topic :: Software Development :: Libraries
30
+ Classifier: Topic :: Software Development :: Testing
31
+ Classifier: Topic :: Utilities
32
+ Requires-Python: >=3.9
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Dynamic: license-file
36
+
37
+
38
+ <div align="center">
39
+ <img style="margin: 20px;max-width: 68%" src="https://docs.balder.dev/en/latest/_static/balder_w_boarder.png" alt="Balder logo">
40
+ </div>
41
+
42
+ Balder is a flexible Python test system that allows you to reuse test code written once for different but similar
43
+ platforms, devices, or applications. It enables you to install ready-to-use test cases and provides various test
44
+ development features that help you test your software or devices much faster.
45
+
46
+ You can use shared test code by installing an [existing BalderHub project](https://hub.balder.dev), or you can create
47
+ your own. This makes test development for your project much faster, since it is oftentimes enough to install a BalderHub
48
+ project and only provide the user-specific code.
49
+
50
+ Be part of the progress and share your tests with others, your company, or the whole world.
51
+
52
+ # Installation
53
+
54
+ You can install the latest release with pip:
55
+
56
+ ```
57
+ python -m pip install baldertest
58
+ ```
59
+
60
+ # Run Balder
61
+
62
+ After you've installed it, you can run Balder inside a Balder environment with the following command:
63
+
64
+ ```
65
+ balder
66
+ ```
67
+
68
+ You can also provide a specific path to the balder environment directory by using this console argument:
69
+
70
+ ```
71
+ balder --working-dir /path/to/working/dir
72
+ ```
73
+
74
+ # How does it work?
75
+
76
+ Balder allows you to reuse previously written test code by dividing it into the components that **are needed** for a
77
+ test (`Scenario`) and the components that **you have** (`Setup`).
78
+
79
+ You can define a test within a method of a `Scenario` class. This is often an abstract layer, where you only describe
80
+ the general business logic without providing any specific implementation details.
81
+
82
+ These specific implementation details are provided in the `Setup` classes. They describe exactly **what you have**. In
83
+ these classes, you provide an implementation for the abstract elements that were defined earlier in the `Scenario`.
84
+
85
+ Balder then automatically searches for matching mappings and runs your tests using them.
86
+
87
+ ## Define the `Scenario` class
88
+
89
+ Inside `Scenario` or `Setup` classes, you can describe the environment using inner `Device` classes. For example, let's
90
+ write a test that validates the functionality of a lamp. For that, keep in mind that we want to make this test as
91
+ flexible as possible. It should be able to run with all kind of things that have a lamp:
92
+
93
+ ```python
94
+ import balder
95
+ from lib.scenario_features import BaseLightFeature
96
+
97
+
98
+ class ScenarioLight(balder.Scenario):
99
+
100
+ # The device with its features that are required for this test
101
+ class LightSpendingDevice(balder.Device):
102
+ light = BaseLightFeature()
103
+
104
+ def test_check_light(self):
105
+ self.LightSpendingDevice.light.switch_on()
106
+ assert self.LightSpendingDevice.light.light_is_on()
107
+ self.LightSpendingDevice.light.switch_off()
108
+ assert not self.LightSpendingDevice.light.light_is_on()
109
+
110
+
111
+
112
+ ```
113
+
114
+ Here, we have defined that a `LightSpendingDevice` **needs to have** a feature called `BaseLightFeature` so that this
115
+ scenario can be executed.
116
+
117
+ We have also added a test case (named with a `test_*()` prefix) called `test_check_light`, which executes the validation
118
+ of a lamp, by switching it on and off and checking its state.
119
+
120
+ **Note:** The `BaseLightFeature` is an abstract Feature class that defines the abstract methods `switch_on()`,
121
+ `switch_off()`, and `light_is_on()`.
122
+
123
+
124
+ ## Define the `Setup` class
125
+
126
+ The next step is defining a `Setup` class, which describes what we have. For a `Scenario` to match a `Setup`, the
127
+ features of all scenario devices must be implemented by the mapped setup devices.
128
+
129
+ For example, if we want to test a car that includes a lamp, we could have a setup like the one shown below:
130
+
131
+ ```python
132
+ import balder
133
+ from lib.setup_features import CarEngineFeature, CarLightFeature
134
+
135
+
136
+ class SetupGarage(balder.Setup):
137
+
138
+ class Car(balder.Device):
139
+ car_engine = CarEngineFeature()
140
+ car_light = CarLightFeature() # subclass of `lib.scenario_feature.BaseLightFeature`
141
+ ...
142
+
143
+
144
+ ```
145
+
146
+ When you run Balder in this environment, it will collect the `ScenarioLight` and the `SetupMyCar` classes and try to
147
+ find mappings between them. Based on the `ScenarioLight`, Balder looks for a device that provides an implementation of
148
+ the single `BaseLightFeature`. To do this, it scans all available setups. Since the `SetupMyCar.Car` device provides an
149
+ implementation through the `CarLightFeature`, this device will match.
150
+
151
+ ```shell
152
+ +----------------------------------------------------------------------------------------------------------------------+
153
+ | BALDER Testsystem |
154
+ | python version 3.10.12 (main, Aug 15 2025, 14:32:43) [GCC 11.4.0] | balder version 0.1.0b14 |
155
+ +----------------------------------------------------------------------------------------------------------------------+
156
+ Collect 1 Setups and 1 Scenarios
157
+ resolve them to 1 valid variations
158
+
159
+ ================================================== START TESTSESSION ===================================================
160
+ SETUP SetupGarage
161
+ SCENARIO ScenarioLight
162
+ VARIATION ScenarioLight.LightSpendingDevice:SetupGarage.Car
163
+ TEST ScenarioLight.test_check_light [.]
164
+ ================================================== FINISH TESTSESSION ==================================================
165
+ TOTAL NOT_RUN: 0 | TOTAL FAILURE: 0 | TOTAL ERROR: 0 | TOTAL SUCCESS: 1 | TOTAL SKIP: 0 | TOTAL COVERED_BY: 0
166
+ ```
167
+
168
+ ## Add another Device to the `Setup` class
169
+
170
+ Now the big advantage of Balder comes into play. We can run our test with all devices that can implement the
171
+ `BaseLightFeature`, independent of how this will be implemented in detail. **You do not need to rewrite the test**.
172
+
173
+ So, We have more devices in our garage. So let's add them:
174
+
175
+ ```python
176
+ import balder
177
+ from lib.setup_features import CarEngineFeature, CarLightFeature, PedalFeature, BicycleLightFeature, GateOpenerFeature
178
+
179
+
180
+ class SetupGarage(balder.Setup):
181
+
182
+ class Car(balder.Device):
183
+ car_engine = CarEngineFeature()
184
+ car_light = CarLightFeature() # subclass of `lib.scenario_feature.BaseLightFeature`
185
+ ...
186
+
187
+ class Bicycle(balder.Device):
188
+ pedals = PedalFeature()
189
+ light = BicycleLightFeature() # another subclass of `lib.scenario_feature.BaseLightFeature`
190
+
191
+ class GarageGate(balder.Device):
192
+ opener = GateOpenerFeature()
193
+
194
+ ```
195
+
196
+ If we run Balder now, it will find more mappings because the `Bicycle` device also provides an implementation for the
197
+ `BaseLightFeature` we are looking for.
198
+
199
+ ```shell
200
+ +----------------------------------------------------------------------------------------------------------------------+
201
+ | BALDER Testsystem |
202
+ | python version 3.10.12 (main, Aug 15 2025, 14:32:43) [GCC 11.4.0] | balder version 0.1.0b14 |
203
+ +----------------------------------------------------------------------------------------------------------------------+
204
+ Collect 1 Setups and 1 Scenarios
205
+ resolve them to 2 valid variations
206
+
207
+ ================================================== START TESTSESSION ===================================================
208
+ SETUP SetupGarage
209
+ SCENARIO ScenarioLight
210
+ VARIATION ScenarioLight.LightSpendingDevice:SetupGarage.Bicycle
211
+ TEST ScenarioLight.test_check_light [.]
212
+ VARIATION ScenarioLight.LightSpendingDevice:SetupGarage.Car
213
+ TEST ScenarioLight.test_check_light [.]
214
+ ================================================== FINISH TESTSESSION ==================================================
215
+ TOTAL NOT_RUN: 0 | TOTAL FAILURE: 0 | TOTAL ERROR: 0 | TOTAL SUCCESS: 2 | TOTAL SKIP: 0 | TOTAL COVERED_BY: 0
216
+ ```
217
+
218
+ Balder handles all of this for you. You only need to describe your environment by defining `Scenario` and `Setup`
219
+ classes, then provide the specific implementations by creating the features. Balder will automatically search for and
220
+ apply the mappings between them.
221
+
222
+ **NOTE:** Balder offers many more elements to design complete device structures, including connections between multiple
223
+ devices.
224
+
225
+ You can learn more about that in the
226
+ [Tutorial Section of the Documentation](https://docs.balder.dev/en/latest/tutorial_guide/index.html).
227
+
228
+
229
+ # Example: Use an installable BalderHub package
230
+
231
+ With Balder, you can create custom test environments or install open-source-available test packages, known as
232
+ [BalderHub packages](https://hub.balder.dev). For example, if you want to test the login functionality of a website, simply use the
233
+ ready-to-use scenario `ScenarioSimpleLogin` from the [`balderhub-auth` package](https://hub.balder.dev/projects/auth/en/latest/examples.html),
234
+
235
+
236
+ We want to use [Selenium](https://www.selenium.dev/) to control the browser and of course use html elements, so let's install
237
+ `balderhub-selenium` and `balderhub-html` right away.
238
+
239
+ ```
240
+ $ pip install balderhub-auth balderhub-selenium balderhub-html
241
+ ```
242
+
243
+ So as mentioned, you don't need to define a scenario and a test yourself; you can simply import it:
244
+
245
+ ```python
246
+ # file `scenario_balderhub.py`
247
+
248
+ from balderhub.auth.scenarios import ScenarioSimpleLogin
249
+
250
+ ```
251
+
252
+ According to the [documentation of this BalderHub project](https://hub.balder.dev/projects/auth/en/latest/examples.html),
253
+ we only need to define the login page by overwriting the ``LoginPage`` feature:
254
+
255
+ ```python
256
+
257
+ # file `lib/pages.py`
258
+
259
+ import balderhub.auth.contrib.html.pages
260
+ from balderhub.html.lib.utils import Selector
261
+ from balderhub.url.lib.utils import Url
262
+ import balderhub.html.lib.utils.components as html
263
+
264
+
265
+ class LoginPage(balderhub.auth.contrib.html.pages.LoginPage):
266
+
267
+ url = Url('https://example.com')
268
+
269
+ # Overwrite abstract property
270
+ @property
271
+ def input_username(self):
272
+ return html.inputs.HtmlTextInput.by_selector(self.driver, Selector.by_name('user'))
273
+
274
+ # Overwrite abstract property
275
+ @property
276
+ def input_password(self):
277
+ return html.inputs.HtmlPasswordInput.by_selector(self.driver, Selector.by_name('user'))
278
+
279
+ # Overwrite abstract property
280
+ @property
281
+ def btn_login(self):
282
+ return html.HtmlButtonElement.by_selector(self.driver, Selector.by_id('submit-button'))
283
+
284
+ ```
285
+
286
+ And use it in our setup:
287
+
288
+ ```python
289
+
290
+
291
+ # file `setups/setup_office.py`
292
+
293
+ import balder
294
+ import balderhub.auth.lib.scenario_features.role
295
+ from balderhub.selenium.lib.setup_features import SeleniumChromeWebdriverFeature
296
+
297
+ from lib.pages import LoginPage
298
+
299
+ class UserConfig(balderhub.auth.lib.scenario_features.role.UserRoleFeature):
300
+ # provide the credentials for the log in
301
+ username = 'admin'
302
+ password = 'secret'
303
+
304
+ class SetupOffice(balder.Setup):
305
+
306
+ class Server(balder.Device):
307
+ user = UserConfig()
308
+
309
+ class Browser(balder.Device):
310
+ selenium = SeleniumChromeWebdriverFeature()
311
+ page_login = LoginPage()
312
+
313
+ # fixture to prepare selenium - will be executed before the test session runs
314
+ @balder.fixture('session')
315
+ def selenium(self):
316
+ self.Browser.selenium.create()
317
+ yield
318
+ self.Browser.selenium.quit()
319
+ ```
320
+
321
+ When you run Balder now, it will execute a complete login test that you didn't write yourself -
322
+ **it was created by the open-source community**.
323
+
324
+ ```shell
325
+ +----------------------------------------------------------------------------------------------------------------------+
326
+ | BALDER Testsystem |
327
+ | python version 3.10.12 (main, Aug 15 2025, 14:32:43) [GCC 11.4.0] | balder version 0.1.0b14 |
328
+ +----------------------------------------------------------------------------------------------------------------------+
329
+ Collect 1 Setups and 1 Scenarios
330
+ resolve them to 1 valid variations
331
+
332
+ ================================================== START TESTSESSION ===================================================
333
+ SETUP SetupOffice
334
+ SCENARIO ScenarioSimpleLogin
335
+ VARIATION ScenarioSimpleLogin.Client:SetupOffice.Browser | ScenarioSimpleLogin.System:SetupOffice.Server
336
+ TEST ScenarioSimpleLogin.test_login [.]
337
+ ================================================== FINISH TESTSESSION ==================================================
338
+ TOTAL NOT_RUN: 0 | TOTAL FAILURE: 0 | TOTAL ERROR: 0 | TOTAL SUCCESS: 1 | TOTAL SKIP: 0 | TOTAL COVERED_BY: 0
339
+ ```
340
+
341
+ If you'd like to learn more about it, feel free to dive [into the documentation](https://balder.dev).
342
+
343
+ # Contribution guidelines
344
+
345
+ Any help is appreciated. If you want to contribute to balder, take a look into the
346
+ [contribution guidelines](https://github.com/balder-dev/balder/blob/main/CONTRIBUTING.md).
347
+
348
+ Are you an expert in your field? Do you enjoy the concept of balder? How about creating your own
349
+ BalderHub project? You can contribute to an existing project or create your own. If you are not sure, a project for
350
+ your idea already exists or if you want to discuss your ideas with others, feel free to
351
+ [create an issue in the BalderHub main entry project](https://github.com/balder-dev/hub.balder.dev/issues) or
352
+ [start a new discussion](https://github.com/balder-dev/hub.balder.dev/discussions).
353
+
354
+ # License
355
+
356
+ Balder is free and Open-Source
357
+
358
+ Copyright (c) 2022-2025 Max Stahlschmidt and others
359
+
360
+ Distributed under the terms of the MIT license
@@ -1,5 +1,5 @@
1
1
  _balder/__init__.py,sha256=Qk4wkVInPlXLFV36Yco5K7PDawJoeeWQVakzj6g5pmA,195
2
- _balder/_version.py,sha256=g5k4tbPdhrhEa3D2X-tMb0NvEt7IbfLQzEQ3Vp3Tq9w,521
2
+ _balder/_version.py,sha256=Ok5oAXdWgR9aghaFXTafTeDW6sYO3uVe6d2Nket57R4,704
3
3
  _balder/balder_plugin.py,sha256=EQzJP1dwwVDydhMLJtAmTCXOczlDuXBJur05lalmK_k,3136
4
4
  _balder/balder_session.py,sha256=ezT86gC_VzPQZOQ4r5qQ75IEm6rXZHiIpEqZDczkRsE,16149
5
5
  _balder/balder_settings.py,sha256=U96PVep7dGSaTXrMfeZMYf6oCIcEDPEqrBlFcoX476s,582
@@ -31,7 +31,7 @@ _balder/previous_executor_mark.py,sha256=gwpGu7d-kwPzQT8CmaPfuEG6fess2Upf5Q-zX6O
31
31
  _balder/routing_path.py,sha256=icIp09sC51sBsNHZPrWzGOtLqrOT5Se_dj0Db7MwtwY,17093
32
32
  _balder/scenario.py,sha256=beHkEqb9pnhrMOt1qfgATSBWVfHahw9RsOCT-uxK6TE,954
33
33
  _balder/setup.py,sha256=sIyuqhDSCzxtkWXW48jRrLDvGWkZwgpL6NvEKrFM65E,890
34
- _balder/solver.py,sha256=NbpAdvrGWdJTY6eZmNZFr7YDubyggY0yW64rDB3JkT0,13121
34
+ _balder/solver.py,sha256=eB7pf9224AJAbeF4HlMDHMMy06mmq3C-LiyX4NBlphw,13031
35
35
  _balder/testresult.py,sha256=AE_LWCwmR9aR36IHfPARaaqossBahv-P2URCZ8cFAvM,6893
36
36
  _balder/unmapped_vdevice.py,sha256=oKr01YVTLViWtZkYz8kx8ccTx-KmwgNrHuQqqD4eLQw,513
37
37
  _balder/vdevice.py,sha256=fc2xuMnTuN1RyfWh9mqFgLdSO9yGA75eERobTXUQ9JA,215
@@ -53,13 +53,13 @@ _balder/controllers/vdevice_controller.py,sha256=rWK3oHzXbNlgXf0xbWyssDevx0qQ7Bm
53
53
  _balder/executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  _balder/executor/basic_executable_executor.py,sha256=RWwc8c4B7qO2BxlUB8dZH9IC64qm_KWh8tGCoBB8v6o,5395
55
55
  _balder/executor/basic_executor.py,sha256=6rLSBrTGodlaRVfHV2kpXPjbAqUPEq9-2txCxz_-fXg,9327
56
- _balder/executor/executor_tree.py,sha256=CfwUDUAgCb_k_haaqX7GC23DB2LLEpkL-NnSCEdufiA,10659
56
+ _balder/executor/executor_tree.py,sha256=ZyZYRyeOjvZEW_BRds9Ujfa3TrNxa_ouAGRsLNoSVwk,11457
57
57
  _balder/executor/parametrized_testcase_executor.py,sha256=Wk4XitAVOMGU7pSn7AOyvOsuL9CorcqFJ-iRuVtat1M,2149
58
- _balder/executor/scenario_executor.py,sha256=tfQ1tKvmhAUgUkAcgJ94hI46hLcXAhIMD-d9qCmJiXg,8152
59
- _balder/executor/setup_executor.py,sha256=QWMWhfzsssRT_fHOUKjw4gHfC2CaZjhL5NxF1eNl35w,8604
58
+ _balder/executor/scenario_executor.py,sha256=NMx4hJpY5Xtsd4ugrQTdX4bcczgi_aabtlqHIkMfXl8,8211
59
+ _balder/executor/setup_executor.py,sha256=-nLTH0enq7IbWMlXPZF_sY5Mt78aDesMkFpco6qU8uM,9167
60
60
  _balder/executor/testcase_executor.py,sha256=Yrifk_8hsm1KKrE6udd84EyBywmWiUN00MlcezMQ7xA,8216
61
61
  _balder/executor/unresolved_parametrized_testcase_executor.py,sha256=Pb-LhEFNspb03pYTqTyFvSWPFMoRCjO8OVjjP2rHFMw,7628
62
- _balder/executor/variation_executor.py,sha256=2VLXmPmRVFi69bkqVkHaz055l5OEPMNer1qSKD3SN9M,51515
62
+ _balder/executor/variation_executor.py,sha256=wdWOIuXqkxsl-wifuEFWYaH_hLuvaYjvotdVPMM2g8I,53252
63
63
  _balder/objects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
64
  _balder/objects/connections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
65
  _balder/objects/connections/osi_1_physical.py,sha256=74lKWJd6ETEtvNXH0_dmTbkZlStJ_af218pQkUht0aA,2189
@@ -81,9 +81,9 @@ balder/connections.py,sha256=H6rf7UsiVY_FeZLngZXCT9WDw9cQqpiDiPbz_0J4yjM,2331
81
81
  balder/devices.py,sha256=zupHtz8yaiEjzR8CrvgZU-RzsDQcZFeN5mObfhtjwSw,173
82
82
  balder/exceptions.py,sha256=iaR4P2L7K3LggYSDnjCGLheZEaGgnMilxDQdoYD5KHQ,1954
83
83
  balder/parametrization.py,sha256=R8U67f6DEnXdDc9cGOgS8yFTEAfhglv1v9mnAUAExUg,150
84
- baldertest-0.1.0b14.dist-info/licenses/LICENSE,sha256=Daz9qTpqbiq-klWb2Q9lYOmn3rJ5oIQnbs62sGcqOZ4,1084
85
- baldertest-0.1.0b14.dist-info/METADATA,sha256=4jKylhMf05-alO4ThYvff56kssjzuh6dWMcobpMAUpE,15806
86
- baldertest-0.1.0b14.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
87
- baldertest-0.1.0b14.dist-info/entry_points.txt,sha256=hzqu_nrMKTCi5IJqzS1fhIXWEiL7mTGZ-kgj2lUYlRU,65
88
- baldertest-0.1.0b14.dist-info/top_level.txt,sha256=RUkIBkNLqHMemx2C9aEpoS65dpqb6_jU-oagIPxGQEA,15
89
- baldertest-0.1.0b14.dist-info/RECORD,,
84
+ baldertest-0.1.2.dist-info/licenses/LICENSE,sha256=kNB2-kpqR9c3rEyXsMaQiqb1jiLZ5j6R6TSda63dcJY,1089
85
+ baldertest-0.1.2.dist-info/METADATA,sha256=OI7I2-tt0pTFpfxF95nOODxncv8y0XAXv61JWuJV0Eo,14730
86
+ baldertest-0.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
87
+ baldertest-0.1.2.dist-info/entry_points.txt,sha256=hzqu_nrMKTCi5IJqzS1fhIXWEiL7mTGZ-kgj2lUYlRU,65
88
+ baldertest-0.1.2.dist-info/top_level.txt,sha256=RUkIBkNLqHMemx2C9aEpoS65dpqb6_jU-oagIPxGQEA,15
89
+ baldertest-0.1.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 Max Stahlschmidt and others
3
+ Copyright (c) 2022-2025 Max Stahlschmidt and others
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,389 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: baldertest
3
- Version: 0.1.0b14
4
- Summary: balder: reusable scenario based test framework
5
- Home-page: https://docs.balder.dev
6
- Author: Max Stahlschmidt and others
7
- License: MIT
8
- Project-URL: Source, https://github.com/balder-dev/balder
9
- Project-URL: Tracker, https://github.com/balder-dev/balder/issues
10
- Keywords: test,systemtest,reusable,scenario
11
- Platform: unix
12
- Platform: linux
13
- Platform: osx
14
- Platform: cygwin
15
- Platform: win32
16
- Classifier: License :: OSI Approved :: MIT License
17
- Classifier: Operating System :: MacOS :: MacOS X
18
- Classifier: Operating System :: Microsoft :: Windows
19
- Classifier: Operating System :: POSIX
20
- Classifier: Intended Audience :: Developers
21
- Classifier: Programming Language :: Python :: 3
22
- Classifier: Programming Language :: Python :: 3 :: Only
23
- Classifier: Programming Language :: Python :: 3.9
24
- Classifier: Programming Language :: Python :: 3.10
25
- Classifier: Topic :: Software Development :: Libraries
26
- Classifier: Topic :: Software Development :: Testing
27
- Classifier: Topic :: Utilities
28
- Requires-Python: >=3.9
29
- Description-Content-Type: text/markdown
30
- License-File: LICENSE
31
- Dynamic: license-file
32
-
33
-
34
- <div align="center">
35
- <img style="margin: 20px;max-width: 68%" src="https://docs.balder.dev/en/latest/_static/balder_w_boarder.png" alt="Balder logo">
36
- </div>
37
-
38
- Balder is a very powerful, universal and flexible python test system that allows you to reuse a once written testcode as
39
- efficiently as possible for different but similar platforms/devices/applications. Balder's goal is
40
- being a platform for combining the single steps of defining, developing and documenting the entire test
41
- process while using test scenarios which can be reused across different projects.
42
-
43
- You can share your own testcode by creating a new BalderHub project, or you use an
44
- [existing BalderHub project](https://hub.balder.dev), by simply installing and using it. This makes the test development
45
- for your project much faster, since it is often times enough to only provide the user-specific code.
46
-
47
- Be part of the progress and share your tests with others, your company or the whole world.
48
-
49
- # Installation
50
-
51
- You can install the latest release with pip:
52
-
53
- ```
54
- python -m pip install baldertest
55
- ```
56
-
57
- # Run Balder
58
-
59
- After you've installed it, you can run Balder inside a Balder environment with the following command:
60
-
61
- ```
62
- balder
63
- ```
64
-
65
- You can also provide a specific path to the balder environment directory by using this console argument:
66
-
67
- ```
68
- balder --working-dir /path/to/working/dir
69
- ```
70
-
71
- # How does it work?
72
-
73
- Balder allows you to reuse previously written test code by dividing it into the components that **are needed** for a
74
- test (`Scenario`) and the components that **we have** (`Setup`).
75
-
76
- `Scenario` classes define a test. Only describe the most important aspects **you need** for the execution of the
77
- corresponding test (method of the scenario class) inside `Scenario` classes. Often it is enough to define abstract
78
- methods in the scenario-level features.
79
-
80
- In contrast, `Setup` classes describe exactly what **you have**. This is where you define all the devices and their
81
- features. Balder will then automatically search for mappings and run your test with them.
82
-
83
- ## Define the `Scenario` class
84
-
85
- Inside `Scenario` or `Setup` classes, inner `Device` classes describe your environment. For example, if you want to test
86
- the process of sending a message between two devices, you can create a `Scenario` like shown below:
87
-
88
- ```python
89
- import balder
90
- from .features import SendMessageFeature, RecvMessageFeature
91
-
92
-
93
- class ScenarioMessaging(balder.Scenario):
94
-
95
- class Sender(balder.Device):
96
- send = SendMessageFeature()
97
-
98
- @balder.connect(Sender, over_connection=balder.Connection())
99
- class Receiver(balder.Device):
100
- recv = RecvMessageFeature()
101
-
102
-
103
-
104
- ```
105
-
106
- You have now defined, that the `Sender` device must be able to send messages (has `SendMessageFeature()`), while
107
- the `Receiver` device must be able to receive messages (has `RecvMessageFeature()`). Both devices are connected with
108
- each other. For this we use the general connection `balder.Connection()`, which allows every type of connection.
109
-
110
- You can implement your test, by adding a new method that starts with `test_*()`:
111
-
112
- ```python
113
- import balder
114
- from .features import SendMessageFeature, RecvMessageFeature
115
-
116
-
117
- class ScenarioMessaging(balder.Scenario):
118
-
119
- class Sender(balder.Device):
120
- send = SendMessageFeature()
121
-
122
- @balder.connect(Sender, over_connection=balder.Connection())
123
- class Receiver(balder.Device):
124
- recv = RecvMessageFeature()
125
-
126
- def test_send_msg(self):
127
- MESSAGE_TXT = "Hello World"
128
- self.Sender.send.send_msg(MESSAGE_TXT)
129
- received_msg = self.Receiver.recv.get_last_received_msg()
130
- assert received_msg == MESSAGE_TXT
131
-
132
- ```
133
-
134
- ## Define the `Setup` class
135
-
136
- The next step is defining a `Setup` class, which describes what we have. For a `Scenario` to match a `Setup`, all of
137
- your scenario-devices must be able to map to a sub selection of some setup-devices.
138
-
139
- For example, if you want to verify if your DUT is able to receive messages from your computer (both are connected over
140
- USB), just create a `Setup` class with the both devices `Computer` and `Dut` and add an implementation (subclasses) of
141
- our previously defined feature classes `SendMessageFeature` and `RecvMessageFeature` to them:
142
-
143
- ```python
144
- import balder
145
- from balder import connections as cnns
146
- from .setup_features import SendUsbMessageFeature, RecvUsbMessageFeature
147
-
148
-
149
- class SetupOverUsb(balder.Setup):
150
-
151
- class Computer(balder.Device):
152
- # non-abstract subclass of `SendMessageFeature`
153
- send = SendUsbMessageFeature()
154
-
155
- @balder.connect(Computer, over_connection=cnns.UsbConnection())
156
- class Dut(balder.Device):
157
- # non-abstract subclass of `RecvMessageFeature`
158
- recv = RecvUsbMessageFeature()
159
-
160
- ```
161
-
162
- With the features `SendUsbMessageFeature` and `RecvUsbMessageFeature`, both devices hold an implementation of our
163
- previous scenario-level features `SendMessageFeature` and `RecvMessageFeature`. They are the child classes of our
164
- scenario-level features and hold the full implementation for sending/receiving data over USB.
165
-
166
- As soon as you run Balder, Balder will automatically detect that our scenario `ScenarioMessaging` can be mapped to the
167
- `SetupOverUsb`. This will cause Balder to run the test `test_send_msg()` with the implemented setup-level version of
168
- the features.
169
-
170
- The big advantage of Balder is the reusability. If you want to test if the communication also works in the other
171
- direction, just add the features inverted:
172
-
173
- ```python
174
- import balder
175
- from balder import connections as cnns
176
- from .setup_features import SendUsbMessageFeature, RecvUsbMessageFeature
177
-
178
-
179
- class SetupOverUsb(balder.Setup):
180
-
181
- class Computer(balder.Device):
182
- # non-abstract subclass of `SendMessageFeature`
183
- send = SendUsbMessageFeature()
184
- # non-abstract subclass of `RecvMessageFeature`
185
- recv = RecvUsbMessageFeature()
186
-
187
- @balder.connect(Computer, over_connection=cnns.UsbConnection())
188
- class Dut(balder.Device):
189
- # non-abstract subclass of `SendMessageFeature`
190
- send = SendUsbMessageFeature()
191
- # non-abstract subclass of `RecvMessageFeature`
192
- recv = RecvUsbMessageFeature()
193
-
194
- ```
195
-
196
- Balder will now run the test once with the `Computer` being the sender and once with the `Dut` being the Sender, even
197
- though you didn't implement anything new.
198
-
199
- | | `ScenarioMessaging.Sender` | `ScenarioMessaging.Sender` |
200
- |-------------|----------------------------|----------------------------|
201
- | VARIATION 1 | `SetupOverUsb.Computer` | `SetupOverUsb.Dut` |
202
- | VARIATION 2 | `SetupOverUsb.Dut` | `SetupOverUsb.Computer` |
203
-
204
- Do you have another device, that should be tested too? Just add it to your setup:
205
-
206
- ```python
207
- import balder
208
- from balder import connections as cnns
209
- from .setup_features import SendUsbMessageFeature, RecvUsbMessageFeature
210
-
211
-
212
- class SetupOverUsb(balder.Setup):
213
-
214
- class Computer(balder.Device):
215
- # non-abstract subclass of `SendMessageFeature`
216
- send = SendUsbMessageFeature()
217
- # non-abstract subclass of `RecvMessageFeature`
218
- recv = RecvUsbMessageFeature()
219
-
220
- @balder.connect(Computer, over_connection=cnns.UsbConnection())
221
- class Dut(balder.Device):
222
- # non-abstract subclass of `SendMessageFeature`
223
- send = SendUsbMessageFeature()
224
- # non-abstract subclass of `RecvMessageFeature`
225
- recv = RecvUsbMessageFeature()
226
-
227
- @balder.connect(Computer, over_connection=cnns.UsbConnection())
228
- class AnotherDut(balder.Device):
229
- # non-abstract subclass of `RecvMessageFeature`
230
- recv = RecvUsbMessageFeature()
231
-
232
- ```
233
-
234
- Now balder will run our scenario once each with the following mappings:
235
-
236
- | | `ScenarioMessaging.Sender` | `ScenarioMessaging.Sender` |
237
- |-------------|----------------------------|----------------------------|
238
- | VARIATION 1 | `SetupOverUsb.Computer` | `SetupOverUsb.Dut` |
239
- | VARIATION 2 | `SetupOverUsb.Dut` | `SetupOverUsb.Computer` |
240
- | VARIATION 3 | `SetupOverUsb.Computer` | `SetupOverUsb.AnotherDut` |
241
-
242
- If you want to test a `Dut` device that does not use USB for communication, you can also add other feature
243
- implementations of ``SendMessageFeature`` and ``RecvMessageFeature`` in your setup devices. For this we just add a new
244
- setup:
245
-
246
- ```python
247
- import balder
248
- from balder import connections as cnns
249
- from .setup_features import SendUsbMessageFeature, RecvUsbMessageFeature, SendBluetoothMessageFeature, RecvBluetoothMessageFeature
250
-
251
-
252
- class SetupOverUsb(balder.Setup):
253
-
254
- ...
255
-
256
- class SetupOverBluetooth(balder.Setup):
257
- class Computer(balder.Device):
258
- # non-abstract subclass of `SendMessageFeature`
259
- send = SendBluetoothMessageFeature()
260
-
261
- @balder.connect(Computer, over_connection=cnns.BluetoothConnection)
262
- class Dut(balder.Device):
263
- # non-abstract subclass of `RecvMessageFeature`
264
- recv = RecvBluetoothMessageFeature()
265
-
266
- ```
267
-
268
- If you now execute Balder, it will run the scenario with all possible device constellations of the `SetupOverUsb` and
269
- the `SetupOverBluetooth`.
270
-
271
- | | `ScenarioMessaging.Sender` | `ScenarioMessaging.Sender` |
272
- |-------------|---------------------------------|----------------------------|
273
- | VARIATION 1 | `SetupOverUsb.Computer` | `SetupOverUsb.Dut` |
274
- | VARIATION 2 | `SetupOverUsb.Dut` | `SetupOverUsb.Computer` |
275
- | VARIATION 3 | `SetupOverUsb.Computer` | `SetupOverUsb.AnotherDut` |
276
- | VARIATION 4 | `SetupOverBluetooth.Computer` | `SetupOverBluetooth.Dut` |
277
-
278
- NOTE: *You could also add all of these devices in a shared setup and use one common feature for both protocols, but for this you would need to use VDevices. You can read more about this in [the documentation section about VDevices](https://docs.balder.dev/en/latest/basics/vdevices.html)*
279
-
280
-
281
- # Example: Use an installable BalderHub package
282
-
283
- With Balder you can create custom test environments or install open source available test packages, so called
284
- [BalderHub packages](https://hub.balder.dev). If you want to test a SNMP client device for example, you can use the
285
- [package balderhub-snmpagent](https://github.com/balder-dev/balderhub-snmpagent). Just install it with:
286
-
287
- ```
288
- $ pip install balderhub-snmpagent
289
- ```
290
-
291
- You only need to provide two things: The configuration of your DUT as subclass of `SnmpSystemConfig` and your
292
- environment (the `Setup` class):
293
-
294
- ```python
295
- # file `features.py`
296
- from balderhub.snmpagent.lib import features
297
-
298
- class MySnmpSystemConfig(features.SnmpSystemConfig):
299
-
300
- host = "192.168.178.28"
301
- sys_descr = "my fancy sysDescr"
302
- sys_object_id = "1.3.6.1.4.1.1234.2.3.9.1"
303
- read_community = "public"
304
- write_community = "public"
305
-
306
- ```
307
-
308
- ```python
309
- # file `setup_example.py`
310
- import balder
311
- from balderhub.snmpagent.lib.connections import SnmpConnection
312
- from balderhub.snmpagent.lib.features import HasSnmpSystemGroupFeature
313
- from balderhub.snmpagent.lib.setup_features import SendSnmpGetRequestPysnmpFeature, SendSnmpSetRequestPysnmpFeature
314
- from . import features as setup_features
315
-
316
-
317
- class SetupPrinter(balder.Setup):
318
-
319
- class Printer(balder.Device):
320
- _snmp_sys = HasSnmpSystemGroupFeature()
321
- config = setup_features.SnmpSystemConfig()
322
-
323
- @balder.connect(Printer, over_connection=SnmpConnection())
324
- class HostPc(balder.Device):
325
- get_request_snmp = SendSnmpGetRequestPysnmpFeature()
326
- set_request_snmp = SendSnmpSetRequestPysnmpFeature()
327
-
328
- ```
329
-
330
- Call Balder in your project:
331
-
332
- ```
333
- $ balder
334
- ```
335
-
336
- And all existing and matching tests in [balderhub-snmpagent](https://github.com/balder-dev/balderhub-snmpagent) will
337
- then be executed for you:
338
-
339
- ```
340
- +----------------------------------------------------------------------------------------------------------------------+
341
- | BALDER Testsystem |
342
- | python version 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] | balder version 0.1.0b6 |
343
- +----------------------------------------------------------------------------------------------------------------------+
344
- Collect 1 Setups and 3 Scenarios
345
- resolve them to 3 mapping candidates
346
-
347
- ================================================== START TESTSESSION ===================================================
348
- SETUP SetupPrinter
349
- SCENARIO ScenarioMibSysDescr
350
- VARIATION ScenarioMibSysDescr.SnmpAgent:SetupPrinter.Printer | ScenarioMibSysDescr.SnmpManager:SetupPrinter.HostPc
351
- TEST ScenarioMibSysDescr.test_get_sys_descr [.]
352
- TEST ScenarioMibSysDescr.test_get_sys_descr_ascii_check [.]
353
- TEST ScenarioMibSysDescr.test_set_sys_descr [.]
354
- SCENARIO ScenarioMibSysObjectId
355
- VARIATION ScenarioMibSysObjectId.SnmpAgent:SetupPrinter.Printer | ScenarioMibSysObjectId.SnmpManager:SetupPrinter.HostPc
356
- TEST ScenarioMibSysObjectId.test_get_sys_object_id [.]
357
- TEST ScenarioMibSysObjectId.test_set_sys_object_id [.]
358
- SCENARIO ScenarioMibSysUpTime
359
- VARIATION ScenarioMibSysUpTime.SnmpAgent:SetupPrinter.Printer | ScenarioMibSysUpTime.SnmpManager:SetupPrinter.HostPc
360
- TEST ScenarioMibSysUpTime.test_get_sys_up_time [.]
361
- TEST ScenarioMibSysUpTime.test_get_sys_up_time_changed_check [.]
362
- TEST ScenarioMibSysUpTime.test_set_sys_up_time [.]
363
- ================================================== FINISH TESTSESSION ==================================================
364
- TOTAL NOT_RUN: 0 | TOTAL FAILURE: 0 | TOTAL ERROR: 0 | TOTAL SUCCESS: 8 | TOTAL SKIP: 0 | TOTAL COVERED_BY: 0
365
- ```
366
-
367
-
368
- # Contribution guidelines
369
-
370
- Any help is appreciated. If you want to contribute to balder, take a look into the
371
- [contribution guidelines](https://github.com/balder-dev/balder/blob/main/CONTRIBUTING.md).
372
-
373
- Balder is still in its early steps. Unfortunately, this also means that we don't have a broad variety of
374
- [BalderHub projects](https://hub.balder.dev) at the moment.
375
-
376
- Are you an expert in your field? Do you enjoy the concept of balder? How about you create your own
377
- BalderHub project? Take a look into our [Balder GitHub Group](https://github.com/balder-dev) and feel free to share
378
- your ideas. You can contribute to an existing project or create your own. If you are not sure, a project for your idea
379
- already exists or if you want to discuss your ideas with others, feel free to
380
- [create an issue in the BalderHub main entry project](https://github.com/balder-dev/hub.balder.dev/issues) or
381
- [start a new discussion](https://github.com/balder-dev/hub.balder.dev/discussions).
382
-
383
- # License
384
-
385
- Balder is free and Open-Source
386
-
387
- Copyright (c) 2022 Max Stahlschmidt and others
388
-
389
- Distributed under the terms of the MIT license