Skip to content

Commit 1f1619b

Browse files
ci: integrate Trunk into CI workflows to identify flaky tests (#9245)
This PR adds functionality to the test framework to generate test results in the form of JUnit XML. It also introduces a new step in the workflow to upload these test results to Trunk for flaky test detection.
1 parent 6207892 commit 1f1619b

File tree

8 files changed

+146
-6
lines changed

8 files changed

+146
-6
lines changed

.github/workflows/ci-dgraph-code-coverage.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ jobs:
2323
#!/bin/bash
2424
# build the test binary
2525
cd t; go build .
26+
- name: Install gotestsum
27+
run: go install gotest.tools/gotestsum@latest
2628
- name: Clean Up Environment
2729
run: |
2830
#!/bin/bash

.github/workflows/ci-dgraph-core-tests.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424
go-version-file: go.mod
2525
- name: Install protobuf-compiler
2626
run: sudo apt update && sudo apt install -y protobuf-compiler
27+
- name: Install gotestsum
28+
run: go install gotest.tools/gotestsum@latest
2729
- name: Check protobuf
2830
run: |
2931
cd ./protos
@@ -57,3 +59,11 @@ jobs:
5759
./t -r
5860
# sleep
5961
sleep 5
62+
- name: Upload Test Results
63+
if: always() # Upload the results even if the tests fail
64+
continue-on-error: true # don't fail this job if the upload fails
65+
uses: trunk-io/analytics-uploader@main
66+
with:
67+
junit-paths: "./test-results.xml"
68+
org-slug: hypermode
69+
token: ${{ secrets.TRUNK_TOKEN }}

.github/workflows/ci-dgraph-ldbc-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ jobs:
2525
go-version-file: go.mod
2626
- name: Make Linux Build and Docker Image
2727
run: make docker-image
28+
- name: Install gotestsum
29+
run: go install gotest.tools/gotestsum@latest
2830
- name: Build Test Binary
2931
run : |
3032
#!/bin/bash

.github/workflows/ci-dgraph-load-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424
go-version-file: go.mod
2525
- name: Make Linux Build and Docker Image
2626
run: make docker-image # this internally builds dgraph binary
27+
- name: Install gotestsum
28+
run: go install gotest.tools/gotestsum@latest
2729
- name: Build Test Binary
2830
run: |
2931
#!/bin/bash

.github/workflows/ci-dgraph-systest-tests.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ jobs:
3232
git diff --exit-code -- .
3333
- name: Make Linux Build and Docker Image
3434
run: make docker-image
35+
- name: Install gotestsum
36+
run: go install gotest.tools/gotestsum@latest
3537
- name: Build Test Binary
3638
run: |
3739
#!/bin/bash
@@ -57,3 +59,11 @@ jobs:
5759
./t -r
5860
# sleep
5961
sleep 5
62+
- name: Upload Test Results
63+
if: always() # Upload the results even if the tests fail
64+
continue-on-error: true # don't fail this job if the upload fails
65+
uses: trunk-io/analytics-uploader@main
66+
with:
67+
junit-paths: "./test-results.xml"
68+
org-slug: hypermode
69+
token: ${{ secrets.TRUNK_TOKEN }}

.github/workflows/ci-dgraph-tests-arm64.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ jobs:
3030
go mod tidy
3131
make regenerate
3232
git diff --exit-code -- .
33+
- name: Install gotestsum
34+
run: go install gotest.tools/gotestsum@latest
3335
- name: Make Linux Build and Docker Image
3436
run: make docker-image # this internally builds dgraph binary
3537
- name: Build Test Binary

.github/workflows/ci-dgraph-vector-tests.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ jobs:
3232
git diff --exit-code -- .
3333
- name: Make Linux Build and Docker Image
3434
run: make docker-image
35+
- name: Install gotestsum
36+
run: go install gotest.tools/gotestsum@latest
3537
- name: Build Test Binary
3638
run: |
3739
#!/bin/bash
@@ -57,3 +59,11 @@ jobs:
5759
./t -r
5860
# sleep
5961
sleep 5
62+
- name: Upload Test Results
63+
if: always() # Upload the results even if the tests fail
64+
continue-on-error: true # don't fail this job if the upload fails
65+
uses: trunk-io/analytics-uploader@main
66+
with:
67+
junit-paths: "./test-results.xml"
68+
org-slug: hypermode
69+
token: ${{ secrets.TRUNK_TOKEN }}

t/t.go

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"bufio"
2121
"bytes"
2222
"context"
23+
"encoding/xml"
2324
"fmt"
2425
"log"
2526
"math/rand"
@@ -102,6 +103,39 @@ var (
102103
runCoverage = pflag.Bool("coverage", false, "Set true to calculate test coverage")
103104
)
104105

106+
type TestSuites struct {
107+
XMLName xml.Name `xml:"testsuites"`
108+
Tests int `xml:"tests,attr"`
109+
Failures int `xml:"failures,attr"`
110+
Errors int `xml:"errors,attr"`
111+
Time float64 `xml:"time,attr"`
112+
TestSuites []TestSuite `xml:"testsuite"`
113+
}
114+
115+
type TestSuite struct {
116+
XMLName xml.Name `xml:"testsuite"`
117+
Name string `xml:"name,attr"`
118+
Tests int `xml:"tests,attr"`
119+
Failures int `xml:"failures,attr"`
120+
Time float64 `xml:"time,attr"`
121+
Timestamp string `xml:"timestamp,attr,omitempty"`
122+
TestCases []TestCase `xml:"testcase"`
123+
Properties []Property `xml:"properties>property,omitempty"`
124+
}
125+
126+
type TestCase struct {
127+
XMLName xml.Name `xml:"testcase"`
128+
ClassName string `xml:"classname,attr"`
129+
Name string `xml:"name,attr"`
130+
Time float64 `xml:"time,attr"`
131+
}
132+
133+
type Property struct {
134+
XMLName xml.Name `xml:"property"`
135+
Name string `xml:"name,attr"`
136+
Value string `xml:"value,attr"`
137+
}
138+
105139
func commandWithContext(ctx context.Context, args ...string) *exec.Cmd {
106140
cmd := exec.CommandContext(ctx, args[0], args[1:]...) //nolint:gosec
107141
cmd.Stdout = os.Stdout
@@ -265,8 +299,53 @@ func stopCluster(composeFile, prefix string, wg *sync.WaitGroup, err error) {
265299
}()
266300
}
267301

268-
func runTestsFor(ctx context.Context, pkg, prefix string) error {
269-
var args = []string{"go", "test", "-failfast", "-v", "-tags=integration"}
302+
func combineJUnitXML(outputFile string, files []string) error {
303+
if len(files) == 0 {
304+
return fmt.Errorf("no files to merge")
305+
}
306+
307+
combined := &TestSuites{}
308+
for _, file := range files {
309+
data, err := os.ReadFile(file)
310+
if err != nil {
311+
return fmt.Errorf("failed to read file %s: %w", file, err)
312+
}
313+
314+
var suites TestSuites
315+
if err := xml.Unmarshal(data, &suites); err != nil {
316+
return fmt.Errorf("failed to parse XML from %s: %w", file, err)
317+
}
318+
319+
// Aggregate data into the combined structure
320+
combined.Tests += suites.Tests
321+
combined.Failures += suites.Failures
322+
combined.Errors += suites.Errors
323+
combined.Time += suites.Time
324+
combined.TestSuites = append(combined.TestSuites, suites.TestSuites...)
325+
}
326+
327+
output, err := xml.MarshalIndent(combined, "", " ")
328+
if err != nil {
329+
return fmt.Errorf("failed to marshal combined XML: %w", err)
330+
}
331+
332+
output = append([]byte(xml.Header), output...)
333+
334+
if err := os.WriteFile(outputFile, output, 0644); err != nil {
335+
return fmt.Errorf("failed to write output file: %w", err)
336+
}
337+
338+
fmt.Printf("Combined XML written to %s\n", outputFile)
339+
return nil
340+
}
341+
342+
func sanitizeFilename(pkg string) string {
343+
return strings.ReplaceAll(pkg, "/", "_")
344+
}
345+
346+
func runTestsFor(ctx context.Context, pkg, prefix string, xmlFile string) error {
347+
args := []string{"gotestsum", "--junitfile", xmlFile, "--format", "standard-verbose", "--max-fails", "1", "--",
348+
"-v", "-failfast", "-tags=integration"}
270349
if *race {
271350
args = append(args, "-timeout", "180m")
272351
// Todo: There are few race errors in tests itself. Enable this once that is fixed.
@@ -394,6 +473,27 @@ func runTests(taskCh chan task, closer *z.Closer) error {
394473
ctx := closer.Ctx()
395474
ctx = context.WithValue(ctx, _threadIdKey{}, threadId)
396475

476+
tmpDir, err := os.MkdirTemp("", "dgraph-test-xml")
477+
if err != nil {
478+
return fmt.Errorf("failed to create temp directory: %v", err)
479+
}
480+
defer func() {
481+
if err := os.RemoveAll(tmpDir); err != nil {
482+
log.Printf("Failed to remove temporary directory %s: %v", tmpDir, err)
483+
}
484+
}()
485+
486+
var xmlFiles []string
487+
488+
defer func() {
489+
finalXMLFile := filepath.Join(*baseDir, "test-results.xml")
490+
if err := combineJUnitXML(finalXMLFile, xmlFiles); err != nil {
491+
log.Printf("Error merging XML files: %v\n", err)
492+
} else {
493+
fmt.Printf("Merged test results into %s\n", finalXMLFile)
494+
}
495+
}()
496+
397497
for task := range taskCh {
398498
if ctx.Err() != nil {
399499
err = ctx.Err()
@@ -403,20 +503,22 @@ func runTests(taskCh chan task, closer *z.Closer) error {
403503
continue
404504
}
405505

506+
xmlFile := filepath.Join(tmpDir, sanitizeFilename(task.pkg.ID))
507+
xmlFiles = append(xmlFiles, xmlFile) // Add XML file path regardless of success or failure
406508
if task.isCommon {
407509
if *runCustom {
408510
// If we only need to run custom cluster tests, then skip this one.
409511
continue
410512
}
411513
start()
412-
if err = runTestsFor(ctx, task.pkg.ID, prefix); err != nil {
514+
if err = runTestsFor(ctx, task.pkg.ID, prefix, xmlFile); err != nil {
413515
// fmt.Printf("ERROR for package: %s. Err: %v\n", task.pkg.ID, err)
414516
return err
415517
}
416518
} else {
417519
// we are not using err variable here because we dont want to
418520
// print logs of default cluster in case of custom test fail.
419-
if cerr := runCustomClusterTest(ctx, task.pkg.ID, wg); cerr != nil {
521+
if cerr := runCustomClusterTest(ctx, task.pkg.ID, wg, xmlFile); cerr != nil {
420522
return cerr
421523
}
422524
}
@@ -441,7 +543,7 @@ func getClusterPrefix() string {
441543
}
442544

443545
// for tests that require custom docker-compose file (located in test directory)
444-
func runCustomClusterTest(ctx context.Context, pkg string, wg *sync.WaitGroup) error {
546+
func runCustomClusterTest(ctx context.Context, pkg string, wg *sync.WaitGroup, xmlFile string) error {
445547
fmt.Printf("Bringing up cluster for package: %s\n", pkg)
446548
var err error
447549
compose := composeFileFor(pkg)
@@ -455,7 +557,7 @@ func runCustomClusterTest(ctx context.Context, pkg string, wg *sync.WaitGroup) e
455557
defer stopCluster(compose, prefix, wg, err)
456558
}
457559

458-
err = runTestsFor(ctx, pkg, prefix)
560+
err = runTestsFor(ctx, pkg, prefix, xmlFile)
459561
return err
460562
}
461563

0 commit comments

Comments
 (0)