Skip to content

Commit 4898cef

Browse files
committed
feat: go-test workflow for mono-repos
Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
1 parent 4a76752 commit 4898cef

File tree

10 files changed

+312
-2
lines changed

10 files changed

+312
-2
lines changed

.claude/CLAUDE.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
66

77
This repository provides shared, reusable GitHub Actions workflows for the go-openapi organization. The workflows are designed to be called from other go-openapi repositories to standardize CI/CD processes across the entire project family.
88

9+
## GitHub Actions Skills
10+
11+
**IMPORTANT**: When working with GitHub Actions workflows in this repository, refer to the comprehensive GitHub Actions skill:
12+
13+
📖 **See `.claude/skills/github-actions.md`** for:
14+
- Code style and formatting requirements (expression spacing, workflow commands)
15+
- Security best practices (avoiding `secrets[inputs.name]` vulnerability)
16+
- Race condition handling patterns (optimistic execution with error handling)
17+
- Common workflow patterns (bot-credentials, wait-pending-jobs, auto-merge)
18+
- Action definition best practices
19+
- Documentation standards
20+
21+
When proposing new reusable actions, always create them in `go-openapi/gh-actions` (not in this repo).
22+
923
## Testing & Development Commands
1024

1125
### Running Tests
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
name: go test [monorepo]
2+
3+
permissions:
4+
contents: read
5+
pull-requests: read
6+
7+
on:
8+
workflow_call:
9+
10+
defaults:
11+
run:
12+
shell: bash
13+
14+
jobs:
15+
lint:
16+
name: Lint
17+
runs-on: ubuntu-latest
18+
outputs:
19+
is_monorepo: ${{ steps.detect-monorepo.outputs.is_monorepo }}
20+
steps:
21+
-
22+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
23+
with:
24+
fetch-depth: 0
25+
-
26+
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
27+
with:
28+
go-version: stable
29+
check-latest: true
30+
cache: true
31+
cache-dependency-path: '**/go.sum'
32+
-
33+
name: Detect go mono-repo
34+
id: detect-monorepo
35+
run: |
36+
count_modules=$(go list -m|wc -l)
37+
if [[ "${count_modules}" -gt 1 ]] ; then
38+
echo "is_monorepo=true" >> "${GITHUB_OUTPUT}"
39+
echo "::notice title=is_monorepo::true"
40+
exit
41+
fi
42+
echo "is_monorepo=false" >> "${GITHUB_OUTPUT}"
43+
echo "::notice title=is_monorepo::false"
44+
-
45+
name: golangci-lint [mono-repo]
46+
# golangci-action v9.1+ has an experimental built-in mono repo detection setup.
47+
if: ${{ steps.detect-monorepo.outputs.is_monorepo == 'true' }}
48+
uses: golangci/golangci-lint-action@e7fa5ac41e1cf5b7d48e45e42232ce7ada589601 # v9.1.0
49+
with:
50+
version: latest
51+
skip-cache: true
52+
experimental: "automatic-module-directories"
53+
-
54+
name: golangci-lint
55+
if: ${{ steps.detect-monorepo.outputs.is_monorepo != 'true' }}
56+
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
57+
with:
58+
version: latest
59+
only-new-issues: true
60+
skip-cache: true
61+
62+
# Carry out the linting the traditional way, within a shell loop
63+
#-
64+
# name: Lint multiple modules
65+
# if: ${{ steps.detect-monorepo.outputs.is_monorepo == 'true' }}
66+
# # golangci-lint doesn't support go.work to lint multiple modules in one single pass
67+
# run: |
68+
# set -euxo pipefail
69+
# git fetch origin master
70+
# git show --no-patch --oneline origin/master
71+
# while read -r module_location ; do
72+
# pushd "${module_location}"
73+
# golangci-lint run --new-from-rev origin/master
74+
# popd
75+
# done < <(go list -f '{{.Dir}}' -m)
76+
77+
test:
78+
name: Unit tests mono-repo
79+
needs: [ lint ]
80+
runs-on: ${{ matrix.os }}
81+
strategy:
82+
matrix:
83+
os: [ ubuntu-latest, macos-latest, windows-latest ]
84+
go: ['oldstable', 'stable' ]
85+
steps:
86+
-
87+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
88+
-
89+
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
90+
id: go-setup
91+
with:
92+
go-version: '${{ matrix.go }}'
93+
check-latest: true
94+
cache: true
95+
cache-dependency-path: '**/go.sum'
96+
-
97+
name: Detect go version
98+
id: detect-test-work-supported
99+
run: |
100+
go_minor_version=$(echo '${{ steps.go-setup.outputs.go-version }}'|cut -d' ' -f3|cut -d'.' -f2)
101+
echo "go-minor-version=${go_minor_version}" >> "${GITHUB_OUTPUT}"
102+
103+
if [[ "${go_minor_version}" -ge 25 && -f "go.work" ]] ; then
104+
echo "supported=true" >> "${GITHUB_OUTPUT}"
105+
echo "::notice title=go test work supported::true"
106+
else
107+
echo "supported=false" >> "${GITHUB_OUTPUT}"
108+
echo "::notice title=go test work supported::false"
109+
fi
110+
echo "::notice title=go minor version::${go_minor_version}"
111+
-
112+
name: Install gotestsum
113+
uses: go-openapi/gh-actions/install/gotestsum@6c7952706aa7afa9141262485767d9270ef5b00b # v1.3.0
114+
-
115+
name: Run unit tests on all modules in this repo (go1.25+ with go.work)
116+
if: >
117+
${{
118+
needs.lint.outputs.is_monorepo == 'true' && steps.detect-test-work-supported.outputs.supported == 'true'
119+
}}
120+
# with go.work file enabled, go test recognizes sub-modules and collects all packages to be covered
121+
# without specifying -coverpkg.
122+
#
123+
# This requires:
124+
# * go.work properly initialized with use of all known modules
125+
# * go.work committed to git
126+
run: >
127+
gotestsum
128+
--jsonfile 'unit.report.${{ matrix.os }}-${{ matrix.go }}.json'
129+
--
130+
work
131+
-race
132+
-p 2
133+
-count 1
134+
-timeout=20m
135+
-coverprofile='unit.coverage.${{ matrix.os }}-${{ matrix.go }}.out'
136+
-covermode=atomic
137+
./...
138+
-
139+
name: Run unit tests on all modules in this repo (<go1.25 or no go.work)
140+
if: >
141+
${{
142+
needs.lint.outputs.is_monorepo == 'true' && steps.detect-test-work-supported.outputs.supported != 'true'
143+
}}
144+
run: |
145+
declare -a ALL_MODULES
146+
BASH_MAJOR=$(echo "${BASH_VERSION}"|cut -d'.' -f1)
147+
if [[ "${BASH_MAJOR}" -ge 4 ]] ; then
148+
mapfile ALL_MODULES < <(go list -f '{{.Dir}}/...' -m)
149+
else
150+
# for older bash versions, e.g. on macOS runner. This fallback will eventually disappear.
151+
while read -r line ; do
152+
ALL_MODULES+=("${line}")
153+
done < <(go list -f '{{.Dir}}/...' -m)
154+
fi
155+
echo "::notice title=Modules found::${ALL_MODULES[@]}"
156+
157+
gotestsum \
158+
--jsonfile 'unit.report.${{ matrix.os }}-${{ matrix.go }}.json' \
159+
-- \
160+
-race \
161+
-p 2 \
162+
-count 1 \
163+
-timeout=20m \
164+
-coverprofile='unit.coverage.${{ matrix.os }}-${{ matrix.go }}.out' \
165+
-covermode=atomic \
166+
${ALL_MODULES[@]}
167+
-
168+
name: Run unit tests
169+
if: ${{ needs.lint.outputs.is_monorepo != 'true' && needs.lint.outputs.is_monorepo != true }}
170+
run: >
171+
gotestsum
172+
--jsonfile 'unit.report.${{ matrix.os }}-${{ matrix.go }}.json'
173+
--
174+
-race
175+
-p 2
176+
-count 1
177+
-timeout=20m
178+
-coverprofile='unit.coverage.${{ matrix.os }}-${{ matrix.go }}.out'
179+
-covermode=atomic
180+
-coverpkg="$(go list)"/...
181+
./...
182+
-
183+
name: Upload coverage artifacts
184+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
185+
with:
186+
# *.coverage.* pattern is automatically detected by codecov
187+
path: '**/*.coverage.*.out'
188+
name: 'unit.coverage.${{ matrix.os }}-${{ matrix.go }}'
189+
retention-days: 1
190+
-
191+
name: Upload test report artifacts
192+
# upload report even if tests fail. BTW, this is when they are valuable.
193+
if: ${{ !cancelled() }}
194+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
195+
with:
196+
path: '**/unit.report.*.json'
197+
name: 'unit.report.${{ matrix.os }}-${{ matrix.go }}'
198+
retention-days: 1
199+
200+
fuzz-test:
201+
# fuzz-test supports go monorepos
202+
uses: ./.github/workflows/fuzz-test.yml
203+
204+
test-complete:
205+
# description: |
206+
# Be explicit about all tests being passed. This allows for setting up only a few status checks on PRs.
207+
name: tests completed
208+
needs: [test,fuzz-test]
209+
runs-on: ubuntu-latest
210+
steps:
211+
-
212+
name: Tests completed
213+
run: |
214+
echo "::notice title=Success::All tests passed"
215+
216+
collect-coverage:
217+
needs: [test-complete]
218+
if: ${{ !cancelled() && needs.test-complete.result == 'success' }}
219+
uses: ./.github/workflows/collect-coverage.yml
220+
221+
collect-reports:
222+
needs: [test]
223+
if: ${{ !cancelled() }}
224+
uses: ./.github/workflows/collect-reports.yml
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: go test [mono-repo, Test only]
2+
3+
permissions:
4+
contents: read
5+
pull-requests: read
6+
7+
on:
8+
push:
9+
tags:
10+
- v*
11+
branches:
12+
- master
13+
14+
pull_request:
15+
16+
jobs:
17+
go-monorepo-test:
18+
uses: ./.github/workflows/go-test-monorepo.yml
19+
secrets: inherit

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ profile.cov
2121
# vendor/
2222

2323
# Go workspace file
24-
go.work
25-
go.work.sum
24+
#go.work
25+
#go.work.sum
2626

2727
# env file
2828
.env

go.work

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
go 1.24.0
2+
3+
use (
4+
.
5+
./sample-monorepo
6+
)

sample-monorepo/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package monorepo is used to test CI workflows for monorepos.
2+
package monorepo

sample-monorepo/go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/go-openapi/ci-workflows/sample-monorepo
2+
3+
go 1.24.0
4+
5+
require github.com/go-openapi/testify/v2 v2.0.2

sample-monorepo/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
2+
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=

sample-monorepo/pkg/pkg.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Package pkg exercises CI pipelines.
2+
package pkg
3+
4+
func Pkg() string {
5+
return ""
6+
}
7+
8+
func fuzzable(input []byte) string {
9+
if len(input) > 0 {
10+
return string(input)
11+
}
12+
13+
return "0"
14+
}

sample-monorepo/pkg/pkg_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package pkg
2+
3+
import (
4+
"testing"
5+
6+
"github.com/go-openapi/testify/v2/assert"
7+
"github.com/go-openapi/testify/v2/require"
8+
)
9+
10+
func TestPkg(t *testing.T) {
11+
assert.Empty(t, Pkg())
12+
}
13+
14+
func FuzzMonorepo(f *testing.F) {
15+
f.Add([]byte(nil))
16+
f.Add([]byte{})
17+
f.Add([]byte{'x'})
18+
19+
f.Fuzz(func(t *testing.T, input []byte) {
20+
require.NotPanics(t, func() {
21+
_ = fuzzable(input)
22+
})
23+
})
24+
}

0 commit comments

Comments
 (0)