about summary refs log tree commit diff
path: root/main/civisibility/integrations/gotesting/testing_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'main/civisibility/integrations/gotesting/testing_test.go')
-rw-r--r--main/civisibility/integrations/gotesting/testing_test.go354
1 files changed, 354 insertions, 0 deletions
diff --git a/main/civisibility/integrations/gotesting/testing_test.go b/main/civisibility/integrations/gotesting/testing_test.go
new file mode 100644
index 0000000..bf3b48e
--- /dev/null
+++ b/main/civisibility/integrations/gotesting/testing_test.go
@@ -0,0 +1,354 @@
+// 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 (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"strconv"
+	"testing"
+
+	"ci-visibility-test-github/main/civisibility/constants"
+	"ci-visibility-test-github/main/civisibility/integrations"
+
+	ddhttp "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
+	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
+	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer"
+	ddtracer "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
+
+	"github.com/stretchr/testify/assert"
+)
+
+var currentM *testing.M
+var mTracer mocktracer.Tracer
+
+// TestMain is the entry point for testing and runs before any test.
+func TestMain(m *testing.M) {
+	currentM = m
+	mTracer = integrations.InitializeCIVisibilityMock()
+
+	// (*M)(m).Run() cast m to gotesting.M and just run
+	// or use a helper method gotesting.RunM(m)
+
+	// os.Exit((*M)(m).Run())
+	_ = RunM(m)
+
+	finishedSpans := mTracer.FinishedSpans()
+	// 1 session span
+	// 1 module span
+	// 1 suite span (optional 1 from reflections_test.go)
+	// 6 tests spans
+	// 7 sub stest spans
+	// 2 normal spans (from integration tests)
+	// 1 benchmark span (optional - require the -bench option)
+	if len(finishedSpans) < 17 {
+		panic("expected at least 17 finished spans, got " + strconv.Itoa(len(finishedSpans)))
+	}
+
+	sessionSpans := getSpansWithType(finishedSpans, constants.SpanTypeTestSession)
+	if len(sessionSpans) != 1 {
+		panic("expected exactly 1 session span, got " + strconv.Itoa(len(sessionSpans)))
+	}
+
+	moduleSpans := getSpansWithType(finishedSpans, constants.SpanTypeTestModule)
+	if len(moduleSpans) != 1 {
+		panic("expected exactly 1 module span, got " + strconv.Itoa(len(moduleSpans)))
+	}
+
+	suiteSpans := getSpansWithType(finishedSpans, constants.SpanTypeTestSuite)
+	if len(suiteSpans) < 1 {
+		panic("expected at least 1 suite span, got " + strconv.Itoa(len(suiteSpans)))
+	}
+
+	testSpans := getSpansWithType(finishedSpans, constants.SpanTypeTest)
+	if len(testSpans) < 12 {
+		panic("expected at least 12 suite span, got " + strconv.Itoa(len(testSpans)))
+	}
+
+	httpSpans := getSpansWithType(finishedSpans, ext.SpanTypeHTTP)
+	if len(httpSpans) != 2 {
+		panic("expected exactly 2 normal spans, got " + strconv.Itoa(len(httpSpans)))
+	}
+
+	os.Exit(0)
+}
+
+// TestMyTest01 demonstrates instrumentation of InternalTests
+func TestMyTest01(t *testing.T) {
+	assertTest(t)
+}
+
+// TestMyTest02 demonstrates instrumentation of subtests.
+func TestMyTest02(gt *testing.T) {
+	assertTest(gt)
+
+	// To instrument subTests we just need to cast
+	// testing.T to our gotesting.T
+	// using: newT := (*gotesting.T)(t)
+	// Then all testing.T will be available but instrumented
+	t := (*T)(gt)
+	// or
+	t = GetTest(gt)
+
+	t.Run("sub01", func(oT2 *testing.T) {
+		t2 := (*T)(oT2) // Cast the sub-test to gotesting.T
+		t2.Log("From sub01")
+		t2.Run("sub03", func(t3 *testing.T) {
+			t3.Log("From sub03")
+		})
+	})
+}
+
+func Test_Foo(gt *testing.T) {
+	assertTest(gt)
+	t := (*T)(gt)
+	var tests = []struct {
+		name  string
+		input string
+		want  string
+	}{
+		{"yellow should return color", "yellow", "color"},
+		{"banana should return fruit", "banana", "fruit"},
+		{"duck should return animal", "duck", "animal"},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			t.Log(test.name)
+		})
+	}
+}
+
+// TestWithExternalCalls demonstrates testing with external HTTP calls.
+func TestWithExternalCalls(gt *testing.T) {
+	assertTest(gt)
+	t := (*T)(gt)
+
+	// Create a new HTTP test server
+	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		_, _ = w.Write([]byte("Hello World"))
+	}))
+	defer s.Close()
+
+	t.Run("default", func(t *testing.T) {
+
+		// if we want to use the test span as a parent of a child span
+		// we can extract the SpanContext and use it in other integrations
+		ctx := (*T)(t).Context()
+
+		// Wrap the default HTTP transport for tracing
+		rt := ddhttp.WrapRoundTripper(http.DefaultTransport)
+		client := &http.Client{
+			Transport: rt,
+		}
+
+		// Create a new HTTP request
+		req, err := http.NewRequest("GET", s.URL+"/hello/world", nil)
+		if err != nil {
+			t.FailNow()
+		}
+
+		// Use the span context here so the http span will appear as a child of the test
+		req = req.WithContext(ctx)
+
+		res, err := client.Do(req)
+		if err != nil {
+			t.FailNow()
+		}
+		_ = res.Body.Close()
+	})
+
+	t.Run("custom-name", func(t *testing.T) {
+
+		// we can also add custom tags to the test span by retrieving the
+		// context and call the `ddtracer.SpanFromContext` api
+		ctx := (*T)(t).Context()
+		span, _ := ddtracer.SpanFromContext(ctx)
+
+		// Custom namer function for the HTTP request
+		customNamer := func(req *http.Request) string {
+			value := fmt.Sprintf("%s %s", req.Method, req.URL.Path)
+
+			// Then we can set custom tags to that test span
+			span.SetTag("customNamer.Value", value)
+			return value
+		}
+
+		rt := ddhttp.WrapRoundTripper(http.DefaultTransport, ddhttp.RTWithResourceNamer(customNamer))
+		client := &http.Client{
+			Transport: rt,
+		}
+
+		req, err := http.NewRequest("GET", s.URL+"/hello/world", nil)
+		if err != nil {
+			t.FailNow()
+		}
+
+		// Use the span context here so the http span will appear as a child of the test
+		req = req.WithContext(ctx)
+
+		res, err := client.Do(req)
+		if err != nil {
+			t.FailNow()
+		}
+		_ = res.Body.Close()
+	})
+}
+
+// TestSkip demonstrates skipping a test with a message.
+func TestSkip(gt *testing.T) {
+	assertTest(gt)
+
+	t := (*T)(gt)
+
+	// because we use the instrumented Skip
+	// the message will be reported as the skip reason.
+	t.Skip("Nothing to do here, skipping!")
+}
+
+// BenchmarkFirst demonstrates benchmark instrumentation with sub-benchmarks.
+func BenchmarkFirst(gb *testing.B) {
+
+	// Same happens with sub benchmarks
+	// we just need to cast testing.B to gotesting.B
+	// using: newB := (*gotesting.B)(b)
+	b := (*B)(gb)
+	// or
+	b = GetBenchmark(gb)
+
+	var mapArray []map[string]string
+	b.Run("child01", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			mapArray = append(mapArray, map[string]string{})
+		}
+	})
+
+	b.Run("child02", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			mapArray = append(mapArray, map[string]string{})
+		}
+	})
+
+	b.Run("child03", func(b *testing.B) {
+		GetBenchmark(b).Skip("The reason...")
+	})
+
+	_ = gb.Elapsed()
+}
+
+func assertTest(t *testing.T) {
+	assert := assert.New(t)
+	spans := mTracer.OpenSpans()
+	hasSession := false
+	hasModule := false
+	hasSuite := false
+	hasTest := false
+	for _, span := range spans {
+		spanTags := span.Tags()
+
+		// Assert Session
+		if span.Tag(ext.SpanType) == constants.SpanTypeTestSession {
+			assert.Contains(spanTags, constants.TestSessionIDTag)
+			assertCommon(assert, span)
+			hasSession = true
+		}
+
+		// Assert Module
+		if span.Tag(ext.SpanType) == constants.SpanTypeTestModule {
+			assert.Subset(spanTags, map[string]interface{}{
+				constants.TestModule:    "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations/gotesting",
+				constants.TestFramework: "golang.org/pkg/testing",
+			})
+			assert.Contains(spanTags, constants.TestSessionIDTag)
+			assert.Contains(spanTags, constants.TestModuleIDTag)
+			assert.Contains(spanTags, constants.TestFrameworkVersion)
+			assertCommon(assert, span)
+			hasModule = true
+		}
+
+		// Assert Suite
+		if span.Tag(ext.SpanType) == constants.SpanTypeTestSuite {
+			assert.Subset(spanTags, map[string]interface{}{
+				constants.TestModule:    "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations/gotesting",
+				constants.TestFramework: "golang.org/pkg/testing",
+			})
+			assert.Contains(spanTags, constants.TestSessionIDTag)
+			assert.Contains(spanTags, constants.TestModuleIDTag)
+			assert.Contains(spanTags, constants.TestSuiteIDTag)
+			assert.Contains(spanTags, constants.TestFrameworkVersion)
+			assert.Contains(spanTags, constants.TestSuite)
+			assertCommon(assert, span)
+			hasSuite = true
+		}
+
+		// Assert Test
+		if span.Tag(ext.SpanType) == constants.SpanTypeTest {
+			assert.Subset(spanTags, map[string]interface{}{
+				constants.TestModule:    "gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations/gotesting",
+				constants.TestFramework: "golang.org/pkg/testing",
+				constants.TestSuite:     "testing_test.go",
+				constants.TestName:      t.Name(),
+				constants.TestType:      constants.TestTypeTest,
+			})
+			assert.Contains(spanTags, constants.TestSessionIDTag)
+			assert.Contains(spanTags, constants.TestModuleIDTag)
+			assert.Contains(spanTags, constants.TestSuiteIDTag)
+			assert.Contains(spanTags, constants.TestFrameworkVersion)
+			assert.Contains(spanTags, constants.TestCodeOwners)
+			assert.Contains(spanTags, constants.TestSourceFile)
+			assert.Contains(spanTags, constants.TestSourceStartLine)
+			assertCommon(assert, span)
+			hasTest = true
+		}
+	}
+
+	assert.True(hasSession)
+	assert.True(hasModule)
+	assert.True(hasSuite)
+	assert.True(hasTest)
+}
+
+func assertCommon(assert *assert.Assertions, span mocktracer.Span) {
+	spanTags := span.Tags()
+
+	assert.Subset(spanTags, map[string]interface{}{
+		constants.Origin:   constants.CIAppTestOrigin,
+		constants.TestType: constants.TestTypeTest,
+	})
+
+	assert.Contains(spanTags, ext.ResourceName)
+	assert.Contains(spanTags, constants.TestCommand)
+	assert.Contains(spanTags, constants.TestCommandWorkingDirectory)
+	assert.Contains(spanTags, constants.OSPlatform)
+	assert.Contains(spanTags, constants.OSArchitecture)
+	assert.Contains(spanTags, constants.OSVersion)
+	assert.Contains(spanTags, constants.RuntimeVersion)
+	assert.Contains(spanTags, constants.RuntimeName)
+	assert.Contains(spanTags, constants.GitRepositoryURL)
+	assert.Contains(spanTags, constants.GitCommitSHA)
+	// GitHub CI does not provide commit details
+	if spanTags[constants.CIProviderName] != "github" {
+		assert.Contains(spanTags, constants.GitCommitMessage)
+		assert.Contains(spanTags, constants.GitCommitAuthorEmail)
+		assert.Contains(spanTags, constants.GitCommitAuthorDate)
+		assert.Contains(spanTags, constants.GitCommitCommitterEmail)
+		assert.Contains(spanTags, constants.GitCommitCommitterDate)
+		assert.Contains(spanTags, constants.GitCommitCommitterName)
+	}
+	assert.Contains(spanTags, constants.CIWorkspacePath)
+}
+
+func getSpansWithType(spans []mocktracer.Span, spanType string) []mocktracer.Span {
+	var result []mocktracer.Span
+	for _, span := range spans {
+		if span.Tag(ext.SpanType) == spanType {
+			result = append(result, span)
+		}
+	}
+
+	return result
+}