public async Task DisposingProjectDisposesProjectBuilder() { // When a project is disposed, all project builders should be shut down await RemoteBuildEngineManager.RecycleAllBuilders(); Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); FilePath solFile = Util.GetSampleProject("builder-manager-tests", "builder-manager-tests.sln"); FilePath projectFile; using (var sol = (Solution)await Services.ProjectService.ReadWorkspaceItem(Util.GetMonitor(), solFile)) { var project = (DotNetProject)sol.Items.FirstOrDefault(p => p.Name == "App"); projectFile = project.FileName; await project.GetReferencedAssemblies(sol.Configurations [0].Selector); Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(1, await RemoteBuildEngineManager.CountActiveBuildersForProject(project.FileName)); sol.RootFolder.Items.Remove(project); project.Dispose(); Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(1, RemoteBuildEngineManager.EnginesCount); Assert.AreEqual(0, await RemoteBuildEngineManager.CountActiveBuildersForProject(project.FileName)); } Assert.AreEqual(0, await RemoteBuildEngineManager.CountActiveBuildersForProject(projectFile)); Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); Assert.AreEqual(0, RemoteBuildEngineManager.EnginesCount); }
public async Task UseCorrentProjectDependencyWhenNotBuildingReferencesOnCleanBuilder() { // Same as above, but now project dependencies are not included in the build. // The project should still pick the right dependency FilePath solFile = Util.GetSampleProject("sln-config-mapping", "sln-config-mapping.sln"); var sol = (Solution)await Services.ProjectService.ReadWorkspaceItem(Util.GetMonitor(), solFile); var lib = sol.Items.First(i => i.Name == "lib"); var lib2 = sol.Items.First(i => i.Name == "lib2"); var app = sol.Items.First(i => i.Name == "app"); // Build the library in Debug and Release modes, to make sure all assemblies are generated // Also, the last build is Release, so that the project in memory is configured for reelase await lib.Build(Util.GetMonitor(false), sol.Configurations ["Debug|x86"].Selector, true); await lib.Build(Util.GetMonitor(false), sol.Configurations ["Release|x86"].Selector, true); await RemoteBuildEngineManager.RecycleAllBuilders(); Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); // Build the app in Debug mode. It should still pick the Extra dependency await app.Build(Util.GetMonitor(false), sol.Configurations ["Debug|x86"].Selector, false); Assert.IsTrue(File.Exists(app.ItemDirectory.Combine("bin", "Debug", "libextra.dll"))); }
public async Task ParallelBuilds() { // Check that the project system can start two builds in parallel. await RemoteBuildEngineManager.RecycleAllBuilders(); Assert.AreEqual(0, RemoteBuildEngineManager.ActiveEnginesCount); var currentSetting = Runtime.Preferences.ParallelBuild.Value; try { Runtime.Preferences.ParallelBuild.Set(true); FilePath solFile = Util.GetSampleProject("builder-manager-tests", "builder-manager-tests.sln"); using (var sol = (Solution)await Services.ProjectService.ReadWorkspaceItem(Util.GetMonitor(), solFile)) { // DepMain depends on both Dep1 and Dep2. var demMain = sol.Items.FirstOrDefault(p => p.Name == "DepMain"); var dep1 = sol.Items.FirstOrDefault(p => p.Name == "Dep1"); var dep2 = sol.Items.FirstOrDefault(p => p.Name == "Dep2"); InitBuildSyncEvent(dep1); InitBuildSyncEvent(dep2); // Start the build var build1 = demMain.Build(Util.GetMonitor(), sol.Configurations [0].Selector, true); // Wait for sync signal from projects Dep1 and Dep2, which will mean that // both projects started building in parallel var syncAll = Task.WhenAll(WaitForBuildSyncEvent(dep1), WaitForBuildSyncEvent(dep2)); //NOTE: this has a longer timeout because otherwise it sometimes times out //maybe something to do with the fact it compiles tasks if (await Task.WhenAny(syncAll, Task.Delay(timeoutMs)) != syncAll) { Assert.Fail("Not all builds were started"); } // Finish the build SignalBuildToContinue(dep1); SignalBuildToContinue(dep2); if (await Task.WhenAny(build1, Task.Delay(timeoutMs)) != build1) { Assert.Fail("Build did not end in time"); } Assert.AreEqual(0, build1.Result.ErrorCount); } } finally { Runtime.Preferences.ParallelBuild.Set(currentSetting); } }
public async Task RecoverFromBuilderCrash() { await RemoteBuildEngineManager.RecycleAllBuilders(); string projFile = Util.GetSampleProject("builder-manager-tests", "crasher", "ConsoleProject.csproj"); using (var p = (Project)await Services.ProjectService.ReadSolutionItem(Util.GetMonitor(), projFile)) { var result = await p.Build(Util.GetMonitor(), ConfigurationSelector.Default); Assert.AreEqual(1, result.ErrorCount, "#1"); } }
public async Task ConcurrentLongAndShortOperations() { // Tests that when a short operation is started while a long operation is in progress, // a new builder is created to execute the short operation 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 = (Project)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); // The build is now in progess. Start a short operation. var context = new TargetEvaluationContext { BuilderQueue = BuilderQueue.ShortOperations }; var build2 = project2.RunTarget(Util.GetMonitor(), "QuickTarget", sol.Configurations [0].Selector, context); if (await Task.WhenAny(build2, Task.Delay(5000)) != build2) { Assert.Fail("Build did not start"); } 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.NotNull(build2.Result); } }
public async Task ConcurrentLongOperations() { // 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 = 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); // 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"); } // The second build should finish before the first one 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); } }
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 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 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 ConcurrentShortAndBuildOperations() { // If a builder is running a short operation and a build is started, // the build operation will wait for the sort operation to finish // and will use the same builder, instead of starting a new one. // Also, the build session should not start until the short operation // is finished. 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. var build2 = project2.Build(Util.GetMonitor(), sol.Configurations [0].Selector); // Wait a bit. This should be enough to ensure the build has started. await Task.Delay(1000); // The RunTarget request should be queued, no 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(5000)) != build1) { Assert.Fail("Build did not end in time"); } // And now the second build should end if (await Task.WhenAny(build2, Task.Delay(5000)) != build2) { Assert.Fail("Build did not end in time"); } Assert.NotNull(build1.Result); Assert.AreEqual(0, build2.Result.ErrorCount); Assert.AreEqual(1, RemoteBuildEngineManager.ActiveEnginesCount); } }
public async Task ShortOperationsInSingleBuilder() { // Tests that targets using BuilderQueue.ShortOperations share the same builder // and don't spawn a new builder when one of them is being executed and another // one starts executing. 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.ShortOperations }; 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(5000)) != build1) { Assert.Fail("Build did not end in time"); } // And now the second build should end if (await Task.WhenAny(build2, Task.Delay(5000)) != 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 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 RunTarget to avoid the BindTask which will cancel the build on project dispose. var build1 = project1.RunTarget(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); } } } }
public async Task <SolutionFolderItem> ReloadItem(ProgressMonitor monitor, SolutionFolderItem sitem) { if (Items.IndexOf(sitem) == -1) { throw new InvalidOperationException("Solution item '" + sitem.Name + "' does not belong to folder '" + Name + "'"); } if (sitem is SolutionItem item) { // Load the new item SolutionItem newItem; try { if (ParentSolution.IsSolutionItemEnabled(item.FileName)) { using (var ctx = new SolutionLoadContext(ParentSolution)) newItem = await Services.ProjectService.ReadSolutionItem(monitor, item.FileName, null, ctx : ctx, itemGuid : item.ItemId); } else { UnknownSolutionItem e = new UnloadedSolutionItem() { FileName = item.FileName }; e.ItemId = item.ItemId; e.TypeGuid = item.TypeGuid; newItem = e; } } catch (Exception ex) { newItem = new UnknownSolutionItem { LoadError = ex.Message, FileName = item.FileName }; } if (!Items.Contains(item)) { // The old item is gone, which probably means it has already been reloaded (BXC20615), or maybe removed. // In this case, there isn't anything else we can do newItem.Dispose(); // Find the replacement if it exists return(Items.OfType <SolutionItem> ().FirstOrDefault(it => it.FileName == item.FileName)); } // Replace in the file list Items.Replace(item, newItem); item.ParentFolder = null; DisconnectChildEntryEvents(item); ConnectChildEntryEvents(newItem); // Shutdown project builder before the ItemAdded event is fired. This should prevent the old out of // date project builder being used by the TypeSystemService when getting reference information. The // TypeSystemService loads the project when the ItemAdded event is fired before the item is disposed. // Disposing the project will also shutdown the project builder but this happens too late and can // result in the old project builder being used which does not have the latest project xml. if (item is Project) { await RemoteBuildEngineManager.UnloadProject(item.FileName); } NotifyModified("Items"); OnItemRemoved(new SolutionItemChangeEventArgs(item, ParentSolution, true) { ReplacedItem = item }, true); OnItemAdded(new SolutionItemChangeEventArgs(newItem, ParentSolution, true) { ReplacedItem = item }, true); item.Dispose(); return(newItem); } return(sitem); }