Marco Ricci commited on 2025-08-31 21:09:13
Zeige 2 geänderte Dateien mit 437 Einfügungen und 198 Löschungen.
(This is part 10 of a series of refactorings for the test suite.) In the heavy-duty command-line tests, factor out common test setups and common hypothesis strategies. In particular, share the hypothesis strategies and the skeletal procedure for both affected state machines because they offer more or less the same transition rules. For the hypothesis test machinery, fix some types and some import aliases that affect the heavy-duty command-line tests.
| ... | ... |
@@ -22,8 +22,7 @@ from typing import TYPE_CHECKING |
| 22 | 22 |
import hypothesis |
| 23 | 23 |
from hypothesis import strategies |
| 24 | 24 |
|
| 25 |
-import tests.data |
|
| 26 |
-import tests.machinery |
|
| 25 |
+from tests import data, machinery |
|
| 27 | 26 |
from derivepassphrase import _types |
| 28 | 27 |
|
| 29 | 28 |
__all__ = () |
| ... | ... |
@@ -53,9 +52,7 @@ def get_concurrency_step_count( |
| 53 | 52 |
""" |
| 54 | 53 |
if settings is None: # pragma: no cover |
| 55 | 54 |
settings = hypothesis.settings() |
| 56 |
- return min( |
|
| 57 |
- tests.machinery.get_concurrency_limit(), settings.stateful_step_count |
|
| 58 |
- ) |
|
| 55 |
+ return min(machinery.get_concurrency_limit(), settings.stateful_step_count) |
|
| 59 | 56 |
|
| 60 | 57 |
|
| 61 | 58 |
# Hypothesis strategies |
| ... | ... |
@@ -63,7 +60,9 @@ def get_concurrency_step_count( |
| 63 | 60 |
|
| 64 | 61 |
|
| 65 | 62 |
@strategies.composite |
| 66 |
-def vault_full_service_config(draw: strategies.DrawFn) -> dict[str, int]: |
|
| 63 |
+def vault_full_service_config( |
|
| 64 |
+ draw: strategies.DrawFn, |
|
| 65 |
+) -> _types.VaultConfigServicesSettings: |
|
| 67 | 66 |
"""Hypothesis strategy for full vault service configurations. |
| 68 | 67 |
|
| 69 | 68 |
Returns a sample configuration with restrictions on length, repeat |
| ... | ... |
@@ -106,10 +105,12 @@ def vault_full_service_config(draw: strategies.DrawFn) -> dict[str, int]: |
| 106 | 105 |
@strategies.composite |
| 107 | 106 |
def smudged_vault_test_config( |
| 108 | 107 |
draw: strategies.DrawFn, |
| 109 |
- config: Any = strategies.sampled_from(tests.data.TEST_CONFIGS).filter( # noqa: B008 |
|
| 110 |
- tests.data.VaultTestConfig.is_smudgable |
|
| 108 |
+ config: strategies.SearchStrategy[ |
|
| 109 |
+ data.VaultTestConfig |
|
| 110 |
+ ] = strategies.sampled_from(data.TEST_CONFIGS).filter( # noqa: B008 |
|
| 111 |
+ data.VaultTestConfig.is_smudgable |
|
| 111 | 112 |
), |
| 112 |
-) -> Any: |
|
| 113 |
+) -> data.VaultTestConfig: |
|
| 113 | 114 |
"""Hypothesis strategy to replace falsy values with other falsy values. |
| 114 | 115 |
|
| 115 | 116 |
Uses [`_types.js_truthiness`][] internally, which is tested |
| ... | ... |
@@ -167,6 +168,4 @@ def smudged_vault_test_config( |
| 167 | 168 |
if not _types.js_truthiness(value) and value != 0: |
| 168 | 169 |
service[key] = draw(strategies.sampled_from(falsy_no_zero)) |
| 169 | 170 |
hypothesis.assume(obj != conf.config) |
| 170 |
- return tests.data.VaultTestConfig( |
|
| 171 |
- obj, conf.comment, conf.validation_settings |
|
| 172 |
- ) |
|
| 171 |
+ return data.VaultTestConfig(obj, conf.comment, conf.validation_settings) |
| ... | ... |
@@ -26,7 +26,8 @@ from tests.machinery import pytest as pytest_machinery |
| 26 | 26 |
|
| 27 | 27 |
if TYPE_CHECKING: |
| 28 | 28 |
import multiprocessing |
| 29 |
- from collections.abc import Iterable |
|
| 29 |
+ from collections.abc import Iterable, Sequence |
|
| 30 |
+ from collections.abc import Set as AbstractSet |
|
| 30 | 31 |
|
| 31 | 32 |
from typing_extensions import Literal |
| 32 | 33 |
|
| ... | ... |
@@ -48,9 +49,26 @@ VALID_PROPERTIES = ( |
| 48 | 49 |
"""Known vault properties. Used for the [`ConfigManagementStateMachine`][].""" |
| 49 | 50 |
|
| 50 | 51 |
|
| 52 |
+class Strategies: |
|
| 53 |
+ """Common hypothesis data generation strategies.""" |
|
| 54 |
+ |
|
| 55 |
+ @staticmethod |
|
| 56 |
+ def assemble_config( |
|
| 57 |
+ global_data: _types.VaultConfigGlobalSettings, |
|
| 58 |
+ service_data: Sequence[tuple[str, _types.VaultConfigServicesSettings]], |
|
| 59 |
+ ) -> _types.VaultConfig: |
|
| 60 |
+ """Return a vault config using the global and service data.""" |
|
| 61 |
+ services_dict = dict(service_data) |
|
| 62 |
+ return ( |
|
| 63 |
+ {"global": global_data, "services": services_dict}
|
|
| 64 |
+ if global_data |
|
| 65 |
+ else {"services": services_dict}
|
|
| 66 |
+ ) |
|
| 67 |
+ |
|
| 68 |
+ @staticmethod |
|
| 51 | 69 |
def build_reduced_vault_config_settings( |
| 52 | 70 |
config: _types.VaultConfigServicesSettings, |
| 53 |
- keys_to_prune: frozenset[str], |
|
| 71 |
+ keys_to_prune: AbstractSet[str], |
|
| 54 | 72 |
) -> _types.VaultConfigServicesSettings: |
| 55 | 73 |
"""Return a service settings object with certain keys pruned. |
| 56 | 74 |
|
| ... | ... |
@@ -66,44 +84,48 @@ def build_reduced_vault_config_settings( |
| 66 | 84 |
config2.pop(key, None) # type: ignore[misc] |
| 67 | 85 |
return config2 |
| 68 | 86 |
|
| 87 |
+ # Prefer this explicit composite strategy oven `strategies.builds` |
|
| 88 |
+ # because the type checker can better introspect this. |
|
| 89 |
+ @strategies.composite |
|
| 90 |
+ @staticmethod |
|
| 91 |
+ def services_strategy( |
|
| 92 |
+ draw: strategies.DrawFn, |
|
| 93 |
+ ) -> _types.VaultConfigServicesSettings: |
|
| 94 |
+ """Return a strategy to build incomplete service configurations. |
|
| 69 | 95 |
|
| 70 |
-SERVICES_STRATEGY = strategies.builds( |
|
| 71 |
- build_reduced_vault_config_settings, |
|
| 72 |
- hypothesis_machinery.vault_full_service_config(), |
|
| 96 |
+ Args: |
|
| 97 |
+ draw: |
|
| 98 |
+ The `draw` function, as provided for by hypothesis. |
|
| 99 |
+ |
|
| 100 |
+ Returns: |
|
| 101 |
+ A strategy that generates `vault` service configurations, |
|
| 102 |
+ with some settings left unspecified. |
|
| 103 |
+ |
|
| 104 |
+ """ |
|
| 105 |
+ config = draw( |
|
| 106 |
+ hypothesis_machinery.vault_full_service_config(), label="config" |
|
| 107 |
+ ) |
|
| 108 |
+ keys_to_prune = draw( |
|
| 73 | 109 |
strategies.sets( |
| 74 |
- strategies.sampled_from(VALID_PROPERTIES), |
|
| 75 |
- max_size=7, |
|
| 110 |
+ strategies.sampled_from(VALID_PROPERTIES), max_size=7 |
|
| 76 | 111 |
), |
| 112 |
+ label="keys_to_prune", |
|
| 77 | 113 |
) |
| 78 |
-"""A hypothesis strategy to build incomplete service configurations.""" |
|
| 79 |
- |
|
| 80 |
- |
|
| 81 |
-def services_strategy() -> strategies.SearchStrategy[ |
|
| 82 |
- _types.VaultConfigServicesSettings |
|
| 83 |
-]: |
|
| 84 |
- """Return a strategy to build incomplete service configurations.""" |
|
| 85 |
- return SERVICES_STRATEGY |
|
| 86 |
- |
|
| 87 |
- |
|
| 88 |
-def assemble_config( |
|
| 89 |
- global_data: _types.VaultConfigGlobalSettings, |
|
| 90 |
- service_data: list[tuple[str, _types.VaultConfigServicesSettings]], |
|
| 91 |
-) -> _types.VaultConfig: |
|
| 92 |
- """Return a vault config using the global and service data.""" |
|
| 93 |
- services_dict = dict(service_data) |
|
| 94 |
- return ( |
|
| 95 |
- {"global": global_data, "services": services_dict}
|
|
| 96 |
- if global_data |
|
| 97 |
- else {"services": services_dict}
|
|
| 114 |
+ # The `hypothesis.strategies.composite` decorator cannot handle |
|
| 115 |
+ # bound class or instance methods, so we must code this as |
|
| 116 |
+ # a static method. As such, we cannot access sibling helper |
|
| 117 |
+ # methods via `self` or `cls`, but must go via the class name. |
|
| 118 |
+ return Strategies.build_reduced_vault_config_settings( |
|
| 119 |
+ config, keys_to_prune |
|
| 98 | 120 |
) |
| 99 | 121 |
|
| 100 |
- |
|
| 101 | 122 |
@strategies.composite |
| 102 |
-def draw_service_name_and_data( |
|
| 123 |
+ @staticmethod |
|
| 124 |
+ def service_name_and_data_strategy( |
|
| 103 | 125 |
draw: hypothesis.strategies.DrawFn, |
| 104 | 126 |
num_entries: int, |
| 105 | 127 |
) -> tuple[tuple[str, _types.VaultConfigServicesSettings], ...]: |
| 106 |
- """Draw a service name and settings, as a hypothesis strategy. |
|
| 128 |
+ """Return a strategy for tuples of service names and settings. |
|
| 107 | 129 |
|
| 108 | 130 |
Will draw service names from [`KNOWN_SERVICES`][] and service |
| 109 | 131 |
settings via [`services_strategy`][]. |
| ... | ... |
@@ -115,7 +137,8 @@ def draw_service_name_and_data( |
| 115 | 137 |
The number of services to draw. |
| 116 | 138 |
|
| 117 | 139 |
Returns: |
| 118 |
- A sequence of pairs of service names and service settings. |
|
| 140 |
+ A strategy that generates a sequence of pairs of service |
|
| 141 |
+ names and service settings. |
|
| 119 | 142 |
|
| 120 | 143 |
""" |
| 121 | 144 |
possible_services = list(KNOWN_SERVICES) |
| ... | ... |
@@ -126,24 +149,55 @@ def draw_service_name_and_data( |
| 126 | 149 |
) |
| 127 | 150 |
possible_services.remove(selected_services[-1]) |
| 128 | 151 |
return tuple( |
| 129 |
- (service, draw(services_strategy())) for service in selected_services |
|
| 152 |
+ (service, draw(Strategies.services_strategy())) |
|
| 153 |
+ for service in selected_services |
|
| 130 | 154 |
) |
| 131 | 155 |
|
| 156 |
+ @strategies.composite |
|
| 157 |
+ @staticmethod |
|
| 158 |
+ def vault_full_config(draw: strategies.DrawFn) -> _types.VaultConfig: |
|
| 159 |
+ """Return a strategy to build full vault configurations.""" |
|
| 160 |
+ services = draw(Strategies.services_strategy(), label="services") |
|
| 161 |
+ num_entries = draw( |
|
| 162 |
+ strategies.integers(min_value=2, max_value=4), label="num_entries" |
|
| 163 |
+ ) |
|
| 164 |
+ service_name_and_data = draw( |
|
| 165 |
+ Strategies.service_name_and_data_strategy(num_entries), |
|
| 166 |
+ label="service_name_and_data", |
|
| 167 |
+ ) |
|
| 168 |
+ return Strategies.assemble_config(services, service_name_and_data) |
|
| 132 | 169 |
|
| 133 |
-VAULT_FULL_CONFIG = strategies.builds( |
|
| 134 |
- assemble_config, |
|
| 135 |
- services_strategy(), |
|
| 136 |
- strategies.integers( |
|
| 137 |
- min_value=2, |
|
| 138 |
- max_value=4, |
|
| 139 |
- ).flatmap(draw_service_name_and_data), |
|
| 170 |
+ @staticmethod |
|
| 171 |
+ def maybe_unset_strategy() -> strategies.SearchStrategy[AbstractSet[str]]: |
|
| 172 |
+ """Return a strategy to build sets of names to maybe unset.""" |
|
| 173 |
+ return strategies.sets( |
|
| 174 |
+ strategies.sampled_from(VALID_PROPERTIES), max_size=3 |
|
| 140 | 175 |
) |
| 141 |
-"""A hypothesis strategy to build full vault configurations.""" |
|
| 142 | 176 |
|
| 177 |
+ @staticmethod |
|
| 178 |
+ def service_name_strategy() -> strategies.SearchStrategy[str]: |
|
| 179 |
+ """Return a strategy for service names.""" |
|
| 180 |
+ return strategies.sampled_from(KNOWN_SERVICES) |
|
| 143 | 181 |
|
| 144 |
-def vault_full_config() -> strategies.SearchStrategy[_types.VaultConfig]: |
|
| 145 |
- """Return a strategy to build full vault configurations.""" |
|
| 146 |
- return VAULT_FULL_CONFIG |
|
| 182 |
+ @staticmethod |
|
| 183 |
+ def setting_strategy( |
|
| 184 |
+ setting_bundle: stateful.Bundle[_types.VaultConfigServicesSettings], |
|
| 185 |
+ ) -> strategies.SearchStrategy[_types.VaultConfigServicesSettings]: |
|
| 186 |
+ """Return a strategy for setting objects, given a setting bundle.""" |
|
| 187 |
+ return setting_bundle.filter(bool) |
|
| 188 |
+ |
|
| 189 |
+ @strategies.composite |
|
| 190 |
+ @staticmethod |
|
| 191 |
+ def config_and_service_strategy( |
|
| 192 |
+ draw: strategies.DrawFn, |
|
| 193 |
+ configuration: stateful.Bundle[_types.VaultConfig], |
|
| 194 |
+ ) -> tuple[_types.VaultConfig, str]: |
|
| 195 |
+ """Return a strategy for a vault config and a service name tuple.""" |
|
| 196 |
+ config_strategy = configuration.filter(lambda c: bool(c["services"])) |
|
| 197 |
+ config = draw(config_strategy, label="config") |
|
| 198 |
+ keys = tuple(config["services"].keys()) |
|
| 199 |
+ key = draw(strategies.sampled_from(keys), label="key") |
|
| 200 |
+ return (config, key) |
|
| 147 | 201 |
|
| 148 | 202 |
|
| 149 | 203 |
class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| ... | ... |
@@ -188,7 +242,7 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| 188 | 242 |
@stateful.initialize( |
| 189 | 243 |
target=configuration, |
| 190 | 244 |
configs=strategies.lists( |
| 191 |
- vault_full_config(), |
|
| 245 |
+ Strategies.vault_full_config(), |
|
| 192 | 246 |
min_size=8, |
| 193 | 247 |
max_size=8, |
| 194 | 248 |
), |
| ... | ... |
@@ -203,7 +257,7 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| 203 | 257 |
@stateful.initialize( |
| 204 | 258 |
target=setting, |
| 205 | 259 |
configs=strategies.lists( |
| 206 |
- vault_full_config(), |
|
| 260 |
+ Strategies.vault_full_config(), |
|
| 207 | 261 |
min_size=4, |
| 208 | 262 |
max_size=4, |
| 209 | 263 |
), |
| ... | ... |
@@ -233,25 +287,62 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| 233 | 287 |
"services": {**c2["services"], **c1["services"]},
|
| 234 | 288 |
} |
| 235 | 289 |
|
| 236 |
- @stateful.rule( |
|
| 237 |
- target=configuration, |
|
| 238 |
- config=configuration, |
|
| 239 |
- setting=setting.filter(bool), |
|
| 240 |
- maybe_unset=strategies.sets( |
|
| 241 |
- strategies.sampled_from(VALID_PROPERTIES), |
|
| 242 |
- max_size=3, |
|
| 243 |
- ), |
|
| 244 |
- overwrite=strategies.booleans(), |
|
| 290 |
+ def call_cli( |
|
| 291 |
+ self, |
|
| 292 |
+ command_line: list[str], |
|
| 293 |
+ expected_config: _types.VaultConfig, |
|
| 294 |
+ /, |
|
| 295 |
+ *, |
|
| 296 |
+ input: str | bytes | None = None, |
|
| 297 |
+ overwrite: bool = False, |
|
| 298 |
+ ) -> _types.VaultConfig: |
|
| 299 |
+ """Call the command-line interface for config manipulation. |
|
| 300 |
+ |
|
| 301 |
+ Args: |
|
| 302 |
+ command_line: |
|
| 303 |
+ The command-line to execute via |
|
| 304 |
+ a [`machinery.CliRunner`][]. |
|
| 305 |
+ |
|
| 306 |
+ The `--overwrite-existing`/`--merge-existing` options |
|
| 307 |
+ should be managed via the `overwrite` option instead of |
|
| 308 |
+ being explicitly specified. |
|
| 309 |
+ expected_config: |
|
| 310 |
+ The expected configuration after calling this |
|
| 311 |
+ command-line. |
|
| 312 |
+ |
|
| 313 |
+ Raises: |
|
| 314 |
+ AssertionError: |
|
| 315 |
+ The command exited with an error status. |
|
| 316 |
+ |
|
| 317 |
+ *Or*, the actual resulting configuration does not match |
|
| 318 |
+ the expected configuration. |
|
| 319 |
+ |
|
| 320 |
+ """ |
|
| 321 |
+ overwriting = ( |
|
| 322 |
+ ["--overwrite-existing"] if overwrite else ["--merge-existing"] |
|
| 245 | 323 |
) |
| 246 |
- def set_globals( |
|
| 324 |
+ result = self.runner.invoke( |
|
| 325 |
+ cli.derivepassphrase_vault, |
|
| 326 |
+ [*overwriting, *command_line], |
|
| 327 |
+ input=input, |
|
| 328 |
+ catch_exceptions=False, |
|
| 329 |
+ ) |
|
| 330 |
+ assert result.clean_exit(empty_stderr=False) |
|
| 331 |
+ assert cli_helpers.load_config() == expected_config |
|
| 332 |
+ return expected_config |
|
| 333 |
+ |
|
| 334 |
+ def set_globals_expected_result( |
|
| 247 | 335 |
self, |
| 248 | 336 |
config: _types.VaultConfig, |
| 249 | 337 |
setting: _types.VaultConfigGlobalSettings, |
| 250 |
- maybe_unset: set[str], |
|
| 338 |
+ maybe_unset: AbstractSet[str], |
|
| 251 | 339 |
overwrite: bool, |
| 252 |
- ) -> _types.VaultConfig: |
|
| 340 |
+ ) -> tuple[_types.VaultConfig, AbstractSet[str]]: |
|
| 253 | 341 |
"""Set the global settings of a configuration. |
| 254 | 342 |
|
| 343 |
+ This is the "calculate the correct result" section of the |
|
| 344 |
+ `set_globals` rule. |
|
| 345 |
+ |
|
| 255 | 346 |
Args: |
| 256 | 347 |
config: |
| 257 | 348 |
The configuration to edit. |
| ... | ... |
@@ -267,10 +358,12 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| 267 | 358 |
`--merge-existing` command-line arguments. |
| 268 | 359 |
|
| 269 | 360 |
Returns: |
| 270 |
- The amended configuration. |
|
| 361 |
+ A 2-tuple containing the amended configuration, then the |
|
| 362 |
+ settings keys that were actually unset. |
|
| 271 | 363 |
|
| 272 | 364 |
""" |
| 273 |
- cli_helpers.save_config(config) |
|
| 365 |
+ config = copy.deepcopy(config) |
|
| 366 |
+ setting = copy.deepcopy(setting) |
|
| 274 | 367 |
config_global = config.get("global", {})
|
| 275 | 368 |
maybe_unset = set(maybe_unset) - setting.keys() |
| 276 | 369 |
if overwrite: |
| ... | ... |
@@ -280,36 +373,116 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| 280 | 373 |
config_global.pop(key, None) # type: ignore[misc] |
| 281 | 374 |
config.setdefault("global", {}).update(setting)
|
| 282 | 375 |
assert _types.is_vault_config(config) |
| 283 |
- # NOTE: This relies on settings_obj containing only the keys |
|
| 376 |
+ return config, maybe_unset |
|
| 377 |
+ |
|
| 378 |
+ @stateful.rule( |
|
| 379 |
+ target=configuration, |
|
| 380 |
+ config=configuration, |
|
| 381 |
+ setting=Strategies.setting_strategy(setting), |
|
| 382 |
+ maybe_unset=Strategies.maybe_unset_strategy(), |
|
| 383 |
+ overwrite=strategies.booleans(), |
|
| 384 |
+ ) |
|
| 385 |
+ def set_globals( |
|
| 386 |
+ self, |
|
| 387 |
+ config: _types.VaultConfig, |
|
| 388 |
+ setting: _types.VaultConfigGlobalSettings, |
|
| 389 |
+ maybe_unset: AbstractSet[str], |
|
| 390 |
+ overwrite: bool, |
|
| 391 |
+ ) -> _types.VaultConfig: |
|
| 392 |
+ """Set the global settings of a configuration. |
|
| 393 |
+ |
|
| 394 |
+ Args: |
|
| 395 |
+ config: |
|
| 396 |
+ The configuration to edit. |
|
| 397 |
+ setting: |
|
| 398 |
+ The new global settings. |
|
| 399 |
+ maybe_unset: |
|
| 400 |
+ Settings keys to additionally unset, if not already |
|
| 401 |
+ present in the new settings. Corresponds to the |
|
| 402 |
+ `--unset` command-line argument. |
|
| 403 |
+ overwrite: |
|
| 404 |
+ Overwrite the settings object if true, or merge if |
|
| 405 |
+ false. Corresponds to the `--overwrite-existing` and |
|
| 406 |
+ `--merge-existing` command-line arguments. |
|
| 407 |
+ |
|
| 408 |
+ Returns: |
|
| 409 |
+ The amended configuration. |
|
| 410 |
+ |
|
| 411 |
+ """ |
|
| 412 |
+ cli_helpers.save_config(config) |
|
| 413 |
+ expected_config, maybe_unset = self.set_globals_expected_result( |
|
| 414 |
+ config=config, |
|
| 415 |
+ setting=setting, |
|
| 416 |
+ maybe_unset=maybe_unset, |
|
| 417 |
+ overwrite=overwrite, |
|
| 418 |
+ ) |
|
| 419 |
+ # NOTE: This relies on `settings` containing only the keys |
|
| 284 | 420 |
# "length", "repeat", "upper", "lower", "number", "space", |
| 285 | 421 |
# "dash" and "symbol". |
| 286 |
- result = self.runner.invoke( |
|
| 287 |
- cli.derivepassphrase_vault, |
|
| 288 |
- [ |
|
| 289 |
- "--config", |
|
| 290 |
- "--overwrite-existing" if overwrite else "--merge-existing", |
|
| 291 |
- ] |
|
| 292 |
- + [f"--unset={key}" for key in maybe_unset]
|
|
| 293 |
- + [ |
|
| 422 |
+ unset_commands = [f"--unset={key}" for key in maybe_unset]
|
|
| 423 |
+ set_commands = [ |
|
| 294 | 424 |
f"--{key}={value}"
|
| 295 | 425 |
for key, value in setting.items() |
| 296 | 426 |
if key in VALID_PROPERTIES |
| 297 |
- ], |
|
| 298 |
- catch_exceptions=False, |
|
| 427 |
+ ] |
|
| 428 |
+ return self.call_cli( |
|
| 429 |
+ ["--config", *unset_commands, *set_commands], |
|
| 430 |
+ expected_config, |
|
| 431 |
+ overwrite=overwrite, |
|
| 299 | 432 |
) |
| 300 |
- assert result.clean_exit(empty_stderr=False) |
|
| 301 |
- assert cli_helpers.load_config() == config |
|
| 302 |
- return config |
|
| 433 |
+ |
|
| 434 |
+ def set_service_expected_result( |
|
| 435 |
+ self, |
|
| 436 |
+ service: str, |
|
| 437 |
+ config: _types.VaultConfig, |
|
| 438 |
+ setting: _types.VaultConfigGlobalSettings, |
|
| 439 |
+ maybe_unset: AbstractSet[str], |
|
| 440 |
+ overwrite: bool, |
|
| 441 |
+ ) -> tuple[_types.VaultConfig, AbstractSet[str]]: |
|
| 442 |
+ """Set the named service settings for a configuration. |
|
| 443 |
+ |
|
| 444 |
+ This is the "calculate the correct result" section of the |
|
| 445 |
+ `set_service` rule. |
|
| 446 |
+ |
|
| 447 |
+ Args: |
|
| 448 |
+ config: |
|
| 449 |
+ The configuration to edit. |
|
| 450 |
+ service: |
|
| 451 |
+ The name of the service to set. |
|
| 452 |
+ setting: |
|
| 453 |
+ The new service settings. |
|
| 454 |
+ maybe_unset: |
|
| 455 |
+ Settings keys to additionally unset, if not already |
|
| 456 |
+ present in the new settings. Corresponds to the |
|
| 457 |
+ `--unset` command-line argument. |
|
| 458 |
+ overwrite: |
|
| 459 |
+ Overwrite the settings object if true, or merge if |
|
| 460 |
+ false. Corresponds to the `--overwrite-existing` and |
|
| 461 |
+ `--merge-existing` command-line arguments. |
|
| 462 |
+ |
|
| 463 |
+ Returns: |
|
| 464 |
+ A 2-tuple containing the amended configuration, then the |
|
| 465 |
+ settings keys that were actually unset. |
|
| 466 |
+ |
|
| 467 |
+ """ |
|
| 468 |
+ config = copy.deepcopy(config) |
|
| 469 |
+ config_service = config["services"].get(service, {})
|
|
| 470 |
+ maybe_unset = set(maybe_unset) - setting.keys() |
|
| 471 |
+ if overwrite: |
|
| 472 |
+ config["services"][service] = config_service = {}
|
|
| 473 |
+ elif maybe_unset: |
|
| 474 |
+ for key in maybe_unset: |
|
| 475 |
+ config_service.pop(key, None) # type: ignore[misc] |
|
| 476 |
+ config["services"].setdefault(service, {}).update(setting)
|
|
| 477 |
+ assert _types.is_vault_config(config) |
|
| 478 |
+ return config, maybe_unset |
|
| 303 | 479 |
|
| 304 | 480 |
@stateful.rule( |
| 305 | 481 |
target=configuration, |
| 306 | 482 |
config=configuration, |
| 307 |
- service=strategies.sampled_from(KNOWN_SERVICES), |
|
| 308 |
- setting=setting.filter(bool), |
|
| 309 |
- maybe_unset=strategies.sets( |
|
| 310 |
- strategies.sampled_from(VALID_PROPERTIES), |
|
| 311 |
- max_size=3, |
|
| 312 |
- ), |
|
| 483 |
+ service=Strategies.service_name_strategy(), |
|
| 484 |
+ setting=Strategies.setting_strategy(setting), |
|
| 485 |
+ maybe_unset=Strategies.maybe_unset_strategy(), |
|
| 313 | 486 |
overwrite=strategies.booleans(), |
| 314 | 487 |
) |
| 315 | 488 |
def set_service( |
| ... | ... |
@@ -317,7 +490,7 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| 317 | 490 |
config: _types.VaultConfig, |
| 318 | 491 |
service: str, |
| 319 | 492 |
setting: _types.VaultConfigServicesSettings, |
| 320 |
- maybe_unset: set[str], |
|
| 493 |
+ maybe_unset: AbstractSet[str], |
|
| 321 | 494 |
overwrite: bool, |
| 322 | 495 |
) -> _types.VaultConfig: |
| 323 | 496 |
"""Set the named service settings for a configuration. |
| ... | ... |
@@ -343,35 +516,48 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| 343 | 516 |
|
| 344 | 517 |
""" |
| 345 | 518 |
cli_helpers.save_config(config) |
| 346 |
- config_service = config["services"].get(service, {})
|
|
| 347 |
- maybe_unset = set(maybe_unset) - setting.keys() |
|
| 348 |
- if overwrite: |
|
| 349 |
- config["services"][service] = config_service = {}
|
|
| 350 |
- elif maybe_unset: |
|
| 351 |
- for key in maybe_unset: |
|
| 352 |
- config_service.pop(key, None) # type: ignore[misc] |
|
| 353 |
- config["services"].setdefault(service, {}).update(setting)
|
|
| 354 |
- assert _types.is_vault_config(config) |
|
| 355 |
- # NOTE: This relies on settings_obj containing only the keys |
|
| 519 |
+ expected_config, maybe_unset = self.set_service_expected_result( |
|
| 520 |
+ service, |
|
| 521 |
+ config=config, |
|
| 522 |
+ setting=setting, |
|
| 523 |
+ maybe_unset=maybe_unset, |
|
| 524 |
+ overwrite=overwrite, |
|
| 525 |
+ ) |
|
| 526 |
+ # NOTE: This relies on `settings` containing only the keys |
|
| 356 | 527 |
# "length", "repeat", "upper", "lower", "number", "space", |
| 357 | 528 |
# "dash" and "symbol". |
| 358 |
- result = self.runner.invoke( |
|
| 359 |
- cli.derivepassphrase_vault, |
|
| 360 |
- [ |
|
| 361 |
- "--config", |
|
| 362 |
- "--overwrite-existing" if overwrite else "--merge-existing", |
|
| 363 |
- ] |
|
| 364 |
- + [f"--unset={key}" for key in maybe_unset]
|
|
| 365 |
- + [ |
|
| 529 |
+ unset_commands = [f"--unset={key}" for key in maybe_unset]
|
|
| 530 |
+ set_commands = [ |
|
| 366 | 531 |
f"--{key}={value}"
|
| 367 | 532 |
for key, value in setting.items() |
| 368 | 533 |
if key in VALID_PROPERTIES |
| 369 | 534 |
] |
| 370 |
- + ["--", service], |
|
| 371 |
- catch_exceptions=False, |
|
| 535 |
+ service_arg = ["--", service] |
|
| 536 |
+ return self.call_cli( |
|
| 537 |
+ ["--config", *unset_commands, *set_commands, *service_arg], |
|
| 538 |
+ expected_config, |
|
| 539 |
+ overwrite=overwrite, |
|
| 372 | 540 |
) |
| 373 |
- assert result.clean_exit(empty_stderr=False) |
|
| 374 |
- assert cli_helpers.load_config() == config |
|
| 541 |
+ |
|
| 542 |
+ def purge_global_expected_result( |
|
| 543 |
+ self, |
|
| 544 |
+ config: _types.VaultConfig, |
|
| 545 |
+ ) -> _types.VaultConfig: |
|
| 546 |
+ """Purge the globals of a configuration. |
|
| 547 |
+ |
|
| 548 |
+ This is the "calculate the correct result" section of the |
|
| 549 |
+ `purge_global` rule. |
|
| 550 |
+ |
|
| 551 |
+ Args: |
|
| 552 |
+ config: |
|
| 553 |
+ The configuration to edit. |
|
| 554 |
+ |
|
| 555 |
+ Returns: |
|
| 556 |
+ The pruned configuration. |
|
| 557 |
+ |
|
| 558 |
+ """ |
|
| 559 |
+ config = copy.deepcopy(config) |
|
| 560 |
+ config.pop("global", None)
|
|
| 375 | 561 |
return config |
| 376 | 562 |
|
| 377 | 563 |
@stateful.rule( |
| ... | ... |
@@ -393,26 +579,37 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| 393 | 579 |
|
| 394 | 580 |
""" |
| 395 | 581 |
cli_helpers.save_config(config) |
| 396 |
- config.pop("global", None)
|
|
| 397 |
- result = self.runner.invoke( |
|
| 398 |
- cli.derivepassphrase_vault, |
|
| 399 |
- ["--delete-globals"], |
|
| 400 |
- input="y", |
|
| 401 |
- catch_exceptions=False, |
|
| 402 |
- ) |
|
| 403 |
- assert result.clean_exit(empty_stderr=False) |
|
| 404 |
- assert cli_helpers.load_config() == config |
|
| 582 |
+ expected_config = self.purge_global_expected_result(config) |
|
| 583 |
+ return self.call_cli(["--delete-globals"], expected_config) |
|
| 584 |
+ |
|
| 585 |
+ def purge_service_expected_result( |
|
| 586 |
+ self, |
|
| 587 |
+ config: _types.VaultConfig, |
|
| 588 |
+ service: str, |
|
| 589 |
+ ) -> _types.VaultConfig: |
|
| 590 |
+ """Purge the settings of a named service in a configuration. |
|
| 591 |
+ |
|
| 592 |
+ This is the "calculate the correct result" section of the |
|
| 593 |
+ `purge_global` rule. |
|
| 594 |
+ |
|
| 595 |
+ Args: |
|
| 596 |
+ config: |
|
| 597 |
+ The configuration to edit. |
|
| 598 |
+ service: |
|
| 599 |
+ The service name to purge. |
|
| 600 |
+ |
|
| 601 |
+ Returns: |
|
| 602 |
+ The pruned configuration. |
|
| 603 |
+ |
|
| 604 |
+ """ |
|
| 605 |
+ config = copy.deepcopy(config) |
|
| 606 |
+ config["services"].pop(service, None) |
|
| 405 | 607 |
return config |
| 406 | 608 |
|
| 407 | 609 |
@stateful.rule( |
| 408 | 610 |
target=configuration, |
| 409 |
- config_and_service=configuration.filter( |
|
| 410 |
- lambda c: bool(c["services"]) |
|
| 411 |
- ).flatmap( |
|
| 412 |
- lambda c: strategies.tuples( |
|
| 413 |
- strategies.just(c), |
|
| 414 |
- strategies.sampled_from(tuple(c["services"].keys())), |
|
| 415 |
- ) |
|
| 611 |
+ config_and_service=Strategies.config_and_service_strategy( |
|
| 612 |
+ configuration |
|
| 416 | 613 |
), |
| 417 | 614 |
) |
| 418 | 615 |
def purge_service( |
| ... | ... |
@@ -432,16 +629,20 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| 432 | 629 |
""" |
| 433 | 630 |
config, service = config_and_service |
| 434 | 631 |
cli_helpers.save_config(config) |
| 435 |
- config["services"].pop(service, None) |
|
| 436 |
- result = self.runner.invoke( |
|
| 437 |
- cli.derivepassphrase_vault, |
|
| 438 |
- ["--delete", "--", service], |
|
| 439 |
- input="y", |
|
| 440 |
- catch_exceptions=False, |
|
| 441 |
- ) |
|
| 442 |
- assert result.clean_exit(empty_stderr=False) |
|
| 443 |
- assert cli_helpers.load_config() == config |
|
| 444 |
- return config |
|
| 632 |
+ expected_config = self.purge_service_expected_result(config, service) |
|
| 633 |
+ return self.call_cli(["--delete", "--", service], expected_config) |
|
| 634 |
+ |
|
| 635 |
+ def purge_all_expected_result(self) -> _types.VaultConfig: |
|
| 636 |
+ """Purge the entire configuration. |
|
| 637 |
+ |
|
| 638 |
+ This is the "calculate the correct result" section of the |
|
| 639 |
+ `purge_all` rule. |
|
| 640 |
+ |
|
| 641 |
+ Returns: |
|
| 642 |
+ The empty configuration. |
|
| 643 |
+ |
|
| 644 |
+ """ |
|
| 645 |
+ return {"services": {}}
|
|
| 445 | 646 |
|
| 446 | 647 |
@stateful.rule( |
| 447 | 648 |
target=configuration, |
| ... | ... |
@@ -462,16 +663,41 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| 462 | 663 |
|
| 463 | 664 |
""" |
| 464 | 665 |
cli_helpers.save_config(config) |
| 465 |
- config = {"services": {}}
|
|
| 466 |
- result = self.runner.invoke( |
|
| 467 |
- cli.derivepassphrase_vault, |
|
| 468 |
- ["--clear"], |
|
| 469 |
- input="y", |
|
| 470 |
- catch_exceptions=False, |
|
| 666 |
+ expected_config = self.purge_all_expected_result() |
|
| 667 |
+ return self.call_cli(["--clear"], expected_config) |
|
| 668 |
+ |
|
| 669 |
+ def import_configuration_expected_result( |
|
| 670 |
+ self, |
|
| 671 |
+ base_config: _types.VaultConfig, |
|
| 672 |
+ config_to_import: _types.VaultConfig, |
|
| 673 |
+ overwrite: bool, |
|
| 674 |
+ ) -> _types.VaultConfig: |
|
| 675 |
+ """Import the given configuration into a base configuration. |
|
| 676 |
+ |
|
| 677 |
+ This is the "calculate the correct result" section of the |
|
| 678 |
+ `import_configuration` rule. |
|
| 679 |
+ |
|
| 680 |
+ Args: |
|
| 681 |
+ base_config: |
|
| 682 |
+ The configuration to import into. |
|
| 683 |
+ config_to_import: |
|
| 684 |
+ The configuration to import. |
|
| 685 |
+ overwrite: |
|
| 686 |
+ Overwrite the base configuration if true, or merge if |
|
| 687 |
+ false. Corresponds to the `--overwrite-existing` and |
|
| 688 |
+ `--merge-existing` command-line arguments. |
|
| 689 |
+ |
|
| 690 |
+ Returns: |
|
| 691 |
+ The imported or merged configuration. |
|
| 692 |
+ |
|
| 693 |
+ """ |
|
| 694 |
+ result = ( |
|
| 695 |
+ copy.deepcopy(config_to_import) |
|
| 696 |
+ if overwrite |
|
| 697 |
+ else self.fold_configs(config_to_import, base_config) |
|
| 471 | 698 |
) |
| 472 |
- assert result.clean_exit(empty_stderr=False) |
|
| 473 |
- assert cli_helpers.load_config() == config |
|
| 474 |
- return config |
|
| 699 |
+ assert _types.is_vault_config(result) |
|
| 700 |
+ return result |
|
| 475 | 701 |
|
| 476 | 702 |
@stateful.rule( |
| 477 | 703 |
target=configuration, |
| ... | ... |
@@ -502,22 +728,17 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine): |
| 502 | 728 |
|
| 503 | 729 |
""" |
| 504 | 730 |
cli_helpers.save_config(base_config) |
| 505 |
- config = ( |
|
| 506 |
- self.fold_configs(config_to_import, base_config) |
|
| 507 |
- if not overwrite |
|
| 508 |
- else config_to_import |
|
| 509 |
- ) |
|
| 510 |
- assert _types.is_vault_config(config) |
|
| 511 |
- result = self.runner.invoke( |
|
| 512 |
- cli.derivepassphrase_vault, |
|
| 513 |
- ["--import", "-"] |
|
| 514 |
- + (["--overwrite-existing"] if overwrite else []), |
|
| 731 |
+ expected_config = self.import_configuration_expected_result( |
|
| 732 |
+ base_config=base_config, |
|
| 733 |
+ config_to_import=config_to_import, |
|
| 734 |
+ overwrite=overwrite, |
|
| 735 |
+ ) |
|
| 736 |
+ return self.call_cli( |
|
| 737 |
+ ["--import", "-"], |
|
| 738 |
+ expected_config, |
|
| 515 | 739 |
input=json.dumps(config_to_import), |
| 516 |
- catch_exceptions=False, |
|
| 740 |
+ overwrite=overwrite, |
|
| 517 | 741 |
) |
| 518 |
- assert result.clean_exit(empty_stderr=False) |
|
| 519 |
- assert cli_helpers.load_config() == config |
|
| 520 |
- return config |
|
| 521 | 742 |
|
| 522 | 743 |
def teardown(self) -> None: |
| 523 | 744 |
"""Upon teardown, exit all contexts entered in `__init__`.""" |
| ... | ... |
@@ -806,10 +1027,26 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
| 806 | 1027 |
settings |
| 807 | 1028 |
) |
| 808 | 1029 |
|
| 1030 |
+ @staticmethod |
|
| 1031 |
+ def overwrite_or_merge_commands(*, overwrite: bool = False) -> list[str]: |
|
| 1032 |
+ """Return a partial command-line for overwriting or merging. |
|
| 1033 |
+ |
|
| 1034 |
+ Args: |
|
| 1035 |
+ overwrite: |
|
| 1036 |
+ If true, overwrite the configuration, else merge in the |
|
| 1037 |
+ relevant parts. |
|
| 1038 |
+ |
|
| 1039 |
+ Returns: |
|
| 1040 |
+ A list of command-line options for selecting config |
|
| 1041 |
+ overwriting or config merging. |
|
| 1042 |
+ |
|
| 1043 |
+ """ |
|
| 1044 |
+ return ["--overwrite-existing"] if overwrite else ["--merge-existing"] |
|
| 1045 |
+ |
|
| 809 | 1046 |
@stateful.initialize( |
| 810 | 1047 |
target=configuration, |
| 811 | 1048 |
configs=strategies.lists( |
| 812 |
- vault_full_config(), |
|
| 1049 |
+ Strategies.vault_full_config(), |
|
| 813 | 1050 |
min_size=8, |
| 814 | 1051 |
max_size=8, |
| 815 | 1052 |
), |
| ... | ... |
@@ -824,7 +1061,7 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
| 824 | 1061 |
@stateful.initialize( |
| 825 | 1062 |
target=setting, |
| 826 | 1063 |
configs=strategies.lists( |
| 827 |
- vault_full_config(), |
|
| 1064 |
+ Strategies.vault_full_config(), |
|
| 828 | 1065 |
min_size=4, |
| 829 | 1066 |
max_size=4, |
| 830 | 1067 |
), |
| ... | ... |
@@ -840,7 +1077,7 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
| 840 | 1077 |
return stateful.multiple(*map(copy.deepcopy, settings)) |
| 841 | 1078 |
|
| 842 | 1079 |
@stateful.initialize( |
| 843 |
- config=vault_full_config(), |
|
| 1080 |
+ config=Strategies.vault_full_config(), |
|
| 844 | 1081 |
) |
| 845 | 1082 |
def declare_initial_action( |
| 846 | 1083 |
self, |
| ... | ... |
@@ -865,11 +1102,8 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
| 865 | 1102 |
self.actions.append(action) |
| 866 | 1103 |
|
| 867 | 1104 |
@stateful.rule( |
| 868 |
- setting=setting.filter(bool), |
|
| 869 |
- maybe_unset=strategies.sets( |
|
| 870 |
- strategies.sampled_from(VALID_PROPERTIES), |
|
| 871 |
- max_size=3, |
|
| 872 |
- ), |
|
| 1105 |
+ setting=Strategies.setting_strategy(setting), |
|
| 1106 |
+ maybe_unset=Strategies.maybe_unset_strategy(), |
|
| 873 | 1107 |
overwrite=strategies.booleans(), |
| 874 | 1108 |
) |
| 875 | 1109 |
def add_set_globals_action( |
| ... | ... |
@@ -894,18 +1128,21 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
| 894 | 1128 |
|
| 895 | 1129 |
""" |
| 896 | 1130 |
maybe_unset = set(maybe_unset) - setting.keys() |
| 897 |
- command_line = ( |
|
| 898 |
- [ |
|
| 899 |
- "--config", |
|
| 900 |
- "--overwrite-existing" if overwrite else "--merge-existing", |
|
| 901 |
- ] |
|
| 902 |
- + [f"--unset={key}" for key in maybe_unset]
|
|
| 903 |
- + [ |
|
| 1131 |
+ # NOTE: This relies on `settings` containing only the keys |
|
| 1132 |
+ # "length", "repeat", "upper", "lower", "number", "space", |
|
| 1133 |
+ # "dash" and "symbol". |
|
| 1134 |
+ unset_commands = [f"--unset={key}" for key in maybe_unset]
|
|
| 1135 |
+ set_commands = [ |
|
| 904 | 1136 |
f"--{key}={value}"
|
| 905 | 1137 |
for key, value in setting.items() |
| 906 | 1138 |
if key in VALID_PROPERTIES |
| 907 | 1139 |
] |
| 908 |
- ) |
|
| 1140 |
+ command_line = [ |
|
| 1141 |
+ "--config", |
|
| 1142 |
+ *self.overwrite_or_merge_commands(overwrite=overwrite), |
|
| 1143 |
+ *unset_commands, |
|
| 1144 |
+ *set_commands, |
|
| 1145 |
+ ] |
|
| 909 | 1146 |
input = None # noqa: A001 |
| 910 | 1147 |
hypothesis.note(f"# {command_line = }, {input = }")
|
| 911 | 1148 |
action = FakeConfigurationMutexAction( |
| ... | ... |
@@ -914,12 +1151,9 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
| 914 | 1151 |
self.actions.append(action) |
| 915 | 1152 |
|
| 916 | 1153 |
@stateful.rule( |
| 917 |
- service=strategies.sampled_from(KNOWN_SERVICES), |
|
| 918 |
- setting=setting.filter(bool), |
|
| 919 |
- maybe_unset=strategies.sets( |
|
| 920 |
- strategies.sampled_from(VALID_PROPERTIES), |
|
| 921 |
- max_size=3, |
|
| 922 |
- ), |
|
| 1154 |
+ service=Strategies.service_name_strategy(), |
|
| 1155 |
+ setting=Strategies.setting_strategy(setting), |
|
| 1156 |
+ maybe_unset=Strategies.maybe_unset_strategy(), |
|
| 923 | 1157 |
overwrite=strategies.booleans(), |
| 924 | 1158 |
) |
| 925 | 1159 |
def add_set_service_action( |
| ... | ... |
@@ -947,19 +1181,23 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
| 947 | 1181 |
|
| 948 | 1182 |
""" |
| 949 | 1183 |
maybe_unset = set(maybe_unset) - setting.keys() |
| 950 |
- command_line = ( |
|
| 951 |
- [ |
|
| 952 |
- "--config", |
|
| 953 |
- "--overwrite-existing" if overwrite else "--merge-existing", |
|
| 954 |
- ] |
|
| 955 |
- + [f"--unset={key}" for key in maybe_unset]
|
|
| 956 |
- + [ |
|
| 1184 |
+ # NOTE: This relies on `settings` containing only the keys |
|
| 1185 |
+ # "length", "repeat", "upper", "lower", "number", "space", |
|
| 1186 |
+ # "dash" and "symbol". |
|
| 1187 |
+ unset_commands = [f"--unset={key}" for key in maybe_unset]
|
|
| 1188 |
+ set_commands = [ |
|
| 957 | 1189 |
f"--{key}={value}"
|
| 958 | 1190 |
for key, value in setting.items() |
| 959 | 1191 |
if key in VALID_PROPERTIES |
| 960 | 1192 |
] |
| 961 |
- + ["--", service] |
|
| 962 |
- ) |
|
| 1193 |
+ command_line = [ |
|
| 1194 |
+ "--config", |
|
| 1195 |
+ *self.overwrite_or_merge_commands(overwrite=overwrite), |
|
| 1196 |
+ *unset_commands, |
|
| 1197 |
+ *set_commands, |
|
| 1198 |
+ "--", |
|
| 1199 |
+ service, |
|
| 1200 |
+ ] |
|
| 963 | 1201 |
input = None # noqa: A001 |
| 964 | 1202 |
hypothesis.note(f"# {command_line = }, {input = }")
|
| 965 | 1203 |
action = FakeConfigurationMutexAction( |
| ... | ... |
@@ -981,7 +1219,7 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
| 981 | 1219 |
self.actions.append(action) |
| 982 | 1220 |
|
| 983 | 1221 |
@stateful.rule( |
| 984 |
- service=strategies.sampled_from(KNOWN_SERVICES), |
|
| 1222 |
+ service=Strategies.service_name_strategy(), |
|
| 985 | 1223 |
) |
| 986 | 1224 |
def add_purge_service_action( |
| 987 | 1225 |
self, |
| ... | ... |
@@ -1035,9 +1273,11 @@ class FakeConfigurationMutexStateMachine(stateful.RuleBasedStateMachine): |
| 1035 | 1273 |
`--merge-existing` command-line arguments. |
| 1036 | 1274 |
|
| 1037 | 1275 |
""" |
| 1038 |
- command_line = ["--import", "-"] + ( |
|
| 1039 |
- ["--overwrite-existing"] if overwrite else [] |
|
| 1040 |
- ) |
|
| 1276 |
+ command_line = [ |
|
| 1277 |
+ "--import", |
|
| 1278 |
+ "-", |
|
| 1279 |
+ *self.overwrite_or_merge_commands(overwrite=overwrite), |
|
| 1280 |
+ ] |
|
| 1041 | 1281 |
input = json.dumps(config_to_import) # noqa: A001 |
| 1042 | 1282 |
hypothesis.note(f"# {command_line = }, {input = }")
|
| 1043 | 1283 |
action = FakeConfigurationMutexAction( |
| 1044 | 1284 |