From 5bdef5d5774f83aef6ca22c1827208cac15bd904 Mon Sep 17 00:00:00 2001 From: Moe Dashti Date: Sat, 3 Oct 2026 01:05:01 -0700 Subject: [PATCH 1/5] Gave each regenerated test timer a distinct deadline. Timers with equal deadlines are grouped into one state machine timer, and the wall clock on macOS does not always tick between two nodes of one refresh walk, so the task refresher test counted two timer groups where it expected three. --- service/history/hsm/hsmtest/sm.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/service/history/hsm/hsmtest/sm.go b/service/history/hsm/hsmtest/sm.go index d01aec19a01..0a5c72308cc 100644 --- a/service/history/hsm/hsmtest/sm.go +++ b/service/history/hsm/hsmtest/sm.go @@ -3,6 +3,7 @@ package hsmtest import ( "fmt" "strings" + "sync/atomic" "time" "go.temporal.io/server/service/history/hsm" @@ -41,10 +42,17 @@ func (d *Data) SetState(s State) { d.state = s } +// regenerated counts the timer tasks handed out. Timers with equal deadlines +// are grouped into one state machine timer, and the wall clock does not tick +// between two nodes of one walk on every platform, so each deadline is pushed +// out by one more nanosecond than the last. +var regenerated atomic.Int64 + func (d *Data) RegenerateTasks(node *hsm.Node) ([]hsm.Task, error) { + deadline := time.Now().Add(time.Hour + time.Duration(regenerated.Add(1))) return []hsm.Task{ NewTask( - hsm.TaskAttributes{Deadline: time.Now().Add(time.Hour)}, + hsm.TaskAttributes{Deadline: deadline}, false, ), NewTask( From a3b8ca66397d477130993880100c631d069d537d Mon Sep 17 00:00:00 2001 From: Moe Dashti Date: Sat, 3 Oct 2026 01:05:01 -0700 Subject: [PATCH 2/5] Ignored exemplars when comparing exported metrics in tests. The OpenTelemetry SDK returns a nil or an empty exemplar slice depending on whether its reservoir was touched, so the comparison failed on a difference the test is not about. --- common/metrics/otel_metrics_handler_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/common/metrics/otel_metrics_handler_test.go b/common/metrics/otel_metrics_handler_test.go index a2d0b34eb53..bf08a9460e1 100644 --- a/common/metrics/otel_metrics_handler_test.go +++ b/common/metrics/otel_metrics_handler_test.go @@ -193,9 +193,14 @@ func TestMeter(t *testing.T) { cmpopts.SortSlices(func(x, y metricdata.Metrics) bool { return x.Name < y.Name }), - cmpopts.IgnoreFields(metricdata.DataPoint[int64]{}, "StartTime", "Time"), - cmpopts.IgnoreFields(metricdata.DataPoint[float64]{}, "StartTime", "Time"), - cmpopts.IgnoreFields(metricdata.HistogramDataPoint[int64]{}, "StartTime", "Time", "Bounds"), + // Exemplars are ignored rather than equated empty: the SDK hands back + // either a nil or an allocated-but-empty slice depending on whether + // its reservoir was ever touched, and equating empty globally would + // also hide a nil-versus-empty attribute set or data point. + cmpopts.IgnoreFields(metricdata.DataPoint[int64]{}, "StartTime", "Time", "Exemplars"), + cmpopts.IgnoreFields(metricdata.DataPoint[float64]{}, "StartTime", "Time", "Exemplars"), + cmpopts.IgnoreFields( + metricdata.HistogramDataPoint[int64]{}, "StartTime", "Time", "Bounds", "Exemplars"), ); diff != "" { t.Errorf("mismatch (-want, +got):\n%s", diff) } @@ -264,7 +269,8 @@ func TestMeter_TimerInSeconds(t *testing.T) { cmp.Comparer(func(a1, a2 attribute.Set) bool { return a1.Equals(&a2) }), - cmpopts.IgnoreFields(metricdata.HistogramDataPoint[float64]{}, "StartTime", "Time", "Bounds"), + cmpopts.IgnoreFields( + metricdata.HistogramDataPoint[float64]{}, "StartTime", "Time", "Bounds", "Exemplars"), ); diff != "" { t.Errorf("mismatch (-want, +got):\n%s", diff) } From 9fd000a92fe9d156c929b3af51434c52d6bd5e13 Mon Sep 17 00:00:00 2001 From: Moe Dashti Date: Sat, 3 Oct 2026 01:05:02 -0700 Subject: [PATCH 3/5] Kept hand-written files under chasm gen directories on regeneration. A gen package sometimes needs a hand-written file next to the generated ones, and wiping the directory deleted it without saying so. --- cmd/tools/protogen/main.go | 53 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/cmd/tools/protogen/main.go b/cmd/tools/protogen/main.go index cf2c00b2e6e..251f2a26adb 100644 --- a/cmd/tools/protogen/main.go +++ b/cmd/tools/protogen/main.go @@ -165,16 +165,65 @@ func newGenerator() (*generator, error) { return &gen, nil } +// removeExistingGenDirs clears out the previous run's output so a proto that +// was deleted does not leave its Go behind. +// +// Only generated files go. A gen package sometimes needs a hand-written file +// beside the generated ones, because a method has to be declared in the same +// package as the type it is on, and wiping the directory deleted those without +// saying so. Empty directories are then pruned, which is what removing a proto +// used to rely on. func (g *generator) removeExistingGenDirs() error { for _, dir := range g.chasmLibDirs { genDir := filepath.Join(dir, "gen") - if err := os.RemoveAll(genDir); err != nil { - return fmt.Errorf("error removing directory %s: %w", genDir, err) + if !exists(genDir) { + continue + } + if err := filepath.Walk(genDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() || !strings.HasSuffix(info.Name(), ".pb.go") { + return nil + } + return os.Remove(path) + }); err != nil { + return fmt.Errorf("error removing generated files under %s: %w", genDir, err) + } + if err := pruneEmptyDirs(genDir); err != nil { + return fmt.Errorf("error pruning empty directories under %s: %w", genDir, err) } } return nil } +// pruneEmptyDirs removes dir and any descendant left with nothing in it, +// deepest first. +func pruneEmptyDirs(dir string) error { + entries, err := os.ReadDir(dir) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + for _, entry := range entries { + if entry.IsDir() { + if err := pruneEmptyDirs(filepath.Join(dir, entry.Name())); err != nil { + return err + } + } + } + remaining, err := os.ReadDir(dir) + if err != nil { + return err + } + if len(remaining) == 0 { + return os.Remove(dir) + } + return nil +} + func (g *generator) backupProtos() error { if exists(g.protoOut) { if err := os.Rename(g.protoOut, g.protoBackup); err != nil { From 26c88426743f5ab4c8cedca198e5b3bb111effca Mon Sep 17 00:00:00 2001 From: Moe Dashti Date: Sat, 3 Oct 2026 01:05:19 -0700 Subject: [PATCH 4/5] Built the mixed-brain tests in their own module. The mixed-brain suite is a separate Go module that needs CGO, so building it from the root module's package list fails. --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 88d01fe4909..9cfdddc8fa5 100644 --- a/Makefile +++ b/Makefile @@ -532,7 +532,8 @@ clean-test-output: build-tests: @printf $(COLOR) "Build tests..." - @CGO_ENABLED=$(CGO_ENABLED) go test $(TEST_TAG_FLAG) -exec="true" -count=0 $(TEST_DIRS) + @CGO_ENABLED=$(CGO_ENABLED) go test $(TEST_TAG_FLAG) -exec="true" -count=0 $(filter-out $(MIXED_BRAIN_TEST_ROOT)%,$(TEST_DIRS)) + @cd $(MIXED_BRAIN_TEST_ROOT) && CGO_ENABLED=1 go test $(TEST_TAG_FLAG) -exec="true" -count=0 ./... unit-test: clean-test-output @printf $(COLOR) "Run unit tests..." From 8584ee4917e9019010e6cb73c4dc6abb83a83403 Mon Sep 17 00:00:00 2001 From: Moe Dashti Date: Sat, 3 Oct 2026 01:05:20 -0700 Subject: [PATCH 5/5] Deleted the previous test log before a test run. verify-test-log reads the whole file, so a log left by an earlier run was judged as part of this one. --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 9cfdddc8fa5..4ae6fc07ac7 100644 --- a/Makefile +++ b/Makefile @@ -525,9 +525,12 @@ workflowcheck: $(WORKFLOWCHECK) check: lint shell-check ##### Tests ##### +# verify-test-log reads the whole file, so a log left behind by an earlier run +# would be judged as if it belonged to this one. clean-test-output: @printf $(COLOR) "Delete test output..." @rm -rf $(TEST_OUTPUT_ROOT) + @rm -f test.log @go clean -testcache build-tests: