From 3d4e707414260d57d7c57965eb1d28040e412984 Mon Sep 17 00:00:00 2001 From: Dimitar Dimitrov Date: Thu, 5 Jun 2025 15:58:23 +0200 Subject: [PATCH 1/8] feat: add Git author tracking functionality Add Git client implementation to track test authors: - Find test files using grep search through repository - Extract commit history using git log -L for specific test functions - Filter commits to last 6 months to focus on recent contributors - Add FilePath and RecentCommits to FlakyTest struct - Update action to include repository-directory input - Enhance test reporting with author information Builds on PR1's Loki analysis with Git history tracking. GitHub username resolution will be added in PR3. # Conflicts: # actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go --- actions/go-flaky-tests/action.yaml | 5 + .../cmd/go-flaky-tests/analyzer.go | 95 +++++++++++- .../cmd/go-flaky-tests/config.go | 26 ++-- .../go-flaky-tests/cmd/go-flaky-tests/git.go | 135 ++++++++++++++++++ 4 files changed, 246 insertions(+), 15 deletions(-) create mode 100644 actions/go-flaky-tests/cmd/go-flaky-tests/git.go diff --git a/actions/go-flaky-tests/action.yaml b/actions/go-flaky-tests/action.yaml index 1f7e7d6d3..9964a851c 100644 --- a/actions/go-flaky-tests/action.yaml +++ b/actions/go-flaky-tests/action.yaml @@ -19,6 +19,10 @@ inputs: description: "Time range for the query (e.g., '1h', '24h', '7d')" required: false default: "1h" + repository-directory: + description: "Relative path to the directory with a git repository" + required: false + default: ${{ github.workspace }} top-k: description: "Include only the top K flaky tests by distinct branches count in analysis" required: false @@ -44,4 +48,5 @@ runs: LOKI_PASSWORD: ${{ inputs.loki-password }} REPOSITORY: ${{ inputs.repository }} TIME_RANGE: ${{ inputs.time-range }} + REPOSITORY_DIRECTORY: ${{ inputs.repository-directory }} TOP_K: ${{ inputs.top-k }} diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go b/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go index 1efb8964a..a0b8924c5 100644 --- a/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go @@ -7,22 +7,38 @@ import ( "os" "path/filepath" "strings" + "time" ) type FileSystem interface { WriteFile(filename string, data []byte, perm os.FileMode) error } +type GitClient interface { + FindTestFile(testName string) (string, error) + GetFileAuthors(filePath, testName string) ([]CommitInfo, error) +} + type TestFailureAnalyzer struct { lokiClient LokiClient + gitClient GitClient fileSystem FileSystem } +type CommitInfo struct { + Hash string `json:"hash"` + Author string `json:"author"` + Timestamp time.Time `json:"timestamp"` + Title string `json:"title"` +} + type FlakyTest struct { TestName string `json:"test_name"` + FilePath string `json:"file_path"` TotalFailures int `json:"total_failures"` BranchCounts map[string]int `json:"branch_counts"` ExampleWorkflows []GithubActionsWorkflow `json:"example_workflows"` + RecentCommits []CommitInfo `json:"recent_commits"` } type GithubActionsWorkflow struct { @@ -32,7 +48,17 @@ type GithubActionsWorkflow struct { } func (f *FlakyTest) String() string { - return fmt.Sprintf("%s (%d total failures)", f.TestName, f.TotalFailures) + var authors []string + for _, commit := range f.RecentCommits { + if commit.Author != "" && commit.Author != "unknown" { + authors = append(authors, commit.Author) + } + } + authorsStr := "unknown" + if len(authors) > 0 { + authorsStr = strings.Join(authors, ", ") + } + return fmt.Sprintf("%s (%d total failures; recently changed by %s)", f.TestName, f.TotalFailures, authorsStr) } type FailuresReport struct { @@ -48,18 +74,20 @@ func (fs *DefaultFileSystem) WriteFile(filename string, data []byte, perm os.Fil return os.WriteFile(filename, data, perm) } -func NewTestFailureAnalyzer(loki LokiClient, fs FileSystem) *TestFailureAnalyzer { +func NewTestFailureAnalyzer(loki LokiClient, git GitClient, fs FileSystem) *TestFailureAnalyzer { return &TestFailureAnalyzer{ lokiClient: loki, + gitClient: git, fileSystem: fs, } } func NewDefaultTestFailureAnalyzer(config Config) *TestFailureAnalyzer { lokiClient := NewDefaultLokiClient(config) + gitClient := NewDefaultGitClient(config) fileSystem := &DefaultFileSystem{} - return NewTestFailureAnalyzer(lokiClient, fileSystem) + return NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) } func (t *TestFailureAnalyzer) AnalyzeFailures(config Config) (*FailuresReport, error) { @@ -84,6 +112,35 @@ func (t *TestFailureAnalyzer) AnalyzeFailures(config Config) (*FailuresReport, e } log.Printf("๐Ÿงช Found %d flaky tests that meet criteria", len(flakyTests)) + log.Printf("๐Ÿ“ Finding test files in repository...") + err = t.findFilePaths(flakyTests) + if err != nil { + return nil, fmt.Errorf("failed to find file paths for flaky tests: %w", err) + } + + log.Printf("๐Ÿ‘ฅ Finding authors of flaky tests...") + err = t.findTestAuthors(flakyTests) + if err != nil { + return nil, fmt.Errorf("failed to find test authors: %w", err) + } + + for _, test := range flakyTests { + if len(test.RecentCommits) > 0 { + var authors []string + for _, commit := range test.RecentCommits { + if commit.Author != "" && commit.Author != "unknown" { + authors = append(authors, commit.Author) + } + } + if len(authors) > 0 { + log.Printf("๐Ÿ‘ค %s: %s", test.TestName, strings.Join(authors, ", ")) + } else { + log.Printf("๐Ÿ‘ค %s: no authors found", test.TestName) + } + } else { + log.Printf("๐Ÿ‘ค %s: no commits found", test.TestName) + } + } if flakyTests == nil { flakyTests = []FlakyTest{} @@ -146,6 +203,38 @@ func (t *TestFailureAnalyzer) generateReport(result FailuresReport) (string, err return filepath.Abs(reportPath) } +func (t *TestFailureAnalyzer) findFilePaths(flakyTests []FlakyTest) error { + for i, test := range flakyTests { + filePath, err := t.gitClient.FindTestFile(test.TestName) + if err != nil { + return fmt.Errorf("failed to find file path for test %s: %w", test.TestName, err) + } + flakyTests[i].FilePath = filePath + } + return nil +} + +func (t *TestFailureAnalyzer) findTestAuthors(flakyTests []FlakyTest) error { + for i, test := range flakyTests { + commits, err := t.gitClient.GetFileAuthors(test.FilePath, test.TestName) + if err != nil { + return fmt.Errorf("failed to get authors for test %s in %s: %w", test.TestName, test.FilePath, err) + } + flakyTests[i].RecentCommits = commits + + if len(commits) > 0 { + var authors []string + for _, commit := range commits { + authors = append(authors, commit.Author) + } + log.Printf("๐Ÿ‘ค %s: %s", test.TestName, strings.Join(authors, ", ")) + } else { + log.Printf("๐Ÿ‘ค %s: no commits found", test.TestName) + } + } + return nil +} + func generateSummary(flakyTests []FlakyTest) string { if len(flakyTests) == 0 { return "No flaky tests found in the specified time range." diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/config.go b/actions/go-flaky-tests/cmd/go-flaky-tests/config.go index a07714d29..fcc8b90e1 100644 --- a/actions/go-flaky-tests/cmd/go-flaky-tests/config.go +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/config.go @@ -6,22 +6,24 @@ import ( ) type Config struct { - LokiURL string - LokiUsername string - LokiPassword string - Repository string - TimeRange string - TopK int + LokiURL string + LokiUsername string + LokiPassword string + Repository string + TimeRange string + RepositoryDirectory string + TopK int } func getConfigFromEnv() Config { return Config{ - LokiURL: os.Getenv("LOKI_URL"), - LokiUsername: os.Getenv("LOKI_USERNAME"), - LokiPassword: os.Getenv("LOKI_PASSWORD"), - Repository: os.Getenv("REPOSITORY"), - TimeRange: getEnvWithDefault("TIME_RANGE", "24h"), - TopK: getIntEnvWithDefault("TOP_K", 3), + LokiURL: os.Getenv("LOKI_URL"), + LokiUsername: os.Getenv("LOKI_USERNAME"), + LokiPassword: os.Getenv("LOKI_PASSWORD"), + Repository: os.Getenv("REPOSITORY"), + TimeRange: getEnvWithDefault("TIME_RANGE", "24h"), + RepositoryDirectory: getEnvWithDefault("REPOSITORY_DIRECTORY", "."), + TopK: getIntEnvWithDefault("TOP_K", 3), } } diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/git.go b/actions/go-flaky-tests/cmd/go-flaky-tests/git.go new file mode 100644 index 000000000..56208dd4b --- /dev/null +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/git.go @@ -0,0 +1,135 @@ +package main + +import ( + "fmt" + "log" + "os/exec" + "strconv" + "strings" + "time" +) + + +type DefaultGitClient struct { + config Config +} + +func NewDefaultGitClient(config Config) *DefaultGitClient { + return &DefaultGitClient{config: config} +} + +func (g *DefaultGitClient) FindTestFile(testName string) (string, error) { + return findTestFilePath(g.config.RepositoryDirectory, testName) +} + +func (g *DefaultGitClient) GetFileAuthors(filePath, testName string) ([]CommitInfo, error) { + return getFileAuthors(g.config, filePath, testName) +} + +func findTestFilePath(repoDir, testName string) (string, error) { + if !strings.HasPrefix(testName, "Test") { + return "", fmt.Errorf("invalid test name format: %s", testName) + } + + grepCmd := exec.Command("grep", "-rl", "--include=*_test.go", fmt.Sprintf("func %s(", testName), ".") + grepCmd.Dir = repoDir + + result, err := grepCmd.Output() + if err != nil { + return "", fmt.Errorf("failed to search for test function %s: %w", testName, err) + } + + lines := strings.Split(strings.TrimSpace(string(result)), "\n") + if len(lines) > 0 && lines[0] != "" { + if len(lines) > 1 { + log.Printf("Warning: test function %s found in multiple files, using first match: %s", testName, lines[0]) + } + + filePath := strings.TrimPrefix(lines[0], "./") + return filePath, nil + } + + return "", fmt.Errorf("test function %s not found in repository", testName) +} + +func guessTestFilePath(testName string) string { + if strings.HasPrefix(testName, "Test") { + name := strings.TrimPrefix(testName, "Test") + if name != "" { + var result strings.Builder + for i, r := range name { + if i > 0 && r >= 'A' && r <= 'Z' { + result.WriteRune('_') + } + result.WriteRune(r) + } + return strings.ToLower(result.String()) + "_test.go" + } + } + + return "unknown_test_file" +} + +func getFileAuthors(config Config, filePath, testName string) ([]CommitInfo, error) { + return getFileAuthorsWithClient(config.RepositoryDirectory, filePath, testName) +} + +func getFileAuthorsWithClient(repoDir, filePath, testName string) ([]CommitInfo, error) { + cmd := exec.Command("git", "log", "-3", "-L", fmt.Sprintf(":%s:%s", testName, filePath), "--pretty=format:%H|%ct|%s", "-s") + cmd.Dir = repoDir + + result, err := cmd.Output() + if err != nil { + log.Printf("Warning: failed to get git log for test %s in %s: %v", testName, filePath, err) + return []CommitInfo{}, nil + } + + lines := strings.Split(strings.TrimSpace(string(result)), "\n") + if len(lines) == 0 || lines[0] == "" { + log.Printf("Warning: no git log results for test %s in %s", testName, filePath) + return []CommitInfo{}, nil + } + + var commits []CommitInfo + sixMonthsAgo := time.Now().AddDate(0, -6, 0) + + for _, line := range lines { + parts := strings.SplitN(strings.TrimSpace(line), "|", 3) + if len(parts) != 3 { + return nil, fmt.Errorf("invalid git log format for test %s in %s: %s", testName, filePath, line) + } + + hash := parts[0] + timestampStr := parts[1] + title := parts[2] + + if hash == "" { + continue + } + + var timestamp time.Time + if timestampUnix, err := strconv.ParseInt(timestampStr, 10, 64); err == nil { + timestamp = time.Unix(timestampUnix, 0) + } + + if timestamp.Before(sixMonthsAgo) { + continue + } + + username := "unknown" // Will be resolved via GitHub in PR3 + + if strings.HasSuffix(username, "[bot]") { + continue + } + + commitInfo := CommitInfo{ + Hash: hash, + Author: username, + Timestamp: timestamp, + Title: title, + } + commits = append(commits, commitInfo) + } + + return commits, nil +} From 7c752c5512311fa01355b19185bb070d96ce1055 Mon Sep 17 00:00:00 2001 From: Dimitar Dimitrov Date: Thu, 5 Jun 2025 16:27:59 +0200 Subject: [PATCH 2/8] Format Go code with gofmt --- actions/go-flaky-tests/cmd/go-flaky-tests/git.go | 1 - 1 file changed, 1 deletion(-) diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/git.go b/actions/go-flaky-tests/cmd/go-flaky-tests/git.go index 56208dd4b..b376d1e66 100644 --- a/actions/go-flaky-tests/cmd/go-flaky-tests/git.go +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/git.go @@ -9,7 +9,6 @@ import ( "time" ) - type DefaultGitClient struct { config Config } From 293f81881152f1c76b5391f8c4cdfa09ca98a351 Mon Sep 17 00:00:00 2001 From: Dimitar Dimitrov Date: Thu, 5 Jun 2025 17:55:42 +0200 Subject: [PATCH 3/8] Revert "Remove PR2-related features from documentation" This reverts commit 9a29cc395231db6a54e7ddb2bedabd6ec0727147. --- actions/go-flaky-tests/CHANGELOG.md | 2 ++ actions/go-flaky-tests/README.md | 27 +++++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/actions/go-flaky-tests/CHANGELOG.md b/actions/go-flaky-tests/CHANGELOG.md index 59b526957..59996a017 100644 --- a/actions/go-flaky-tests/CHANGELOG.md +++ b/actions/go-flaky-tests/CHANGELOG.md @@ -6,10 +6,12 @@ - Initial implementation of flaky test analysis action - Loki integration for fetching test failure logs +- Git history analysis to find test authors - Comprehensive test suite with golden file testing ### Features - **Loki Log Analysis**: Fetches and parses test failure logs using LogQL - **Flaky Test Detection**: Identifies tests that fail inconsistently across branches +- **Git Author Tracking**: Finds recent commits that modified flaky tests - **Configurable Limits**: Top-K filtering to focus on most problematic tests diff --git a/actions/go-flaky-tests/README.md b/actions/go-flaky-tests/README.md index b73e7e1b5..ae92722d1 100644 --- a/actions/go-flaky-tests/README.md +++ b/actions/go-flaky-tests/README.md @@ -1,11 +1,12 @@ # Go Flaky Tests -A GitHub Action that detects and analyzes flaky Go tests by fetching logs from Loki. +A GitHub Action that detects and analyzes flaky Go tests by fetching logs from Loki and finding their authors. ## Features - **Loki Integration**: Fetches test failure logs from Loki using LogQL queries - **Flaky Test Detection**: Identifies tests that fail inconsistently across different branches +- **Git History Analysis**: Finds test files and extracts recent commit authors ## Usage @@ -35,14 +36,15 @@ jobs: ## Inputs -| Input | Description | Required | Default | -| --------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | -| `loki-url` | Loki endpoint URL | โœ… | - | -| `loki-username` | Username for Loki authentication | โŒ | - | -| `loki-password` | Password for Loki authentication. If using Grafana Cloud, then the access policy for this token needs the `logs:read` scope. | โŒ | - | -| `repository` | Repository name in 'owner/repo' format | โœ… | - | -| `time-range` | Time range for the query (e.g., '1h', '24h', '7d') | โŒ | `1h` | -| `top-k` | Include only the top K flaky tests by distinct branches count | โŒ | `3` | +| Input | Description | Required | Default | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------- | +| `loki-url` | Loki endpoint URL | โœ… | - | +| `loki-username` | Username for Loki authentication | โŒ | - | +| `loki-password` | Password for Loki authentication. If using Grafana Cloud, then the access policy for this token needs the `logs:read` scope. | โŒ | - | +| `repository` | Repository name in 'owner/repo' format | โœ… | - | +| `time-range` | Time range for the query (e.g., '1h', '24h', '7d') | โŒ | `1h` | +| `repository-directory` | Relative path to the directory with a git repository | โŒ | `${{ github.workspace }}` | +| `top-k` | Include only the top K flaky tests by distinct branches count | โŒ | `3` | ## Outputs @@ -57,6 +59,8 @@ jobs: 1. **Fetch Logs**: Queries Loki for test failure logs within the specified time range 2. **Parse Failures**: Extracts test names, branches, and workflow URLs from logs 3. **Detect Flaky Tests**: Identifies tests that fail on multiple branches or multiple times on main/master +4. **Find Test Files**: Locates test files in the repository using grep +5. **Extract Authors**: Uses `git log -L` to find recent commits that modified each test ## Flaky Test Detection Logic @@ -76,13 +80,16 @@ Run the analysis locally using the provided script: export LOKI_URL="your-loki-url" export REPOSITORY="owner/repo" export TIME_RANGE="24h" +export REPOSITORY_DIRECTORY="." + # Run the analysis -go run ./cmd/go-flaky-tests +./run-local.sh ``` ## Requirements - Go 1.22 or later +- Git repository with test files - Access to Loki instance with test failure logs ## Output Format From f7eca8101447b1155415ce14e0e27090d140b7c9 Mon Sep 17 00:00:00 2001 From: Dimitar Dimitrov Date: Thu, 5 Jun 2025 18:19:29 +0200 Subject: [PATCH 4/8] Remove unnecessary function --- actions/go-flaky-tests/README.md | 2 +- .../go-flaky-tests/cmd/go-flaky-tests/git.go | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/actions/go-flaky-tests/README.md b/actions/go-flaky-tests/README.md index ae92722d1..e6906e713 100644 --- a/actions/go-flaky-tests/README.md +++ b/actions/go-flaky-tests/README.md @@ -43,7 +43,7 @@ jobs: | `loki-password` | Password for Loki authentication. If using Grafana Cloud, then the access policy for this token needs the `logs:read` scope. | โŒ | - | | `repository` | Repository name in 'owner/repo' format | โœ… | - | | `time-range` | Time range for the query (e.g., '1h', '24h', '7d') | โŒ | `1h` | -| `repository-directory` | Relative path to the directory with a git repository | โŒ | `${{ github.workspace }}` | +| `repository-directory` | Relative path to the directory with a git repository | โŒ | `${{ github.workspace }}` | | `top-k` | Include only the top K flaky tests by distinct branches count | โŒ | `3` | ## Outputs diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/git.go b/actions/go-flaky-tests/cmd/go-flaky-tests/git.go index b376d1e66..c72591727 100644 --- a/actions/go-flaky-tests/cmd/go-flaky-tests/git.go +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/git.go @@ -51,24 +51,6 @@ func findTestFilePath(repoDir, testName string) (string, error) { return "", fmt.Errorf("test function %s not found in repository", testName) } -func guessTestFilePath(testName string) string { - if strings.HasPrefix(testName, "Test") { - name := strings.TrimPrefix(testName, "Test") - if name != "" { - var result strings.Builder - for i, r := range name { - if i > 0 && r >= 'A' && r <= 'Z' { - result.WriteRune('_') - } - result.WriteRune(r) - } - return strings.ToLower(result.String()) + "_test.go" - } - } - - return "unknown_test_file" -} - func getFileAuthors(config Config, filePath, testName string) ([]CommitInfo, error) { return getFileAuthorsWithClient(config.RepositoryDirectory, filePath, testName) } From 1e302d7bc8b7ae0646d26b07fa65256a89293727 Mon Sep 17 00:00:00 2001 From: Dimitar Dimitrov Date: Thu, 5 Jun 2025 18:47:31 +0200 Subject: [PATCH 5/8] Adapt tests --- .../cmd/go-flaky-tests/main_test.go | 214 +++++++++++++++--- .../testdata/complex_scenario.json | 41 +++- .../testdata/single_test_scenario.json | 6 +- 3 files changed, 231 insertions(+), 30 deletions(-) diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/main_test.go b/actions/go-flaky-tests/cmd/go-flaky-tests/main_test.go index d0861afd1..2987a4c7f 100644 --- a/actions/go-flaky-tests/cmd/go-flaky-tests/main_test.go +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/main_test.go @@ -29,7 +29,8 @@ func (m *MockLokiClient) FetchLogs() (*LokiResponse, error) { // MockGitClient implements GitClient for testing type MockGitClient struct { - testFiles map[string]string // testName -> filePath + testFiles map[string]string // testName -> filePath + authors map[string][]CommitInfo // testName -> commits fileErr error authorErr error } @@ -44,6 +45,16 @@ func (m *MockGitClient) FindTestFile(testName string) (string, error) { return "", fmt.Errorf("test file not found for %s", testName) } +func (m *MockGitClient) GetFileAuthors(filePath, testName string) ([]CommitInfo, error) { + if m.authorErr != nil { + return nil, m.authorErr + } + if commits, exists := m.authors[testName]; exists { + return commits, nil + } + return []CommitInfo{}, nil +} + // MockFileSystem implements FileSystem for testing type MockFileSystem struct { writtenFiles map[string][]byte @@ -91,12 +102,13 @@ func createTestLokiResponse(entries []RawLogEntry) *LokiResponse { func createTestConfig() Config { return Config{ - LokiURL: "http://localhost:3100", - LokiUsername: "user", - LokiPassword: "pass", - Repository: "test/repo", - TimeRange: "24h", - TopK: 3, + LokiURL: "http://localhost:3100", + LokiUsername: "user", + LokiPassword: "pass", + Repository: "test/repo", + TimeRange: "24h", + RepositoryDirectory: "/tmp/test", + TopK: 3, } } @@ -114,9 +126,23 @@ func TestAnalyzer_AnalyzeFailures_Success(t *testing.T) { // Setup mocks lokiClient := &MockLokiClient{response: lokiResponse} + gitClient := &MockGitClient{ + testFiles: map[string]string{ + "TestUserLogin": "user_test.go", + "TestPayment": "payment_test.go", + }, + authors: map[string][]CommitInfo{ + "TestUserLogin": { + {Hash: "abc123", Author: "alice", Timestamp: time.Now().AddDate(0, -1, 0), Title: "Fix user login"}, + }, + "TestPayment": { + {Hash: "def456", Author: "bob", Timestamp: time.Now().AddDate(0, -2, 0), Title: "Update payment logic"}, + }, + }, + } fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() // Run the analysis phase only @@ -135,10 +161,12 @@ func TestAnalyzer_AnalyzeFailures_Success(t *testing.T) { userTest := findTestByName(report.FlakyTests, "TestUserLogin") require.NotNil(t, userTest, "TestUserLogin should be found") assert.Equal(t, 2, userTest.TotalFailures, "TestUserLogin should have 2 failures") + assert.Equal(t, "user_test.go", userTest.FilePath, "TestUserLogin should have correct file path") paymentTest := findTestByName(report.FlakyTests, "TestPayment") require.NotNil(t, paymentTest, "TestPayment should be found") assert.Equal(t, 1, paymentTest.TotalFailures, "TestPayment should have 1 failure") + assert.Equal(t, "payment_test.go", paymentTest.FilePath, "TestPayment should have correct file path") // Check that report file was written assert.NotEmpty(t, report.ReportPath, "Report path should be set") @@ -147,9 +175,10 @@ func TestAnalyzer_AnalyzeFailures_Success(t *testing.T) { func TestAnalyzer_AnalyzeFailures_LokiError(t *testing.T) { lokiClient := &MockLokiClient{err: fmt.Errorf("loki connection failed")} + gitClient := &MockGitClient{} fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() report, err := analyzer.AnalyzeFailures(config) @@ -169,9 +198,10 @@ func TestAnalyzer_AnalyzeFailures_EmptyResponse(t *testing.T) { } lokiClient := &MockLokiClient{response: lokiResponse} + gitClient := &MockGitClient{} fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() report, err := analyzer.AnalyzeFailures(config) @@ -193,12 +223,14 @@ func TestAnalyzer_ActionReport_WithoutPostingIssues(t *testing.T) { FlakyTests: []FlakyTest{ { TestName: "TestUserLogin", + FilePath: "user_test.go", TotalFailures: 2, BranchCounts: map[string]int{"main": 1, "feature": 1}, ExampleWorkflows: []GithubActionsWorkflow{{RunURL: "https://github.com/test/repo/actions/runs/1"}, {RunURL: "https://github.com/test/repo/actions/runs/2"}}, }, { TestName: "TestPayment", + FilePath: "payment_test.go", TotalFailures: 1, BranchCounts: map[string]int{"main": 1}, ExampleWorkflows: []GithubActionsWorkflow{{RunURL: "https://github.com/test/repo/actions/runs/3"}}, @@ -208,9 +240,10 @@ func TestAnalyzer_ActionReport_WithoutPostingIssues(t *testing.T) { // Setup mocks lokiClient := &MockLokiClient{} + gitClient := &MockGitClient{} fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() // Run the enactment phase only @@ -228,6 +261,7 @@ func TestAnalyzer_ActionReport_ProductionMode(t *testing.T) { FlakyTests: []FlakyTest{ { TestName: "TestUserLogin", + FilePath: "user_test.go", TotalFailures: 2, BranchCounts: map[string]int{"main": 2}, ExampleWorkflows: []GithubActionsWorkflow{{RunURL: "https://github.com/test/repo/actions/runs/1"}}, @@ -237,9 +271,10 @@ func TestAnalyzer_ActionReport_ProductionMode(t *testing.T) { // Setup mocks lokiClient := &MockLokiClient{} + gitClient := &MockGitClient{} fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() // Run the enactment phase only @@ -259,9 +294,10 @@ func TestAnalyzer_ActionReport_EmptyReport(t *testing.T) { // Setup mocks lokiClient := &MockLokiClient{} + gitClient := &MockGitClient{} fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() // Run the enactment phase only @@ -274,9 +310,10 @@ func TestAnalyzer_ActionReport_EmptyReport(t *testing.T) { func TestAnalyzer_ActionReport_NilReport(t *testing.T) { // Setup mocks lokiClient := &MockLokiClient{} + gitClient := &MockGitClient{} fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() // Run the enactment phase with nil report @@ -300,9 +337,23 @@ func TestAnalyzer_Run_Success(t *testing.T) { // Setup mocks lokiClient := &MockLokiClient{response: lokiResponse} + gitClient := &MockGitClient{ + testFiles: map[string]string{ + "TestUserLogin": "user_test.go", + "TestPayment": "payment_test.go", + }, + authors: map[string][]CommitInfo{ + "TestUserLogin": { + {Hash: "abc123", Author: "alice", Timestamp: time.Now().AddDate(0, -1, 0), Title: "Fix user login"}, + }, + "TestPayment": { + {Hash: "def456", Author: "bob", Timestamp: time.Now().AddDate(0, -2, 0), Title: "Update payment logic"}, + }, + }, + } fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() // Run the analysis @@ -328,6 +379,7 @@ func TestAnalyzer_Run_Success(t *testing.T) { for _, test := range result.FlakyTests { testNames[test.TestName] = true assert.Greater(t, test.TotalFailures, 0, "Test %s should have failures", test.TestName) + assert.NotEmpty(t, test.FilePath, "Test %s should have file path", test.TestName) } assert.True(t, testNames["TestUserLogin"], "TestUserLogin should be detected as flaky") @@ -336,9 +388,10 @@ func TestAnalyzer_Run_Success(t *testing.T) { func TestAnalyzer_Run_LokiError(t *testing.T) { lokiClient := &MockLokiClient{err: fmt.Errorf("loki connection failed")} + gitClient := &MockGitClient{} fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() err := analyzer.Run(config) @@ -347,6 +400,24 @@ func TestAnalyzer_Run_LokiError(t *testing.T) { assert.Contains(t, err.Error(), "failed to fetch logs from Loki", "Error should mention Loki failure") } +func TestAnalyzer_Run_GitError(t *testing.T) { + logEntries := []RawLogEntry{ + {TestName: "TestExample", Branch: "main", WorkflowRunURL: "https://github.com/test/repo/actions/runs/1"}, + } + + lokiClient := &MockLokiClient{response: createTestLokiResponse(logEntries)} + gitClient := &MockGitClient{fileErr: fmt.Errorf("git command failed")} + fileSystem := &MockFileSystem{} + + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) + config := createTestConfig() + + err := analyzer.Run(config) + + require.Error(t, err, "Expected error from Git failure") + assert.Contains(t, err.Error(), "failed to find file paths", "Error should mention Git file path failure") +} + func TestAnalyzer_Run_EmptyLokiResponse(t *testing.T) { emptyResponse := &LokiResponse{ Status: "success", @@ -357,9 +428,10 @@ func TestAnalyzer_Run_EmptyLokiResponse(t *testing.T) { } lokiClient := &MockLokiClient{response: emptyResponse} + gitClient := &MockGitClient{} fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() err := analyzer.Run(config) @@ -384,9 +456,14 @@ func TestAnalyzer_Run_NonFlakyTests(t *testing.T) { } lokiClient := &MockLokiClient{response: createTestLokiResponse(logEntries)} + gitClient := &MockGitClient{ + testFiles: map[string]string{ + "TestFeatureOnly": "feature_test.go", + }, + } fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() err := analyzer.Run(config) @@ -444,9 +521,15 @@ func TestAnalyzer_Run_TopKLimit(t *testing.T) { } lokiClient := &MockLokiClient{response: createTestLokiResponse(logEntries)} + gitClient := &MockGitClient{ + testFiles: map[string]string{ + "TestA": "a_test.go", "TestB": "b_test.go", "TestC": "c_test.go", + "TestD": "d_test.go", "TestE": "e_test.go", + }, + } fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() config.TopK = 3 @@ -469,9 +552,12 @@ func TestAnalyzer_Run_NoProductionMode(t *testing.T) { } lokiClient := &MockLokiClient{response: createTestLokiResponse(logEntries)} + gitClient := &MockGitClient{ + testFiles: map[string]string{"TestUserLogin": "user_test.go"}, + } fileSystem := &MockFileSystem{} - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := createTestConfig() err := analyzer.Run(config) @@ -492,32 +578,44 @@ func TestFlakyTest_String(t *testing.T) { test: FlakyTest{ TestName: "TestUserLogin", TotalFailures: 3, + RecentCommits: []CommitInfo{ + {Author: "alice", Hash: "abc123", Title: "Fix login"}, + }, }, - expected: "TestUserLogin (3 total failures)", + expected: "TestUserLogin (3 total failures; recently changed by alice)", }, { name: "test with multiple authors", test: FlakyTest{ TestName: "TestPayment", TotalFailures: 5, + RecentCommits: []CommitInfo{ + {Author: "alice", Hash: "abc123", Title: "Fix payment"}, + {Author: "bob", Hash: "def456", Title: "Update payment logic"}, + }, }, - expected: "TestPayment (5 total failures)", + expected: "TestPayment (5 total failures; recently changed by alice, bob)", }, { name: "test with no commits", test: FlakyTest{ TestName: "TestDatabase", TotalFailures: 2, + RecentCommits: []CommitInfo{}, }, - expected: "TestDatabase (2 total failures)", + expected: "TestDatabase (2 total failures; recently changed by unknown)", }, { name: "test with unknown authors", test: FlakyTest{ TestName: "TestAPI", TotalFailures: 1, + RecentCommits: []CommitInfo{ + {Author: "unknown", Hash: "abc123", Title: "Some change"}, + {Author: "", Hash: "def456", Title: "Another change"}, + }, }, - expected: "TestAPI (1 total failures)", + expected: "TestAPI (1 total failures; recently changed by unknown)", }, } @@ -564,12 +662,36 @@ func TestAnalyzer_Run_GoldenFiles(t *testing.T) { name string lokiFile string expectedFile string + setupMocks func() *MockGitClient config func() Config }{ { name: "complex_scenario", lokiFile: "complex_loki_response.json", expectedFile: "complex_scenario.json", + setupMocks: func() *MockGitClient { + gitClient := &MockGitClient{ + testFiles: map[string]string{ + "TestDatabaseConnection": "internal/database/connection_test.go", + "TestUserAuthentication": "auth/user_test.go", + "TestPaymentProcessing": "payment/processor_test.go", + }, + authors: map[string][]CommitInfo{ + "TestDatabaseConnection": { + {Hash: "abc123def456", Author: "alice", Timestamp: mustParseTime("2024-01-15T10:30:00Z"), Title: "Optimize database connection pooling"}, + {Hash: "789ghi012jkl", Author: "bob", Timestamp: mustParseTime("2024-01-10T14:22:00Z"), Title: "Add connection timeout handling"}, + }, + "TestUserAuthentication": { + {Hash: "345mno678pqr", Author: "charlie", Timestamp: mustParseTime("2024-01-12T09:15:00Z"), Title: "Implement OAuth2 authentication flow"}, + }, + "TestPaymentProcessing": { + {Hash: "901stu234vwx", Author: "dave", Timestamp: mustParseTime("2024-01-08T16:45:00Z"), Title: "Add Stripe payment integration"}, + {Hash: "567yza890bcd", Author: "eve", Timestamp: mustParseTime("2024-01-05T11:30:00Z"), Title: "Refactor payment processing logic"}, + }, + }, + } + return gitClient + }, config: func() Config { config := createTestConfig() config.TopK = 10 // Don't limit for this test @@ -580,13 +702,27 @@ func TestAnalyzer_Run_GoldenFiles(t *testing.T) { name: "empty_scenario", lokiFile: "", expectedFile: "empty_scenario.json", - config: createTestConfig, + setupMocks: func() *MockGitClient { + return &MockGitClient{} + }, + config: createTestConfig, }, { name: "single_test_scenario", lokiFile: "", expectedFile: "single_test_scenario.json", - config: createTestConfig, + setupMocks: func() *MockGitClient { + gitClient := &MockGitClient{ + testFiles: map[string]string{ + "TestLoginFlow": "handlers/login_test.go", + }, + authors: map[string][]CommitInfo{ + "TestLoginFlow": {}, // No recent commits + }, + } + return gitClient + }, + config: createTestConfig, }, } @@ -620,10 +756,11 @@ func TestAnalyzer_Run_GoldenFiles(t *testing.T) { // Setup mocks lokiClient := &MockLokiClient{response: lokiResponse} + gitClient := tt.setupMocks() fileSystem := &MockFileSystem{} // Run analysis - analyzer := NewTestFailureAnalyzer(lokiClient, fileSystem) + analyzer := NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem) config := tt.config() err := analyzer.Run(config) @@ -646,6 +783,11 @@ func TestAnalyzer_Run_GoldenFiles(t *testing.T) { // Compare results (ignoring report_path which will be different) actual.ReportPath = expected.ReportPath + // For time-sensitive tests, normalize timestamps + if tt.name == "complex_scenario" { + normalizeTimestamps(&actual, &expected) + } + // Normalize workflow order for comparison normalizeWorkflowOrder(&actual, &expected) @@ -673,6 +815,24 @@ func mustParseTime(timeStr string) time.Time { return t } +func normalizeTimestamps(actual, expected *FailuresReport) { + // For golden file tests, we normalize timestamps to expected values + // since actual git log times will differ from test data + for i, actualTest := range actual.FlakyTests { + for j := range expected.FlakyTests { + if expected.FlakyTests[j].TestName == actualTest.TestName { + // Copy expected timestamps to actual for comparison + if len(expected.FlakyTests[j].RecentCommits) == len(actualTest.RecentCommits) { + for k := range actualTest.RecentCommits { + actual.FlakyTests[i].RecentCommits[k].Timestamp = expected.FlakyTests[j].RecentCommits[k].Timestamp + } + } + break + } + } + } +} + func normalizeWorkflowOrder(actual, expected *FailuresReport) { // Sort workflow URLs to make comparison order-independent for i, actualTest := range actual.FlakyTests { diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/testdata/complex_scenario.json b/actions/go-flaky-tests/cmd/go-flaky-tests/testdata/complex_scenario.json index 0548514ca..3e5f352af 100644 --- a/actions/go-flaky-tests/cmd/go-flaky-tests/testdata/complex_scenario.json +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/testdata/complex_scenario.json @@ -1,10 +1,11 @@ { "test_count": 3, - "analysis_summary": "Found 3 flaky tests. Most common tests: TestDatabaseConnection (3 total failures), TestPaymentProcessing (2 total failures), TestUserAuthentication (2 total failures)", + "analysis_summary": "Found 3 flaky tests. Most common tests: TestDatabaseConnection (3 total failures; recently changed by alice, bob), TestPaymentProcessing (2 total failures; recently changed by dave, eve), TestUserAuthentication (2 total failures; recently changed by charlie)", "report_path": "test-failure-analysis.json", "flaky_tests": [ { "test_name": "TestDatabaseConnection", + "file_path": "internal/database/connection_test.go", "total_failures": 3, "branch_counts": { "feature/db-optimization": 1, @@ -14,10 +15,25 @@ { "run_url": "https://github.com/test/repo/actions/runs/101" }, { "run_url": "https://github.com/test/repo/actions/runs/102" }, { "run_url": "https://github.com/test/repo/actions/runs/103" } + ], + "recent_commits": [ + { + "hash": "abc123def456", + "author": "alice", + "timestamp": "2024-01-15T10:30:00Z", + "title": "Optimize database connection pooling" + }, + { + "hash": "789ghi012jkl", + "author": "bob", + "timestamp": "2024-01-10T14:22:00Z", + "title": "Add connection timeout handling" + } ] }, { "test_name": "TestPaymentProcessing", + "file_path": "payment/processor_test.go", "total_failures": 2, "branch_counts": { "feature/stripe-integration": 1, @@ -26,10 +42,25 @@ "example_workflows": [ { "run_url": "https://github.com/test/repo/actions/runs/301" }, { "run_url": "https://github.com/test/repo/actions/runs/302" } + ], + "recent_commits": [ + { + "hash": "901stu234vwx", + "author": "dave", + "timestamp": "2024-01-08T16:45:00Z", + "title": "Add Stripe payment integration" + }, + { + "hash": "567yza890bcd", + "author": "eve", + "timestamp": "2024-01-05T11:30:00Z", + "title": "Refactor payment processing logic" + } ] }, { "test_name": "TestUserAuthentication", + "file_path": "auth/user_test.go", "total_failures": 2, "branch_counts": { "feature/oauth": 1, @@ -38,6 +69,14 @@ "example_workflows": [ { "run_url": "https://github.com/test/repo/actions/runs/201" }, { "run_url": "https://github.com/test/repo/actions/runs/202" } + ], + "recent_commits": [ + { + "hash": "345mno678pqr", + "author": "charlie", + "timestamp": "2024-01-12T09:15:00Z", + "title": "Implement OAuth2 authentication flow" + } ] } ] diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/testdata/single_test_scenario.json b/actions/go-flaky-tests/cmd/go-flaky-tests/testdata/single_test_scenario.json index a99197cd6..03a53c951 100644 --- a/actions/go-flaky-tests/cmd/go-flaky-tests/testdata/single_test_scenario.json +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/testdata/single_test_scenario.json @@ -1,10 +1,11 @@ { "test_count": 1, - "analysis_summary": "Found 1 flaky tests. Most common tests: TestLoginFlow (2 total failures)", + "analysis_summary": "Found 1 flaky tests. Most common tests: TestLoginFlow (2 total failures; recently changed by unknown)", "report_path": "test-failure-analysis.json", "flaky_tests": [ { "test_name": "TestLoginFlow", + "file_path": "handlers/login_test.go", "total_failures": 2, "branch_counts": { "main": 2 @@ -12,7 +13,8 @@ "example_workflows": [ { "run_url": "https://github.com/test/repo/actions/runs/401" }, { "run_url": "https://github.com/test/repo/actions/runs/402" } - ] + ], + "recent_commits": [] } ] } From af952697d3dcb2502e6d113c94edee29b9fa14e9 Mon Sep 17 00:00:00 2001 From: Dimitar Dimitrov Date: Thu, 5 Jun 2025 19:58:28 +0200 Subject: [PATCH 6/8] Pull author resolution out of git client --- .../cmd/go-flaky-tests/analyzer.go | 4 ++-- .../go-flaky-tests/cmd/go-flaky-tests/git.go | 22 +++++++++---------- .../cmd/go-flaky-tests/main_test.go | 2 +- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go b/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go index a0b8924c5..3d636b157 100644 --- a/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go @@ -16,7 +16,7 @@ type FileSystem interface { type GitClient interface { FindTestFile(testName string) (string, error) - GetFileAuthors(filePath, testName string) ([]CommitInfo, error) + TestCommits(filePath, testName string) ([]CommitInfo, error) } type TestFailureAnalyzer struct { @@ -216,7 +216,7 @@ func (t *TestFailureAnalyzer) findFilePaths(flakyTests []FlakyTest) error { func (t *TestFailureAnalyzer) findTestAuthors(flakyTests []FlakyTest) error { for i, test := range flakyTests { - commits, err := t.gitClient.GetFileAuthors(test.FilePath, test.TestName) + commits, err := t.gitClient.TestCommits(test.FilePath, test.TestName) if err != nil { return fmt.Errorf("failed to get authors for test %s in %s: %w", test.TestName, test.FilePath, err) } diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/git.go b/actions/go-flaky-tests/cmd/go-flaky-tests/git.go index c72591727..f7ebe162f 100644 --- a/actions/go-flaky-tests/cmd/go-flaky-tests/git.go +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/git.go @@ -21,7 +21,7 @@ func (g *DefaultGitClient) FindTestFile(testName string) (string, error) { return findTestFilePath(g.config.RepositoryDirectory, testName) } -func (g *DefaultGitClient) GetFileAuthors(filePath, testName string) ([]CommitInfo, error) { +func (g *DefaultGitClient) TestCommits(filePath, testName string) ([]CommitInfo, error) { return getFileAuthors(g.config, filePath, testName) } @@ -56,7 +56,8 @@ func getFileAuthors(config Config, filePath, testName string) ([]CommitInfo, err } func getFileAuthorsWithClient(repoDir, filePath, testName string) ([]CommitInfo, error) { - cmd := exec.Command("git", "log", "-3", "-L", fmt.Sprintf(":%s:%s", testName, filePath), "--pretty=format:%H|%ct|%s", "-s") + // Get 10 commits, because some of them might just be only bots. + cmd := exec.Command("git", "log", "-10", "-L", fmt.Sprintf(":%s:%s", testName, filePath), "--pretty=format:%H|%ct|%s|%an", "-s") cmd.Dir = repoDir result, err := cmd.Output() @@ -75,18 +76,15 @@ func getFileAuthorsWithClient(repoDir, filePath, testName string) ([]CommitInfo, sixMonthsAgo := time.Now().AddDate(0, -6, 0) for _, line := range lines { - parts := strings.SplitN(strings.TrimSpace(line), "|", 3) - if len(parts) != 3 { + parts := strings.SplitN(strings.TrimSpace(line), "|", 4) + if len(parts) != 4 { return nil, fmt.Errorf("invalid git log format for test %s in %s: %s", testName, filePath, line) } hash := parts[0] timestampStr := parts[1] title := parts[2] - - if hash == "" { - continue - } + author := parts[3] var timestamp time.Time if timestampUnix, err := strconv.ParseInt(timestampStr, 10, 64); err == nil { @@ -97,19 +95,19 @@ func getFileAuthorsWithClient(repoDir, filePath, testName string) ([]CommitInfo, continue } - username := "unknown" // Will be resolved via GitHub in PR3 - - if strings.HasSuffix(username, "[bot]") { + if strings.HasSuffix(author, "[bot]") { continue } commitInfo := CommitInfo{ Hash: hash, - Author: username, Timestamp: timestamp, Title: title, } commits = append(commits, commitInfo) + if len(commits) >= 3 { + break + } } return commits, nil diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/main_test.go b/actions/go-flaky-tests/cmd/go-flaky-tests/main_test.go index 2987a4c7f..ca11072eb 100644 --- a/actions/go-flaky-tests/cmd/go-flaky-tests/main_test.go +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/main_test.go @@ -45,7 +45,7 @@ func (m *MockGitClient) FindTestFile(testName string) (string, error) { return "", fmt.Errorf("test file not found for %s", testName) } -func (m *MockGitClient) GetFileAuthors(filePath, testName string) ([]CommitInfo, error) { +func (m *MockGitClient) TestCommits(filePath, testName string) ([]CommitInfo, error) { if m.authorErr != nil { return nil, m.authorErr } From a579ef379e5fd0ca80915f4f8a55a9122c8045c4 Mon Sep 17 00:00:00 2001 From: Dimitar Dimitrov Date: Thu, 5 Jun 2025 21:02:44 +0200 Subject: [PATCH 7/8] Revert "Remove repository-directory parameter from local script" This reverts commit 085c861742c3c1aaf94ac2b6b2fda05e73a9ff25. --- actions/go-flaky-tests/run-local.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/actions/go-flaky-tests/run-local.sh b/actions/go-flaky-tests/run-local.sh index 618a237fa..68cce00f9 100644 --- a/actions/go-flaky-tests/run-local.sh +++ b/actions/go-flaky-tests/run-local.sh @@ -21,12 +21,13 @@ usage() { echo " --loki-password PASS Password for Loki authentication" echo " --repository REPO Repository name in 'owner/repo' format (required)" echo " --time-range RANGE Time range for query (default: ${DEFAULT_TIME_RANGE})" + echo " --repository-directory DIR Repository directory (default: current directory)" echo " --skip-posting-issues BOOL Skip creating GitHub issues (default: ${DEFAULT_SKIP_POSTING_ISSUES})" echo " --top-k NUM Number of top flaky tests to analyze (default: ${DEFAULT_TOP_K})" echo "" echo "Environment variables:" echo " LOKI_URL, LOKI_USERNAME, LOKI_PASSWORD, REPOSITORY, TIME_RANGE," - echo " SKIP_POSTING_ISSUES, TOP_K" + echo " REPOSITORY_DIRECTORY, SKIP_POSTING_ISSUES, TOP_K" echo "" echo "Example:" echo " $0 --loki-url http://localhost:3100 --repository myorg/myrepo --time-range 7d" @@ -59,6 +60,10 @@ while [[ $# -gt 0 ]]; do TIME_RANGE="$2" shift 2 ;; + --repository-directory) + REPOSITORY_DIRECTORY="$2" + shift 2 + ;; --skip-posting-issues) SKIP_POSTING_ISSUES="$2" shift 2 @@ -78,6 +83,7 @@ done TIME_RANGE="${TIME_RANGE:-$DEFAULT_TIME_RANGE}" TOP_K="${TOP_K:-$DEFAULT_TOP_K}" SKIP_POSTING_ISSUES="${SKIP_POSTING_ISSUES:-$DEFAULT_SKIP_POSTING_ISSUES}" +REPOSITORY_DIRECTORY="${REPOSITORY_DIRECTORY:-$(pwd)}" # Validate required parameters if [[ -z "$LOKI_URL" ]]; then @@ -96,12 +102,14 @@ export LOKI_USERNAME export LOKI_PASSWORD export REPOSITORY export TIME_RANGE +export REPOSITORY_DIRECTORY export SKIP_POSTING_ISSUES export TOP_K echo "๐Ÿ”ง Running go-flaky-tests locally..." echo "๐Ÿ“Š Repository: $REPOSITORY" echo "โฐ Time range: $TIME_RANGE" +echo "๐Ÿ“ Repository directory: $REPOSITORY_DIRECTORY" echo "๐Ÿ” Top K tests: $TOP_K" echo "๐Ÿƒ Dry run mode: $SKIP_POSTING_ISSUES" echo "" From c096b12dbc661f91f2bd11cb36f154be5b47f6cd Mon Sep 17 00:00:00 2001 From: Dimitar Dimitrov Date: Wed, 25 Jun 2025 13:12:46 +0200 Subject: [PATCH 8/8] Remove redundant author printing --- actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go b/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go index 3d636b157..d4b3343f9 100644 --- a/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go +++ b/actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go @@ -227,9 +227,6 @@ func (t *TestFailureAnalyzer) findTestAuthors(flakyTests []FlakyTest) error { for _, commit := range commits { authors = append(authors, commit.Author) } - log.Printf("๐Ÿ‘ค %s: %s", test.TestName, strings.Join(authors, ", ")) - } else { - log.Printf("๐Ÿ‘ค %s: no commits found", test.TestName) } } return nil