mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 17:36:45 +08:00
* test(server): reduce slow unit test waits Co-authored-by: multica-agent <[email protected]> * test(server): cut slow Go unit test time further Follow-up to the first pass on MUL-7271. Test-only changes plus a few package-private seams whose production values are unchanged. - Re-executed race-built test binaries no longer pay the race runtime's 1s atexit sleep (GORACE=atexit_sleep_ms=0 for child processes only). - pkg/agent: event-driven or shorter test-only bounds for handshake, drain and idle graces; redundant timeout variants merged. - daemon/execenv: git fixtures copied from a template repo built once; tests that re-ran the same worktree lifecycle folded into their supersets; ForkLock-guarded fake-CLI writes so parallel tests cannot hit ETXTBSY. - handler/service: cap and paging tests shrink their caps instead of seeding thousands of rows; lock waits probe pg_locks instead of sleeping; one shared pgxpool per service test binary; cheaper cron and RSA fixtures. - wecom: the phishing-link sweep reuses one parser and derives CRLF from LF with a sampled cross-check; fake sockets acknowledge instead of waiting out the ack timeout. - migrate: the migration corpus is parsed once; isolated DB tests run in parallel. Co-authored-by: multica-agent <[email protected]> * test(server): remove transport timeout unit tests Co-authored-by: multica-agent <[email protected]> * test(server): restore two non-timeout tests and make a bound load-bearing Follow-up to the review of #8318. All test-only; no production code changes. - daemon: restore the three read-liveness cases that the transport-timeout sweep took with it. They assert that a ping, a pong and an application frame each EXTEND the read deadline, so a healthy wakeup socket survives without traffic of our own — the deadline never fires on the happy path, which is why they are not transport-timeout tests. Verified they fail (all three) when the extension calls are removed from wakeup.go, and pass in 0.57s together. The pure "times out without peer traffic" case stays deleted. - pkg/agent: restore the parent-DEADLINE half of the opaque-env redaction pair. codex.go treats a parent deadline and a parent cancellation as one ending; only the Canceled arm was left covered. The fake now writes the secret to stderr before reading the handshake, so the check no longer races our own initialize write, and the budget is 2s against a measured 430-465ms launch. Verified it fails when the !contextEnded guard is removed. - pkg/agent: TestRunCollectQuietDoesNotWaitWhenTheAnswerIsIn had its bound widened to a literal 5s, which the shrunk 750ms drain grace can never reach — the test could no longer fail. It now raises the drain grace to 10s for its own duration and asserts under 3s: healthy runs take 0.19-0.44s, and a broken short-circuit takes 10.19s and fails. - wecom: gofmt the import block in relay_settle_budget_test.go. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> Co-authored-by: J <[email protected]>
258 lines
8.8 KiB
Go
258 lines
8.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/rand/v2"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
func TestSourceContextMigrationsRollbackFailsClosedWithCapturedData(t *testing.T) {
|
|
t.Parallel()
|
|
adminPool := openTestPool(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
|
defer cancel()
|
|
|
|
suffix := fmt.Sprintf("%d_%d", time.Now().UnixNano(), rand.Uint32())
|
|
schema := "migrate_source_context_" + suffix
|
|
schemaIdent := pgx.Identifier{schema}.Sanitize()
|
|
if _, err := adminPool.Exec(ctx, "CREATE SCHEMA "+schemaIdent); err != nil {
|
|
t.Fatalf("create schema: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cleanupCancel()
|
|
if _, err := adminPool.Exec(cleanupCtx, "DROP SCHEMA IF EXISTS "+schemaIdent+" CASCADE"); err != nil {
|
|
t.Logf("drop schema %s: %v", schema, err)
|
|
}
|
|
})
|
|
|
|
pool := openTestPoolWithSearchPath(t, schema)
|
|
if _, err := pool.Exec(ctx, `CREATE TABLE attachment (id UUID NOT NULL)`); err != nil {
|
|
t.Fatalf("create attachment fixture: %v", err)
|
|
}
|
|
|
|
versions := []string{
|
|
"407_issue_source_context",
|
|
"408_issue_source_context_id_index",
|
|
"409_issue_source_context_issue_index",
|
|
"410_issue_source_context_origin_task_index",
|
|
"411_attachment_source_context_index",
|
|
"412_issue_source_context_object_intent_key_index",
|
|
"413_issue_source_context_object_intent_due_index",
|
|
"414_issue_source_context_object_intent_context_index",
|
|
}
|
|
options := runOptions{
|
|
Direction: "up",
|
|
Files: realMigrationFiles(t, versions, "up"),
|
|
SchemaMigrationsTable: schema + ".schema_migrations",
|
|
AdvisoryLockKey: int64(rand.Uint64()&0x7fffffffffffffff) | 1,
|
|
Hooks: hooksForDirection("up"),
|
|
}
|
|
if err := runMigrations(ctx, pool, options); err != nil {
|
|
t.Fatalf("apply source-context migrations: %v", err)
|
|
}
|
|
|
|
assertSourceContextSchema(t, pool, schema, true)
|
|
assertSourceContextIndexes(t, pool, schema, true)
|
|
assertSourceContextMigrationLedger(t, pool, schema, versions, true)
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO issue_source_context (
|
|
id, workspace_id, issue_id, origin_task_id, source_issue_id,
|
|
anchor_comment_id, captured_by_user_id, snapshot_version,
|
|
snapshot, capture_digest, state, attached_at
|
|
) VALUES (
|
|
'00000000-0000-0000-0000-000000000001',
|
|
'00000000-0000-0000-0000-000000000002',
|
|
'00000000-0000-0000-0000-000000000003',
|
|
NULL,
|
|
'00000000-0000-0000-0000-000000000004',
|
|
'00000000-0000-0000-0000-000000000005',
|
|
'00000000-0000-0000-0000-000000000006',
|
|
1,
|
|
'{}'::jsonb,
|
|
'digest',
|
|
'attached',
|
|
now()
|
|
)
|
|
`); err != nil {
|
|
t.Fatalf("insert source-context fixture: %v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO attachment (id, source_context_id)
|
|
VALUES (
|
|
'00000000-0000-0000-0000-000000000007',
|
|
'00000000-0000-0000-0000-000000000001'
|
|
)
|
|
`); err != nil {
|
|
t.Fatalf("insert source-context attachment fixture: %v", err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `
|
|
INSERT INTO issue_source_context_object_intent (
|
|
storage_key, workspace_id, source_context_id, attachment_id, object_url
|
|
) VALUES (
|
|
'workspaces/test/source-context/snapshot.txt',
|
|
'00000000-0000-0000-0000-000000000002',
|
|
'00000000-0000-0000-0000-000000000001',
|
|
'00000000-0000-0000-0000-000000000007',
|
|
'https://objects.example/source-context/snapshot.txt'
|
|
)
|
|
`); err != nil {
|
|
t.Fatalf("insert source-context object intent fixture: %v", err)
|
|
}
|
|
|
|
reversed := append([]string(nil), versions...)
|
|
for left, right := 0, len(reversed)-1; left < right; left, right = left+1, right-1 {
|
|
reversed[left], reversed[right] = reversed[right], reversed[left]
|
|
}
|
|
options.Direction = "down"
|
|
options.Files = realMigrationFiles(t, reversed, "down")
|
|
options.Hooks = hooksForDirection("down")
|
|
err := runMigrations(ctx, pool, options)
|
|
if err == nil || !strings.Contains(err.Error(), "cannot roll back issue source context") {
|
|
t.Fatalf("rollback with captured data error = %v, want fail-closed source-context error", err)
|
|
}
|
|
assertSourceContextSchema(t, pool, schema, true)
|
|
assertSourceContextIndexes(t, pool, schema, true)
|
|
assertSourceContextMigrationLedger(t, pool, schema, versions, true)
|
|
for table, want := range map[string]int{
|
|
"issue_source_context": 1,
|
|
"issue_source_context_object_intent": 1,
|
|
"attachment": 1,
|
|
} {
|
|
var count int
|
|
if err := pool.QueryRow(ctx, "SELECT count(*) FROM "+table).Scan(&count); err != nil {
|
|
t.Fatalf("count %s after rejected rollback: %v", table, err)
|
|
}
|
|
if count != want {
|
|
t.Fatalf("%s rows after rejected rollback = %d, want %d", table, count, want)
|
|
}
|
|
}
|
|
|
|
if _, err := pool.Exec(ctx, `
|
|
DELETE FROM issue_source_context_object_intent;
|
|
DELETE FROM attachment WHERE source_context_id IS NOT NULL;
|
|
DELETE FROM issue_source_context;
|
|
`); err != nil {
|
|
t.Fatalf("clean source-context fixtures before retry: %v", err)
|
|
}
|
|
if err := runMigrations(ctx, pool, options); err != nil {
|
|
t.Fatalf("roll back source-context migrations after cleanup: %v", err)
|
|
}
|
|
|
|
assertSourceContextSchema(t, pool, schema, false)
|
|
assertSourceContextIndexes(t, pool, schema, false)
|
|
assertSourceContextMigrationLedger(t, pool, schema, versions, false)
|
|
var attachmentExists bool
|
|
if err := pool.QueryRow(ctx, `SELECT to_regclass($1) IS NOT NULL`, schema+".attachment").Scan(&attachmentExists); err != nil {
|
|
t.Fatalf("read attachment table existence after rollback: %v", err)
|
|
}
|
|
if !attachmentExists {
|
|
t.Fatal("attachment table was removed by source-context rollback")
|
|
}
|
|
|
|
options.Direction = "up"
|
|
options.Files = realMigrationFiles(t, versions, "up")
|
|
options.Hooks = hooksForDirection("up")
|
|
if err := runMigrations(ctx, pool, options); err != nil {
|
|
t.Fatalf("reapply source-context migrations: %v", err)
|
|
}
|
|
assertSourceContextSchema(t, pool, schema, true)
|
|
|
|
var count int
|
|
if err := pool.QueryRow(ctx, `SELECT count(*) FROM issue_source_context`).Scan(&count); err != nil {
|
|
t.Fatalf("count source contexts after reapply: %v", err)
|
|
}
|
|
if count != 0 {
|
|
t.Fatalf("source contexts after guarded rollback and reapply = %d, want 0", count)
|
|
}
|
|
}
|
|
|
|
func assertSourceContextSchema(t *testing.T, pool interface {
|
|
QueryRow(context.Context, string, ...any) pgx.Row
|
|
}, schema string, want bool) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
for _, relation := range []string{"issue_source_context", "issue_source_context_object_intent"} {
|
|
var exists bool
|
|
if err := pool.QueryRow(ctx, `SELECT to_regclass($1) IS NOT NULL`, schema+"."+relation).Scan(&exists); err != nil {
|
|
t.Fatalf("read %s existence: %v", relation, err)
|
|
}
|
|
if exists != want {
|
|
t.Fatalf("%s existence = %v, want %v", relation, exists, want)
|
|
}
|
|
}
|
|
|
|
var columnExists bool
|
|
if err := pool.QueryRow(ctx, `
|
|
SELECT EXISTS (
|
|
SELECT 1
|
|
FROM information_schema.columns
|
|
WHERE table_schema = $1
|
|
AND table_name = 'attachment'
|
|
AND column_name = 'source_context_id'
|
|
)
|
|
`, schema).Scan(&columnExists); err != nil {
|
|
t.Fatalf("read attachment.source_context_id existence: %v", err)
|
|
}
|
|
if columnExists != want {
|
|
t.Fatalf("attachment.source_context_id existence = %v, want %v", columnExists, want)
|
|
}
|
|
}
|
|
|
|
func assertSourceContextIndexes(t *testing.T, pool interface {
|
|
QueryRow(context.Context, string, ...any) pgx.Row
|
|
}, schema string, want bool) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
for _, index := range []string{
|
|
"idx_issue_source_context_id",
|
|
"idx_issue_source_context_issue",
|
|
"idx_issue_source_context_origin_task",
|
|
"idx_attachment_source_context",
|
|
"idx_issue_source_context_object_intent_key",
|
|
"idx_issue_source_context_object_intent_due",
|
|
"idx_issue_source_context_object_intent_context",
|
|
} {
|
|
var usable bool
|
|
if err := pool.QueryRow(ctx, `
|
|
SELECT EXISTS (
|
|
SELECT 1
|
|
FROM pg_class index_relation
|
|
JOIN pg_namespace namespace ON namespace.oid = index_relation.relnamespace
|
|
JOIN pg_index index_state ON index_state.indexrelid = index_relation.oid
|
|
WHERE namespace.nspname = $1
|
|
AND index_relation.relname = $2
|
|
AND index_state.indisvalid
|
|
AND index_state.indisready
|
|
)
|
|
`, schema, index).Scan(&usable); err != nil {
|
|
t.Fatalf("read %s usability: %v", index, err)
|
|
}
|
|
if usable != want {
|
|
t.Fatalf("%s usable = %v, want %v", index, usable, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func assertSourceContextMigrationLedger(t *testing.T, pool interface {
|
|
QueryRow(context.Context, string, ...any) pgx.Row
|
|
}, schema string, versions []string, want bool) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
table := pgx.Identifier{schema, "schema_migrations"}.Sanitize()
|
|
for _, version := range versions {
|
|
var recorded bool
|
|
if err := pool.QueryRow(ctx, "SELECT EXISTS (SELECT 1 FROM "+table+" WHERE version = $1)", version).Scan(&recorded); err != nil {
|
|
t.Fatalf("read migration ledger entry %s: %v", version, err)
|
|
}
|
|
if recorded != want {
|
|
t.Fatalf("migration ledger entry %s recorded = %v, want %v", version, recorded, want)
|
|
}
|
|
}
|
|
}
|