We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
No doc for #3467
The new feature added in #3467 is not documented in the docs. 

The new feature:

> provide[s] alternative location for
> tests or side effects playbook in test sequence like this:
> 
>  ```
>  test_sequence:
>     - create
>     - converge
>     - verify
>     - side_effect side_effect1.yml
>     - verify tests_for_effect1/
>     - side_effect side_effect2.yml
>     - verify tests_for_effect2/
>  ```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/molecule/scenario.py`
Content:
```
1 #  Copyright (c) 2015-2018 Cisco Systems, Inc.
2 #
3 #  Permission is hereby granted, free of charge, to any person obtaining a copy
4 #  of this software and associated documentation files (the "Software"), to
5 #  deal in the Software without restriction, including without limitation the
6 #  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 #  sell copies of the Software, and to permit persons to whom the Software is
8 #  furnished to do so, subject to the following conditions:
9 #
10 #  The above copyright notice and this permission notice shall be included in
11 #  all copies or substantial portions of the Software.
12 #
13 #  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 #  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 #  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 #  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 #  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 #  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 #  DEALINGS IN THE SOFTWARE.
20 """Molecule Scenario Module."""
21 
22 import errno
23 import fcntl
24 import fnmatch
25 import logging
26 import os
27 import shutil
28 from pathlib import Path
29 from time import sleep
30 from typing import Optional
31 
32 from molecule import scenarios, util
33 from molecule.constants import RC_TIMEOUT
34 
35 LOG = logging.getLogger(__name__)
36 
37 
38 class Scenario(object):
39     """
40     A scenario allows Molecule test a role in a particular way, this is a \
41     fundamental change from Molecule v1.
42 
43     A scenario is a self-contained directory containing everything necessary
44     for testing the role in a particular way.  The default scenario is named
45     ``default``, and every role should contain a default scenario.
46 
47     Unless mentioned explicitly, the scenario name will be the directory name
48     hosting the files.
49 
50     Any option set in this section will override the defaults.
51 
52     .. code-block:: yaml
53 
54         scenario:
55           create_sequence:
56             - dependency
57             - create
58             - prepare
59           check_sequence:
60             - dependency
61             - cleanup
62             - destroy
63             - create
64             - prepare
65             - converge
66             - check
67             - destroy
68           converge_sequence:
69             - dependency
70             - create
71             - prepare
72             - converge
73           destroy_sequence:
74             - dependency
75             - cleanup
76             - destroy
77           test_sequence:
78             - dependency
79             - lint
80             - cleanup
81             - destroy
82             - syntax
83             - create
84             - prepare
85             - converge
86             - idempotence
87             - side_effect
88             - verify
89             - cleanup
90             - destroy
91     """  # noqa
92 
93     def __init__(self, config):
94         """
95         Initialize a new scenario class and returns None.
96 
97         :param config: An instance of a Molecule config.
98         :return: None
99         """
100         self._lock = None
101         self.config = config
102         self._setup()
103 
104     def _remove_scenario_state_directory(self):
105         """Remove scenario cached disk stored state.
106 
107         :return: None
108         """
109         directory = str(Path(self.ephemeral_directory).parent)
110         LOG.info("Removing %s", directory)
111         shutil.rmtree(directory)
112 
113     def prune(self):
114         """
115         Prune the scenario ephemeral directory files and returns None.
116 
117         "safe files" will not be pruned, including the ansible configuration
118         and inventory used by this scenario, the scenario state file, and
119         files declared as "safe_files" in the ``driver`` configuration
120         declared in ``molecule.yml``.
121 
122         :return: None
123         """
124         LOG.info("Pruning extra files from scenario ephemeral directory")
125         safe_files = [
126             self.config.provisioner.config_file,
127             self.config.provisioner.inventory_file,
128             self.config.state.state_file,
129         ] + self.config.driver.safe_files
130         files = util.os_walk(self.ephemeral_directory, "*")
131         for f in files:
132             if not any(sf for sf in safe_files if fnmatch.fnmatch(f, sf)):
133                 try:
134                     os.remove(f)
135                 except OSError as e:
136                     if e.errno != errno.ENOENT:
137                         raise
138 
139         # Remove empty directories.
140         for dirpath, dirs, files in os.walk(self.ephemeral_directory, topdown=False):
141             if not dirs and not files:
142                 os.removedirs(dirpath)
143 
144     @property
145     def name(self):
146         return self.config.config["scenario"]["name"]
147 
148     @property
149     def directory(self):
150         if self.config.molecule_file:
151             return os.path.dirname(self.config.molecule_file)
152         else:
153             return os.getcwd()
154 
155     @property
156     def ephemeral_directory(self):
157         path = os.getenv("MOLECULE_EPHEMERAL_DIRECTORY", None)
158         if not path:
159 
160             project_directory = os.path.basename(self.config.project_directory)
161 
162             if self.config.is_parallel:
163                 project_directory = f"{project_directory}-{self.config._run_uuid}"
164 
165             project_scenario_directory = os.path.join(
166                 self.config.cache_directory, project_directory, self.name
167             )
168 
169             path = ephemeral_directory(project_scenario_directory)
170 
171         if os.environ.get("MOLECULE_PARALLEL", False) and not self._lock:
172             with open(os.path.join(path, ".lock"), "w") as self._lock:
173                 for i in range(1, 5):
174                     try:
175                         fcntl.lockf(self._lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
176                         break
177                     except OSError:
178                         delay = 30 * i
179                         LOG.warning(
180                             "Retrying to acquire lock on %s, waiting for %s seconds",
181                             path,
182                             delay,
183                         )
184                         sleep(delay)
185                 else:
186                     LOG.warning("Timedout trying to acquire lock on %s", path)
187                     raise SystemExit(RC_TIMEOUT)
188 
189         return path
190 
191     @property
192     def inventory_directory(self):
193         return os.path.join(self.ephemeral_directory, "inventory")
194 
195     @property
196     def check_sequence(self):
197         return self.config.config["scenario"]["check_sequence"]
198 
199     @property
200     def cleanup_sequence(self):
201         return self.config.config["scenario"]["cleanup_sequence"]
202 
203     @property
204     def converge_sequence(self):
205         return self.config.config["scenario"]["converge_sequence"]
206 
207     @property
208     def create_sequence(self):
209         return self.config.config["scenario"]["create_sequence"]
210 
211     @property
212     def dependency_sequence(self):
213         return ["dependency"]
214 
215     @property
216     def destroy_sequence(self):
217         return self.config.config["scenario"]["destroy_sequence"]
218 
219     @property
220     def idempotence_sequence(self):
221         return ["idempotence"]
222 
223     @property
224     def lint_sequence(self):
225         # see https://github.com/ansible-community/molecule/issues/2216
226         return ["dependency", "lint"]
227 
228     @property
229     def prepare_sequence(self):
230         return ["prepare"]
231 
232     @property
233     def side_effect_sequence(self):
234         return ["side_effect"]
235 
236     @property
237     def syntax_sequence(self):
238         return ["syntax"]
239 
240     @property
241     def test_sequence(self):
242         return self.config.config["scenario"]["test_sequence"]
243 
244     @property
245     def verify_sequence(self):
246         return ["verify"]
247 
248     @property
249     def sequence(self):
250         """
251         Select the sequence based on scenario and subcommand of the provided \
252         scenario object and returns a list.
253 
254         :param scenario: A scenario object.
255         :param skipped: An optional bool to include skipped scenarios.
256         :return: list
257         """
258         s = scenarios.Scenarios([self.config])
259         matrix = s._get_matrix()
260 
261         try:
262             return matrix[self.name][self.config.subcommand]
263         except KeyError:
264             # TODO(retr0h): May change this handling in the future.
265             return []
266 
267     def _setup(self):
268         """
269         Prepare the scenario for Molecule and returns None.
270 
271         :return: None
272         """
273         if not os.path.isdir(self.inventory_directory):
274             os.makedirs(self.inventory_directory, exist_ok=True)
275 
276 
277 def ephemeral_directory(path: Optional[str] = None) -> str:
278     """
279     Return temporary directory to be used by molecule.
280 
281     Molecule users should not make any assumptions about its location,
282     permissions or its content as this may change in future release.
283     """
284     d = os.getenv("MOLECULE_EPHEMERAL_DIRECTORY")
285     if not d:
286         d = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
287     if not d:
288         raise RuntimeError("Unable to determine ephemeral directory to use.")
289     d = os.path.abspath(os.path.join(d, path if path else "molecule"))
290 
291     if not os.path.isdir(d):
292         os.umask(0o077)
293         Path(d).mkdir(mode=0o700, parents=True, exist_ok=True)
294 
295     return d
296
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/molecule/scenario.py b/src/molecule/scenario.py
--- a/src/molecule/scenario.py
+++ b/src/molecule/scenario.py
@@ -88,6 +88,70 @@
             - verify
             - cleanup
             - destroy
+
+    :Advanced testing
+
+    If needed, Molecule can run multiple side effects and tests within a scenario.
+    This allows to perform advanced testing for stateful software under role/playbook
+    management. Actions `side_effect` and `verify` can take optional arguments to change
+    the playbook/test they execute.
+
+    Example of test sequence with multiple side effects and tests:
+
+    .. code-block:: yaml
+
+         test_sequence:
+           - converge
+           - side_effect reboot.yaml
+           - verify after_reboot/
+           - side_effect alter_configs.yaml
+           - converge
+           - verify other_test1.py other_test2.py
+           - side_effect
+           - verify
+
+    ``side_effect`` without an argument is executing the usual `side_effect` configured in
+      `provisioner.playbooks` section of molecule.yml.
+
+    ``side_effect`` can have one or more arguments (separated by spaces) which is
+    a playbook (plabyooks) to execute. If the argument for ``side_effect`` is present,
+    it's executed instead. The path to the playbook is relative to the molecule.yml location.
+    Normal side effect settings (from `provisioner.playbooks`) are ignored for action with
+    argument.
+
+    ``verify`` without an argument is executing usual tests configured in the verifier section
+    of molecule.yml.
+
+    If one or more arguments (separated by spaces) are present, each argument is treated
+    as a test name (file or directory) to pass to the verifier (either Ansible or Testinfra).
+    The kind of verifier is set in the `verifier` section of molecule.yml and is applied to all
+    `verify` actions in the scenario.
+
+    The path to tests is relative to the molecule.yml file location. The `additional_files_or_dirs`
+    setting for verifier is ignored if the `verify` action has an argument.
+
+    Multiple `side_effect` and `verify` actions can be used to a create a combination
+    of playbooks and tests, for example, for end-to-end playbook testing.
+
+    Additional `converge` and `idempotence` actions can be used multiple times:
+
+    .. code-block:: yaml
+
+         test_sequence:
+           - converge
+           - idempotence
+           - side_effect
+           - verify
+           - converge
+           - idempotence
+           - side_effect effect2.yml
+           - converge
+           - idempotence
+           - verify test2/
+           - side_effect effect3.yml
+           - verify test3/
+           - idempotence
+
     """  # noqa
 
     def __init__(self, config):

{"golden_diff": "diff --git a/src/molecule/scenario.py b/src/molecule/scenario.py\n--- a/src/molecule/scenario.py\n+++ b/src/molecule/scenario.py\n@@ -88,6 +88,70 @@\n             - verify\n             - cleanup\n             - destroy\n+\n+    :Advanced testing\n+\n+    If needed, Molecule can run multiple side effects and tests within a scenario.\n+    This allows to perform advanced testing for stateful software under role/playbook\n+    management. Actions `side_effect` and `verify` can take optional arguments to change\n+    the playbook/test they execute.\n+\n+    Example of test sequence with multiple side effects and tests:\n+\n+    .. code-block:: yaml\n+\n+         test_sequence:\n+           - converge\n+           - side_effect reboot.yaml\n+           - verify after_reboot/\n+           - side_effect alter_configs.yaml\n+           - converge\n+           - verify other_test1.py other_test2.py\n+           - side_effect\n+           - verify\n+\n+    ``side_effect`` without an argument is executing the usual `side_effect` configured in\n+      `provisioner.playbooks` section of molecule.yml.\n+\n+    ``side_effect`` can have one or more arguments (separated by spaces) which is\n+    a playbook (plabyooks) to execute. If the argument for ``side_effect`` is present,\n+    it's executed instead. The path to the playbook is relative to the molecule.yml location.\n+    Normal side effect settings (from `provisioner.playbooks`) are ignored for action with\n+    argument.\n+\n+    ``verify`` without an argument is executing usual tests configured in the verifier section\n+    of molecule.yml.\n+\n+    If one or more arguments (separated by spaces) are present, each argument is treated\n+    as a test name (file or directory) to pass to the verifier (either Ansible or Testinfra).\n+    The kind of verifier is set in the `verifier` section of molecule.yml and is applied to all\n+    `verify` actions in the scenario.\n+\n+    The path to tests is relative to the molecule.yml file location. The `additional_files_or_dirs`\n+    setting for verifier is ignored if the `verify` action has an argument.\n+\n+    Multiple `side_effect` and `verify` actions can be used to a create a combination\n+    of playbooks and tests, for example, for end-to-end playbook testing.\n+\n+    Additional `converge` and `idempotence` actions can be used multiple times:\n+\n+    .. code-block:: yaml\n+\n+         test_sequence:\n+           - converge\n+           - idempotence\n+           - side_effect\n+           - verify\n+           - converge\n+           - idempotence\n+           - side_effect effect2.yml\n+           - converge\n+           - idempotence\n+           - verify test2/\n+           - side_effect effect3.yml\n+           - verify test3/\n+           - idempotence\n+\n     \"\"\"  # noqa\n \n     def __init__(self, config):\n", "issue": "No doc for #3467\nThe new feature added in #3467 is not documented in the docs. \r\n\r\nThe new feature:\r\n\r\n> provide[s] alternative location for\r\n> tests or side effects playbook in test sequence like this:\r\n> \r\n>  ```\r\n>  test_sequence:\r\n>     - create\r\n>     - converge\r\n>     - verify\r\n>     - side_effect side_effect1.yml\r\n>     - verify tests_for_effect1/\r\n>     - side_effect side_effect2.yml\r\n>     - verify tests_for_effect2/\r\n>  ```\r\n\n", "before_files": [{"content": "#  Copyright (c) 2015-2018 Cisco Systems, Inc.\n#\n#  Permission is hereby granted, free of charge, to any person obtaining a copy\n#  of this software and associated documentation files (the \"Software\"), to\n#  deal in the Software without restriction, including without limitation the\n#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n#  sell copies of the Software, and to permit persons to whom the Software is\n#  furnished to do so, subject to the following conditions:\n#\n#  The above copyright notice and this permission notice shall be included in\n#  all copies or substantial portions of the Software.\n#\n#  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n#  DEALINGS IN THE SOFTWARE.\n\"\"\"Molecule Scenario Module.\"\"\"\n\nimport errno\nimport fcntl\nimport fnmatch\nimport logging\nimport os\nimport shutil\nfrom pathlib import Path\nfrom time import sleep\nfrom typing import Optional\n\nfrom molecule import scenarios, util\nfrom molecule.constants import RC_TIMEOUT\n\nLOG = logging.getLogger(__name__)\n\n\nclass Scenario(object):\n    \"\"\"\n    A scenario allows Molecule test a role in a particular way, this is a \\\n    fundamental change from Molecule v1.\n\n    A scenario is a self-contained directory containing everything necessary\n    for testing the role in a particular way.  The default scenario is named\n    ``default``, and every role should contain a default scenario.\n\n    Unless mentioned explicitly, the scenario name will be the directory name\n    hosting the files.\n\n    Any option set in this section will override the defaults.\n\n    .. code-block:: yaml\n\n        scenario:\n          create_sequence:\n            - dependency\n            - create\n            - prepare\n          check_sequence:\n            - dependency\n            - cleanup\n            - destroy\n            - create\n            - prepare\n            - converge\n            - check\n            - destroy\n          converge_sequence:\n            - dependency\n            - create\n            - prepare\n            - converge\n          destroy_sequence:\n            - dependency\n            - cleanup\n            - destroy\n          test_sequence:\n            - dependency\n            - lint\n            - cleanup\n            - destroy\n            - syntax\n            - create\n            - prepare\n            - converge\n            - idempotence\n            - side_effect\n            - verify\n            - cleanup\n            - destroy\n    \"\"\"  # noqa\n\n    def __init__(self, config):\n        \"\"\"\n        Initialize a new scenario class and returns None.\n\n        :param config: An instance of a Molecule config.\n        :return: None\n        \"\"\"\n        self._lock = None\n        self.config = config\n        self._setup()\n\n    def _remove_scenario_state_directory(self):\n        \"\"\"Remove scenario cached disk stored state.\n\n        :return: None\n        \"\"\"\n        directory = str(Path(self.ephemeral_directory).parent)\n        LOG.info(\"Removing %s\", directory)\n        shutil.rmtree(directory)\n\n    def prune(self):\n        \"\"\"\n        Prune the scenario ephemeral directory files and returns None.\n\n        \"safe files\" will not be pruned, including the ansible configuration\n        and inventory used by this scenario, the scenario state file, and\n        files declared as \"safe_files\" in the ``driver`` configuration\n        declared in ``molecule.yml``.\n\n        :return: None\n        \"\"\"\n        LOG.info(\"Pruning extra files from scenario ephemeral directory\")\n        safe_files = [\n            self.config.provisioner.config_file,\n            self.config.provisioner.inventory_file,\n            self.config.state.state_file,\n        ] + self.config.driver.safe_files\n        files = util.os_walk(self.ephemeral_directory, \"*\")\n        for f in files:\n            if not any(sf for sf in safe_files if fnmatch.fnmatch(f, sf)):\n                try:\n                    os.remove(f)\n                except OSError as e:\n                    if e.errno != errno.ENOENT:\n                        raise\n\n        # Remove empty directories.\n        for dirpath, dirs, files in os.walk(self.ephemeral_directory, topdown=False):\n            if not dirs and not files:\n                os.removedirs(dirpath)\n\n    @property\n    def name(self):\n        return self.config.config[\"scenario\"][\"name\"]\n\n    @property\n    def directory(self):\n        if self.config.molecule_file:\n            return os.path.dirname(self.config.molecule_file)\n        else:\n            return os.getcwd()\n\n    @property\n    def ephemeral_directory(self):\n        path = os.getenv(\"MOLECULE_EPHEMERAL_DIRECTORY\", None)\n        if not path:\n\n            project_directory = os.path.basename(self.config.project_directory)\n\n            if self.config.is_parallel:\n                project_directory = f\"{project_directory}-{self.config._run_uuid}\"\n\n            project_scenario_directory = os.path.join(\n                self.config.cache_directory, project_directory, self.name\n            )\n\n            path = ephemeral_directory(project_scenario_directory)\n\n        if os.environ.get(\"MOLECULE_PARALLEL\", False) and not self._lock:\n            with open(os.path.join(path, \".lock\"), \"w\") as self._lock:\n                for i in range(1, 5):\n                    try:\n                        fcntl.lockf(self._lock, fcntl.LOCK_EX | fcntl.LOCK_NB)\n                        break\n                    except OSError:\n                        delay = 30 * i\n                        LOG.warning(\n                            \"Retrying to acquire lock on %s, waiting for %s seconds\",\n                            path,\n                            delay,\n                        )\n                        sleep(delay)\n                else:\n                    LOG.warning(\"Timedout trying to acquire lock on %s\", path)\n                    raise SystemExit(RC_TIMEOUT)\n\n        return path\n\n    @property\n    def inventory_directory(self):\n        return os.path.join(self.ephemeral_directory, \"inventory\")\n\n    @property\n    def check_sequence(self):\n        return self.config.config[\"scenario\"][\"check_sequence\"]\n\n    @property\n    def cleanup_sequence(self):\n        return self.config.config[\"scenario\"][\"cleanup_sequence\"]\n\n    @property\n    def converge_sequence(self):\n        return self.config.config[\"scenario\"][\"converge_sequence\"]\n\n    @property\n    def create_sequence(self):\n        return self.config.config[\"scenario\"][\"create_sequence\"]\n\n    @property\n    def dependency_sequence(self):\n        return [\"dependency\"]\n\n    @property\n    def destroy_sequence(self):\n        return self.config.config[\"scenario\"][\"destroy_sequence\"]\n\n    @property\n    def idempotence_sequence(self):\n        return [\"idempotence\"]\n\n    @property\n    def lint_sequence(self):\n        # see https://github.com/ansible-community/molecule/issues/2216\n        return [\"dependency\", \"lint\"]\n\n    @property\n    def prepare_sequence(self):\n        return [\"prepare\"]\n\n    @property\n    def side_effect_sequence(self):\n        return [\"side_effect\"]\n\n    @property\n    def syntax_sequence(self):\n        return [\"syntax\"]\n\n    @property\n    def test_sequence(self):\n        return self.config.config[\"scenario\"][\"test_sequence\"]\n\n    @property\n    def verify_sequence(self):\n        return [\"verify\"]\n\n    @property\n    def sequence(self):\n        \"\"\"\n        Select the sequence based on scenario and subcommand of the provided \\\n        scenario object and returns a list.\n\n        :param scenario: A scenario object.\n        :param skipped: An optional bool to include skipped scenarios.\n        :return: list\n        \"\"\"\n        s = scenarios.Scenarios([self.config])\n        matrix = s._get_matrix()\n\n        try:\n            return matrix[self.name][self.config.subcommand]\n        except KeyError:\n            # TODO(retr0h): May change this handling in the future.\n            return []\n\n    def _setup(self):\n        \"\"\"\n        Prepare the scenario for Molecule and returns None.\n\n        :return: None\n        \"\"\"\n        if not os.path.isdir(self.inventory_directory):\n            os.makedirs(self.inventory_directory, exist_ok=True)\n\n\ndef ephemeral_directory(path: Optional[str] = None) -> str:\n    \"\"\"\n    Return temporary directory to be used by molecule.\n\n    Molecule users should not make any assumptions about its location,\n    permissions or its content as this may change in future release.\n    \"\"\"\n    d = os.getenv(\"MOLECULE_EPHEMERAL_DIRECTORY\")\n    if not d:\n        d = os.getenv(\"XDG_CACHE_HOME\", os.path.expanduser(\"~/.cache\"))\n    if not d:\n        raise RuntimeError(\"Unable to determine ephemeral directory to use.\")\n    d = os.path.abspath(os.path.join(d, path if path else \"molecule\"))\n\n    if not os.path.isdir(d):\n        os.umask(0o077)\n        Path(d).mkdir(mode=0o700, parents=True, exist_ok=True)\n\n    return d\n", "path": "src/molecule/scenario.py"}], "after_files": [{"content": "#  Copyright (c) 2015-2018 Cisco Systems, Inc.\n#\n#  Permission is hereby granted, free of charge, to any person obtaining a copy\n#  of this software and associated documentation files (the \"Software\"), to\n#  deal in the Software without restriction, including without limitation the\n#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n#  sell copies of the Software, and to permit persons to whom the Software is\n#  furnished to do so, subject to the following conditions:\n#\n#  The above copyright notice and this permission notice shall be included in\n#  all copies or substantial portions of the Software.\n#\n#  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n#  DEALINGS IN THE SOFTWARE.\n\"\"\"Molecule Scenario Module.\"\"\"\n\nimport errno\nimport fcntl\nimport fnmatch\nimport logging\nimport os\nimport shutil\nfrom pathlib import Path\nfrom time import sleep\nfrom typing import Optional\n\nfrom molecule import scenarios, util\nfrom molecule.constants import RC_TIMEOUT\n\nLOG = logging.getLogger(__name__)\n\n\nclass Scenario(object):\n    \"\"\"\n    A scenario allows Molecule test a role in a particular way, this is a \\\n    fundamental change from Molecule v1.\n\n    A scenario is a self-contained directory containing everything necessary\n    for testing the role in a particular way.  The default scenario is named\n    ``default``, and every role should contain a default scenario.\n\n    Unless mentioned explicitly, the scenario name will be the directory name\n    hosting the files.\n\n    Any option set in this section will override the defaults.\n\n    .. code-block:: yaml\n\n        scenario:\n          create_sequence:\n            - dependency\n            - create\n            - prepare\n          check_sequence:\n            - dependency\n            - cleanup\n            - destroy\n            - create\n            - prepare\n            - converge\n            - check\n            - destroy\n          converge_sequence:\n            - dependency\n            - create\n            - prepare\n            - converge\n          destroy_sequence:\n            - dependency\n            - cleanup\n            - destroy\n          test_sequence:\n            - dependency\n            - lint\n            - cleanup\n            - destroy\n            - syntax\n            - create\n            - prepare\n            - converge\n            - idempotence\n            - side_effect\n            - verify\n            - cleanup\n            - destroy\n\n    :Advanced testing\n\n    If needed, Molecule can run multiple side effects and tests within a scenario.\n    This allows to perform advanced testing for stateful software under role/playbook\n    management. Actions `side_effect` and `verify` can take optional arguments to change\n    the playbook/test they execute.\n\n    Example of test sequence with multiple side effects and tests:\n\n    .. code-block:: yaml\n\n         test_sequence:\n           - converge\n           - side_effect reboot.yaml\n           - verify after_reboot/\n           - side_effect alter_configs.yaml\n           - converge\n           - verify other_test1.py other_test2.py\n           - side_effect\n           - verify\n\n    ``side_effect`` without an argument is executing the usual `side_effect` configured in\n      `provisioner.playbooks` section of molecule.yml.\n\n    ``side_effect`` can have one or more arguments (separated by spaces) which is\n    a playbook (plabyooks) to execute. If the argument for ``side_effect`` is present,\n    it's executed instead. The path to the playbook is relative to the molecule.yml location.\n    Normal side effect settings (from `provisioner.playbooks`) are ignored for action with\n    argument.\n\n    ``verify`` without an argument is executing usual tests configured in the verifier section\n    of molecule.yml.\n\n    If one or more arguments (separated by spaces) are present, each argument is treated\n    as a test name (file or directory) to pass to the verifier (either Ansible or Testinfra).\n    The kind of verifier is set in the `verifier` section of molecule.yml and is applied to all\n    `verify` actions in the scenario.\n\n    The path to tests is relative to the molecule.yml file location. The `additional_files_or_dirs`\n    setting for verifier is ignored if the `verify` action has an argument.\n\n    Multiple `side_effect` and `verify` actions can be used to a create a combination\n    of playbooks and tests, for example, for end-to-end playbook testing.\n\n    Additional `converge` and `idempotence` actions can be used multiple times:\n\n    .. code-block:: yaml\n\n         test_sequence:\n           - converge\n           - idempotence\n           - side_effect\n           - verify\n           - converge\n           - idempotence\n           - side_effect effect2.yml\n           - converge\n           - idempotence\n           - verify test2/\n           - side_effect effect3.yml\n           - verify test3/\n           - idempotence\n\n    \"\"\"  # noqa\n\n    def __init__(self, config):\n        \"\"\"\n        Initialize a new scenario class and returns None.\n\n        :param config: An instance of a Molecule config.\n        :return: None\n        \"\"\"\n        self._lock = None\n        self.config = config\n        self._setup()\n\n    def _remove_scenario_state_directory(self):\n        \"\"\"Remove scenario cached disk stored state.\n\n        :return: None\n        \"\"\"\n        directory = str(Path(self.ephemeral_directory).parent)\n        LOG.info(\"Removing %s\", directory)\n        shutil.rmtree(directory)\n\n    def prune(self):\n        \"\"\"\n        Prune the scenario ephemeral directory files and returns None.\n\n        \"safe files\" will not be pruned, including the ansible configuration\n        and inventory used by this scenario, the scenario state file, and\n        files declared as \"safe_files\" in the ``driver`` configuration\n        declared in ``molecule.yml``.\n\n        :return: None\n        \"\"\"\n        LOG.info(\"Pruning extra files from scenario ephemeral directory\")\n        safe_files = [\n            self.config.provisioner.config_file,\n            self.config.provisioner.inventory_file,\n            self.config.state.state_file,\n        ] + self.config.driver.safe_files\n        files = util.os_walk(self.ephemeral_directory, \"*\")\n        for f in files:\n            if not any(sf for sf in safe_files if fnmatch.fnmatch(f, sf)):\n                try:\n                    os.remove(f)\n                except OSError as e:\n                    if e.errno != errno.ENOENT:\n                        raise\n\n        # Remove empty directories.\n        for dirpath, dirs, files in os.walk(self.ephemeral_directory, topdown=False):\n            if not dirs and not files:\n                os.removedirs(dirpath)\n\n    @property\n    def name(self):\n        return self.config.config[\"scenario\"][\"name\"]\n\n    @property\n    def directory(self):\n        if self.config.molecule_file:\n            return os.path.dirname(self.config.molecule_file)\n        else:\n            return os.getcwd()\n\n    @property\n    def ephemeral_directory(self):\n        path = os.getenv(\"MOLECULE_EPHEMERAL_DIRECTORY\", None)\n        if not path:\n\n            project_directory = os.path.basename(self.config.project_directory)\n\n            if self.config.is_parallel:\n                project_directory = f\"{project_directory}-{self.config._run_uuid}\"\n\n            project_scenario_directory = os.path.join(\n                self.config.cache_directory, project_directory, self.name\n            )\n\n            path = ephemeral_directory(project_scenario_directory)\n\n        if os.environ.get(\"MOLECULE_PARALLEL\", False) and not self._lock:\n            with open(os.path.join(path, \".lock\"), \"w\") as self._lock:\n                for i in range(1, 5):\n                    try:\n                        fcntl.lockf(self._lock, fcntl.LOCK_EX | fcntl.LOCK_NB)\n                        break\n                    except OSError:\n                        delay = 30 * i\n                        LOG.warning(\n                            \"Retrying to acquire lock on %s, waiting for %s seconds\",\n                            path,\n                            delay,\n                        )\n                        sleep(delay)\n                else:\n                    LOG.warning(\"Timedout trying to acquire lock on %s\", path)\n                    raise SystemExit(RC_TIMEOUT)\n\n        return path\n\n    @property\n    def inventory_directory(self):\n        return os.path.join(self.ephemeral_directory, \"inventory\")\n\n    @property\n    def check_sequence(self):\n        return self.config.config[\"scenario\"][\"check_sequence\"]\n\n    @property\n    def cleanup_sequence(self):\n        return self.config.config[\"scenario\"][\"cleanup_sequence\"]\n\n    @property\n    def converge_sequence(self):\n        return self.config.config[\"scenario\"][\"converge_sequence\"]\n\n    @property\n    def create_sequence(self):\n        return self.config.config[\"scenario\"][\"create_sequence\"]\n\n    @property\n    def dependency_sequence(self):\n        return [\"dependency\"]\n\n    @property\n    def destroy_sequence(self):\n        return self.config.config[\"scenario\"][\"destroy_sequence\"]\n\n    @property\n    def idempotence_sequence(self):\n        return [\"idempotence\"]\n\n    @property\n    def lint_sequence(self):\n        # see https://github.com/ansible-community/molecule/issues/2216\n        return [\"dependency\", \"lint\"]\n\n    @property\n    def prepare_sequence(self):\n        return [\"prepare\"]\n\n    @property\n    def side_effect_sequence(self):\n        return [\"side_effect\"]\n\n    @property\n    def syntax_sequence(self):\n        return [\"syntax\"]\n\n    @property\n    def test_sequence(self):\n        return self.config.config[\"scenario\"][\"test_sequence\"]\n\n    @property\n    def verify_sequence(self):\n        return [\"verify\"]\n\n    @property\n    def sequence(self):\n        \"\"\"\n        Select the sequence based on scenario and subcommand of the provided \\\n        scenario object and returns a list.\n\n        :param scenario: A scenario object.\n        :param skipped: An optional bool to include skipped scenarios.\n        :return: list\n        \"\"\"\n        s = scenarios.Scenarios([self.config])\n        matrix = s._get_matrix()\n\n        try:\n            return matrix[self.name][self.config.subcommand]\n        except KeyError:\n            # TODO(retr0h): May change this handling in the future.\n            return []\n\n    def _setup(self):\n        \"\"\"\n        Prepare the scenario for Molecule and returns None.\n\n        :return: None\n        \"\"\"\n        if not os.path.isdir(self.inventory_directory):\n            os.makedirs(self.inventory_directory, exist_ok=True)\n\n\ndef ephemeral_directory(path: Optional[str] = None) -> str:\n    \"\"\"\n    Return temporary directory to be used by molecule.\n\n    Molecule users should not make any assumptions about its location,\n    permissions or its content as this may change in future release.\n    \"\"\"\n    d = os.getenv(\"MOLECULE_EPHEMERAL_DIRECTORY\")\n    if not d:\n        d = os.getenv(\"XDG_CACHE_HOME\", os.path.expanduser(\"~/.cache\"))\n    if not d:\n        raise RuntimeError(\"Unable to determine ephemeral directory to use.\")\n    d = os.path.abspath(os.path.join(d, path if path else \"molecule\"))\n\n    if not os.path.isdir(d):\n        os.umask(0o077)\n        Path(d).mkdir(mode=0o700, parents=True, exist_ok=True)\n\n    return d\n", "path": "src/molecule/scenario.py"}]}