-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Multithreading migration: Group 7 — 4 attribute-only + 2 Pattern B tasks #53118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
e49fbd6
fe19a6f
9d4c2d2
f298249
53d6b12
b82ca91
aae3335
aae3ef3
d11c422
c93afc4
228c57a
a8f097c
a1ac0c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,165 @@ | ||||||||||||||
| // Licensed to the .NET Foundation under one or more agreements. | ||||||||||||||
| // The .NET Foundation licenses this file to you under the MIT license. | ||||||||||||||
|
|
||||||||||||||
| using FluentAssertions; | ||||||||||||||
| using Microsoft.Build.Framework; | ||||||||||||||
| using System.Collections.Concurrent; | ||||||||||||||
| using System.Reflection; | ||||||||||||||
| using System.Threading; | ||||||||||||||
| using System.Threading.Tasks; | ||||||||||||||
| using Xunit; | ||||||||||||||
|
|
||||||||||||||
| namespace Microsoft.NET.Build.Tasks.UnitTests | ||||||||||||||
| { | ||||||||||||||
| public class GivenAFilterResolvedFilesMultiThreading | ||||||||||||||
| { | ||||||||||||||
| private const string AssetsJson = """ | ||||||||||||||
| { | ||||||||||||||
| "version": 3, | ||||||||||||||
| "targets": { ".NETCoreApp,Version=v8.0": {} }, | ||||||||||||||
| "libraries": {}, | ||||||||||||||
| "packageFolders": {}, | ||||||||||||||
| "projectFileDependencyGroups": { ".NETCoreApp,Version=v8.0": [] }, | ||||||||||||||
| "project": { | ||||||||||||||
| "version": "1.0.0", | ||||||||||||||
| "frameworks": { "net8.0": {} } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| """; | ||||||||||||||
|
|
||||||||||||||
| [Fact] | ||||||||||||||
| public void FilterResolvedFiles_HasMultiThreadableAttribute() | ||||||||||||||
| { | ||||||||||||||
| typeof(FilterResolvedFiles).GetCustomAttribute<MSBuildMultiThreadableTaskAttribute>() | ||||||||||||||
| .Should().NotBeNull("task must be decorated with [MSBuildMultiThreadableTask]"); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| [Fact] | ||||||||||||||
| public void AssetsFilePath_IsResolvedRelativeToProjectDirectory() | ||||||||||||||
| { | ||||||||||||||
| var projectDir = Path.GetFullPath(Path.Combine(Path.GetTempPath(), $"filter-mt-{Guid.NewGuid():N}")); | ||||||||||||||
| Directory.CreateDirectory(projectDir); | ||||||||||||||
| try | ||||||||||||||
| { | ||||||||||||||
| var assetsDir = Path.Combine(projectDir, "obj"); | ||||||||||||||
| Directory.CreateDirectory(assetsDir); | ||||||||||||||
| File.WriteAllText(Path.Combine(assetsDir, "project.assets.json"), AssetsJson); | ||||||||||||||
|
|
||||||||||||||
| var task = new FilterResolvedFiles | ||||||||||||||
| { | ||||||||||||||
| BuildEngine = new MockBuildEngine(), | ||||||||||||||
| AssetsFilePath = "obj\\project.assets.json", | ||||||||||||||
| ResolvedFiles = Array.Empty<ITaskItem>(), | ||||||||||||||
| PackagesToPrune = Array.Empty<ITaskItem>(), | ||||||||||||||
| TargetFramework = ".NETCoreApp,Version=v8.0", | ||||||||||||||
| TaskEnvironment = TaskEnvironmentHelper.CreateForTest(projectDir), | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| var result = task.Execute(); | ||||||||||||||
| result.Should().BeTrue("task should succeed when assets file is found via TaskEnvironment"); | ||||||||||||||
| } | ||||||||||||||
| finally | ||||||||||||||
| { | ||||||||||||||
| Directory.Delete(projectDir, true); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| private static (bool result, MockBuildEngine engine) RunTask( | ||||||||||||||
| string assetsRelPath, string projectDir) | ||||||||||||||
| { | ||||||||||||||
| var engine = new MockBuildEngine(); | ||||||||||||||
| var task = new FilterResolvedFiles | ||||||||||||||
| { | ||||||||||||||
| BuildEngine = engine, | ||||||||||||||
| AssetsFilePath = assetsRelPath, | ||||||||||||||
| ResolvedFiles = Array.Empty<ITaskItem>(), | ||||||||||||||
| PackagesToPrune = Array.Empty<ITaskItem>(), | ||||||||||||||
| TargetFramework = ".NETCoreApp,Version=v8.0", | ||||||||||||||
| TaskEnvironment = TaskEnvironmentHelper.CreateForTest(projectDir), | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| var result = task.Execute(); | ||||||||||||||
| return (result, engine); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| [Fact] | ||||||||||||||
| public void ItProducesSameResultsInMultiProcessAndMultiThreadedEnvironments() | ||||||||||||||
| { | ||||||||||||||
| var projectDir = Path.GetFullPath(Path.Combine(Path.GetTempPath(), $"filter-parity-{Guid.NewGuid():N}")); | ||||||||||||||
| var otherDir = Path.GetFullPath(Path.Combine(Path.GetTempPath(), $"filter-decoy-{Guid.NewGuid():N}")); | ||||||||||||||
| Directory.CreateDirectory(projectDir); | ||||||||||||||
| Directory.CreateDirectory(otherDir); | ||||||||||||||
| var savedCwd = Directory.GetCurrentDirectory(); | ||||||||||||||
| try | ||||||||||||||
| { | ||||||||||||||
| var objDir = Path.Combine(projectDir, "obj"); | ||||||||||||||
| Directory.CreateDirectory(objDir); | ||||||||||||||
| File.WriteAllText(Path.Combine(objDir, "project.assets.json"), AssetsJson); | ||||||||||||||
|
|
||||||||||||||
| var assetsRelPath = Path.Combine("obj", "project.assets.json"); | ||||||||||||||
|
|
||||||||||||||
| // --- Multiprocess mode: CWD == projectDir --- | ||||||||||||||
| Directory.SetCurrentDirectory(projectDir); | ||||||||||||||
| var (mpResult, mpEngine) = RunTask(assetsRelPath, projectDir); | ||||||||||||||
|
|
||||||||||||||
| // --- Multithreaded mode: CWD == otherDir --- | ||||||||||||||
| Directory.SetCurrentDirectory(otherDir); | ||||||||||||||
| var (mtResult, mtEngine) = RunTask(assetsRelPath, projectDir); | ||||||||||||||
|
|
||||||||||||||
| mpResult.Should().Be(mtResult, | ||||||||||||||
| "task should return the same success/failure in both environments"); | ||||||||||||||
| mpEngine.Errors.Count.Should().Be(mtEngine.Errors.Count, | ||||||||||||||
| "error count should be the same in both environments"); | ||||||||||||||
| mpEngine.Warnings.Count.Should().Be(mtEngine.Warnings.Count, | ||||||||||||||
| "warning count should be the same in both environments"); | ||||||||||||||
| } | ||||||||||||||
| finally | ||||||||||||||
| { | ||||||||||||||
| Directory.SetCurrentDirectory(savedCwd); | ||||||||||||||
| Directory.Delete(projectDir, true); | ||||||||||||||
| if (Directory.Exists(otherDir)) Directory.Delete(otherDir, true); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| [Theory] | ||||||||||||||
| [InlineData(4)] | ||||||||||||||
| [InlineData(16)] | ||||||||||||||
| public void FilterResolvedFiles_ConcurrentExecution(int parallelism) | ||||||||||||||
| { | ||||||||||||||
| var projectDir = Path.GetFullPath(Path.Combine(Path.GetTempPath(), $"filter-concurrent-{Guid.NewGuid():N}")); | ||||||||||||||
| Directory.CreateDirectory(projectDir); | ||||||||||||||
| try | ||||||||||||||
| { | ||||||||||||||
| var objDir = Path.Combine(projectDir, "obj"); | ||||||||||||||
| Directory.CreateDirectory(objDir); | ||||||||||||||
| File.WriteAllText(Path.Combine(objDir, "project.assets.json"), AssetsJson); | ||||||||||||||
|
|
||||||||||||||
| var errors = new ConcurrentBag<string>(); | ||||||||||||||
| var barrier = new Barrier(parallelism); | ||||||||||||||
| Parallel.For(0, parallelism, new ParallelOptions { MaxDegreeOfParallelism = parallelism }, i => | ||||||||||||||
| { | ||||||||||||||
| try | ||||||||||||||
| { | ||||||||||||||
| var task = new FilterResolvedFiles | ||||||||||||||
| { | ||||||||||||||
| BuildEngine = new MockBuildEngine(), | ||||||||||||||
| AssetsFilePath = Path.Combine("obj", "project.assets.json"), | ||||||||||||||
| ResolvedFiles = Array.Empty<ITaskItem>(), | ||||||||||||||
| PackagesToPrune = Array.Empty<ITaskItem>(), | ||||||||||||||
| TargetFramework = ".NETCoreApp,Version=v8.0", | ||||||||||||||
| TaskEnvironment = TaskEnvironmentHelper.CreateForTest(projectDir), | ||||||||||||||
| }; | ||||||||||||||
| barrier.SignalAndWait(); | ||||||||||||||
| task.Execute(); | ||||||||||||||
|
||||||||||||||
| task.Execute(); | |
| var result = task.Execute(); | |
| if (!result) | |
| { | |
| errors.Add($"Thread {i}: task.Execute returned false"); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AssetsFilePathis set to a Windows-specific relative string ("obj\project.assets.json"). On non-Windows this becomes a filename containing a backslash, soTaskEnvironment.GetAbsolutePathwill resolve to a path that doesn’t exist and the test will fail. UsePath.Combine("obj", "project.assets.json")(or"obj/project.assets.json") so the test is OS-agnostic.