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
20 changes: 19 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- name: Run Lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.1.6
version: v2.13.0

test:
name: Test
Expand All @@ -55,3 +55,21 @@ jobs:
name: coverage-report
path: coverage.out
if-no-files-found: error

# The master ruleset requires a status check named exactly "Test and Lint"
# (the workflow name). Report it from one job that passes only when both
# Lint and Test succeeded, so the required context reflects both jobs.
test-and-lint:
name: Test and Lint
needs: [lint, test]
if: always()
runs-on: ubuntu-latest
steps:
- name: Require Lint and Test to succeed
env:
LINT_RESULT: ${{ needs.lint.result }}
TEST_RESULT: ${{ needs.test.result }}
run: |
echo "Lint: $LINT_RESULT, Test: $TEST_RESULT"
test "$LINT_RESULT" = success
test "$TEST_RESULT" = success
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,6 @@ clean: ## Clean up build artifacts and coverage reports.

tools: ## Install required development tools.
@echo "==> Installing development tools..."
@go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6
@go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.0

.PHONY: all lint update-deps test tidy fmt vet ci help cover-html clean tools
18 changes: 9 additions & 9 deletions kits/configkit/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func RegisterRequirementType(key string, tt reflect.Type) {
func registerRequirementType(key string, tt reflect.Type) {
// Unwrap pointers for introspection.
base := tt
for base.Kind() == reflect.Ptr {
for base.Kind() == reflect.Pointer {
base = base.Elem()
}

Expand Down Expand Up @@ -161,7 +161,7 @@ func Spec(req Requirement) ([]FieldSpec, error) {
}

func walkStruct(t reflect.Type, prefix string, out *[]FieldSpec) {
if t.Kind() == reflect.Ptr {
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
Expand Down Expand Up @@ -193,7 +193,7 @@ func walkStruct(t reflect.Type, prefix string, out *[]FieldSpec) {
// If struct, recurse; otherwise record leaf
ft := f.Type
base := ft
for base.Kind() == reflect.Ptr {
for base.Kind() == reflect.Pointer {
base = base.Elem()
}
switch base.Kind() {
Expand Down Expand Up @@ -280,7 +280,7 @@ func RegisterKnown(key string, sample any) {
if t == nil {
return
}
for t.Kind() == reflect.Ptr {
for t.Kind() == reflect.Pointer {
t = t.Elem()
}
knownMu.Lock()
Expand Down Expand Up @@ -443,7 +443,7 @@ func extractFieldAndRule(s string) (field, rule string) {
// yamlPathFromStructNS maps a validator StructNamespace (Go struct path) to a yaml-like path.
func yamlPathFromStructNS(ns string, root reflect.Type) string {
// Unwrap pointer
for root.Kind() == reflect.Ptr {
for root.Kind() == reflect.Pointer {
root = root.Elem()
}
if root.Kind() != reflect.Struct || ns == "" {
Expand Down Expand Up @@ -471,7 +471,7 @@ func yamlPathFromStructNS(ns string, root reflect.Type) string {
}
// next
t := f.Type
for t.Kind() == reflect.Ptr {
for t.Kind() == reflect.Pointer {
t = t.Elem()
}
cur = t
Expand All @@ -485,7 +485,7 @@ func yamlPathFromStructNS(ns string, root reflect.Type) string {
// --- Unknown key detection ---

func findUnknownKeys(y interface{}, t reflect.Type, prefix string) []string {
for t.Kind() == reflect.Ptr {
for t.Kind() == reflect.Pointer {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
Expand Down Expand Up @@ -526,7 +526,7 @@ func findUnknownKeys(y interface{}, t reflect.Type, prefix string) []string {
// Merge inline struct fields into allowed space for name existence checks.
for _, f := range inlineFields {
ft := f.Type
for ft.Kind() == reflect.Ptr {
for ft.Kind() == reflect.Pointer {
ft = ft.Elem()
}
if ft.Kind() != reflect.Struct {
Expand Down Expand Up @@ -561,7 +561,7 @@ func findUnknownKeys(y interface{}, t reflect.Type, prefix string) []string {
// Recurse only into struct fields; maps/slices allow arbitrary nested keys
f := allowed[ks]
ft := f.Type
for ft.Kind() == reflect.Ptr {
for ft.Kind() == reflect.Pointer {
ft = ft.Elem()
}
if ft.Kind() == reflect.Struct {
Expand Down
13 changes: 1 addition & 12 deletions kits/shutdownkit/fx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,12 @@ func TestModule_ProvidesShutdownContexts(t *testing.T) {

func TestShutdownLifecycle(t *testing.T) {
var sd ShutdownDeps
gracefulObserved := make(chan struct{}, 1)

app := fx.New(
shutdownkit.Module(),
fx.Provide(func() *zap.Logger { return zaptest.NewLogger(t) }),
fx.Invoke(func(d ShutdownDeps) {
sd = d
// observe graceful cancellation outside OnStop to avoid ordering issues
go func() {
<-sd.Graceful.Done()
gracefulObserved <- struct{}{}
}()
// run a managed goroutine
sd.WG.Add(1)
go func() {
Expand All @@ -94,10 +88,5 @@ func TestShutdownLifecycle(t *testing.T) {
defer cancelStop()
require.NoError(t, app.Stop(ctxStop))

select {
case <-gracefulObserved:
// ok
default:
t.Fatal("expected graceful context to be cancelled during Stop")
}
require.ErrorIs(t, sd.Graceful.Err(), context.Canceled)
}
10 changes: 10 additions & 0 deletions kits/signals/signal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"os/exec"
"os/signal"
"sync"
"syscall"
"testing"
Expand Down Expand Up @@ -275,6 +276,15 @@ func TestGracefulThenSignalIgnored_NoForce(t *testing.T) {
// Ensure that after graceful is canceled programmatically, a subsequent OS
// signal is effectively ignored by the handler loop (which exits on
// graceful Done) and does not flip the force context.
// The handler loop exits and calls signal.Stop once graceful is canceled,
// so SIGTERM would fall back to its default action and kill the test
// binary ("signal: terminated") whenever the loop wins that race. Keep a
// test-owned subscription so the self-sent signal is always consumed here
// and the assertion below is about the Shutdown, not process survival.
guard := make(chan os.Signal, 1)
signal.Notify(guard, syscall.SIGTERM)
defer signal.Stop(guard)

var wg sync.WaitGroup
s := sig.NewWithSignals(context.Background(), &wg)

Expand Down
13 changes: 9 additions & 4 deletions kits/telemetry/fx.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,11 @@ func applyConfigDefaults(cfg *Config) {
// buildResource creates the OTEL resource by merging attributes from the default
// resource, configuration, and runtime metadata package.
func buildResource(cfg Config) (*sdkresource.Resource, error) {
// Use the SDK default's schema URL. Pinning semconv.SchemaURL here conflicts
// once resource.Default() moves to a newer semantic convention.
base := sdkresource.Default()
schemaURL := base.SchemaURL()

// Standard attributes
attrs := []attribute.KeyValue{
semconv.ServiceName(cfg.ServiceName),
Expand All @@ -246,11 +251,11 @@ func buildResource(cfg Config) (*sdkresource.Resource, error) {
if *cfg.Disabled {
attrs = append(attrs, attribute.Bool("otel.sdk.disabled", true))
}
mainAttrs := sdkresource.NewWithAttributes(semconv.SchemaURL, attrs...)
mainAttrs := sdkresource.NewWithAttributes(schemaURL, attrs...)

// Extra attributes from runtime metadata package
metaAttrs := sdkresource.NewWithAttributes(
semconv.SchemaURL,
schemaURL,
runtimeinfo.OTELAttributes()...,
)

Expand All @@ -259,10 +264,10 @@ func buildResource(cfg Config) (*sdkresource.Resource, error) {
for k, v := range cfg.ResourceAttributes {
extraConfigAttrs = append(extraConfigAttrs, attribute.String(k, v))
}
extraAttrs := sdkresource.NewWithAttributes(semconv.SchemaURL, extraConfigAttrs...)
extraAttrs := sdkresource.NewWithAttributes(schemaURL, extraConfigAttrs...)

// Merge all resource sources.
res, err := sdkresource.Merge(sdkresource.Default(), mainAttrs)
res, err := sdkresource.Merge(base, mainAttrs)
if err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions kits/telemetry/fx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ func TestBuildResourceIncludesAttributes(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if res.SchemaURL() != sdkresource.Default().SchemaURL() {
t.Fatalf("resource schema URL = %q, want SDK default %q", res.SchemaURL(), sdkresource.Default().SchemaURL())
}
attrs := res.Attributes()
if !attrEquals(attrs, semconv.ServiceNameKey, "svc") {
t.Fatalf("missing service name attribute")
Expand Down
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[tools]
go = "1.24.0"
golangci-lint = "2.1.6"
golangci-lint = "2.13.0"
Loading