diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d14778483..c6d2fdf6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,6 +109,9 @@ jobs: # by default on the Linux runner, but we set it explicitly to be safe. LAZYGIT_RACE_DETECTOR: ${{ matrix.race && '1' || '' }} CGO_ENABLED: ${{ matrix.race && '1' || '' }} + # Append each test's duration to this file; run_integration_tests.sh + # prints the slowest at the end, to spot slow/anomalous tests. + LAZYGIT_TEST_TIMING: /tmp/test_timings.txt run: | mkdir -p /tmp/code_coverage ./scripts/run_integration_tests.sh diff --git a/pkg/integration/clients/go_test.go b/pkg/integration/clients/go_test.go index d3f15b88a..4c1faa557 100644 --- a/pkg/integration/clients/go_test.go +++ b/pkg/integration/clients/go_test.go @@ -30,6 +30,7 @@ func TestIntegration(t *testing.T) { parallelTotal := tryConvert(os.Getenv("PARALLEL_TOTAL"), 1) parallelIndex := tryConvert(os.Getenv("PARALLEL_INDEX"), 0) raceDetector := os.Getenv("LAZYGIT_RACE_DETECTOR") != "" + logTimingsPath := os.Getenv("LAZYGIT_TEST_TIMING") // LAZYGIT_GOCOVERDIR is the directory where we write coverage files to. If this directory // is defined, go binaries built with the -cover flag will write coverage files to // to it. @@ -58,7 +59,8 @@ func TestIntegration(t *testing.T) { CodeCoverageDir: codeCoverageDir, InputDelay: 0, // Allow two attempts at each test to get around flakiness - MaxAttempts: 1, + MaxAttempts: 1, + LogTimingsPath: logTimingsPath, }) assert.NoError(t, err) diff --git a/pkg/integration/components/runner.go b/pkg/integration/components/runner.go index 5640c3e70..78cb5439f 100644 --- a/pkg/integration/components/runner.go +++ b/pkg/integration/components/runner.go @@ -5,6 +5,8 @@ import ( "os" "os/exec" "path/filepath" + "sync" + "time" lazycoreUtils "github.com/jesseduffield/lazycore/pkg/utils" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" @@ -24,6 +26,12 @@ type RunTestArgs struct { CodeCoverageDir string InputDelay int MaxAttempts int + // If set, each test's run duration is appended to this file (as + // " "). run_integration_tests.sh prints the slowest at + // the end, so slow or anomalous tests can be spotted across CI runs. We + // write to a file rather than stdout/stderr because `go test` captures + // those and only shows them with -v. Empty disables it. + LogTimingsPath string } // This function lets you run tests either from within `go test` or from a regular binary. @@ -47,6 +55,11 @@ func RunTests(args RunTestArgs) error { return err } + // Start each run with a fresh timings file (see RunTestArgs.LogTimingsPath). + if args.LogTimingsPath != "" { + _ = os.Remove(args.LogTimingsPath) + } + for _, test := range args.Tests { args.TestWrapper(test, func() error { paths := NewPaths( @@ -99,7 +112,11 @@ func runTest( return err } + start := time.Now() pid, err := args.RunCmd(cmd) + if args.LogTimingsPath != "" { + logTestTiming(args.LogTimingsPath, test.Name(), time.Since(start)) + } // Print race detector log regardless of the command's exit status if args.RaceDetector { @@ -112,6 +129,23 @@ func runTest( return err } +// timingsMutex serializes appends to the timings file, since tests run in +// parallel. +var timingsMutex sync.Mutex + +func logTestTiming(path, name string, duration time.Duration) { + timingsMutex.Lock() + defer timingsMutex.Unlock() + + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return + } + defer f.Close() + + fmt.Fprintf(f, "%.2f %s\n", duration.Seconds(), name) +} + func prepareTestDir( test *IntegrationTest, paths Paths, diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 1dadeec97..2bf010f19 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -37,4 +37,12 @@ if test -f ~/.gitconfig.lazygit.bak; then mv ~/.gitconfig.lazygit.bak ~/.gitconfig fi +# If per-test timings were collected (LAZYGIT_TEST_TIMING points at the file the +# harness appends to), print them sorted by slowest first so they show up in the +# CI log. +if [ -n "$LAZYGIT_TEST_TIMING" ] && [ -f "$LAZYGIT_TEST_TIMING" ]; then + echo "Test timings (seconds):" + sort -rn "$LAZYGIT_TEST_TIMING" +fi + exit $EXITCODE