Repository: chaostoolkit/chaostoolkit-lib

Issue:
Pass th experiment to all controls
Currently, only the current context (steady-state, method, activity...) is passed down to a control fonction. We should also pass the experiment as it contains a larger context that may be useful as well.

Ideally this should go into 1.0.0rc2

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f55e69d..383e0cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,8 @@
 - Use `ImportError` exception instead of `ModuleNotFoundError` which is not
   declared in Python 3.5 [#67][67]
 - Disallow empty JSON path [#68][68]
+- Pass the experiment to each control when requested via the `experiment`
+  parameter of the Python function [#69][69]
 
 [63]: https://github.com/chaostoolkit/chaostoolkit-lib/issues/63
 [64]: https://github.com/chaostoolkit/chaostoolkit-lib/issues/64
@@ -21,6 +23,7 @@
 [66]: https://github.com/chaostoolkit/chaostoolkit-lib/issues/66
 [67]: https://github.com/chaostoolkit/chaostoolkit-lib/issues/67
 [68]: https://github.com/chaostoolkit/chaostoolkit-lib/issues/68
+[69]: https://github.com/chaostoolkit/chaostoolkit-lib/issues/69
 
 ## [1.0.0rc1][] - 2018-11-30
 
diff --git a/chaoslib/control/__init__.py b/chaoslib/control/__init__.py
index 2b4b58c..a9a399b 100644
--- a/chaoslib/control/__init__.py
+++ b/chaoslib/control/__init__.py
@@ -255,7 +255,7 @@ def apply_controls(level: str, experiment: Experiment,
             if provider_type == "python":
                 apply_python_control(
                     level="{}-{}".format(level, scope), control=control,
-                    context=context, state=state,
+                    context=context, state=state, experiment=experiment,
                     configuration=configuration, secrets=secrets)
         except InterruptExecution as c:
             logger.debug(
diff --git a/chaoslib/control/python.py b/chaoslib/control/python.py
index 80cc607..977bb27 100644
--- a/chaoslib/control/python.py
+++ b/chaoslib/control/python.py
@@ -73,7 +73,7 @@ def validate_python_control(control: Control):
                                   mod=mod_name, name=name))
 
 
-def apply_python_control(level: str, control: Control,
+def apply_python_control(level: str, control: Control, experiment: Experiment,
                          context: Union[Activity, Experiment],
                          state: Union[Journal, Run, List[Run]] = None,
                          configuration: Configuration = None,
@@ -104,6 +104,9 @@ def apply_python_control(level: str, control: Control,
     if "state" in sig.parameters:
         arguments["state"] = state
 
+    if "experiment" in sig.parameters:
+        arguments["experiment"] = experiment
+
     func(context=context, **arguments)
 
 


Test patch:
diff --git a/tests/fixtures/controls/dummy_with_experiment.py b/tests/fixtures/controls/dummy_with_experiment.py
new file mode 100644
index 0000000..17a6d61
--- /dev/null
+++ b/tests/fixtures/controls/dummy_with_experiment.py
@@ -0,0 +1,69 @@
+import os
+from typing import Any, Callable, Dict, List, Union
+
+from chaoslib.types import Activity, Configuration, \
+    Experiment, Hypothesis, Journal, Run, Secrets, Settings
+
+value_from_config = None
+
+
+def configure_control(config: Configuration, secrets = Secrets):
+    global value_from_config
+    print(config)
+    value_from_config = config.get("dummy-key", "default")
+
+
+def cleanup_control():
+    global value_from_config
+    value_from_config = None
+
+
+def before_experiment_control(context: Experiment, **kwargs):
+    context["before_experiment_control"] = True
+
+
+def after_experiment_control(context: Experiment, state: Journal, **kwargs):
+    context["after_experiment_control"] = True
+    state["after_experiment_control"] = True
+
+
+def before_hypothesis_control(experiment: Experiment, context: Hypothesis, **kwargs):
+    context["before_hypothesis_control"] = True
+    context["has_experiment_before"] = experiment is not None
+
+
+def after_hypothesis_control(experiment: Experiment, context: Hypothesis,
+                             state: Dict[str, Any], **kwargs):
+    context["after_hypothesis_control"] = True
+    state["after_hypothesis_control"] = True
+    context["has_experiment_after"] = experiment is not None
+
+
+def before_method_control(context: Experiment, **kwargs):
+    context["before_method_control"] = True
+
+
+def after_method_control(context: Experiment, state: List[Run], **kwargs):
+    context["after_method_control"] = True
+    state.append("after_method_control")
+
+
+def before_rollback_control(context: Experiment, **kwargs):
+    context["before_rollback_control"] = True
+
+
+def after_rollback_control(context: Experiment, state: List[Run], **kwargs):
+    context["after_rollback_control"] = True
+    state.append("after_rollback_control")
+
+
+def before_activity_control(experiment: Experiment, context: Activity, **kwargs):
+    context["before_activity_control"] = True
+    context["has_experiment_before"] = experiment is not None
+
+
+def after_activity_control(experiment: Experiment, context: Activity,
+                           state: Run, **kwargs):
+    context["after_activity_control"] = True
+    state["after_activity_control"] = True
+    context["has_experiment_after"] = experiment is not None
diff --git a/tests/fixtures/experiments.py b/tests/fixtures/experiments.py
index 738293a..1f69ff4 100644
--- a/tests/fixtures/experiments.py
+++ b/tests/fixtures/experiments.py
@@ -225,6 +225,10 @@ ExperimentWithControls = {
     ]
 }
 
+
+ExperimentWithControlAccessingExperiment = deepcopy(ExperimentWithControls)
+ExperimentWithControlAccessingExperiment["controls"][0]["provider"]["module"] = "fixtures.controls.dummy_with_experiment"
+
 ExperimentCanBeInterruptedByControl = deepcopy(ExperimentWithControls)
 ExperimentCanBeInterruptedByControl["controls"] = [
     {
diff --git a/tests/test_control.py b/tests/test_control.py
index 1f3789e..dc6fd4b 100644
--- a/tests/test_control.py
+++ b/tests/test_control.py
@@ -227,3 +227,18 @@ def test_validate_python_control_needs_a_module():
                 "type": "python"
             }
         })
+
+
+def test_controls_can_access_experiment():
+    exp = deepcopy(experiments.ExperimentWithControlAccessingExperiment)
+    exp["dry"] = True
+
+    hypo = exp.get("steady-state-hypothesis")
+    run_experiment(exp)
+    assert hypo["has_experiment_before"] is True
+    assert hypo["has_experiment_after"] is True
+
+    activities = get_all_activities(exp)
+    for activity in activities:
+        assert activity["has_experiment_before"] is True
+        assert activity["has_experiment_after"] is True
\ No newline at end of file