From 362267470ea1fbdfde85d9b26217a995b02d1674 Mon Sep 17 00:00:00 2001 From: Aleksey Shakhmatov Date: Mon, 23 Mar 2026 14:35:20 +0300 Subject: [PATCH] Add CI/publish workflows, tracer tests, fix Go version in CLAUDE.md Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitea/workflows/ci.yml | 23 +++++++ .gitea/workflows/publish.yml | 39 +++++++++++ CLAUDE.md | 2 +- tracer_test.go | 124 +++++++++++++++++++++++++++++++++++ 4 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitea/workflows/publish.yml create mode 100644 tracer_test.go diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..5da0ca4 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Vet + run: go vet ./... + + - name: Test + run: go test -race -count=1 ./... diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml new file mode 100644 index 0000000..b44a00e --- /dev/null +++ b/.gitea/workflows/publish.yml @@ -0,0 +1,39 @@ +name: Publish + +on: + push: + tags: ["v*"] + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Vet + run: go vet ./... + + - name: Test + run: go test -race -count=1 ./... + + - name: Publish to Gitea Package Registry + run: | + VERSION=${GITHUB_REF#refs/tags/} + MODULE=$(go list -m) + + # Create module zip with required prefix: module@version/ + git archive --format=zip --prefix="${MODULE}@${VERSION}/" HEAD -o module.zip + + # Gitea Go Package Registry API + curl -s -f \ + -X PUT \ + -H "Authorization: token ${{ secrets.PUBLISH_TOKEN }}" \ + -H "Content-Type: application/zip" \ + --data-binary @module.zip \ + "${{ github.server_url }}/api/packages/pkg/go/upload?module=${MODULE}&version=${VERSION}" + env: + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} diff --git a/CLAUDE.md b/CLAUDE.md index cbab9c3..f1f6ef2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,7 @@ go vet ./... # static analysis ## Architecture -- **Module**: `git.codelab.vc/pkg/obsx`, Go 1.24, depends on OpenTelemetry and Prometheus client_golang +- **Module**: `git.codelab.vc/pkg/obsx`, Go 1.25.7, depends on OpenTelemetry and Prometheus client_golang - **Single package** `obsx` ### Core patterns diff --git a/tracer_test.go b/tracer_test.go new file mode 100644 index 0000000..c68b855 --- /dev/null +++ b/tracer_test.go @@ -0,0 +1,124 @@ +package obsx + +import ( + "context" + "testing" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" +) + +func setupTestTracer(t *testing.T) *tracetest.InMemoryExporter { + t.Helper() + exp := tracetest.NewInMemoryExporter() + tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exp)) + otel.SetTracerProvider(tp) + t.Cleanup(func() { _ = tp.Shutdown(context.Background()) }) + return exp +} + +func TestSetupTracer_SetsGlobalProvider(t *testing.T) { + prev := otel.GetTracerProvider() + t.Cleanup(func() { otel.SetTracerProvider(prev) }) + + cfg := TracerConfig{ + ServiceName: "test-service", + Endpoint: "localhost:4317", + } + + shutdown, err := SetupTracer(context.Background(), cfg) + if err != nil { + t.Fatalf("SetupTracer returned error: %v", err) + } + t.Cleanup(func() { _ = shutdown(context.Background()) }) + + tp := otel.GetTracerProvider() + if _, ok := tp.(*sdktrace.TracerProvider); !ok { + t.Errorf("expected *sdktrace.TracerProvider, got %T", tp) + } +} + +func TestSetupTracer_DefaultSampler(t *testing.T) { + cfg := TracerConfig{Sampler: 0} + cfg.defaults() + + if cfg.Sampler != 1.0 { + t.Errorf("expected default sampler=1.0, got %f", cfg.Sampler) + } +} + +func TestSetupTracer_NegativeSampler(t *testing.T) { + cfg := TracerConfig{Sampler: -0.5} + cfg.defaults() + + if cfg.Sampler != 1.0 { + t.Errorf("expected default sampler=1.0 for negative value, got %f", cfg.Sampler) + } +} + +func TestSetupTracer_ServiceVersion(t *testing.T) { + prev := otel.GetTracerProvider() + t.Cleanup(func() { otel.SetTracerProvider(prev) }) + + exp := tracetest.NewInMemoryExporter() + + res, err := resource.New(context.Background(), + resource.WithAttributes( + semconv.ServiceName("version-test"), + semconv.ServiceVersion("1.2.3"), + ), + ) + if err != nil { + t.Fatalf("resource.New returned error: %v", err) + } + + tp := sdktrace.NewTracerProvider( + sdktrace.WithSyncer(exp), + sdktrace.WithResource(res), + ) + otel.SetTracerProvider(tp) + t.Cleanup(func() { _ = tp.Shutdown(context.Background()) }) + + _, span := StartSpan(context.Background(), "test-span") + span.End() + + spans := exp.GetSpans() + if len(spans) == 0 { + t.Fatal("expected at least one span") + } + + found := false + for _, attr := range spans[0].Resource.Attributes() { + if string(attr.Key) == "service.version" && attr.Value.AsString() == "1.2.3" { + found = true + break + } + } + if !found { + t.Errorf("service.version=1.2.3 not found in resource attributes: %v", spans[0].Resource.Attributes()) + } +} + +func TestStartSpan_CreatesSpan(t *testing.T) { + prev := otel.GetTracerProvider() + t.Cleanup(func() { otel.SetTracerProvider(prev) }) + + exp := setupTestTracer(t) + + ctx, span := StartSpan(context.Background(), "my-operation") + if ctx == nil { + t.Fatal("StartSpan returned nil context") + } + span.End() + + spans := exp.GetSpans() + if len(spans) == 0 { + t.Fatal("expected at least one span") + } + if spans[0].Name != "my-operation" { + t.Errorf("expected span name 'my-operation', got %q", spans[0].Name) + } +}