diff options
author | Manuel Palenzuela Merino <manuel.palenzuela@datadoghq.com> | 2024-12-05 11:07:35 +0100 |
---|---|---|
committer | Manuel Palenzuela Merino <manuel.palenzuela@datadoghq.com> | 2024-12-05 11:08:17 +0100 |
commit | ee3c0aa3d71a857ae7fb60f1b98cf129ee6ad71d (patch) | |
tree | 8aeaa3b76be6cdcf326acf3627b3d283a8b2c372 /main/civisibility/integrations/gotesting/testingT.go | |
parent | Update README.md (diff) | |
download | test-repo-ee3c0aa3d71a857ae7fb60f1b98cf129ee6ad71d.tar.gz test-repo-ee3c0aa3d71a857ae7fb60f1b98cf129ee6ad71d.tar.bz2 test-repo-ee3c0aa3d71a857ae7fb60f1b98cf129ee6ad71d.zip |
Add tests
Diffstat (limited to 'main/civisibility/integrations/gotesting/testingT.go')
-rw-r--r-- | main/civisibility/integrations/gotesting/testingT.go | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/main/civisibility/integrations/gotesting/testingT.go b/main/civisibility/integrations/gotesting/testingT.go new file mode 100644 index 0000000..5b7f117 --- /dev/null +++ b/main/civisibility/integrations/gotesting/testingT.go @@ -0,0 +1,217 @@ +// 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 ( + "context" + "fmt" + "reflect" + "runtime" + "sync" + "sync/atomic" + "testing" + "time" + + "ci-visibility-test-github/main/civisibility/integrations" + "ci-visibility-test-github/main/civisibility/utils" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" +) + +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 + +// GetTest is a helper to return *gotesting.T from *testing.T. +// Internally, it is just a (*gotesting.T)(t) cast. +func GetTest(t *testing.T) *T { + return (*T)(t) +} + +// Run runs f as a subtest of t called name. It runs f in a separate goroutine +// and blocks until f returns or calls t.Parallel to become a parallel test. +// Run reports whether f succeeded (or at least did not fail before calling t.Parallel). +// +// Run may be called simultaneously from multiple goroutines, but all such calls +// must return before the outer test function for t returns. +func (ddt *T) Run(name string, f func(*testing.T)) bool { + // 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) + + t := (*testing.T)(ddt) + return t.Run(name, 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) + test := suite.CreateTest(t.Name()) + test.SetTestFunc(originalFunc) + setCiVisibilityTest(t, test) + defer func() { + if r := recover(); r != nil { + // Handle panic and set error information. + test.SetErrorInfo("panic", fmt.Sprint(r), utils.GetStacktrace(1)) + test.Close(integrations.ResultStatusFail) + checkModuleAndSuite(module, suite) + integrations.ExitCiVisibility() + panic(r) + } else { + // Normal finalization: determine the test result based on its state. + if t.Failed() { + test.SetTag(ext.Error, true) + suite.SetTag(ext.Error, true) + module.SetTag(ext.Error, true) + test.Close(integrations.ResultStatusFail) + } else if t.Skipped() { + test.Close(integrations.ResultStatusSkip) + } else { + test.Close(integrations.ResultStatusPass) + } + checkModuleAndSuite(module, suite) + } + }() + + // Execute the original test function. + f(t) + }) +} + +// Context returns the CI Visibility context of the Test span. +// This may be used to create test's children spans useful for +// integration tests. +func (ddt *T) Context() context.Context { + t := (*testing.T)(ddt) + ciTest := getCiVisibilityTest(t) + if ciTest != nil { + return ciTest.Context() + } + + return context.Background() +} + +// Fail marks the function as having failed but continues execution. +func (ddt *T) Fail() { ddt.getTWithError("Fail", "failed test").Fail() } + +// FailNow marks the function as having failed and stops its execution +// by calling runtime.Goexit (which then runs all deferred calls in the +// current goroutine). Execution will continue at the next test or benchmark. +// FailNow must be called from the goroutine running the test or benchmark function, +// not from other goroutines created during the test. Calling FailNow does not stop +// those other goroutines. +func (ddt *T) FailNow() { + t := ddt.getTWithError("FailNow", "failed test") + integrations.ExitCiVisibility() + t.FailNow() +} + +// Error is equivalent to Log followed by Fail. +func (ddt *T) Error(args ...any) { ddt.getTWithError("Error", fmt.Sprint(args...)).Error(args...) } + +// Errorf is equivalent to Logf followed by Fail. +func (ddt *T) Errorf(format string, args ...any) { + ddt.getTWithError("Errorf", fmt.Sprintf(format, args...)).Errorf(format, args...) +} + +// Fatal is equivalent to Log followed by FailNow. +func (ddt *T) Fatal(args ...any) { ddt.getTWithError("Fatal", fmt.Sprint(args...)).Fatal(args...) } + +// Fatalf is equivalent to Logf followed by FailNow. +func (ddt *T) Fatalf(format string, args ...any) { + ddt.getTWithError("Fatalf", fmt.Sprintf(format, args...)).Fatalf(format, args...) +} + +// Skip is equivalent to Log followed by SkipNow. +func (ddt *T) Skip(args ...any) { ddt.getTWithSkip(fmt.Sprint(args...)).Skip(args...) } + +// Skipf is equivalent to Logf followed by SkipNow. +func (ddt *T) Skipf(format string, args ...any) { + ddt.getTWithSkip(fmt.Sprintf(format, args...)).Skipf(format, args...) +} + +// SkipNow marks the test as having been skipped and stops its execution +// by calling runtime.Goexit. If a test fails (see Error, Errorf, Fail) and is then skipped, +// it is still considered to have failed. Execution will continue at the next test or benchmark. +// SkipNow must be called from the goroutine running the test, not from other goroutines created +// during the test. Calling SkipNow does not stop those other goroutines. +func (ddt *T) SkipNow() { + t := (*testing.T)(ddt) + ciTest := getCiVisibilityTest(t) + if ciTest != nil { + ciTest.Close(integrations.ResultStatusSkip) + } + + t.SkipNow() +} + +// Parallel signals that this test is to be run in parallel with (and only with) +// other parallel tests. When a test is run multiple times due to use of +// -test.count or -test.cpu, multiple instances of a single test never run in +// parallel with each other. +func (ddt *T) Parallel() { (*testing.T)(ddt).Parallel() } + +// Deadline reports the time at which the test binary will have +// exceeded the timeout specified by the -timeout flag. +// The ok result is false if the -timeout flag indicates “no timeout” (0). +func (ddt *T) Deadline() (deadline time.Time, ok bool) { + return (*testing.T)(ddt).Deadline() +} + +// Setenv calls os.Setenv(key, value) and uses Cleanup to +// restore the environment variable to its original value +// after the test. Because Setenv affects the whole process, +// it cannot be used in parallel tests or tests with parallel ancestors. +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) + ciTest := getCiVisibilityTest(t) + if ciTest != nil { + ciTest.SetErrorInfo(errType, errMessage, utils.GetStacktrace(2)) + } + return t +} + +func (ddt *T) getTWithSkip(skipReason string) *testing.T { + t := (*testing.T)(ddt) + ciTest := getCiVisibilityTest(t) + if ciTest != nil { + ciTest.CloseWithFinishTimeAndSkipReason(integrations.ResultStatusSkip, time.Now(), 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 +} |