Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,34 @@ Useful options:
- `--refresh-image` (rebuild local Docker image)
- `--refresh` (both cache and image refresh)
- `--no-cache-volume` (run without persistent cache)
- `--lock-timeout SECONDS` (wait for shared cache-volume lock)
- `--no-lock` (disable cache-volume serialization; unsafe if shared)

> [!TIP]
>
> This is useful for editor build systems and for AI agents,
> because test runs no longer commandeer your active editor window.

You can also add the headless runner as build system:

```json
"build_systems":
[
{
"name": "Test Package (Docker)",
"shell_cmd": "\"${packages}\\UnitTesting\\docker\\ut-run-tests.cmd\" .",
"working_dir": "$project_path",
"file_regex": "^\\s*File \"/root/\\.config/sublime-text/Packages/[^/]+/(.+)\", line ([0-9]+)",
},
{
"name": "Test Current File (Docker)",
"shell_cmd": "\"${packages}\\UnitTesting\\docker\\ut-run-tests.cmd\" . --file \"${file}\"",
"working_dir": "$project_path",
"file_regex": "^\\s*File \"/root/\\.config/sublime-text/Packages/[^/]+/(.+)\", line ([0-9]+)",
}
]
```


## GitHub Actions

Expand Down
32 changes: 32 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,38 @@ The container entrypoint writes a marker in `/root/.cache/unittesting`.
With `-v unittesting-home:/root`, bootstrap/install runs once and later runs
only refresh your package files and execute tests.

## Serialized runs

The shared cache volume contains the Sublime data directory, including
`Packages`, `Lib`, UnitTesting schedules and test output files. Concurrent
runs against the same volume are serialized by default to avoid races while
copying packages, writing schedules and syncing Package Control libraries.

Use `--lock-timeout SECONDS` to control how long a runner waits for the cache
volume lock. Use `--no-lock` only if you know the selected cache volume is not
shared by another runner.

## Concurrent runs

You can control concurrency by choosing how many cache volumes you use. The
default single volume serializes all runs. A stable volume per package allows
different packages to run concurrently while still keeping warm caches:

```sh
ut-run-tests . --cache-volume unittesting-home-gitsavvy
```

To maximize concurrency, use a stable volume per checkout directory. For
example, in a POSIX shell:

```sh
volume="unittesting-home-$(pwd -P | sha256sum | cut -c1-12)"
ut-run-tests . --cache-volume "$volume"
```

Runs that choose different volumes do not wait on each other. Runs that choose
the same volume remain serialized.

## Refresh/update controls (without direct docker commands)

Use launcher flags instead of calling `docker` manually:
Expand Down
53 changes: 50 additions & 3 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ set -e

PATH="$HOME/.local/bin:$PATH"
BOOTSTRAP_MARKER="$HOME/.cache/unittesting/bootstrap.done"
DEPENDENCY_MARKER_DIR="$HOME/.cache/unittesting/package-dependencies"

ensure_git_identity() {
# Align local container behavior with typical CI runners where git
Expand Down Expand Up @@ -49,6 +50,40 @@ ensure_ci_platform_compat() {
fi
}

package_control_sync_required() {
if [ ! -f "${ST_PACKAGES_DIR%/*}/Installed Packages/Package Control.sublime-package" ]; then
return 0
fi

local dependency_fingerprint
dependency_fingerprint="$(package_dependency_fingerprint)"
if [ -z "$dependency_fingerprint" ]; then
return 1
fi

[ "$(cat "$DEPENDENCY_MARKER_DIR/$PACKAGE.sha256" 2>/dev/null || true)" != "$dependency_fingerprint" ]
}

package_dependency_fingerprint() {
local dependencies_file="$ST_PACKAGES_DIR/$PACKAGE/dependencies.json"
if [ ! -f "$dependencies_file" ]; then
return 0
fi

sha256sum "$dependencies_file" | awk '{ print $1 }'
}

write_package_dependency_marker() {
local dependency_fingerprint
dependency_fingerprint="$(package_dependency_fingerprint)"
if [ -z "$dependency_fingerprint" ]; then
return
fi

mkdir -p "$DEPENDENCY_MARKER_DIR"
printf '%s\n' "$dependency_fingerprint" > "$DEPENDENCY_MARKER_DIR/$PACKAGE.sha256"
}

sudo sh -e /etc/init.d/xvfb start
ensure_git_identity
ensure_ci_platform_compat
Expand All @@ -72,18 +107,30 @@ if [ -d "$UNITTESTING_SOURCE/sbin" ]; then
fi
fi

NEEDS_PACKAGE_CONTROL_SYNC=false
if [ ! -f "$BOOTSTRAP_MARKER" ]; then
# Bootstrap from a neutral cwd to avoid Windows worktree .git indirection
# paths breaking git commands inside the Linux container.
(cd /tmp && /docker.sh bootstrap skip_package_copy)
NEEDS_PACKAGE_CONTROL_SYNC=true
fi

# Always refresh checked-out package into Packages/<PACKAGE> before syncing
# Package Control libraries, so dependencies.json from the package under test
# is visible to Package Control.
/docker.sh copy_tested_package overwrite

if package_control_sync_required; then
NEEDS_PACKAGE_CONTROL_SYNC=true
fi

if [ "$NEEDS_PACKAGE_CONTROL_SYNC" = true ]; then
/docker.sh install_package_control
write_package_dependency_marker
mkdir -p "$(dirname "$BOOTSTRAP_MARKER")"
touch "$BOOTSTRAP_MARKER"
fi

# Always refresh checked-out package into Packages/<PACKAGE>
/docker.sh copy_tested_package overwrite

if [ "$#" -gt 0 ]; then
/docker.sh "$@"
else
Expand Down
Loading
Loading