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
11 changes: 11 additions & 0 deletions docs/detectors/swift.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Microsoft.ComponentDetection.Orchestrator.Experiments.Configs;

using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Detectors.Swift;

/// <summary>
/// Records Swift Package Manager discoveries against an empty baseline.
/// </summary>
public class SwiftDetectorExperiment : IExperimentConfiguration
{
/// <inheritdoc />
public string Name => "SwiftDetectorExperiment";

/// <inheritdoc />
public bool IsInControlGroup(IComponentDetector componentDetector) => false;

/// <inheritdoc />
public bool IsInExperimentGroup(IComponentDetector componentDetector) => componentDetector is SwiftResolvedComponentDetector;

/// <inheritdoc />
public bool ShouldRecord(IComponentDetector componentDetector, int numComponents) => true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public static IServiceCollection AddComponentDetection(this IServiceCollection s
services.AddSingleton<IExperimentConfiguration, SimplePipExperiment>();
services.AddSingleton<IExperimentConfiguration, CondaLockDetectorExperiment>();
services.AddSingleton<IExperimentConfiguration, LinuxApplicationLayerExperiment>();
services.AddSingleton<IExperimentConfiguration, SwiftDetectorExperiment>();

// Detectors
// CocoaPods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public void ExperimentConfig_HaveDistinctNames()
names.Should().OnlyHaveUniqueItems();
}

[TestMethod]
public void ExperimentConfig_RegistersSwiftDetectorExperiment()
{
this.configs.OfType<SwiftDetectorExperiment>().Should().ContainSingle();
}

[TestMethod]
public async Task ExperimentConfig_InitShouldNotThrowAsync()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ 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;
Expand Down Expand Up @@ -165,6 +169,69 @@ 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<ExperimentDiff>(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<ExperimentDiff>()),
Times.Never());
}

[TestMethod]
public async Task FinishAsync_SwallowsExceptionsAsync()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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<IComponentDetector>()).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<IComponentDetector>()).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<IComponentDetector>(), numComponents).Should().BeTrue();
}
}
Loading