diff --git a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/IExperimentConfiguration.cs b/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/IExperimentConfiguration.cs index 49a4b3f48..cbfdf8851 100644 --- a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/IExperimentConfiguration.cs +++ b/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/IExperimentConfiguration.cs @@ -1,5 +1,6 @@ namespace Microsoft.ComponentDetection.Orchestrator.Experiments.Configs; +using System.Threading.Tasks; using Microsoft.ComponentDetection.Contracts; /// @@ -15,6 +16,12 @@ public interface IExperimentConfiguration /// string Name { get; } + /// + /// Initializes the experiment configuration. + /// + /// A representing the asynchronous operation. + Task InitAsync() => Task.CompletedTask; + /// /// Specifies if the detector is in the control group. /// diff --git a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/RustCliDetectorExperiment.cs b/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/RustCliDetectorExperiment.cs index e17e71696..3ec089917 100644 --- a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/RustCliDetectorExperiment.cs +++ b/src/Microsoft.ComponentDetection.Orchestrator/Experiments/Configs/RustCliDetectorExperiment.cs @@ -1,5 +1,6 @@ namespace Microsoft.ComponentDetection.Orchestrator.Experiments.Configs; +using System.Threading.Tasks; using Microsoft.ComponentDetection.Contracts; using Microsoft.ComponentDetection.Detectors.Rust; @@ -8,6 +9,15 @@ namespace Microsoft.ComponentDetection.Orchestrator.Experiments.Configs; /// public class RustCliDetectorExperiment : IExperimentConfiguration { + private readonly ICommandLineInvocationService commandLineInvocationService; + private bool cargoCliAvailable; + + /// + /// Initializes a new instance of the class. + /// + /// The command line invocation service. + public RustCliDetectorExperiment(ICommandLineInvocationService commandLineInvocationService) => this.commandLineInvocationService = commandLineInvocationService; + /// public string Name => "RustCliDetector"; @@ -18,5 +28,8 @@ public class RustCliDetectorExperiment : IExperimentConfiguration public bool IsInExperimentGroup(IComponentDetector componentDetector) => componentDetector is RustCliDetector; /// - public bool ShouldRecord(IComponentDetector componentDetector, int numComponents) => true; + public bool ShouldRecord(IComponentDetector componentDetector, int numComponents) => this.cargoCliAvailable; + + /// + public async Task InitAsync() => this.cargoCliAvailable = await this.commandLineInvocationService.CanCommandBeLocatedAsync("cargo", null); } diff --git a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/ExperimentService.cs b/src/Microsoft.ComponentDetection.Orchestrator/Experiments/ExperimentService.cs index 68939cd28..30bcc5c44 100644 --- a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/ExperimentService.cs +++ b/src/Microsoft.ComponentDetection.Orchestrator/Experiments/ExperimentService.cs @@ -43,6 +43,23 @@ public ExperimentService( this.logger = logger; } + /// + public async Task InitializeAsync() + { + foreach (var config in this.experiments.Keys) + { + try + { + await config.InitAsync(); + } + catch (Exception e) + { + this.logger.LogWarning(e, "Failed to initialize experiment {Experiment}, skipping it", config.Name); + this.experiments.TryRemove(config, out _); + } + } + } + /// public void RecordDetectorRun( IComponentDetector detector, diff --git a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/IExperimentService.cs b/src/Microsoft.ComponentDetection.Orchestrator/Experiments/IExperimentService.cs index 4477abbbc..9e00a2c9c 100644 --- a/src/Microsoft.ComponentDetection.Orchestrator/Experiments/IExperimentService.cs +++ b/src/Microsoft.ComponentDetection.Orchestrator/Experiments/IExperimentService.cs @@ -11,6 +11,12 @@ namespace Microsoft.ComponentDetection.Orchestrator.Experiments; /// public interface IExperimentService { + /// + /// Initializes the experiment services by preparing the experiment configurations. + /// + /// A representing the asynchronous operation. + Task InitializeAsync(); + /// /// Records the results of a detector execution and processes the results for any active experiments. /// diff --git a/src/Microsoft.ComponentDetection.Orchestrator/Services/DetectorProcessingService.cs b/src/Microsoft.ComponentDetection.Orchestrator/Services/DetectorProcessingService.cs index 658de9a71..93e17ea44 100644 --- a/src/Microsoft.ComponentDetection.Orchestrator/Services/DetectorProcessingService.cs +++ b/src/Microsoft.ComponentDetection.Orchestrator/Services/DetectorProcessingService.cs @@ -55,6 +55,7 @@ public async Task ProcessDetectorsAsync( ? this.GenerateDirectoryExclusionPredicate(settings.SourceDirectory.ToString(), settings.DirectoryExclusionList, settings.DirectoryExclusionListObsolete, allowWindowsPaths: false, ignoreCase: false) : this.GenerateDirectoryExclusionPredicate(settings.SourceDirectory.ToString(), settings.DirectoryExclusionList, settings.DirectoryExclusionListObsolete, allowWindowsPaths: true, ignoreCase: true); + await this.experimentService.InitializeAsync(); this.experimentService.RemoveUnwantedExperimentsbyDetectors(detectorRestrictions.DisabledDetectors); IEnumerable> scanTasks = detectors diff --git a/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentServiceTests.cs b/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentServiceTests.cs index 3c5df555c..c867f8ddb 100644 --- a/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentServiceTests.cs +++ b/test/Microsoft.ComponentDetection.Orchestrator.Tests/Experiments/ExperimentServiceTests.cs @@ -1,5 +1,6 @@ namespace Microsoft.ComponentDetection.Orchestrator.Tests.Experiments; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -279,9 +280,9 @@ public async Task RecordDetectorRun_CheckUnwantedDetectors_RemoveExperimentAsync var detectorList = new List { new NuGetComponentDetector( - new Mock().Object, - new Mock().Object, - new Mock>().Object), this.detectorMock.Object, + new Mock().Object, + new Mock().Object, + new Mock>().Object), this.detectorMock.Object, }; service.RemoveUnwantedExperimentsbyDetectors(detectorList); @@ -310,9 +311,9 @@ public async Task RecordDetectorRun_CheckUnwantedDetectors_KeepExperimentAsync() var detectorList = new List { new NuGetComponentDetector( - new Mock().Object, - new Mock().Object, - new Mock>().Object), + new Mock().Object, + new Mock().Object, + new Mock>().Object), }; service.RemoveUnwantedExperimentsbyDetectors(detectorList); @@ -325,4 +326,35 @@ public async Task RecordDetectorRun_CheckUnwantedDetectors_KeepExperimentAsync() x => x.ProcessExperimentAsync(this.experimentConfigMock.Object, It.IsAny()), Times.Once()); } + + [TestMethod] + public async Task InitializeAsync_InitsConfigsAsync() + { + var service = new ExperimentService( + new[] { this.experimentConfigMock.Object }, + new[] { this.experimentProcessorMock.Object }, + this.graphTranslationServiceMock.Object, + this.loggerMock.Object); + + await service.InitializeAsync(); + + this.experimentConfigMock.Verify(x => x.InitAsync(), Times.Once()); + } + + [TestMethod] + public async Task InitializeAsync_SwallowsExceptionsAsync() + { + this.experimentConfigMock.Setup(x => x.InitAsync()).ThrowsAsync(new InvalidOperationException()); + + var service = new ExperimentService( + new[] { this.experimentConfigMock.Object }, + new[] { this.experimentProcessorMock.Object }, + this.graphTranslationServiceMock.Object, + this.loggerMock.Object); + + var action = async () => await service.InitializeAsync(); + + await action.Should().NotThrowAsync(); + this.experimentConfigMock.Verify(x => x.InitAsync(), Times.Once()); + } } diff --git a/test/Microsoft.ComponentDetection.Orchestrator.Tests/Services/DetectorProcessingServiceTests.cs b/test/Microsoft.ComponentDetection.Orchestrator.Tests/Services/DetectorProcessingServiceTests.cs index 936384915..417570e8c 100644 --- a/test/Microsoft.ComponentDetection.Orchestrator.Tests/Services/DetectorProcessingServiceTests.cs +++ b/test/Microsoft.ComponentDetection.Orchestrator.Tests/Services/DetectorProcessingServiceTests.cs @@ -321,17 +321,17 @@ public async Task ProcessDetectorsAsync_DirectoryExclusionPredicateWorksAsExpect public void GenerateDirectoryExclusionPredicate_IgnoreCaseAndAllowWindowsPathsWorksAsExpected() { /* - * We can't test a scenario like: - * - * SourceDirectory = /Some/Source/Directory - * DirectoryExclusionList = *Some/* - * allowWindowsPath = false - * - * and expect to exclude the directory, because when - * we pass the SourceDirectory path to DirectoryInfo and we are running the test on Windows, - * DirectoryInfo transalate it to C:\\Some\Source\Directory - * making the test fail - */ + * We can't test a scenario like: + * + * SourceDirectory = /Some/Source/Directory + * DirectoryExclusionList = *Some/* + * allowWindowsPath = false + * + * and expect to exclude the directory, because when + * we pass the SourceDirectory path to DirectoryInfo and we are running the test on Windows, + * DirectoryInfo transalate it to C:\\Some\Source\Directory + * making the test fail + */ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -534,6 +534,19 @@ public async Task ProcessDetectorsAsync_RecordsDetectorRunsAsync() Times.Once()); } + [TestMethod] + public async Task ProcessDetectorsAsync_InitializesExperimentsAsync() + { + this.detectorsToUse = new[] + { + this.firstFileComponentDetectorMock.Object, this.secondFileComponentDetectorMock.Object, + }; + + await this.serviceUnderTest.ProcessDetectorsAsync(DefaultArgs, this.detectorsToUse, new DetectorRestrictions()); + + this.experimentServiceMock.Verify(x => x.InitializeAsync(), Times.Once); + } + private Mock SetupFileDetectorMock(string id) { var mockFileDetector = new Mock();