Skip to content

Commit

Permalink
(feat) internal/civisibility: add Known Tests feature and refactor EF…
Browse files Browse the repository at this point in the history
…D logic
  • Loading branch information
tonyredondo committed Jan 31, 2025
1 parent 2ee7731 commit 2d92d3e
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 173 deletions.
22 changes: 11 additions & 11 deletions internal/civisibility/integrations/civisibility_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ var (
// ciVisibilitySettings contains the CI Visibility settings for this session
ciVisibilitySettings net.SettingsResponseData

// ciVisibilityEarlyFlakyDetectionSettings contains the CI Visibility Early Flake Detection data for this session
ciVisibilityEarlyFlakyDetectionSettings net.EfdResponseData
// ciVisibilityKnownTests contains the CI Visibility Known Tests data for this session
ciVisibilityKnownTests net.KnownTestsResponseData

// ciVisibilityFlakyRetriesSettings contains the CI Visibility Flaky Retries settings for this session
ciVisibilityFlakyRetriesSettings FlakyRetriesSetting
Expand Down Expand Up @@ -121,14 +121,14 @@ func ensureAdditionalFeaturesInitialization(serviceName string) {
return
}

// if early flake detection is enabled then we run the early flake detection request
if ciVisibilitySettings.EarlyFlakeDetection.Enabled {
ciEfdData, err := ciVisibilityClient.GetEarlyFlakeDetectionData()
// if early flake detection is enabled then we run the known tests request
if ciVisibilitySettings.KnownTestsEnabled {
ciEfdData, err := ciVisibilityClient.GetKnownTests()
if err != nil {
log.Error("civisibility: error getting CI visibility early flake detection data: %v", err)
log.Error("civisibility: error getting CI visibility known tests data: %v", err)
} else if ciEfdData != nil {
ciVisibilityEarlyFlakyDetectionSettings = *ciEfdData
log.Debug("civisibility: early flake detection data loaded.")
ciVisibilityKnownTests = *ciEfdData
log.Debug("civisibility: known tests data loaded.")
}
}

Expand Down Expand Up @@ -172,11 +172,11 @@ func GetSettings() *net.SettingsResponseData {
return &ciVisibilitySettings
}

// GetEarlyFlakeDetectionSettings gets the early flake detection known tests data
func GetEarlyFlakeDetectionSettings() *net.EfdResponseData {
// GetKnownTests gets the known tests data
func GetKnownTests() *net.KnownTestsResponseData {
// call to ensure the additional features initialization is completed (service name can be null here)
ensureAdditionalFeaturesInitialization("")
return &ciVisibilityEarlyFlakyDetectionSettings
return &ciVisibilityKnownTests
}

// GetFlakyRetriesSettings gets the flaky retries settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ type MockClient struct {
SendCoveragePayloadFunc func(ciTestCovPayload io.Reader) error
SendCoveragePayloadWithFormatFunc func(ciTestCovPayload io.Reader, format string) error
GetSettingsFunc func() (*net.SettingsResponseData, error)
GetEarlyFlakeDetectionDataFunc func() (*net.EfdResponseData, error)
GetEarlyFlakeDetectionDataFunc func() (*net.KnownTestsResponseData, error)
GetCommitsFunc func(localCommits []string) ([]string, error)
SendPackFilesFunc func(commitSha string, packFiles []string) (bytes int64, err error)
GetSkippableTestsFunc func() (correlationId string, skippables map[string]map[string][]net.SkippableResponseDataAttributes, err error)
Expand All @@ -91,7 +91,7 @@ func (m *MockClient) GetSettings() (*net.SettingsResponseData, error) {
return m.GetSettingsFunc()
}

func (m *MockClient) GetEarlyFlakeDetectionData() (*net.EfdResponseData, error) {
func (m *MockClient) GetKnownTests() (*net.KnownTestsResponseData, error) {
return m.GetEarlyFlakeDetectionDataFunc()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,15 +243,15 @@ func applyFlakyTestRetriesAdditionalFeature(targetFunc func(*testing.T)) (func(*

// applyEarlyFlakeDetectionAdditionalFeature applies the early flake detection feature as a wrapper of a func(*testing.T)
func applyEarlyFlakeDetectionAdditionalFeature(testInfo *commonInfo, targetFunc func(*testing.T), settings *net.SettingsResponseData) (func(*testing.T), bool) {
earlyFlakeDetectionData := integrations.GetEarlyFlakeDetectionSettings()
if earlyFlakeDetectionData != nil &&
len(earlyFlakeDetectionData.Tests) > 0 {
knownTestsData := integrations.GetKnownTests()
if knownTestsData != nil &&
len(knownTestsData.Tests) > 0 {

// Define is a known test flag
isAKnownTest := false

// Check if the test is a known test or a new one
if knownSuites, ok := earlyFlakeDetectionData.Tests[testInfo.moduleName]; ok {
if knownSuites, ok := knownTestsData.Tests[testInfo.moduleName]; ok {
if knownTests, ok := knownSuites[testInfo.suiteName]; ok {
if slices.Contains(knownTests, testInfo.testName) {
isAKnownTest = true
Expand Down
21 changes: 11 additions & 10 deletions internal/civisibility/integrations/gotesting/testcontroller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ func runFlakyTestRetriesTests(m *testing.M) {

func runEarlyFlakyTestDetectionTests(m *testing.M) {
// mock the settings api to enable automatic test retries
server := setUpHttpServer(false, true, &net.EfdResponseData{
Tests: net.EfdResponseDataModules{
"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations/gotesting": net.EfdResponseDataSuites{
server := setUpHttpServer(false, true, &net.KnownTestsResponseData{
Tests: net.KnownTestsResponseDataModules{
"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations/gotesting": net.KnownTestsResponseDataSuites{
"reflections_test.go": []string{
"TestGetFieldPointerFrom",
"TestGetInternalTestArray",
Expand Down Expand Up @@ -243,9 +243,9 @@ func runEarlyFlakyTestDetectionTests(m *testing.M) {

func runFlakyTestRetriesWithEarlyFlakyTestDetectionTests(m *testing.M) {
// mock the settings api to enable automatic test retries
server := setUpHttpServer(true, true, &net.EfdResponseData{
Tests: net.EfdResponseDataModules{
"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations/gotesting": net.EfdResponseDataSuites{
server := setUpHttpServer(true, true, &net.KnownTestsResponseData{
Tests: net.KnownTestsResponseDataModules{
"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/integrations/gotesting": net.KnownTestsResponseDataSuites{
"reflections_test.go": []string{
"TestGetFieldPointerFrom",
"TestGetInternalTestArray",
Expand Down Expand Up @@ -569,7 +569,7 @@ type (
)

func setUpHttpServer(flakyRetriesEnabled bool,
earlyFlakyDetectionEnabled bool, earlyFlakyDetectionData *net.EfdResponseData,
earlyFlakyDetectionEnabled bool, earlyFlakyDetectionData *net.KnownTestsResponseData,
itrEnabled bool, itrData []net.SkippableResponseDataAttributes) *httptest.Server {
// mock the settings api to enable automatic test retries
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -591,6 +591,7 @@ func setUpHttpServer(flakyRetriesEnabled bool,
FlakyTestRetriesEnabled: flakyRetriesEnabled,
ItrEnabled: itrEnabled,
TestsSkipping: itrEnabled,
KnownTestsEnabled: earlyFlakyDetectionEnabled,
}
response.Data.Attributes.EarlyFlakeDetection.Enabled = earlyFlakyDetectionEnabled
response.Data.Attributes.EarlyFlakeDetection.SlowTestRetries.FiveS = 10
Expand All @@ -604,9 +605,9 @@ func setUpHttpServer(flakyRetriesEnabled bool,
w.Header().Set("Content-Type", "application/json")
response := struct {
Data struct {
ID string `json:"id"`
Type string `json:"type"`
Attributes net.EfdResponseData `json:"attributes"`
ID string `json:"id"`
Type string `json:"type"`
Attributes net.KnownTestsResponseData `json:"attributes"`
} `json:"data,omitempty"`
}{}

Expand Down
2 changes: 1 addition & 1 deletion internal/civisibility/utils/net/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type (
// Client is an interface for sending requests to the Datadog backend.
Client interface {
GetSettings() (*SettingsResponseData, error)
GetEarlyFlakeDetectionData() (*EfdResponseData, error)
GetKnownTests() (*KnownTestsResponseData, error)
GetCommits(localCommits []string) ([]string, error)
SendPackFiles(commitSha string, packFiles []string) (bytes int64, err error)
SendCoveragePayload(ciTestCovPayload io.Reader) error
Expand Down
116 changes: 0 additions & 116 deletions internal/civisibility/utils/net/efd_api.go

This file was deleted.

116 changes: 116 additions & 0 deletions internal/civisibility/utils/net/known_tests_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// 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 net

import (
"fmt"
"time"

"gopkg.in/DataDog/dd-trace-go.v1/internal/civisibility/utils/telemetry"
)

const (
knownTestsRequestType string = "ci_app_libraries_tests_request"
knownTestsURLPath string = "api/v2/ci/libraries/tests"
)

type (
knownTestsRequest struct {
Data knownTestsRequestHeader `json:"data"`
}

knownTestsRequestHeader struct {
ID string `json:"id"`
Type string `json:"type"`
Attributes KnownTestsRequestData `json:"attributes"`
}

KnownTestsRequestData struct {
Service string `json:"service"`
Env string `json:"env"`
RepositoryURL string `json:"repository_url"`
Configurations testConfigurations `json:"configurations"`
}

knownTestsResponse struct {
Data struct {
ID string `json:"id"`
Type string `json:"type"`
Attributes KnownTestsResponseData `json:"attributes"`
} `json:"data"`
}

KnownTestsResponseData struct {
Tests KnownTestsResponseDataModules `json:"tests"`
}

KnownTestsResponseDataModules map[string]KnownTestsResponseDataSuites
KnownTestsResponseDataSuites map[string][]string
)

func (c *client) GetKnownTests() (*KnownTestsResponseData, error) {
if c.repositoryURL == "" || c.commitSha == "" {
return nil, fmt.Errorf("civisibility.GetKnownTests: repository URL and commit SHA are required")
}

body := knownTestsRequest{
Data: knownTestsRequestHeader{
ID: c.id,
Type: knownTestsRequestType,
Attributes: KnownTestsRequestData{
Service: c.serviceName,
Env: c.environment,
RepositoryURL: c.repositoryURL,
Configurations: c.testConfigurations,
},
},
}

request := c.getPostRequestConfig(knownTestsURLPath, body)
if request.Compressed {
telemetry.KnownTestsRequest(telemetry.CompressedRequestCompressedType)
} else {
telemetry.KnownTestsRequest(telemetry.UncompressedRequestCompressedType)
}

startTime := time.Now()
response, err := c.handler.SendRequest(*request)
telemetry.KnownTestsRequestMs(float64(time.Since(startTime).Milliseconds()))

if err != nil {
telemetry.KnownTestsRequestErrors(telemetry.NetworkErrorType)
return nil, fmt.Errorf("sending known tests request: %s", err.Error())
}

if response.StatusCode < 200 || response.StatusCode >= 300 {
telemetry.KnownTestsRequestErrors(telemetry.GetErrorTypeFromStatusCode(response.StatusCode))
}
if response.Compressed {
telemetry.KnownTestsResponseBytes(telemetry.CompressedResponseCompressedType, float64(len(response.Body)))
} else {
telemetry.KnownTestsResponseBytes(telemetry.UncompressedResponseCompressedType, float64(len(response.Body)))
}

var responseObject knownTestsResponse
err = response.Unmarshal(&responseObject)
if err != nil {
return nil, fmt.Errorf("unmarshalling known tests response: %s", err.Error())
}

testCount := 0
if responseObject.Data.Attributes.Tests != nil {
for _, suites := range responseObject.Data.Attributes.Tests {
if suites == nil {
continue
}
for _, tests := range suites {
testCount += len(tests)
}
}
}
telemetry.KnownTestsResponseTests(float64(testCount))
return &responseObject.Data.Attributes, nil
}
Loading

0 comments on commit 2d92d3e

Please sign in to comment.