Skip to content

Commit

Permalink
[CI Visibility] Manual Api and Go/Testing integration (#2742)
Browse files Browse the repository at this point in the history
Co-authored-by: liashenko <[email protected]>
  • Loading branch information
tonyredondo and liashenko authored Jul 5, 2024
1 parent 6d9882b commit 93cfbc8
Show file tree
Hide file tree
Showing 14 changed files with 3,036 additions and 0 deletions.
118 changes: 118 additions & 0 deletions internal/civisibility/integrations/civisibility.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2024 Datadog, Inc.

package integrations

import (
"os"
"os/signal"
"regexp"
"strings"
"sync"
"syscall"

"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/constants"
"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils"
)

// ciVisibilityCloseAction defines an action to be executed when CI visibility is closing.
type ciVisibilityCloseAction func()

var (
// ciVisibilityInitializationOnce ensures we initialize the CI visibility tracer only once.
ciVisibilityInitializationOnce sync.Once

// closeActions holds CI visibility close actions.
closeActions []ciVisibilityCloseAction

// closeActionsMutex synchronizes access to closeActions.
closeActionsMutex sync.Mutex

// mTracer contains the mock tracer instance for testing purposes
mTracer mocktracer.Tracer
)

// EnsureCiVisibilityInitialization initializes the CI visibility tracer if it hasn't been initialized already.
func EnsureCiVisibilityInitialization() {
internalCiVisibilityInitialization(func(opts []tracer.StartOption) {
// Initialize the tracer.
tracer.Start(opts...)
})
}

// InitializeCIVisibilityMock initialize the mocktracer for CI Visibility usage
func InitializeCIVisibilityMock() mocktracer.Tracer {
internalCiVisibilityInitialization(func([]tracer.StartOption) {
// Initialize the mocktracer
mTracer = mocktracer.Start()
})
return mTracer
}

func internalCiVisibilityInitialization(tracerInitializer func([]tracer.StartOption)) {
ciVisibilityInitializationOnce.Do(func() {
// Since calling this method indicates we are in CI Visibility mode, set the environment variable.
_ = os.Setenv(constants.CiVisibilityEnabledEnvironmentVariable, "1")

Check failure on line 59 in internal/civisibility/integrations/civisibility.go

View workflow job for this annotation

GitHub Actions / go get -u smoke test

undefined: constants.CiVisibilityEnabledEnvironmentVariable

Check failure on line 59 in internal/civisibility/integrations/civisibility.go

View workflow job for this annotation

GitHub Actions / PR Unit and Integration Tests / test-core

undefined: constants.CiVisibilityEnabledEnvironmentVariable

// Avoid sampling rate warning (in CI Visibility mode we send all data)
_ = os.Setenv("DD_TRACE_SAMPLE_RATE", "1")

// Preload the CodeOwner file
_ = utils.GetCodeOwners()

// Preload all CI, Git, and CodeOwners tags.
ciTags := utils.GetCiTags()

Check failure on line 68 in internal/civisibility/integrations/civisibility.go

View workflow job for this annotation

GitHub Actions / go get -u smoke test

undefined: utils.GetCiTags

Check failure on line 68 in internal/civisibility/integrations/civisibility.go

View workflow job for this annotation

GitHub Actions / PR Unit and Integration Tests / test-core

undefined: utils.GetCiTags

// Check if DD_SERVICE has been set; otherwise default to the repo name (from the spec).
var opts []tracer.StartOption
if v := os.Getenv("DD_SERVICE"); v == "" {
if repoURL, ok := ciTags[constants.GitRepositoryURL]; ok {
// regex to sanitize the repository url to be used as a service name
repoRegex := regexp.MustCompile(`(?m)/([a-zA-Z0-9\\\-_.]*)$`)
matches := repoRegex.FindStringSubmatch(repoURL)
if len(matches) > 1 {
repoURL = strings.TrimSuffix(matches[1], ".git")
}
opts = append(opts, tracer.WithService(repoURL))
}
}

// Initialize the tracer
tracerInitializer(opts)

// Handle SIGINT and SIGTERM signals to ensure we close all open spans and flush the tracer before exiting
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-signals
ExitCiVisibility()
os.Exit(1)
}()
})
}

// PushCiVisibilityCloseAction adds a close action to be executed when CI visibility exits.
func PushCiVisibilityCloseAction(action ciVisibilityCloseAction) {
closeActionsMutex.Lock()
defer closeActionsMutex.Unlock()
closeActions = append([]ciVisibilityCloseAction{action}, closeActions...)
}

// ExitCiVisibility executes all registered close actions and stops the tracer.
func ExitCiVisibility() {
closeActionsMutex.Lock()
defer closeActionsMutex.Unlock()
defer func() {
closeActions = []ciVisibilityCloseAction{}

tracer.Flush()
tracer.Stop()
}()
for _, v := range closeActions {
v()
}
}
103 changes: 103 additions & 0 deletions internal/civisibility/integrations/gotesting/reflections.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2024 Datadog, Inc.

package gotesting

import (
"errors"
"reflect"
"sync"
"testing"
"unsafe"
)

// getFieldPointerFrom gets an unsafe.Pointer (gc-safe type of pointer) to a struct field
// useful to get or set values to private field
func getFieldPointerFrom(value any, fieldName string) (unsafe.Pointer, error) {
indirectValue := reflect.Indirect(reflect.ValueOf(value))
member := indirectValue.FieldByName(fieldName)
if member.IsValid() {
return unsafe.Pointer(member.UnsafeAddr()), nil
}

return unsafe.Pointer(nil), errors.New("member is invalid")
}

// TESTING

// getInternalTestArray gets the pointer to the testing.InternalTest array inside a
// testing.M instance containing all the "root" tests
func getInternalTestArray(m *testing.M) *[]testing.InternalTest {
if ptr, err := getFieldPointerFrom(m, "tests"); err == nil {
return (*[]testing.InternalTest)(ptr)
}
return nil
}

// BENCHMARKS

// get the pointer to the internal benchmark array
// getInternalBenchmarkArray gets the pointer to the testing.InternalBenchmark array inside
// a testing.M instance containing all the "root" benchmarks
func getInternalBenchmarkArray(m *testing.M) *[]testing.InternalBenchmark {
if ptr, err := getFieldPointerFrom(m, "benchmarks"); err == nil {
return (*[]testing.InternalBenchmark)(ptr)
}
return nil
}

// commonPrivateFields is collection of required private fields from testing.common
type commonPrivateFields struct {
mu *sync.RWMutex
level *int
name *string // Name of test or benchmark.
}

// AddLevel increase or decrease the testing.common.level field value, used by
// testing.B to create the name of the benchmark test
func (c *commonPrivateFields) AddLevel(delta int) int {
c.mu.Lock()
defer c.mu.Unlock()
*c.level = *c.level + delta
return *c.level
}

// benchmarkPrivateFields is a collection of required private fields from testing.B
// also contains a pointer to the original testing.B for easy access
type benchmarkPrivateFields struct {
commonPrivateFields
B *testing.B
benchFunc *func(b *testing.B)
result *testing.BenchmarkResult
}

// getBenchmarkPrivateFields is a method to retrieve all required privates field from
// testing.B, returning a benchmarkPrivateFields instance
func getBenchmarkPrivateFields(b *testing.B) *benchmarkPrivateFields {
benchFields := &benchmarkPrivateFields{
B: b,
}

// common
if ptr, err := getFieldPointerFrom(b, "mu"); err == nil {
benchFields.mu = (*sync.RWMutex)(ptr)
}
if ptr, err := getFieldPointerFrom(b, "level"); err == nil {
benchFields.level = (*int)(ptr)
}
if ptr, err := getFieldPointerFrom(b, "name"); err == nil {
benchFields.name = (*string)(ptr)
}

// benchmark
if ptr, err := getFieldPointerFrom(b, "benchFunc"); err == nil {
benchFields.benchFunc = (*func(b *testing.B))(ptr)
}
if ptr, err := getFieldPointerFrom(b, "result"); err == nil {
benchFields.result = (*testing.BenchmarkResult)(ptr)
}

return benchFields
}
150 changes: 150 additions & 0 deletions internal/civisibility/integrations/gotesting/reflections_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2024 Datadog, Inc.

package gotesting

import (
"sync"
"testing"

"github.com/stretchr/testify/assert"
)

// TestGetFieldPointerFrom tests the getFieldPointerFrom function.
func TestGetFieldPointerFrom(t *testing.T) {
// Create a mock struct with a private field
mockStruct := struct {
privateField string
}{
privateField: "testValue",
}

// Attempt to get a pointer to the private field
ptr, err := getFieldPointerFrom(&mockStruct, "privateField")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}

if ptr == nil {
t.Fatal("Expected a valid pointer, got nil")
}

// Dereference the pointer to get the actual value
actualValue := (*string)(ptr)
if *actualValue != mockStruct.privateField {
t.Fatalf("Expected 'testValue', got %s", *actualValue)
}

// Modify the value through the pointer
*actualValue = "modified value"
if *actualValue != mockStruct.privateField {
t.Fatalf("Expected 'modified value', got %s", mockStruct.privateField)
}

// Attempt to get a pointer to a non-existent field
_, err = getFieldPointerFrom(&mockStruct, "nonExistentField")
if err == nil {
t.Fatal("Expected an error for non-existent field, got nil")
}
}

// TestGetInternalTestArray tests the getInternalTestArray function.
func TestGetInternalTestArray(t *testing.T) {
assert := assert.New(t)

// Get the internal test array from the mock testing.M
tests := getInternalTestArray(currentM)
assert.NotNil(tests)

// Check that the test array contains the expected test
var testNames []string
for _, v := range *tests {
testNames = append(testNames, v.Name)
assert.NotNil(v.F)
}

assert.Contains(testNames, "TestGetFieldPointerFrom")
assert.Contains(testNames, "TestGetInternalTestArray")
assert.Contains(testNames, "TestGetInternalBenchmarkArray")
assert.Contains(testNames, "TestCommonPrivateFields_AddLevel")
assert.Contains(testNames, "TestGetBenchmarkPrivateFields")
}

// TestGetInternalBenchmarkArray tests the getInternalBenchmarkArray function.
func TestGetInternalBenchmarkArray(t *testing.T) {
assert := assert.New(t)

// Get the internal benchmark array from the mock testing.M
benchmarks := getInternalBenchmarkArray(currentM)
assert.NotNil(benchmarks)

// Check that the benchmark array contains the expected benchmark
var testNames []string
for _, v := range *benchmarks {
testNames = append(testNames, v.Name)
assert.NotNil(v.F)
}

assert.Contains(testNames, "BenchmarkDummy")
}

// TestCommonPrivateFields_AddLevel tests the AddLevel method of commonPrivateFields.
func TestCommonPrivateFields_AddLevel(t *testing.T) {
// Create a commonPrivateFields struct with a mutex and a level
level := 1
commonFields := &commonPrivateFields{
mu: &sync.RWMutex{},
level: &level,
}

// Add a level and check the new level
newLevel := commonFields.AddLevel(1)
if newLevel != 2 || newLevel != *commonFields.level {
t.Fatalf("Expected level to be 2, got %d", newLevel)
}

// Subtract a level and check the new level
newLevel = commonFields.AddLevel(-1)
if newLevel != 1 || newLevel != *commonFields.level {
t.Fatalf("Expected level to be 1, got %d", newLevel)
}
}

// TestGetBenchmarkPrivateFields tests the getBenchmarkPrivateFields function.
func TestGetBenchmarkPrivateFields(t *testing.T) {
// Create a new testing.B instance
b := &testing.B{}

// Get the private fields of the benchmark
benchFields := getBenchmarkPrivateFields(b)
if benchFields == nil {
t.Fatal("Expected a valid benchmarkPrivateFields, got nil")
}

// Set values to the private fields
*benchFields.name = "BenchmarkTest"
*benchFields.level = 1
*benchFields.benchFunc = func(b *testing.B) {}
*benchFields.result = testing.BenchmarkResult{}

// Check that the private fields have the expected values
if benchFields.level == nil || *benchFields.level != 1 {
t.Fatalf("Expected level to be 1, got %v", *benchFields.level)
}

if benchFields.name == nil || *benchFields.name != b.Name() {
t.Fatalf("Expected name to be 'BenchmarkTest', got %v", *benchFields.name)
}

if benchFields.benchFunc == nil {
t.Fatal("Expected benchFunc to be set, got nil")
}

if benchFields.result == nil {
t.Fatal("Expected result to be set, got nil")
}
}

func BenchmarkDummy(*testing.B) {}
Loading

0 comments on commit 93cfbc8

Please sign in to comment.