| // Copyright 2024 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package counter_test | |
| import ( | |
| "fmt" | |
| "os" | |
| "path/filepath" | |
| "strings" | |
| "sync" | |
| "testing" | |
| "time" | |
| "golang.org/x/telemetry/counter/countertest" | |
| "golang.org/x/telemetry/internal/counter" | |
| "golang.org/x/telemetry/internal/regtest" | |
| "golang.org/x/telemetry/internal/telemetry" | |
| "golang.org/x/telemetry/internal/testenv" | |
| ) | |
| func init() { | |
| // Catch any bugs encountered while mapping counters. | |
| counter.CrashOnBugs = true | |
| } | |
| func TestConcurrentExtension(t *testing.T) { | |
| testenv.SkipIfUnsupportedPlatform(t) | |
| // This test verifies that files may be concurrently extended: when one file | |
| // discovers that its entries exceed the mapped data, it remaps the data. | |
| // Both programs populate enough new records to extend the file multiple | |
| // times. | |
| const numCounters = 50000 | |
| prog1 := regtest.NewProgram(t, "inc1", func() int { | |
| for i := 0; i < numCounters; i++ { | |
| counter.New(fmt.Sprint("gophers", i)).Inc() | |
| } | |
| return 0 | |
| }) | |
| prog2 := regtest.NewProgram(t, "inc2", func() int { | |
| for i := numCounters; i < 2*numCounters; i++ { | |
| counter.New(fmt.Sprint("gophers", i)).Inc() | |
| } | |
| return 0 | |
| }) | |
| dir := t.TempDir() | |
| now := time.Now().UTC() | |
| // Run a no-op program in the telemetry dir to ensure that the weekends file | |
| // exists, and avoid the race described in golang/go#68390. | |
| // (We could also call countertest.Open here, but better to avoid mutating | |
| // state in the current process for a test that is otherwise hermetic) | |
| prog0 := regtest.NewProgram(t, "init", func() int { return 0 }) | |
| if _, err := regtest.RunProgAsOf(t, dir, now, prog0); err != nil { | |
| t.Fatal(err) | |
| } | |
| var wg sync.WaitGroup | |
| wg.Add(2) | |
| // Run the programs concurrently. | |
| go func() { | |
| defer wg.Done() | |
| if out, err := regtest.RunProgAsOf(t, dir, now, prog1); err != nil { | |
| t.Errorf("prog1 failed: %v; output:\n%s", err, out) | |
| } | |
| }() | |
| go func() { | |
| defer wg.Done() | |
| if out, err := regtest.RunProgAsOf(t, dir, now, prog2); err != nil { | |
| t.Errorf("prog2 failed: %v; output:\n%s", err, out) | |
| } | |
| }() | |
| wg.Wait() | |
| counts := readCountsForDir(t, telemetry.NewDir(dir).LocalDir()) | |
| if got, want := len(counts), 2*numCounters; got != want { | |
| t.Errorf("Got %d counters, want %d", got, want) | |
| } | |
| for name, value := range counts { | |
| if value != 1 { | |
| t.Errorf("count(%s) = %d, want 1", name, value) | |
| } | |
| } | |
| } | |
| func readCountsForDir(t *testing.T, dir string) map[string]uint64 { | |
| entries, err := os.ReadDir(dir) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| var countFiles []string | |
| for _, entry := range entries { | |
| if strings.HasSuffix(entry.Name(), ".count") { | |
| countFiles = append(countFiles, filepath.Join(dir, entry.Name())) | |
| } | |
| } | |
| if len(countFiles) != 1 { | |
| t.Fatalf("found %d count files, want 1; directory contents: %v", len(countFiles), entries) | |
| } | |
| counters, _, err := countertest.ReadFile(countFiles[0]) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| return counters | |
| } |