chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:12:33 +08:00
commit aacb60a4af
3387 changed files with 981307 additions and 0 deletions
+161
View File
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""Fail closed when required CI jobs or classifier outputs are incomplete."""
from __future__ import annotations
import os
import sys
from collections.abc import Mapping
from typing import Final
BOOLEAN_FLAGS: Final[tuple[str, ...]] = (
"docs_only",
"runtime_changed",
"test_changed",
"ci_changed",
"dependency_changed",
"release_changed",
"windows_full_required",
"frontend_changed",
"tui_changed",
"desktop_changed",
"python_changed",
"platform_sensitive_changed",
"build_wheel_required",
"full_required",
)
ALWAYS_REQUIRED_RESULTS: Final[tuple[tuple[str, str], ...]] = (
("RESULT_CLASSIFY", "Classify changed files"),
("RESULT_WORKFLOW_LINT", "Workflow lint"),
("RESULT_README_LOCALE", "README locale parity"),
)
def _flag_env(name: str) -> str:
return f"FLAG_{name.upper()}"
def _read_flags(env: Mapping[str, str], errors: list[str]) -> dict[str, bool]:
flags: dict[str, bool] = {}
for name in BOOLEAN_FLAGS:
raw = env.get(_flag_env(name), "")
if raw not in {"true", "false"}:
errors.append(f"Classifier output {name} must be exactly true or false; got {raw!r}.")
continue
flags[name] = raw == "true"
return flags
def _require_result(
env: Mapping[str, str],
errors: list[str],
variable: str,
label: str,
*,
required: bool,
) -> None:
result = env.get(variable, "")
allowed = {"success"} if required else {"success", "skipped"}
if result not in allowed:
expectation = "succeed" if required else "be successful or intentionally skipped"
errors.append(f"{label} must {expectation}; got {result or 'missing'}.")
def check_ci_results(env: Mapping[str, str]) -> list[str]:
"""Return gate errors; an empty list means the aggregate check may pass."""
errors: list[str] = []
flags = _read_flags(env, errors)
for variable, label in ALWAYS_REQUIRED_RESULTS:
_require_result(env, errors, variable, label, required=True)
if len(flags) != len(BOOLEAN_FLAGS):
return errors
full = flags["full_required"]
conditional_results = (
("RESULT_FRONTEND", "Frontend build and typecheck", flags["frontend_changed"] or full),
("RESULT_TUI", "OpenTUI package tests", flags["tui_changed"] or full),
("RESULT_DESKTOP", "Desktop Electron unit tests", flags["desktop_changed"] or full),
("RESULT_UBUNTU", "Ubuntu quality gate", flags["python_changed"] or full),
(
"RESULT_WINDOWS_SMOKE",
"Windows compatibility smoke tests",
flags["python_changed"]
or flags["platform_sensitive_changed"]
or flags["dependency_changed"]
or flags["release_changed"]
or full,
),
(
"RESULT_WINDOWS_FULL",
"Windows high-risk matrix",
flags["windows_full_required"] or full,
),
(
"RESULT_MACOS_RECOVERY",
"macOS profile recovery and native no-replace tests",
flags["platform_sensitive_changed"] or flags["desktop_changed"] or full,
),
(
"RESULT_DESKTOP_RECOVERY_E2E",
"Desktop recovery E2E matrix",
flags["platform_sensitive_changed"] or flags["desktop_changed"] or full,
),
(
"RESULT_RELEASE",
"Release packaging contracts",
flags["release_changed"] or full,
),
)
for variable, label, required in conditional_results:
_require_result(env, errors, variable, label, required=required)
if flags["platform_sensitive_changed"] and not flags["windows_full_required"]:
errors.append("Platform-sensitive changes must require the Windows high-risk matrix.")
if full:
if flags["docs_only"]:
errors.append("A full CI run cannot be classified as docs-only.")
for name in BOOLEAN_FLAGS:
if name in {"docs_only", "full_required"}:
continue
if not flags[name]:
errors.append(f"Full CI classification must set {name}=true.")
if flags["docs_only"]:
active = [
name
for name in BOOLEAN_FLAGS
if name != "docs_only" and flags[name]
]
if active:
errors.append(
"Docs-only classification cannot enable other flags: " + ", ".join(active)
)
return errors
def main() -> int:
errors = check_ci_results(os.environ)
for variable, label in ALWAYS_REQUIRED_RESULTS:
print(f"{label}: {os.environ.get(variable, 'missing')}")
print(
"Classifier flags: "
+ " ".join(
f"{name}={os.environ.get(_flag_env(name), 'missing')}" for name in BOOLEAN_FLAGS
)
)
if not errors:
print("All required CI results are complete and successful.")
return 0
for error in errors:
print(f"ERROR: {error}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())
+221
View File
@@ -0,0 +1,221 @@
#!/usr/bin/env bash
set -euo pipefail
changed_files="${1:?usage: classify-ci-changes.sh <changed-files-list>}"
output_file="${GITHUB_OUTPUT:?GITHUB_OUTPUT must be set}"
docs_only=true
runtime_changed=false
test_changed=false
ci_changed=false
dependency_changed=false
release_changed=false
windows_full_required=false
frontend_changed=false
tui_changed=false
desktop_changed=false
python_changed=false
platform_sensitive_changed=false
build_wheel_required=false
full_required=false
seen_file=false
mark_non_docs_changed() {
docs_only=false
}
mark_runtime_changed() {
mark_non_docs_changed
runtime_changed=true
python_changed=true
build_wheel_required=true
}
mark_test_changed() {
mark_non_docs_changed
test_changed=true
python_changed=true
}
mark_ci_changed() {
mark_non_docs_changed
ci_changed=true
python_changed=true
}
mark_dependency_changed() {
mark_runtime_changed
dependency_changed=true
release_changed=true
windows_full_required=true
}
mark_release_changed() {
mark_non_docs_changed
release_changed=true
windows_full_required=true
}
mark_frontend_changed() {
mark_non_docs_changed
frontend_changed=true
}
mark_tui_changed() {
mark_runtime_changed
tui_changed=true
}
mark_desktop_changed() {
mark_non_docs_changed
desktop_changed=true
}
mark_platform_sensitive_changed() {
mark_non_docs_changed
platform_sensitive_changed=true
windows_full_required=true
}
mark_full_required() {
docs_only=false
runtime_changed=true
test_changed=true
ci_changed=true
dependency_changed=true
release_changed=true
windows_full_required=true
frontend_changed=true
tui_changed=true
desktop_changed=true
python_changed=true
platform_sensitive_changed=true
build_wheel_required=true
full_required=true
}
while IFS= read -r path || [[ -n "${path}" ]]; do
path="${path%$'\r'}"
[[ -z "${path}" ]] && continue
seen_file=true
case "${path}" in
.ci/run-all)
mark_full_required
;;
pyproject.toml | uv.lock)
mark_dependency_changed
;;
opensquilla-webui/*)
mark_frontend_changed
;;
src/opensquilla/cli/tui/opentui/package/*)
mark_tui_changed
;;
.github/workflows/ci.yml | .github/scripts/classify-ci-changes.sh | .github/scripts/check_ci_results.py | .github/scripts/windows_test_shards.py)
# Changes to the gate itself must exercise every path it can suppress.
mark_full_required
;;
.github/workflows/wheelhouse-release.yml)
mark_ci_changed
mark_release_changed
;;
.github/workflows/*)
mark_full_required
;;
.github/scripts/verify-release-profile-preservation.py)
mark_ci_changed
mark_release_changed
mark_platform_sensitive_changed
;;
.github/scripts/*)
mark_full_required
;;
tests/test_scripts/test_build_wheelhouse_zip.py | tests/test_install_scripts.py | tests/test_root_start_scripts.py | tests/test_release_consistency.py | tests/test_public_release_hygiene.py)
mark_test_changed
mark_release_changed
;;
tests/test_tools/test_shell_* | tests/test_tools/test_path_* | tests/test_sandbox/* | tests/test_desktop/* | tests/test_compat/* | tests/test_recovery/* | tests/test_migration/* | tests/test_migrations/* | tests/test_persistence/* | tests/test_session/* | tests/test_scheduler/* | tests/test_uninstall/* | tests/test_packaging/*)
mark_test_changed
mark_platform_sensitive_changed
;;
tests/test_onboarding/* | tests/test_provider/* | tests/test_provider*.py)
mark_test_changed
mark_platform_sensitive_changed
;;
tests/functional/test_gateway_*_e2e.py)
mark_test_changed
mark_platform_sensitive_changed
;;
tests/*)
# New or unclassified tests fail closed to the Windows high-risk suite.
mark_test_changed
mark_platform_sensitive_changed
;;
scripts/build_wheelhouse_zip.py | scripts/install_source.sh | scripts/install_source.ps1)
mark_runtime_changed
mark_release_changed
;;
install.sh | install.ps1 | start.sh | start.ps1 | README.release.md | RELEASES.md)
mark_release_changed
;;
desktop/electron/scripts/test-packaged-update-policy.mjs)
mark_desktop_changed
mark_platform_sensitive_changed
mark_release_changed
;;
desktop/*)
mark_platform_sensitive_changed
mark_desktop_changed
;;
src/opensquilla/uninstall/*)
mark_runtime_changed
mark_release_changed
;;
src/opensquilla/recovery/* | src/opensquilla/migration/* | src/opensquilla/persistence/* | src/opensquilla/memory/* | src/opensquilla/session/* | src/opensquilla/scheduler/* | src/opensquilla/sandbox/* | src/opensquilla/tools/boundary.py | src/opensquilla/tools/builtin/code_exec.py | src/opensquilla/tools/builtin/filesystem.py | src/opensquilla/tools/builtin/git.py | src/opensquilla/tools/builtin/shell.py | src/opensquilla/tools/builtin/shell_policy.py | src/opensquilla/tools/path_* | src/opensquilla/tools/policy* | src/opensquilla/tools/write_*)
mark_runtime_changed
mark_platform_sensitive_changed
;;
src/opensquilla/onboarding/* | src/opensquilla/provider/*)
mark_runtime_changed
mark_platform_sensitive_changed
;;
migrations/*)
mark_runtime_changed
mark_platform_sensitive_changed
;;
src/* | scripts/*)
# Unknown runtime surfaces are high risk until explicitly proven otherwise.
mark_runtime_changed
mark_platform_sensitive_changed
;;
docs/* | README.md | README.*.md | CHANGELOG.md | CODE_OF_CONDUCT.md | CONTRIBUTING.md | MIGRATION.md | SECURITY.md | SUPPORT.md | THIRD_PARTY_NOTICES.md | META_SKILL_GUIDE.md | .github/pull_request_template.md | .github/ISSUE_TEMPLATE/*)
;;
*)
# Fail closed for new non-documentation paths.
mark_runtime_changed
mark_platform_sensitive_changed
;;
esac
done < "${changed_files}"
if [[ "${seen_file}" == "false" ]]; then
mark_full_required
fi
{
printf 'docs_only=%s\n' "${docs_only}"
printf 'runtime_changed=%s\n' "${runtime_changed}"
printf 'test_changed=%s\n' "${test_changed}"
printf 'ci_changed=%s\n' "${ci_changed}"
printf 'dependency_changed=%s\n' "${dependency_changed}"
printf 'release_changed=%s\n' "${release_changed}"
printf 'windows_full_required=%s\n' "${windows_full_required}"
printf 'frontend_changed=%s\n' "${frontend_changed}"
printf 'tui_changed=%s\n' "${tui_changed}"
printf 'desktop_changed=%s\n' "${desktop_changed}"
printf 'python_changed=%s\n' "${python_changed}"
printf 'platform_sensitive_changed=%s\n' "${platform_sensitive_changed}"
printf 'build_wheel_required=%s\n' "${build_wheel_required}"
printf 'full_required=%s\n' "${full_required}"
} >> "${output_file}"
+437
View File
@@ -0,0 +1,437 @@
#!/usr/bin/env python3
"""Synchronize OpenSquilla issue state after pull request lifecycle changes.
This script is intentionally safe for `pull_request_target`: it treats pull
request text as data only and never executes code from the pull request head.
"""
from __future__ import annotations
import json
import os
import re
import sys
from typing import Any, NamedTuple
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen
HAS_LINKED_PR_LABEL = "has-linked-pr"
MERGED_TO_DEV_LABEL = "merged-to-dev"
NEEDS_VERIFICATION_LABEL = "needs-verification"
LABEL_DEFINITIONS = {
HAS_LINKED_PR_LABEL: {
"color": "C5DEF5",
"description": "An open pull request is linked to this issue",
},
MERGED_TO_DEV_LABEL: {
"color": "5319E7",
"description": "A related fix has merged to dev but has not necessarily shipped",
},
NEEDS_VERIFICATION_LABEL: {
"color": "C5DEF5",
"description": "Maintainers or reporters should retest the current behavior",
},
}
CLOSING_KEYWORDS = frozenset(
{
"close",
"closes",
"closed",
"fix",
"fixes",
"fixed",
"resolve",
"resolves",
"resolved",
}
)
#: Mutation statuses that describe the target's state or the token's scope on
#: that one target (labeling a pull request needs pull-requests write scope
#: this workflow does not request; locked or deleted issues answer 403/410)
#: rather than a broken sync setup — the affected action is logged and
#: skipped instead of failing every remaining action in the job.
TOLERATED_MUTATION_STATUSES = frozenset({403, 404, 410})
REFERENCE_KEYWORDS = frozenset({"ref", "refs", "reference", "references"})
OPEN_PR_ACTIONS = frozenset({"opened", "reopened", "edited"})
KEYWORD_RE = re.compile(
r"\b(?P<keyword>close|closes|closed|fix|fixes|fixed|resolve|resolves|"
r"resolved|ref|refs|reference|references)\s*:?\s+(?P<ref>"
r"#\d+|"
r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#\d+|"
r"https://github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/issues/\d+"
r")\b",
re.IGNORECASE,
)
LOCAL_REF_RE = re.compile(r"^#(?P<number>\d+)$")
REPO_REF_RE = re.compile(
r"^(?P<owner>[A-Za-z0-9_.-]+)/(?P<repo>[A-Za-z0-9_.-]+)#(?P<number>\d+)$"
)
ISSUE_URL_RE = re.compile(
r"^https://github\.com/(?P<owner>[A-Za-z0-9_.-]+)/(?P<repo>[A-Za-z0-9_.-]+)/"
r"issues/(?P<number>\d+)$"
)
class ParsedIssueLinks(NamedTuple):
closing: tuple[int, ...]
references: tuple[int, ...]
@property
def all(self) -> tuple[int, ...]:
return tuple(dict.fromkeys((*self.closing, *self.references)))
class IssueSyncAction(NamedTuple):
issue_number: int
kind: str
pr_number: int
pr_url: str
class GitHubClient:
def __init__(self, *, token: str, repository: str) -> None:
self._token = token
self._repository = repository
self._api_root = f"https://api.github.com/repos/{repository}"
def request_json(
self,
method: str,
path: str,
*,
payload: dict[str, Any] | None = None,
ignore_statuses: set[int] | None = None,
) -> Any:
ignore_statuses = ignore_statuses or set()
data = None
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {self._token}",
"X-GitHub-Api-Version": "2022-11-28",
}
if payload is not None:
data = json.dumps(payload).encode("utf-8")
headers["Content-Type"] = "application/json"
request = Request(
f"{self._api_root}{path}",
data=data,
headers=headers,
method=method,
)
try:
with urlopen(request, timeout=30) as response:
raw = response.read()
except HTTPError as exc:
if exc.code in ignore_statuses:
return None
body = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(f"GitHub API {method} {path} failed: {exc.code} {body}") from exc
if not raw:
return None
return json.loads(raw.decode("utf-8"))
def get_issue(self, issue_number: int) -> dict[str, Any] | None:
"""Fetch an issue, or None when it is missing or unreadable.
Pull requests are returned too (the REST issues endpoint covers
both) — callers use the ``pull_request`` key to tell them apart.
"""
found = self.request_json(
"GET",
f"/issues/{issue_number}",
ignore_statuses=TOLERATED_MUTATION_STATUSES,
)
return found if isinstance(found, dict) else None
def ensure_label(self, name: str) -> None:
definition = LABEL_DEFINITIONS[name]
label_name = quote(name, safe="")
found = self.request_json(
"GET",
f"/labels/{label_name}",
ignore_statuses={404},
)
if found is not None:
return
self.request_json(
"POST",
"/labels",
payload={
"name": name,
"color": definition["color"],
"description": definition["description"],
},
# 422: another run created the label between the GET and here.
ignore_statuses=TOLERATED_MUTATION_STATUSES | {422},
)
def add_labels(self, issue_number: int, labels: list[str]) -> None:
for label in labels:
self.ensure_label(label)
self.request_json(
"POST",
f"/issues/{issue_number}/labels",
payload={"labels": labels},
ignore_statuses=TOLERATED_MUTATION_STATUSES,
)
def remove_label(self, issue_number: int, label: str) -> None:
label_name = quote(label, safe="")
self.request_json(
"DELETE",
f"/issues/{issue_number}/labels/{label_name}",
ignore_statuses=TOLERATED_MUTATION_STATUSES,
)
def list_comments(self, issue_number: int) -> list[dict[str, Any]]:
all_comments: list[dict[str, Any]] = []
page = 1
while True:
comments = self.request_json(
"GET",
f"/issues/{issue_number}/comments?per_page=100&page={page}",
)
if not isinstance(comments, list) or not comments:
return all_comments
all_comments.extend(comments)
if len(comments) < 100:
return all_comments
page += 1
def create_comment(self, issue_number: int, body: str) -> None:
self.request_json(
"POST",
f"/issues/{issue_number}/comments",
payload={"body": body},
# Locked conversations reject comments with 403; the labels above
# still applied, so the sync outcome is preserved.
ignore_statuses=TOLERATED_MUTATION_STATUSES,
)
def _normalize_repo_ref(raw_ref: str, *, owner: str, repo: str) -> int | None:
local = LOCAL_REF_RE.match(raw_ref)
if local is not None:
return int(local.group("number"))
repo_ref = REPO_REF_RE.match(raw_ref)
if repo_ref is not None:
if repo_ref.group("owner").lower() != owner.lower():
return None
if repo_ref.group("repo").lower() != repo.lower():
return None
return int(repo_ref.group("number"))
issue_url = ISSUE_URL_RE.match(raw_ref)
if issue_url is not None:
if issue_url.group("owner").lower() != owner.lower():
return None
if issue_url.group("repo").lower() != repo.lower():
return None
return int(issue_url.group("number"))
return None
def parse_linked_issues(body: str | None, *, owner: str, repo: str) -> ParsedIssueLinks:
closing: list[int] = []
references: list[int] = []
for match in KEYWORD_RE.finditer(body or ""):
issue_number = _normalize_repo_ref(match.group("ref"), owner=owner, repo=repo)
if issue_number is None:
continue
keyword = match.group("keyword").lower()
if keyword in CLOSING_KEYWORDS:
closing.append(issue_number)
elif keyword in REFERENCE_KEYWORDS:
references.append(issue_number)
return ParsedIssueLinks(
closing=tuple(dict.fromkeys(closing)),
references=tuple(dict.fromkeys(references)),
)
def plan_issue_sync_actions(event: dict[str, Any]) -> tuple[IssueSyncAction, ...]:
action = event.get("action")
pr = event.get("pull_request") or {}
repository = event.get("repository") or {}
full_name = repository.get("full_name") or ""
if "/" not in full_name:
return ()
owner, repo = full_name.split("/", 1)
pr_number = int(pr["number"])
pr_url = str(pr.get("html_url") or "")
parsed = parse_linked_issues(pr.get("body"), owner=owner, repo=repo)
if action in OPEN_PR_ACTIONS:
return tuple(
IssueSyncAction(
issue_number=issue_number,
kind="linked_pr_open",
pr_number=pr_number,
pr_url=pr_url,
)
for issue_number in parsed.all
)
if action != "closed":
return ()
if pr.get("merged") is True and (pr.get("base") or {}).get("ref") == "dev":
closing_actions = tuple(
IssueSyncAction(
issue_number=issue_number,
kind="merged_to_dev",
pr_number=pr_number,
pr_url=pr_url,
)
for issue_number in parsed.closing
)
closing_issue_numbers = set(parsed.closing)
reference_cleanup_actions = tuple(
IssueSyncAction(
issue_number=issue_number,
kind="remove_linked_pr",
pr_number=pr_number,
pr_url=pr_url,
)
for issue_number in parsed.references
if issue_number not in closing_issue_numbers
)
return (*closing_actions, *reference_cleanup_actions)
if pr.get("merged") is not True:
return tuple(
IssueSyncAction(
issue_number=issue_number,
kind="closed_unmerged",
pr_number=pr_number,
pr_url=pr_url,
)
for issue_number in parsed.all
)
return tuple(
IssueSyncAction(
issue_number=issue_number,
kind="remove_linked_pr",
pr_number=pr_number,
pr_url=pr_url,
)
for issue_number in parsed.all
)
def comment_marker(*, kind: str, pr_number: int) -> str:
marker_kind = kind.replace("_", "-")
return f"<!-- opensquilla-issue-link-sync:{marker_kind}:pr-{pr_number} -->"
def has_marker(comments: list[dict[str, Any]], marker: str) -> bool:
return any(marker in str(comment.get("body") or "") for comment in comments)
def _merged_to_dev_comment(action: IssueSyncAction) -> str:
marker = comment_marker(kind=action.kind, pr_number=action.pr_number)
return (
f"{marker}\n"
f"The linked fix for this issue has merged to `dev` via #{action.pr_number} "
f"({action.pr_url}). Keeping it open for verification before release."
)
def apply_action(client: GitHubClient, action: IssueSyncAction) -> None:
if action.kind == "linked_pr_open":
client.add_labels(action.issue_number, [HAS_LINKED_PR_LABEL])
return
if action.kind == "merged_to_dev":
client.add_labels(
action.issue_number,
[MERGED_TO_DEV_LABEL, NEEDS_VERIFICATION_LABEL],
)
client.remove_label(action.issue_number, HAS_LINKED_PR_LABEL)
marker = comment_marker(kind=action.kind, pr_number=action.pr_number)
if not has_marker(client.list_comments(action.issue_number), marker):
client.create_comment(action.issue_number, _merged_to_dev_comment(action))
return
if action.kind in {"closed_unmerged", "remove_linked_pr"}:
client.remove_label(action.issue_number, HAS_LINKED_PR_LABEL)
return
raise ValueError(f"Unsupported issue sync action: {action.kind}")
def classify_sync_target(client: GitHubClient, issue_number: int) -> str:
"""Classify a parsed ``#N`` reference before mutating it.
``#N`` is textual and shares one number space between issues and pull
requests, so a PR body saying "the main path was fixed in #535" parses as
an issue link even though 535 is a pull request. Labeling a PR needs a
scope this workflow does not hold (and is not the sync's job), so only
``"issue"`` targets are acted on; ``"pull_request"`` and ``"missing"``
are skipped by the caller.
"""
found = client.get_issue(issue_number)
if found is None:
return "missing"
if found.get("pull_request"):
return "pull_request"
return "issue"
def _load_event(path: str) -> dict[str, Any]:
with open(path, encoding="utf-8") as handle:
return json.load(handle)
def main() -> int:
event_path = os.environ.get("GITHUB_EVENT_PATH")
repository = os.environ.get("GITHUB_REPOSITORY")
token = os.environ.get("GITHUB_TOKEN")
if not event_path:
print("GITHUB_EVENT_PATH is required.", file=sys.stderr)
return 2
if not repository:
print("GITHUB_REPOSITORY is required.", file=sys.stderr)
return 2
if not token:
print("GITHUB_TOKEN is required.", file=sys.stderr)
return 2
event = _load_event(event_path)
actions = plan_issue_sync_actions(event)
if not actions:
print("No issue sync actions required.")
return 0
client = GitHubClient(token=token, repository=repository)
for action in actions:
target_kind = classify_sync_target(client, action.issue_number)
if target_kind != "issue":
print(
f"Skipping {action.kind} for #{action.issue_number} "
f"from PR #{action.pr_number}: target is {target_kind}."
)
continue
print(
f"Applying {action.kind} for issue #{action.issue_number} "
f"from PR #{action.pr_number}."
)
apply_action(client, action)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
set -euo pipefail
base_ref="${PR_BASE_REF:-${BASE_REF:-${BASE:-}}}"
event_path="${GITHUB_EVENT_PATH:-}"
if [[ -z "${base_ref}" ]]; then
{
echo "::error title=Missing PR target::PR_BASE_REF is required."
echo "Unable to validate the pull request target branch."
} >&2
exit 1
fi
if [[ "${base_ref}" == "main" ]]; then
echo "Pull request targets main."
exit 0
fi
event_label_names() {
if [[ -n "${PR_LABELS:-}" ]]; then
tr ',' '\n' <<< "${PR_LABELS}"
return
fi
if [[ -z "${event_path}" || ! -f "${event_path}" ]]; then
return
fi
local python_bin=""
if command -v python3 >/dev/null 2>&1; then
python_bin="python3"
elif command -v python >/dev/null 2>&1; then
python_bin="python"
fi
if [[ -n "${python_bin}" ]]; then
"${python_bin}" - "${event_path}" <<'PY'
import json
import sys
with open(sys.argv[1], encoding="utf-8") as event_file:
event = json.load(event_file)
for label in event.get("pull_request", {}).get("labels", []):
name = label.get("name")
if name:
print(name)
PY
return
fi
if command -v jq >/dev/null 2>&1; then
jq -r '.pull_request.labels[]?.name // empty' "${event_path}"
fi
}
has_allowed_label() {
local allowed_kind="${1:?allowed kind is required}"
local label
while IFS= read -r label; do
case "${allowed_kind}:${label}" in
staging:maintainer-staging | staging:collaboration)
return 0
;;
esac
done < <(event_label_names)
return 1
}
is_staging_branch() {
case "${base_ref}" in
sandbox-* | integration/* | staging/* | release/* | hotfix/*)
return 0
;;
esac
return 1
}
if is_staging_branch || has_allowed_label staging; then
echo "Pull request targets a staging/collaboration branch."
echo "This is not a final integration path; final integration should target main."
exit 0
fi
{
echo "::error title=Wrong PR target::Ordinary pull requests should target main."
echo "Use sandbox-*, integration/*, staging/*, release/*, hotfix/*, or a maintainer-staging/collaboration label for maintainer collaboration PRs."
echo "Retarget this pull request to main or an approved staging/collaboration branch."
} >&2
exit 1
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Warn when a pull request body misses OpenSquilla governance fields."""
from __future__ import annotations
import json
import os
import re
from pathlib import Path
from typing import NamedTuple
class RequiredField(NamedTuple):
name: str
pattern: re.Pattern[str]
REQUIRED_FIELDS = (
RequiredField("Scope boundary", re.compile(r"(?im)^\s*Scope boundary\s*:")),
RequiredField("Base branch", re.compile(r"(?im)^\s*Base branch\s*:")),
RequiredField(
"Target exception",
re.compile(r"(?im)^\s*(?:Target exception|Main exception)\s*:"),
),
RequiredField("Linked issue", re.compile(r"(?im)^\s*Linked issue\s*:")),
RequiredField("Release note", re.compile(r"(?im)^\s*Release note\s*:")),
RequiredField("Tests", re.compile(r"(?im)^\s*(?:#+\s*)?Tests\s*:?\s*$")),
RequiredField("Maintainer live check", re.compile(r"(?im)^\s*Maintainer live check\s*:")),
RequiredField("Third-party origin", re.compile(r"(?im)^\s*Third-party origin\s*:")),
)
def missing_fields(body: str | None) -> list[str]:
text = body or ""
return [field.name for field in REQUIRED_FIELDS if field.pattern.search(text) is None]
def _annotation_escape(value: str) -> str:
return value.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
def _warning(title: str, message: str) -> None:
print(f"::warning title={_annotation_escape(title)}::{_annotation_escape(message)}")
def _is_strict() -> bool:
return os.environ.get("PR_BODY_LINT_STRICT", "0").strip().lower() in {
"1",
"true",
"yes",
}
def _load_pr_body(event_path: str) -> str | None:
with Path(event_path).open(encoding="utf-8") as event_file:
event = json.load(event_file)
return (event.get("pull_request") or {}).get("body")
def main() -> int:
strict = _is_strict()
event_path = os.environ.get("GITHUB_EVENT_PATH")
if not event_path:
_warning("Missing PR event", "GITHUB_EVENT_PATH is not set; skipping PR body lint.")
return 1 if strict else 0
try:
body = _load_pr_body(event_path)
except (OSError, json.JSONDecodeError) as exc:
_warning("Unreadable PR event", f"Unable to read pull request event: {exc}")
return 1 if strict else 0
missing = missing_fields(body)
if not missing:
print("PR body includes required governance fields.")
return 0
for field in missing:
_warning(
"Missing PR template field",
f"Add the `{field}:` field to make review intent explicit.",
)
print(
"PR body lint is warning-only. Maintainers may harden it by setting "
"PR_BODY_LINT_STRICT=1 after existing pull requests are migrated."
)
return 1 if strict else 0
if __name__ == "__main__":
raise SystemExit(main())
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "$#" -ne 2 ]]; then
echo "usage: $0 CANDIDATE_DMG LABEL" >&2
exit 2
fi
candidate_dmg="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
label="$2"
if [[ ! "${label}" =~ ^[A-Za-z0-9._-]{1,80}$ ]]; then
echo "label must contain only ASCII letters, digits, dot, underscore, or dash" >&2
exit 2
fi
sandbox="${RUNNER_TEMP}/opensquilla-release-preservation-${label}"
old_dir="${sandbox}/rc3"
old_mount="${sandbox}/rc3-mount"
candidate_mount="${sandbox}/candidate-mount"
install_root="${sandbox}/Applications"
user_data="${sandbox}/user-data/OpenSquilla"
profile="${user_data}/opensquilla"
probe="${GITHUB_WORKSPACE}/.github/scripts/verify-release-profile-preservation.py"
old_asset="OpenSquilla-0.5.0-rc3-mac-arm64.dmg"
mkdir -p "${old_dir}" "${old_mount}" "${candidate_mount}" "${install_root}" "${user_data}"
cleanup() {
hdiutil detach "${candidate_mount}" -quiet >/dev/null 2>&1 || true
hdiutil detach "${old_mount}" -quiet >/dev/null 2>&1 || true
if [[ -n "${app_pid:-}" ]]; then
kill "${app_pid}" >/dev/null 2>&1 || true
wait "${app_pid}" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT
gh release download v0.5.0rc3 \
--repo opensquilla/opensquilla \
--pattern "${old_asset}" \
--dir "${old_dir}"
old_dmg="${old_dir}/${old_asset}"
test -f "${old_dmg}"
test -f "${candidate_dmg}"
hdiutil attach -nobrowse -readonly -mountpoint "${old_mount}" "${old_dmg}"
ditto "${old_mount}/OpenSquilla.app" "${install_root}/OpenSquilla.app"
hdiutil detach "${old_mount}" -quiet
python "${probe}" seed --home "${profile}" --label "${label}"
hdiutil attach -nobrowse -readonly -mountpoint "${candidate_mount}" "${candidate_dmg}"
mv "${install_root}/OpenSquilla.app" "${install_root}/OpenSquilla.rc3.app"
ditto "${candidate_mount}/OpenSquilla.app" "${install_root}/OpenSquilla.app"
hdiutil detach "${candidate_mount}" -quiet
python "${probe}" verify --home "${profile}" --label "${label}"
app_binary="${install_root}/OpenSquilla.app/Contents/MacOS/OpenSquilla"
test -x "${app_binary}"
OPENSQUILLA_DESKTOP_DISABLE_AUTO_UPDATE=1 \
"${app_binary}" --use-mock-keychain "--user-data-dir=${user_data}" \
>"${sandbox}/candidate-desktop.log" 2>&1 &
app_pid=$!
sleep 8
kill -0 "${app_pid}"
kill "${app_pid}" || true
wait "${app_pid}" || true
app_pid=""
gateway_binary="$(find \
"${install_root}/OpenSquilla.app/Contents/Resources/runtime/gateway" \
-type f -name opensquilla-gateway -perm -111 -print -quit)"
test -x "${gateway_binary}"
OPENSQUILLA_RECOVERY_OFFLINE=1 "${gateway_binary}" recovery inspect \
--home "${profile}" --json >"${sandbox}/candidate-inspect.json"
python - "${profile}" "${sandbox}/candidate-inspect.json" <<'PY'
import json
from pathlib import Path
import sys
home = Path(sys.argv[1]).resolve()
report = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
assert report["outcome"] in {"ready", "attention"}, report
assert Path(report["primary_home"]).resolve() == home, report
assert Path(report["effective_workspace"]).resolve() == home / "workspace", report
configured_state = [
candidate
for candidate in report["candidates"]
if candidate["kind"] == "state" and candidate["configured"] and candidate["valid"]
]
assert len(configured_state) == 1, report
assert Path(configured_state[0]["path"]).resolve() == home / "state", report
PY
python "${probe}" verify --home "${profile}" --label "${label}"
python - "${install_root}/OpenSquilla.app" "${install_root}/OpenSquilla.rc3.app" <<'PY'
import shutil
import sys
for app_path in sys.argv[1:]:
shutil.rmtree(app_path)
PY
test ! -e "${install_root}/OpenSquilla.app"
test ! -e "${install_root}/OpenSquilla.rc3.app"
python "${probe}" verify --home "${profile}" --label "${label}"
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""Seed and verify synthetic Desktop profile data around installer operations."""
from __future__ import annotations
import argparse
import json
import re
import sqlite3
import sys
from pathlib import Path
_LABEL_PATTERN = re.compile(r"[A-Za-z0-9._-]{1,80}")
def _validated_label(value: str) -> str:
if _LABEL_PATTERN.fullmatch(value) is None:
raise ValueError("label must contain only ASCII letters, digits, dot, underscore, or dash")
return value
def _workspace_files(label: str) -> dict[str, str]:
return {
"IDENTITY.md": f"# Synthetic {label} identity sentinel\n",
"USER.md": f"# Synthetic {label} user\n",
"SOUL.md": f"# Synthetic {label} soul\n",
"MEMORY.md": f"# Synthetic {label} memory\n",
}
def _config_text(home: Path, label: str) -> str:
return (
f"# Synthetic {label} release-preservation profile\n"
f"state_dir = {json.dumps(str(home / 'state'))}\n"
f"workspace_dir = {json.dumps(str(home / 'workspace'))}\n"
)
def seed_profile(home: Path, label: str) -> None:
"""Create a minimal synthetic RC3-shaped profile without replacing any file."""
home = home.resolve()
workspace = home / "workspace"
state = home / "state"
protected = [home / "config.toml", state / "sessions.db"] + [
workspace / name for name in _workspace_files(label)
]
existing = [path for path in protected if path.exists()]
if existing:
raise FileExistsError(f"refusing to overwrite preservation fixture: {existing[0]}")
workspace.mkdir(parents=True, exist_ok=True)
state.mkdir(parents=True, exist_ok=True)
for name, expected in _workspace_files(label).items():
(workspace / name).write_text(expected, encoding="utf-8", newline="")
(home / "config.toml").write_text(_config_text(home, label), encoding="utf-8", newline="")
with sqlite3.connect(state / "sessions.db") as connection:
connection.execute(
"CREATE TABLE release_preservation_chat (id TEXT PRIMARY KEY, body TEXT NOT NULL)"
)
connection.execute(
"INSERT INTO release_preservation_chat (id, body) VALUES (?, ?)",
(f"{label}-session", f"synthetic retained chat ({label})"),
)
result = connection.execute("PRAGMA quick_check").fetchone()
if result != ("ok",):
raise RuntimeError(f"seeded sessions.db failed PRAGMA quick_check: {result!r}")
def verify_profile(home: Path, label: str) -> None:
"""Verify exact fixture bytes and a read-only SQLite integrity probe."""
home = home.resolve()
workspace = home / "workspace"
state = home / "state"
for name, expected in _workspace_files(label).items():
actual = (workspace / name).read_text(encoding="utf-8")
if actual != expected:
raise AssertionError(f"{name} changed while installing or uninstalling Desktop")
actual_config = (home / "config.toml").read_text(encoding="utf-8")
if actual_config != _config_text(home, label):
raise AssertionError("config.toml changed while installing or uninstalling Desktop")
database = state / "sessions.db"
with sqlite3.connect(f"{database.as_uri()}?mode=ro", uri=True) as connection:
quick_check = connection.execute("PRAGMA quick_check").fetchone()
if quick_check != ("ok",):
raise AssertionError(f"sessions.db failed PRAGMA quick_check: {quick_check!r}")
row = connection.execute("SELECT id, body FROM release_preservation_chat").fetchone()
expected_row = (f"{label}-session", f"synthetic retained chat ({label})")
if row != expected_row:
raise AssertionError(f"sessions.db retained-chat row changed: {row!r}")
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("operation", choices=("seed", "verify"))
parser.add_argument("--home", type=Path, required=True)
parser.add_argument("--label", type=_validated_label, required=True)
return parser
def main() -> int:
args = _parser().parse_args()
try:
if args.operation == "seed":
seed_profile(args.home, args.label)
print(f"profile preservation fixture seeded: {args.home}")
else:
verify_profile(args.home, args.label)
print(f"profile preservation verified: {args.home}")
except (
AssertionError,
FileExistsError,
OSError,
RuntimeError,
sqlite3.Error,
ValueError,
) as exc:
print(str(exc), file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,126 @@
param(
[Parameter(Mandatory = $true)]
[string]$CandidateInstaller,
[Parameter(Mandatory = $true)]
[ValidatePattern('^[A-Za-z0-9._-]{1,80}$')]
[string]$Label
)
$ErrorActionPreference = 'Stop'
$repository = 'opensquilla/opensquilla'
$oldTag = 'v0.5.0rc3'
$oldAsset = 'OpenSquilla-0.5.0-rc3-win-x64.exe'
$candidate = (Resolve-Path -LiteralPath $CandidateInstaller).Path
$sandbox = Join-Path $env:RUNNER_TEMP "opensquilla-release-preservation-$Label"
$oldDir = Join-Path $sandbox 'rc3'
$installDir = Join-Path $sandbox 'OpenSquilla'
$appData = Join-Path $sandbox 'appdata'
$userData = Join-Path $appData 'OpenSquilla'
$profile = Join-Path $userData 'opensquilla'
$probe = Join-Path $PWD '.github\scripts\verify-release-profile-preservation.py'
$env:APPDATA = $appData
$env:OPENSQUILLA_DESKTOP_DISABLE_AUTO_UPDATE = '1'
$env:OPENSQUILLA_RECOVERY_OFFLINE = '1'
New-Item -ItemType Directory -Force -Path $oldDir, $appData | Out-Null
gh release download $oldTag --repo $repository --pattern $oldAsset --dir $oldDir
if ($LASTEXITCODE -ne 0) { throw 'Failed to download the RC3 Windows installer.' }
$oldInstaller = Join-Path $oldDir $oldAsset
function Stop-InstalledProcesses {
Get-Process -Name 'OpenSquilla', 'opensquilla-gateway' -ErrorAction SilentlyContinue |
ForEach-Object {
try {
$path = if ($_.Path) { [IO.Path]::GetFullPath($_.Path) } else { '' }
$prefix = [IO.Path]::GetFullPath($installDir + [IO.Path]::DirectorySeparatorChar)
if ($path.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) {
& taskkill.exe /PID $_.Id /T /F 2>$null | Out-Null
}
} catch {
if ($_.Exception.Message -notmatch 'exited|cannot find|No process') { throw }
}
}
}
try {
$old = Start-Process -FilePath $oldInstaller -ArgumentList @('/S', "/D=$installDir") `
-Wait -PassThru
if ($old.ExitCode -ne 0) { throw "RC3 installer failed with exit code $($old.ExitCode)." }
python $probe seed --home $profile --label $Label
if ($LASTEXITCODE -ne 0) { throw 'Failed to seed the synthetic RC3 profile.' }
$installed = Start-Process -FilePath $candidate -ArgumentList @('/S', "/D=$installDir") `
-Wait -PassThru
if ($installed.ExitCode -ne 0) {
throw "Candidate installer failed with exit code $($installed.ExitCode)."
}
python $probe verify --home $profile --label $Label
if ($LASTEXITCODE -ne 0) { throw 'Candidate installation changed RC3 profile data.' }
$app = Join-Path $installDir 'OpenSquilla.exe'
if (-not (Test-Path -LiteralPath $app -PathType Leaf)) {
throw 'Candidate installation did not publish OpenSquilla.exe.'
}
$launched = Start-Process -FilePath $app `
-ArgumentList @('--use-mock-keychain', "--user-data-dir=$userData") -PassThru
Start-Sleep -Seconds 8
if ($launched.HasExited) {
throw "Candidate Desktop exited during launch verification: $($launched.ExitCode)"
}
Stop-InstalledProcesses
$gateway = Get-ChildItem -Path (Join-Path $installDir 'resources\runtime\gateway') `
-Filter 'opensquilla-gateway.exe' -File -Recurse | Select-Object -First 1
if (-not $gateway) { throw 'Packaged recovery CLI was not found.' }
$inspectionRaw = & $gateway.FullName recovery inspect --home $profile --json
if ($LASTEXITCODE -ne 0) { throw 'Packaged recovery inspection failed.' }
$inspection = $inspectionRaw | ConvertFrom-Json
if ($inspection.outcome -notin @('ready', 'attention')) {
throw "Unsafe packaged profile inspection: $inspectionRaw"
}
if ([IO.Path]::GetFullPath($inspection.primary_home) -ne [IO.Path]::GetFullPath($profile)) {
throw 'Candidate selected a different primary profile after upgrade.'
}
if (
[IO.Path]::GetFullPath($inspection.effective_workspace) -ne
[IO.Path]::GetFullPath((Join-Path $profile 'workspace'))
) {
throw 'Candidate selected a different workspace after upgrade.'
}
$configuredState = @($inspection.candidates | Where-Object {
$_.kind -eq 'state' -and $_.configured -and $_.valid
})
if (
$configuredState.Count -ne 1 -or
[IO.Path]::GetFullPath($configuredState[0].path) -ne
[IO.Path]::GetFullPath((Join-Path $profile 'state'))
) {
throw 'Candidate selected a different state directory after upgrade.'
}
python $probe verify --home $profile --label $Label
if ($LASTEXITCODE -ne 0) { throw 'Candidate launch changed RC3 profile data.' }
$uninstaller = Get-ChildItem -LiteralPath $installDir -Filter 'Uninstall*.exe' -File |
Select-Object -First 1
if (-not $uninstaller) { throw 'Candidate Windows uninstaller was not found.' }
$uninstall = Start-Process -FilePath $uninstaller.FullName -ArgumentList @('/S') `
-Wait -PassThru
if ($uninstall.ExitCode -ne 0) {
throw "Candidate uninstaller failed with exit code $($uninstall.ExitCode)."
}
$deadline = [DateTime]::UtcNow.AddSeconds(30)
while (
(Test-Path -LiteralPath $app -PathType Leaf) -and
[DateTime]::UtcNow -lt $deadline
) {
Start-Sleep -Seconds 1
}
if (Test-Path -LiteralPath $app -PathType Leaf) {
throw 'Candidate uninstaller did not remove OpenSquilla.exe.'
}
python $probe verify --home $profile --label $Label
if ($LASTEXITCODE -ne 0) { throw 'Candidate uninstaller changed RC3 profile data.' }
} finally {
Stop-InstalledProcesses
}
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env python3
"""Route offline pytest files into stable Windows CI responsibility shards."""
from __future__ import annotations
import argparse
import sys
import tomllib
import xml.etree.ElementTree as ET
from pathlib import Path, PurePosixPath
from typing import Final
SHARD_NAMES: Final[tuple[str, ...]] = (
"core",
"gateway-sqlite",
"recovery-migration",
"desktop-installer-contracts",
)
_GATEWAY_SQLITE_PREFIXES: Final[tuple[str, ...]] = (
"tests/test_gateway/",
"tests/test_health/",
"tests/test_observability/",
"tests/test_persistence/",
"tests/test_scheduler/",
"tests/test_search/",
"tests/test_session/",
)
_RECOVERY_MIGRATION_PREFIXES: Final[tuple[str, ...]] = (
"tests/test_migration/",
"tests/test_migrations/",
"tests/test_recovery/",
)
_DESKTOP_INSTALLER_PREFIXES: Final[tuple[str, ...]] = (
"tests/test_desktop/",
"tests/test_dist/",
"tests/test_packaging/",
"tests/test_uninstall/",
)
_GATEWAY_SQLITE_NAME_TOKENS: Final[tuple[str, ...]] = (
"database",
"gateway",
"memory",
"scheduler",
"session",
"sqlite",
)
_RECOVERY_MIGRATION_NAME_TOKENS: Final[tuple[str, ...]] = (
"legacy_config",
"migrate",
"migration",
"recovery",
)
_DESKTOP_INSTALLER_NAME_TOKENS: Final[tuple[str, ...]] = (
"artifact",
"desktop",
"install",
"release",
"uninstall",
"wheelhouse",
)
_DESKTOP_INSTALLER_EXACT: Final[frozenset[str]] = frozenset(
{
"tests/test_compose_yaml_shape.py",
"tests/test_root_start_scripts.py",
}
)
_CORE_EXACT: Final[frozenset[str]] = frozenset(
{
"tests/test_ci/test_router_artifact_manifest.py",
}
)
def discover_test_files(root: Path) -> tuple[str, ...]:
"""Return every pytest file below ``tests/`` as a repository-relative path."""
tests_root = root / "tests"
excluded = _pytest_excluded_prefixes(root)
relative_paths = (
path.relative_to(root).as_posix() for path in tests_root.rglob("test_*.py")
)
return tuple(
sorted(
relative
for relative in relative_paths
if not any(relative.startswith(prefix) for prefix in excluded)
)
)
def _pytest_excluded_prefixes(root: Path) -> tuple[str, ...]:
pyproject = root / "pyproject.toml"
try:
data = tomllib.loads(pyproject.read_text(encoding="utf-8"))
except (OSError, tomllib.TOMLDecodeError) as exc:
raise ValueError(f"cannot read pytest collection contract from {pyproject}") from exc
configured = data.get("tool", {}).get("pytest", {}).get("ini_options", {})
return tuple(
f"{PurePosixPath(path).as_posix().rstrip('/')}/"
for path in configured.get("norecursedirs", ())
if PurePosixPath(path).as_posix().startswith("tests/")
)
def matching_specialized_shards(path: str) -> tuple[str, ...]:
"""Return specialized shards whose responsibility rules match ``path``."""
normalized = PurePosixPath(path).as_posix()
if normalized in _CORE_EXACT:
return ()
name = PurePosixPath(normalized).name.casefold()
prefix_matches: list[str] = []
if normalized.startswith(_GATEWAY_SQLITE_PREFIXES):
prefix_matches.append("gateway-sqlite")
if normalized.startswith(_RECOVERY_MIGRATION_PREFIXES):
prefix_matches.append("recovery-migration")
if normalized.startswith(_DESKTOP_INSTALLER_PREFIXES):
prefix_matches.append("desktop-installer-contracts")
if prefix_matches:
return tuple(prefix_matches)
matches: list[str] = []
if any(token in name for token in _GATEWAY_SQLITE_NAME_TOKENS):
matches.append("gateway-sqlite")
if any(token in name for token in _RECOVERY_MIGRATION_NAME_TOKENS):
matches.append("recovery-migration")
if normalized in _DESKTOP_INSTALLER_EXACT or any(
token in name for token in _DESKTOP_INSTALLER_NAME_TOKENS
):
matches.append("desktop-installer-contracts")
return tuple(matches)
def shard_for_test(path: str) -> str:
"""Return the one responsibility shard for ``path`` or fail on ambiguity."""
matches = matching_specialized_shards(path)
if len(matches) > 1:
joined = ", ".join(matches)
raise ValueError(f"test file matches multiple Windows shards: {path} ({joined})")
return matches[0] if matches else "core"
def files_for_shard(root: Path, shard: str) -> tuple[str, ...]:
if shard not in SHARD_NAMES:
raise ValueError(f"unknown Windows shard: {shard}")
return tuple(path for path in discover_test_files(root) if shard_for_test(path) == shard)
def _write_failure_summary(junit_path: Path, summary_path: Path, exit_code: int) -> None:
summary_path.parent.mkdir(parents=True, exist_ok=True)
lines = [f"pytest_exit_code={exit_code}"]
if not junit_path.is_file():
lines.append("junit_status=unavailable")
summary_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
return
try:
root = ET.parse(junit_path).getroot()
except (ET.ParseError, OSError) as exc:
lines.extend(("junit_status=unreadable", f"detail={type(exc).__name__}"))
summary_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
return
for testcase in root.iter("testcase"):
failure = testcase.find("failure")
if failure is None:
failure = testcase.find("error")
if failure is None:
continue
class_name = testcase.get("classname", "")
test_name = testcase.get("name", "unknown")
node = f"{class_name}::{test_name}" if class_name else test_name
detail = (failure.text or failure.get("message") or "failure details unavailable").strip()
lines.extend(
(
"junit_status=failed",
f"first_failure={node}",
"detail:",
detail[:12_000],
)
)
break
else:
lines.append("junit_status=passed" if exit_code == 0 else "junit_status=no-test-failure")
summary_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
def _run(args: argparse.Namespace) -> int:
import pytest
root = args.root.resolve()
files = files_for_shard(root, args.shard)
if not files:
print(f"Windows shard {args.shard!r} has no tests", file=sys.stderr)
return 2
args.junit.parent.mkdir(parents=True, exist_ok=True)
args.summary.parent.mkdir(parents=True, exist_ok=True)
args.summary.write_text("pytest_status=started\n", encoding="utf-8")
pytest_args = list(args.pytest_args)
if pytest_args[:1] == ["--"]:
pytest_args = pytest_args[1:]
pytest_args.extend(str(root / path) for path in files)
pytest_args.append(f"--junitxml={args.junit}")
print(f"Running {len(files)} test files in Windows shard {args.shard}")
exit_code = int(pytest.main(pytest_args))
_write_failure_summary(args.junit, args.summary, exit_code)
return exit_code
def _list(args: argparse.Namespace) -> int:
for path in files_for_shard(args.root.resolve(), args.shard):
print(path)
return 0
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)
list_parser = subparsers.add_parser("list", help="list files assigned to one shard")
list_parser.add_argument("shard", choices=SHARD_NAMES)
list_parser.add_argument("--root", type=Path, default=Path.cwd())
list_parser.set_defaults(handler=_list)
run_parser = subparsers.add_parser("run", help="run one shard through pytest")
run_parser.add_argument("shard", choices=SHARD_NAMES)
run_parser.add_argument("--root", type=Path, default=Path.cwd())
run_parser.add_argument("--junit", type=Path, required=True)
run_parser.add_argument("--summary", type=Path, required=True)
run_parser.set_defaults(handler=_run)
return parser
def main() -> int:
parser = _parser()
args, pytest_args = parser.parse_known_args()
if args.command != "run" and pytest_args:
parser.error(f"unrecognized arguments: {' '.join(pytest_args)}")
args.pytest_args = pytest_args
return int(args.handler(args))
if __name__ == "__main__":
raise SystemExit(main())