diff --git a/ddtrace/tracer/civisibility_payload.go b/ddtrace/tracer/civisibility_payload.go index df8ffc04cc..34685e8bbe 100644 --- a/ddtrace/tracer/civisibility_payload.go +++ b/ddtrace/tracer/civisibility_payload.go @@ -11,6 +11,7 @@ import ( "github.com/tinylib/msgp/msgp" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/version" ) @@ -46,6 +47,7 @@ func (p *ciVisibilityPayload) push(event *ciVisibilityEvent) error { // // A pointer to a newly initialized civisibilitypayload instance. func newCiVisibilityPayload() *ciVisibilityPayload { + log.Debug("ciVisibilityPayload: creating payload instance") return &ciVisibilityPayload{newPayload()} } @@ -61,6 +63,7 @@ func newCiVisibilityPayload() *ciVisibilityPayload { // A pointer to a bytes.Buffer containing the encoded CI Visibility payload. // An error if reading from the buffer or encoding the payload fails. func (p *ciVisibilityPayload) getBuffer(config *config) (*bytes.Buffer, error) { + log.Debug("ciVisibilityPayload: .getBuffer (count: %v)", p.itemCount()) /* The Payload format in the CI Visibility protocol is like this: diff --git a/ddtrace/tracer/civisibility_transport.go b/ddtrace/tracer/civisibility_transport.go index db64b5d73d..0731332022 100644 --- a/ddtrace/tracer/civisibility_transport.go +++ b/ddtrace/tracer/civisibility_transport.go @@ -105,6 +105,8 @@ func newCiVisibilityTransport(config *config) *ciVisibilityTransport { testCycleURL = fmt.Sprintf("%s/%s/%s", config.agentURL.String(), EvpProxyPath, TestCyclePath) } + log.Debug("ciVisibilityTransport: creating transport instance [agentless: %v, testcycleurl: %v]", agentlessEnabled, testCycleURL) + return &ciVisibilityTransport{ config: config, testCycleURLPath: testCycleURL, @@ -157,6 +159,7 @@ func (t *ciVisibilityTransport) send(p *payload) (body io.ReadCloser, err error) req.Header.Set("Content-Encoding", "gzip") } + log.Debug("ciVisibilityTransport: sending transport request: %v bytes", buffer.Len()) response, err := t.config.httpClient.Do(req) if err != nil { return nil, err diff --git a/ddtrace/tracer/civisibility_tslv.go b/ddtrace/tracer/civisibility_tslv.go index 377f6d5656..ef6614d48f 100644 --- a/ddtrace/tracer/civisibility_tslv.go +++ b/ddtrace/tracer/civisibility_tslv.go @@ -273,6 +273,7 @@ func getCiVisibilityEvent(span *span) *ciVisibilityEvent { // A pointer to the created ciVisibilityEvent. func createTestEventFromSpan(span *span) *ciVisibilityEvent { tSpan := createTslvSpan(span) + tSpan.ParentID = 0 tSpan.SessionID = getAndRemoveMetaToUInt64(span, constants.TestSessionIDTag) tSpan.ModuleID = getAndRemoveMetaToUInt64(span, constants.TestModuleIDTag) tSpan.SuiteID = getAndRemoveMetaToUInt64(span, constants.TestSuiteIDTag) @@ -298,6 +299,7 @@ func createTestEventFromSpan(span *span) *ciVisibilityEvent { // A pointer to the created ciVisibilityEvent. func createTestSuiteEventFromSpan(span *span) *ciVisibilityEvent { tSpan := createTslvSpan(span) + tSpan.ParentID = 0 tSpan.SessionID = getAndRemoveMetaToUInt64(span, constants.TestSessionIDTag) tSpan.ModuleID = getAndRemoveMetaToUInt64(span, constants.TestModuleIDTag) tSpan.SuiteID = getAndRemoveMetaToUInt64(span, constants.TestSuiteIDTag) @@ -320,6 +322,7 @@ func createTestSuiteEventFromSpan(span *span) *ciVisibilityEvent { // A pointer to the created ciVisibilityEvent. func createTestModuleEventFromSpan(span *span) *ciVisibilityEvent { tSpan := createTslvSpan(span) + tSpan.ParentID = 0 tSpan.SessionID = getAndRemoveMetaToUInt64(span, constants.TestSessionIDTag) tSpan.ModuleID = getAndRemoveMetaToUInt64(span, constants.TestModuleIDTag) return &ciVisibilityEvent{ @@ -341,6 +344,7 @@ func createTestModuleEventFromSpan(span *span) *ciVisibilityEvent { // A pointer to the created ciVisibilityEvent. func createTestSessionEventFromSpan(span *span) *ciVisibilityEvent { tSpan := createTslvSpan(span) + tSpan.ParentID = 0 tSpan.SessionID = getAndRemoveMetaToUInt64(span, constants.TestSessionIDTag) return &ciVisibilityEvent{ span: span, diff --git a/ddtrace/tracer/civisibility_writer.go b/ddtrace/tracer/civisibility_writer.go index 1582b200a8..969b5edea6 100644 --- a/ddtrace/tracer/civisibility_writer.go +++ b/ddtrace/tracer/civisibility_writer.go @@ -45,6 +45,7 @@ type ciVisibilityTraceWriter struct { // // A pointer to an initialized ciVisibilityTraceWriter. func newCiVisibilityTraceWriter(c *config) *ciVisibilityTraceWriter { + log.Debug("ciVisibilityTraceWriter: creating trace writer instance") return &ciVisibilityTraceWriter{ config: c, payload: newCiVisibilityPayload(), @@ -62,7 +63,7 @@ func (w *ciVisibilityTraceWriter) add(trace []*span) { for _, s := range trace { cvEvent := getCiVisibilityEvent(s) if err := w.payload.push(cvEvent); err != nil { - log.Error("Error encoding msgpack: %v", err) + log.Error("ciVisibilityTraceWriter: Error encoding msgpack: %v", err) } if w.payload.size() > agentlessPayloadSizeLimit { w.flush() @@ -104,16 +105,16 @@ func (w *ciVisibilityTraceWriter) flush() { var err error for attempt := 0; attempt <= w.config.sendRetries; attempt++ { size, count = p.size(), p.itemCount() - log.Debug("Sending payload: size: %d events: %d\n", size, count) + log.Debug("ciVisibilityTraceWriter: sending payload: size: %d events: %d\n", size, count) _, err = w.config.transport.send(p.payload) if err == nil { - log.Debug("sent events after %d attempts", attempt+1) + log.Debug("ciVisibilityTraceWriter: sent events after %d attempts", attempt+1) return } - log.Error("failure sending events (attempt %d), will retry: %v", attempt+1, err) + log.Error("ciVisibilityTraceWriter: failure sending events (attempt %d), will retry: %v", attempt+1, err) p.reset() time.Sleep(time.Millisecond) } - log.Error("lost %d events: %v", count, err) + log.Error("ciVisibilityTraceWriter: lost %d events: %v", count, err) }(oldp) } diff --git a/internal/civisibility/integrations/gotesting/instrumentation.go b/internal/civisibility/integrations/gotesting/instrumentation.go index 619de787ba..01abacc910 100644 --- a/internal/civisibility/integrations/gotesting/instrumentation.go +++ b/internal/civisibility/integrations/gotesting/instrumentation.go @@ -11,9 +11,11 @@ import ( "reflect" "runtime" "strings" + "sync" "sync/atomic" "testing" "time" + "unsafe" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations" @@ -23,6 +25,66 @@ import ( // The following functions are being used by the gotesting package for manual instrumentation and the orchestrion // automatic instrumentation +type ( + instrumentationMetadata struct { + IsInternal bool + } + + ddTestItem struct { + test integrations.DdTest + error atomic.Int32 + skipped atomic.Int32 + } +) + +var ( + // instrumentationMap holds a map of *runtime.Func for tracking instrumented functions + instrumentationMap = map[*runtime.Func]*instrumentationMetadata{} + + // instrumentationMapMutex is a read-write mutex for synchronizing access to instrumentationMap. + instrumentationMapMutex sync.RWMutex + + // ciVisibilityTests holds a map of *testing.T or *testing.B to civisibility.DdTest for tracking tests. + ciVisibilityTests = map[unsafe.Pointer]*ddTestItem{} + + // ciVisibilityTestsMutex is a read-write mutex for synchronizing access to ciVisibilityTests. + ciVisibilityTestsMutex sync.RWMutex +) + +// getInstrumentationMetadata gets the stored instrumentation metadata for a given *runtime.Func. +func getInstrumentationMetadata(fn *runtime.Func) *instrumentationMetadata { + instrumentationMapMutex.RLock() + defer instrumentationMapMutex.RUnlock() + if v, ok := instrumentationMap[fn]; ok { + return v + } + return nil +} + +// setInstrumentationMetadata stores an instrumentation metadata for a given *runtime.Func. +func setInstrumentationMetadata(fn *runtime.Func, metadata *instrumentationMetadata) { + instrumentationMapMutex.RLock() + defer instrumentationMapMutex.RUnlock() + instrumentationMap[fn] = metadata +} + +// getCiVisibilityTest retrieves the CI visibility test associated with a given *testing.T, *testing.B, *testing.common +func getCiVisibilityTest(tb testing.TB) *ddTestItem { + ciVisibilityTestsMutex.RLock() + defer ciVisibilityTestsMutex.RUnlock() + if v, ok := ciVisibilityTests[reflect.ValueOf(tb).UnsafePointer()]; ok { + return v + } + return nil +} + +// setCiVisibilityTest associates a CI visibility test with a given *testing.T, *testing.B, *testing.common +func setCiVisibilityTest(tb testing.TB, ciTest integrations.DdTest) { + ciVisibilityTestsMutex.Lock() + defer ciVisibilityTestsMutex.Unlock() + ciVisibilityTests[reflect.ValueOf(tb).UnsafePointer()] = &ddTestItem{test: ciTest} +} + // instrumentTestingM helper function to instrument internalTests and internalBenchmarks in a `*testing.M` instance. func instrumentTestingM(m *testing.M) func(exitCode int) { // Initialize CI Visibility @@ -61,13 +123,30 @@ func instrumentTestingTFunc(f func(*testing.T)) func(*testing.T) { moduleName, suiteName := utils.GetModuleAndSuiteName(fReflect.Pointer()) originalFunc := runtime.FuncForPC(fReflect.Pointer()) - // Increment the test count in the module. - atomic.AddInt32(modulesCounters[moduleName], 1) + // Avoid instrumenting twice + metadata := getInstrumentationMetadata(originalFunc) + if metadata != nil && metadata.IsInternal { + // If is an internal test, we don't instrument because f is already the instrumented func by executeInternalTest + return f + } + + instrumentedFn := func(t *testing.T) { + // Initialize module counters if not already present. + if _, ok := modulesCounters[moduleName]; !ok { + var v int32 + modulesCounters[moduleName] = &v + } + // Increment the test count in the module. + atomic.AddInt32(modulesCounters[moduleName], 1) - // Increment the test count in the suite. - atomic.AddInt32(suitesCounters[suiteName], 1) + // Initialize suite counters if not already present. + if _, ok := suitesCounters[suiteName]; !ok { + var v int32 + suitesCounters[suiteName] = &v + } + // Increment the test count in the suite. + atomic.AddInt32(suitesCounters[suiteName], 1) - return func(t *testing.T) { // Create or retrieve the module, suite, and test for CI visibility. module := session.GetOrCreateModuleWithFramework(moduleName, testFramework, runtime.Version()) suite := module.GetOrCreateSuite(suiteName) @@ -101,51 +180,47 @@ func instrumentTestingTFunc(f func(*testing.T)) func(*testing.T) { // Execute the original test function. f(t) } + setInstrumentationMetadata(runtime.FuncForPC(reflect.Indirect(reflect.ValueOf(instrumentedFn)).Pointer()), &instrumentationMetadata{IsInternal: true}) + return instrumentedFn } -// instrumentTestingTSetErrorInfo helper function to set an error in the `testing.T` CI Visibility span -func instrumentTestingTSetErrorInfo(t *testing.T, errType string, errMessage string, skip int) { - ciTest := getCiVisibilityTest(t) - if ciTest != nil { - ciTest.SetErrorInfo(errType, errMessage, utils.GetStacktrace(2+skip)) +// instrumentSetErrorInfo helper function to set an error in the `*testing.T, *testing.B, *testing.common` CI Visibility span +func instrumentSetErrorInfo(tb testing.TB, errType string, errMessage string, skip int) { + ciTestItem := getCiVisibilityTest(tb) + if ciTestItem != nil && ciTestItem.error.CompareAndSwap(0, 1) && ciTestItem.test != nil { + ciTestItem.test.SetErrorInfo(errType, errMessage, utils.GetStacktrace(2+skip)) } } -// instrumentTestingTCloseAndSkip helper function to close and skip with a reason a `testing.T` CI Visibility span -func instrumentTestingTCloseAndSkip(t *testing.T, skipReason string) { - ciTest := getCiVisibilityTest(t) - if ciTest != nil { - ciTest.CloseWithFinishTimeAndSkipReason(integrations.ResultStatusSkip, time.Now(), skipReason) +// instrumentCloseAndSkip helper function to close and skip with a reason a `*testing.T, *testing.B, *testing.common` CI Visibility span +func instrumentCloseAndSkip(tb testing.TB, skipReason string) { + ciTestItem := getCiVisibilityTest(tb) + if ciTestItem != nil && ciTestItem.skipped.CompareAndSwap(0, 1) && ciTestItem.test != nil { + ciTestItem.test.CloseWithFinishTimeAndSkipReason(integrations.ResultStatusSkip, time.Now(), skipReason) } } -// instrumentTestingTSkipNow helper function to close and skip a `testing.T` CI Visibility span -func instrumentTestingTSkipNow(t *testing.T) { - ciTest := getCiVisibilityTest(t) - if ciTest != nil { - ciTest.Close(integrations.ResultStatusSkip) +// instrumentSkipNow helper function to close and skip a `*testing.T, *testing.B, *testing.common` CI Visibility span +func instrumentSkipNow(tb testing.TB) { + ciTestItem := getCiVisibilityTest(tb) + if ciTestItem != nil && ciTestItem.skipped.CompareAndSwap(0, 1) && ciTestItem.test != nil { + ciTestItem.test.Close(integrations.ResultStatusSkip) } } // instrumentTestingBFunc helper function to instrument a benchmark function func(*testing.B) func instrumentTestingBFunc(pb *testing.B, name string, f func(*testing.B)) (string, func(*testing.B)) { - // Avoid instrumenting twice - if hasCiVisibilityBenchmarkFunc(&f) { - return name, f - } - // Reflect the function to obtain its pointer. fReflect := reflect.Indirect(reflect.ValueOf(f)) moduleName, suiteName := utils.GetModuleAndSuiteName(fReflect.Pointer()) originalFunc := runtime.FuncForPC(fReflect.Pointer()) - // Increment the test count in the module. - atomic.AddInt32(modulesCounters[moduleName], 1) - - // Increment the test count in the suite. - atomic.AddInt32(suitesCounters[suiteName], 1) + // Avoid instrumenting twice + if hasCiVisibilityBenchmarkFunc(originalFunc) { + return name, f + } - return subBenchmarkAutoName, func(b *testing.B) { + instrumentedFunc := func(b *testing.B) { // The sub-benchmark implementation relies on creating a dummy sub benchmark (called [DD:TestVisibility]) with // a Run over the original sub benchmark function to get the child results without interfering measurements // By doing this the name of the sub-benchmark are changed @@ -155,6 +230,22 @@ func instrumentTestingBFunc(pb *testing.B, name string, f func(*testing.B)) (str // benchmark/[DD:TestVisibility]/child // We use regex and decrement the depth level of the benchmark to restore the original name + // Initialize module counters if not already present. + if _, ok := modulesCounters[moduleName]; !ok { + var v int32 + modulesCounters[moduleName] = &v + } + // Increment the test count in the module. + atomic.AddInt32(modulesCounters[moduleName], 1) + + // Initialize suite counters if not already present. + if _, ok := suitesCounters[suiteName]; !ok { + var v int32 + suitesCounters[suiteName] = &v + } + // Increment the test count in the suite. + atomic.AddInt32(suitesCounters[suiteName], 1) + // Decrement level. bpf := getBenchmarkPrivateFields(b) bpf.AddLevel(-1) @@ -190,7 +281,7 @@ func instrumentTestingBFunc(pb *testing.B, name string, f func(*testing.B)) (str // Replace this function with the original one (executed only once - the first iteration[b.run1]). *iPfOfB.benchFunc = f // Set b to the CI visibility test. - setCiVisibilityBenchmark(b, test) + setCiVisibilityTest(b, test) // Enable the timer again. b.ResetTimer() @@ -200,8 +291,7 @@ func instrumentTestingBFunc(pb *testing.B, name string, f func(*testing.B)) (str f(b) } - setCiVisibilityBenchmarkFunc(&instrumentedFunc) - defer deleteCiVisibilityBenchmarkFunc(&instrumentedFunc) + setCiVisibilityBenchmarkFunc(runtime.FuncForPC(reflect.Indirect(reflect.ValueOf(instrumentedFunc)).Pointer())) b.Run(name, instrumentedFunc) endTime := time.Now() @@ -258,28 +348,7 @@ func instrumentTestingBFunc(pb *testing.B, name string, f func(*testing.B)) (str checkModuleAndSuite(module, suite) } -} - -// instrumentTestingBSetErrorInfo helper function to set an error in the `testing.B` CI Visibility span -func instrumentTestingBSetErrorInfo(b *testing.B, errType string, errMessage string, skip int) { - ciTest := getCiVisibilityBenchmark(b) - if ciTest != nil { - ciTest.SetErrorInfo(errType, errMessage, utils.GetStacktrace(2+skip)) - } -} - -// instrumentTestingBCloseAndSkip helper function to close and skip with a reason a `testing.B` CI Visibility span -func instrumentTestingBCloseAndSkip(b *testing.B, skipReason string) { - ciTest := getCiVisibilityBenchmark(b) - if ciTest != nil { - ciTest.CloseWithFinishTimeAndSkipReason(integrations.ResultStatusSkip, time.Now(), skipReason) - } -} - -// instrumentTestingBSkipNow helper function to close and skip a `testing.B` CI Visibility span -func instrumentTestingBSkipNow(b *testing.B) { - ciTest := getCiVisibilityBenchmark(b) - if ciTest != nil { - ciTest.Close(integrations.ResultStatusSkip) - } + setCiVisibilityBenchmarkFunc(originalFunc) + setCiVisibilityBenchmarkFunc(runtime.FuncForPC(reflect.Indirect(reflect.ValueOf(instrumentedFunc)).Pointer())) + return subBenchmarkAutoName, instrumentedFunc } diff --git a/internal/civisibility/integrations/gotesting/testing.go b/internal/civisibility/integrations/gotesting/testing.go index a90a2bcd94..91cc246fc9 100644 --- a/internal/civisibility/integrations/gotesting/testing.go +++ b/internal/civisibility/integrations/gotesting/testing.go @@ -128,7 +128,7 @@ func (ddm *M) instrumentInternalTests(internalTests *[]testing.InternalTest) { // executeInternalTest wraps the original test function to include CI visibility instrumentation. func (ddm *M) executeInternalTest(testInfo *testingTInfo) func(*testing.T) { originalFunc := runtime.FuncForPC(reflect.Indirect(reflect.ValueOf(testInfo.originalFunc)).Pointer()) - return func(t *testing.T) { + instrumentedFunc := func(t *testing.T) { // Create or retrieve the module, suite, and test for CI visibility. module := session.GetOrCreateModuleWithFramework(testInfo.moduleName, testFramework, runtime.Version()) suite := module.GetOrCreateSuite(testInfo.suiteName) @@ -165,6 +165,9 @@ func (ddm *M) executeInternalTest(testInfo *testingTInfo) func(*testing.T) { // Execute the original test function. testInfo.originalFunc(t) } + + setInstrumentationMetadata(runtime.FuncForPC(reflect.Indirect(reflect.ValueOf(instrumentedFunc)).Pointer()), &instrumentationMetadata{IsInternal: true}) + return instrumentedFunc } // instrumentInternalBenchmarks instruments the internal benchmarks for CI visibility. @@ -216,13 +219,13 @@ func (ddm *M) instrumentInternalBenchmarks(internalBenchmarks *[]testing.Interna // executeInternalBenchmark wraps the original benchmark function to include CI visibility instrumentation. func (ddm *M) executeInternalBenchmark(benchmarkInfo *testingBInfo) func(*testing.B) { - return func(b *testing.B) { + originalFunc := runtime.FuncForPC(reflect.Indirect(reflect.ValueOf(benchmarkInfo.originalFunc)).Pointer()) + instrumentedInternalFunc := func(b *testing.B) { // decrement level getBenchmarkPrivateFields(b).AddLevel(-1) startTime := time.Now() - originalFunc := runtime.FuncForPC(reflect.Indirect(reflect.ValueOf(benchmarkInfo.originalFunc)).Pointer()) module := session.GetOrCreateModuleWithFrameworkAndStartTime(benchmarkInfo.moduleName, testFramework, runtime.Version(), startTime) suite := module.GetOrCreateSuiteWithStartTime(benchmarkInfo.suiteName, startTime) test := suite.CreateTestWithStartTime(benchmarkInfo.testName, startTime) @@ -231,7 +234,7 @@ func (ddm *M) executeInternalBenchmark(benchmarkInfo *testingBInfo) func(*testin // Run the original benchmark function. var iPfOfB *benchmarkPrivateFields var recoverFunc *func(r any) - b.Run(b.Name(), func(b *testing.B) { + instrumentedFunc := func(b *testing.B) { // Stop the timer to perform initialization and replacements. b.StopTimer() @@ -253,13 +256,16 @@ func (ddm *M) executeInternalBenchmark(benchmarkInfo *testingBInfo) func(*testin // Replace the benchmark function with the original one (this must be executed only once - the first iteration[b.run1]). *iPfOfB.benchFunc = benchmarkInfo.originalFunc // Set the CI visibility benchmark. - setCiVisibilityBenchmark(b, test) + setCiVisibilityTest(b, test) // Restart the timer and execute the original benchmark function. b.ResetTimer() b.StartTimer() benchmarkInfo.originalFunc(b) - }) + } + + setCiVisibilityBenchmarkFunc(runtime.FuncForPC(reflect.Indirect(reflect.ValueOf(instrumentedFunc)).Pointer())) + b.Run(b.Name(), instrumentedFunc) endTime := time.Now() results := iPfOfB.result @@ -315,6 +321,9 @@ func (ddm *M) executeInternalBenchmark(benchmarkInfo *testingBInfo) func(*testin checkModuleAndSuite(module, suite) } + setCiVisibilityBenchmarkFunc(originalFunc) + setCiVisibilityBenchmarkFunc(runtime.FuncForPC(reflect.Indirect(reflect.ValueOf(instrumentedInternalFunc)).Pointer())) + return instrumentedInternalFunc } // RunM runs the tests and benchmarks using CI visibility. diff --git a/internal/civisibility/integrations/gotesting/testingB.go b/internal/civisibility/integrations/gotesting/testingB.go index 2fc80eafbd..b37bef009d 100644 --- a/internal/civisibility/integrations/gotesting/testingB.go +++ b/internal/civisibility/integrations/gotesting/testingB.go @@ -9,6 +9,7 @@ import ( "context" "fmt" "regexp" + "runtime" "sync" "testing" "time" @@ -17,20 +18,14 @@ import ( ) var ( - // ciVisibilityBenchmarks holds a map of *testing.B to civisibility.DdTest for tracking benchmarks. - ciVisibilityBenchmarks = map[*testing.B]integrations.DdTest{} - - // ciVisibilityBenchmarksMutex is a read-write mutex for synchronizing access to ciVisibilityBenchmarks. - ciVisibilityBenchmarksMutex sync.RWMutex - // subBenchmarkAutoName is a placeholder name for CI Visibility sub-benchmarks. subBenchmarkAutoName = "[DD:TestVisibility]" // subBenchmarkAutoNameRegex is a regex pattern to match the sub-benchmark auto name. subBenchmarkAutoNameRegex = regexp.MustCompile(`(?si)\/\[DD:TestVisibility\].*`) - // civisibilityBenchmarksFuncs holds a map of *func(*testing.B) for tracking instrumented functions - civisibilityBenchmarksFuncs = map[*func(*testing.B)]struct{}{} + // civisibilityBenchmarksFuncs holds a map of *runtime.Func for tracking instrumented functions + civisibilityBenchmarksFuncs = map[*runtime.Func]struct{}{} // civisibilityBenchmarksFuncsMutex is a read-write mutex for synchronizing access to civisibilityBenchmarksFuncs. civisibilityBenchmarksFuncsMutex sync.RWMutex @@ -59,9 +54,9 @@ func (ddb *B) Run(name string, f func(*testing.B)) bool { // integration tests. func (ddb *B) Context() context.Context { b := (*testing.B)(ddb) - ciTest := getCiVisibilityBenchmark(b) - if ciTest != nil { - return ciTest.Context() + ciTestItem := getCiVisibilityTest(b) + if ciTestItem != nil && ciTestItem.test != nil { + return ciTestItem.test.Context() } return context.Background() @@ -113,7 +108,7 @@ func (ddb *B) Skipf(format string, args ...any) { // during the test. Calling SkipNow does not stop those other goroutines. func (ddb *B) SkipNow() { b := (*testing.B)(ddb) - instrumentTestingBSkipNow(b) + instrumentSkipNow(b) b.SkipNow() } @@ -179,37 +174,18 @@ func (ddb *B) SetParallelism(p int) { (*testing.B)(ddb).SetParallelism(p) } func (ddb *B) getBWithError(errType string, errMessage string) *testing.B { b := (*testing.B)(ddb) - instrumentTestingBSetErrorInfo(b, errType, errMessage, 1) + instrumentSetErrorInfo(b, errType, errMessage, 1) return b } func (ddb *B) getBWithSkip(skipReason string) *testing.B { b := (*testing.B)(ddb) - instrumentTestingBCloseAndSkip(b, skipReason) + instrumentCloseAndSkip(b, skipReason) return b } -// getCiVisibilityBenchmark retrieves the CI visibility benchmark associated with a given *testing.B. -func getCiVisibilityBenchmark(b *testing.B) integrations.DdTest { - ciVisibilityBenchmarksMutex.RLock() - defer ciVisibilityBenchmarksMutex.RUnlock() - - if v, ok := ciVisibilityBenchmarks[b]; ok { - return v - } - - return nil -} - -// setCiVisibilityBenchmark associates a CI visibility benchmark with a given *testing.B. -func setCiVisibilityBenchmark(b *testing.B, ciTest integrations.DdTest) { - ciVisibilityBenchmarksMutex.Lock() - defer ciVisibilityBenchmarksMutex.Unlock() - ciVisibilityBenchmarks[b] = ciTest -} - -// hasCiVisibilityBenchmarkFunc gets if a func(*testing.B) is being instrumented. -func hasCiVisibilityBenchmarkFunc(fn *func(*testing.B)) bool { +// hasCiVisibilityBenchmarkFunc gets if a *runtime.Func is being instrumented. +func hasCiVisibilityBenchmarkFunc(fn *runtime.Func) bool { civisibilityBenchmarksFuncsMutex.RLock() defer civisibilityBenchmarksFuncsMutex.RUnlock() @@ -220,16 +196,9 @@ func hasCiVisibilityBenchmarkFunc(fn *func(*testing.B)) bool { return false } -// setCiVisibilityBenchmarkFunc tracks a func(*testing.B) as instrumented benchmark. -func setCiVisibilityBenchmarkFunc(fn *func(*testing.B)) { +// setCiVisibilityBenchmarkFunc tracks a *runtime.Func as instrumented benchmark. +func setCiVisibilityBenchmarkFunc(fn *runtime.Func) { civisibilityBenchmarksFuncsMutex.RLock() defer civisibilityBenchmarksFuncsMutex.RUnlock() civisibilityBenchmarksFuncs[fn] = struct{}{} } - -// deleteCiVisibilityBenchmarkFunc untracks a func(*testing.B) as instrumented benchmark. -func deleteCiVisibilityBenchmarkFunc(fn *func(*testing.B)) { - civisibilityBenchmarksFuncsMutex.RLock() - defer civisibilityBenchmarksFuncsMutex.RUnlock() - delete(civisibilityBenchmarksFuncs, fn) -} diff --git a/internal/civisibility/integrations/gotesting/testingT.go b/internal/civisibility/integrations/gotesting/testingT.go index 40e72a2122..e05f60abc8 100644 --- a/internal/civisibility/integrations/gotesting/testingT.go +++ b/internal/civisibility/integrations/gotesting/testingT.go @@ -8,21 +8,12 @@ package gotesting import ( "context" "fmt" - "sync" "testing" "time" "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations" ) -var ( - // ciVisibilityTests holds a map of *testing.T to civisibility.DdTest for tracking tests. - ciVisibilityTests = map[*testing.T]integrations.DdTest{} - - // ciVisibilityTestsMutex is a read-write mutex for synchronizing access to ciVisibilityTests. - ciVisibilityTestsMutex sync.RWMutex -) - // T is a type alias for testing.T to provide additional methods for CI visibility. type T testing.T @@ -49,9 +40,9 @@ func (ddt *T) Run(name string, f func(*testing.T)) bool { // integration tests. func (ddt *T) Context() context.Context { t := (*testing.T)(ddt) - ciTest := getCiVisibilityTest(t) - if ciTest != nil { - return ciTest.Context() + ciTestItem := getCiVisibilityTest(t) + if ciTestItem != nil && ciTestItem.test != nil { + return ciTestItem.test.Context() } return context.Background() @@ -103,7 +94,7 @@ func (ddt *T) Skipf(format string, args ...any) { // during the test. Calling SkipNow does not stop those other goroutines. func (ddt *T) SkipNow() { t := (*testing.T)(ddt) - instrumentTestingTSkipNow(t) + instrumentSkipNow(t) t.SkipNow() } @@ -128,31 +119,12 @@ func (ddt *T) Setenv(key, value string) { (*testing.T)(ddt).Setenv(key, value) } func (ddt *T) getTWithError(errType string, errMessage string) *testing.T { t := (*testing.T)(ddt) - instrumentTestingTSetErrorInfo(t, errType, errMessage, 1) + instrumentSetErrorInfo(t, errType, errMessage, 1) return t } func (ddt *T) getTWithSkip(skipReason string) *testing.T { t := (*testing.T)(ddt) - instrumentTestingTCloseAndSkip(t, skipReason) + instrumentCloseAndSkip(t, skipReason) return t } - -// getCiVisibilityTest retrieves the CI visibility test associated with a given *testing.T. -func getCiVisibilityTest(t *testing.T) integrations.DdTest { - ciVisibilityTestsMutex.RLock() - defer ciVisibilityTestsMutex.RUnlock() - - if v, ok := ciVisibilityTests[t]; ok { - return v - } - - return nil -} - -// setCiVisibilityTest associates a CI visibility test with a given *testing.T. -func setCiVisibilityTest(t *testing.T, ciTest integrations.DdTest) { - ciVisibilityTestsMutex.Lock() - defer ciVisibilityTestsMutex.Unlock() - ciVisibilityTests[t] = ciTest -} diff --git a/internal/civisibility/integrations/gotesting/testing_test.go b/internal/civisibility/integrations/gotesting/testing_test.go index e45e62d15d..4345e9e822 100644 --- a/internal/civisibility/integrations/gotesting/testing_test.go +++ b/internal/civisibility/integrations/gotesting/testing_test.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/httptest" "os" + "slices" "strconv" "testing" @@ -35,7 +36,10 @@ func TestMain(m *testing.M) { // or use a helper method gotesting.RunM(m) // os.Exit((*M)(m).Run()) - _ = RunM(m) + exit := RunM(m) + if exit != 0 { + os.Exit(exit) + } finishedSpans := mTracer.FinishedSpans() // 1 session span @@ -107,19 +111,28 @@ func Test_Foo(gt *testing.T) { assertTest(gt) t := (*T)(gt) var tests = []struct { + index byte name string input string want string }{ - {"yellow should return color", "yellow", "color"}, - {"banana should return fruit", "banana", "fruit"}, - {"duck should return animal", "duck", "animal"}, + {1, "yellow should return color", "yellow", "color"}, + {2, "banana should return fruit", "banana", "fruit"}, + {3, "duck should return animal", "duck", "animal"}, } + buf := []byte{} for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Log(test.name) + buf = append(buf, test.index) }) } + + expected := []byte{1, 2, 3} + if !slices.Equal(buf, expected) { + t.Error("error in subtests closure") + } } // TestWithExternalCalls demonstrates testing with external HTTP calls.