testshed 0.0.1__tar.gz

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.
Files changed (42) hide show
  1. testshed-0.0.1/LICENSE +21 -0
  2. testshed-0.0.1/PKG-INFO +176 -0
  3. testshed-0.0.1/README.md +147 -0
  4. testshed-0.0.1/pyproject.toml +48 -0
  5. testshed-0.0.1/setup.cfg +4 -0
  6. testshed-0.0.1/src/__init__.py +1 -0
  7. testshed-0.0.1/src/_internal/state.py +35 -0
  8. testshed-0.0.1/src/core/__init__.py +6 -0
  9. testshed-0.0.1/src/core/bootstrap.py +41 -0
  10. testshed-0.0.1/src/core/wrapper.py +42 -0
  11. testshed-0.0.1/src/docker/__init__.py +16 -0
  12. testshed-0.0.1/src/docker/container.py +51 -0
  13. testshed-0.0.1/src/docker/container_config.py +45 -0
  14. testshed-0.0.1/src/docker/decorators.py +23 -0
  15. testshed-0.0.1/src/docker/factory.py +68 -0
  16. testshed-0.0.1/src/docker/inline_volume.py +41 -0
  17. testshed-0.0.1/src/docker/probes/http_probe.py +23 -0
  18. testshed-0.0.1/src/docker/probes/readiness_check.py +48 -0
  19. testshed-0.0.1/src/docker/runtime/cleanup.py +30 -0
  20. testshed-0.0.1/src/docker/runtime/error_handler.py +33 -0
  21. testshed-0.0.1/src/docker/runtime/file_system.py +62 -0
  22. testshed-0.0.1/src/docker/runtime/shell.py +75 -0
  23. testshed-0.0.1/src/docker/volume_manager.py +48 -0
  24. testshed-0.0.1/src/fixtures/__init__.py +56 -0
  25. testshed-0.0.1/src/fixtures/docker.py +36 -0
  26. testshed-0.0.1/src/fixtures/playwright.py +29 -0
  27. testshed-0.0.1/src/fixtures/shed.py +69 -0
  28. testshed-0.0.1/src/playwright/__init__.py +4 -0
  29. testshed-0.0.1/src/playwright/factory.py +33 -0
  30. testshed-0.0.1/src/plugin/__init__.py +17 -0
  31. testshed-0.0.1/src/plugin/addoptions.py +89 -0
  32. testshed-0.0.1/src/plugin/configure.py +63 -0
  33. testshed-0.0.1/src/plugin/presenter.py +21 -0
  34. testshed-0.0.1/src/py.typed +0 -0
  35. testshed-0.0.1/src/utils/network.py +13 -0
  36. testshed-0.0.1/src/utils/version.py +33 -0
  37. testshed-0.0.1/testshed.egg-info/PKG-INFO +176 -0
  38. testshed-0.0.1/testshed.egg-info/SOURCES.txt +40 -0
  39. testshed-0.0.1/testshed.egg-info/dependency_links.txt +1 -0
  40. testshed-0.0.1/testshed.egg-info/entry_points.txt +2 -0
  41. testshed-0.0.1/testshed.egg-info/requires.txt +9 -0
  42. testshed-0.0.1/testshed.egg-info/top_level.txt +1 -0
testshed-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 KloudKIT
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: testshed
3
+ Version: 0.0.1
4
+ Summary: 🧪 The support crew for your test bench
5
+ Author-email: Dov Benyomin Sohacheski <b@kloud.email>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/kloudkit/testshed
8
+ Project-URL: Bug Tracker, https://github.com/kloudkit/testshed/issues
9
+ Project-URL: Source Code, https://github.com/kloudkit/testshed
10
+ Keywords: pytest,docker,fixtures,playwright,testing
11
+ Classifier: Framework :: Pytest
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: playwright<2.0.0,>=1.54.0
21
+ Requires-Dist: pytest<9.0.0,>=8.4.1
22
+ Requires-Dist: python-on-whales<1.0.0,>=0.78.0
23
+ Requires-Dist: PyYAML<7.0.0,>=6.0.2
24
+ Requires-Dist: requests<3.0.0,>=2.32.4
25
+ Provides-Extra: dev
26
+ Requires-Dist: pre-commit>=4.2.0; extra == "dev"
27
+ Requires-Dist: ruff>=0.12.0; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # KloudKIT TestShed
31
+
32
+ `KloudKIT TestShed` is a `pytest` plugin that streamlines integration tests with Docker
33
+ containers and Playwright—handling setup, execution, and teardown so you can focus on
34
+ writing effective tests.
35
+
36
+ ## Features
37
+
38
+ - **Automated Docker management:** Spin up and control containers from tests.
39
+ - **Playwright integration:** Run browser tests in isolated Docker environments.
40
+ - **Configurable via markers & CLI:** Tune environments per test or suite.
41
+ - **Automatic resource cleanup:** Ensures a clean state after tests.
42
+
43
+ ## Installation
44
+
45
+ ```sh
46
+ pip install testshed
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ ### Docker container testing
52
+
53
+ TestShed provides fixtures to manage containers inside your tests.
54
+
55
+ #### High-level `shed` fixtures
56
+
57
+ Use the `shed` fixture for smart container management with configurable defaults:
58
+
59
+ ```python
60
+ import pytest
61
+
62
+ from kloudkit.testshed.docker import Container, HttpProbe
63
+
64
+ class MyAppContainer(Container):
65
+ DEFAULT_USER = "app"
66
+
67
+ @pytest.fixture(scope="session")
68
+ def shed_container_defaults():
69
+ """Override this fixture to set project-specific defaults."""
70
+
71
+ return {
72
+ "container_class": MyAppContainer,
73
+ "envs": {"APP_PORT": 3000},
74
+ "probe": HttpProbe(port=3000, endpoint="/health"),
75
+ }
76
+
77
+ def test_my_app(shed):
78
+ # Uses your configured defaults automatically
79
+ assert shed.execute("whoami") == "app"
80
+
81
+ @shed_env(DEBUG="true")
82
+ def test_my_app_with_debug(shed):
83
+ # New container with override, merged with defaults
84
+ assert shed.execute("echo $DEBUG") == "true"
85
+ assert shed.execute("echo $APP_PORT") == "3000"
86
+ ```
87
+
88
+ You can also use the factory directly:
89
+
90
+ ```python
91
+ def test_custom_setup(shed_factory):
92
+ container = shed_factory(envs={"CUSTOM_VAR": "value"})
93
+ # ... test logic ...
94
+ ```
95
+
96
+ #### Basic Docker container
97
+
98
+ For a lower-level API, use the `docker_sidecar` fixture to create containers:
99
+
100
+ ```python
101
+ import pytest
102
+
103
+ def test_my_docker_app(docker_sidecar):
104
+ # Launch a simple Nginx container
105
+ nginx = docker_sidecar("nginx:latest", publish=[(8080, 80)])
106
+
107
+ # Execute a command inside the container
108
+ assert "nginx version" in nginx.execute(["nginx", "-v"])
109
+
110
+ # Access the container's IP
111
+ print(f"Nginx container IP: {nginx.ip()}")
112
+
113
+ # Interact with the file system
114
+ assert "/usr/share/nginx/html" in nginx.fs.ls("/usr/share/nginx")
115
+ ```
116
+
117
+ #### Configure containers with decorators
118
+
119
+ Configure containers using `pytest` markers/decorators:
120
+
121
+ - **`@shed_config(**kwargs)`:** Generic container args.
122
+ - **`@shed_env(**envs)`:** Environment variables.
123
+ - **`@shed_volumes(*mounts)`:** Volume mounts as `(source, dest)` or `InlineVolume`.
124
+
125
+ ```python
126
+ from kloudkit.testshed.docker import InlineVolume
127
+
128
+ @shed_env(MY_ENV_VAR="hello")
129
+ @shed_volumes(
130
+ ("/path/to/host/data", "/app/data:ro"),
131
+ InlineVolume("/app/config.txt", "any content you want", mode=0o644),
132
+ )
133
+ def test_configured_docker_app(docker_sidecar):
134
+ app = docker_sidecar("my-custom-app:latest")
135
+ # ... test logic ...
136
+ ```
137
+
138
+ ### Playwright browser testing
139
+
140
+ Get a Playwright browser instance running in Docker via `playwright_browser`:
141
+
142
+ ```python
143
+ def test_example_website(playwright_browser):
144
+ page = playwright_browser.new_page()
145
+ page.goto("http://example.com")
146
+ assert "Example Domain" in page.title()
147
+ # ... more Playwright test logic ...
148
+ ```
149
+
150
+ ### Command-line options
151
+
152
+ TestShed extends `pytest` with options to control the Docker environment:
153
+
154
+ - `--shed`: Enable TestShed for the current test suite *(default: disabled)*.
155
+ - `--shed-image IMAGE`: Base image *(e.g., `ghcr.io/acme/app`)*.
156
+ - `--shed-tag TAG|SHA`: Image tag or digest *(default: `tests`)*.
157
+ - `--shed-build-context DIR`: Docker build context *(default: `pytest.ini` directory)*.
158
+ - `--shed-require-local-image`: Fail if image isn’t present locally *(no build/pull)*.
159
+ - `--shed-rebuild`: Force rebuilding the test image.
160
+ - `--shed-network NAME`: Docker network *(default: `testshed-network`)*.
161
+ - `--shed-skip-bootstrap`: Skip Docker bootstrapping *(useful for unit tests)*.
162
+
163
+ > [!NOTE]
164
+ > When TestShed is installed globally, you must explicitly enable it per suite with
165
+ > `--shed`.
166
+ > This prevents it from configuring Docker in projects that don’t use it.
167
+
168
+ #### Examples
169
+
170
+ ```bash
171
+ # Enable TestShed for your suite
172
+ pytest --shed --shed-image my-test-image --shed-rebuild
173
+
174
+ # Run tests without TestShed (default)
175
+ pytest
176
+ ```
@@ -0,0 +1,147 @@
1
+ # KloudKIT TestShed
2
+
3
+ `KloudKIT TestShed` is a `pytest` plugin that streamlines integration tests with Docker
4
+ containers and Playwright—handling setup, execution, and teardown so you can focus on
5
+ writing effective tests.
6
+
7
+ ## Features
8
+
9
+ - **Automated Docker management:** Spin up and control containers from tests.
10
+ - **Playwright integration:** Run browser tests in isolated Docker environments.
11
+ - **Configurable via markers & CLI:** Tune environments per test or suite.
12
+ - **Automatic resource cleanup:** Ensures a clean state after tests.
13
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ pip install testshed
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Docker container testing
23
+
24
+ TestShed provides fixtures to manage containers inside your tests.
25
+
26
+ #### High-level `shed` fixtures
27
+
28
+ Use the `shed` fixture for smart container management with configurable defaults:
29
+
30
+ ```python
31
+ import pytest
32
+
33
+ from kloudkit.testshed.docker import Container, HttpProbe
34
+
35
+ class MyAppContainer(Container):
36
+ DEFAULT_USER = "app"
37
+
38
+ @pytest.fixture(scope="session")
39
+ def shed_container_defaults():
40
+ """Override this fixture to set project-specific defaults."""
41
+
42
+ return {
43
+ "container_class": MyAppContainer,
44
+ "envs": {"APP_PORT": 3000},
45
+ "probe": HttpProbe(port=3000, endpoint="/health"),
46
+ }
47
+
48
+ def test_my_app(shed):
49
+ # Uses your configured defaults automatically
50
+ assert shed.execute("whoami") == "app"
51
+
52
+ @shed_env(DEBUG="true")
53
+ def test_my_app_with_debug(shed):
54
+ # New container with override, merged with defaults
55
+ assert shed.execute("echo $DEBUG") == "true"
56
+ assert shed.execute("echo $APP_PORT") == "3000"
57
+ ```
58
+
59
+ You can also use the factory directly:
60
+
61
+ ```python
62
+ def test_custom_setup(shed_factory):
63
+ container = shed_factory(envs={"CUSTOM_VAR": "value"})
64
+ # ... test logic ...
65
+ ```
66
+
67
+ #### Basic Docker container
68
+
69
+ For a lower-level API, use the `docker_sidecar` fixture to create containers:
70
+
71
+ ```python
72
+ import pytest
73
+
74
+ def test_my_docker_app(docker_sidecar):
75
+ # Launch a simple Nginx container
76
+ nginx = docker_sidecar("nginx:latest", publish=[(8080, 80)])
77
+
78
+ # Execute a command inside the container
79
+ assert "nginx version" in nginx.execute(["nginx", "-v"])
80
+
81
+ # Access the container's IP
82
+ print(f"Nginx container IP: {nginx.ip()}")
83
+
84
+ # Interact with the file system
85
+ assert "/usr/share/nginx/html" in nginx.fs.ls("/usr/share/nginx")
86
+ ```
87
+
88
+ #### Configure containers with decorators
89
+
90
+ Configure containers using `pytest` markers/decorators:
91
+
92
+ - **`@shed_config(**kwargs)`:** Generic container args.
93
+ - **`@shed_env(**envs)`:** Environment variables.
94
+ - **`@shed_volumes(*mounts)`:** Volume mounts as `(source, dest)` or `InlineVolume`.
95
+
96
+ ```python
97
+ from kloudkit.testshed.docker import InlineVolume
98
+
99
+ @shed_env(MY_ENV_VAR="hello")
100
+ @shed_volumes(
101
+ ("/path/to/host/data", "/app/data:ro"),
102
+ InlineVolume("/app/config.txt", "any content you want", mode=0o644),
103
+ )
104
+ def test_configured_docker_app(docker_sidecar):
105
+ app = docker_sidecar("my-custom-app:latest")
106
+ # ... test logic ...
107
+ ```
108
+
109
+ ### Playwright browser testing
110
+
111
+ Get a Playwright browser instance running in Docker via `playwright_browser`:
112
+
113
+ ```python
114
+ def test_example_website(playwright_browser):
115
+ page = playwright_browser.new_page()
116
+ page.goto("http://example.com")
117
+ assert "Example Domain" in page.title()
118
+ # ... more Playwright test logic ...
119
+ ```
120
+
121
+ ### Command-line options
122
+
123
+ TestShed extends `pytest` with options to control the Docker environment:
124
+
125
+ - `--shed`: Enable TestShed for the current test suite *(default: disabled)*.
126
+ - `--shed-image IMAGE`: Base image *(e.g., `ghcr.io/acme/app`)*.
127
+ - `--shed-tag TAG|SHA`: Image tag or digest *(default: `tests`)*.
128
+ - `--shed-build-context DIR`: Docker build context *(default: `pytest.ini` directory)*.
129
+ - `--shed-require-local-image`: Fail if image isn’t present locally *(no build/pull)*.
130
+ - `--shed-rebuild`: Force rebuilding the test image.
131
+ - `--shed-network NAME`: Docker network *(default: `testshed-network`)*.
132
+ - `--shed-skip-bootstrap`: Skip Docker bootstrapping *(useful for unit tests)*.
133
+
134
+ > [!NOTE]
135
+ > When TestShed is installed globally, you must explicitly enable it per suite with
136
+ > `--shed`.
137
+ > This prevents it from configuring Docker in projects that don’t use it.
138
+
139
+ #### Examples
140
+
141
+ ```bash
142
+ # Enable TestShed for your suite
143
+ pytest --shed --shed-image my-test-image --shed-rebuild
144
+
145
+ # Run tests without TestShed (default)
146
+ pytest
147
+ ```
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "testshed"
7
+ description = "🧪 The support crew for your test bench"
8
+ requires-python = ">=3.11"
9
+ authors = [{ name = "Dov Benyomin Sohacheski", email = "b@kloud.email" }]
10
+ license = { text = "MIT" }
11
+ keywords = ["pytest", "docker", "fixtures", "playwright", "testing"]
12
+ classifiers = [
13
+ "Framework :: Pytest",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Typing :: Typed",
19
+ ]
20
+ dependencies = [
21
+ "playwright>=1.54.0,<2.0.0",
22
+ "pytest>=8.4.1,<9.0.0",
23
+ "python-on-whales>=0.78.0,<1.0.0",
24
+ "PyYAML>=6.0.2,<7.0.0",
25
+ "requests>=2.32.4,<3.0.0",
26
+ ]
27
+ dynamic = ["readme", "version"]
28
+
29
+ [project.urls]
30
+ "Homepage" = "https://github.com/kloudkit/testshed"
31
+ "Bug Tracker" = "https://github.com/kloudkit/testshed/issues"
32
+ "Source Code" = "https://github.com/kloudkit/testshed"
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["pre-commit>=4.2.0", "ruff>=0.12.0"]
36
+
37
+ [project.entry-points.pytest11]
38
+ testshed = "kloudkit.testshed.plugin"
39
+
40
+ [tool.setuptools]
41
+ package-dir = { "kloudkit.testshed" = "src" }
42
+
43
+ [tool.setuptools.dynamic]
44
+ readme = { file = ["README.md"], content-type = "text/markdown" }
45
+ version = { attr = "kloudkit.testshed.__version__" }
46
+
47
+ [tool.coverage.report]
48
+ exclude_lines = ["@(abc\\.)?abstractmethod", "# pragma: no cover"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"
@@ -0,0 +1,35 @@
1
+ from dataclasses import dataclass, field
2
+ from pathlib import Path
3
+
4
+
5
+ @dataclass(slots=True)
6
+ class Options:
7
+ labels: dict[str, str] = field(
8
+ default_factory=lambda: {"com.kloudkit.testshed": "testing-container"}
9
+ )
10
+ network: str = "testshed-network"
11
+ image: str | None = None
12
+ tag: str = "tests"
13
+ src_path: Path | None = None
14
+ tests_path: Path | None = None
15
+ stubs_path: Path | None = None
16
+
17
+ @property
18
+ def image_and_tag(self) -> str:
19
+ """Fully-qualified Docker testing image for test runs."""
20
+
21
+ sep = ":"
22
+
23
+ if self.tag.startswith("sha"):
24
+ sep = "@"
25
+
26
+ return f"{self.image}{sep}{self.tag}"
27
+
28
+
29
+ _state: Options = Options()
30
+
31
+
32
+ def get_state() -> Options:
33
+ """Get the current state."""
34
+
35
+ return _state
@@ -0,0 +1,6 @@
1
+ from kloudkit.testshed.core.wrapper import Wrapper
2
+
3
+
4
+ __all__ = (
5
+ "Wrapper",
6
+ )
@@ -0,0 +1,41 @@
1
+ from pathlib import Path
2
+
3
+ from python_on_whales import docker
4
+
5
+ import pytest
6
+
7
+
8
+ def init_shed_network(network: str) -> None:
9
+ """Ensure the required Docker network exists."""
10
+
11
+ if not docker.network.exists(network):
12
+ docker.network.create(network)
13
+
14
+
15
+ def init_shed_image(
16
+ image: str,
17
+ *,
18
+ require_local_image: bool,
19
+ force_build: bool,
20
+ context_path: Path,
21
+ ) -> None:
22
+ """Build the Docker image when missing or rebuild is forced."""
23
+
24
+ image_missing = not docker.image.exists(image)
25
+
26
+ if image_missing and require_local_image:
27
+ pytest.exit(f"Required image [{image}] not found. Aborting")
28
+
29
+ if image_missing:
30
+ print(f"Testing image [{image}] not found")
31
+ force_build = True
32
+
33
+ if force_build:
34
+ print(f"Forcing build of test image [{image}]")
35
+
36
+ docker.build(
37
+ context_path=context_path,
38
+ pull=True,
39
+ progress="plain",
40
+ tags=image,
41
+ )
@@ -0,0 +1,42 @@
1
+ from types import SimpleNamespace
2
+ from typing import Any, Generic, TypeVar
3
+
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ class Wrapper(Generic[T]):
9
+ __slots__ = ("_wrapped", "_args")
10
+
11
+ def __init__(self, wrapped: T, **kwargs: Any) -> None:
12
+ self._wrapped: T = wrapped
13
+ self._args = SimpleNamespace(**kwargs)
14
+
15
+ def __getattr__(self, name: str) -> Any:
16
+ """Delegate lookups to the wrapped object."""
17
+
18
+ return getattr(self._wrapped, name)
19
+
20
+ def __dir__(self) -> list[str]:
21
+ """Augment `dir` with attributes from the wrapped object."""
22
+
23
+ return sorted(set(super().__dir__()) | set(dir(self._wrapped)))
24
+
25
+ def __repr__(self) -> str:
26
+ """Representation of the wrapper, including passed keyword args."""
27
+
28
+ args_dict = vars(self._args)
29
+ suffix = ""
30
+
31
+ if args_dict:
32
+ kv = ", ".join(f"{k}={args_dict[k]!r}" for k in sorted(args_dict))
33
+
34
+ suffix = f", {kv}"
35
+
36
+ return f"{type(self).__name__}({self._wrapped!r}{suffix})"
37
+
38
+ @property
39
+ def wrapped(self) -> T:
40
+ """Get the underlying wrapped object with its precise type."""
41
+
42
+ return self._wrapped
@@ -0,0 +1,16 @@
1
+ from python_on_whales import DockerException, docker
2
+
3
+ from kloudkit.testshed.docker.container import Container
4
+ from kloudkit.testshed.docker.factory import Factory
5
+ from kloudkit.testshed.docker.inline_volume import InlineVolume
6
+ from kloudkit.testshed.docker.probes.http_probe import HttpProbe
7
+
8
+
9
+ __all__ = (
10
+ "Container",
11
+ "docker",
12
+ "DockerException",
13
+ "Factory",
14
+ "HttpProbe",
15
+ "InlineVolume",
16
+ )
@@ -0,0 +1,51 @@
1
+ from python_on_whales import Container as NativeContainer, docker
2
+
3
+ from kloudkit.testshed.core.wrapper import Wrapper
4
+ from kloudkit.testshed.docker.runtime.file_system import FileSystem
5
+ from kloudkit.testshed.docker.runtime.shell import Shell
6
+
7
+
8
+ class Container(Wrapper[NativeContainer]):
9
+ BASH_PATH = "/bin/bash"
10
+ SH_PATH = "/bin/sh"
11
+ ZSH_PATH = "/usr/bin/zsh"
12
+
13
+ LOGIN_SHELL = False
14
+ DEFAULT_USER = None
15
+ DEFAULT_SHELL = None
16
+
17
+ def ip(self) -> str:
18
+ """Retrieve internal IP address of container."""
19
+
20
+ return self.execute(["hostname", "-i"])
21
+
22
+ @property
23
+ def fs(self) -> FileSystem:
24
+ """Higher order file system."""
25
+
26
+ return FileSystem(self)
27
+
28
+ @property
29
+ def execute(self) -> Shell:
30
+ """Higher order execution."""
31
+
32
+ return Shell(
33
+ self._wrapped,
34
+ bash_path=self.BASH_PATH,
35
+ sh_path=self.SH_PATH,
36
+ zsh_path=self.ZSH_PATH,
37
+ user=self.DEFAULT_USER,
38
+ shell=self.DEFAULT_SHELL,
39
+ login_shell=self.LOGIN_SHELL,
40
+ )
41
+
42
+ @classmethod
43
+ def run(cls, *args, **kwargs):
44
+ """Wrap the native `docker.run`."""
45
+
46
+ instance = docker.run(*args, **kwargs)
47
+
48
+ if isinstance(instance, str):
49
+ return instance
50
+
51
+ return cls(instance)
@@ -0,0 +1,45 @@
1
+ from dataclasses import dataclass
2
+ from typing import Self
3
+
4
+ import pytest
5
+
6
+
7
+ @dataclass(frozen=True, slots=True)
8
+ class ContainerConfig:
9
+ envs: dict[str, str]
10
+ volumes: tuple[str, ...]
11
+ args: dict[str, str]
12
+ test_name: str
13
+
14
+ @property
15
+ def has_overrides(self) -> bool:
16
+ """If any fields are non-empty."""
17
+
18
+ return bool(self.envs or self.volumes or self.args)
19
+
20
+ def to_dict(self) -> dict:
21
+ """Return a plain dict with merged configs."""
22
+
23
+ return dict(
24
+ envs=self.envs,
25
+ volumes=self.volumes,
26
+ test_name=self.test_name,
27
+ **self.args,
28
+ )
29
+
30
+ @classmethod
31
+ def create(cls, request: pytest.FixtureRequest) -> Self:
32
+ """Create configs from a pytest request of the current node."""
33
+
34
+ item = request.node
35
+
36
+ config_marker = item.get_closest_marker("shed_config")
37
+ env_marker = item.get_closest_marker("shed_env")
38
+ volumes_marker = item.get_closest_marker("shed_volumes")
39
+
40
+ return cls(
41
+ envs=env_marker.kwargs if env_marker else {},
42
+ volumes=tuple(volumes_marker.args) if volumes_marker else tuple(),
43
+ test_name=item.nodeid,
44
+ args=(config_marker.kwargs if config_marker else {}),
45
+ )
@@ -0,0 +1,23 @@
1
+ from kloudkit.testshed.docker.inline_volume import InlineVolume
2
+
3
+ import pytest
4
+
5
+
6
+ def shed_config(**configs) -> pytest.MarkDecorator:
7
+ """Assign generic configs to the `shed` instance."""
8
+
9
+ return pytest.mark.shed_config(**configs)
10
+
11
+
12
+ def shed_env(**envs) -> pytest.MarkDecorator:
13
+ """Assign environment variables to the `shed` instance."""
14
+
15
+ return pytest.mark.shed_env(**envs)
16
+
17
+
18
+ def shed_volumes(
19
+ *mounts: tuple[str, str] | InlineVolume,
20
+ ) -> pytest.MarkDecorator:
21
+ """Assign volume mounts to the `shed` instance."""
22
+
23
+ return pytest.mark.shed_volumes(*mounts)