Skip to content

Commit 2acf955

Browse files
denikclaude
andauthored
acc: Introduce phases; speed up "test-update" 2x. (#4795)
## Changes - Introduce Phase field in test.toml that allows specifying order in which tests are run. All tests in Phase=0 are run before tests in Phase=1 - Introduce 'inherit' tag on acceptance tests config fields that disables default inheritance of test configs. This is needed as Phase=1 is frequently applied on parent test. - Simplify acceptance test runner. Previously if there was a single entry for a given EnvMatrix, it would be omitted from the test name, e.g. TestAccept/bla/DATABRICKS_BUNDLE_ENGINE=direct would become TestAccept/bla if "direct" was the only option. Now we keep the full name (helps to see what variant is being run). ## Why We have tests that depend on output of other tests. Because of that "make test-update" has to "go test -update" twice. This is no longer needed, one update is enough. ## Tests Manually, by removing all output for local tests and running full update: ``` % git grep -l 'Local = true' '**/out.test.toml' | find_out_files.py | wc -l 2420 % git grep -l 'Local = true' '**/out.test.toml' | find_out_files.py | xargs rm ``` --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent eb9b9a8 commit 2acf955

File tree

13 files changed

+172
-27
lines changed

13 files changed

+172
-27
lines changed

Makefile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,6 @@ test-slow-acc:
9898
.PHONY: test-update
9999
test-update:
100100
-go test ./acceptance -run '^TestAccept$$' -update -timeout=${LOCAL_TIMEOUT}
101-
@# at the moment second pass is required because some tests show diff against output of another test for easier review
102-
-go test ./acceptance -run '^TestAccept$$' -update -timeout=${LOCAL_TIMEOUT}
103101

104102
# Updates acceptance test output for template tests only
105103
.PHONY: test-update-templates

acceptance/acceptance_test.go

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"sort"
2121
"strconv"
2222
"strings"
23+
"sync"
2324
"testing"
2425
"time"
2526
"unicode/utf8"
@@ -293,14 +294,33 @@ func testAccept(t *testing.T, inprocessMode bool, singleTest string) int {
293294

294295
envFilters := getEnvFilters(t)
295296

297+
// Phases are only needed in update mode, where phase 0 tests regenerate
298+
// output files that phase 1 tests read via $TESTDIR. In normal runs,
299+
// those files are already committed and stable.
300+
usePhases := testdiff.OverwriteMode
301+
var phase0wg sync.WaitGroup
302+
phase1Gate := make(chan struct{})
303+
if !usePhases {
304+
close(phase1Gate)
305+
}
306+
296307
for _, dir := range testDirs {
297308
totalDirs += 1
298309

299310
t.Run(dir, func(t *testing.T) {
300311
selectedDirs += 1
301312

302313
config, configPath := internal.LoadConfig(t, dir)
303-
skipReason := getSkipReason(&config, configPath)
314+
err := validateTestPhase(config.Phase)
315+
if err != nil {
316+
t.Fatalf("Invalid config %s: %s", configPath, err)
317+
}
318+
319+
// Apply default: CloudSlow implies Cloud. Do this before generating
320+
// the materialized config so the implication is visible in out.test.toml.
321+
if isTruePtr(config.CloudSlow) {
322+
config.Cloud = config.CloudSlow
323+
}
304324

305325
// Generate materialized config for this test.
306326
// We do this before skipping the test, so the configs are generated for all tests.
@@ -313,21 +333,32 @@ func testAccept(t *testing.T, inprocessMode bool, singleTest string) int {
313333
t.Skip("Skipping test execution (only regenerating out.test.toml)")
314334
}
315335

336+
skipReason := getSkipReason(&config, configPath)
316337
if skipReason != "" {
317338
skippedDirs += 1
318339
t.Skip(skipReason)
319340
}
320341

321342
runParallel := !inprocessMode
322-
323343
if benchmarkMode && strings.Contains(dir, "benchmark") {
324344
runParallel = false
325345
}
326346

347+
// t.Run blocks until t.Parallel() is called, so Add must happen before t.Parallel().
348+
// This ensures all phase0 adds are visible before the wait goroutine starts.
349+
if usePhases && config.Phase == 0 {
350+
phase0wg.Add(1)
351+
t.Cleanup(phase0wg.Done)
352+
}
353+
327354
if runParallel {
328355
t.Parallel()
329356
}
330357

358+
if config.Phase != 0 {
359+
<-phase1Gate
360+
}
361+
331362
// Build extra vars for exclusion matching (config state as env vars)
332363
var extraVars []string
333364
if cloudEnv != "" {
@@ -336,26 +367,25 @@ func testAccept(t *testing.T, inprocessMode bool, singleTest string) int {
336367

337368
expanded := internal.ExpandEnvMatrix(config.EnvMatrix, config.EnvMatrixExclude, extraVars)
338369

339-
if len(expanded) == 1 {
340-
// env vars aren't part of the test case name, so log them for debugging
341-
if len(expanded[0]) > 0 {
342-
t.Logf("Running test with env %v", expanded[0])
343-
}
344-
runTest(t, dir, 0, coverDir, repls.Clone(), config, expanded[0], envFilters)
345-
} else {
346-
for ind, envset := range expanded {
347-
envname := strings.Join(envset, "/")
348-
t.Run(envname, func(t *testing.T) {
349-
if runParallel {
350-
t.Parallel()
351-
}
352-
runTest(t, dir, ind, coverDir, repls.Clone(), config, envset, envFilters)
353-
})
354-
}
370+
for ind, envset := range expanded {
371+
envname := strings.Join(envset, "/")
372+
t.Run(envname, func(t *testing.T) {
373+
if runParallel {
374+
t.Parallel()
375+
}
376+
runTest(t, dir, ind, coverDir, repls.Clone(), config, envset, envFilters)
377+
})
355378
}
356379
})
357380
}
358381

382+
if usePhases {
383+
go func() {
384+
phase0wg.Wait()
385+
close(phase1Gate)
386+
}()
387+
}
388+
359389
t.Logf("Summary (dirs): %d/%d/%d run/selected/total, %d skipped", selectedDirs-skippedDirs, selectedDirs, totalDirs, skippedDirs)
360390

361391
return selectedDirs - skippedDirs
@@ -405,13 +435,16 @@ func getTests(t *testing.T) []string {
405435
return testDirs
406436
}
407437

408-
// Return a reason to skip the test. Empty string means "don't skip".
409-
func getSkipReason(config *internal.TestConfig, configPath string) string {
410-
// Apply default first, so that it's visible in out.test.toml
411-
if isTruePtr(config.CloudSlow) {
412-
config.Cloud = config.CloudSlow
438+
func validateTestPhase(phase int) error {
439+
if phase == 0 || phase == 1 {
440+
return nil
413441
}
414442

443+
return fmt.Errorf("Phase must be 0 or 1, got %d", phase)
444+
}
445+
446+
// Return a reason to skip the test. Empty string means "don't skip".
447+
func getSkipReason(config *internal.TestConfig, configPath string) string {
415448
if os.Getenv("DATABRICKS_TEST_SKIPLOCAL") != "" && isTruePtr(config.Local) {
416449
return "Disabled via DATABRICKS_TEST_SKIPLOCAL environment variable in " + configPath
417450
}

acceptance/bundle/resources/permissions/out.test.toml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
Phase = 1
12
RecordRequests = true
23
Ignore = ['.databricks']

acceptance/bundle/templates/default-python/classic/out.test.toml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
Phase = 1
12
RecordRequests = true
23
EnvMatrix.READPLAN = ["", "1"]

acceptance/bundle/templates/default-python/serverless-customcatalog/out.test.toml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

acceptance/bundle/templates/default-python/serverless-customcatalog/test.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
Phase = 1
2+
13
[[Server]]
24
Pattern = "GET /api/2.1/unity-catalog/current-metastore-assignment"
35
Response.Body = '{"default_catalog_name": "customcatalog"}'

acceptance/bundle/user_agent/out.test.toml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

acceptance/bundle/user_agent/test.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
Phase = 1
12
RecordRequests = true
23
Local = true
34
IncludeRequestHeaders = ["User-Agent"]

0 commit comments

Comments
 (0)