Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 33 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,52 @@ on:

permissions:
contents: read
pull-requests: read

jobs:
# ---------------------------------------------------------------------------
# BUILD AND UNIT TESTS (special case - Gazelle + build + unit tests)
# LINT (gofmt via Go SDK, yamlfmt via go run)
# ---------------------------------------------------------------------------
build-and-unit-test:
name: Build and Unit Test
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup

- name: Run linters
run: make lint

# ---------------------------------------------------------------------------
# TIDY (module files + BUILD files in sync)
# ---------------------------------------------------------------------------
tidy:
name: Tidy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup

- name: Check module files are tidy
run: make check-tidy

- name: Check BUILD files are up to date
run: |
echo "Running Gazelle to check BUILD files..." >&2
make gazelle
if ! git diff --quiet; then
echo "BUILD files are out of date!" >&2
echo "" >&2
echo "The following files were modified by Gazelle:" >&2
git diff --name-only >&2
echo "" >&2
echo "Please run 'make gazelle' locally and commit the changes." >&2
exit 1
fi
echo "BUILD files are up to date" >&2
run: make check-gazelle

# ---------------------------------------------------------------------------
# BUILD AND UNIT TESTS
# ---------------------------------------------------------------------------
build-and-unit-test:
name: Build and Unit Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup

- name: Build project
run: make build

- name: Run unit tests
run: make test || echo "No unit tests found"
run: make test

# ---------------------------------------------------------------------------
# INTEGRATION TESTS (e2e, gateway, orchestrator)
Expand Down Expand Up @@ -133,6 +146,8 @@ jobs:
name: Required Checks
runs-on: ubuntu-latest
needs:
- lint
- tidy
- build-and-unit-test
- e2e
- gateway-integration-test
Expand Down
5 changes: 5 additions & 0 deletions .yamlfmt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
formatter:
type: basic
indent: 2
retain_line_breaks_single: true
include_document_start: false
19 changes: 17 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ submitqueue/
│ ├── counter/ # Sequential number generation (interface + mysql/)
│ ├── queue/ # Messaging queue abstraction (interface + sql/)
│ └── storage/ # Storage abstraction (interface + mysql/)
├── core/ # Shared infrastructure packages reused across services
├── core/ # Shared infrastructure packages reused across services
│ ├── consumer/ # Queue consumption framework (lifecycle, ack/nack, routing)
│ └── errs/ # Error classification framework (user vs infra, retryability)
├── tool/ # Development and CI tooling
├── example/server/ # Runnable servers with Docker Compose
├── test/
│ ├── e2e/ # End-to-end tests (full stack)
Expand Down Expand Up @@ -157,10 +158,15 @@ integration-test: build-all-linux ## Run all integration tests (auto-builds bina
```bash
make build # Build all services
make test # Run unit tests
make lint # Run all linters (fmt + YAML)
make fmt # Format Go and YAML code
make check-tidy # Check go.mod and MODULE.bazel are tidy
make check-gazelle # Check BUILD.bazel files are up to date
make tidy # Run go mod tidy + bazel mod tidy
make gazelle # Update BUILD.bazel files
make integration-test # Run all integration tests (Docker-based)
make e2e-test # Run end-to-end tests
make proto # Regenerate proto files
make gazelle # Update BUILD.bazel files
make local-start # Start full stack with Docker Compose
make local-ps # Show running containers and ports
make local-logs # View logs from all services
Expand Down Expand Up @@ -256,6 +262,15 @@ deps = [

See [doc/howto/TESTING.md](doc/howto/TESTING.md) for full testing guide.

### CI and Validation

CI runs on every PR and enforces all checks via a `required-checks` gate. **Before committing, validate locally:**

1. `make fmt` — format Go and YAML code (CI will reject unformatted code)
2. `make lint` — run all linters (formatting check)
3. `make check-tidy` — ensure `go.mod` and `MODULE.bazel` are tidy
4. `make check-gazelle` — ensure `BUILD.bazel` files are up to date

### Code Style

1. **Structured logging** — `zap.SugaredLogger` with `Debugw`/`Infow`/`Errorw(msg, key, val, ...)`. Never unstructured methods.
Expand Down
58 changes: 52 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,27 @@ ORCHESTRATOR_COMPOSE_FILE = example/server/orchestrator/docker-compose.yml
# Fixed project name for local manual testing (tests use unique random names)
LOCAL_PROJECT = submitqueue

# yamlfmt version for YAML formatting (override with: make fmt YAMLFMT_VERSION=v0.16.0)
YAMLFMT_VERSION ?= v0.16.0

# goimports version for Go formatting + import fixing
GOIMPORTS_VERSION ?= v0.33.0

# Set REPO_ROOT for docker-compose
export REPO_ROOT := $(shell pwd)

.PHONY: build build-all-linux build-gateway-linux build-orchestrator-linux clean clean-proto deps e2e-test gazelle integration-test integration-test-consumer integration-test-extensions integration-test-gateway integration-test-orchestrator license-fix lint lint-license 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
# Fails if git working tree is dirty. Usage: $(call assert_clean,fix command)
define assert_clean
@if ! git diff --quiet; then \
echo "The following files need updating:" >&2; \
git diff --name-only >&2; \
echo "" >&2; \
echo "Please run '$(1)' locally and commit the changes." >&2; \
exit 1; \
fi
endef

.PHONY: build build-all-linux build-gateway-linux build-orchestrator-linux check-gazelle check-tidy clean clean-proto deps e2e-test fmt gazelle integration-test integration-test-consumer integration-test-extensions integration-test-gateway integration-test-orchestrator license-fix lint lint-fmt lint-license 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


build: ## Build all services and examples
Expand Down Expand Up @@ -41,6 +58,16 @@ build-orchestrator-linux: ## Build Orchestrator Linux binary for Docker
cp -f bazel-bin/example/server/orchestrator/orchestrator .docker-bin/orchestrator
@echo "Orchestrator Linux binary ready at .docker-bin/orchestrator"

check-gazelle: ## Check BUILD.bazel files are up to date
@echo "Running Gazelle to check BUILD files..."
@$(BAZEL) run //:gazelle
$(call assert_clean,make gazelle)
@echo "BUILD files are up to date."

check-tidy: tidy ## Check that go.mod and MODULE.bazel are tidy
$(call assert_clean,make tidy)
@echo "Module files are up to date."

clean: ## Clean generated files and binaries
@echo "Cleaning with Bazel..."
@$(BAZEL) clean
Expand All @@ -53,16 +80,20 @@ clean-proto: ## Clean generated proto files
@rm -rf orchestrator/protopb/*.pb.go
@echo "Proto clean complete!"

deps: ## Install Go dependencies
@echo "Installing Go dependencies..."
@go mod download
@go mod tidy
deps: tidy-go ## Download and tidy Go dependencies
@echo "Dependencies installed!"

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

fmt: ## Format Go and YAML code
@echo "Formatting Go code..."
@$(BAZEL) run @rules_go//go -- run golang.org/x/tools/cmd/goimports@$(GOIMPORTS_VERSION) -w .
@echo "Formatting YAML files..."
@$(BAZEL) run @rules_go//go -- run github.com/google/yamlfmt/cmd/yamlfmt@$(YAMLFMT_VERSION)
@echo "Formatting complete!"

gazelle: ## Update BUILD.bazel files
@echo "Running Gazelle to update BUILD files..."
@$(BAZEL) run //:gazelle
Expand Down Expand Up @@ -90,7 +121,12 @@ integration-test-orchestrator: build-orchestrator-linux ## Run Orchestrator inte
license-fix: ## Add missing license headers to source files
@$(BAZEL) run //tool/linter/licenseheader -- --fix

lint: lint-license ## Run all linters
lint: lint-fmt lint-license ## Run all linters
@echo "All lint checks passed."

lint-fmt: fmt ## Check code formatting (fails if unformatted)
$(call assert_clean,make fmt)
@echo "All code is properly formatted."

lint-license: ## Check license headers on all source files
@$(BAZEL) run //tool/linter/licenseheader -- --check
Expand Down Expand Up @@ -241,6 +277,16 @@ test-no-cache: ## Run unit tests without cache (force re-run)
@echo "Running unit tests (no cache)..."
@$(BAZEL) test //... --test_tag_filters=-manual,-integration --nocache_test_results

tidy: tidy-go tidy-bazel ## Run go mod tidy and bazel mod tidy

tidy-bazel: ## Run bazel mod tidy
@echo "Running bazel mod tidy..."
@$(BAZEL) mod tidy

tidy-go: ## Run go mod tidy
@echo "Running go mod tidy..."
@$(BAZEL) run @rules_go//go -- mod tidy -e

help: ## Show this help message
@echo "Available targets:"
@echo ""
Expand Down
4 changes: 2 additions & 2 deletions core/consumer/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ type TopicConfig struct {
// TopicRegistry provides queue, topic name, and subscription config for topics.
// Each topic can have a different queue backend and topic name.
type TopicRegistry struct {
queues map[TopicKey]queue.Queue
topicNames map[TopicKey]string
queues map[TopicKey]queue.Queue
topicNames map[TopicKey]string
subscriptionConfigs map[topicGroup]queue.SubscriptionConfig
}

Expand Down
6 changes: 3 additions & 3 deletions core/consumer/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ func TestNewTopicRegistry(t *testing.T) {
registry, err := consumer.NewTopicRegistry(
[]consumer.TopicConfig{
{
Key: consumer.TopicKeyRequest,
Name: "request",
Key: consumer.TopicKeyRequest,
Name: "request",
Queue: mockQ,
Subscription: extqueue.DefaultSubscriptionConfig(
"worker-1", "group-a",
Expand All @@ -58,7 +58,7 @@ func TestNewTopicRegistry(t *testing.T) {

func TestNewTopicRegistry_InvalidTopicName(t *testing.T) {
tests := []struct {
name string
name string
topicName string
}{
{
Expand Down
2 changes: 1 addition & 1 deletion core/errs/errs.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
package errs

import (
"errors"
"context"
"errors"
)

// userError represents an error caused by invalid user input or actions.
Expand Down
1 change: 0 additions & 1 deletion entity/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ func (s BuildStatus) IsTerminal() bool {
return s == BuildStatusPassed || s == BuildStatusFailed || s == BuildStatusCancelled
}


// SpeculationPathInfo represents the base and head commits of a speculation path used in a build.
type SpeculationPathInfo struct {
// Base is a list of batchIDs(in order) that form the base of this speculation path.
Expand Down
1 change: 0 additions & 1 deletion entity/queue/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,3 @@ func TestMessage_Fields(t *testing.T) {
assert.Equal(t, msg.PublishedAt, copied.PublishedAt)
assert.Equal(t, msg.Metadata, copied.Metadata)
}

1 change: 0 additions & 1 deletion entity/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package entity

import "encoding/json"


// RequestLandStrategy defines the possible source control integration methods.
type RequestLandStrategy string

Expand Down
8 changes: 4 additions & 4 deletions example/server/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ services:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: submitqueue
ports:
- "3306" # Random ephemeral port to avoid conflicts
- "3306" # Random ephemeral port to avoid conflicts
healthcheck:
# Use 127.0.0.1 (TCP) instead of localhost (Unix socket). MySQL treats
# "localhost" as a socket connection, which can be ready before the TCP
Expand All @@ -34,7 +34,7 @@ services:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: submitqueue
ports:
- "3306" # Random ephemeral port to avoid conflicts
- "3306" # Random ephemeral port to avoid conflicts
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-proot"]
interval: 5s
Expand All @@ -46,7 +46,7 @@ services:
context: ${REPO_ROOT}
dockerfile: example/server/gateway/Dockerfile
ports:
- "8080" # Random ephemeral port to avoid conflicts
- "8080" # Random ephemeral port to avoid conflicts
environment:
- PORT=:8080
# Application database connection
Expand All @@ -64,7 +64,7 @@ services:
context: ${REPO_ROOT}
dockerfile: example/server/orchestrator/Dockerfile
ports:
- "8080" # Random ephemeral port to avoid conflicts
- "8080" # Random ephemeral port to avoid conflicts
environment:
- PORT=:8080
# Application database connection (for request state, batches, etc.)
Expand Down
6 changes: 3 additions & 3 deletions example/server/gateway/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ services:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: submitqueue
ports:
- "3306" # Random ephemeral port to avoid conflicts
- "3306" # Random ephemeral port to avoid conflicts
healthcheck:
# Use 127.0.0.1 (TCP) instead of localhost (Unix socket). MySQL treats
# "localhost" as a socket connection, which can be ready before the TCP
Expand All @@ -34,7 +34,7 @@ services:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: submitqueue
ports:
- "3306" # Random ephemeral port to avoid conflicts
- "3306" # Random ephemeral port to avoid conflicts
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-proot"]
interval: 5s
Expand All @@ -46,7 +46,7 @@ services:
context: ${REPO_ROOT}
dockerfile: example/server/gateway/Dockerfile
ports:
- "8080" # Random ephemeral port to avoid conflicts
- "8080" # Random ephemeral port to avoid conflicts
environment:
- PORT=:8080
# Application database connection
Expand Down
6 changes: 3 additions & 3 deletions example/server/orchestrator/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ services:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: submitqueue
ports:
- "3306" # Random ephemeral port to avoid conflicts
- "3306" # Random ephemeral port to avoid conflicts
healthcheck:
# Use 127.0.0.1 (TCP) instead of localhost (Unix socket). MySQL treats
# "localhost" as a socket connection, which can be ready before the TCP
Expand All @@ -34,7 +34,7 @@ services:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: submitqueue
ports:
- "3306" # Random ephemeral port to avoid conflicts
- "3306" # Random ephemeral port to avoid conflicts
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-proot"]
interval: 5s
Expand All @@ -46,7 +46,7 @@ services:
context: ${REPO_ROOT}
dockerfile: example/server/orchestrator/Dockerfile
ports:
- "8080" # Random ephemeral port to avoid conflicts
- "8080" # Random ephemeral port to avoid conflicts
environment:
- PORT=:8080
# Application database connection (for request state, batches, etc.)
Expand Down
Loading