Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions civisibility/integrations/civisibility_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ var (
ciVisibilityFlakyRetriesSettings FlakyRetriesSetting

// ciVisibilitySkippables contains the CI Visibility skippable tests for this session
ciVisibilitySkippables map[string]map[string][]net.SkippableResponseDataAttributes
ciVisibilitySkippables net.SkippableTests

// ciVisibilityTestManagementTests contains the CI Visibility test management tests for this session
ciVisibilityTestManagementTests net.TestManagementTestsResponseDataModules
Expand Down Expand Up @@ -373,7 +373,7 @@ func GetFlakyRetriesSettings() *FlakyRetriesSetting {
}

// GetSkippableTests gets the skippable tests from the backend
func GetSkippableTests() map[string]map[string][]net.SkippableResponseDataAttributes {
func GetSkippableTests() net.SkippableTests {
// call to ensure the additional features initialization is completed
ensureAdditionalFeaturesInitialization(autoDetectServiceName)
return ciVisibilitySkippables
Expand Down
2 changes: 1 addition & 1 deletion civisibility/utils/net/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type (
GetKnownTestsRawResponse() json.RawMessage
GetCommits(localCommits []string) ([]string, error)
SendPackFiles(commitSha string, packFiles []string) (bytes int64, err error)
GetSkippableTests() (correlationID string, skippables map[string]map[string][]SkippableResponseDataAttributes, err error)
GetSkippableTests() (correlationID string, skippables SkippableTests, err error)
GetSkippableTestsRawResponse() json.RawMessage
GetTestManagementTests() (*TestManagementTestsResponseDataModules, error)
GetTestManagementTestsRawResponse() json.RawMessage
Expand Down
4 changes: 2 additions & 2 deletions civisibility/utils/net/raw_response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func newRawResponseTestServer(t *testing.T, responses map[string]string) *httpte
func TestClientStoresRawBackendResponses(t *testing.T) {
settingsResponse := `{"data":{"id":"settings-id","type":"ci_app_test_service_libraries_settings","attributes":{"itr_enabled":true,"tests_skipping":true,"known_tests_enabled":true,"test_management":{"enabled":true,"attempt_to_fix_retries":3}}}}`
knownTestsResponse := `{"data":{"id":"known-tests-id","type":"ci_app_libraries_tests_request","attributes":{"tests":{"module-a":{"suite-a":["test-a"]}}}}}`
skippableTestsResponse := `{"meta":{"correlation_id":"correlation-id"},"data":[{"id":"skippable-id","type":"test","attributes":{"suite":"suite-a","name":"test-a","parameters":"params","configurations":{"os.platform":"linux","os.architecture":"amd64","runtime.name":"ruby","runtime.version":"3.3.0"}}}]}`
skippableTestsResponse := `{"meta":{"correlation_id":"correlation-id"},"data":[{"id":"skippable-id","type":"test","attributes":{"module":"module-a","suite":"suite-a","name":"test-a","parameters":"params","configurations":{"os.platform":"linux","os.architecture":"amd64","runtime.name":"ruby","runtime.version":"3.3.0"}}}]}`
testManagementResponse := `{"data":{"id":"test-management-id","type":"ci_app_libraries_tests_request","attributes":{"modules":{"module-a":{"suites":{"suite-a":{"tests":{"test-a":{"properties":{"quarantined":true,"disabled":false,"attempt_to_fix":true}}}}}}}}}}`

server := newRawResponseTestServer(t, map[string]string{
Expand Down Expand Up @@ -89,7 +89,7 @@ func TestClientStoresRawBackendResponses(t *testing.T) {
if err != nil {
t.Fatalf("GetSkippableTests() returned error: %v", err)
}
if correlationID != "correlation-id" || skippableTests["suite-a"]["test-a"][0].Parameters != "params" {
if correlationID != "correlation-id" || !skippableTests["module-a.suite-a.test-a.params"] {
t.Fatalf("GetSkippableTests() returned unexpected processed data: correlationID=%s skippableTests=%+v", correlationID, skippableTests)
}
if string(client.GetSkippableTestsRawResponse()) != skippableTestsResponse {
Expand Down
24 changes: 10 additions & 14 deletions civisibility/utils/net/skippable.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,17 @@ type (
}

SkippableResponseDataAttributes struct {
Module string `json:"module"`
Suite string `json:"suite"`
Name string `json:"name"`
Parameters string `json:"parameters"`
Configurations testConfigurations `json:"configurations"`
}

SkippableTests map[string]bool
)

func (c *client) GetSkippableTests() (correlationID string, skippables map[string]map[string][]SkippableResponseDataAttributes, err error) {
func (c *client) GetSkippableTests() (correlationID string, skippables SkippableTests, err error) {
if c.repositoryURL == "" || c.commitSha == "" {
err = fmt.Errorf("civisibility.GetSkippableTests: repository URL and commit SHA are required")
return
Expand Down Expand Up @@ -91,7 +94,7 @@ func (c *client) GetSkippableTests() (correlationID string, skippables map[strin
return "", nil, fmt.Errorf("unmarshalling skippable tests response: %s", err)
}

skippableTestsMap := map[string]map[string][]SkippableResponseDataAttributes{}
skippableTestsMap := SkippableTests{}
for _, data := range responseObject.Data {

// Filter out the tests that do not match the test configurations
Expand Down Expand Up @@ -120,19 +123,12 @@ func (c *client) GetSkippableTests() (correlationID string, skippables map[strin
continue
}

var ok bool
var testsMap map[string][]SkippableResponseDataAttributes
if testsMap, ok = skippableTestsMap[data.Attributes.Suite]; !ok {
testsMap = map[string][]SkippableResponseDataAttributes{}
skippableTestsMap[data.Attributes.Suite] = testsMap
}

if test, ok := testsMap[data.Attributes.Name]; ok {
testsMap[data.Attributes.Name] = append(test, data.Attributes)
} else {
testsMap[data.Attributes.Name] = []SkippableResponseDataAttributes{data.Attributes}
}
skippableTestsMap[skippableTestKey(data.Attributes)] = true
}

return responseObject.Meta.CorrelationID, skippableTestsMap, nil
}

func skippableTestKey(test SkippableResponseDataAttributes) string {
return test.Module + "." + test.Suite + "." + test.Name + "." + test.Parameters
}
26 changes: 23 additions & 3 deletions internal/planner/discovered_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

func (tp *TestPlanner) recordFullDiscoveryResults(
discoveredTests []testoptimization.Test,
skippableTests map[string]bool,
skippableTests testSkipper,
subdirPrefix string,
) {
discoveredTestsCount := len(discoveredTests)
Expand All @@ -25,8 +25,8 @@ func (tp *TestPlanner) recordFullDiscoveryResults(
tp.testFiles[normalizedSourceFile] = struct{}{}
}

if !skippableTests[test.FQN()] {
slog.Debug("Test is not skipped", "test", test.FQN(), "sourceFile", test.SuiteSourceFile)
if !skippableTests.Contains(test) {
slog.Debug("Test is not skipped", "test", test.DatadogTestId(), "sourceFile", test.SuiteSourceFile)
recordRunnableTest(tp.suiteAggregates, test, normalizedSourceFile)
} else {
recordSkippedTest(tp.suiteAggregates, test, normalizedSourceFile)
Expand All @@ -37,6 +37,26 @@ func (tp *TestPlanner) recordFullDiscoveryResults(
slog.Info("Processed the discovered tests", "skippableTestsCount", skippableTestsCount, "discoveredTestsCount", discoveredTestsCount)
}

type testSkipper struct {
tiaSkippableTests map[string]bool
disabledTests map[string]bool
}

func newTestSkipper(tiaSkippableTests, disabledTests map[string]bool) testSkipper {
return testSkipper{
tiaSkippableTests: tiaSkippableTests,
disabledTests: disabledTests,
}
}

func (s testSkipper) Contains(test testoptimization.Test) bool {
return s.tiaSkippableTests[test.DatadogTestId()] || s.disabledTests[test.FQN()]
}

func (s testSkipper) Count() int {
return len(s.tiaSkippableTests) + len(s.disabledTests)
}

func (tp *TestPlanner) recordFastDiscoveryFallbackFiles(discoveredTestFiles []string) {
for _, testFile := range discoveredTestFiles {
if testFile != "" {
Expand Down
7 changes: 5 additions & 2 deletions internal/planner/high_skippable_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ func TestTestPlanner_Plan_HighSkippableIntegrationSelectsExpectedRunnerCountAndR
}
runner := NewWithDependencies(
&MockPlatformDetector{Platform: mockPlatform},
&MockTestOptimizationClient{SkippableTests: fixture.skippableTestSet()},
&MockTestOptimizationClient{
Settings: testOptimizationSettings(true, true, false),
SkippableTests: fixture.skippableTestSet(),
},
&MockTestSuiteDurationsClient{Durations: fixture.TestSuiteDurations},
newDefaultMockCIProviderDetector(),
)
Expand Down Expand Up @@ -141,7 +144,7 @@ func (f highSkippableIntegrationFixture) nonFullySkippedTestFiles() []string {
sourceFile := strings.TrimPrefix(test.SuiteSourceFile, "core/")
counts := countsByFile[sourceFile]
counts.total++
if skippableTests[test.FQN()] {
if skippableTests[test.DatadogTestId()] {
counts.skipped++
}
countsByFile[sourceFile] = counts
Expand Down
45 changes: 32 additions & 13 deletions internal/planner/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ func (tp *TestPlanner) PreparePlanningData(ctx context.Context) error {
discoveryCtx, cancelDiscovery := context.WithCancel(ctx)
defer cancelDiscovery()

var skippableTests map[string]bool
var skippedTests testSkipper
var discoveredTests []testoptimization.Test
var discoveredTestFiles []string
var fullDiscoverySucceeded bool
Expand All @@ -296,24 +296,21 @@ func (tp *TestPlanner) PreparePlanningData(ctx context.Context) error {

repositorySettings := tp.optimizationClient.GetSettings()
tp.planReport.DatadogSettings = newDatadogSettingsReport(repositorySettings)
tiaSkippingEnabled := false
if repositorySettings != nil {
slog.Debug("Repository settings", "itr_enabled", repositorySettings.ItrEnabled, "tests_skipping", repositorySettings.TestsSkipping)
slog.Debug("Repository settings", "tia_enabled", repositorySettings.ItrEnabled, "tests_skipping", repositorySettings.TestsSkipping)
tiaSkippingEnabled = repositorySettings.ItrEnabled && repositorySettings.TestsSkipping

if !repositorySettings.ItrEnabled || !repositorySettings.TestsSkipping {
slog.Info("ITR or test skipping disabled, cancelling full test discovery")
if !tiaSkippingEnabled {
slog.Info("TIA or test skipping disabled, cancelling full test discovery")
cancelDiscovery()
}
}

tp.testSuiteDurations = tp.durationsClient.GetTestSuiteDurations()

startTime := time.Now()
slog.Info("Fetching skippable tests from Datadog...")
skippableTests = tp.optimizationClient.GetSkippableTests()
tp.planReport.SkippableTestsCount = len(skippableTests)
tp.planReport.KnownTests = newKnownTestsReport(tp.optimizationClient.GetKnownTests())
tp.planReport.ManagedFlakyTests = newManagedFlakyTestsReport(tp.optimizationClient.GetTestManagementTestsData())
slog.Info("Fetched skippable tests", "duration", time.Since(startTime))
skippedTests = tp.fetchTestsToSkip(tiaSkippingEnabled)
tp.planReport.SkippableTestsCount = skippedTests.Count()

return nil
})
Expand Down Expand Up @@ -380,7 +377,7 @@ func (tp *TestPlanner) PreparePlanningData(ctx context.Context) error {
// into a collection of testSuiteAggregate structs.
// This collection is used to calculate the skippable percentage and the weighted test files.
if fullDiscoverySucceeded {
tp.recordFullDiscoveryResults(discoveredTests, skippableTests, subdirPrefix)
tp.recordFullDiscoveryResults(discoveredTests, skippedTests, subdirPrefix)
tp.estimateDiscoveredSuiteDurations()

slog.Info("Full test discovery succeeded; using full discovery results and ignoring fast-discovered-only files",
Expand All @@ -406,10 +403,32 @@ func (tp *TestPlanner) PreparePlanningData(ctx context.Context) error {
return nil
}

func (tp *TestPlanner) fetchTestsToSkip(tiaSkippingEnabled bool) testSkipper {
startTime := time.Now()
slog.Info("Fetching tests to skip from Datadog...")

tiaSkippableTests := map[string]bool{}
if tiaSkippingEnabled {
tiaSkippableTests = tp.optimizationClient.GetSkippableTests()
}

tp.planReport.KnownTests = newKnownTestsReport(tp.optimizationClient.GetKnownTests())
testManagementTests := tp.optimizationClient.GetTestManagementTestsData()
tp.planReport.ManagedFlakyTests = newManagedFlakyTestsReport(testManagementTests)

disabledTests := testoptimization.DisabledTestsFromTestManagementData(testManagementTests)
slog.Info("Fetched tests to skip",
"duration", time.Since(startTime),
"tiaSkippableTestsCount", len(tiaSkippableTests),
"disabledTestsCount", len(disabledTests))

return newTestSkipper(tiaSkippableTests, disabledTests)
}

func (tp *TestPlanner) estimateDiscoveredSuiteDurations() {
for key, aggregate := range tp.suiteAggregates {
// Without backend timing data, use test counts as the estimate:
// TotalDuration is the full suite before ITR skips, while EstimatedDuration
// TotalDuration is the full suite before TIA skips, while EstimatedDuration
// is the runnable remainder after skipped tests are removed.
aggregate.TotalDuration = float64(aggregate.NumTests) * float64(time.Second)
aggregate.EstimatedDuration = float64(aggregate.NumTests-aggregate.NumTestsSkipped) * float64(time.Second)
Expand Down
Loading
Loading