Skip to content

Commit 239a667

Browse files
committed
feat(ci): add lint, tidy, coverage, and formatting CI jobs
1 parent 5aa1dae commit 239a667

30 files changed

Lines changed: 1254 additions & 97 deletions

File tree

.github/workflows/ci.yml

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,102 @@ on:
1313

1414
permissions:
1515
contents: read
16-
pull-requests: read
16+
pull-requests: write
1717

1818
jobs:
1919
# ---------------------------------------------------------------------------
20-
# BUILD AND UNIT TESTS (special case - Gazelle + build + unit tests)
20+
# LINT (all tools from Bazel — gofmt via Go SDK, yamllint via Go binary)
21+
# ---------------------------------------------------------------------------
22+
lint:
23+
name: Lint
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
- uses: ./.github/actions/setup
28+
29+
- name: Run linters
30+
run: make lint
31+
32+
# ---------------------------------------------------------------------------
33+
# TIDY (module files + BUILD files in sync)
34+
# ---------------------------------------------------------------------------
35+
tidy:
36+
name: Tidy
37+
runs-on: ubuntu-latest
38+
steps:
39+
- uses: actions/checkout@v4
40+
- uses: ./.github/actions/setup
41+
42+
- name: Check module files are tidy
43+
run: make check-tidy
44+
45+
- name: Check BUILD files are up to date
46+
run: make check-gazelle
47+
48+
# ---------------------------------------------------------------------------
49+
# BUILD AND UNIT TESTS
2150
# ---------------------------------------------------------------------------
2251
build-and-unit-test:
2352
name: Build and Unit Test
2453
runs-on: ubuntu-latest
2554
steps:
2655
- uses: actions/checkout@v4
56+
with:
57+
# Full history needed for diff coverage (git merge-base origin/main HEAD).
58+
fetch-depth: 0
2759
- uses: ./.github/actions/setup
2860

29-
- name: Check BUILD files are up to date
61+
- name: Build project
62+
run: make build
63+
64+
- name: Run unit tests with coverage
65+
if: "!contains(github.event.pull_request.labels.*.name, 'COVERAGE_EXEMPTION')"
66+
run: make check-coverage
67+
68+
- name: Run unit tests (coverage exempted)
69+
if: contains(github.event.pull_request.labels.*.name, 'COVERAGE_EXEMPTION')
70+
run: make coverage
71+
72+
- name: Coverage summary
73+
if: always()
3074
run: |
31-
echo "Running Gazelle to check BUILD files..." >&2
32-
make gazelle
33-
if ! git diff --quiet; then
34-
echo "BUILD files are out of date!" >&2
35-
echo "" >&2
36-
echo "The following files were modified by Gazelle:" >&2
37-
git diff --name-only >&2
38-
echo "" >&2
39-
echo "Please run 'make gazelle' locally and commit the changes." >&2
40-
exit 1
75+
if [ -f /tmp/coverage-summary.txt ]; then
76+
echo "### Coverage Report" >> "$GITHUB_STEP_SUMMARY"
77+
echo '```' >> "$GITHUB_STEP_SUMMARY"
78+
cat /tmp/coverage-summary.txt >> "$GITHUB_STEP_SUMMARY"
79+
echo '```' >> "$GITHUB_STEP_SUMMARY"
4180
fi
42-
echo "BUILD files are up to date" >&2
4381
44-
- name: Build project
45-
run: make build
82+
- name: Notify coverage exemption
83+
if: >-
84+
github.event_name == 'pull_request' &&
85+
contains(github.event.pull_request.labels.*.name, 'COVERAGE_EXEMPTION')
86+
env:
87+
GH_TOKEN: ${{ github.token }}
88+
run: |
89+
SUMMARY=""
90+
if [ -f /tmp/coverage-summary.txt ]; then
91+
SUMMARY=$(cat /tmp/coverage-summary.txt)
92+
fi
93+
gh pr comment ${{ github.event.pull_request.number }} --body "$(cat <<EOF
94+
**Coverage enforcement exempted** via \`COVERAGE_EXEMPTION\` label.
95+
96+
\`\`\`
97+
${SUMMARY}
98+
\`\`\`
4699
47-
- name: Run unit tests
48-
run: make test || echo "No unit tests found"
100+
Reviewers: please verify this exemption is justified.
101+
EOF
102+
)"
103+
104+
- name: Upload coverage report
105+
if: always()
106+
uses: actions/upload-artifact@v4
107+
with:
108+
name: coverage-report
109+
path: coverage-html/
110+
if-no-files-found: ignore
111+
retention-days: 14
49112

50113
# ---------------------------------------------------------------------------
51114
# INTEGRATION TESTS (e2e, gateway, orchestrator)
@@ -120,6 +183,8 @@ jobs:
120183
name: Required Checks
121184
runs-on: ubuntu-latest
122185
needs:
186+
- lint
187+
- tidy
123188
- build-and-unit-test
124189
- e2e
125190
- gateway-integration-test

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ MODULE.bazel.lock
1212
bin/
1313
.docker-bin/
1414

15+
# Coverage
16+
coverage-html/
17+
1518
# Make completion cache
1619
.make_targets_cache

MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use_repo(
3636
"com_github_spf13_cobra",
3737
"com_github_stretchr_testify",
3838
"com_github_uber_go_tally_v4",
39+
"in_gopkg_yaml_v3",
3940
"org_golang_google_grpc",
4041
"org_golang_google_protobuf",
4142
"org_uber_go_fx",

Makefile

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,46 @@ ORCHESTRATOR_COMPOSE_FILE = example/server/orchestrator/docker-compose.yml
1010
# Fixed project name for local manual testing (tests use unique random names)
1111
LOCAL_PROJECT = submitqueue
1212

13+
# Minimum overall coverage percentage (override with: make check-coverage COVERAGE_THRESHOLD=80)
14+
COVERAGE_THRESHOLD ?= 75
15+
16+
# Minimum diff coverage percentage for new/changed lines (override with: make check-coverage DIFF_COVERAGE_THRESHOLD=90)
17+
DIFF_COVERAGE_THRESHOLD ?= 85
18+
1319
# Set REPO_ROOT for docker-compose
1420
export REPO_ROOT := $(shell pwd)
1521

16-
.PHONY: build build-all-linux build-gateway-linux build-orchestrator-linux clean clean-proto deps e2e-test gazelle integration-test integration-test-extensions integration-test-gateway integration-test-orchestrator local-clean local-gateway-start local-gateway-stop local-init-schemas local-logs local-orchestrator-start local-orchestrator-stop local-ps local-restart local-start local-stop proto query-deps query-targets run-client-gateway run-client-orchestrator run-queue-admin test test-no-cache help
22+
# Runs the coverage tool with optional diff detection.
23+
# Usage: $(call run_coverage,extra flags...)
24+
define run_coverage
25+
@LCOV=$$($(BAZEL) info output_path)/_coverage/_coverage_report.dat; \
26+
MERGE_BASE=$$(git merge-base origin/main HEAD 2>/dev/null || echo ""); \
27+
DIFF_ARGS=""; \
28+
if [ -n "$$MERGE_BASE" ] && [ "$$(git rev-parse HEAD)" != "$$MERGE_BASE" ]; then \
29+
git diff $$MERGE_BASE HEAD > /tmp/coverage-diff.patch; \
30+
DIFF_ARGS="-diff /tmp/coverage-diff.patch"; \
31+
fi; \
32+
$(BAZEL) run //tool/coverage -- \
33+
$$DIFF_ARGS \
34+
-summary /tmp/coverage-summary.txt \
35+
-html $(CURDIR)/coverage-html \
36+
-src $(CURDIR) \
37+
$(1) \
38+
$$LCOV
39+
endef
40+
41+
# Fails if git working tree is dirty. Usage: $(call assert_clean,fix command)
42+
define assert_clean
43+
@if ! git diff --quiet; then \
44+
echo "The following files need updating:" >&2; \
45+
git diff --name-only >&2; \
46+
echo "" >&2; \
47+
echo "Please run '$(1)' locally and commit the changes." >&2; \
48+
exit 1; \
49+
fi
50+
endef
51+
52+
.PHONY: build build-all-linux build-gateway-linux build-orchestrator-linux check-coverage check-gazelle check-tidy clean clean-proto coverage deps e2e-test fmt gazelle integration-test integration-test-extensions integration-test-gateway integration-test-orchestrator lint lint-fmt lint-yaml local-clean local-gateway-start local-gateway-stop local-init-schemas local-logs local-orchestrator-start local-orchestrator-stop local-ps local-restart local-start local-stop proto query-deps query-targets run-client-gateway run-client-orchestrator run-queue-admin test test-no-cache tidy tidy-bazel tidy-go help
1753

1854

1955
build: ## Build all services and examples
@@ -41,6 +77,21 @@ build-orchestrator-linux: ## Build Orchestrator Linux binary for Docker
4177
cp -f bazel-bin/example/server/orchestrator/orchestrator .docker-bin/orchestrator
4278
@echo "Orchestrator Linux binary ready at .docker-bin/orchestrator"
4379

80+
check-coverage: ## Enforce coverage thresholds (overall: $(COVERAGE_THRESHOLD)%, diff: $(DIFF_COVERAGE_THRESHOLD)%)
81+
@echo "Running tests with coverage..."
82+
@$(BAZEL) coverage //... --test_tag_filters=-manual,-integration --combined_report=lcov
83+
$(call run_coverage,-threshold $(COVERAGE_THRESHOLD) -diff-threshold $(DIFF_COVERAGE_THRESHOLD))
84+
85+
check-gazelle: ## Check BUILD.bazel files are up to date
86+
@echo "Running Gazelle to check BUILD files..."
87+
@$(BAZEL) run //:gazelle
88+
$(call assert_clean,make gazelle)
89+
@echo "BUILD files are up to date."
90+
91+
check-tidy: tidy ## Check that go.mod and MODULE.bazel are tidy
92+
$(call assert_clean,make tidy)
93+
@echo "Module files are up to date."
94+
4495
clean: ## Clean generated files and binaries
4596
@echo "Cleaning with Bazel..."
4697
@$(BAZEL) clean
@@ -53,16 +104,24 @@ clean-proto: ## Clean generated proto files
53104
@rm -rf orchestrator/protopb/*.pb.go
54105
@echo "Proto clean complete!"
55106

56-
deps: ## Install Go dependencies
57-
@echo "Installing Go dependencies..."
58-
@go mod download
59-
@go mod tidy
107+
coverage: ## Run tests with coverage, generate HTML report
108+
@echo "Running tests with coverage..."
109+
@$(BAZEL) coverage //... --test_tag_filters=-manual,-integration --combined_report=lcov
110+
$(call run_coverage,)
111+
112+
deps: tidy-go ## Download and tidy Go dependencies
60113
@echo "Dependencies installed!"
61114

62115
e2e-test: build-all-linux ## Run end-to-end tests (hermetic, auto-builds binaries)
63116
@echo "Running end-to-end tests..."
64117
@$(BAZEL) test //test/e2e:e2e_test --test_output=streamed
65118

119+
fmt: ## Format Go code
120+
@GOFMT=$$($(BAZEL) run @rules_go//go -- env GOROOT 2>/dev/null | tail -1)/bin/gofmt; \
121+
echo "Formatting Go code (using $$GOFMT)..."; \
122+
$$GOFMT -w .; \
123+
echo "Formatting complete!"
124+
66125
gazelle: ## Update BUILD.bazel files
67126
@echo "Running Gazelle to update BUILD files..."
68127
@$(BAZEL) run //:gazelle
@@ -83,6 +142,18 @@ integration-test-orchestrator: build-orchestrator-linux ## Run Orchestrator inte
83142
@echo "Running Orchestrator integration tests..."
84143
@$(BAZEL) test //test/integration/orchestrator:orchestrator_test --test_output=streamed
85144

145+
lint: lint-fmt lint-yaml ## Run all linters
146+
@echo "All lint checks passed."
147+
148+
lint-fmt: fmt ## Check Go code formatting (fails if unformatted)
149+
$(call assert_clean,make fmt)
150+
@echo "All Go files are properly formatted."
151+
152+
lint-yaml: ## Check YAML files for syntax errors
153+
@echo "Checking YAML files..."
154+
@$(BAZEL) run //tool/linter/yamllint -- .
155+
@echo "All YAML files are valid."
156+
86157
local-clean: ## Stop and remove all local services, volumes, and images
87158
@echo "Cleaning all services and data..."
88159
@$(COMPOSE) -f $(COMPOSE_FILE) -p $(LOCAL_PROJECT) down -v --rmi local
@@ -229,6 +300,16 @@ test-no-cache: ## Run unit tests without cache (force re-run)
229300
@echo "Running unit tests (no cache)..."
230301
@$(BAZEL) test //... --test_tag_filters=-manual,-integration --nocache_test_results
231302

303+
tidy: tidy-go tidy-bazel ## Run go mod tidy and bazel mod tidy
304+
305+
tidy-bazel: ## Run bazel mod tidy
306+
@echo "Running bazel mod tidy..."
307+
@$(BAZEL) mod tidy
308+
309+
tidy-go: ## Run go mod tidy
310+
@echo "Running go mod tidy..."
311+
@$(BAZEL) run @rules_go//go -- mod tidy -e
312+
232313
help: ## Show this help message
233314
@echo "Available targets:"
234315
@echo ""

core/consumer/registry.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ type TopicConfig struct {
5757
// TopicRegistry provides queue, topic name, and subscription config for topics.
5858
// Each topic can have a different queue backend and topic name.
5959
type TopicRegistry struct {
60-
queues map[TopicKey]queue.Queue
61-
topicNames map[TopicKey]string
60+
queues map[TopicKey]queue.Queue
61+
topicNames map[TopicKey]string
6262
subscriptionConfigs map[topicGroup]queue.SubscriptionConfig
6363
}
6464

core/consumer/registry_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ func TestNewTopicRegistry(t *testing.T) {
1818
registry, err := consumer.NewTopicRegistry(
1919
[]consumer.TopicConfig{
2020
{
21-
Key: consumer.TopicKeyRequest,
22-
Name: "request",
21+
Key: consumer.TopicKeyRequest,
22+
Name: "request",
2323
Queue: mockQ,
2424
Subscription: extqueue.DefaultSubscriptionConfig(
2525
"worker-1", "group-a",
@@ -44,7 +44,7 @@ func TestNewTopicRegistry(t *testing.T) {
4444

4545
func TestNewTopicRegistry_InvalidTopicName(t *testing.T) {
4646
tests := []struct {
47-
name string
47+
name string
4848
topicName string
4949
}{
5050
{

entity/build.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ func (s BuildStatus) IsTerminal() bool {
3939
return s == BuildStatusPassed || s == BuildStatusFailed || s == BuildStatusCancelled
4040
}
4141

42-
4342
// SpeculationPathInfo represents the base and head commits of a speculation path used in a build.
4443
type SpeculationPathInfo struct {
4544
// Base is a list of batchIDs(in order) that form the base of this speculation path.

entity/queue/message_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,3 @@ func TestMessage_Fields(t *testing.T) {
6262
assert.Equal(t, msg.PublishedAt, copied.PublishedAt)
6363
assert.Equal(t, msg.Metadata, copied.Metadata)
6464
}
65-

entity/request.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package entity
22

33
import "encoding/json"
44

5-
65
// RequestLandStrategy defines the possible source control integration methods.
76
type RequestLandStrategy string
87

example/server/orchestrator/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ import (
2121
githubchecker "github.com/uber/submitqueue/extension/mergechecker/github"
2222
extqueue "github.com/uber/submitqueue/extension/queue"
2323
queueMySQL "github.com/uber/submitqueue/extension/queue/mysql"
24-
mysqlstorage "github.com/uber/submitqueue/extension/storage/mysql"
2524
"github.com/uber/submitqueue/extension/storage"
25+
mysqlstorage "github.com/uber/submitqueue/extension/storage/mysql"
2626
"github.com/uber/submitqueue/orchestrator/controller"
2727
"github.com/uber/submitqueue/orchestrator/controller/batch"
2828
"github.com/uber/submitqueue/orchestrator/controller/build"
29+
"github.com/uber/submitqueue/orchestrator/controller/buildsignal"
2930
"github.com/uber/submitqueue/orchestrator/controller/conclude"
3031
"github.com/uber/submitqueue/orchestrator/controller/merge"
31-
"github.com/uber/submitqueue/orchestrator/controller/buildsignal"
3232
"github.com/uber/submitqueue/orchestrator/controller/request"
3333
"github.com/uber/submitqueue/orchestrator/controller/score"
3434
"github.com/uber/submitqueue/orchestrator/controller/speculate"

0 commit comments

Comments
 (0)