public async Task ConcurrentShortAndLongOperations() { // If a builder is running a short operation and a long operation is started, // that long operation will wait for the sort operation to finish // and will use the same builder, instead of starting a new one. await RemoteBuildEngineManager.RecycleAllBuilders(); Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); FilePath solFile = Util.GetSampleProject("builder-manager-tests", "builder-manager-tests.sln"); using (var sol = (Solution)await Services.ProjectService.ReadWorkspaceItem(Util.GetMonitor(), solFile)) { var project1 = (Project)sol.Items.FirstOrDefault(p => p.Name == "SyncBuildProject"); var project2 = (Project)sol.Items.FirstOrDefault(p => p.Name == "App"); InitBuildSyncEvent(project1); // Start the first target. The FileSync target will pause until signaled to continue. // Select the ShortOperations build queue. var context = new TargetEvaluationContext { BuilderQueue = BuilderQueue.ShortOperations }; var build1 = project1.RunTarget(Util.GetMonitor(), "FileSync", sol.Configurations [0].Selector, context); // Wait for the build to reach the sync task await WaitForBuildSyncEvent(project1); Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); // The build is now in progess. Run a new target. context = new TargetEvaluationContext { BuilderQueue = BuilderQueue.LongOperations }; var build2 = project2.RunTarget(Util.GetMonitor(), "QuickTarget", sol.Configurations [0].Selector, context); // Wait a bit. This should be enough to ensure the build has started. await Task.Delay(1000); // The RunTarget request should be queued, not new builder should be spawned Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); // Continue building the first project SignalBuildToContinue(project1); // The first build should end now if (await Task.WhenAny(build1, Task.Delay(timeoutMs)) != build1) { Assert.Fail("Build did not end in time"); } // And now the second build should end if (await Task.WhenAny(build2, Task.Delay(timeoutMs)) != build2) { Assert.Fail("Build did not end in time"); } Assert.NotNull(build1.Result); Assert.NotNull(build2.Result); Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); } }
public async Task RecycleBuildersIsGraceful() { // RecycleBuilders can be called in the middle of a build and that should not interrupt // the build. Builders will be shut down when the build is finished. var currentDelay = RemoteBuildEngineManager.EngineDisposalDelay; try { RemoteBuildEngineManager.EngineDisposalDelay = 400; await RemoteBuildEngineManager.RecycleAllBuilders(); Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); FilePath solFile = Util.GetSampleProject("builder-manager-tests", "builder-manager-tests.sln"); using (var sol = (Solution)await Services.ProjectService.ReadWorkspaceItem(Util.GetMonitor(), solFile)) { var project1 = sol.Items.FirstOrDefault(p => p.Name == "SyncBuildProject"); var project2 = sol.Items.FirstOrDefault(p => p.Name == "App"); InitBuildSyncEvent(project1); // Start the build var build1 = project1.Build(Util.GetMonitor(), sol.Configurations [0].Selector); // Wait for the build to reach the sync task await WaitForBuildSyncEvent(project1); Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(0, await RemoteBuildEngineManager.CountActiveBuildersForProject(project2.FileName)); // The build is now in progess. Start a new build var build2 = project2.Build(Util.GetMonitor(), sol.Configurations [0].Selector); if (await Task.WhenAny(build2, Task.Delay(5000)) != build2) { Assert.Fail("Build did not start"); } // There should be one builder for each build Assert.AreEqual(2, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(2, RemoteBuildEngineManager.EnginesCount); // Ask for all active builders to be shut down await RemoteBuildEngineManager.RecycleAllBuilders(); // There is a build in progress, so there must still be one engine running Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(1, RemoteBuildEngineManager.EnginesCount); SignalBuildToContinue(project1); if (await Task.WhenAny(build1, Task.Delay(5000)) != build1) { Assert.Fail("Build did not end in time"); } Assert.AreEqual(0, build1.Result.ErrorCount); Assert.AreEqual(0, build2.Result.ErrorCount); // The builder that was running the build and was shutdown should be immediately stopped after build finishes Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(0, RemoteBuildEngineManager.EnginesCount); } } finally { RemoteBuildEngineManager.EngineDisposalDelay = currentDelay; } }
public async Task ExtraBuilderAutoShutdown() { // When an extra builder is created, it should be shut down when it is // not used anymore, after a delay var currentDelay = RemoteBuildEngineManager.EngineDisposalDelay; try { RemoteBuildEngineManager.EngineDisposalDelay = 400; await RemoteBuildEngineManager.RecycleAllBuilders(); Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); FilePath solFile = Util.GetSampleProject("builder-manager-tests", "builder-manager-tests.sln"); using (var sol = (Solution)await Services.ProjectService.ReadWorkspaceItem(Util.GetMonitor(), solFile)) { var project1 = sol.Items.FirstOrDefault(p => p.Name == "SyncBuildProject"); var project2 = sol.Items.FirstOrDefault(p => p.Name == "App"); InitBuildSyncEvent(project1); // Start the build var build1 = project1.Build(Util.GetMonitor(), sol.Configurations [0].Selector); // Wait for the build to reach the sync task await WaitForBuildSyncEvent(project1); Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(0, await RemoteBuildEngineManager.CountActiveBuildersForProject(project2.FileName)); // The build is now in progess. Start a new build var build2 = project2.Build(Util.GetMonitor(), sol.Configurations [0].Selector); if (await Task.WhenAny(build2, Task.Delay(5000)) != build2) { Assert.Fail("Build did not start"); } Assert.AreEqual(2, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(1, await RemoteBuildEngineManager.CountActiveBuildersForProject(project2.FileName)); // Build engine disposal delay is set to 400ms, so it should be gone after waiting the following wait. await Task.Delay(500); // The second builder should now be gone Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(1, RemoteBuildEngineManager.EnginesCount); Assert.AreEqual(0, await RemoteBuildEngineManager.CountActiveBuildersForProject(project2.FileName)); SignalBuildToContinue(project1); if (await Task.WhenAny(build1, Task.Delay(5000)) != build1) { Assert.Fail("Build did not end in time"); } } } finally { RemoteBuildEngineManager.EngineDisposalDelay = currentDelay; } }
public async Task AtLeastOneBuilderPersolution() { // There should always be at least one builder running per solution, // until the solution is explicitly disposed var currentDelay = RemoteBuildEngineManager.EngineDisposalDelay; try { RemoteBuildEngineManager.EngineDisposalDelay = 400; await RemoteBuildEngineManager.RecycleAllBuilders(); Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); FilePath solFile = Util.GetSampleProject("builder-manager-tests", "builder-manager-tests.sln"); using (var sol = (Solution)await Services.ProjectService.ReadWorkspaceItem(Util.GetMonitor(), solFile)) { var project1 = sol.Items.FirstOrDefault(p => p.Name == "SyncBuildProject"); var project2 = sol.Items.FirstOrDefault(p => p.Name == "App"); InitBuildSyncEvent(project1); // Start the build var build1 = project1.Build(Util.GetMonitor(), sol.Configurations [0].Selector); // Wait for the build to reach the sync task await WaitForBuildSyncEvent(project1); Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(0, await RemoteBuildEngineManager.CountActiveBuildersForProject(project2.FileName)); // The build is now in progess. Start a new build var build2 = project2.Build(Util.GetMonitor(), sol.Configurations [0].Selector); if (await Task.WhenAny(build2, Task.Delay(5000)) != build2) { Assert.Fail("Build did not start"); } Assert.AreEqual(2, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(2, RemoteBuildEngineManager.EnginesCount); Assert.AreEqual(1, await RemoteBuildEngineManager.CountActiveBuildersForProject(project1.FileName)); Assert.AreEqual(1, await RemoteBuildEngineManager.CountActiveBuildersForProject(project2.FileName)); SignalBuildToContinue(project1); if (await Task.WhenAny(build1, Task.Delay(5000)) != build1) { Assert.Fail("Build did not end in time"); } // Build engine disposal delay is set to 400ms, so unused // builders should go away after a 500ms wait. await Task.Delay(500); // There should be at least one builder left Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(1, RemoteBuildEngineManager.EnginesCount); } } finally { RemoteBuildEngineManager.EngineDisposalDelay = currentDelay; } // All builders should be gone now Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(0, RemoteBuildEngineManager.EnginesCount); }
public async Task ReloadProject_ProjectDisposedWhilstTargetRunning_AnotherTargetRun() { // When a long operation is running and a new one is requested, a new builder is created await RemoteBuildEngineManager.RecycleAllBuilders(); Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); FilePath solFile = Util.GetSampleProject("builder-manager-tests", "builder-manager-tests.sln"); using (var sol = (Solution)await Services.ProjectService.ReadWorkspaceItem(Util.GetMonitor(), solFile)) { var project1 = (Project)sol.Items.FirstOrDefault(p => p.Name == "SyncBuildProject"); InitBuildSyncEvent(project1); // Start the build. Use RunTargetInternal to avoid the BindTask which will cancel the build on project dispose. var build1 = project1.RunTargetInternal(Util.GetMonitor(), "Build", sol.Configurations [0].Selector); // Wait for the build to reach the sync task await WaitForBuildSyncEvent(project1); // The build is now in progess. Simulate reloading the project. using (var project2 = (Project)await sol.RootFolder.ReloadItem(Util.GetMonitor(), project1)) { var builderTask = project2.GetProjectBuilder(CancellationToken.None, null, allowBusy: true); // Allow second build to finish and dispose its project builder. Have to do this here otherwise // GetProjectBuilder will hang waiting for a connection response back after it creates a new // project builder. SignalBuildToContinue(project1); if (await Task.WhenAny(build1, Task.Delay(timeoutMs)) != build1) { Assert.Fail("Build did not end in time"); } Assert.AreEqual(0, build1.Result.BuildResult.ErrorCount); using (var builder = await builderTask) { // Sanity check. We should only have one project builder and one build engine. Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(1, RemoteBuildEngineManager.EnginesCount); Assert.AreEqual(1, await RemoteBuildEngineManager.CountActiveBuildersForProject(project2.FileName)); var configs = project2.GetConfigurations(sol.Configurations [0].Selector, false); var build2 = await Task.Run(() => { // Previously this would throw a NullReferenceException since the builder has been disposed // and the engine is null. return(builder.Run( configs, new StringWriter(), new MSBuildLogger(), MSBuildVerbosity.Quiet, new [] { "ResolveAssemblyReferences" }, new string [0], new string [0], new System.Collections.Generic.Dictionary <string, string> (), CancellationToken.None)); }); Assert.AreEqual(0, build2.Errors.Length); } } } }