public void SetUp() { LoggingServiceFactory loggingFactory = new LoggingServiceFactory(LoggerMode.Synchronous, 1); _loggingService = loggingFactory.CreateInstance(BuildComponentType.LoggingService) as LoggingService; _customLogger = new MyCustomLogger(); _mockHost = new MockHost(); _mockHost.LoggingService = _loggingService; _loggingService.RegisterLogger(_customLogger); _elementLocation = ElementLocation.Create("MockFile", 5, 5); BuildRequest buildRequest = new BuildRequest(1 /* submissionId */, 1, 1, new List<string>(), null, BuildEventContext.Invalid, null); BuildRequestConfiguration configuration = new BuildRequestConfiguration(1, new BuildRequestData("Nothing", new Dictionary<string, string>(), "4.0", new string[0], null), "2.0"); configuration.Project = new ProjectInstance(ProjectRootElement.Create()); BuildRequestEntry entry = new BuildRequestEntry(buildRequest, configuration); BuildResult buildResult = new BuildResult(buildRequest, false); buildResult.AddResultsForTarget("Build", new TargetResult(new TaskItem[] { new TaskItem("IamSuper", configuration.ProjectFullPath) }, TestUtilities.GetSkippedResult())); _mockRequestCallback = new MockIRequestBuilderCallback(new BuildResult[] { buildResult }); entry.Builder = (IRequestBuilder)_mockRequestCallback; _taskHost = new TaskHost(_mockHost, entry, _elementLocation, null /*Dont care about the callback either unless doing a build*/); _taskHost.LoggingContext = new TaskLoggingContext(_loggingService, BuildEventContext.Invalid); }
public void TestSimpleBuildRequest() { BuildRequestConfiguration configuration = CreateTestProject(1); try { TestTargetBuilder targetBuilder = (TestTargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); IConfigCache configCache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); configCache.AddConfiguration(configuration); BuildRequest request = CreateNewBuildRequest(1, new string[1] { "target1" }); BuildRequestEntry entry = new BuildRequestEntry(request, configuration); BuildResult result = new BuildResult(request); result.AddResultsForTarget("target1", GetEmptySuccessfulTargetResult()); targetBuilder.SetResultsToReturn(result); _requestBuilder.BuildRequest(GetNodeLoggingContext(), entry); WaitForEvent(_buildRequestCompletedEvent, "Build Request Completed"); Assert.Equal(BuildRequestEntryState.Complete, entry.State); Assert.Equal(entry, _buildRequestCompleted_Entry); Assert.Equal(BuildResultCode.Success, _buildRequestCompleted_Entry.Result.OverallResult); } finally { DeleteTestProject(configuration); } }
/// <summary> /// Indicates to the TaskHost that it is no longer needed. /// Called by TaskBuilder when the task using the EngineProxy is done. /// </summary> internal void MarkAsInactive() { lock (_callbackMonitor) { VerifyActiveProxy(); _activeProxy = false; // Since the task has a pointer to this class it may store it in a static field. Null out // internal data so the leak of this object doesn't lead to a major memory leak. _host = null; _requestEntry = null; // Don't bother clearing the tiny task location _taskLoggingContext = null; _targetBuilderCallback = null; // Clear out the sponsor (who is responsible for keeping the EngineProxy remoting lease alive until the task is done) // this will be null if the engine proxy was never sent across an AppDomain boundary. if (_sponsor != null) { ILease lease = (ILease)RemotingServices.GetLifetimeService(this); if (lease != null) { lease.Unregister(_sponsor); } _sponsor.Close(); _sponsor = null; } } }
public void TestConstructorNullTarget() { ProjectInstance project = CreateTestProject(true /* Returns enabled */); BuildRequestConfiguration config = new BuildRequestConfiguration(1, new BuildRequestData("foo", new Dictionary<string, string>(), "foo", new string[0], null), "2.0"); BuildRequestEntry requestEntry = new BuildRequestEntry(CreateNewBuildRequest(1, new string[] { "foo" }), config); Lookup lookup = new Lookup(new ItemDictionary<ProjectItemInstance>(project.Items), new PropertyDictionary<ProjectPropertyInstance>(project.Properties), null); TargetEntry entry = new TargetEntry(requestEntry, this, null, lookup, null, _host, false); }
public void TestConstructorBad() { Assert.Throws<ArgumentNullException>(() => { BuildRequestEntry entry = new BuildRequestEntry(null, null); } ); }
public void TestConstructorGood() { BuildRequest request = CreateNewBuildRequest(1, new string[0] { }); BuildRequestData data = new BuildRequestData("foo", new Dictionary<string, string>(), "foo", new string[0], null); BuildRequestConfiguration config = new BuildRequestConfiguration(1, data, "2.0"); BuildRequestEntry entry = new BuildRequestEntry(request, config); Assert.AreEqual(entry.State, BuildRequestEntryState.Ready); Assert.AreEqual(entry.Request, request); }
public QAMockTargetBuilder() { _host = null; _testDataProvider = null; _testDefinition = null; _requestCallBack = null; _requestEntry = null; _projectLoggingContext = null; _buildDone = new AutoResetEvent(false); }
/// <summary> /// Builds the specified targets of an entry. The cancel event should only be set to true if we are planning /// on simulating execution time when a target is built /// </summary> public Task<BuildResult> BuildTargets(ProjectLoggingContext loggingContext, BuildRequestEntry entry, IRequestBuilderCallback callback, string[] targetNames, Lookup baseLookup, CancellationToken cancellationToken) { _requestEntry = entry; _projectLoggingContext = loggingContext; _requestCallBack = callback; _testDefinition = _testDataProvider[entry.Request.ConfigurationId]; _cancellationToken = cancellationToken; BuildResult result = GenerateResults(targetNames); return Task<BuildResult>.FromResult(result); }
/// <summary> /// Constructor that takes in nothing. /// </summary> public QARequestBuilder() { _host = null; _configCache = null; _resultsCache = null; _builderThread = null; _requestedEntry = null; _cancelEvent = new AutoResetEvent(false); _continueEvent = new AutoResetEvent(false); _threadStarted = new ManualResetEvent(false); _currentProjectDefinition = null; }
public void BuildRequest(NodeLoggingContext context, BuildRequestEntry entry) { Assert.Null(_builderThread); // "Received BuildRequest while one was in progress" _continueEvent = new AutoResetEvent(false); _cancelEvent = new AutoResetEvent(false); _entry = entry; entry.Continue(); _builderThread = new Thread(BuilderThreadProc); _builderThread.Start(); }
/// <summary> /// Constructor /// </summary> /// <param name="host">The component host</param> /// <param name="requestEntry">The build request entry</param> /// <param name="taskLocation">The <see cref="ElementLocation"/> of the task.</param> /// <param name="targetBuilderCallback">An <see cref="ITargetBuilderCallback"/> to use to invoke targets and build projects.</param> public TaskHost(IBuildComponentHost host, BuildRequestEntry requestEntry, ElementLocation taskLocation, ITargetBuilderCallback targetBuilderCallback) { ErrorUtilities.VerifyThrowArgumentNull(host, "host"); ErrorUtilities.VerifyThrowArgumentNull(requestEntry, "requestEntry"); ErrorUtilities.VerifyThrowInternalNull(taskLocation, "taskLocation"); _host = host; _requestEntry = requestEntry; _taskLocation = taskLocation; _targetBuilderCallback = targetBuilderCallback; _continueOnError = false; _activeProxy = true; _callbackMonitor = new Object(); }
public void TestSimpleStateProgression() { // Start in Ready BuildRequest request = CreateNewBuildRequest(1, new string[1] { "foo" }); BuildRequestConfiguration config = new BuildRequestConfiguration(1, new BuildRequestData("foo", new Dictionary<string, string>(), "foo", new string[0], null), "2.0"); BuildRequestEntry entry = new BuildRequestEntry(request, config); Assert.AreEqual(entry.State, BuildRequestEntryState.Ready); Assert.AreEqual(entry.Request, request); Assert.IsNull(entry.Result); // Move to active. Should not be any results yet. IDictionary<int, BuildResult> results = entry.Continue(); Assert.AreEqual(entry.State, BuildRequestEntryState.Active); Assert.IsNull(entry.Result); Assert.IsNull(results); // Wait for results, move to waiting. BuildRequest waitingRequest = CreateNewBuildRequest(2, new string[1] { "bar" }); entry.WaitForResult(waitingRequest); Assert.AreEqual(entry.State, BuildRequestEntryState.Waiting); Assert.AreEqual(entry.Request, request); Assert.IsNull(entry.Result); // Provide the results, move to ready. BuildResult requiredResult = new BuildResult(waitingRequest); requiredResult.AddResultsForTarget("bar", TestUtilities.GetEmptySucceedingTargetResult()); entry.ReportResult(requiredResult); Assert.AreEqual(entry.State, BuildRequestEntryState.Ready); Assert.AreEqual(entry.Request, request); Assert.IsNull(entry.Result); // Continue the build, move to active. results = entry.Continue(); Assert.AreEqual(entry.State, BuildRequestEntryState.Active); Assert.IsNull(entry.Result); Assert.AreEqual(results.Count, 1); Assert.IsTrue(results.ContainsKey(requiredResult.NodeRequestId)); Assert.AreEqual(results[requiredResult.NodeRequestId], requiredResult); // Complete the build, move to completed. BuildResult result = new BuildResult(request); result.AddResultsForTarget("foo", TestUtilities.GetEmptySucceedingTargetResult()); entry.Complete(result); Assert.AreEqual(entry.State, BuildRequestEntryState.Complete); Assert.IsNotNull(entry.Result); Assert.AreEqual(entry.Result, result); }
/// <summary> /// The constructor. /// </summary> /// <param name="requestEntry">The build request entry for the target.</param> /// <param name="targetBuilderCallback">The target builder callback.</param> /// <param name="targetSpecification">The specification for the target to build.</param> /// <param name="baseLookup">The lookup to use.</param> /// <param name="parentTarget">The parent of this entry, if any.</param> /// <param name="host">The Build Component Host to use.</param> /// <param name="stopProcessingOnCompletion">True if the target builder should stop processing the current target stack when this target is complete.</param> internal TargetEntry(BuildRequestEntry requestEntry, ITargetBuilderCallback targetBuilderCallback, TargetSpecification targetSpecification, Lookup baseLookup, TargetEntry parentTarget, IBuildComponentHost host, bool stopProcessingOnCompletion) { ErrorUtilities.VerifyThrowArgumentNull(requestEntry, "requestEntry"); ErrorUtilities.VerifyThrowArgumentNull(targetBuilderCallback, "targetBuilderCallback"); ErrorUtilities.VerifyThrowArgumentNull(targetSpecification, "targetName"); ErrorUtilities.VerifyThrowArgumentNull(baseLookup, "lookup"); ErrorUtilities.VerifyThrowArgumentNull(host, "host"); _requestEntry = requestEntry; _targetBuilderCallback = targetBuilderCallback; _targetSpecification = targetSpecification; _parentTarget = parentTarget; _expander = new Expander <ProjectPropertyInstance, ProjectItemInstance>(baseLookup.ReadOnlyLookup, baseLookup.ReadOnlyLookup); _state = TargetEntryState.Dependencies; _baseLookup = baseLookup; _host = host; this.StopProcessingOnCompletion = stopProcessingOnCompletion; }
public void TestMixedWaitingRequests() { BuildRequest request = CreateNewBuildRequest(1, new string[1] { "foo" }); BuildRequestConfiguration config = new BuildRequestConfiguration(1, new BuildRequestData("foo", new Dictionary<string, string>(), "foo", new string[0], null), "2.0"); BuildRequestEntry entry = new BuildRequestEntry(request, config); Assert.AreEqual(entry.State, BuildRequestEntryState.Ready); entry.Continue(); Assert.AreEqual(entry.State, BuildRequestEntryState.Active); BuildRequest waitingRequest1 = CreateNewBuildRequest(2, new string[1] { "bar" }); entry.WaitForResult(waitingRequest1); Assert.AreEqual(entry.State, BuildRequestEntryState.Waiting); BuildRequest waitingRequest2 = CreateNewBuildRequest(-1, new string[1] { "xor" }); entry.WaitForResult(waitingRequest2); Assert.AreEqual(entry.State, BuildRequestEntryState.Waiting); Assert.IsNull(entry.GetRequestsToIssueIfReady(), "Entry should not be ready to issue because there are unresolved configurations"); entry.ResolveConfigurationRequest(-1, 3); Assert.AreEqual(entry.State, BuildRequestEntryState.Waiting); BuildResult requiredResult1 = new BuildResult(waitingRequest1); requiredResult1.AddResultsForTarget("bar", TestUtilities.GetEmptySucceedingTargetResult()); entry.ReportResult(requiredResult1); Assert.AreEqual(entry.State, BuildRequestEntryState.Waiting); BuildResult requiredResult2 = new BuildResult(waitingRequest2); requiredResult2.AddResultsForTarget("xor", TestUtilities.GetEmptySucceedingTargetResult()); entry.ReportResult(requiredResult2); Assert.AreEqual(entry.State, BuildRequestEntryState.Ready); }
/// <summary> /// Builds the specified targets. /// </summary> /// <param name="loggingContext">The logging context for the project.</param> /// <param name="entry">The BuildRequestEntry for which we are building targets.</param> /// <param name="callback">The callback to be used to handle new project build requests.</param> /// <param name="targetNames">The names of the targets to build.</param> /// <param name="baseLookup">The Lookup containing all current items and properties for this target.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use when building the targets.</param> /// <returns>The target's outputs and result codes</returns> public async Task <BuildResult> BuildTargets(ProjectLoggingContext loggingContext, BuildRequestEntry entry, IRequestBuilderCallback callback, string[] targetNames, Lookup baseLookup, CancellationToken cancellationToken) { ErrorUtilities.VerifyThrowArgumentNull(loggingContext, "projectLoggingContext"); ErrorUtilities.VerifyThrowArgumentNull(entry, "entry"); ErrorUtilities.VerifyThrowArgumentNull(callback, "requestBuilderCallback"); ErrorUtilities.VerifyThrowArgumentNull(targetNames, "targetNames"); ErrorUtilities.VerifyThrowArgumentNull(baseLookup, "baseLookup"); ErrorUtilities.VerifyThrow(targetNames.Length > 0, "List of targets must be non-empty"); ErrorUtilities.VerifyThrow(_componentHost != null, "InitializeComponent must be called before building targets."); _requestEntry = entry; _requestBuilderCallback = callback; _projectLoggingContext = loggingContext; _cancellationToken = cancellationToken; // Clone the base lookup so that if we are re-entered by another request while this one in blocked, we don't have visibility to // their state, and they have no visibility into ours. _baseLookup = baseLookup.Clone(); _targetsToBuild = new ConcurrentStack <TargetEntry>(); // Get the actual target objects from the names BuildRequestConfiguration configuration = _requestEntry.RequestConfiguration; bool previousCacheableStatus = configuration.IsCacheable; configuration.IsCacheable = false; configuration.RetrieveFromCache(); _projectInstance = configuration.Project; // Now get the current results cache entry. ResultsCache resultsCache = (ResultsCache)_componentHost.GetComponent(BuildComponentType.ResultsCache); BuildResult existingBuildResult = null; resultsCache.ResultsDictionary.TryGetValue(_requestEntry.Request.ConfigurationId, out existingBuildResult); _buildResult = new BuildResult(entry.Request, existingBuildResult, null); if (existingBuildResult == null) { // Add this result so that if our project gets re-entered we won't rebuild any targets we have already built. resultsCache.AddResult(_buildResult); } List <TargetSpecification> targets = new List <TargetSpecification>(targetNames.Length); foreach (string targetName in targetNames) { var targetExists = _projectInstance.Targets.ContainsKey(targetName); if (!targetExists && entry.Request.BuildRequestDataFlags.HasFlag(BuildRequestDataFlags.SkipNonexistentTargets)) { _projectLoggingContext.LogComment(Framework.MessageImportance.Low, "TargetSkippedWhenSkipNonexistentTargets", targetName); continue; } targets.Add(new TargetSpecification(targetName, targetExists ? _projectInstance.Targets[targetName].Location : _projectInstance.ProjectFileLocation)); } // Push targets onto the stack. This method will reverse their push order so that they // get built in the same order specified in the array. await PushTargets(targets, null, baseLookup, false, false, TargetBuiltReason.None); // Now process the targets ITaskBuilder taskBuilder = _componentHost.GetComponent(BuildComponentType.TaskBuilder) as ITaskBuilder; try { await ProcessTargetStack(taskBuilder); } finally { // If there are still targets left on the stack, they need to be removed from the 'active targets' list foreach (TargetEntry target in _targetsToBuild) { configuration.ActivelyBuildingTargets.Remove(target.Name); } ((IBuildComponent)taskBuilder).ShutdownComponent(); } if (_cancellationToken.IsCancellationRequested) { throw new BuildAbortedException(); } // Gather up outputs for the requested targets and return those. All of our information should be in the base lookup now. BuildResult resultsToReport = new BuildResult(_buildResult, targetNames); // Return after-build project state if requested. if (_requestEntry.Request.BuildRequestDataFlags.HasFlag(BuildRequestDataFlags.ProvideProjectStateAfterBuild)) { resultsToReport.ProjectStateAfterBuild = _projectInstance; } configuration.IsCacheable = previousCacheableStatus; return(resultsToReport); }
/// <summary> /// Not Implemented /// </summary> public void BuildRequest(NodeLoggingContext nodeLoggingContext, BuildRequestEntry entry) { throw new NotImplementedException(); }
public void TestComplexOrdering2() { string projectBody = @" <Target Name='BuildDep'> <BuildDepTask/> </Target> <Target Name='BeforeDepDep'> <BeforeDepDepTask/> </Target> <Target Name='BeforeBeforeDep' BeforeTargets='BeforeDep'> <BeforeBeforeDepTask/> </Target> <Target Name='AfterBeforeBeforeDep' AfterTargets='BeforeBeforeDep'> <AfterBeforeBeforeDepTask/> </Target> <Target Name='BeforeDep' DependsOnTargets='BeforeDepDep'> <BeforeDepTask/> </Target> <Target Name='Before' DependsOnTargets='BeforeDep' BeforeTargets='Build'> <BeforeTask/> </Target> <Target Name='AfterBeforeDepDep'> <AfterBeforeDepDepTask/> </Target> <Target Name='AfterBeforeDep' DependsOnTargets='AfterBeforeDepDep'> <AfterBeforeDepTask/> </Target> <Target Name='AfterBefore' DependsOnTargets='AfterBeforeDep' AfterTargets='Before'> <AfterBeforeTask/> </Target> <Target Name='Build' DependsOnTargets='BuildDep'> <BuildTask/> </Target> "; ProjectInstance project = CreateTestProject(projectBody); TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, new string[] { "Build" }), cache[1]); BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, entry.Request.Targets.ToArray(), CreateStandardLookup(project), CancellationToken.None).Result; AssertTaskExecutionOrder(new string[] { "BuildDepTask", "BeforeDepDepTask", "BeforeBeforeDepTask", "AfterBeforeBeforeDepTask", "BeforeDepTask", "BeforeTask", "AfterBeforeDepDepTask", "AfterBeforeDepTask", "AfterBeforeTask", "BuildTask" }); }
/// <summary> /// Not Implemented /// </summary> private void MockIRequestBuilderCallback_OnBuildRequestCompleted(BuildRequestEntry completedEntry) { throw new NotImplementedException(); }
public void TestConstructorBad() { BuildRequestEntry entry = new BuildRequestEntry(null, null); }
public void TestResultsWithNoMatch1() { BuildRequest request = CreateNewBuildRequest(1, new string[1] { "foo" }); BuildRequestConfiguration config = new BuildRequestConfiguration(1, new BuildRequestData("foo", new Dictionary<string, string>(), "foo", new string[0], null), "2.0"); BuildRequestEntry entry = new BuildRequestEntry(request, config); Assert.AreEqual(entry.State, BuildRequestEntryState.Ready); entry.Continue(); Assert.AreEqual(entry.State, BuildRequestEntryState.Active); BuildRequest waitingRequest1 = CreateNewBuildRequest(2, new string[1] { "bar" }); entry.WaitForResult(waitingRequest1); Assert.AreEqual(entry.State, BuildRequestEntryState.Waiting); BuildRequest randomRequest = CreateNewBuildRequest(3, new string[0]); BuildResult requiredResult = new BuildResult(randomRequest); requiredResult.AddResultsForTarget("bar", TestUtilities.GetEmptySucceedingTargetResult()); entry.ReportResult(requiredResult); Assert.AreEqual(entry.State, BuildRequestEntryState.Waiting); }
public void TestBeforeTargetsEscaped() { string projectBody = @" <Target Name='Build;Me' Outputs='$(Test)'> <BuildTask/> </Target> <Target Name='Before' BeforeTargets='Build%3bMe'> <BeforeTask/> </Target>"; ProjectInstance project = CreateTestProject(projectBody); TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, new string[] { "Build;Me" }), cache[1]); BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, entry.Request.Targets.ToArray(), CreateStandardLookup(project), CancellationToken.None).Result; AssertTaskExecutionOrder(new string[] { "BeforeTask", "BuildTask" }); }
public void TestLegacyCallTarget() { string projectBody = @" <Target Name='Build'> <CallTarget Targets='Foo;Goo'/> </Target> <Target Name='Foo' DependsOnTargets='Foo2'> <FooTarget/> </Target> <Target Name='Goo'> <GooTarget/> </Target> <Target Name='Foo2'> <Foo2Target/> </Target> "; ProjectInstance project = CreateTestProject(projectBody); TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, new string[] { "Build" }), cache[1]); BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, entry.Request.Targets.ToArray(), CreateStandardLookup(project), CancellationToken.None).Result; AssertTaskExecutionOrder(new string[] { "CallTarget", "Foo2Target", "FooTarget", "GooTarget" }); }
/// <summary> /// Not Implemented /// </summary> private void MockIRequestBuilderCallback_OnBuildRequestBlocked(BuildRequestEntry sourceEntry, int blockingGlobalRequestId, string blockingTarget) { throw new NotImplementedException(); }
public void TestDependencyBuildWithSkip() { ProjectInstance project = CreateTestProject(); // DepSkip depends on Skip (which skips) but should succeed itself. TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, new string[] { "DepSkip" }), cache[1]); BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, entry.Request.Targets.ToArray(), CreateStandardLookup(project), CancellationToken.None).Result; Assert.True(result.HasResultsForTarget("DepSkip")); Assert.False(result.HasResultsForTarget("Skip")); Assert.Equal(TargetResultCode.Success, result["DepSkip"].ResultCode); IResultsCache resultsCache = (IResultsCache)_host.GetComponent(BuildComponentType.ResultsCache); Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("SkipCondition")); Assert.Equal(TargetResultCode.Skipped, resultsCache.GetResultForRequest(entry.Request)["SkipCondition"].ResultCode); }
public void TestCancelWithNoEntriesAfterBuild() { string projectBody = @" <Target Name='Build'> <BuildTask/> </Target> "; ProjectInstance project = CreateTestProject(projectBody); TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, new string[] { "Build" }), cache[1]); using (CancellationTokenSource source = new CancellationTokenSource()) { BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, entry.Request.Targets.ToArray(), CreateStandardLookup(project), source.Token).Result; AssertTaskExecutionOrder(new string[] { "BuildTask" }); // This simply should not fail. source.Cancel(); } }
public void TestDependencyBuild() { ProjectInstance project = CreateTestProject(); // The Baz project depends on the Bar target. Both should succeed. TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, new string[] { "Baz" }), cache[1]); BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, entry.Request.Targets.ToArray(), CreateStandardLookup(project), CancellationToken.None).Result; // The result returned from the builder includes only those for the specified targets. Assert.True(result.HasResultsForTarget("Baz")); Assert.False(result.HasResultsForTarget("Bar")); Assert.Equal(TargetResultCode.Success, result["Baz"].ResultCode); // The results cache should have ALL of the results. IResultsCache resultsCache = (IResultsCache)_host.GetComponent(BuildComponentType.ResultsCache); Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Bar")); Assert.Equal(TargetResultCode.Success, resultsCache.GetResultForRequest(entry.Request)["Bar"].ResultCode); }
public void TestNoReadyToWaiting() { BuildRequest request = CreateNewBuildRequest(1, new string[1] { "foo" }); BuildRequestData data1 = new BuildRequestData("foo", new Dictionary<string, string>(), "foo", new string[0], null); BuildRequestConfiguration config = new BuildRequestConfiguration(1, data1, "2.0"); BuildRequestEntry entry = new BuildRequestEntry(request, config); Assert.AreEqual(entry.State, BuildRequestEntryState.Ready); BuildRequest waitingRequest1 = CreateNewBuildRequest(2, new string[1] { "bar" }); entry.WaitForResult(waitingRequest1); }
public void TestAfterTargetsSingleWithErrorAndParent() { string projectBody = @" <Target Name='After' AfterTargets='Build'> <AfterTask/> </Target> <Target Name='Build'> <BuildTask/> <OnError ExecuteTargets='ErrorTarget'/> </Target> <Target Name='ErrorTarget'> <Error/> </Target> <Target Name='ErrorTarget2'> <Error2/> </Target> <Target Name='PostBuild' DependsOnTargets='Build'> <OnError ExecuteTargets='ErrorTarget2'/> </Target> "; MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder); taskBuilder.FailTaskNumber = 2; // Succeed on BuildTask, fail on AfterTask ProjectInstance project = CreateTestProject(projectBody); TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, new string[] { "PostBuild" }), cache[1]); BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, entry.Request.Targets.ToArray(), CreateStandardLookup(project), CancellationToken.None).Result; AssertTaskExecutionOrder(new string[] { "BuildTask", "AfterTask", "Error2" }); }
public void TestNoReadyToComplete() { BuildRequest request = CreateNewBuildRequest(1, new string[1] { "foo" }); BuildRequestData data1 = new BuildRequestData("foo", new Dictionary<string, string>(), "foo", new string[0], null); BuildRequestConfiguration config = new BuildRequestConfiguration(1, data1, "2.0"); BuildRequestEntry entry = new BuildRequestEntry(request, config); Assert.AreEqual(entry.State, BuildRequestEntryState.Ready); BuildResult requiredResult = new BuildResult(request); requiredResult.AddResultsForTarget("foo", TestUtilities.GetEmptySucceedingTargetResult()); entry.Complete(requiredResult); }
public void TestAfterTargetsWithOneReferringToTwo() { string projectBody = @" <Target Name='Build'> <BuildTask/> </Target> <Target Name='Foo'> <FooTask/> </Target> <Target Name='After' AfterTargets='Build;Foo'> <AfterTask/> </Target> "; ProjectInstance project = CreateTestProject(projectBody); TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, new string[] { "Foo" }), cache[1]); BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, entry.Request.Targets.ToArray(), CreateStandardLookup(project), CancellationToken.None).Result; AssertTaskExecutionOrder(new string[] { "FooTask", "AfterTask" }); }
/// <summary> /// Builds the specified targets. /// </summary> /// <param name="loggingContext">The logging context for the project.</param> /// <param name="entry">The BuildRequestEntry for which we are building targets.</param> /// <param name="callback">The callback to be used to handle new project build requests.</param> /// <param name="targetNames">The names of the targets to build.</param> /// <param name="baseLookup">The Lookup containing all current items and properties for this target.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use when building the targets.</param> /// <returns>The target's outputs and result codes</returns> public async Task <BuildResult> BuildTargets(ProjectLoggingContext loggingContext, BuildRequestEntry entry, IRequestBuilderCallback callback, string[] targetNames, Lookup baseLookup, CancellationToken cancellationToken) { ErrorUtilities.VerifyThrowArgumentNull(loggingContext, "projectLoggingContext"); ErrorUtilities.VerifyThrowArgumentNull(entry, nameof(entry)); ErrorUtilities.VerifyThrowArgumentNull(callback, "requestBuilderCallback"); ErrorUtilities.VerifyThrowArgumentNull(targetNames, nameof(targetNames)); ErrorUtilities.VerifyThrowArgumentNull(baseLookup, nameof(baseLookup)); ErrorUtilities.VerifyThrow(targetNames.Length > 0, "List of targets must be non-empty"); ErrorUtilities.VerifyThrow(_componentHost != null, "InitializeComponent must be called before building targets."); _requestEntry = entry; _requestBuilderCallback = callback; _projectLoggingContext = loggingContext; _cancellationToken = cancellationToken; // Clone the base lookup so that if we are re-entered by another request while this one in blocked, we don't have visibility to // their state, and they have no visibility into ours. _baseLookup = baseLookup.Clone(); _targetsToBuild = new ConcurrentStack <TargetEntry>(); // Get the actual target objects from the names BuildRequestConfiguration configuration = _requestEntry.RequestConfiguration; bool previousCacheableStatus = configuration.IsCacheable; configuration.IsCacheable = false; configuration.RetrieveFromCache(); _projectInstance = configuration.Project; // Now get the current results cache entry. IResultsCache resultsCache = (IResultsCache)_componentHost.GetComponent(BuildComponentType.ResultsCache); BuildResult existingBuildResult = resultsCache.GetResultsForConfiguration(_requestEntry.Request.ConfigurationId); _buildResult = new BuildResult(entry.Request, existingBuildResult, null); if (existingBuildResult == null) { // Add this result so that if our project gets re-entered we won't rebuild any targets we have already built. resultsCache.AddResult(_buildResult); } List <TargetSpecification> targets = new List <TargetSpecification>(targetNames.Length); foreach (string targetName in targetNames) { var targetExists = _projectInstance.Targets.TryGetValue(targetName, out ProjectTargetInstance targetInstance); if (!targetExists && entry.Request.BuildRequestDataFlags.HasFlag(BuildRequestDataFlags.SkipNonexistentTargets)) { _projectLoggingContext.LogComment(Framework.MessageImportance.Low, "TargetSkippedWhenSkipNonexistentTargets", targetName); } else { targets.Add(new TargetSpecification(targetName, targetExists ? targetInstance.Location : _projectInstance.ProjectFileLocation)); } } // Push targets onto the stack. This method will reverse their push order so that they // get built in the same order specified in the array. await PushTargets(targets, null, baseLookup, false, false, TargetBuiltReason.None); // Now process the targets ITaskBuilder taskBuilder = _componentHost.GetComponent(BuildComponentType.TaskBuilder) as ITaskBuilder; var staticGraph = new StaticGraph(); staticGraph.ProjectPath = _projectInstance.FullPath; try { await ProcessTargetStack(taskBuilder, staticGraph.StaticTargets); } finally { // If there are still targets left on the stack, they need to be removed from the 'active targets' list foreach (TargetEntry target in _targetsToBuild) { configuration.ActivelyBuildingTargets.Remove(target.Name); } ((IBuildComponent)taskBuilder).ShutdownComponent(); } if (_cancellationToken.IsCancellationRequested) { throw new BuildAbortedException(); } if (entry.IsStatic) { using (var stream = new FileStream(Environment.GetEnvironmentVariable("MSBUILDSTATIC_OUTPUT"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read)) { staticGraph.Files = SimulatedFileSystem.Instance.KnownFiles.ToList(); new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(StaticGraph)).WriteObject(stream, staticGraph); } } // Gather up outputs for the requested targets and return those. All of our information should be in the base lookup now. ComputeAfterTargetFailures(targetNames); BuildResult resultsToReport = new BuildResult(_buildResult, targetNames); // Return after-build project state if requested. if (_requestEntry.Request.BuildRequestDataFlags.HasFlag(BuildRequestDataFlags.ProvideProjectStateAfterBuild)) { resultsToReport.ProjectStateAfterBuild = _projectInstance; } if (_requestEntry.Request.RequestedProjectState != null) { resultsToReport.ProjectStateAfterBuild = _projectInstance.FilteredCopy(_requestEntry.Request.RequestedProjectState); } configuration.IsCacheable = previousCacheableStatus; return(resultsToReport); }
public void TestSimpleBuild() { ProjectInstance project = CreateTestProject(); // The Empty target has no inputs or outputs. TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, new string[] { "Empty" }), cache[1]); BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, entry.Request.Targets.ToArray(), CreateStandardLookup(project), CancellationToken.None).Result; Assert.True(result.HasResultsForTarget("Empty")); Assert.Equal(TargetResultCode.Success, result["Empty"].ResultCode); Assert.Equal(0, result["Empty"].Items.Length); }
/// <summary> /// Creates a project logging context. /// </summary> /// <param name="entry">The entry on which to base the logging context.</param> /// <returns>The context</returns> private ProjectLoggingContext GetProjectLoggingContext(BuildRequestEntry entry) { return new ProjectLoggingContext(new NodeLoggingContext(_host, 1, false), entry, null); }
/// <summary> /// Not Implemented /// </summary> private void MockIRequestBuilderCallback_OnNewBuildRequests(BuildRequestEntry sourceEntry, FullyQualifiedBuildRequest[] requests) { throw new NotImplementedException(); }
/// <summary> /// Runs all of the tasks for this target, batched as necessary. /// </summary> internal async Task ExecuteTarget(ITaskBuilder taskBuilder, BuildRequestEntry requestEntry, ProjectLoggingContext projectLoggingContext, CancellationToken cancellationToken) { #if MSBUILDENABLEVSPROFILING try { string beginTargetBuild = String.Format(CultureInfo.CurrentCulture, "Build Target {0} in Project {1} - Start", this.Name, projectFullPath); DataCollection.CommentMarkProfile(8800, beginTargetBuild); #endif try { VerifyState(_state, TargetEntryState.Execution); ErrorUtilities.VerifyThrow(!_isExecuting, "Target {0} is already executing", _target.Name); _cancellationToken = cancellationToken; _isExecuting = true; // Generate the batching buckets. Note that each bucket will get a lookup based on the baseLookup. This lookup will be in its // own scope, which we will collapse back down into the baseLookup at the bottom of the function. List <ItemBucket> buckets = BatchingEngine.PrepareBatchingBuckets(GetBatchableParametersForTarget(), _baseLookup, _target.Location); WorkUnitResult aggregateResult = new WorkUnitResult(); TargetLoggingContext targetLoggingContext = null; bool targetSuccess = false; int numberOfBuckets = buckets.Count; string projectFullPath = requestEntry.RequestConfiguration.ProjectFullPath; string parentTargetName = null; if (ParentEntry != null && ParentEntry.Target != null) { parentTargetName = ParentEntry.Target.Name; } for (int i = 0; i < numberOfBuckets; i++) { ItemBucket bucket = buckets[i]; // If one of the buckets failed, stop building. if (aggregateResult.ActionCode == WorkUnitActionCode.Stop) { break; } targetLoggingContext = projectLoggingContext.LogTargetBatchStarted(projectFullPath, _target, parentTargetName); WorkUnitResult bucketResult = null; targetSuccess = false; Lookup.Scope entryForInference = null; Lookup.Scope entryForExecution = null; try { // This isn't really dependency analysis. This is up-to-date checking. Based on this we will be able to determine if we should // run tasks in inference or execution mode (or both) or just skip them altogether. ItemDictionary <ProjectItemInstance> changedTargetInputs; ItemDictionary <ProjectItemInstance> upToDateTargetInputs; Lookup lookupForInference; Lookup lookupForExecution; // UNDONE: (Refactor) Refactor TargetUpToDateChecker to take a logging context, not a logging service. TargetUpToDateChecker dependencyAnalyzer = new TargetUpToDateChecker(requestEntry.RequestConfiguration.Project, _target, targetLoggingContext.LoggingService, targetLoggingContext.BuildEventContext); DependencyAnalysisResult dependencyResult = dependencyAnalyzer.PerformDependencyAnalysis(bucket, out changedTargetInputs, out upToDateTargetInputs); switch (dependencyResult) { // UNDONE: Need to enter/leave debugger scope properly for the <Target> element. case DependencyAnalysisResult.FullBuild: case DependencyAnalysisResult.IncrementalBuild: case DependencyAnalysisResult.SkipUpToDate: // Create the lookups used to hold the current set of properties and items lookupForInference = bucket.Lookup; lookupForExecution = bucket.Lookup.Clone(); // Push the lookup stack up one so that we are only modifying items and properties in that scope. entryForInference = lookupForInference.EnterScope("ExecuteTarget() Inference"); entryForExecution = lookupForExecution.EnterScope("ExecuteTarget() Execution"); // if we're doing an incremental build, we need to effectively run the task twice -- once // to infer the outputs for up-to-date input items, and once to actually execute the task; // as a result we need separate sets of item and property collections to track changes if (dependencyResult == DependencyAnalysisResult.IncrementalBuild) { // subset the relevant items to those that are up-to-date foreach (string itemType in upToDateTargetInputs.ItemTypes) { lookupForInference.PopulateWithItems(itemType, upToDateTargetInputs[itemType]); } // subset the relevant items to those that have changed foreach (string itemType in changedTargetInputs.ItemTypes) { lookupForExecution.PopulateWithItems(itemType, changedTargetInputs[itemType]); } } // We either have some work to do or at least we need to infer outputs from inputs. bucketResult = await ProcessBucket(taskBuilder, targetLoggingContext, GetTaskExecutionMode(dependencyResult), lookupForInference, lookupForExecution); // Now aggregate the result with the existing known results. There are four rules, assuming the target was not // skipped due to being up-to-date: // 1. If this bucket failed or was cancelled, the aggregate result is failure. // 2. If this bucket Succeeded and we have not previously failed, the aggregate result is a success. // 3. Otherwise, the bucket was skipped, which has no effect on the aggregate result. // 4. If the bucket's action code says to stop, then we stop, regardless of the success or failure state. if (dependencyResult != DependencyAnalysisResult.SkipUpToDate) { aggregateResult = aggregateResult.AggregateResult(bucketResult); } else { if (aggregateResult.ResultCode == WorkUnitResultCode.Skipped) { aggregateResult = aggregateResult.AggregateResult(new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null)); } } // Pop the lookup scopes, causing them to collapse their values back down into the // bucket's lookup. // NOTE: this order is important because when we infer outputs, we are trying // to produce the same results as would be produced from a full build; as such // if we're doing both the infer and execute steps, we want the outputs from // the execute step to override the outputs of the infer step -- this models // the full build scenario more correctly than if the steps were reversed entryForInference.LeaveScope(); entryForInference = null; entryForExecution.LeaveScope(); entryForExecution = null; targetSuccess = (bucketResult != null) && (bucketResult.ResultCode == WorkUnitResultCode.Success); break; case DependencyAnalysisResult.SkipNoInputs: case DependencyAnalysisResult.SkipNoOutputs: // We have either no inputs or no outputs, so there is nothing to do. targetSuccess = true; break; } } catch (InvalidProjectFileException e) { // Make sure the Invalid Project error gets logged *before* TargetFinished. Otherwise, // the log is confusing. targetLoggingContext.LogInvalidProjectFileError(e); if (null != entryForInference) { entryForInference.LeaveScope(); } if (null != entryForExecution) { entryForExecution.LeaveScope(); } aggregateResult = aggregateResult.AggregateResult(new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null)); } finally { // Don't log the last target finished event until we can process the target outputs as we want to attach them to the // last target batch. if (targetLoggingContext != null && i < numberOfBuckets - 1) { targetLoggingContext.LogTargetBatchFinished(projectFullPath, targetSuccess, null); targetLoggingContext = null; } } } // Produce the final results. List <TaskItem> targetOutputItems = new List <TaskItem>(); try { // If any legacy CallTarget operations took place, integrate them back in to the main lookup now. LeaveLegacyCallTargetScopes(); // Publish the items for each bucket back into the baseLookup. Note that EnterScope() was actually called on each // bucket inside of the ItemBucket constructor, which is why you don't see it anywhere around here. foreach (ItemBucket bucket in buckets) { bucket.LeaveScope(); } string targetReturns = _target.Returns; ElementLocation targetReturnsLocation = _target.ReturnsLocation; // If there are no targets in the project file that use the "Returns" attribute, that means that we // revert to the legacy behavior in the case where Returns is not specified (null, rather // than the empty string, which indicates no returns). Legacy behavior is for all // of the target's Outputs to be returned. // On the other hand, if there is at least one target in the file that uses the Returns attribute, // then all targets in the file are run according to the new behaviour (return nothing unless otherwise // specified by the Returns attribute). if (targetReturns == null) { if (!_target.ParentProjectSupportsReturnsAttribute) { targetReturns = _target.Outputs; targetReturnsLocation = _target.OutputsLocation; } } if (!String.IsNullOrEmpty(targetReturns)) { // Determine if we should keep duplicates. bool keepDupes = ConditionEvaluator.EvaluateCondition ( _target.KeepDuplicateOutputs, ParserOptions.AllowPropertiesAndItemLists, _expander, ExpanderOptions.ExpandPropertiesAndItems, requestEntry.ProjectRootDirectory, _target.KeepDuplicateOutputsLocation, projectLoggingContext.LoggingService, projectLoggingContext.BuildEventContext ); // NOTE: we need to gather the outputs in batches, because the output specification may reference item metadata // Also, we are using the baseLookup, which has possibly had changes made to it since the project started. Because of this, the // set of outputs calculated here may differ from those which would have been calculated at the beginning of the target. It is // assumed the user intended this. List <ItemBucket> batchingBuckets = BatchingEngine.PrepareBatchingBuckets(GetBatchableParametersForTarget(), _baseLookup, _target.Location); if (keepDupes) { foreach (ItemBucket bucket in batchingBuckets) { targetOutputItems.AddRange(bucket.Expander.ExpandIntoTaskItemsLeaveEscaped(targetReturns, ExpanderOptions.ExpandAll, targetReturnsLocation)); } } else { HashSet <TaskItem> addedItems = new HashSet <TaskItem>(); foreach (ItemBucket bucket in batchingBuckets) { IList <TaskItem> itemsToAdd = bucket.Expander.ExpandIntoTaskItemsLeaveEscaped(targetReturns, ExpanderOptions.ExpandAll, targetReturnsLocation); foreach (TaskItem item in itemsToAdd) { if (!addedItems.Contains(item)) { targetOutputItems.Add(item); addedItems.Add(item); } } } } } } finally { if (targetLoggingContext != null) { // log the last target finished since we now have the target outputs. targetLoggingContext.LogTargetBatchFinished(projectFullPath, targetSuccess, targetOutputItems != null && targetOutputItems.Count > 0 ? targetOutputItems : null); } } _targetResult = new TargetResult(targetOutputItems.ToArray(), aggregateResult); if (aggregateResult.ResultCode == WorkUnitResultCode.Failed && aggregateResult.ActionCode == WorkUnitActionCode.Stop) { _state = TargetEntryState.ErrorExecution; } else { _state = TargetEntryState.Completed; } } finally { _isExecuting = false; } #if MSBUILDENABLEVSPROFILING } finally { string endTargetBuild = String.Format(CultureInfo.CurrentCulture, "Build Target {0} in Project {1} - End", this.Name, projectFullPath); DataCollection.CommentMarkProfile(8801, endTargetBuild); } #endif }
public void TestDependencyBuildWithError() { ProjectInstance project = CreateTestProject(); // The DepError target builds Foo (which succeeds), Skip (which skips) and Error (which fails), and Baz2 // Baz2 should not run since it came after Error. // Error tries to build Foo again as an error (which is already built) and Bar, which produces outputs. // DepError builds Baz as an error, which produces outputs TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder); taskBuilder.FailTaskNumber = 3; // Succeed on Foo's one task, and Error's first task, and fail the second. IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, new string[] { "DepError" }), cache[1]); BuildResult result = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, entry.Request.Targets.ToArray(), CreateStandardLookup(project), CancellationToken.None).Result; Assert.True(result.HasResultsForTarget("DepError")); Assert.False(result.HasResultsForTarget("Foo")); Assert.False(result.HasResultsForTarget("Skip")); Assert.False(result.HasResultsForTarget("Error")); Assert.False(result.HasResultsForTarget("Baz2")); Assert.False(result.HasResultsForTarget("Bar")); Assert.False(result.HasResultsForTarget("Baz")); IResultsCache resultsCache = (IResultsCache)_host.GetComponent(BuildComponentType.ResultsCache); Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Foo")); Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Skip")); Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Error")); Assert.False(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Baz2")); Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Bar")); Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Baz")); Assert.Equal(TargetResultCode.Failure, resultsCache.GetResultForRequest(entry.Request)["DepError"].ResultCode); Assert.Equal(TargetResultCode.Success, resultsCache.GetResultForRequest(entry.Request)["Foo"].ResultCode); Assert.Equal(TargetResultCode.Success, resultsCache.GetResultForRequest(entry.Request)["Skip"].ResultCode); Assert.Equal(TargetResultCode.Failure, resultsCache.GetResultForRequest(entry.Request)["Error"].ResultCode); Assert.Equal(TargetResultCode.Success, resultsCache.GetResultForRequest(entry.Request)["Bar"].ResultCode); Assert.Equal(TargetResultCode.Success, resultsCache.GetResultForRequest(entry.Request)["Baz"].ResultCode); }