Skip to content

Commit dd9da4b

Browse files
committed
Simplify Docker runner refresh controls
Replace low-level image build/pull knobs with user-facing lifecycle flags: - --refresh-image rebuilds the local image - --reset-cache recreates the cache volume - --fresh does both Keep automatic rebuilds when the docker context changes or the image is missing, and document how to refresh Sublime/Package Control state without direct docker commands.
1 parent e76790d commit dd9da4b

3 files changed

Lines changed: 98 additions & 49 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ Useful options:
151151
- `--pattern test_foo.py --tests-dir tests/subdir`
152152
- `--coverage`
153153
- `--failfast`
154+
- `--scheduler-delay-ms 0` (default)
155+
- `--refresh-cache` (re-bootstrap cached `/root` state)
156+
- `--refresh-image` (rebuild local Docker image)
157+
- `--refresh` (both cache and image refresh)
158+
- `--no-cache-volume` (run without persistent cache)
154159

155160
> [!TIP]
156161
>

docker/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,23 @@ The container entrypoint writes a marker in `/root/.cache/unittesting`.
5050
With `-v unittesting-home:/root`, bootstrap/install runs once and later runs
5151
only refresh your package files and execute tests.
5252

53+
## Refresh/update controls (without direct docker commands)
54+
55+
Use launcher flags instead of calling `docker` manually:
56+
57+
- `--refresh-cache`: recreate `unittesting-home` cache volume (forces fresh
58+
bootstrap, including Sublime Text/Package Control install path)
59+
- `--refresh-image`: rebuild local image (for Dockerfile/entrypoint changes)
60+
- `--refresh`: both `--refresh-cache` and `--refresh-image`
61+
62+
Examples:
63+
64+
```sh
65+
ut-run-tests . --refresh-image
66+
ut-run-tests . --refresh-cache
67+
ut-run-tests . --refresh
68+
```
69+
5370
## Run a single test file
5471

5572
```sh

docker/run_tests.py

Lines changed: 76 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,23 @@ def main(argv: list[str] | None = None) -> int:
3232
args = parse_args(argv)
3333
ensure_docker()
3434

35+
if args.refresh:
36+
args.refresh_image = True
37+
args.refresh_cache = True
38+
39+
image = args.docker_image
40+
41+
if args.refresh_image or args.refresh_cache:
42+
if args.refresh_image:
43+
maybe_build_image(image, refresh=True)
44+
45+
if args.cache_volume and args.refresh_cache:
46+
reset_docker_volume(args.cache_volume)
47+
ensure_docker_volume(args.cache_volume)
48+
49+
print("Refresh complete.")
50+
return 0
51+
3552
package_root = args.package_root.resolve()
3653
if not package_root.is_dir():
3754
print(f"Error: package root does not exist: {package_root}", file=sys.stderr)
@@ -50,11 +67,7 @@ def main(argv: list[str] | None = None) -> int:
5067
package_root, args.file, args.tests_dir, args.pattern
5168
)
5269

53-
image = args.docker_image
54-
maybe_build_image(image, args)
55-
56-
if args.pull:
57-
run_checked(["docker", "pull", image])
70+
maybe_build_image(image, refresh=False)
5871

5972
if args.cache_volume:
6073
ensure_docker_volume(args.cache_volume)
@@ -77,8 +90,12 @@ def main(argv: list[str] | None = None) -> int:
7790
print(f"Package name: {package_name}")
7891
print(f"Docker image: {image}")
7992
print(f"Scheduler delay: {args.scheduler_delay_ms}ms")
93+
if args.refresh_image:
94+
print("Image refresh: enabled")
8095
if args.cache_volume:
8196
print(f"Cache volume: {args.cache_volume}")
97+
if args.refresh_cache:
98+
print("Cache refresh: enabled")
8299
if tests_dir and pattern:
83100
print(f"Test target: {tests_dir}/{pattern}")
84101

@@ -97,75 +114,75 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace:
97114
type=Path,
98115
help="Path to the package root (default: current directory).",
99116
)
100-
parser.add_argument("--file", help="Run only tests from this file.")
101-
parser.add_argument("--pattern", help="Custom unittest discovery pattern.")
102-
parser.add_argument("--tests-dir", help="Custom tests directory.")
103-
parser.add_argument("--package-name", help="Override package name.")
104-
parser.add_argument("--coverage", action="store_true", help="Enable coverage.")
105-
parser.add_argument("--failfast", action="store_true", help="Stop on first failure.")
106117

107-
parser.add_argument(
118+
test_group = parser.add_argument_group("test options")
119+
test_group.add_argument("--file", help="Run only tests from this file.")
120+
test_group.add_argument("--pattern", help="Custom unittest discovery pattern.")
121+
test_group.add_argument("--tests-dir", help="Custom tests directory.")
122+
test_group.add_argument("--package-name", help="Override package name.")
123+
test_group.add_argument("--coverage", action="store_true", help="Enable coverage.")
124+
test_group.add_argument("--failfast", action="store_true", help="Stop on first failure.")
125+
test_group.add_argument(
126+
"--scheduler-delay-ms",
127+
type=int,
128+
default=0,
129+
help="Delay before running scheduled tests inside Sublime (default: 0).",
130+
)
131+
132+
docker_group = parser.add_argument_group("docker options")
133+
docker_group.add_argument(
134+
"--refresh",
135+
action="store_true",
136+
help="Rebuild image and recreate cache volume.",
137+
)
138+
docker_group.add_argument(
108139
"--docker-image",
109140
default=DEFAULT_IMAGE,
110141
help=f"Docker image to run (default: {DEFAULT_IMAGE}).",
111142
)
112-
parser.add_argument(
143+
docker_group.add_argument(
144+
"--refresh-image",
145+
action="store_true",
146+
help="Rebuild the local Docker image.",
147+
)
148+
docker_group.add_argument(
113149
"--cache-volume",
114150
default=DEFAULT_CACHE_VOLUME,
115151
help=(
116152
"Docker volume mounted at /root to cache Sublime setup "
117153
f"(default: {DEFAULT_CACHE_VOLUME})."
118154
),
119155
)
120-
parser.add_argument(
156+
docker_group.add_argument(
121157
"--no-cache-volume",
122158
dest="cache_volume",
123159
action="store_const",
124160
const=None,
125161
help="Disable persistent cache volume.",
126162
)
127-
parser.add_argument(
163+
docker_group.add_argument(
128164
"--sublime-text-version",
129165
type=int,
130166
default=4,
131167
help="Sublime Text major version inside container.",
132168
)
133-
parser.add_argument(
134-
"--scheduler-delay-ms",
135-
type=int,
136-
default=0,
137-
help="Delay before running scheduled tests inside Sublime (default: 0).",
138-
)
139-
parser.add_argument(
140-
"--pull",
141-
action="store_true",
142-
help="Pull docker image before running.",
143-
)
144-
145-
parser.add_argument(
146-
"--build-image",
169+
docker_group.add_argument(
170+
"--refresh-cache",
147171
action="store_true",
148-
help="Force rebuild of local docker image from script directory.",
149-
)
150-
parser.add_argument(
151-
"--build-if-missing",
152-
dest="build_if_missing",
153-
action="store_true",
154-
default=True,
155-
help="Build image from script directory if missing (default: true).",
156-
)
157-
parser.add_argument(
158-
"--no-build-if-missing",
159-
dest="build_if_missing",
160-
action="store_false",
161-
help="Do not auto-build image if missing.",
172+
help=(
173+
"Recreate the cache volume so Sublime Text and Package Control "
174+
"are re-installed."
175+
),
162176
)
163177

164178
args = parser.parse_args(argv)
165179

166180
if args.file and args.pattern:
167181
parser.error("--file and --pattern are mutually exclusive")
168182

183+
if args.refresh_cache and not args.cache_volume:
184+
parser.error("--refresh-cache requires a cache volume (omit --no-cache-volume)")
185+
169186
return args
170187

171188

@@ -174,7 +191,7 @@ def ensure_docker() -> None:
174191
raise SystemExit("Error: docker executable not found in PATH")
175192

176193

177-
def maybe_build_image(image: str, args: argparse.Namespace) -> None:
194+
def maybe_build_image(image: str, refresh: bool) -> None:
178195
context_dir = Path(__file__).resolve().parent
179196
if not context_dir.is_dir():
180197
raise SystemExit(f"Error: missing docker build context: {context_dir}")
@@ -184,14 +201,11 @@ def maybe_build_image(image: str, args: argparse.Namespace) -> None:
184201
image_hash = docker_image_context_hash(image) if image_exists else None
185202
context_changed = image_exists and image_hash != context_hash
186203

187-
should_build = args.build_image
188-
should_build = should_build or (args.build_if_missing and not image_exists)
189-
should_build = should_build or context_changed
190-
204+
should_build = refresh or not image_exists or context_changed
191205
if not should_build:
192206
return
193207

194-
if context_changed and not args.build_image:
208+
if context_changed and not refresh:
195209
print("Docker context changed since last image build, rebuilding...")
196210

197211
print(f"Building docker image '{image}' from {context_dir} ...")
@@ -266,6 +280,19 @@ def ensure_docker_volume(name: str) -> None:
266280
run_checked(["docker", "volume", "create", name])
267281

268282

283+
def reset_docker_volume(name: str) -> None:
284+
result = subprocess.run(
285+
["docker", "volume", "inspect", name],
286+
stdout=subprocess.DEVNULL,
287+
stderr=subprocess.DEVNULL,
288+
)
289+
if result.returncode != 0:
290+
return
291+
292+
print(f"Resetting cache volume: {name}")
293+
run_checked(["docker", "volume", "rm", name])
294+
295+
269296
def resolve_test_target(
270297
package_root: Path,
271298
test_file: str | None,

0 commit comments

Comments
 (0)