merge main to #666
This commit is contained in:
commit
ea92e0212b
160 files changed
+11719
-1469
No files matched your search
@@ -129,6 +129,245 @@ func Value() string {
|
||||
}
|
||||
}
|
||||
|
||||
// shardChangedPackagesShards mirrors the shard names scripts/ci/test-packages.sh
|
||||
// asserts complete coverage for in its verify mode. Keeping the list here in
|
||||
// full is deliberate: the union assertion below fails if a shard is added
|
||||
// upstream without being selected, and an unknown-shard error fails if one is
|
||||
// removed, so shard-plan drift cannot silently shrink focused-test coverage.
|
||||
var shardChangedPackagesShards = []string{
|
||||
"app",
|
||||
"generators",
|
||||
"helpers",
|
||||
"cli",
|
||||
"smoke",
|
||||
"remaining",
|
||||
"release-scripts",
|
||||
}
|
||||
|
||||
func TestChangedTestPackagesShardSelectionCoversEveryImpactedPackageExactlyOnce(t *testing.T) {
|
||||
repository := newShardChangedPackagesRepository(t)
|
||||
baseRef := commitChangedTestPackagesFixture(t, repository, "initial shard fixture")
|
||||
|
||||
writeChangedTestPackagesFixture(t, repository, "core/core.go", `package core
|
||||
|
||||
func Value() string {
|
||||
return "changed"
|
||||
}
|
||||
`)
|
||||
headRef := commitChangedTestPackagesFixture(t, repository, "change core")
|
||||
|
||||
impacted := runChangedTestPackages(t, repository, "list", baseRef, headRef)
|
||||
if len(impacted) == 0 {
|
||||
t.Fatal("shard fixture produced no impacted packages")
|
||||
}
|
||||
|
||||
owner := map[string]string{}
|
||||
union := make([]string, 0, len(impacted))
|
||||
for _, shard := range shardChangedPackagesShards {
|
||||
for _, pkg := range runChangedTestPackagesShard(t, repository, shard, baseRef, headRef) {
|
||||
if previous, ok := owner[pkg]; ok {
|
||||
t.Fatalf("package %q selected by both shard %q and %q", pkg, previous, shard)
|
||||
}
|
||||
owner[pkg] = shard
|
||||
union = append(union, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(union)
|
||||
if !slices.Equal(union, impacted) {
|
||||
t.Fatalf("shard selection union = %q, want every impacted package %q", union, impacted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangedTestPackagesShardSelectionFailsClosedOnUnknownShard(t *testing.T) {
|
||||
repository := newShardChangedPackagesRepository(t)
|
||||
baseRef := commitChangedTestPackagesFixture(t, repository, "initial shard fixture")
|
||||
|
||||
writeChangedTestPackagesFixture(t, repository, "core/core.go", `package core
|
||||
|
||||
func Value() string {
|
||||
return "changed"
|
||||
}
|
||||
`)
|
||||
headRef := commitChangedTestPackagesFixture(t, repository, "change core")
|
||||
|
||||
script := filepath.Join(repository, "scripts", "ci", "changed-test-packages.sh")
|
||||
command := exec.Command(script, "list-shard", "nonexistent", baseRef, headRef)
|
||||
command.Dir = repository
|
||||
output, err := command.CombinedOutput()
|
||||
if err == nil {
|
||||
t.Fatalf("unknown shard unexpectedly succeeded:\n%s", output)
|
||||
}
|
||||
// An unknown shard must abort rather than report an empty selection: a
|
||||
// silently empty shard would let a mistyped CI shard name skip every test
|
||||
// while still reporting success.
|
||||
if !strings.Contains(string(output), "unknown test package shard") {
|
||||
t.Fatalf("unknown shard failure = %q, want fail-closed diagnostic", output)
|
||||
}
|
||||
if strings.TrimSpace(string(output)) == "" {
|
||||
t.Fatal("unknown shard produced empty output instead of a diagnostic")
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangedTestPackagesShardSelectionIsEmptyForUnaffectedShard(t *testing.T) {
|
||||
repository := newShardChangedPackagesRepository(t)
|
||||
baseRef := commitChangedTestPackagesFixture(t, repository, "initial shard fixture")
|
||||
|
||||
writeChangedTestPackagesFixture(t, repository, "internal/helpers/helpers.go", `package helpers
|
||||
|
||||
import "example.com/shardfixture/core"
|
||||
|
||||
func Value() string {
|
||||
return core.Value() + " helpers changed"
|
||||
}
|
||||
`)
|
||||
headRef := commitChangedTestPackagesFixture(t, repository, "change helpers only")
|
||||
|
||||
if selected := runChangedTestPackagesShard(t, repository, "helpers", baseRef, headRef); len(selected) == 0 {
|
||||
t.Fatal("helpers shard selected nothing for a helpers-only change")
|
||||
}
|
||||
// An unaffected shard is a normal outcome and must exit zero with no
|
||||
// output so the CI step can no-op instead of failing the job.
|
||||
if selected := runChangedTestPackagesShard(t, repository, "app", baseRef, headRef); len(selected) != 0 {
|
||||
t.Fatalf("app shard = %q, want empty for a helpers-only change", selected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangedTestPackagesShardSelectionRejectsMissingShardArgument(t *testing.T) {
|
||||
repository := newShardChangedPackagesRepository(t)
|
||||
baseRef := commitChangedTestPackagesFixture(t, repository, "initial shard fixture")
|
||||
|
||||
script := filepath.Join(repository, "scripts", "ci", "changed-test-packages.sh")
|
||||
command := exec.Command(script, "list-shard", baseRef, baseRef)
|
||||
command.Dir = repository
|
||||
output, err := command.CombinedOutput()
|
||||
if err == nil {
|
||||
t.Fatalf("list-shard without a shard argument unexpectedly succeeded:\n%s", output)
|
||||
}
|
||||
if !strings.Contains(string(output), "usage:") {
|
||||
t.Fatalf("missing shard argument failure = %q, want usage diagnostic", output)
|
||||
}
|
||||
}
|
||||
|
||||
func runChangedTestPackagesShard(t *testing.T, repository, shard, baseRef, headRef string) []string {
|
||||
t.Helper()
|
||||
|
||||
output := runChangedTestPackagesCommand(
|
||||
t,
|
||||
repository,
|
||||
filepath.Join(repository, "scripts", "ci", "changed-test-packages.sh"),
|
||||
"list-shard",
|
||||
shard,
|
||||
baseRef,
|
||||
headRef,
|
||||
)
|
||||
return strings.Fields(output)
|
||||
}
|
||||
|
||||
// newShardChangedPackagesRepository builds a fixture whose directory layout
|
||||
// matches every shard scripts/ci/test-packages.sh knows about, so shard
|
||||
// selection can be exercised without depending on the real repository's working
|
||||
// tree state. Every package imports core, letting a single core edit mark the
|
||||
// whole module impacted.
|
||||
func newShardChangedPackagesRepository(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
repository := t.TempDir()
|
||||
for path, contents := range map[string]string{
|
||||
"go.mod": `module example.com/shardfixture
|
||||
|
||||
go 1.22
|
||||
`,
|
||||
"core/core.go": `package core
|
||||
|
||||
func Value() string {
|
||||
return "core"
|
||||
}
|
||||
`,
|
||||
"internal/app/app.go": `package app
|
||||
|
||||
import "example.com/shardfixture/core"
|
||||
|
||||
func Value() string {
|
||||
return core.Value() + " app"
|
||||
}
|
||||
`,
|
||||
"internal/generator/gen/gen.go": `package gen
|
||||
|
||||
import "example.com/shardfixture/core"
|
||||
|
||||
func Value() string {
|
||||
return core.Value() + " gen"
|
||||
}
|
||||
`,
|
||||
"internal/helpers/helpers.go": `package helpers
|
||||
|
||||
import "example.com/shardfixture/core"
|
||||
|
||||
func Value() string {
|
||||
return core.Value() + " helpers"
|
||||
}
|
||||
`,
|
||||
"internal/cli/cli.go": `package cli
|
||||
|
||||
import "example.com/shardfixture/core"
|
||||
|
||||
func Value() string {
|
||||
return core.Value() + " cli"
|
||||
}
|
||||
`,
|
||||
"test/smoke/smoke.go": `package smoke
|
||||
|
||||
import "example.com/shardfixture/core"
|
||||
|
||||
func Value() string {
|
||||
return core.Value() + " smoke"
|
||||
}
|
||||
`,
|
||||
"test/scripts/scripts.go": `package scripts
|
||||
|
||||
import "example.com/shardfixture/core"
|
||||
|
||||
func Value() string {
|
||||
return core.Value() + " scripts"
|
||||
}
|
||||
`,
|
||||
"pkg/extra/extra.go": `package extra
|
||||
|
||||
import "example.com/shardfixture/core"
|
||||
|
||||
func Value() string {
|
||||
return core.Value() + " extra"
|
||||
}
|
||||
`,
|
||||
} {
|
||||
writeChangedTestPackagesFixture(t, repository, path, contents)
|
||||
}
|
||||
|
||||
projectRoot, err := filepath.Abs(filepath.Join("..", ".."))
|
||||
if err != nil {
|
||||
t.Fatalf("resolve project root: %v", err)
|
||||
}
|
||||
for _, name := range []string{"changed-test-packages.sh", "test-packages.sh"} {
|
||||
script, err := os.ReadFile(filepath.Join(projectRoot, "scripts", "ci", name))
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", name, err)
|
||||
}
|
||||
scriptPath := filepath.Join(repository, "scripts", "ci", name)
|
||||
if err := os.MkdirAll(filepath.Dir(scriptPath), 0o755); err != nil {
|
||||
t.Fatalf("create script directory: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(scriptPath, script, 0o755); err != nil {
|
||||
t.Fatalf("copy %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
runChangedTestPackagesCommand(t, repository, "git", "init", "-q", "-b", "main")
|
||||
runChangedTestPackagesCommand(t, repository, "git", "config", "user.name", "DWS CI")
|
||||
runChangedTestPackagesCommand(t, repository, "git", "config", "user.email", "dws-ci@example.invalid")
|
||||
return repository
|
||||
}
|
||||
|
||||
func newChangedTestPackagesRepository(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -1013,6 +1013,12 @@ if (!isHighRisk("internal/helpers/minutes.go")) {
|
||||
if (isHighRisk("internal/helpersx/minutes.go")) {
|
||||
throw new Error("helper high-risk classification must respect the path boundary");
|
||||
}
|
||||
if (!isHighRisk("internal/shortcut/wiki/wiki.go")) {
|
||||
throw new Error("shortcut changes must use the sharded full suite");
|
||||
}
|
||||
if (isHighRisk("internal/shortcuts/wiki/wiki.go")) {
|
||||
throw new Error("shortcut high-risk classification must respect the path boundary");
|
||||
}
|
||||
for (const filename of [
|
||||
"internal/cli/param_concepts.json",
|
||||
"internal/cli/param_concepts.schema.json",
|
||||
@@ -1137,6 +1143,7 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
|
||||
"filename.startsWith('scripts/')",
|
||||
"filename.startsWith('verify/')",
|
||||
"filename.startsWith('internal/helpers/')",
|
||||
"filename.startsWith('internal/shortcut/')",
|
||||
"filename === 'test/fixtures/cli-interface-baseline.txt'",
|
||||
"filename.startsWith('internal/interfacesnapshot/')",
|
||||
"filename.startsWith('internal/cobracmd/')",
|
||||
@@ -1144,13 +1151,13 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
|
||||
"filename.startsWith('pkg/cmdutil/')",
|
||||
"filename === 'skills_embed.go'",
|
||||
"filename.startsWith('test/mock_mcp/')",
|
||||
"name: Test (changed packages)",
|
||||
`name: "Test (focused: ${{ matrix.shard }})"`,
|
||||
"changed-test-packages.sh",
|
||||
"Verify authoritative synthetic merge",
|
||||
`test "$(git rev-parse HEAD^1)" = "$PR_BASE_SHA"`,
|
||||
`test "$(git rev-parse HEAD^2)" = "$PR_HEAD_SHA"`,
|
||||
`echo "TEST_HEAD_REF=$(git rev-parse HEAD)"`,
|
||||
"list \"$TEST_BASE_REF\" \"$TEST_HEAD_REF\"",
|
||||
`list-shard "$package_shard" "$TEST_BASE_REF" "$TEST_HEAD_REF"`,
|
||||
"needs.lint.outputs.full_suite != 'true'",
|
||||
`name: "Test (race: ${{ matrix.shard }})"`,
|
||||
"name: Test (workflow and release contracts)",
|
||||
@@ -1210,8 +1217,34 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
|
||||
if !strings.Contains(focusedJob, "timeout-minutes: 20") {
|
||||
t.Error("focused test job must allow the scoped race suite up to 20 minutes")
|
||||
}
|
||||
if !strings.Contains(focusedJob, `go test -v -race -count=1 -timeout=15m "${packages[@]}"`) {
|
||||
t.Error("focused race tests must retain enough package-level time for internal/app")
|
||||
// The focused path fans the impacted set across the same shards as test-race
|
||||
// and runs each shard the way test-race runs it, so no single job carries
|
||||
// internal/app together with its reverse dependencies. internal/app is split
|
||||
// further into one shard per bounded partition, which keeps its package-level
|
||||
// headroom through the process-isolating helper instead of one long -timeout
|
||||
// and is strictly stronger than a single app job: every partition process
|
||||
// releases the framework registries it populated, and the partitions run
|
||||
// concurrently rather than end to end. Each partition shard still selects the
|
||||
// same single internal/app package, so the impacted-package query maps the
|
||||
// shard name back to app. release-scripts is asserted because its dedicated
|
||||
// job only runs at full-suite or release-sensitive scope, so losing it here
|
||||
// would silently stop testing test/scripts changes.
|
||||
for _, want := range []string{
|
||||
`app-*) package_shard=app ;;`,
|
||||
`test "${#packages[@]}" -eq 1`,
|
||||
`./scripts/ci/run-app-race-tests.sh run "${packages[0]}" "${TEST_SHARD#app-}"`,
|
||||
`if [ "$TEST_SHARD" = "release-scripts" ]; then`,
|
||||
`go test -v -count=1 -timeout=10m "${packages[@]}"`,
|
||||
"timeout_budget=12m",
|
||||
`if [ "$TEST_SHARD" = "cli" ] ||`,
|
||||
`[ "$TEST_SHARD" = "smoke" ]; then`,
|
||||
"timeout_budget=15m",
|
||||
`go test -v -race -count=1 -timeout="$timeout_budget" "${packages[@]}"`,
|
||||
"- release-scripts",
|
||||
} {
|
||||
if !strings.Contains(focusedJob, want) {
|
||||
t.Errorf("focused test job missing shard contract %q", want)
|
||||
}
|
||||
}
|
||||
|
||||
raceStart := focusedEnd
|
||||
@@ -1220,14 +1253,16 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
|
||||
t.Fatal("Code Admission workflow missing race test job boundaries")
|
||||
}
|
||||
raceJob := admission[raceStart:raceEnd]
|
||||
// The app shard uses independently bounded test processes so process-global
|
||||
// command registries are released before the Schema assembly peak. Other full
|
||||
// race shards retain the dynamic package timeout: default/floor 12m, with
|
||||
// cli/smoke raised to 15m on slower hosted runners.
|
||||
// internal/app is carried by one shard per bounded partition, so process-global
|
||||
// command registries are released with each partition process and the Schema
|
||||
// assembly peak no longer sits in front of the other partitions. Each
|
||||
// partition shard resolves back to the same single internal/app package.
|
||||
// Other full race shards retain the dynamic package timeout: default/floor
|
||||
// 12m, with cli/smoke raised to 15m on slower hosted runners.
|
||||
for _, want := range []string{
|
||||
`if [ "$TEST_SHARD" = "app" ]; then`,
|
||||
`app-*) package_shard=app ;;`,
|
||||
`test "${#packages[@]}" -eq 1`,
|
||||
`./scripts/ci/run-app-race-tests.sh run "${packages[0]}"`,
|
||||
`./scripts/ci/run-app-race-tests.sh run "${packages[0]}" "${TEST_SHARD#app-}"`,
|
||||
"timeout_budget=12m",
|
||||
`if [ "$TEST_SHARD" = "cli" ] ||`,
|
||||
`[ "$TEST_SHARD" = "smoke" ]; then`,
|
||||
|
||||
@@ -14,6 +14,39 @@ func TestCITestPackagePlanCoversDefaultPackagesExactlyOnce(t *testing.T) {
|
||||
if !strings.Contains(output, "default packages exactly once") {
|
||||
t.Fatalf("verify output = %q, want coverage summary", output)
|
||||
}
|
||||
if !strings.Contains(output, "full-suite packages exactly once") {
|
||||
t.Fatalf("verify output = %q, want coverage shard plan summary", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCICoveragePackagePlanRoutesFullSuiteScope(t *testing.T) {
|
||||
root := testPackagePlanRoot(t)
|
||||
remaining := strings.Fields(runTestPackagePlan(t, root, "list-coverage", "remaining"))
|
||||
|
||||
for _, suffix := range []string{"/cmd", "/internal/output", "/skills"} {
|
||||
if !containsPackageSuffix(remaining, suffix) {
|
||||
t.Errorf("coverage remaining shard does not contain package ending in %q", suffix)
|
||||
}
|
||||
}
|
||||
for _, suffix := range []string{
|
||||
"/internal/app",
|
||||
"/internal/cli",
|
||||
"/internal/generator",
|
||||
"/internal/helpers",
|
||||
"/test/smoke",
|
||||
"/test/scripts",
|
||||
"/pkg/cmdutil",
|
||||
"/scripts/policy/coverage-gate",
|
||||
} {
|
||||
if containsPackageSuffix(remaining, suffix) {
|
||||
t.Errorf("coverage remaining shard unexpectedly contains package ending in %q", suffix)
|
||||
}
|
||||
}
|
||||
|
||||
app := strings.Fields(runTestPackagePlan(t, root, "list-coverage", "app"))
|
||||
if !containsPackageSuffix(app, "/internal/app") {
|
||||
t.Error("coverage app shard does not contain /internal/app")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCITestPackagePlanRoutesPublicTestSuites(t *testing.T) {
|
||||
@@ -66,6 +99,74 @@ func TestCIAppRacePartitionsCoverTopLevelTestsExactlyOnce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCIAppRacePartitionMatrixMatchesHelper pins the workflow's app partition
|
||||
// shards to the partition set the helper actually runs. The partitions are
|
||||
// separate CI jobs now, so the helper's own "covered exactly once" check can no
|
||||
// longer prove the whole package ran: a partition the helper knows about but no
|
||||
// matrix shard dispatches would silently stop running while every job stays
|
||||
// green. Both directions are asserted so a stale matrix shard fails too.
|
||||
func TestCIAppRacePartitionMatrixMatchesHelper(t *testing.T) {
|
||||
root := testPackagePlanRoot(t)
|
||||
script := filepath.Join(root, "scripts", "ci", "run-app-race-tests.sh")
|
||||
cmd := exec.Command("sh", script, "list-partitions")
|
||||
cmd.Dir = root
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("%s list-partitions failed: %v\n%s", script, err, output)
|
||||
}
|
||||
partitions := strings.Fields(string(output))
|
||||
if len(partitions) == 0 {
|
||||
t.Fatalf("list-partitions returned no partitions: %q", output)
|
||||
}
|
||||
|
||||
workflow, err := os.ReadFile(filepath.Join(root, ".github", "workflows", "ci.yml"))
|
||||
if err != nil {
|
||||
t.Fatalf("read ci.yml: %v", err)
|
||||
}
|
||||
admission := string(workflow)
|
||||
|
||||
for _, job := range []struct {
|
||||
name string
|
||||
startMark string
|
||||
endMark string
|
||||
}{
|
||||
{"test-focused", "\n test-focused:\n", "\n test-race:\n"},
|
||||
{"test-race", "\n test-race:\n", "\n test-release-scripts:\n"},
|
||||
} {
|
||||
start := strings.Index(admission, job.startMark)
|
||||
end := strings.Index(admission, job.endMark)
|
||||
if start < 0 || end <= start {
|
||||
t.Fatalf("ci.yml is missing %s job boundaries", job.name)
|
||||
}
|
||||
body := admission[start:end]
|
||||
|
||||
for _, partition := range partitions {
|
||||
want := "- app-" + partition
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("%s matrix is missing shard %q for a partition the helper runs", job.name, want)
|
||||
}
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(body, "\n") {
|
||||
shard := strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(shard, "- app-") {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimPrefix(shard, "- app-")
|
||||
matched := false
|
||||
for _, partition := range partitions {
|
||||
if partition == name {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
t.Errorf("%s matrix shard %q has no matching helper partition", job.name, shard)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCITestPackagePlanFailsClosedWhenGoListFails(t *testing.T) {
|
||||
root := testPackagePlanRoot(t)
|
||||
fakeBin := t.TempDir()
|
||||
|
||||
@@ -118,3 +118,108 @@ esac
|
||||
assertDiffProfiles(t, runGate(t, &empty), "coverage.txt")
|
||||
})
|
||||
}
|
||||
|
||||
// TestCoverageWorkflowShardsAndBaselineCache pins the full-suite coverage
|
||||
// architecture: the candidate profile is produced by disjoint per-shard
|
||||
// helper jobs and reassembled before enforcement, and the merge-base profile
|
||||
// is reused only through an exact-key cache written by a green main push of
|
||||
// that same commit. Near-miss reuse (restore-keys) would compare the
|
||||
// candidate against the wrong commit and must never appear.
|
||||
func TestCoverageWorkflowShardsAndBaselineCache(t *testing.T) {
|
||||
root, err := filepath.Abs(filepath.Join("..", ".."))
|
||||
if err != nil {
|
||||
t.Fatalf("Abs(repo root) error = %v", err)
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(root, ".github", "workflows", "ci.yml"))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile(ci.yml) error = %v", err)
|
||||
}
|
||||
admission := string(data)
|
||||
|
||||
currentStart := strings.Index(admission, "\n coverage-current:\n")
|
||||
fullStart := strings.Index(admission, "\n coverage-current-full:\n")
|
||||
supportingStart := strings.Index(admission, "\n coverage-supporting:\n")
|
||||
baselineStart := strings.Index(admission, "\n coverage-baseline:\n")
|
||||
gateStart := strings.Index(admission, "\n coverage:\n")
|
||||
policyStart := strings.Index(admission, "\n policy:\n")
|
||||
if currentStart < 0 || fullStart <= currentStart || supportingStart <= fullStart ||
|
||||
baselineStart <= supportingStart || gateStart <= baselineStart || policyStart <= gateStart {
|
||||
t.Fatal("CI workflow missing ordered coverage job boundaries")
|
||||
}
|
||||
|
||||
currentJob := admission[currentStart:fullStart]
|
||||
if !strings.Contains(currentJob, "needs.lint.outputs.full_suite != 'true'") {
|
||||
t.Error("coverage-current must be scoped-tier only; the full suite belongs to the shard matrix")
|
||||
}
|
||||
if strings.Contains(currentJob, "./ ./cmd/... ./internal/... ./skills/...") {
|
||||
t.Error("coverage-current must not retain the retired single serial full-suite run")
|
||||
}
|
||||
|
||||
fullJob := admission[fullStart:supportingStart]
|
||||
for _, want := range []string{
|
||||
"needs.lint.outputs.full_suite == 'true'",
|
||||
"fail-fast: false",
|
||||
" - app",
|
||||
" - cli",
|
||||
" - generators",
|
||||
" - helpers",
|
||||
" - remaining",
|
||||
`./scripts/ci/test-packages.sh list-coverage "$COVERAGE_SHARD"`,
|
||||
"go test -count=1 -p 1",
|
||||
`-coverprofile="coverage-shard-$COVERAGE_SHARD.txt"`,
|
||||
"-covermode=atomic",
|
||||
"name: coverage-current-shard-${{ matrix.shard }}",
|
||||
} {
|
||||
if !strings.Contains(fullJob, want) {
|
||||
t.Errorf("coverage-current-full missing shard contract %q", want)
|
||||
}
|
||||
}
|
||||
|
||||
baselineJob := admission[baselineStart:gateStart]
|
||||
cachePath := "coverage-cache.txt"
|
||||
baselineKey := "dws-coverage-full-v2-${{ env.COVERAGE_BASE_REF }}-go${{ steps.setup-go.outputs.go-version }}"
|
||||
for _, want := range []string{
|
||||
"uses: actions/cache/restore@v4",
|
||||
"uses: actions/cache/save@v4",
|
||||
"path: " + cachePath,
|
||||
"key: " + baselineKey,
|
||||
"if: steps.baseline-cache.outputs.cache-hit != 'true'",
|
||||
"cp coverage-cache.txt coverage-base.txt",
|
||||
"cp coverage-base.txt coverage-cache.txt",
|
||||
} {
|
||||
if !strings.Contains(baselineJob, want) {
|
||||
t.Errorf("coverage-baseline missing cache contract %q", want)
|
||||
}
|
||||
}
|
||||
if strings.Count(baselineJob, "key: "+baselineKey) != 2 {
|
||||
t.Error("coverage-baseline restore and save must use the identical exact cache key")
|
||||
}
|
||||
if strings.Count(baselineJob, "path: "+cachePath) != 2 {
|
||||
t.Error("coverage-baseline restore and save must use the identical cache path/version")
|
||||
}
|
||||
if strings.Contains(baselineJob, "restore-keys") {
|
||||
t.Error("coverage baseline cache must stay exact-key; prefix restore-keys can resurrect a wrong-commit baseline")
|
||||
}
|
||||
|
||||
gateJob := admission[gateStart:policyStart]
|
||||
for _, want := range []string{
|
||||
"pattern: coverage-current-*",
|
||||
"merge-multiple: true",
|
||||
"for shard in app cli generators helpers remaining; do",
|
||||
"test ! -f coverage.txt",
|
||||
`test "$(head -n 1 "$profile")" = "mode: atomic"`,
|
||||
"github.event_name == 'push'",
|
||||
"cp coverage.txt coverage-cache.txt",
|
||||
"path: " + cachePath,
|
||||
"key: dws-coverage-full-v2-${{ github.sha }}-go${{ steps.setup-go.outputs.go-version }}",
|
||||
`"current shards:$CURRENT_FULL_RESULT:$current_full_expected"`,
|
||||
} {
|
||||
if !strings.Contains(gateJob, want) {
|
||||
t.Errorf("coverage gate missing shard assembly contract %q", want)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Count(gateJob, "path: "+cachePath) != 1 {
|
||||
t.Error("green main push must save the candidate profile through the same cache path/version as baseline restore")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2026 Alibaba Group
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
package scripts_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWikiShortcutE2ERequiresInteractiveConfirmation(t *testing.T) {
|
||||
script := filepath.Join("..", "..", "scripts", "dev", "wiki-shortcut-e2e.py")
|
||||
source, err := os.ReadFile(script)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bytes.Contains(source, []byte("--yes")) {
|
||||
t.Fatal("real-data Wiki E2E script must not embed confirmation bypass flags")
|
||||
}
|
||||
|
||||
python, err := exec.LookPath("python3")
|
||||
if err != nil {
|
||||
t.Skip("python3 is unavailable")
|
||||
}
|
||||
cmd := exec.Command(python, script)
|
||||
cmd.Stdin = strings.NewReader("")
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err == nil {
|
||||
t.Fatal("non-interactive real-data E2E unexpectedly succeeded")
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "run in an interactive terminal") {
|
||||
t.Fatalf("non-interactive failure = %q, want explicit terminal requirement", stderr.String())
|
||||
}
|
||||
if strings.Contains(stdout.String(), "PASS ") {
|
||||
t.Fatalf("non-interactive E2E performed work before refusing: %q", stdout.String())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user