Skip to content

Commit 6c12e22

Browse files
authored
ci: Out of the tarpit (#2923)
* Lint fixes * Use latest go version for go-check Fixes nil pointer issue in staticcheck * Add test_analysis helper script * Use custom go-test-template * Add some tests to the test_analysis script * Always upload test_results db * Attempt to fix test on windows * Better if statement * Try to fix flaky test * Disable caching setup-go on Windows * Better if statement * Tweak * Always upload summary and artifact * Close db * No extra newline
1 parent 99433a2 commit 6c12e22

File tree

14 files changed

+613
-11
lines changed

14 files changed

+613
-11
lines changed

.github/actions/go-test-setup/action.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ runs:
99
shell: bash
1010
# This matches only tests with "NoCover" in their test name to avoid running all tests again.
1111
run: go test -tags nocover -run NoCover -v ./...
12+
- name: Install testing tools
13+
shell: bash
14+
run: cd scripts/test_analysis && go install ./cmd/gotest2sql
15+
- name: Install test_analysis
16+
shell: bash
17+
run: cd scripts/test_analysis && go install .

.github/workflows/go-check.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ jobs:
1717
go-check:
1818
uses: ipdxco/unified-github-workflows/.github/workflows/[email protected]
1919
with:
20+
go-version: "1.22.x"
2021
go-generate-ignore-protoc-version-comments: true
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
name: Go Test
2+
on:
3+
workflow_call:
4+
inputs:
5+
go-versions:
6+
required: false
7+
type: string
8+
default: '["this", "next"]'
9+
secrets:
10+
CODECOV_TOKEN:
11+
required: false
12+
13+
defaults:
14+
run:
15+
shell: bash
16+
17+
jobs:
18+
unit:
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
os: ["ubuntu", "macos", "windows"]
23+
go: ${{ fromJSON(inputs.go-versions) }}
24+
env:
25+
GOTESTFLAGS: -cover -coverprofile=module-coverage.txt -coverpkg=./...
26+
GO386FLAGS: ""
27+
GORACEFLAGS: ""
28+
runs-on: ${{ fromJSON(vars[format('UCI_GO_TEST_RUNNER_{0}', matrix.os)] || format('"{0}-latest"', matrix.os)) }}
29+
name: ${{ matrix.os }} (go ${{ matrix.go }})
30+
steps:
31+
- name: Use msys2 on windows
32+
if: matrix.os == 'windows'
33+
# The executable for msys2 is also called bash.cmd
34+
# https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md#shells
35+
# If we prepend its location to the PATH
36+
# subsequent 'shell: bash' steps will use msys2 instead of gitbash
37+
run: echo "C:/msys64/usr/bin" >> $GITHUB_PATH
38+
- name: Check out the repository
39+
uses: actions/checkout@v4
40+
with:
41+
submodules: recursive
42+
- name: Check out the latest stable version of Go
43+
uses: actions/setup-go@v5
44+
with:
45+
go-version: stable
46+
cache: ${{ matrix.os != 'windows' }} # Windows VMs are slow to use caching. Can add ~15m to the job
47+
- name: Read the Unified GitHub Workflows configuration
48+
id: config
49+
uses: ipdxco/unified-github-workflows/.github/actions/read-config@main
50+
- name: Read the go.mod file
51+
id: go-mod
52+
uses: ipdxco/unified-github-workflows/.github/actions/read-go-mod@main
53+
- name: Determine the Go version to use based on the go.mod file
54+
id: go
55+
env:
56+
MATRIX_GO: ${{ matrix.go }}
57+
GO_MOD_VERSION: ${{ fromJSON(steps.go-mod.outputs.json).Go }}
58+
run: |
59+
if [[ "$MATRIX_GO" == "this" ]]; then
60+
echo "version=$GO_MOD_VERSION.x" >> $GITHUB_OUTPUT
61+
elif [[ "$MATRIX_GO" == "next" ]]; then
62+
MAJOR="${GO_MOD_VERSION%.[0-9]*}"
63+
MINOR="${GO_MOD_VERSION#[0-9]*.}"
64+
echo "version=$MAJOR.$(($MINOR+1)).x" >> $GITHUB_OUTPUT
65+
elif [[ "$MATRIX_GO" == "prev" ]]; then
66+
MAJOR="${GO_MOD_VERSION%.[0-9]*}"
67+
MINOR="${GO_MOD_VERSION#[0-9]*.}"
68+
echo "version=$MAJOR.$(($MINOR-1)).x" >> $GITHUB_OUTPUT
69+
else
70+
echo "version=$MATRIX_GO" >> $GITHUB_OUTPUT
71+
fi
72+
- name: Enable shuffle flag for go test command
73+
if: toJSON(fromJSON(steps.config.outputs.json).shuffle) != 'false'
74+
run: |
75+
echo "GOTESTFLAGS=-shuffle=on $GOTESTFLAGS" >> $GITHUB_ENV
76+
echo "GO386FLAGS=-shuffle=on $GO386FLAGS" >> $GITHUB_ENV
77+
echo "GORACEFLAGS=-shuffle=on $GORACEFLAGS" >> $GITHUB_ENV
78+
- name: Enable verbose flag for go test command
79+
if: toJSON(fromJSON(steps.config.outputs.json).verbose) != 'false'
80+
run: |
81+
echo "GOTESTFLAGS=-v $GOTESTFLAGS" >> $GITHUB_ENV
82+
echo "GO386FLAGS=-v $GO386FLAGS" >> $GITHUB_ENV
83+
echo "GORACEFLAGS=-v $GORACEFLAGS" >> $GITHUB_ENV
84+
- name: Set extra flags for go test command
85+
if: fromJSON(steps.config.outputs.json).gotestflags != ''
86+
run: |
87+
echo "GOTESTFLAGS=${{ fromJSON(steps.config.outputs.json).gotestflags }} $GOTESTFLAGS" >> $GITHUB_ENV
88+
- name: Set extra flags for go test race command
89+
if: fromJSON(steps.config.outputs.json).goraceflags != ''
90+
run: |
91+
echo "GORACEFLAGS=${{ fromJSON(steps.config.outputs.json).goraceflags }} $GORACEFLAGS" >> $GITHUB_ENV
92+
- name: Set up the Go version read from the go.mod file
93+
uses: actions/setup-go@v5
94+
with:
95+
go-version: ${{ steps.go.outputs.version }}
96+
cache: ${{ matrix.os != 'windows' }} # Windows VMs are slow to use caching. Can add ~15m to the job
97+
- name: Display the Go version and environment
98+
run: |
99+
go version
100+
go env
101+
- name: Run repo-specific setup
102+
uses: ./.github/actions/go-test-setup
103+
if: hashFiles('./.github/actions/go-test-setup') != ''
104+
- name: Run tests
105+
id: test
106+
if: contains(fromJSON(steps.config.outputs.json).skipOSes, matrix.os) == false
107+
uses: protocol/[email protected]
108+
with:
109+
run: test_analysis ${{ env.GOTESTFLAGS }}
110+
- name: Upload test results
111+
if: always()
112+
uses: actions/upload-artifact@v3
113+
with:
114+
name: ${{ matrix.os }}_${{ matrix.go }}_test_results.db
115+
path: ./test_results.db
116+
- name: Add failure summary
117+
if: always()
118+
run: |
119+
echo "### Failure Summary" >> $GITHUB_STEP_SUMMARY
120+
test_analysis summarize >> $GITHUB_STEP_SUMMARY
121+
- name: Remove test results
122+
run: rm ./test_results.db
123+
- name: Run tests with race detector
124+
# speed things up. Windows and OSX VMs are slow
125+
if: matrix.os == 'ubuntu' &&
126+
fromJSON(steps.config.outputs.json).skipRace != true &&
127+
contains(fromJSON(steps.config.outputs.json).skipOSes, matrix.os) == false
128+
uses: protocol/[email protected]
129+
id: race
130+
with:
131+
run: test_analysis -race ${{ env.GORACEFLAGS }} ./...
132+
- name: Upload test results (Race)
133+
if: (steps.race.conclusion == 'success' || steps.race.conclusion == 'failure')
134+
uses: actions/upload-artifact@v3
135+
with:
136+
name: ${{ matrix.os }}_${{ matrix.go }}_test_results_race.db
137+
path: ./test_results.db
138+
- name: Add failure summary
139+
if: (steps.race.conclusion == 'success' || steps.race.conclusion == 'failure')
140+
run: |
141+
echo "# Tests with race detector failure summary" >> $GITHUB_STEP_SUMMARY
142+
test_analysis summarize >> $GITHUB_STEP_SUMMARY
143+
- name: Adding Link to Run Analysis
144+
run: echo "### [Test flakiness analysis](https://observablehq.com/d/d74435ea5bbf24c7?run-id=$GITHUB_RUN_ID)" >> $GITHUB_STEP_SUMMARY
145+
- name: Collect coverage files
146+
id: coverages
147+
run: echo "files=$(find . -type f -name 'module-coverage.txt' | tr -s '\n' ',' | sed 's/,$//')" >> $GITHUB_OUTPUT
148+
- name: Upload coverage to Codecov
149+
uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0
150+
with:
151+
files: ${{ steps.coverages.outputs.files }}
152+
env_vars: OS=${{ matrix.os }}, GO=${{ steps.go.outputs.version }}
153+
token: ${{ secrets.CODECOV_TOKEN }}
154+
fail_ci_if_error: false

.github/workflows/go-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ concurrency:
1515

1616
jobs:
1717
go-test:
18-
uses: libp2p/uci/.github/workflows/go-test.yml@v1.0
18+
uses: ./.github/workflows/go-test-template.yml
1919
with:
2020
go-versions: '["1.21.x", "1.22.x"]'
2121
secrets:

libp2p_test.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"crypto/rand"
66
"errors"
77
"fmt"
8+
"net"
9+
"net/netip"
810
"regexp"
911
"strconv"
1012
"strings"
@@ -488,10 +490,23 @@ func TestHostAddrsFactoryAddsCerthashes(t *testing.T) {
488490
h.Close()
489491
}
490492

493+
func newRandomPort(t *testing.T) string {
494+
t.Helper()
495+
// Find an available port
496+
c, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
497+
require.NoError(t, err)
498+
c.LocalAddr().Network()
499+
ipPort := netip.MustParseAddrPort(c.LocalAddr().String())
500+
port := strconv.Itoa(int(ipPort.Port()))
501+
require.NoError(t, c.Close())
502+
return port
503+
}
504+
491505
func TestWebRTCReuseAddrWithQUIC(t *testing.T) {
506+
port := newRandomPort(t)
492507
order := [][]string{
493-
{"/ip4/127.0.0.1/udp/54322/quic-v1", "/ip4/127.0.0.1/udp/54322/webrtc-direct"},
494-
{"/ip4/127.0.0.1/udp/54322/webrtc-direct", "/ip4/127.0.0.1/udp/54322/quic-v1"},
508+
{"/ip4/127.0.0.1/udp/" + port + "/quic-v1", "/ip4/127.0.0.1/udp/" + port + "/webrtc-direct"},
509+
{"/ip4/127.0.0.1/udp/" + port + "/webrtc-direct", "/ip4/127.0.0.1/udp/" + port + "/quic-v1"},
495510
// We do not support WebRTC automatically reusing QUIC addresses if port is not specified, yet.
496511
// {"/ip4/127.0.0.1/udp/0/webrtc-direct", "/ip4/127.0.0.1/udp/0/quic-v1"},
497512
}
@@ -542,16 +557,18 @@ func TestWebRTCReuseAddrWithQUIC(t *testing.T) {
542557
})
543558
}
544559

545-
swapPort := func(addrStrs []string, newPort string) []string {
560+
swapPort := func(addrStrs []string, oldPort, newPort string) []string {
546561
out := make([]string, 0, len(addrStrs))
547562
for _, addrStr := range addrStrs {
548-
out = append(out, strings.Replace(addrStr, "54322", newPort, 1))
563+
out = append(out, strings.Replace(addrStr, oldPort, newPort, 1))
549564
}
550565
return out
551566
}
552567

553568
t.Run("setup with no reuseport. Should fail", func(t *testing.T) {
554-
h1, err := New(ListenAddrStrings(swapPort(order[0], "54323")...), Transport(quic.NewTransport), Transport(libp2pwebrtc.New), QUICReuse(quicreuse.NewConnManager, quicreuse.DisableReuseport()))
569+
oldPort := port
570+
newPort := newRandomPort(t)
571+
h1, err := New(ListenAddrStrings(swapPort(order[0], oldPort, newPort)...), Transport(quic.NewTransport), Transport(libp2pwebrtc.New), QUICReuse(quicreuse.NewConnManager, quicreuse.DisableReuseport()))
555572
require.NoError(t, err) // It's a bug/feature that swarm.Listen does not error if at least one transport succeeds in listening.
556573
defer h1.Close()
557574
// Check that webrtc did fail to listen
@@ -560,7 +577,9 @@ func TestWebRTCReuseAddrWithQUIC(t *testing.T) {
560577
})
561578

562579
t.Run("setup with autonat", func(t *testing.T) {
563-
h1, err := New(EnableAutoNATv2(), ListenAddrStrings(swapPort(order[0], "54324")...), Transport(quic.NewTransport), Transport(libp2pwebrtc.New), QUICReuse(quicreuse.NewConnManager, quicreuse.DisableReuseport()))
580+
oldPort := port
581+
newPort := newRandomPort(t)
582+
h1, err := New(EnableAutoNATv2(), ListenAddrStrings(swapPort(order[0], oldPort, newPort)...), Transport(quic.NewTransport), Transport(libp2pwebrtc.New), QUICReuse(quicreuse.NewConnManager, quicreuse.DisableReuseport()))
564583
require.NoError(t, err) // It's a bug/feature that swarm.Listen does not error if at least one transport succeeds in listening.
565584
defer h1.Close()
566585
// Check that webrtc did fail to listen

p2p/protocol/identify/id.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ func (ids *idService) addConnWithLock(c network.Conn) {
995995
}
996996

997997
func signedPeerRecordFromMessage(msg *pb.Identify) (*record.Envelope, error) {
998-
if msg.SignedPeerRecord == nil || len(msg.SignedPeerRecord) == 0 {
998+
if len(msg.SignedPeerRecord) == 0 {
999999
return nil, nil
10001000
}
10011001
env, _, err := record.ConsumeEnvelope(msg.SignedPeerRecord, peer.PeerRecordEnvelopeDomain)

p2p/protocol/identify/id_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,7 @@ func waitForAddrInStream(t *testing.T, s <-chan ma.Multiaddr, expected ma.Multia
960960
}
961961
continue
962962
case <-time.After(timeout):
963-
t.Fatalf(failMsg)
963+
t.Fatal(failMsg)
964964
}
965965
}
966966
}

p2p/transport/quic/cmd/client/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ func main() {
1414
return
1515
}
1616
if err := cmdlib.RunClient(os.Args[1], os.Args[2]); err != nil {
17-
log.Fatalf(err.Error())
17+
log.Fatal(err.Error())
1818
}
1919
}

p2p/transport/quic/cmd/server/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ func main() {
1414
return
1515
}
1616
if err := cmdlib.RunServer(os.Args[1], nil); err != nil {
17-
log.Fatalf(err.Error())
17+
log.Fatal(err.Error())
1818
}
1919
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// gotest2sql inserts the output of go test -json ./... into a sqlite database
2+
package main
3+
4+
import (
5+
"bufio"
6+
"database/sql"
7+
"encoding/json"
8+
"flag"
9+
"fmt"
10+
"log"
11+
"os"
12+
"time"
13+
14+
_ "github.com/glebarez/go-sqlite"
15+
)
16+
17+
type TestEvent struct {
18+
Time time.Time // encodes as an RFC3339-format string
19+
Action string
20+
Package string
21+
Test string
22+
Elapsed float64 // seconds
23+
Output string
24+
}
25+
26+
func main() {
27+
outputPath := flag.String("output", "", "output db file")
28+
verbose := flag.Bool("v", false, "Print test output to stdout")
29+
flag.Parse()
30+
31+
if *outputPath == "" {
32+
log.Fatal("-output path is required")
33+
}
34+
35+
db, err := sql.Open("sqlite", *outputPath)
36+
if err != nil {
37+
log.Fatal(err)
38+
}
39+
40+
// Create a table to store test results.
41+
_, err = db.Exec(`
42+
CREATE TABLE IF NOT EXISTS test_results (
43+
Time TEXT,
44+
Action TEXT,
45+
Package TEXT,
46+
Test TEXT,
47+
Elapsed REAL,
48+
Output TEXT,
49+
BatchInsertTime TEXT
50+
)`)
51+
if err != nil {
52+
log.Fatal(err)
53+
}
54+
55+
tx, err := db.Begin()
56+
if err != nil {
57+
log.Fatal(err)
58+
}
59+
60+
// Prepare the insert statement once
61+
insertTime := time.Now().Format(time.RFC3339Nano)
62+
stmt, err := tx.Prepare(`
63+
INSERT INTO test_results (Time, Action, Package, Test, Elapsed, Output, BatchInsertTime)
64+
VALUES (?, ?, ?, ?, ?, ?, ?)`)
65+
if err != nil {
66+
log.Fatal(err)
67+
}
68+
defer stmt.Close() // Ensure the statement is closed after use
69+
70+
s := bufio.NewScanner(os.Stdin)
71+
for s.Scan() {
72+
line := s.Bytes()
73+
var ev TestEvent
74+
err = json.Unmarshal(line, &ev)
75+
if err != nil {
76+
log.Fatal(err)
77+
}
78+
if *verbose && ev.Action == "output" {
79+
fmt.Print(ev.Output)
80+
}
81+
82+
_, err = stmt.Exec(
83+
ev.Time.Format(time.RFC3339Nano),
84+
ev.Action,
85+
ev.Package,
86+
ev.Test,
87+
ev.Elapsed,
88+
ev.Output,
89+
insertTime,
90+
)
91+
if err != nil {
92+
log.Fatal(err)
93+
}
94+
}
95+
96+
// Commit the transaction
97+
if err := tx.Commit(); err != nil {
98+
log.Fatal(err)
99+
}
100+
}

0 commit comments

Comments
 (0)