Compare commits

...
Author SHA1 Message Date
chichuan e6b5938bd8 Merge pull request #1025 from DingTalk-Real-AI/codex/changelog-v1.0.59-beta.2
docs: seal changelog for v1.0.59-beta.2
2026-08-17 11:03:27 +08:00
chichuan 4f95373420 docs: seal changelog for v1.0.59-beta.2 2026-08-17 10:59:11 +08:00
github-actions[bot] 6411d26a95 Merge pull request #1023 from DingTalk-Real-AI/fix/app-partition-parallel-jobs
fix(ci): parallelize app test partitions and drop race from the schema partition
2026-08-17 02:45:57 +00:00
chichuan 96b9cbce02 Merge branch 'main' into fix/app-partition-parallel-jobs 2026-08-17 10:22:20 +08:00
chichuan 36877d00dc Merge pull request #1024 from DingTalk-Real-AI/perf/schema-json-projection
perf: skip redundant JSON validation when projecting typed Schema values
2026-08-17 10:21:48 +08:00
chichuan 55d94d3b58 perf: skip redundant JSON validation when projecting typed Schema values
typedJSONValue marshaled a typed value and then routed the result through
rawJSONValue, which runs json.Valid before decoding. On that path the input is
whatever json.Marshal has just produced, so the validation scan can only ever
succeed: it re-read every marshaled document for nothing.

The decode step is now shared by both entry points. rawJSONValue keeps its
json.Valid check, because it still accepts untrusted input, while typedJSONValue
decodes what it marshaled directly. Across the 1121-tool set this removes about a
third of the Schema Catalog projection work: the internal/app schema suite goes
from 26.0s to 17.2s uninstrumented, and from 291.1s to 241.0s under -race.

The delivered Catalog is byte-for-byte unchanged. check-generated-drift,
check-schema-catalog and check-schema-binary each regenerate the same
source_hash sha256:93b8d44eb163bd2898c78397d22af92d378e3dc4e20f56b33277b51e4342e2e6,
and the two error contracts are preserved: typedJSONValue still rejects a value
json.Marshal cannot encode, and rawJSONValue still rejects invalid JSON.
2026-08-16 22:21:16 +08:00
chichuan bfd0976b31 fix(ci): run the app test partitions as parallel shards
The five internal/app partitions ran end to end inside one job, so the app
shard's wall clock was the sum of all five: 780s in CI, of which the schema
partition owned 357s. Each partition is now its own matrix shard, so they run
concurrently and the shard's wall clock is set by its slowest partition rather
than by their total. Every partition shard still selects the same single
internal/app package, so the impacted-package query maps the shard name back to
app and the partition only chooses which tests run.

The helper gains a partition argument and a list-partitions mode. APP_PARTITIONS
is the single source of truth for the set, and the discovery pass still runs in
every job, so each one independently verifies that the partition patterns cover
every top-level test exactly once before running the one it was asked for.

Two fail-closed checks guard the split, because the helper's own coverage check
can no longer prove the whole package ran once the partitions are separate jobs:

- The helper cross-checks APP_PARTITIONS against the coverage counters in both
  directions, so a counted partition that nothing dispatches and a dispatchable
  partition with no counter both fail instead of silently skipping tests.
- TestCIAppRacePartitionMatrixMatchesHelper pins the workflow's app-<partition>
  shards to list-partitions output in both directions, so a partition cannot
  lose its job while every job stays green.

The discovery loop variable is renamed from partition to spec: it would
otherwise shadow the partition requested on the command line, which run mode
reads after the discovery pass completes.
2026-08-16 22:18:04 +08:00
chichuan 4a33e7e893 fix(ci): drop race instrumentation from the app schema partition
The schema partition's 52 tests assert structural Schema-to-Cobra contracts over
a single goroutine: none of them call t.Parallel or start a goroutine, so the
race detector has no concurrent access to observe there. The process-global lazy
metadata that does need race coverage (schema_source_root's atomic.Value, the
parameter-binding lazy loaders) is exercised by internal/cli's concurrent tests,
which stay instrumented.

The instrumentation was not free here. The partition shares a single sync.Once
Catalog build whose work is allocation-heavy, and -race made it roughly 11x
slower: 26s -> 291s locally, and 357s of the app shard's 780s in CI. Within that
partition TestFinalSchemaToolsHaveExecutableBaseCommands alone accounted for
262s, not because the test is expensive but because it is the first caller to pay
for the shared snapshot; its 1121 subtests together measure 0.00s.

run_partition now takes the instrumentation mode explicitly and fails closed on
an unrecognized value, so a typo cannot silently drop -race from a partition that
is supposed to carry it.
2026-08-16 22:17:14 +08:00
github-actions[bot] ee74765383 Merge pull request #1019 from DingTalk-Real-AI/feat/help-feedback-entry
feat: add feedback survey entry to root help
2026-08-16 08:09:01 +08:00
12 changed files with 324 additions and 60 deletions
@@ -0,0 +1,8 @@
---
category: Changed
---
- **Faster Schema Catalog assembly** — projects typed values into payload JSON
without re-running a validation scan over documents `json.Marshal` has just
produced, cutting roughly a third of the projection work across the full tool
set. Untrusted JSON input keeps its existing validation.
+63 -22
View File
@@ -523,12 +523,22 @@ jobs:
# shards at full-suite scope; release-scripts is included because its
# dedicated job only runs at full-suite or release-sensitive scope, and
# dropping it here would stop testing test/scripts changes entirely.
# internal/app is carried by one shard per bounded partition rather than a
# single app shard: the partitions used to run end to end inside one job,
# where the Schema partition alone owned most of the wall clock. The
# app-<partition> names are pinned to the helper's partition set by
# TestCIAppRacePartitionMatrixMatchesHelper, so a partition can never lose
# its job silently.
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
shard:
- app
- app-schema
- app-a-b
- app-c
- app-d-r
- app-s-z-example-fuzz
- generators
- helpers
- cli
@@ -570,9 +580,16 @@ jobs:
TEST_SHARD: ${{ matrix.shard }}
run: |
set -euo pipefail
# Every app partition shard tests the same single internal/app
# package, so the impacted-package query uses the base shard name and
# the partition only selects which tests run.
package_shard="$TEST_SHARD"
case "$TEST_SHARD" in
app-*) package_shard=app ;;
esac
package_output="$(
./scripts/ci/changed-test-packages.sh \
list-shard "$TEST_SHARD" "$TEST_BASE_REF" "$TEST_HEAD_REF"
list-shard "$package_shard" "$TEST_BASE_REF" "$TEST_HEAD_REF"
)"
if [ -z "$package_output" ]; then
echo "No buildable Go package in shard $TEST_SHARD is affected by this revision." \
@@ -612,14 +629,19 @@ jobs:
exit 1
}
done
if [ "$TEST_SHARD" = "app" ]; then
# A single long-lived app test process retains every constructed
# command tree in framework registries. Isolate Schema assembly and
# bounded name ranges so each process releases that state on exit.
test "${#packages[@]}" -eq 1
./scripts/ci/run-app-race-tests.sh run "${packages[0]}"
exit 0
fi
case "$TEST_SHARD" in
app-*)
# A single long-lived app test process retains every constructed
# command tree in framework registries. Each partition is its own
# job, so that state is released when the process exits and the
# partitions run concurrently instead of end to end. The helper
# still verifies that the partition patterns cover every top-level
# test exactly once before running the one it was asked for.
test "${#packages[@]}" -eq 1
./scripts/ci/run-app-race-tests.sh run "${packages[0]}" "${TEST_SHARD#app-}"
exit 0
;;
esac
if [ "$TEST_SHARD" = "release-scripts" ]; then
# Mirror the dedicated release-contract job: these suites shell out
# to archive tooling and are not race-instrumented there.
@@ -640,14 +662,21 @@ jobs:
needs: lint
if: ${{ needs.lint.outputs.changelog_only != 'true' && needs.lint.outputs.docs_only != 'true' && needs.lint.outputs.full_suite == 'true' }}
runs-on: ubuntu-latest
# app runs several independently bounded processes; cli/smoke need headroom
# beyond go test -timeout for setup + assembly.
# internal/app is split across one shard per bounded partition so the
# partitions run concurrently and each releases its framework registries
# when the process exits; cli/smoke need headroom beyond go test -timeout for
# setup + assembly. The app-<partition> names are pinned to the helper's
# partition set by TestCIAppRacePartitionMatrixMatchesHelper.
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
shard:
- app
- app-schema
- app-a-b
- app-c
- app-d-r
- app-s-z-example-fuzz
- generators
- helpers
- cli
@@ -673,18 +702,30 @@ jobs:
TEST_SHARD: ${{ matrix.shard }}
run: |
set -euo pipefail
package_output="$(./scripts/ci/test-packages.sh list "$TEST_SHARD")"
# Every app partition shard tests the same single internal/app
# package, so the package query uses the base shard name and the
# partition only selects which tests run.
package_shard="$TEST_SHARD"
case "$TEST_SHARD" in
app-*) package_shard=app ;;
esac
package_output="$(./scripts/ci/test-packages.sh list "$package_shard")"
test -n "$package_output"
mapfile -t packages <<< "$package_output"
test "${#packages[@]}" -gt 0
if [ "$TEST_SHARD" = "app" ]; then
# A single long-lived app test process retains every constructed
# command tree in framework registries. Isolate Schema assembly and
# bounded name ranges so each process releases that state on exit.
test "${#packages[@]}" -eq 1
./scripts/ci/run-app-race-tests.sh run "${packages[0]}"
exit 0
fi
case "$TEST_SHARD" in
app-*)
# A single long-lived app test process retains every constructed
# command tree in framework registries. Each partition is its own
# job, so that state is released when the process exits and the
# partitions run concurrently instead of end to end. The helper
# still verifies that the partition patterns cover every top-level
# test exactly once before running the one it was asked for.
test "${#packages[@]}" -eq 1
./scripts/ci/run-app-race-tests.sh run "${packages[0]}" "${TEST_SHARD#app-}"
exit 0
;;
esac
# cli/smoke own heavy NewRootCommand / Schema assembly under -race;
# give them a dedicated package timeout on slower hosted runners.
timeout_budget=12m
+31
View File
@@ -6,6 +6,37 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th
## [Unreleased]
## [1.0.59-beta.2] - 2026-08-17
### Added
- **Privacy-safe CLI telemetry** (#1009) — reports reviewed command outcomes and profile identity dimensions while excluding command arguments, output, paths, device fingerprints, and automatic system dimensions; `DO_NOT_TRACK=1` disables reporting.
- **Feedback survey entry in root help** (#1019) — `dws --help` now closes with a Feedback section linking the user-experience survey form.
- **Wiki Shortcut workflows** — publishes 20 reviewed space, member, node, and
activity shortcuts with strict collection validation, cursor handling,
write-terminal evidence, safe read-backs where the backend supports them,
task-oriented routing, and documented backend
boundaries.
### Changed
- **Chat IM ID flags** (#954) — standardizes chat command entry points on `--conversation-id` for conversation IDs and `--message-id` for message IDs, so help, Schema, and Agent recommendations use the same canonical flags.
- **Legacy chat flag compatibility** (#954) — keeps older chat IM ID flags such as `--group`, `--id`, `--chat`, `--open-conversation-id`, `--msg-id`, and `--open-message-id` working as compatibility aliases where applicable, while hiding migrated aliases from recommended help and Schema surfaces.
- **Chat group bots target flag** (#954) — keeps `dws chat group bots` on the visible `--group` flag; this command does not register `--group-name`, and `--group` accepts either an openConversationId or a uniquely resolved group name.
- **Faster Schema Catalog assembly** — projects typed values into payload JSON
without re-running a validation scan over documents `json.Marshal` has just
produced, cutting roughly a third of the projection work across the full tool
set. Untrusted JSON input keeps its existing validation.
### Fixed
- **Chat card update evidence** — distinguishes an accepted update request from an independently verified visible update, preserving the real `bizId` and warning callers not to repeat an unverified write.
- **Chat command guidance** — splits message and group references by task and explains that `--from` is ambiguous between sender and time-range intent.
## [1.0.59-beta.1] - 2026-08-14
### Added
+18 -2
View File
@@ -1127,21 +1127,37 @@ func putRawJSON(payload map[string]any, key string, raw json.RawMessage) error {
return nil
}
// rawJSONValue decodes a JSON value that may come from an untrusted source, so
// it validates before decoding. Callers holding output that json.Marshal just
// produced should use typedJSONValue instead of paying the validation scan.
func rawJSONValue(raw json.RawMessage) (any, error) {
if !json.Valid(raw) {
return nil, fmt.Errorf("invalid JSON value")
}
return decodeValidJSONValue(raw), nil
}
// decodeValidJSONValue decodes JSON whose validity the caller has already
// established, either by json.Valid or by having just marshaled it. Decode
// errors are unreachable under that precondition and are therefore discarded,
// exactly as this path behaved when the decode was inlined into rawJSONValue.
func decodeValidJSONValue(raw json.RawMessage) any {
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber()
var value any
_ = decoder.Decode(&value)
return value, nil
return value
}
// typedJSONValue projects a typed value into the generic JSON shape the payload
// renderers consume. json.Marshal output is valid by construction, so this path
// decodes it directly: routing through rawJSONValue re-scanned every marshaled
// document with json.Valid, which measured ~34% of Schema Catalog assembly time
// across the 1121-tool set (26.0s -> 17.2s for the internal/app schema suite).
func typedJSONValue(value any) (any, error) {
data, err := json.Marshal(value)
if err != nil {
return nil, err
}
return rawJSONValue(data)
return decodeValidJSONValue(data), nil
}
+115 -21
View File
@@ -6,16 +6,35 @@ ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
usage() {
printf '%s\n' \
"usage: $0 verify <app-package>" \
" $0 run <app-package>" >&2
" $0 run <app-package> [partition]" \
" $0 list-partitions" >&2
exit 2
}
[ "$#" -eq 2 ] || usage
mode="$1"
app_package="$2"
# Single source of truth for the partition set. CI runs one job per partition and
# pins its shard names to this list, so a name that appears here without a
# dispatch entry below fails closed rather than silently skipping tests.
APP_PARTITIONS='schema a-b c d-r s-z-example-fuzz'
mode="${1:-}"
partition=""
case "$mode" in
verify|run) ;;
list-partitions)
[ "$#" -eq 1 ] || usage
for name in $APP_PARTITIONS; do
printf '%s\n' "$name"
done
exit 0
;;
verify)
[ "$#" -eq 2 ] || usage
app_package="$2"
;;
run)
[ "$#" -eq 2 ] || [ "$#" -eq 3 ] || usage
app_package="$2"
partition="${3:-}"
;;
*) usage ;;
esac
@@ -70,21 +89,49 @@ if [ "$unmatched_count" -ne 0 ]; then
exit 1
fi
for partition in \
# The loop variable is deliberately not named "partition": that name holds the
# partition requested on the command line, and run mode still executes this
# discovery pass before dispatching.
classified=''
for spec in \
"schema:$schema_count" \
"a-b:$ab_count" \
"c:$c_count" \
"d-r:$dr_count" \
"s-z-example-fuzz:$sz_count"
do
name="${partition%%:*}"
count="${partition#*:}"
name="${spec%%:*}"
count="${spec#*:}"
classified="$classified $name"
if [ "$count" -eq 0 ]; then
printf 'app race partition %s is empty\n' "$name" >&2
exit 1
fi
done
# The counters above and APP_PARTITIONS must describe the same set in both
# directions. A counted partition missing from APP_PARTITIONS would never be
# dispatched by any CI job, and a dispatchable partition with no counter would
# escape the exact-coverage check above.
for name in $APP_PARTITIONS; do
case " $classified " in
*" $name "*) ;;
*)
printf 'app partition %s has no coverage counter\n' "$name" >&2
exit 1
;;
esac
done
for name in $classified; do
case " $APP_PARTITIONS " in
*" $name "*) ;;
*)
printf 'counted partition %s is not a dispatchable app partition\n' "$name" >&2
exit 1
;;
esac
done
total_count="$(wc -l < "$tests" | tr -d ' ')"
assigned_count=$((schema_count + ab_count + c_count + dr_count + sz_count))
if [ "$assigned_count" -ne "$total_count" ]; then
@@ -101,26 +148,73 @@ fi
run_partition() {
name="$1"
run_pattern="$2"
skip_pattern="${3:-}"
instrumentation="$2"
run_pattern="$3"
skip_pattern="${4:-}"
printf 'running internal/app race partition %s\n' "$name"
# Fail closed on an unrecognized mode: a typo must not silently drop race
# instrumentation from a partition that is supposed to carry it.
case "$instrumentation" in
race|no-race) ;;
*)
printf 'unknown instrumentation %s for app partition %s\n' \
"$instrumentation" "$name" >&2
exit 1
;;
esac
printf 'running internal/app %s partition %s\n' "$instrumentation" "$name"
set -- -v -count=1 -timeout=15m -run "$run_pattern"
if [ -n "$skip_pattern" ]; then
go test -v -race -count=1 -timeout=15m \
-run "$run_pattern" -skip "$skip_pattern" "$app_package"
else
go test -v -race -count=1 -timeout=15m \
-run "$run_pattern" "$app_package"
set -- "$@" -skip "$skip_pattern"
fi
if [ "$instrumentation" = race ]; then
set -- -race "$@"
fi
go test "$@" "$app_package"
}
# Schema assembly has the largest transient memory footprint. Run those tests
# in a fresh process, then keep each remaining name range in its own process so
# command trees retained by process-global registries are released between
# partitions. The complementary run/skip patterns preserve the full test set.
#
# The schema partition runs uninstrumented. Its tests assert structural
# Schema-to-Cobra contracts over a single goroutine: none of them call
# t.Parallel or start a goroutine, so the race detector has no concurrent access
# to observe here. The process-global lazy metadata that does need race coverage
# (schema_source_root's atomic.Value, the parameter-binding lazy loaders) is
# exercised by internal/cli's concurrent tests, which stay instrumented. The
# instrumentation is not free on this partition: its shared sync.Once Catalog
# build is allocation-heavy, and -race made the partition roughly 11x slower
# (26s -> 291s locally, 357s in CI) without being able to report anything.
schema_pattern='^Test.*Schema'
run_partition schema "$schema_pattern"
run_partition a-b '^Test[A-B]' "$schema_pattern"
run_partition c '^TestC' "$schema_pattern"
run_partition d-r '^Test[D-R]' "$schema_pattern"
run_partition s-z-example-fuzz '^(Test[S-Z]|Example|Fuzz)' "$schema_pattern"
# Dispatch table for the partition set declared in APP_PARTITIONS. CI passes one
# partition per job so they run concurrently; running without a partition keeps
# the original end-to-end behaviour for local use and for any caller that wants
# the whole package in one invocation.
run_named_partition() {
case "$1" in
schema) run_partition schema no-race "$schema_pattern" ;;
a-b) run_partition a-b race '^Test[A-B]' "$schema_pattern" ;;
c) run_partition c race '^TestC' "$schema_pattern" ;;
d-r) run_partition d-r race '^Test[D-R]' "$schema_pattern" ;;
s-z-example-fuzz)
run_partition s-z-example-fuzz race '^(Test[S-Z]|Example|Fuzz)' "$schema_pattern"
;;
*)
printf 'unknown app partition: %s\n' "$1" >&2
exit 1
;;
esac
}
if [ -n "$partition" ]; then
run_named_partition "$partition"
exit 0
fi
for name in $APP_PARTITIONS; do
run_named_partition "$name"
done
+21 -15
View File
@@ -1157,7 +1157,7 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
`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-shard "$TEST_SHARD" "$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)",
@@ -1219,16 +1219,20 @@ func TestChangelogPRFastPathWorkflowContract(t *testing.T) {
}
// 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 keeps its
// package-level headroom through the process-isolating helper instead of one
// long -timeout, which is strictly stronger: every process releases the
// framework registries it populated. 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.
// 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{
`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-}"`,
`if [ "$TEST_SHARD" = "release-scripts" ]; then`,
`go test -v -count=1 -timeout=10m "${packages[@]}"`,
"timeout_budget=12m",
@@ -1249,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`,
+68
View File
@@ -99,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()