- TestPluginsEndpoints: 401/403/200 for both endpoints, the update rule table, cache hit and expiry on an injected clock, failure caching, at most 4 version lookups at once, call timeout, marketplace validation, markers, https-only icons, disabled registry making no call - fix: marketplace categories come only from plugins the screen offers, not from registry entries dropped for an invalid ID - check-phase34.sh: TestPluginsEndpoints and useSettings.test.ts required
903 lines
36 KiB
Bash
Executable File
903 lines
36 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# check-phase34: fail-closed gate for Phase 34, plugin marketplace: the
|
||
# framework (kiosk, party.Describe, plugin:manifest/install/update, the admin
|
||
# Plugins screen), the plugin.yaml rollout in the public sm-*-plugin
|
||
# repositories and the registry with /marketplace in the summercms.io
|
||
# repositories.
|
||
#
|
||
# Stages:
|
||
# self-test the go test -json, vitest, TAP and unittest detectors on
|
||
# planted output, the marker rule on a planted repository, the
|
||
# additive comparison on planted packages, a missing repository
|
||
# and the docker NOT RUN rule on a faked host.
|
||
# go go vet ./..., go test -short ./..., the Docker-free tests of
|
||
# the phase by required name (GO_TESTS) and the examples/hello
|
||
# workspace module.
|
||
# docker Docker host only: go test ./... in the framework, the
|
||
# non-short install tests by required name (DOCKER_TESTS), and
|
||
# the site plugin's registry suites (SITE_DOCKER_TESTS).
|
||
# plugins each sibling sm-*-plugin repository (PLUGIN_REPOS): it
|
||
# resolves the framework to this checkout, go vet, go test
|
||
# -short, TestPluginManifest by required name in every plugin
|
||
# package and `summer plugin:manifest --check` (built from this
|
||
# checkout) for every committed plugin.yaml.
|
||
# site ../sm-summercmsio-app: the site plugin's go vet, go test
|
||
# -short and Docker-free tests by required name; the app's go
|
||
# vet and go test; the changelog-post unittest suite;
|
||
# scripts/check-deploy.sh when nginx is installed; and in
|
||
# vue-summercmsio-app pnpm test, build:changelog,
|
||
# test:changelog and test:marketplace.
|
||
# admin npm run test (required vitest files ADMIN_FILES), typecheck,
|
||
# scripts/check-admin-openapi.sh --check and
|
||
# scripts/check-admin-dist.sh.
|
||
# docs TestDocsTree, TestCommandsDocumented and summer docs:build
|
||
# --check.
|
||
# marker the lines this phase added since PHASE34_BASE_TAG to non-test
|
||
# Go files, READMEs and docs under modules/, cmd/, internal/ and
|
||
# docs/ (testdata excluded) name no consuming application.
|
||
# additive scripts/apisurface of every package under modules/ changed
|
||
# since PHASE34_BASE_TAG keeps every exported line of the tag
|
||
# and adds no method to an interface that exists at the tag
|
||
# (kiosk is new; party and cabana only gain elements).
|
||
#
|
||
# A Go stage exits non-zero on a failing command, or on a go test -json run
|
||
# that fails, skips a test (a subtest included), matches zero tests, prints
|
||
# "no tests to run" or misses a required name. A stage whose repository or
|
||
# directory does not exist fails with "missing <path>".
|
||
#
|
||
# Several stages may be given at once (--go --plugins ...). --all runs
|
||
# self-test, go, plugins, site, admin, docs, marker and additive, then docker
|
||
# when a Docker daemon answers. Without one docker is NOT RUN, which fails
|
||
# --all unless PHASE34_ALLOW_NO_DOCKER=1.
|
||
#
|
||
# Environment overrides: PHASE34_ROOT (this checkout), PHASE34_SIBLINGS (the
|
||
# directory holding the sibling repositories, default the parent of this
|
||
# checkout), SITE_APP (default $PHASE34_SIBLINGS/sm-summercmsio-app),
|
||
# PHASE34_BASE_TAG (default v0.5.3, the newest tag before the phase),
|
||
# PHASE34_ALLOW_NO_DOCKER, PHASE34_PENDING_MANIFESTS (space-separated plugin
|
||
# repositories whose manifest checks are reported as pending instead of
|
||
# run), PHASE34_PLUGIN_SKIP (space-separated <repo>:<TestName> entries that
|
||
# go test -short -skips for a pre-existing failure outside the phase; each
|
||
# use is printed).
|
||
set -euo pipefail
|
||
|
||
unset FORCE_COLOR
|
||
export NO_COLOR=1
|
||
|
||
ROOT="${PHASE34_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||
SIBLINGS="${PHASE34_SIBLINGS:-$(dirname "$ROOT")}"
|
||
SITE_APP="${SITE_APP:-$SIBLINGS/sm-summercmsio-app}"
|
||
SITE_PLUGIN="$SITE_APP/plugins/golem15/summercms"
|
||
SITE_VUE="$SITE_APP/vue-summercmsio-app"
|
||
PHASE34_BASE_TAG="${PHASE34_BASE_TAG:-v0.5.3}"
|
||
# sm-user-plugin's TestRegisterCORSPath walks up to an application's
|
||
# config/http.yaml and fails in a standalone checkout (34-02 note 1).
|
||
PHASE34_PLUGIN_SKIP="${PHASE34_PLUGIN_SKIP-sm-user-plugin:TestRegisterCORSPath}"
|
||
|
||
# The public plugin repositories that carry a plugin.yaml (plan 34-02).
|
||
PLUGIN_REPOS=(
|
||
sm-translate-plugin sm-user-plugin sm-chat-plugin sm-faq-plugin sm-menu-plugin
|
||
sm-seo-plugin sm-sitemap-plugin sm-mfa-plugin sm-oauthserver-plugin sm-sociallogin-plugin
|
||
sm-paymentgateway-plugin sm-paymentgatewaysubscriptions-plugin sm-pgstripe-plugin
|
||
sm-journal-plugin sm-chatchannels-plugin sm-chatvideo-plugin sm-forum-plugin
|
||
sm-herald-plugin sm-userfriends-plugin sm-chatbots-plugin
|
||
)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Required test names: <package>:<TestName>.
|
||
|
||
# go stage: Docker-free framework tests (-short).
|
||
GO_TESTS=(
|
||
./modules/kiosk:TestManifestSmoke ./modules/kiosk:TestPluginManifest
|
||
./internal/build:TestPluginInfoSmoke ./internal/build:TestAddPluginConstID
|
||
./modules/cabana:TestPluginsScreenSmoke
|
||
# 34-06 unit tests.
|
||
./modules/kiosk:TestReadIdentity ./modules/kiosk:TestDerive ./modules/kiosk:TestManifestFormat
|
||
./modules/kiosk:TestCheck ./modules/kiosk:TestVersions ./modules/kiosk:TestRegistryClient
|
||
./modules/kiosk:TestPackagePath ./modules/kiosk:TestPluginDirs ./modules/kiosk:TestLastElem
|
||
./modules/kiosk:ExampleMarshal ./modules/kiosk:ExampleNewestCompatible
|
||
./modules/party:TestDescribe
|
||
./internal/build:TestPluginDirs ./internal/build:TestEffectiveFramework ./internal/build:TestResolveInstall
|
||
./internal/build:TestAskToProceed ./internal/build:TestAppendManifestPlugins
|
||
./cmd/summer:TestPluginCommands
|
||
./modules/cabana:TestPluginsEndpoints
|
||
)
|
||
|
||
# docker stage: the non-short framework tests (file:// proxies, git
|
||
# fixtures, app builds).
|
||
DOCKER_TESTS=(./internal/build:TestInstallSmoke)
|
||
|
||
# site stage: the site plugin's Docker-free tests (-short).
|
||
SITE_SHORT_TESTS=(.:TestPluginIdentity .:TestRoutesAssemble)
|
||
|
||
# docker stage: the site plugin's Postgres suites.
|
||
SITE_DOCKER_TESTS=(.:TestRegistrySmoke)
|
||
|
||
# admin stage: vitest files that must pass.
|
||
ADMIN_FILES=(tests/views/PluginsView.test.ts tests/state/useSettings.test.ts)
|
||
|
||
# marker stage: names of consuming applications (split so this file does
|
||
# not match itself). summercms.io, the framework's own site and default
|
||
# registry, is allowed.
|
||
APP_NAMES=("grzy""by" "fono""teka" "p[lł]yta""rium" "summercms""io" "sm-demo""-app" "sm-bm""-app")
|
||
|
||
usage() {
|
||
cat >&2 <<'EOF'
|
||
usage:
|
||
check-phase34.sh --self-test
|
||
check-phase34.sh [--go] [--docker] [--plugins] [--site] [--admin] [--docs] [--marker] [--additive]
|
||
check-phase34.sh --all
|
||
EOF
|
||
exit 2
|
||
}
|
||
|
||
# detect_go reads go test -json. Exit 1 fail/build, 2 skip, 3 zero/no-tests,
|
||
# 4 non-JSON, 5 missing required name (REQUIRE_TESTS, space-separated).
|
||
detect_go() {
|
||
python3 - "$1" <<'PY'
|
||
import json, os, sys
|
||
path = sys.argv[1]
|
||
require = [n for n in os.environ.get("REQUIRE_TESTS", "").split() if n]
|
||
passed = set()
|
||
failed = []
|
||
with open(path, encoding="utf-8", errors="replace") as fh:
|
||
for raw in fh:
|
||
line = raw.strip()
|
||
if not line.startswith("{"):
|
||
continue
|
||
try:
|
||
ev = json.loads(line)
|
||
except json.JSONDecodeError:
|
||
print("refuse: non-json test output", file=sys.stderr)
|
||
sys.exit(4)
|
||
action = ev.get("Action")
|
||
test = ev.get("Test") or ""
|
||
pkg = ev.get("Package") or ev.get("ImportPath") or ""
|
||
if action == "build-fail" or (action == "fail" and ev.get("FailedBuild")):
|
||
print(f"refuse: build failed {pkg}", file=sys.stderr)
|
||
sys.exit(1)
|
||
text = ev.get("Output") or ""
|
||
if action == "output" and "no tests to run" in text:
|
||
print(f"refuse: no tests to run in {pkg}", file=sys.stderr)
|
||
sys.exit(3)
|
||
if action == "skip" and test:
|
||
print(f"refuse: skipped {pkg} {test}", file=sys.stderr)
|
||
sys.exit(2)
|
||
if action == "fail":
|
||
failed.append(f"{pkg} {test}".strip())
|
||
if action == "pass" and test:
|
||
passed.add(test)
|
||
if failed:
|
||
print("refuse: failed " + ", ".join(failed), file=sys.stderr)
|
||
sys.exit(1)
|
||
if not passed:
|
||
print("refuse: zero tests", file=sys.stderr)
|
||
sys.exit(3)
|
||
missing = [n for n in require if n not in passed]
|
||
if missing:
|
||
print("refuse: required tests did not pass: " + ", ".join(missing), file=sys.stderr)
|
||
sys.exit(5)
|
||
PY
|
||
}
|
||
|
||
# detect_vitest reads a vitest run's output. Exit 1 failed, skipped or todo
|
||
# tests, 3 no all-passing summary, 5 a required file (REQUIRE_FILES) did
|
||
# not pass.
|
||
detect_vitest() {
|
||
python3 - "$1" <<'PY'
|
||
import os, re, sys
|
||
text = open(sys.argv[1], encoding="utf-8", errors="replace").read()
|
||
text = re.sub(r"\x1b\[[0-9;]*m", "", text)
|
||
need = os.environ.get("REQUIRE_FILES", "").split()
|
||
summary = re.findall(r"^\s*(Test Files|Tests)\s+(.*)$", text, re.M)
|
||
if any(re.search(r"\b(failed|skipped|todo)\b", rest) for _, rest in summary) or re.search(r"^\s*(FAIL|×)\s", text, re.M):
|
||
print("refuse: vitest reports failed, skipped or todo tests", file=sys.stderr)
|
||
sys.exit(1)
|
||
m = re.search(r"^\s*Tests\s+(\d+) passed \((\d+)\)\s*$", text, re.M)
|
||
if not m or m.group(1) != m.group(2) or m.group(1) == "0":
|
||
print("refuse: no all-passing vitest summary", file=sys.stderr)
|
||
sys.exit(3)
|
||
missing = [f for f in need if not re.search(r"^\s*✓ (?:\|\w+\| )?" + re.escape(f) + r" \(\d+ tests?\)", text, re.M)]
|
||
if missing:
|
||
print("refuse: did not pass: " + ", ".join(missing), file=sys.stderr)
|
||
sys.exit(5)
|
||
PY
|
||
}
|
||
|
||
# detect_tap reads node --test TAP output. Exit 1 a "not ok" line or a
|
||
# non-zero fail, skipped or todo count, 3 no "# pass N" with N > 0.
|
||
detect_tap() {
|
||
python3 - "$1" <<'PY'
|
||
import re, sys
|
||
text = open(sys.argv[1], encoding="utf-8", errors="replace").read()
|
||
counts = dict((k, int(v)) for k, v in re.findall(r"^# (pass|fail|skipped|todo|cancelled) (\d+)\s*$", text, re.M))
|
||
if re.search(r"^\s*not ok\b", text, re.M) or any(counts.get(k, 0) for k in ("fail", "skipped", "todo", "cancelled")):
|
||
print("refuse: TAP reports failed, skipped, todo or cancelled tests", file=sys.stderr)
|
||
sys.exit(1)
|
||
if counts.get("pass", 0) == 0:
|
||
print("refuse: TAP reports no passing test", file=sys.stderr)
|
||
sys.exit(3)
|
||
PY
|
||
}
|
||
|
||
# detect_unittest reads python unittest output. Exit 1 a failure, error or
|
||
# skip, 3 no "Ran N tests" with N > 0 followed by OK.
|
||
detect_unittest() {
|
||
python3 - "$1" <<'PY'
|
||
import re, sys
|
||
text = open(sys.argv[1], encoding="utf-8", errors="replace").read()
|
||
if re.search(r"^FAILED\b", text, re.M) or re.search(r"skipped=\d+", text) or re.search(r"^(FAIL|ERROR):", text, re.M):
|
||
print("refuse: unittest reports failed, erroring or skipped tests", file=sys.stderr)
|
||
sys.exit(1)
|
||
m = re.search(r"^Ran (\d+) tests? in [0-9.]+s\s*\n\s*\nOK\s*$", text, re.M)
|
||
if not m or m.group(1) == "0":
|
||
print("refuse: no passing unittest run", file=sys.stderr)
|
||
sys.exit(3)
|
||
PY
|
||
}
|
||
|
||
# go_json DIR ARGS... runs go test -json ARGS in DIR and checks the stream
|
||
# with detect_go.
|
||
go_json() {
|
||
local dir="$1"
|
||
shift
|
||
local log err rc=0 dc=0
|
||
log="$(mktemp)"
|
||
err="$(mktemp)"
|
||
(cd "$dir" && go test -json "$@") >"$log" 2>"$err" || rc=$?
|
||
detect_go "$log" || dc=$?
|
||
if [[ "$rc" -ne 0 || "$dc" -ne 0 ]]; then
|
||
cat "$err" >&2 || true
|
||
python3 -c 'import json,sys
|
||
for raw in open(sys.argv[1], errors="replace"):
|
||
try: ev = json.loads(raw)
|
||
except ValueError: continue
|
||
out = ev.get("Output", "")
|
||
if ev.get("Action") == "output" and ("FAIL" in out or "SKIP" in out or "_test.go:" in out):
|
||
sys.stderr.write(out)' "$log" 2>&1 | tail -n 40 >&2 || true
|
||
rm -f "$log" "$err"
|
||
echo "refuse: FAIL go test $* in $dir (test=$rc detect=$dc)" >&2
|
||
return 1
|
||
fi
|
||
echo "go: $(grep -c '"Action":"pass","Package":"[^"]*","Test":"[^"/]*","Elapsed"' "$log") top-level tests passed in $dir ($*)"
|
||
rm -f "$log" "$err"
|
||
}
|
||
|
||
# go_named DIR PACKAGE FLAGS NAME... runs exactly the named top-level tests
|
||
# and requires every one to pass. FLAGS is word-split.
|
||
go_named() {
|
||
local dir="$1" pkg="$2" flags="$3"
|
||
shift 3
|
||
if [[ "$#" -eq 0 ]]; then
|
||
echo "refuse: go_named $pkg in $dir without required names" >&2
|
||
return 1
|
||
fi
|
||
local names=("$@") regex
|
||
regex="^($(IFS='|'; echo "${names[*]}"))\$"
|
||
# shellcheck disable=SC2086
|
||
REQUIRE_TESTS="${names[*]}" go_json "$dir" $flags -count=1 -run "$regex" "$pkg"
|
||
}
|
||
|
||
# go_pkg_named DIR FLAGS ENTRY... groups <package>:<TestName> entries by
|
||
# package and runs go_named once per package.
|
||
go_pkg_named() {
|
||
local dir="$1" flags="$2"
|
||
shift 2
|
||
local entry pkg name pkgs=()
|
||
declare -A by=()
|
||
for entry in "$@"; do
|
||
pkg="${entry%%:*}"
|
||
name="${entry#*:}"
|
||
if [[ -z "$pkg" || -z "$name" || "$pkg" == "$entry" ]]; then
|
||
echo "refuse: malformed required test entry $entry" >&2
|
||
return 1
|
||
fi
|
||
if [[ -z "${by[$pkg]+x}" ]]; then
|
||
pkgs+=("$pkg")
|
||
by[$pkg]="$name"
|
||
else
|
||
by[$pkg]="${by[$pkg]} $name"
|
||
fi
|
||
done
|
||
for pkg in "${pkgs[@]}"; do
|
||
# shellcheck disable=SC2086
|
||
go_named "$dir" "$pkg" "$flags" ${by[$pkg]}
|
||
done
|
||
}
|
||
|
||
# go_short DIR [SKIP] runs go test -short ./... in DIR, with go test -skip
|
||
# SKIP when given.
|
||
go_short() {
|
||
local dir="$1" skip="${2:-}" out args=(-count=1 -short)
|
||
if [[ -n "$skip" ]]; then
|
||
args+=(-skip "$skip")
|
||
fi
|
||
if ! out="$(cd "$dir" && go test "${args[@]}" ./... 2>&1)"; then
|
||
printf '%s\n' "$out" | grep -v '^ok ' | grep -E '^(FAIL|---|panic|\s+\S+_test\.go:)' | tail -n 60 >&2 || true
|
||
echo "refuse: FAIL go test -short ./... in $dir" >&2
|
||
return 1
|
||
fi
|
||
echo "short: $dir${skip:+ (skip $skip)}"
|
||
}
|
||
|
||
go_vet() {
|
||
local dir="$1"
|
||
shift
|
||
(cd "$dir" && go vet "$@")
|
||
echo "vet: $dir ($*)"
|
||
}
|
||
|
||
require_dir() {
|
||
[[ -d "$1" ]] || {
|
||
echo "refuse: missing $1 ($2)" >&2
|
||
return 1
|
||
}
|
||
}
|
||
|
||
# workspace_check DIR refuses a repository that does not resolve the
|
||
# framework to this checkout (no trust in local go.work files).
|
||
workspace_check() {
|
||
local dir="$1" got rgot rwant
|
||
got="$( (cd "$dir" && go list -m -f '{{if .Replace}}{{.Replace.Dir}}{{else}}{{.Dir}}{{end}}' git.golem15.com/golem15/summercms 2>/dev/null) || true)"
|
||
rgot="$(realpath -e -- "$got" 2>/dev/null || true)"
|
||
rwant="$(realpath -e -- "$ROOT" 2>/dev/null || true)"
|
||
if [[ -z "$got" || -z "$rgot" || "$rgot" != "$rwant" ]]; then
|
||
echo "refuse: $dir resolves git.golem15.com/golem15/summercms to ${got:-<nothing>}, not $ROOT" >&2
|
||
return 1
|
||
fi
|
||
echo "workspace: $dir resolves the framework to $rwant"
|
||
}
|
||
|
||
# marker_hits DIR BASE prints file:line for every line added since BASE to
|
||
# a non-test Go file, a README or a docs page under modules/, cmd/,
|
||
# internal/ or docs/ (testdata excluded) that names a consuming application.
|
||
marker_hits() {
|
||
local dir="$1" base="$2" pattern
|
||
pattern="$(IFS='|'; echo "${APP_NAMES[*]}")"
|
||
git -C "$dir" diff -U0 "$base" HEAD -- \
|
||
'modules/*.go' 'modules/**/*.go' 'cmd/**/*.go' 'internal/**/*.go' \
|
||
'modules/**/README.md' 'cmd/**/README.md' 'internal/**/README.md' 'docs' \
|
||
':(exclude)*_test.go' ':(exclude)**/*_test.go' ':(exclude)**/testdata/**' |
|
||
PATTERN="$pattern" python3 -c '
|
||
import os, re, sys
|
||
pat = re.compile(os.environ["PATTERN"], re.I)
|
||
path, line = "", 0
|
||
for raw in sys.stdin:
|
||
if raw.startswith("+++ "):
|
||
path = raw[6:].rstrip("\n") if raw.startswith("+++ b/") else ""
|
||
continue
|
||
m = re.match(r"^@@ -\S+ \+(\d+)", raw)
|
||
if m:
|
||
line = int(m.group(1))
|
||
continue
|
||
if raw.startswith("+") and not raw.startswith("+++"):
|
||
if path and pat.search(raw[1:]):
|
||
print(f"{path}:{line}: {raw[1:].strip()[:120]}")
|
||
line += 1
|
||
'
|
||
}
|
||
|
||
expect_detector() {
|
||
local detector="$1" name="$2" want="$3" payload="$4" log dc=0
|
||
log="$(mktemp)"
|
||
printf '%s\n' "$payload" >"$log"
|
||
"$detector" "$log" >/dev/null 2>&1 || dc=$?
|
||
rm -f "$log"
|
||
if [[ "$dc" -ne "$want" ]]; then
|
||
echo "refuse: self-test $detector $name: exit $dc, want $want" >&2
|
||
return 1
|
||
fi
|
||
echo "PASS self-test $detector $name"
|
||
}
|
||
|
||
# expect_mode NAME WANT_EXIT WANT_TEXT MODE ENV... runs this script in MODE
|
||
# with ENV and checks its exit status and output.
|
||
expect_mode() {
|
||
local name="$1" want="$2" text="$3" mode="$4"
|
||
shift 4
|
||
local out rc=0
|
||
out="$(env "$@" bash "${BASH_SOURCE[0]}" "$mode" 2>&1)" || rc=$?
|
||
if [[ "$rc" -ne "$want" ]] || ! grep -qF -- "$text" <<<"$out"; then
|
||
printf '%s\n' "$out" | tail -n 10 >&2
|
||
echo "refuse: self-test $name: exit $rc (want $want), output must carry \"$text\"" >&2
|
||
return 1
|
||
fi
|
||
echo "PASS self-test $name"
|
||
}
|
||
|
||
run_self_test() {
|
||
bash -n "${BASH_SOURCE[0]}"
|
||
# A failing test, a build failure, skips, zero tests and a missing
|
||
# required name are each detected.
|
||
expect_detector detect_go pass 0 '{"Action":"pass","Package":"p","Test":"TestReadIdentity"}'
|
||
expect_detector detect_go fail 1 '{"Action":"pass","Package":"p","Test":"TestA"}
|
||
{"Action":"fail","Package":"p","Test":"TestResolveInstall"}'
|
||
expect_detector detect_go build 1 '{"Action":"build-fail","ImportPath":"p"}'
|
||
expect_detector detect_go skip 2 '{"Action":"pass","Package":"p","Test":"TestA"}
|
||
{"Action":"skip","Package":"p","Test":"TestInstallSmoke"}'
|
||
expect_detector detect_go subtest-skip 2 '{"Action":"skip","Package":"p","Test":"TestInstallSmoke/module"}
|
||
{"Action":"pass","Package":"p","Test":"TestInstallSmoke"}'
|
||
expect_detector detect_go zero 3 '{"Action":"pass","Package":"p"}'
|
||
expect_detector detect_go no-tests 3 '{"Action":"output","Package":"p","Output":"testing: warning: no tests to run\n"}
|
||
{"Action":"pass","Package":"p"}'
|
||
expect_detector detect_go nonjson 4 '{"Action":"pass",'
|
||
REQUIRE_TESTS="TestManifestSmoke TestPluginManifest" expect_detector detect_go missing-named 5 '{"Action":"pass","Package":"p","Test":"TestManifestSmoke"}'
|
||
REQUIRE_TESTS="TestPluginManifest" expect_detector detect_go subtest-is-not-parent 5 '{"Action":"pass","Package":"p","Test":"TestOuter/TestPluginManifest"}
|
||
{"Action":"pass","Package":"p","Test":"TestOther"}'
|
||
local ok=' ✓ tests/views/PluginsView.test.ts (9 tests) 15ms
|
||
|
||
Test Files 1 passed (1)
|
||
Tests 9 passed (9)'
|
||
REQUIRE_FILES="tests/views/PluginsView.test.ts" expect_detector detect_vitest pass 0 "$ok"
|
||
REQUIRE_FILES="tests/views/Other.test.ts" expect_detector detect_vitest missing-file 5 "$ok"
|
||
expect_detector detect_vitest failed 1 ' Test Files 1 failed | 1 passed (2)
|
||
Tests 1 failed | 16 passed (17)'
|
||
expect_detector detect_vitest no-summary 3 ' ✓ tests/views/PluginsView.test.ts (9 tests) 15ms'
|
||
expect_detector detect_tap pass 0 $'TAP version 13\nok 1 - installCommand\n1..1\n# pass 1\n# fail 0\n# skipped 0\n# todo 0'
|
||
expect_detector detect_tap not-ok 1 $'TAP version 13\nnot ok 1 - installCommand\n1..1\n# pass 0\n# fail 1'
|
||
expect_detector detect_tap skipped 1 $'TAP version 13\nok 1 - a # SKIP\n1..1\n# pass 1\n# fail 0\n# skipped 1'
|
||
expect_detector detect_tap zero 3 $'TAP version 13\n1..0\n# pass 0\n# fail 0'
|
||
expect_detector detect_unittest pass 0 $'....\n----------------------------------------------------------------------\nRan 4 tests in 0.012s\n\nOK'
|
||
expect_detector detect_unittest failed 1 $'.F..\n----------------------------------------------------------------------\nRan 4 tests in 0.012s\n\nFAILED (failures=1)'
|
||
expect_detector detect_unittest skipped 1 $'.s..\n----------------------------------------------------------------------\nRan 4 tests in 0.012s\n\nOK (skipped=1)'
|
||
expect_detector detect_unittest zero 3 $'\n----------------------------------------------------------------------\nRan 0 tests in 0.000s\n\nOK'
|
||
|
||
local tmp
|
||
tmp="$(mktemp -d)"
|
||
# The marker rule on a planted repository: an added application name in
|
||
# a module source, a command source or a docs page is found; a test
|
||
# file, a testdata fixture and a line that was there before the base
|
||
# are not.
|
||
git -C "$tmp" init -q
|
||
mkdir -p "$tmp/modules/kiosk/testdata" "$tmp/cmd/summer" "$tmp/docs"
|
||
printf 'package kiosk\n\n// Old mentions fono''teka from before the phase.\nfunc Old() {}\n' >"$tmp/modules/kiosk/old.go"
|
||
git -C "$tmp" add -A
|
||
git -C "$tmp" -c user.name=t -c user.email=t@example.test commit -q -m base
|
||
local base
|
||
base="$(git -C "$tmp" rev-parse HEAD)"
|
||
printf 'package kiosk\n\n// Client asks the registry at summercms.io.\nfunc Client() {}\n' >"$tmp/modules/kiosk/client.go"
|
||
printf 'package kiosk\n\n// The grzy''by fixture.\nfunc helper() {}\n' >"$tmp/modules/kiosk/client_test.go"
|
||
printf 'package fixture\n\n// sm-demo''-app fixture.\n' >"$tmp/modules/kiosk/testdata/f.go"
|
||
git -C "$tmp" add -A
|
||
git -C "$tmp" -c user.name=t -c user.email=t@example.test commit -q -m clean
|
||
if [[ -n "$(marker_hits "$tmp" "$base")" ]]; then
|
||
rm -rf "$tmp"
|
||
echo "refuse: self-test marker_hits flagged a clean change, a test file or a fixture" >&2
|
||
return 1
|
||
fi
|
||
printf 'package kiosk\n\n// Client asks the summercms''io app.\nfunc Client() {}\n' >"$tmp/modules/kiosk/client.go"
|
||
printf 'package main\n\n// Used by p''łytarium.\nfunc x() {}\n' >"$tmp/cmd/summer/plugin.go"
|
||
printf '# Marketplace\n\nInstalled in sm-bm''-app.\n' >"$tmp/docs/marketplace.md"
|
||
git -C "$tmp" add -A
|
||
git -C "$tmp" -c user.name=t -c user.email=t@example.test commit -q -m dirty
|
||
local hits
|
||
hits="$(marker_hits "$tmp" "$base")"
|
||
if ! grep -q '^modules/kiosk/client.go:3:' <<<"$hits" || ! grep -q '^cmd/summer/plugin.go:3:' <<<"$hits" ||
|
||
! grep -q '^docs/marketplace.md:3:' <<<"$hits"; then
|
||
rm -rf "$tmp"
|
||
echo "refuse: self-test marker_hits missed a planted name: $hits" >&2
|
||
return 1
|
||
fi
|
||
echo "PASS self-test marker_hits"
|
||
|
||
# A missing repository fails the stage that needs it.
|
||
expect_mode missing-plugins 1 "missing $tmp/none/sm-translate-plugin" --plugins "PHASE34_SIBLINGS=$tmp/none"
|
||
expect_mode missing-site 1 "missing $tmp/none/sm-summercmsio-app" --site "PHASE34_SIBLINGS=$tmp/none" "SITE_APP=$tmp/none/sm-summercmsio-app"
|
||
|
||
# The docker NOT RUN rule: a docker that does not answer.
|
||
mkdir -p "$tmp/bin"
|
||
printf '#!/bin/sh\nexit 1\n' >"$tmp/bin/docker"
|
||
chmod +x "$tmp/bin/docker"
|
||
expect_mode docker-not-run 1 "NOT RUN docker" --docker-gate "PATH=$tmp/bin:$PATH" PHASE34_ALLOW_NO_DOCKER=0
|
||
expect_mode docker-not-run-allowed 0 "NOT RUN docker" --docker-gate "PATH=$tmp/bin:$PATH" PHASE34_ALLOW_NO_DOCKER=1
|
||
expect_mode docker-stage-needs-daemon 1 "needs a reachable Docker daemon" --docker "PATH=$tmp/bin:$PATH"
|
||
rm -rf "$tmp"
|
||
self_test_additive
|
||
echo "phase34 self-test passed"
|
||
}
|
||
|
||
# additive_case TOOL NAME WANT BASE_SRC HEAD_SRC runs a planted package pair
|
||
# through scripts/apisurface and additive_compare; WANT is the line the
|
||
# comparison must print ("removed or changed", "method added to existing
|
||
# interface" or "ok").
|
||
additive_case() {
|
||
local tool="$1" name="$2" want="$3" dir out rc=0 ok=0
|
||
dir="$(mktemp -d)"
|
||
mkdir -p "$dir/base" "$dir/head"
|
||
printf '%s\n' "$4" >"$dir/base/p.go"
|
||
printf '%s\n' "$5" >"$dir/head/p.go"
|
||
if api_surface "$tool" "$dir/base" "$dir/base.api" && api_surface "$tool" "$dir/head" "$dir/head.api"; then
|
||
out="$(additive_compare "selftest/$name" "$dir/base.api" "$dir/head.api" 2>&1)" || rc=$?
|
||
case "$want" in
|
||
ok) [[ "$rc" -eq 0 ]] && grep -q '^additive: ok selftest/' <<<"$out" && ok=1 ;;
|
||
*) [[ "$rc" -ne 0 ]] && grep -q "^additive: $want in selftest/$name: " <<<"$out" && ok=1 ;;
|
||
esac
|
||
fi
|
||
rm -rf -- "${dir:?}"
|
||
if [[ "$ok" -ne 1 ]]; then
|
||
printf '%s\n' "${out:-}" >&2
|
||
echo "refuse: self-test additive $name (want $want, exit $rc)"
|
||
return 1
|
||
fi
|
||
echo "PASS self-test additive $name"
|
||
}
|
||
|
||
# self_test_additive plants the refused and the accepted API changes.
|
||
self_test_additive() {
|
||
local tmp tool failed=0
|
||
tmp="$(mktemp -d)"
|
||
tool="$tmp/apisurface"
|
||
(cd "$ROOT" && go build -o "$tool" ./scripts/apisurface)
|
||
local p=$'package p\n\n'
|
||
additive_case "$tool" removed-func "removed or changed" "${p}func A() {}"$'\n\n'"func B() {}" "${p}func A() {}" || failed=1
|
||
additive_case "$tool" changed-param "removed or changed" "${p}func F(n int) {}" "${p}func F(n int64) {}" || failed=1
|
||
additive_case "$tool" removed-field "removed or changed" "${p}"$'type Info struct {\n\tID string\n\tVersion string\n}' "${p}"$'type Info struct {\n\tID string\n}' || failed=1
|
||
additive_case "$tool" interface-method-added "method added to existing interface" \
|
||
"${p}"$'type HasThing interface {\n\tThing() string\n}' \
|
||
"${p}"$'type HasThing interface {\n\tThing() string\n\tOther() error\n}' || failed=1
|
||
additive_case "$tool" func-added ok "${p}func A() {}" "${p}func A() {}"$'\n\n'"func B(s string) error { return nil }" || failed=1
|
||
additive_case "$tool" struct-field-added ok \
|
||
"${p}"$'type SettingsEntry struct {\n\tCode string `json:"code"`\n}' \
|
||
"${p}"$'type SettingsEntry struct {\n\tCode string `json:"code"`\n\tPath string `json:"path,omitempty"`\n}' || failed=1
|
||
rm -rf -- "${tmp:?}"
|
||
if [[ "$failed" -ne 0 ]]; then
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
run_go() {
|
||
go_vet "$ROOT" ./...
|
||
go_short "$ROOT"
|
||
go_pkg_named "$ROOT" "-short" "${GO_TESTS[@]}"
|
||
require_dir "$ROOT/examples/hello" "examples/hello"
|
||
(cd "$ROOT/examples/hello" && go test -count=1 ./... >/dev/null)
|
||
echo "hello: examples/hello go test ./... ok"
|
||
echo "phase34 go passed"
|
||
}
|
||
|
||
docker_up() {
|
||
command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1
|
||
}
|
||
|
||
run_docker() {
|
||
if ! docker_up; then
|
||
echo "refuse: the docker stage needs a reachable Docker daemon (testcontainers); run it on a Docker host (sudo -u golem when the socket is denied)" >&2
|
||
return 1
|
||
fi
|
||
require_dir "$SITE_PLUGIN" "site plugin"
|
||
local out
|
||
if ! out="$(cd "$ROOT" && go test -count=1 -timeout 30m ./... 2>&1)"; then
|
||
printf '%s\n' "$out" | grep -v '^ok ' | grep -E '^(FAIL|---|panic|\s+\S+_test\.go:)' | tail -n 60 >&2 || true
|
||
echo "refuse: FAIL go test ./... in $ROOT" >&2
|
||
return 1
|
||
fi
|
||
echo "full: $ROOT go test ./..."
|
||
go_pkg_named "$ROOT" "-timeout 20m" "${DOCKER_TESTS[@]}"
|
||
workspace_check "$SITE_PLUGIN"
|
||
go_pkg_named "$SITE_PLUGIN" "-timeout 20m" "${SITE_DOCKER_TESTS[@]}"
|
||
echo "phase34 docker passed"
|
||
}
|
||
|
||
# docker_gate is the docker step of --all: run the stage when a daemon
|
||
# answers; otherwise print NOT RUN and fail unless PHASE34_ALLOW_NO_DOCKER=1.
|
||
docker_gate() {
|
||
if docker_up; then
|
||
if bash "${BASH_SOURCE[0]}" --docker; then
|
||
echo "PASS docker"
|
||
return 0
|
||
fi
|
||
echo "FAIL docker"
|
||
return 1
|
||
fi
|
||
cat >&2 <<'EOF'
|
||
************************************************************************
|
||
NOT RUN docker: no reachable Docker daemon on this host.
|
||
go test ./... of the framework, TestInstallSmoke's non-short subtests and
|
||
the site plugin's TestRegistrySmoke did NOT run. Run on a Docker host
|
||
(through sudo -u golem when the socket is denied):
|
||
bash scripts/check-phase34.sh --docker
|
||
************************************************************************
|
||
EOF
|
||
echo "NOT RUN docker (Docker-host only; see the notice above)"
|
||
if [[ "${PHASE34_ALLOW_NO_DOCKER:-0}" != "1" ]]; then
|
||
echo "FAIL docker: no reachable Docker daemon; set PHASE34_ALLOW_NO_DOCKER=1 and run --docker on a Docker host"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# plugin_skip REPO prints the go test -skip pattern PHASE34_PLUGIN_SKIP
|
||
# names for REPO (empty when none).
|
||
plugin_skip() {
|
||
local repo="$1" entry names=()
|
||
for entry in $PHASE34_PLUGIN_SKIP; do
|
||
[[ "${entry%%:*}" == "$repo" ]] || continue
|
||
names+=("${entry#*:}")
|
||
done
|
||
if [[ "${#names[@]}" -gt 0 ]]; then
|
||
echo "^($(IFS='|'; echo "${names[*]}"))\$"
|
||
fi
|
||
}
|
||
|
||
run_plugins() {
|
||
local repo dir skip rel pkgs=() summer failed=0
|
||
for repo in "${PLUGIN_REPOS[@]}"; do
|
||
require_dir "$SIBLINGS/$repo" "plugin repository"
|
||
done
|
||
scratch_init
|
||
summer="$SCRATCH/summer"
|
||
(cd "$ROOT" && go build -o "$summer" ./cmd/summer)
|
||
for repo in "${PLUGIN_REPOS[@]}"; do
|
||
dir="$SIBLINGS/$repo"
|
||
workspace_check "$dir"
|
||
go_vet "$dir" ./...
|
||
skip="$(plugin_skip "$repo")"
|
||
if [[ -n "$skip" ]]; then
|
||
echo "plugins: note $repo: go test -short -skip '$skip' (PHASE34_PLUGIN_SKIP, pre-existing failure outside the phase)"
|
||
fi
|
||
go_short "$dir" "$skip"
|
||
mapfile -t pkgs < <(git -C "$dir" ls-files -- 'plugin.yaml' '*/plugin.yaml' | grep -v '/testdata/' | xargs -r -n1 dirname)
|
||
if [[ "${#pkgs[@]}" -eq 0 ]]; then
|
||
echo "refuse: missing $dir/plugin.yaml (no committed plugin.yaml)" >&2
|
||
return 1
|
||
fi
|
||
for rel in "${pkgs[@]}"; do
|
||
go_named "$dir" "./$rel" "-short" TestPluginManifest
|
||
if [[ " ${PHASE34_PENDING_MANIFESTS:-} " == *" $repo "* ]]; then
|
||
echo "PENDING manifest check $repo/$rel (PHASE34_PENDING_MANIFESTS)"
|
||
continue
|
||
fi
|
||
if ! (cd "$dir" && "$summer" plugin:manifest --check "$rel" >/dev/null); then
|
||
echo "refuse: FAIL summer plugin:manifest --check $repo/$rel" >&2
|
||
failed=1
|
||
continue
|
||
fi
|
||
echo "manifest: $repo/$rel fresh"
|
||
done
|
||
done
|
||
if [[ "$failed" -ne 0 ]]; then
|
||
return 1
|
||
fi
|
||
echo "phase34 plugins passed (${#PLUGIN_REPOS[@]} repositories)"
|
||
}
|
||
|
||
# run_logged NAME DETECTOR DIR CMD... runs CMD in DIR, keeps its output and
|
||
# checks it with DETECTOR (or only the exit status when DETECTOR is -).
|
||
run_logged() {
|
||
local name="$1" detector="$2" dir="$3" log rc=0 dc=0
|
||
shift 3
|
||
log="$(mktemp)"
|
||
(cd "$dir" && "$@") >"$log" 2>&1 || rc=$?
|
||
if [[ "$detector" != "-" ]]; then
|
||
"$detector" "$log" || dc=$?
|
||
fi
|
||
if [[ "$rc" -ne 0 || "$dc" -ne 0 ]]; then
|
||
grep -E '(not ok|FAIL|Error|error|expected|refuse)' "$log" | head -n 40 >&2 || true
|
||
tail -n 20 "$log" >&2
|
||
rm -f "$log"
|
||
echo "refuse: FAIL $name in $dir (exit=$rc detect=$dc)" >&2
|
||
return 1
|
||
fi
|
||
rm -f "$log"
|
||
echo "$name: ok"
|
||
}
|
||
|
||
run_site() {
|
||
require_dir "$SITE_APP" "summercms.io application"
|
||
require_dir "$SITE_PLUGIN" "summercms.io site plugin"
|
||
require_dir "$SITE_VUE" "vue-summercmsio-app"
|
||
workspace_check "$SITE_PLUGIN"
|
||
go_vet "$SITE_PLUGIN" ./...
|
||
go_short "$SITE_PLUGIN"
|
||
go_pkg_named "$SITE_PLUGIN" "-short" "${SITE_SHORT_TESTS[@]}"
|
||
workspace_check "$SITE_APP"
|
||
go_vet "$SITE_APP" ./...
|
||
run_logged "app go test ./..." - "$SITE_APP" go test -count=1 ./...
|
||
run_logged "changelog-post unittest" detect_unittest "$SITE_APP" python3 -B scripts/test_changelog_post.py
|
||
if command -v nginx >/dev/null 2>&1; then
|
||
run_logged "check-deploy" - "$SITE_APP" bash scripts/check-deploy.sh
|
||
else
|
||
echo "site: nginx not installed, check-deploy.sh not run"
|
||
fi
|
||
run_logged "pnpm test" detect_tap "$SITE_VUE" pnpm test
|
||
run_logged "pnpm build:changelog" - "$SITE_VUE" pnpm build:changelog
|
||
run_logged "pnpm test:changelog" - "$SITE_VUE" pnpm test:changelog
|
||
run_logged "pnpm test:marketplace" - "$SITE_VUE" pnpm test:marketplace
|
||
echo "phase34 site passed"
|
||
}
|
||
|
||
run_admin() {
|
||
require_dir "$ROOT/admin" "admin"
|
||
local log rc=0 dc=0
|
||
log="$(mktemp)"
|
||
npm --prefix "$ROOT/admin" run test >"$log" 2>&1 || rc=$?
|
||
REQUIRE_FILES="${ADMIN_FILES[*]}" detect_vitest "$log" || dc=$?
|
||
if [[ "$rc" -ne 0 || "$dc" -ne 0 ]]; then
|
||
grep -E '(FAIL|×|Error|expected)' "$log" | head -n 40 >&2 || true
|
||
tail -n 20 "$log" >&2
|
||
rm -f "$log"
|
||
echo "refuse: FAIL npm run test in admin (test=$rc detect=$dc)" >&2
|
||
return 1
|
||
fi
|
||
echo "vitest (admin): $(grep -E '^\s*Tests\s' "$log" | tr -s ' ')"
|
||
rm -f "$log"
|
||
npm --prefix "$ROOT/admin" run typecheck >/dev/null
|
||
echo "typecheck: admin"
|
||
(cd "$ROOT" && bash scripts/check-admin-openapi.sh --check)
|
||
(cd "$ROOT" && bash scripts/check-admin-dist.sh)
|
||
echo "phase34 admin passed"
|
||
}
|
||
|
||
run_docs() {
|
||
go_named "$ROOT" ./cmd/summer "" TestDocsTree TestCommandsDocumented
|
||
(cd "$ROOT" && go run ./cmd/summer docs:build --check)
|
||
echo "phase34 docs passed"
|
||
}
|
||
|
||
run_marker() {
|
||
if ! git -C "$ROOT" rev-parse --verify -q "$PHASE34_BASE_TAG^{commit}" >/dev/null; then
|
||
echo "refuse: missing tag $PHASE34_BASE_TAG in $ROOT" >&2
|
||
return 1
|
||
fi
|
||
local hits
|
||
hits="$(marker_hits "$ROOT" "$PHASE34_BASE_TAG")"
|
||
if [[ -n "$hits" ]]; then
|
||
printf '%s\n' "$hits" | head -n 40 >&2
|
||
echo "refuse: framework lines added since $PHASE34_BASE_TAG name a consuming application" >&2
|
||
return 1
|
||
fi
|
||
echo "marker: no line added since $PHASE34_BASE_TAG under modules/, cmd/, internal/ or docs/ names a consuming application"
|
||
echo "phase34 marker passed"
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# additive: the exported API of every package under modules/ changed since
|
||
# the base tag keeps every line of the tag.
|
||
|
||
SCRATCH=""
|
||
CLEANUP_WORKTREES=()
|
||
scratch_cleanup() {
|
||
local entry
|
||
for entry in "${CLEANUP_WORKTREES[@]}"; do
|
||
git -c safe.directory='*' -C "${entry%%|*}" worktree remove --force "${entry#*|}" >/dev/null 2>&1 || true
|
||
git -c safe.directory='*' -C "${entry%%|*}" worktree prune >/dev/null 2>&1 || true
|
||
done
|
||
if [[ -n "$SCRATCH" && -d "$SCRATCH" ]]; then
|
||
chmod -R u+w -- "${SCRATCH:?}" 2>/dev/null || true
|
||
rm -rf -- "${SCRATCH:?}"
|
||
fi
|
||
}
|
||
scratch_init() {
|
||
SCRATCH="$(mktemp -d)"
|
||
trap scratch_cleanup EXIT
|
||
}
|
||
|
||
add_worktree() {
|
||
git -c safe.directory='*' -C "$1" worktree add --detach -q "$2" "$3"
|
||
CLEANUP_WORKTREES+=("$1|$2")
|
||
}
|
||
|
||
# additive_compare PKG BASE_API HEAD_API refuses every API line of the base
|
||
# missing from HEAD (removed or changed) and every interface method or embed
|
||
# HEAD adds to an interface that exists in the base; it prints
|
||
# "additive: ok PKG (+N lines)" otherwise.
|
||
additive_compare() {
|
||
local pkg="$1" base="$2" head="$3" line name refused=0 added
|
||
while IFS= read -r line; do
|
||
[[ -n "$line" ]] || continue
|
||
echo "additive: removed or changed in $pkg: $line" >&2
|
||
refused=1
|
||
done < <(LC_ALL=C comm -23 "$base" "$head")
|
||
while IFS= read -r line; do
|
||
[[ "$line" =~ ^(imethod|iembed)\ ([A-Za-z0-9_]+)\. ]] || continue
|
||
name="${BASH_REMATCH[2]}"
|
||
if grep -qE "^type ${name}(\[.*\])? interface\$" "$base"; then
|
||
echo "additive: method added to existing interface in $pkg: $line" >&2
|
||
refused=1
|
||
fi
|
||
done < <(LC_ALL=C comm -13 "$base" "$head")
|
||
if [[ "$refused" -ne 0 ]]; then
|
||
return 1
|
||
fi
|
||
added="$(LC_ALL=C comm -13 "$base" "$head" | grep -c . || true)"
|
||
echo "additive: ok $pkg (+$added lines)"
|
||
}
|
||
|
||
# api_surface TOOL DIR OUT writes DIR's sorted API to OUT; a directory that
|
||
# does not exist or has no Go files has an empty API.
|
||
api_surface() {
|
||
local tool="$1" dir="$2" out="$3" rc=0
|
||
if [[ ! -d "$dir" ]]; then
|
||
: >"$out"
|
||
return 0
|
||
fi
|
||
"$tool" "$dir" >"$out.raw" 2>"$out.err" || rc=$?
|
||
if [[ "$rc" -eq 2 ]]; then
|
||
: >"$out.raw"
|
||
elif [[ "$rc" -ne 0 ]]; then
|
||
cat "$out.err" >&2
|
||
echo "refuse: apisurface $dir (exit $rc)" >&2
|
||
return 1
|
||
fi
|
||
LC_ALL=C sort "$out.raw" >"$out"
|
||
}
|
||
|
||
run_additive() {
|
||
local dir name failed=0 pkgs=() base new=()
|
||
if ! git -C "$ROOT" rev-parse --verify -q "$PHASE34_BASE_TAG^{commit}" >/dev/null; then
|
||
echo "refuse: missing tag $PHASE34_BASE_TAG in $ROOT" >&2
|
||
return 1
|
||
fi
|
||
scratch_init
|
||
base="$SCRATCH/base"
|
||
add_worktree "$ROOT" "$base" "$PHASE34_BASE_TAG"
|
||
(cd "$ROOT" && go build -o "$SCRATCH/apisurface" ./scripts/apisurface)
|
||
mapfile -t pkgs < <(git -C "$ROOT" diff --name-only "$PHASE34_BASE_TAG" -- modules |
|
||
grep -E '\.go$' | grep -vE '_test\.go$' | grep -v '/testdata/' | xargs -r -n1 dirname | LC_ALL=C sort -u)
|
||
mkdir -p "$SCRATCH/api"
|
||
for dir in "${pkgs[@]}"; do
|
||
if [[ ! -d "$base/$dir" ]]; then
|
||
new+=("$dir")
|
||
continue
|
||
fi
|
||
name="${dir//\//_}"
|
||
api_surface "$SCRATCH/apisurface" "$base/$dir" "$SCRATCH/api/$name.base"
|
||
api_surface "$SCRATCH/apisurface" "$ROOT/$dir" "$SCRATCH/api/$name.head"
|
||
additive_compare "$dir" "$SCRATCH/api/$name.base" "$SCRATCH/api/$name.head" || failed=1
|
||
done
|
||
if [[ "${#new[@]}" -gt 0 ]]; then
|
||
echo "additive: new since $PHASE34_BASE_TAG: ${new[*]}"
|
||
fi
|
||
if [[ "$failed" -ne 0 ]]; then
|
||
echo "refuse: additive: the framework API changed incompatibly since $PHASE34_BASE_TAG" >&2
|
||
return 1
|
||
fi
|
||
echo "phase34 additive passed ($((${#pkgs[@]} - ${#new[@]})) packages against $PHASE34_BASE_TAG)"
|
||
}
|
||
|
||
run_all() {
|
||
local stage
|
||
for stage in self-test go plugins site admin docs marker additive; do
|
||
if bash "${BASH_SOURCE[0]}" "--$stage"; then
|
||
echo "PASS $stage"
|
||
else
|
||
echo "FAIL $stage"
|
||
exit 1
|
||
fi
|
||
done
|
||
docker_gate || exit 1
|
||
echo "Phase 34 gate passed"
|
||
}
|
||
|
||
run_stage() {
|
||
case "$1" in
|
||
--self-test | self-test) run_self_test ;;
|
||
--go | go) run_go ;;
|
||
--docker | docker) run_docker ;;
|
||
--docker-gate) docker_gate ;;
|
||
--plugins | plugins) run_plugins ;;
|
||
--site | site) run_site ;;
|
||
--admin | admin) run_admin ;;
|
||
--docs | docs) run_docs ;;
|
||
--marker | marker) run_marker ;;
|
||
--additive | additive) run_additive ;;
|
||
--all | all) run_all ;;
|
||
*) usage ;;
|
||
esac
|
||
}
|
||
|
||
if [[ "$#" -eq 0 ]]; then
|
||
usage
|
||
fi
|
||
if [[ "$#" -eq 1 ]]; then
|
||
run_stage "$1"
|
||
exit 0
|
||
fi
|
||
for arg in "$@"; do
|
||
if bash "${BASH_SOURCE[0]}" "$arg"; then
|
||
echo "PASS ${arg#--}"
|
||
else
|
||
echo "FAIL ${arg#--}"
|
||
exit 1
|
||
fi
|
||
done
|