testisolation 0.0.0__tar.gz → 0.3.0__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.
- testisolation-0.3.0/.github/workflows/ci.yml +33 -0
- testisolation-0.3.0/.github/workflows/publish.yml +195 -0
- testisolation-0.3.0/.gitignore +18 -0
- testisolation-0.3.0/.rlsbl/config.json +14 -0
- testisolation-0.3.0/.rlsbl/lint/python.toml +25 -0
- testisolation-0.3.0/.rlsbl/managed-files.json +9 -0
- testisolation-0.3.0/LICENSE +21 -0
- testisolation-0.3.0/PKG-INFO +50 -0
- testisolation-0.3.0/README.md +28 -0
- testisolation-0.3.0/pyproject.toml +60 -0
- testisolation-0.3.0/src/testisolation/__init__.py +18 -0
- testisolation-0.3.0/src/testisolation/config.py +396 -0
- testisolation-0.3.0/src/testisolation/env_overrides.py +155 -0
- testisolation-0.3.0/src/testisolation/pgcluster.py +696 -0
- testisolation-0.3.0/src/testisolation/plugin.py +158 -0
- testisolation-0.3.0/src/testisolation/pushguard.py +113 -0
- testisolation-0.3.0/src/testisolation/sandbox.py +79 -0
- testisolation-0.3.0/src/testisolation/socketguard.py +226 -0
- testisolation-0.3.0/tests/conftest.py +126 -0
- testisolation-0.3.0/tests/sample_consumer/README.md +14 -0
- testisolation-0.3.0/tests/sample_consumer/acme/__init__.py +48 -0
- testisolation-0.3.0/tests/sample_consumer/pytest.ini +15 -0
- testisolation-0.3.0/tests/sample_consumer/tests/conftest.py +22 -0
- testisolation-0.3.0/tests/sample_consumer/tests/test_git_workflow.py +49 -0
- testisolation-0.3.0/tests/sample_consumer/tests/test_home_isolation.py +32 -0
- testisolation-0.3.0/tests/sample_consumer/tests/test_network_is_off.py +20 -0
- testisolation-0.3.0/tests/test_chdir_isolation.py +74 -0
- testisolation-0.3.0/tests/test_config_keys.py +197 -0
- testisolation-0.3.0/tests/test_cross_language_parity.py +104 -0
- testisolation-0.3.0/tests/test_env_overrides.py +273 -0
- testisolation-0.3.0/tests/test_harness_selfcheck.py +7 -0
- testisolation-0.3.0/tests/test_pg_cluster.py +191 -0
- testisolation-0.3.0/tests/test_pg_socket_path.py +134 -0
- testisolation-0.3.0/tests/test_push_guard.py +250 -0
- testisolation-0.3.0/tests/test_sample_consumer.py +55 -0
- testisolation-0.3.0/tests/test_socket_guard.py +507 -0
- testisolation-0.3.0/tests/test_threshold.py +154 -0
- testisolation-0.3.0/tests/test_tmp_refusal.py +80 -0
- testisolation-0.3.0/uv.lock +109 -0
- testisolation-0.0.0/PKG-INFO +0 -5
- testisolation-0.0.0/pyproject.toml +0 -9
- testisolation-0.0.0/testisolation/__init__.py +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
# Per-SHA group: re-runs of the same commit dedupe, but a new commit never
|
|
11
|
+
# cancels an earlier commit's in-flight run (release CI conclusions stay intact).
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow_ref }}-${{ github.sha }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
test:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
strategy:
|
|
20
|
+
matrix:
|
|
21
|
+
# requires-python: >= 3.11
|
|
22
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v6
|
|
25
|
+
- uses: astral-sh/setup-uv@v7
|
|
26
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
27
|
+
- run: uv sync --locked
|
|
28
|
+
- run: uv run python -c "import testisolation"
|
|
29
|
+
- name: Install gitleaks
|
|
30
|
+
run: |
|
|
31
|
+
GITLEAKS_VERSION=8.24.3
|
|
32
|
+
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" | tar xz -C /usr/local/bin gitleaks
|
|
33
|
+
- run: uv run pytest --rootdir .
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
inputs:
|
|
7
|
+
tag:
|
|
8
|
+
description: Release tag to publish (e.g. v1.2.3). Overrides the ref for
|
|
9
|
+
retry dispatch.
|
|
10
|
+
required: false
|
|
11
|
+
type: string
|
|
12
|
+
|
|
13
|
+
# One publish run per tag: a workflow_dispatch retry at the same tag
|
|
14
|
+
# queues behind the in-flight run instead of racing it. A publish is never
|
|
15
|
+
# cancelled mid-flight.
|
|
16
|
+
concurrency:
|
|
17
|
+
group: publish-${{ inputs.tag || github.ref_name }}
|
|
18
|
+
cancel-in-progress: false
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
id-token: write
|
|
22
|
+
jobs:
|
|
23
|
+
gate:
|
|
24
|
+
name: Gate on CI
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
permissions:
|
|
27
|
+
checks: read
|
|
28
|
+
contents: read
|
|
29
|
+
env:
|
|
30
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
31
|
+
GH_REPO: ${{ github.repository }}
|
|
32
|
+
GATE_TIMEOUT_MINUTES: '20'
|
|
33
|
+
GATE_GRACE_MINUTES: '5'
|
|
34
|
+
GATE_POLL_SECONDS: '15'
|
|
35
|
+
GATE_MARKER_ATTEMPTS: '5'
|
|
36
|
+
GATE_MARKER_RETRY_SECONDS: '5'
|
|
37
|
+
CI_CHECK_REGEX: ^(test)( \(.*\))?$
|
|
38
|
+
steps:
|
|
39
|
+
- name: Wait for CI to succeed on the release commit
|
|
40
|
+
run: |
|
|
41
|
+
set -euo pipefail
|
|
42
|
+
|
|
43
|
+
# Commit resolution -- explicit marker-first order (never reads the release
|
|
44
|
+
# event payload; dispatch retries have none, so this path is uniform for
|
|
45
|
+
# release events and dispatch retries alike):
|
|
46
|
+
# (1) Marker: rlsbl writes the exact commit CI ran on into the GitHub
|
|
47
|
+
# Release body as a machine-parseable line of the form
|
|
48
|
+
# <!-- rlsbl-ci-sha: <40-hex> -->
|
|
49
|
+
# Prefer it when present -- it pins the precise commit and is immune to
|
|
50
|
+
# ref races. A just-created release can lag on a GitHub API read replica
|
|
51
|
+
# (the marker is written at release creation but may not be visible on
|
|
52
|
+
# the first read), so RETRY the read GATE_MARKER_ATTEMPTS times,
|
|
53
|
+
# GATE_MARKER_RETRY_SECONDS apart, before concluding it is absent. The
|
|
54
|
+
# tag is inputs.tag (TAG_INPUT) when dispatched with an explicit
|
|
55
|
+
# override, else GITHUB_REF_NAME (matches the router resolver).
|
|
56
|
+
# (2) Fallback: older releases predate the marker, so fall back to
|
|
57
|
+
# $GITHUB_SHA, the tag's commit for both release and dispatch-at-tag.
|
|
58
|
+
extract_ci_sha() {
|
|
59
|
+
# Emit the first rlsbl-ci-sha marker SHA found on stdin, if any.
|
|
60
|
+
sed -n 's/.*<!-- rlsbl-ci-sha: \([0-9a-f]\{40\}\) -->.*/\1/p' | head -n1
|
|
61
|
+
}
|
|
62
|
+
tag="${TAG_INPUT:-$GITHUB_REF_NAME}"
|
|
63
|
+
marker_attempts="${GATE_MARKER_ATTEMPTS:-5}"
|
|
64
|
+
marker_retry_seconds="${GATE_MARKER_RETRY_SECONDS:-5}"
|
|
65
|
+
sha=""
|
|
66
|
+
attempt=1
|
|
67
|
+
while [ "$attempt" -le "$marker_attempts" ]; do
|
|
68
|
+
if body="$(gh release view "$tag" --json body --jq .body 2>/dev/null)"; then
|
|
69
|
+
sha="$(printf '%s\n' "$body" | extract_ci_sha)"
|
|
70
|
+
fi
|
|
71
|
+
if [ -n "$sha" ]; then
|
|
72
|
+
break
|
|
73
|
+
fi
|
|
74
|
+
if [ "$attempt" -lt "$marker_attempts" ]; then
|
|
75
|
+
echo "Publish gate: rlsbl-ci-sha marker not yet visible in the '$tag' release body (attempt $attempt/$marker_attempts); retrying in ${marker_retry_seconds}s..."
|
|
76
|
+
sleep "$marker_retry_seconds"
|
|
77
|
+
fi
|
|
78
|
+
attempt=$(( attempt + 1 ))
|
|
79
|
+
done
|
|
80
|
+
if [ -n "$sha" ]; then
|
|
81
|
+
echo "Publish gate: resolved release commit from rlsbl-ci-sha marker in the '$tag' release body."
|
|
82
|
+
else
|
|
83
|
+
sha="$GITHUB_SHA"
|
|
84
|
+
echo "Publish gate: no rlsbl-ci-sha marker after $marker_attempts attempt(s) on the '$tag' release body; falling back to \$GITHUB_SHA."
|
|
85
|
+
fi
|
|
86
|
+
echo "Publish gate: waiting for CI on $GITHUB_REF_NAME (commit $sha)"
|
|
87
|
+
echo "Check-run name filter: $CI_CHECK_REGEX"
|
|
88
|
+
# Timeout, grace window, and poll interval come from the job env above;
|
|
89
|
+
# edit them there if this repository's CI needs different limits.
|
|
90
|
+
|
|
91
|
+
now() { date +%s; }
|
|
92
|
+
start="$(now)"
|
|
93
|
+
deadline=$(( start + GATE_TIMEOUT_MINUTES * 60 ))
|
|
94
|
+
grace_deadline=$(( start + GATE_GRACE_MINUTES * 60 ))
|
|
95
|
+
|
|
96
|
+
while :; do
|
|
97
|
+
if ! resp="$(gh api --paginate "repos/$GITHUB_REPOSITORY/commits/$sha/check-runs?per_page=100")"; then
|
|
98
|
+
echo "Checks API request failed; retrying in ${GATE_POLL_SECONDS}s..."
|
|
99
|
+
sleep "$GATE_POLL_SECONDS"
|
|
100
|
+
continue
|
|
101
|
+
fi
|
|
102
|
+
# Match this project's CI check runs by name; exclude check runs that
|
|
103
|
+
# belong to THIS workflow run (the gate itself and the queued publish
|
|
104
|
+
# jobs would otherwise deadlock the poll loop).
|
|
105
|
+
# A retried CI run creates a BRAND-NEW check-run with the same name as the
|
|
106
|
+
# old one, so a stale failure would block the gate forever. Project id and
|
|
107
|
+
# started_at, then reduce to the latest check-run per name (max started_at,
|
|
108
|
+
# numeric id as tiebreak for retries within the same second) BEFORE the
|
|
109
|
+
# pending / not-success logic runs. This way the newest run decides: still
|
|
110
|
+
# running -> wait; genuinely red -> hard fail.
|
|
111
|
+
runs="$(jq -s --arg re "$CI_CHECK_REGEX" --arg run_id "$GITHUB_RUN_ID" '
|
|
112
|
+
[ .[].check_runs[]
|
|
113
|
+
| select(.name | test($re))
|
|
114
|
+
| select((.details_url // "") | contains("/actions/runs/" + $run_id + "/") | not)
|
|
115
|
+
| {name, status, conclusion, id, started_at} ]
|
|
116
|
+
| group_by(.name)
|
|
117
|
+
| map(sort_by(.started_at, .id) | last)' <<< "$resp")"
|
|
118
|
+
total="$(jq 'length' <<< "$runs")"
|
|
119
|
+
|
|
120
|
+
if [ "$total" -eq 0 ]; then
|
|
121
|
+
if [ "$(now)" -ge "$grace_deadline" ]; then
|
|
122
|
+
echo "::error::Publish gate: no CI check runs matching $CI_CHECK_REGEX appeared on $sha within $GATE_GRACE_MINUTES minutes."
|
|
123
|
+
echo "A scaffolded repository always has a CI workflow, and rlsbl verifies CI on this exact commit BEFORE tagging it, so check runs must exist here."
|
|
124
|
+
echo "Their absence means the check runs were deleted, the commit resolution is wrong, or this tag was created outside rlsbl."
|
|
125
|
+
echo "If CI jobs were renamed, update CI_CHECK_REGEX in this workflow's gate job to match the new names."
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
echo "No matching CI check runs yet; retrying in ${GATE_POLL_SECONDS}s..."
|
|
129
|
+
sleep "$GATE_POLL_SECONDS"
|
|
130
|
+
continue
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
pending="$(jq '[ .[] | select(.status != "completed") ] | length' <<< "$runs")"
|
|
134
|
+
if [ "$pending" -gt 0 ]; then
|
|
135
|
+
if [ "$(now)" -ge "$deadline" ]; then
|
|
136
|
+
echo "::error::Publish gate: timed out after $GATE_TIMEOUT_MINUTES minutes waiting for CI to complete on $sha."
|
|
137
|
+
jq -r '.[] | " \(.name): status=\(.status) conclusion=\(.conclusion // "none")"' <<< "$runs"
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
echo "$pending of $total matching CI check runs still running; retrying in ${GATE_POLL_SECONDS}s..."
|
|
141
|
+
sleep "$GATE_POLL_SECONDS"
|
|
142
|
+
continue
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
not_success="$(jq '[ .[] | select(.conclusion != "success") ]' <<< "$runs")"
|
|
146
|
+
if [ "$(jq 'length' <<< "$not_success")" -gt 0 ]; then
|
|
147
|
+
echo "::error::Publish gate: CI did not pass on $sha -- refusing to publish."
|
|
148
|
+
jq -r '.[] | " \(.name): \(.conclusion)"' <<< "$not_success"
|
|
149
|
+
while IFS= read -r conclusion; do
|
|
150
|
+
case "$conclusion" in
|
|
151
|
+
failure|timed_out)
|
|
152
|
+
echo "CI concluded '$conclusion' on the release commit."
|
|
153
|
+
echo "rlsbl tags and releases a commit only AFTER its CI has gone green, so reaching this branch means one of: CI was re-run on an already-released commit and regressed, a required check was added after the release, or this tag/Release was created outside rlsbl."
|
|
154
|
+
echo "Do NOT re-dispatch this publish workflow expecting a different answer -- a failure baked into the code at this commit fails identically every time, and there is no way to make this tag green."
|
|
155
|
+
echo "Remedy: fix forward on the release branch and cut a NEW release with 'rlsbl release run' (its own CI gate must go green before it is tagged), then mark this one with 'rlsbl release deprecate <version>'."
|
|
156
|
+
;;
|
|
157
|
+
cancelled)
|
|
158
|
+
echo "A CI check run was CANCELLED. A cancelled run proves nothing about the commit, so the gate treats it as a hard failure instead of waiting for a conclusion that will never come."
|
|
159
|
+
echo "Remedy: re-run the cancelled CI workflow on this exact commit (gh run rerun <run-id>). If it concludes success, re-dispatch this publish workflow at the tag ref: gh workflow run <publish workflow> --ref $GITHUB_REF_NAME"
|
|
160
|
+
;;
|
|
161
|
+
skipped)
|
|
162
|
+
echo "A CI check run matching the filter was SKIPPED. The gate cannot treat a skipped check as passing: this project must actually run its own CI on the release commit."
|
|
163
|
+
echo "Remedy: check paths filters and job conditions so CI runs for this project, re-run CI on this commit, then re-dispatch this publish workflow at the tag ref."
|
|
164
|
+
;;
|
|
165
|
+
*)
|
|
166
|
+
echo "CI check concluded '$conclusion' (not success). The gate only proceeds when every matching check concluded success."
|
|
167
|
+
;;
|
|
168
|
+
esac
|
|
169
|
+
done <<< "$(jq -r '.[].conclusion' <<< "$not_success" | sort -u)"
|
|
170
|
+
exit 1
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
echo "Publish gate: all $total matching CI check runs succeeded."
|
|
174
|
+
jq -r '.[] | " \(.name): \(.conclusion)"' <<< "$runs"
|
|
175
|
+
exit 0
|
|
176
|
+
done
|
|
177
|
+
pypi:
|
|
178
|
+
needs: gate
|
|
179
|
+
runs-on: ubuntu-latest
|
|
180
|
+
steps:
|
|
181
|
+
- uses: actions/checkout@v6
|
|
182
|
+
with:
|
|
183
|
+
ref: ${{ inputs.tag || github.event.release.tag_name }}
|
|
184
|
+
- uses: astral-sh/setup-uv@v7
|
|
185
|
+
- name: Install gitleaks
|
|
186
|
+
run: |
|
|
187
|
+
GITLEAKS_VERSION=8.24.3
|
|
188
|
+
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" | tar xz -C /usr/local/bin gitleaks
|
|
189
|
+
- run: uv build --out-dir dist
|
|
190
|
+
- name: Scan artifacts for secrets
|
|
191
|
+
run: |
|
|
192
|
+
gitleaks dir dist/
|
|
193
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
194
|
+
with:
|
|
195
|
+
skip-existing: true
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.log
|
|
5
|
+
.DS_Store
|
|
6
|
+
coverage/
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
target/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
.rlsbl-notes-*.tmp
|
|
12
|
+
.rlsbl/lock
|
|
13
|
+
.rlsbl-monorepo/lock
|
|
14
|
+
.credentials.json
|
|
15
|
+
.*-cache.json
|
|
16
|
+
.env
|
|
17
|
+
.env.local
|
|
18
|
+
*.local-only
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[forbidden-imports]
|
|
2
|
+
modules = [
|
|
3
|
+
"argparse",
|
|
4
|
+
"click",
|
|
5
|
+
"typer",
|
|
6
|
+
"flask",
|
|
7
|
+
"fastapi",
|
|
8
|
+
"django",
|
|
9
|
+
"uvicorn",
|
|
10
|
+
"granian",
|
|
11
|
+
"starlette",
|
|
12
|
+
"tornado",
|
|
13
|
+
"bottle",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[stdout]
|
|
17
|
+
enabled = true
|
|
18
|
+
ignore = []
|
|
19
|
+
|
|
20
|
+
[entry-point]
|
|
21
|
+
enabled = true
|
|
22
|
+
ignore = []
|
|
23
|
+
|
|
24
|
+
[files]
|
|
25
|
+
exclude = []
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"files": {
|
|
4
|
+
".github/workflows/ci.yml": "317dad66e40c753dfa18b5d143ea16cb1c9b4e8426eb3d8f703b67589d8ac129",
|
|
5
|
+
".github/workflows/publish.yml": "bfa60b4f497b67e71b31336e0373a3730aca9b16e6b35d1f7a3e003ee406c01a",
|
|
6
|
+
".gitignore": "c32366f560dbf628eb04a94d25e8d8acf719605b2ea27d31047df16e13580c04",
|
|
7
|
+
".rlsbl/lint/python.toml": "9cfbcef2e010d5fad243437bf2fd8df54a1fae70ee0762f9d11da70fe207501c"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 smm-h
|
|
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,50 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: testisolation
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Uncompromising test isolation: your tests structurally cannot touch your real files, secrets, or git identity -- First-class support for Go, Python with pytest, and TypeScript with Node
|
|
5
|
+
Project-URL: Homepage, https://smmh.dev/testisolation/
|
|
6
|
+
Project-URL: Documentation, https://smmh.dev/testisolation/
|
|
7
|
+
Project-URL: Repository, https://github.com/stricttools/testisolation
|
|
8
|
+
Project-URL: Issues, https://github.com/stricttools/testisolation/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/stricttools/testisolation/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: "S. M. Hosseini" <m.hosseini@veliu.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: credentials,isolation,node-test,pytest,rlsbl,sandbox,security,test-isolation,testing
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Framework :: Pytest
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: Software Development :: Testing
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: pytest>=7.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# testisolation (Python)
|
|
24
|
+
|
|
25
|
+
Uncompromising test isolation: your tests structurally cannot touch your real files, secrets, or git identity -- First-class support for Go, Python with pytest, and TypeScript with Node
|
|
26
|
+
|
|
27
|
+
This is the pytest plugin.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install testisolation
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Installing it is adoption: the plugin loads through its `pytest11` entry point
|
|
34
|
+
and refuses to run a suite that has not declared its safety stance.
|
|
35
|
+
|
|
36
|
+
```toml
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testisolation_sockets = "deny"
|
|
39
|
+
testisolation_socket_allowlist = []
|
|
40
|
+
testisolation_unix_socket_allowlist = []
|
|
41
|
+
testisolation_loopback = "deny"
|
|
42
|
+
testisolation_sandbox_required = "false"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
See the [repository README](https://github.com/stricttools/testisolation) for the full
|
|
46
|
+
key reference and what each guard does.
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# testisolation (Python)
|
|
2
|
+
|
|
3
|
+
Uncompromising test isolation: your tests structurally cannot touch your real files, secrets, or git identity -- First-class support for Go, Python with pytest, and TypeScript with Node
|
|
4
|
+
|
|
5
|
+
This is the pytest plugin.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install testisolation
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Installing it is adoption: the plugin loads through its `pytest11` entry point
|
|
12
|
+
and refuses to run a suite that has not declared its safety stance.
|
|
13
|
+
|
|
14
|
+
```toml
|
|
15
|
+
[tool.pytest.ini_options]
|
|
16
|
+
testisolation_sockets = "deny"
|
|
17
|
+
testisolation_socket_allowlist = []
|
|
18
|
+
testisolation_unix_socket_allowlist = []
|
|
19
|
+
testisolation_loopback = "deny"
|
|
20
|
+
testisolation_sandbox_required = "false"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
See the [repository README](https://github.com/stricttools/testisolation) for the full
|
|
24
|
+
key reference and what each guard does.
|
|
25
|
+
|
|
26
|
+
## License
|
|
27
|
+
|
|
28
|
+
MIT
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "testisolation"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Uncompromising test isolation: your tests structurally cannot touch your real files, secrets, or git identity -- First-class support for Go, Python with pytest, and TypeScript with Node"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "S. M. Hosseini", email = "m.hosseini@veliu.com" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"testing",
|
|
16
|
+
"test-isolation",
|
|
17
|
+
"isolation",
|
|
18
|
+
"sandbox",
|
|
19
|
+
"security",
|
|
20
|
+
"credentials",
|
|
21
|
+
"pytest",
|
|
22
|
+
"node-test",
|
|
23
|
+
"rlsbl",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 3 - Alpha",
|
|
27
|
+
"Framework :: Pytest",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Topic :: Software Development :: Testing",
|
|
31
|
+
]
|
|
32
|
+
dependencies = ["pytest>=7.0"]
|
|
33
|
+
|
|
34
|
+
[project.entry-points.pytest11]
|
|
35
|
+
testisolation = "testisolation.plugin"
|
|
36
|
+
|
|
37
|
+
[dependency-groups]
|
|
38
|
+
dev = ["pytest", "pytest-xdist"]
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/testisolation"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
# testisolation is active in its own suite through the pytest11 entry point, so
|
|
46
|
+
# this project declares its safety stance like any other consumer.
|
|
47
|
+
testisolation_sockets = "deny"
|
|
48
|
+
testisolation_socket_allowlist = []
|
|
49
|
+
testisolation_unix_socket_allowlist = []
|
|
50
|
+
testisolation_loopback = "deny"
|
|
51
|
+
# No sandbox runner exists in this repo yet; the runner ships as a scaffold
|
|
52
|
+
# template from rlsbl. Flip to "true" at adoption.
|
|
53
|
+
testisolation_sandbox_required = "false"
|
|
54
|
+
|
|
55
|
+
[project.urls]
|
|
56
|
+
Homepage = "https://smmh.dev/testisolation/"
|
|
57
|
+
Documentation = "https://smmh.dev/testisolation/"
|
|
58
|
+
Repository = "https://github.com/stricttools/testisolation"
|
|
59
|
+
Issues = "https://github.com/stricttools/testisolation/issues"
|
|
60
|
+
Changelog = "https://github.com/stricttools/testisolation/blob/main/CHANGELOG.md"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""testisolation -- always-on test isolation for pytest suites.
|
|
2
|
+
|
|
3
|
+
The package ships a ``pytest11`` plugin. Installing it is adoption: the plugin
|
|
4
|
+
loads automatically and refuses to run a suite that has not declared its safety
|
|
5
|
+
stance. See :mod:`testisolation.config` for the ini keys.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .config import PRESERVE_VARS, REQUIRED_KEYS, Settings
|
|
9
|
+
from .socketguard import NetworkBlocked, Policy
|
|
10
|
+
|
|
11
|
+
__version__ = "0.3.0"
|
|
12
|
+
|
|
13
|
+
# No ``__all__`` here on purpose. Every name above is defined -- and documented
|
|
14
|
+
# -- on its own module's API-reference page; re-listing them here would make the
|
|
15
|
+
# package root claim them a second time, double-counting them in documentation
|
|
16
|
+
# coverage. The re-exports themselves are the public import path
|
|
17
|
+
# (``from testisolation import NetworkBlocked``) and are covered by the suite.
|
|
18
|
+
_REEXPORTS = (PRESERVE_VARS, REQUIRED_KEYS, Settings, NetworkBlocked, Policy)
|