Skip to content

Commit f2c0b02

Browse files
feat(go-flaky-tests): add github issue management (#1276)
* feat: add GitHub issue management functionality Complete the flaky test action with full GitHub integration: - Add GitHub client implementation for issue management - Create and update GitHub issues for flaky tests - Search for existing issues to avoid duplicates - Automatically reopen closed issues when tests fail again - Rich issue templates with investigation guides and author mentions - GitHub CLI integration for all operations - Add github-token and skip-posting-issues config options Builds on PR2's Git author tracking with complete GitHub workflow. This completes the full analyze-test-failures action functionality. * Format Go code with gofmt * Revert "Remove PR3-related features from documentation" This reverts commit 97c3804. * Adapt tests * Pull author resolution out of git client # Conflicts: # actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go * Make time range configurable * Fix template reference to use go-flaky-tests instead of analyze-test-failures * Revert "Remove github-token parameter from local script" This reverts commit d660967. * Fewer emojis * Tag authors * Include job name and attempt * Mention authors * Fix linter * Add CODEOWNERS
1 parent d0243c6 commit f2c0b02

File tree

12 files changed

+610
-50
lines changed

12 files changed

+610
-50
lines changed

CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@
1919

2020
# Platform Infrasec
2121
/actions/create-github-app-token @grafana/platform-infrasec
22+
23+
# Mimir
24+
actions/go-flaky-tests/cmd/go-flaky-tests @grafana/mimir-maintainers

actions/go-flaky-tests/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@
77
- Initial implementation of flaky test analysis action
88
- Loki integration for fetching test failure logs
99
- Git history analysis to find test authors
10+
- GitHub issue creation and management for flaky tests
11+
- Dry run mode for testing without creating issues
1012
- Comprehensive test suite with golden file testing
1113

1214
### Features
1315

1416
- **Loki Log Analysis**: Fetches and parses test failure logs using LogQL
1517
- **Flaky Test Detection**: Identifies tests that fail inconsistently across branches
1618
- **Git Author Tracking**: Finds recent commits that modified flaky tests
19+
- **GitHub Integration**: Creates and updates issues with detailed test information
1720
- **Configurable Limits**: Top-K filtering to focus on most problematic tests
21+
- **Rich Issue Templates**: Detailed issue descriptions with investigation guidance

actions/go-flaky-tests/README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# Go Flaky Tests
22

3-
A GitHub Action that detects and analyzes flaky Go tests by fetching logs from Loki and finding their authors.
3+
A GitHub Action that detects and analyzes flaky Go tests by fetching logs from Loki, finding their authors, and creating GitHub issues to track them.
44

55
## Features
66

77
- **Loki Integration**: Fetches test failure logs from Loki using LogQL queries
88
- **Flaky Test Detection**: Identifies tests that fail inconsistently across different branches
99
- **Git History Analysis**: Finds test files and extracts recent commit authors
10+
- **GitHub Issue Management**: Creates and updates GitHub issues for flaky tests
11+
- **Dry Run Mode**: Preview functionality without creating actual issues
1012

1113
## Usage
1214

@@ -31,6 +33,7 @@ jobs:
3133
loki-password: ${{ secrets.LOKI_PASSWORD }}
3234
repository: ${{ github.repository }}
3335
time-range: "7d"
36+
skip-posting-issues: "false"
3437
top-k: "5"
3538
```
3639
@@ -44,6 +47,8 @@ jobs:
4447
| `repository` | Repository name in 'owner/repo' format | ✅ | - |
4548
| `time-range` | Time range for the query (e.g., '1h', '24h', '7d') | ❌ | `1h` |
4649
| `repository-directory` | Relative path to the directory with a git repository | ❌ | `${{ github.workspace }}` |
50+
| `github-token` | GitHub token for repository access | ❌ | `${{ github.token }}` |
51+
| `skip-posting-issues` | Skip creating/updating GitHub issues (dry-run mode) | ❌ | `true` |
4752
| `top-k` | Include only the top K flaky tests by distinct branches count | ❌ | `3` |
4853

4954
## Outputs
@@ -61,6 +66,8 @@ jobs:
6166
3. **Detect Flaky Tests**: Identifies tests that fail on multiple branches or multiple times on main/master
6267
4. **Find Test Files**: Locates test files in the repository using grep
6368
5. **Extract Authors**: Uses `git log -L` to find recent commits that modified each test
69+
6. **Resolve Usernames**: Looks up GitHub usernames for commit hashes
70+
7. **Create Issues**: Creates or updates GitHub issues with flaky test information
6471

6572
## Flaky Test Detection Logic
6673

@@ -81,15 +88,32 @@ export LOKI_URL="your-loki-url"
8188
export REPOSITORY="owner/repo"
8289
export TIME_RANGE="24h"
8390
export REPOSITORY_DIRECTORY="."
91+
export SKIP_POSTING_ISSUES="true"
8492
8593
# Run the analysis
8694
./run-local.sh
8795
```
8896

97+
## GitHub Issue Format
98+
99+
The action creates GitHub issues with:
100+
101+
- **Title**: `Flaky test: TestName`
102+
- **Labels**: `flaky-test`
103+
- **Body**: Detailed information about the test including:
104+
- File path and test name
105+
- Investigation tips and next steps
106+
- Recent failure count and affected branches
107+
- Recent authors who modified the test
108+
- Links to failed workflow runs
109+
110+
For existing issues, the action adds comments with updated failure information.
111+
89112
## Requirements
90113

91114
- Go 1.22 or later
92115
- Git repository with test files
116+
- GitHub CLI (automatically available in GitHub Actions)
93117
- Access to Loki instance with test failure logs
94118

95119
## Output Format

actions/go-flaky-tests/action.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ inputs:
2323
description: "Relative path to the directory with a git repository"
2424
required: false
2525
default: ${{ github.workspace }}
26+
github-token:
27+
description: "GitHub token for repository access"
28+
required: false
29+
default: ${{ github.token }}
30+
skip-posting-issues:
31+
description: "Skip creating/updating GitHub issues (dry-run mode)"
32+
required: false
33+
default: "true"
2634
top-k:
2735
description: "Include only the top K flaky tests by distinct branches count in analysis"
2836
required: false
@@ -36,6 +44,13 @@ runs:
3644
with:
3745
go-version: "1.25"
3846

47+
- name: Setup GitHub CLI
48+
run: |
49+
# GitHub CLI is pre-installed on GitHub Actions runners
50+
# Just verify it's available and authenticated
51+
gh --version
52+
shell: bash
53+
3954
- name: Build and run analyzer
4055
shell: bash
4156
run: |
@@ -48,5 +63,7 @@ runs:
4863
LOKI_PASSWORD: ${{ inputs.loki-password }}
4964
REPOSITORY: ${{ inputs.repository }}
5065
TIME_RANGE: ${{ inputs.time-range }}
66+
GITHUB_TOKEN: ${{ inputs.github-token }}
5167
REPOSITORY_DIRECTORY: ${{ inputs.repository-directory }}
68+
SKIP_POSTING_ISSUES: ${{ inputs.skip-posting-issues }}
5269
TOP_K: ${{ inputs.top-k }}

actions/go-flaky-tests/aggregate

10 MB
Binary file not shown.

actions/go-flaky-tests/cmd/go-flaky-tests/analyzer.go

Lines changed: 101 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,19 @@ type GitClient interface {
1919
TestCommits(filePath, testName string) ([]CommitInfo, error)
2020
}
2121

22+
type GitHubClient interface {
23+
GetUsernameForCommit(commitHash string) (string, error)
24+
CreateOrUpdateIssue(test FlakyTest) error
25+
SearchForExistingIssue(issueTitle string) (string, error)
26+
AddCommentToIssue(issueURL string, test FlakyTest) error
27+
ReopenIssue(issueURL string) error
28+
}
29+
2230
type TestFailureAnalyzer struct {
23-
lokiClient LokiClient
24-
gitClient GitClient
25-
fileSystem FileSystem
31+
lokiClient LokiClient
32+
gitClient GitClient
33+
githubClient GitHubClient
34+
fileSystem FileSystem
2635
}
2736

2837
type CommitInfo struct {
@@ -74,20 +83,22 @@ func (fs *DefaultFileSystem) WriteFile(filename string, data []byte, perm os.Fil
7483
return os.WriteFile(filename, data, perm)
7584
}
7685

77-
func NewTestFailureAnalyzer(loki LokiClient, git GitClient, fs FileSystem) *TestFailureAnalyzer {
86+
func NewTestFailureAnalyzer(loki LokiClient, git GitClient, github GitHubClient, fs FileSystem) *TestFailureAnalyzer {
7887
return &TestFailureAnalyzer{
79-
lokiClient: loki,
80-
gitClient: git,
81-
fileSystem: fs,
88+
lokiClient: loki,
89+
gitClient: git,
90+
githubClient: github,
91+
fileSystem: fs,
8292
}
8393
}
8494

8595
func NewDefaultTestFailureAnalyzer(config Config) *TestFailureAnalyzer {
8696
lokiClient := NewDefaultLokiClient(config)
8797
gitClient := NewDefaultGitClient(config)
98+
githubClient := NewDefaultGitHubClient(config)
8899
fileSystem := &DefaultFileSystem{}
89100

90-
return NewTestFailureAnalyzer(lokiClient, gitClient, fileSystem)
101+
return NewTestFailureAnalyzer(lokiClient, gitClient, githubClient, fileSystem)
91102
}
92103

93104
func (t *TestFailureAnalyzer) AnalyzeFailures(config Config) (*FailuresReport, error) {
@@ -165,8 +176,26 @@ func (t *TestFailureAnalyzer) AnalyzeFailures(config Config) (*FailuresReport, e
165176
}
166177

167178
func (t *TestFailureAnalyzer) ActionReport(report *FailuresReport, config Config) error {
168-
log.Printf("📝 Report generated successfully - no additional actions in this version")
169-
log.Printf("✅ Analysis complete!")
179+
if report == nil || len(report.FlakyTests) == 0 {
180+
log.Printf("📝 No flaky tests to enact - skipping GitHub issue creation")
181+
return nil
182+
}
183+
184+
if config.SkipPostingIssues {
185+
log.Printf("🔍 Dry run mode: Generating issue previews...")
186+
err := t.previewIssuesForFlakyTests(report.FlakyTests, config)
187+
if err != nil {
188+
return fmt.Errorf("failed to preview GitHub issues: %w", err)
189+
}
190+
} else {
191+
log.Printf("📝 Creating GitHub issues for flaky tests...")
192+
err := t.createIssuesForFlakyTests(report.FlakyTests)
193+
if err != nil {
194+
return fmt.Errorf("failed to create GitHub issues: %w", err)
195+
}
196+
}
197+
198+
log.Printf("✅ Report enactment complete!")
170199
return nil
171200
}
172201

@@ -222,16 +251,74 @@ func (t *TestFailureAnalyzer) findTestAuthors(flakyTests []FlakyTest) error {
222251
}
223252
flakyTests[i].RecentCommits = commits
224253

225-
if len(commits) > 0 {
226-
var authors []string
227-
for _, commit := range commits {
228-
authors = append(authors, commit.Author)
254+
var authors []string
255+
for commitIdx, commit := range commits {
256+
authors = append(authors, commit.Author)
257+
commits[commitIdx].Author, err = t.githubClient.GetUsernameForCommit(commit.Hash)
258+
if err != nil {
259+
return fmt.Errorf("failed to get author for test %s in %s: %w", test.TestName, test.FilePath, err)
229260
}
230261
}
231262
}
232263
return nil
233264
}
234265

266+
func (t *TestFailureAnalyzer) createIssuesForFlakyTests(flakyTests []FlakyTest) error {
267+
for _, test := range flakyTests {
268+
err := t.githubClient.CreateOrUpdateIssue(test)
269+
if err != nil {
270+
log.Printf("Warning: failed to create issue for test %s: %v", test.TestName, err)
271+
}
272+
}
273+
return nil
274+
}
275+
276+
func (t *TestFailureAnalyzer) previewIssuesForFlakyTests(flakyTests []FlakyTest, config Config) error {
277+
for _, test := range flakyTests {
278+
err := previewIssueForTest(test, config)
279+
if err != nil {
280+
log.Printf("Warning: failed to preview issue for test %s: %v", test.TestName, err)
281+
}
282+
}
283+
return nil
284+
}
285+
286+
func previewIssueForTest(test FlakyTest, config Config) error {
287+
issueTitle := fmt.Sprintf("Flaky %s", test.TestName)
288+
289+
log.Printf("📄 Would create issue for %s:", test.TestName)
290+
log.Printf("Title: %s", issueTitle)
291+
log.Printf("Labels: flaky-test")
292+
log.Printf("")
293+
294+
// Generate the actual markdown content that would be used
295+
issueBody, err := generateInitialIssueBody(test)
296+
if err != nil {
297+
log.Printf("Warning: failed to generate issue body preview: %v", err)
298+
return nil
299+
}
300+
301+
commentBody, err := generateCommentBody(test, config)
302+
if err != nil {
303+
log.Printf("Warning: failed to generate comment body preview: %v", err)
304+
return nil
305+
}
306+
307+
log.Printf("Initial Issue Body Markdown:")
308+
log.Printf("────────────────────────────────────────────────────────────────────────")
309+
log.Printf("%s", issueBody)
310+
log.Printf("────────────────────────────────────────────────────────────────────────")
311+
log.Printf("")
312+
313+
log.Printf("Comment Body Markdown:")
314+
log.Printf("────────────────────────────────────────────────────────────────────────")
315+
log.Printf("%s", commentBody)
316+
log.Printf("────────────────────────────────────────────────────────────────────────")
317+
log.Printf("")
318+
319+
return nil
320+
}
321+
235322
func generateSummary(flakyTests []FlakyTest) string {
236323
if len(flakyTests) == 0 {
237324
return "No flaky tests found in the specified time range."

actions/go-flaky-tests/cmd/go-flaky-tests/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type Config struct {
1212
Repository string
1313
TimeRange string
1414
RepositoryDirectory string
15+
SkipPostingIssues bool
1516
TopK int
1617
}
1718

@@ -23,6 +24,7 @@ func getConfigFromEnv() Config {
2324
Repository: os.Getenv("REPOSITORY"),
2425
TimeRange: getEnvWithDefault("TIME_RANGE", "24h"),
2526
RepositoryDirectory: getEnvWithDefault("REPOSITORY_DIRECTORY", "."),
27+
SkipPostingIssues: getBoolEnvWithDefault("SKIP_POSTING_ISSUES", true),
2628
TopK: getIntEnvWithDefault("TOP_K", 3),
2729
}
2830
}
@@ -34,6 +36,13 @@ func getEnvWithDefault(key, defaultValue string) string {
3436
return defaultValue
3537
}
3638

39+
func getBoolEnvWithDefault(key string, defaultValue bool) bool {
40+
if value := os.Getenv(key); value != "" {
41+
return value == "true" || value == "1"
42+
}
43+
return defaultValue
44+
}
45+
3746
func getIntEnvWithDefault(key string, defaultValue int) int {
3847
if value := os.Getenv(key); value != "" {
3948
if intValue, err := strconv.Atoi(value); err == nil {

actions/go-flaky-tests/cmd/go-flaky-tests/git.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@ func findTestFilePath(repoDir, testName string) (string, error) {
5252
}
5353

5454
func getFileAuthors(config Config, filePath, testName string) ([]CommitInfo, error) {
55-
return getFileAuthorsWithClient(config.RepositoryDirectory, filePath, testName)
55+
githubClient := NewDefaultGitHubClient(config)
56+
return getFileAuthorsWithClient(config.RepositoryDirectory, filePath, testName, githubClient)
5657
}
5758

58-
func getFileAuthorsWithClient(repoDir, filePath, testName string) ([]CommitInfo, error) {
59+
func getFileAuthorsWithClient(repoDir, filePath, testName string, githubClient GitHubClient) ([]CommitInfo, error) {
5960
// Get 10 commits, because some of them might just be only bots.
6061
cmd := exec.Command("git", "log", "-10", "-L", fmt.Sprintf(":%s:%s", testName, filePath), "--pretty=format:%H|%ct|%s|%an", "-s")
6162
cmd.Dir = repoDir

0 commit comments

Comments
 (0)