From 9810f6e6086aafd197e57edb27a224c4dcacbaea Mon Sep 17 00:00:00 2001 From: "Ryan Brandenburg (He/Him)" Date: Mon, 19 Oct 2026 15:26:58 -0700 Subject: [PATCH] Swift Experimental=>Default-On --- docs/detectors/README.md | 2 +- docs/detectors/swift.md | 13 +--- .../swiftpm/SwiftResolvedComponentDetector.cs | 2 +- .../Configs/SwiftDetectorExperiment.cs | 22 ------ .../Extensions/ServiceCollectionExtensions.cs | 1 - .../ComponentDetectorTests.cs | 4 +- .../Experiments/ExperimentConfigTests.cs | 7 +- .../Experiments/ExperimentServiceTests.cs | 67 ------------------- .../SwiftDetectorExperimentTests.cs | 50 -------------- 9 files changed, 11 insertions(+), 157 deletions(-) delete mode 100644 src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/SwiftDetectorExperiment.cs delete mode 100644 test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/SwiftDetectorExperimentTests.cs diff --git a/docs/detectors/README.md b/docs/detectors/README.md index cdf250b60..f22a841d3 100644 --- a/docs/detectors/README.md +++ b/docs/detectors/README.md @@ -124,7 +124,7 @@ | Detector | Status | | ------------------------------ | ------------ | -| SwiftResolvedComponentDetector | Experimental | +| SwiftResolvedComponentDetector | Stable | - [Uv](uv.md) diff --git a/docs/detectors/swift.md b/docs/detectors/swift.md index 59aad5f74..9ad7b2256 100644 --- a/docs/detectors/swift.md +++ b/docs/detectors/swift.md @@ -1,5 +1,7 @@ # Swift Package Manager Detection +Swift detection is enabled by default, and detected components are included in scan results without additional detector arguments. + ## Requirements Swift Package Manager detection requires the following file to be present in the scan directory: @@ -14,17 +16,6 @@ The version, the url and commit hash of the dependencies are stored in this file [This is the only reference in the Apple documentation to the `Package.resolved` file.][1] -## Experiment results - -Swift detection is experimental. The registered `SwiftDetectorExperiment` uses an empty control group -and `SwiftResolvedComponentDetector` as its experiment group, so all Swift discoveries are reported as additions. -It does not depend on CocoaPods detection or compare Swift packages against CocoaPods packages. - -Enable experiment processing with `CD_DETECTOR_EXPERIMENTS` or `DetectorExperiments.Enable`. -Scans with no Swift components do not produce experiment results. Disabling the Swift detector also disables this experiment. -The CLI writes results to `Experiment_SwiftDetectorExperiment_{timestamp}_{processId}.json`; -hosts that emit `ExperimentResults` custom events must register their own `IExperimentProcessor`. - ## Known limitations Right now the detector does not support parsing `Package.swift` file to get the dependencies. diff --git a/src/Microsoft.ComponentDetection.Detectors/swiftpm/SwiftResolvedComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/swiftpm/SwiftResolvedComponentDetector.cs index d6cff8635..9e7fd2df7 100644 --- a/src/Microsoft.ComponentDetection.Detectors/swiftpm/SwiftResolvedComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/swiftpm/SwiftResolvedComponentDetector.cs @@ -15,7 +15,7 @@ namespace Microsoft.ComponentDetection.Detectors.Swift; /// /// Detects Swift Package Manager components. /// -public class SwiftResolvedComponentDetector : FileComponentDetector, IExperimentalDetector +public class SwiftResolvedComponentDetector : FileComponentDetector { // We are only interested in packages coming from remote sources such as git // The Package Kind is not an enum because the Swift Package Manager contract does not specify the possible values. diff --git a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/SwiftDetectorExperiment.cs b/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/SwiftDetectorExperiment.cs deleted file mode 100644 index dc25a409a..000000000 --- a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/SwiftDetectorExperiment.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Microsoft.ComponentDetection.Orchestrator.Experiments.Configs; - -using Microsoft.ComponentDetection.Contracts; -using Microsoft.ComponentDetection.Detectors.Swift; - -/// -/// Records Swift Package Manager discoveries against an empty baseline. -/// -public class SwiftDetectorExperiment : IExperimentConfiguration -{ - /// - public string Name => "SwiftDetectorExperiment"; - - /// - public bool IsInControlGroup(IComponentDetector componentDetector) => false; - - /// - public bool IsInExperimentGroup(IComponentDetector componentDetector) => componentDetector is SwiftResolvedComponentDetector; - - /// - public bool ShouldRecord(IComponentDetector componentDetector, int numComponents) => true; -} diff --git a/src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs b/src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs index edd1f0031..dd33c8a83 100644 --- a/src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs +++ b/src/Microsoft.ComponentDetection.Orchestrator/Extensions/ServiceCollectionExtensions.cs @@ -74,7 +74,6 @@ public static IServiceCollection AddComponentDetection(this IServiceCollection s services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); // Detectors // CocoaPods diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/ComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/ComponentDetectorTests.cs index 63034fbc3..77754f1ae 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/ComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/ComponentDetectorTests.cs @@ -83,12 +83,12 @@ public void CondaLockComponentDetector_ImplementsIExperimentalDetector() } [TestMethod] - public void SwiftResolvedComponentDetector_ImplementsIExperimentalDetector() + public void SwiftResolvedComponentDetector_IsDefaultOn() { var swiftDetector = this.detectors.SingleOrDefault(d => d.Id == "Swift"); swiftDetector.Should().NotBeNull("because SwiftResolvedComponentDetector should be registered"); - swiftDetector.Should().BeAssignableTo("because SwiftResolvedComponentDetector should implement IExperimentalDetector"); + swiftDetector.Should().NotBeAssignableTo("because SwiftResolvedComponentDetector should be default-on"); swiftDetector.Should().NotBeAssignableTo("because SwiftResolvedComponentDetector should be enabled by default"); } diff --git a/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentConfigTests.cs b/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentConfigTests.cs index 7519ca637..166ecf471 100644 --- a/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentConfigTests.cs +++ b/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentConfigTests.cs @@ -6,6 +6,7 @@ namespace Microsoft.ComponentDetection.Orchestrator.Tests.Experiments; using System.Threading.Tasks; using AwesomeAssertions; using Microsoft.ComponentDetection.Contracts; +using Microsoft.ComponentDetection.Detectors.Swift; using Microsoft.ComponentDetection.Orchestrator.Experiments.Configs; using Microsoft.ComponentDetection.Orchestrator.Extensions; using Microsoft.Extensions.DependencyInjection; @@ -36,9 +37,11 @@ public void ExperimentConfig_HaveDistinctNames() } [TestMethod] - public void ExperimentConfig_RegistersSwiftDetectorExperiment() + public void ExperimentConfig_SwiftDetectorIsNotInExperimentGroup() { - this.configs.OfType().Should().ContainSingle(); + var swiftDetector = new SwiftResolvedComponentDetector(null, null, null); + + this.configs.Should().OnlyContain(config => !config.IsInExperimentGroup(swiftDetector)); } [TestMethod] diff --git a/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentServiceTests.cs b/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentServiceTests.cs index 4d8173f57..ee369f242 100644 --- a/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentServiceTests.cs +++ b/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentServiceTests.cs @@ -4,16 +4,12 @@ namespace Microsoft.ComponentDetection.Orchestrator.Tests.Experiments; using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading.Tasks; using AwesomeAssertions; using Microsoft.ComponentDetection.Common.DependencyGraph; using Microsoft.ComponentDetection.Contracts; using Microsoft.ComponentDetection.Contracts.BcdeModels; -using Microsoft.ComponentDetection.Contracts.TypedComponent; -using Microsoft.ComponentDetection.Detectors.CocoaPods; using Microsoft.ComponentDetection.Detectors.NuGet; -using Microsoft.ComponentDetection.Detectors.Swift; using Microsoft.ComponentDetection.Orchestrator.Commands; using Microsoft.ComponentDetection.Orchestrator.Experiments; using Microsoft.ComponentDetection.Orchestrator.Experiments.Configs; @@ -169,69 +165,6 @@ public async Task FinishAsync_ProcessesExperimentsAsync() Times.Once()); } - [TestMethod] - public async Task FinishAsync_ProcessesSwiftExperimentWithoutControlDetectorAsync() - { - var config = new SwiftDetectorExperiment(); - var swiftDetector = new SwiftResolvedComponentDetector(null, null, null); - var podDetector = new PodComponentDetector(null, null, null); - var service = new ExperimentService( - [config], - [this.experimentProcessorMock.Object], - this.graphTranslationServiceMock.Object, - this.loggerMock.Object); - service.RemoveUnwantedExperimentsbyDetectors([podDetector]); - - this.SetupGraphMock([]); - service.RecordDetectorRun(this.detectorMock.Object, this.componentRecorder, this.scanSettingsMock.Object); - - var component = new SwiftComponent - { - Name = "alamofire", - Version = "5.9.1", - RepositoryUrl = new Uri("https://github.com/Alamofire/Alamofire"), - CommitHash = "f455c2975872ccd2d9c81594c658af65716e9b9a", - Kind = "remoteSourceControl", - }; - this.SetupGraphMock([new ScannedComponent { Component = component }]); - service.RecordDetectorRun(swiftDetector, this.componentRecorder, this.scanSettingsMock.Object); - - await service.FinishAsync(); - - this.experimentProcessorMock.Verify( - x => x.ProcessExperimentAsync( - config, - It.Is(diff => - diff.AddedIds.Count == 1 && diff.AddedIds.Contains(component.Id) - && diff.RemovedIds.Count == 0 - && diff.ControlDetectors.Count == 0 - && diff.ExperimentalDetectors.Count == 1 - && diff.ExperimentalDetectors.Single().DetectorId == swiftDetector.Id)), - Times.Once()); - } - - [TestMethod] - public async Task FinishAsync_SkipsSwiftExperimentWithoutSwiftComponentsAsync() - { - var config = new SwiftDetectorExperiment(); - var service = new ExperimentService( - [config], - [this.experimentProcessorMock.Object], - this.graphTranslationServiceMock.Object, - this.loggerMock.Object); - this.SetupGraphMock(ExperimentTestUtils.CreateRandomComponents()); - service.RecordDetectorRun(new PodComponentDetector(null, null, null), this.componentRecorder, this.scanSettingsMock.Object); - - this.SetupGraphMock([]); - service.RecordDetectorRun(new SwiftResolvedComponentDetector(null, null, null), this.componentRecorder, this.scanSettingsMock.Object); - - await service.FinishAsync(); - - this.experimentProcessorMock.Verify( - x => x.ProcessExperimentAsync(config, It.IsAny()), - Times.Never()); - } - [TestMethod] public async Task FinishAsync_SwallowsExceptionsAsync() { diff --git a/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/SwiftDetectorExperimentTests.cs b/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/SwiftDetectorExperimentTests.cs deleted file mode 100644 index 363854359..000000000 --- a/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/SwiftDetectorExperimentTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace Microsoft.ComponentDetection.Orchestrator.Tests.Experiments; - -using AwesomeAssertions; -using Microsoft.ComponentDetection.Contracts; -using Microsoft.ComponentDetection.Detectors.CocoaPods; -using Microsoft.ComponentDetection.Detectors.Swift; -using Microsoft.ComponentDetection.Orchestrator.Experiments.Configs; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; - -[TestClass] -[TestCategory("Governance/All")] -[TestCategory("Governance/ComponentDetection")] -public class SwiftDetectorExperimentTests -{ - private readonly SwiftDetectorExperiment experiment = new(); - private readonly SwiftResolvedComponentDetector swiftDetector = new(null!, null!, null!); - private readonly PodComponentDetector podDetector = new(null!, null!, null!); - - [TestMethod] - public void IsInControlGroup_AlwaysReturnsFalse() - { - this.experiment.IsInControlGroup(this.swiftDetector).Should().BeFalse(); - this.experiment.IsInControlGroup(this.podDetector).Should().BeFalse(); - this.experiment.IsInControlGroup(Mock.Of()).Should().BeFalse(); - } - - [TestMethod] - public void IsInExperimentGroup_SwiftDetector_ReturnsTrue() - { - this.experiment.IsInExperimentGroup(this.swiftDetector).Should().BeTrue(); - } - - [TestMethod] - public void IsInExperimentGroup_OtherDetectors_ReturnsFalse() - { - this.experiment.IsInExperimentGroup(this.podDetector).Should().BeFalse(); - this.experiment.IsInExperimentGroup(Mock.Of()).Should().BeFalse(); - } - - [TestMethod] - [DataRow(0)] - [DataRow(1)] - public void ShouldRecord_AlwaysReturnsTrue(int numComponents) - { - this.experiment.ShouldRecord(this.swiftDetector, numComponents).Should().BeTrue(); - this.experiment.ShouldRecord(this.podDetector, numComponents).Should().BeTrue(); - this.experiment.ShouldRecord(Mock.Of(), numComponents).Should().BeTrue(); - } -}