diff --git a/exporters/autoexport/exporter_test.go b/exporters/autoexport/exporter_test.go index 1e39d363d6e..9e7b0d0b500 100644 --- a/exporters/autoexport/exporter_test.go +++ b/exporters/autoexport/exporter_test.go @@ -22,13 +22,14 @@ import ( "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/trace" ) func TestOTLPExporterReturnedWhenNoEnvOrFallbackExporterConfigured(t *testing.T) { exporter, err := NewSpanExporter(context.Background()) assert.NoError(t, err) - assertOTLPHTTPExporter(t, exporter) + assertOTLPHTTPSpanExporter(t, exporter) } func TestFallbackExporterReturnedWhenNoEnvExporterConfigured(t *testing.T) { @@ -51,7 +52,7 @@ func TestEnvExporterIsPreferredOverFallbackExporter(t *testing.T) { WithFallbackSpanExporter(testExporter), ) assert.NoError(t, err) - assertOTLPHTTPExporter(t, exporter) + assertOTLPHTTPSpanExporter(t, exporter) } func TestEnvExporterOTLPOverHTTP(t *testing.T) { @@ -60,7 +61,7 @@ func TestEnvExporterOTLPOverHTTP(t *testing.T) { exporter, err := NewSpanExporter(context.Background()) assert.NoError(t, err) - assertOTLPHTTPExporter(t, exporter) + assertOTLPHTTPSpanExporter(t, exporter) } func TestEnvExporterOTLPOverGRPC(t *testing.T) { @@ -69,7 +70,7 @@ func TestEnvExporterOTLPOverGRPC(t *testing.T) { exporter, err := NewSpanExporter(context.Background()) assert.NoError(t, err) - assertOTLPGRPCExporter(t, exporter) + assertOTLPGRPCSpanExporter(t, exporter) } func TestEnvExporterOTLPOverGRPCOnlyProtocol(t *testing.T) { @@ -77,7 +78,7 @@ func TestEnvExporterOTLPOverGRPCOnlyProtocol(t *testing.T) { exporter, err := NewSpanExporter(context.Background()) assert.NoError(t, err) - assertOTLPGRPCExporter(t, exporter) + assertOTLPGRPCSpanExporter(t, exporter) } func TestEnvExporterOTLPInvalidProtocol(t *testing.T) { @@ -97,7 +98,19 @@ func TestEnvExporterNone(t *testing.T) { assert.True(t, IsNoneSpanExporter(exporter)) } -func assertOTLPHTTPExporter(t *testing.T, got trace.SpanExporter) { +func assertOTLPHTTPMetricReader(t *testing.T, got metric.Reader) { + t.Helper() + + if !assert.IsType(t, metric.NewPeriodicReader(nil), got) { + return + } + + // Implementation detail hack. This may break when bumping OTLP exporter modules as it uses unexported API. + clientType := reflect.Indirect(reflect.ValueOf(got)).FieldByName("exporter").Elem().Type().String() + assert.Equal(t, "*internal.exporter", clientType) +} + +func assertOTLPHTTPSpanExporter(t *testing.T, got trace.SpanExporter) { t.Helper() if !assert.IsType(t, &otlptrace.Exporter{}, got) { @@ -111,7 +124,7 @@ func assertOTLPHTTPExporter(t *testing.T, got trace.SpanExporter) { assert.False(t, IsNoneSpanExporter(got)) } -func assertOTLPGRPCExporter(t *testing.T, got trace.SpanExporter) { +func assertOTLPGRPCSpanExporter(t *testing.T, got trace.SpanExporter) { t.Helper() if !assert.IsType(t, &otlptrace.Exporter{}, got) { diff --git a/exporters/autoexport/registry_test.go b/exporters/autoexport/registry_test.go index 96bf32d8a19..6dc8c084319 100644 --- a/exporters/autoexport/registry_test.go +++ b/exporters/autoexport/registry_test.go @@ -29,6 +29,22 @@ import ( "go.opentelemetry.io/otel/sdk/trace" ) +type testType struct{ string } + +func factory(val string) func(ctx context.Context) (*testType, error) { + return func(ctx context.Context) (*testType, error) { return &testType{val}, nil } +} + +func newTestRegistry() registry[*testType] { + return registry[*testType]{ + names: map[string]func(context.Context) (*testType, error){ + "": factory(""), + "otlp": factory("otlp"), + "none": factory("none"), + }, + } +} + var stdoutMetricFactory = func(ctx context.Context) (metric.Reader, error) { exp, err := stdoutmetric.New() if err != nil { @@ -46,45 +62,27 @@ var stdoutSpanFactory = func(ctx context.Context) (trace.SpanExporter, error) { } func TestCanStoreExporterFactory(t *testing.T) { - t.Run("spans", func(t *testing.T) { - r := newSpanExporterRegistry() - assert.NotPanics(t, func() { - require.NoError(t, r.store("first", stdoutSpanFactory)) - }) - }) - t.Run("metrics", func(t *testing.T) { - r := newMetricReaderRegistry() - assert.NotPanics(t, func() { - require.NoError(t, r.store("first", stdoutMetricFactory)) - }) + r := newTestRegistry() + assert.NotPanics(t, func() { + require.NoError(t, r.store("first", factory("first"))) }) } func TestLoadOfUnknownExporterReturnsError(t *testing.T) { - t.Run("spans", func(t *testing.T) { - r := newSpanExporterRegistry() - assert.NotPanics(t, func() { - exp, err := r.load(context.Background(), "non-existent") - assert.Equal(t, err, errUnknownExporter, "empty registry should hold nothing") - assert.Nil(t, exp, "non-nil exporter returned") - }) - }) - t.Run("metrics", func(t *testing.T) { - r := newMetricReaderRegistry() - assert.NotPanics(t, func() { - exp, err := r.load(context.Background(), "non-existent") - assert.Equal(t, err, errUnknownExporter, "empty registry should hold nothing") - assert.Nil(t, exp, "non-nil exporter returned") - }) + r := newTestRegistry() + assert.NotPanics(t, func() { + exp, err := r.load(context.Background(), "non-existent") + assert.Equal(t, err, errUnknownExporter, "empty registry should hold nothing") + assert.Nil(t, exp, "non-nil exporter returned") }) } func TestRegistryIsConcurrentSafe(t *testing.T) { const exporterName = "stdout" - r := newSpanExporterRegistry() + r := newTestRegistry() assert.NotPanics(t, func() { - require.NoError(t, r.store(exporterName, stdoutSpanFactory)) + require.NoError(t, r.store(exporterName, factory("stdout"))) }) var wg sync.WaitGroup @@ -93,7 +91,7 @@ func TestRegistryIsConcurrentSafe(t *testing.T) { go func() { defer wg.Done() assert.NotPanics(t, func() { - require.ErrorIs(t, r.store(exporterName, stdoutSpanFactory), errDuplicateRegistration) + require.ErrorIs(t, r.store(exporterName, factory("stdout")), errDuplicateRegistration) }) }() @@ -101,55 +99,100 @@ func TestRegistryIsConcurrentSafe(t *testing.T) { go func() { defer wg.Done() assert.NotPanics(t, func() { - exp, err := r.load(context.Background(), exporterName) + _, err := r.load(context.Background(), exporterName) assert.NoError(t, err, "missing exporter in registry") - assert.IsType(t, &stdouttrace.Exporter{}, exp) }) }() wg.Wait() } -func TestSubsequentCallsToGetExporterReturnsNewInstances(t *testing.T) { +type funcs[T any] struct { + makeExporter func(context.Context, string) (T, error) + assertOTLPHTTP func(*testing.T, T) + registerFactory func(string, func(context.Context) (T, error)) + stdoutFactory func(ctx context.Context) (T, error) + registry *registry[T] +} + +var ( + spanFuncs = funcs[trace.SpanExporter]{ + makeExporter: spanExporter, + assertOTLPHTTP: assertOTLPHTTPSpanExporter, + registerFactory: RegisterSpanExporter, + stdoutFactory: stdoutSpanFactory, + registry: &spanExporterRegistry, + } + + metricFuncs = funcs[metric.Reader]{ + makeExporter: metricReader, + assertOTLPHTTP: assertOTLPHTTPMetricReader, + registerFactory: RegisterMetricReader, + stdoutFactory: stdoutMetricFactory, + registry: &metricReaderRegistry, + } +) + +func (f funcs[T]) testSubsequentCallsToGetExporterReturnsNewInstances(t *testing.T) { const exporterType = "otlp" - exp1, err := spanExporter(context.Background(), exporterType) + + exp1, err := f.makeExporter(context.Background(), exporterType) assert.NoError(t, err) - assertOTLPHTTPExporter(t, exp1) + f.assertOTLPHTTP(t, exp1) exp2, err := spanExporter(context.Background(), exporterType) assert.NoError(t, err) - assertOTLPHTTPExporter(t, exp2) + assertOTLPHTTPSpanExporter(t, exp2) assert.NotSame(t, exp1, exp2) } -func TestDefaultOTLPExporterFactoriesAreAutomaticallyRegistered(t *testing.T) { - exp1, err := spanExporter(context.Background(), "") +func TestSubsequentCallsToGetExporterReturnsNewInstances(t *testing.T) { + t.Run("spans", spanFuncs.testSubsequentCallsToGetExporterReturnsNewInstances) + t.Run("metrics", metricFuncs.testSubsequentCallsToGetExporterReturnsNewInstances) +} + +func (f funcs[T]) testDefaultOTLPExporterFactoriesAreAutomaticallyRegistered(t *testing.T) { + exp1, err := f.makeExporter(context.Background(), "") assert.Nil(t, err) - assertOTLPHTTPExporter(t, exp1) + f.assertOTLPHTTP(t, exp1) - exp2, err := spanExporter(context.Background(), "otlp") + exp2, err := f.makeExporter(context.Background(), "otlp") assert.Nil(t, err) - assertOTLPHTTPExporter(t, exp2) + f.assertOTLPHTTP(t, exp2) } -func TestEnvRegistryCanRegisterExporterFactory(t *testing.T) { +func TestDefaultOTLPExporterFactoriesAreAutomaticallyRegistered(t *testing.T) { + t.Run("spans", spanFuncs.testDefaultOTLPExporterFactoriesAreAutomaticallyRegistered) + t.Run("metrics", metricFuncs.testDefaultOTLPExporterFactoriesAreAutomaticallyRegistered) +} + +func (f funcs[T]) testEnvRegistryCanRegisterExporterFactory(t *testing.T) { const exporterName = "custom" - RegisterSpanExporter(exporterName, stdoutSpanFactory) - t.Cleanup(func() { spanExporterRegistry.drop(exporterName) }) + f.registerFactory(exporterName, f.stdoutFactory) + t.Cleanup(func() { f.registry.drop(exporterName) }) - exp, err := spanExporterRegistry.load(context.Background(), exporterName) + _, err := f.registry.load(context.Background(), exporterName) assert.Nil(t, err, "missing exporter in envRegistry") - assert.IsType(t, &stdouttrace.Exporter{}, exp) } -func TestEnvRegistryPanicsOnDuplicateRegisterCalls(t *testing.T) { +func TestEnvRegistryCanRegisterExporterFactory(t *testing.T) { + t.Run("spans", spanFuncs.testEnvRegistryCanRegisterExporterFactory) + t.Run("metrics", metricFuncs.testEnvRegistryCanRegisterExporterFactory) +} + +func (f funcs[T]) testEnvRegistryPanicsOnDuplicateRegisterCalls(t *testing.T) { const exporterName = "custom" - RegisterSpanExporter(exporterName, stdoutSpanFactory) - t.Cleanup(func() { spanExporterRegistry.drop(exporterName) }) + f.registerFactory(exporterName, f.stdoutFactory) + t.Cleanup(func() { f.registry.drop(exporterName) }) errString := fmt.Sprintf("%s: %q", errDuplicateRegistration, exporterName) assert.PanicsWithError(t, errString, func() { - RegisterSpanExporter(exporterName, stdoutSpanFactory) + f.registerFactory(exporterName, f.stdoutFactory) }) } + +func TestEnvRegistryPanicsOnDuplicateRegisterCalls(t *testing.T) { + t.Run("spans", spanFuncs.testEnvRegistryPanicsOnDuplicateRegisterCalls) + t.Run("metrics", metricFuncs.testEnvRegistryPanicsOnDuplicateRegisterCalls) +}