Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisMode>Recommended</AnalysisMode>
<MSTestAnalysisMode>All</MSTestAnalysisMode>

<!-- See https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0005 -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "3.7.56",
"MSTest.Sdk": "4.1.0"
"MSTest.Sdk": "4.4.0"
},
"test": {
"runner": "Microsoft.Testing.Platform"
Expand Down
60 changes: 30 additions & 30 deletions src/Tests/AnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace ReferenceTrimmer.Tests;

[TestClass]
public sealed class AnalyzerTests
public sealed class AnalyzerTests(TestContext testContext)
{
[TestMethod]
public async Task UsedViaMethodCall()
Expand All @@ -28,7 +28,7 @@ public async Task UnusedReportsDiagnostic()
var diagnostics = await RunAnalyzerAsync(
"class C { }",
dep);
Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
}

Expand Down Expand Up @@ -239,9 +239,9 @@ public async Task MultipleDepsOnlyUnusedReported()
"class C : Used.Foo { }",
[(used.Reference, used.Path, "ProjectReference", "../Used/Used.csproj"),
(unused.Reference, unused.Path, "ProjectReference", "../Unused/Unused.csproj")]);
Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
Assert.IsTrue(diagnostics[0].GetMessage(CultureInfo.InvariantCulture).Contains("Unused"));
Assert.Contains("Unused", diagnostics[0].GetMessage(CultureInfo.InvariantCulture));
}

[TestMethod]
Expand Down Expand Up @@ -298,7 +298,7 @@ public async Task UnusedViaCrefWhenDocModeDisabled()
class C { }",
[(dep.Reference, dep.Path, "ProjectReference", "../Dependency/Dependency.csproj")],
new CSharpParseOptions(documentationMode: DocumentationMode.None));
Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
}

Expand All @@ -314,14 +314,14 @@ public async Task UsedViaTypeForwarding()
var facadeTree = CSharpSyntaxTree.ParseText(@"
using System.Runtime.CompilerServices;
[assembly: TypeForwardedTo(typeof(Dep.Foo))]
");
", cancellationToken: testContext.CancellationToken);
var facadeComp = CSharpCompilation.Create(
"Facade",
[facadeTree],
[CorlibRef, runtime.Reference],
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
string facadePath = Path.Combine(Path.GetTempPath(), $"RT_Test_Facade_{Guid.NewGuid():N}.dll");
var facadeResult = facadeComp.Emit(facadePath);
var facadeResult = facadeComp.Emit(facadePath, cancellationToken: testContext.CancellationToken);
Assert.IsTrue(facadeResult.Success, $"Facade compilation failed:\n{string.Join("\n", facadeResult.Diagnostics)}");
var facadeRef = MetadataReference.CreateFromFile(facadePath);

Expand Down Expand Up @@ -445,9 +445,9 @@ public async Task UnusedPackageReportsRT0003()
var diagnostics = await RunAnalyzerAsync(
"class C { }",
[(dep.Reference, dep.Path, "PackageReference", "Dep.Package")]);
Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0003", diagnostics[0].Id);
Assert.IsTrue(diagnostics[0].GetMessage(CultureInfo.InvariantCulture).Contains("Dep.Package"));
Assert.Contains("Dep.Package", diagnostics[0].GetMessage(CultureInfo.InvariantCulture));
}

[TestMethod]
Expand Down Expand Up @@ -484,7 +484,7 @@ public async Task UnusedBareReferenceReportsRT0001()
var diagnostics = await RunAnalyzerAsync(
"class C { }",
[(dep.Reference, dep.Path, "Reference", dep.Path)]);
Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0001", diagnostics[0].Id);
}

Expand Down Expand Up @@ -544,7 +544,7 @@ public async Task UnusedDelegateNoExternalTypesReportsDiagnostic()
var diagnostics = await RunAnalyzerAsync(
"public class Local {} public delegate Local Produce(Local x);",
dep);
Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
}

Expand Down Expand Up @@ -693,9 +693,9 @@ public async Task UnrelatedReferenceNotMarkedByOverride()
[(baseAsm.Reference, baseAsm.Path, "ProjectReference", "../Base/Base.csproj"),
(derivedAsm.Reference, derivedAsm.Path, "ProjectReference", "../Derived/Derived.csproj"),
(unrelated.Reference, unrelated.Path, "ProjectReference", "../Unrelated/Unrelated.csproj")]);
Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
StringAssert.Contains(diagnostics[0].GetMessage(CultureInfo.InvariantCulture), "Unrelated");
Assert.Contains("Unrelated", diagnostics[0].GetMessage(CultureInfo.InvariantCulture));
}

[TestMethod]
Expand Down Expand Up @@ -850,9 +850,9 @@ public async Task UnrelatedReferenceNotMarkedByInheritance()
[(aAsm.Reference, aAsm.Path, "ProjectReference", "../A/A.csproj"),
(bAsm.Reference, bAsm.Path, "ProjectReference", "../B/B.csproj"),
(unrelated.Reference, unrelated.Path, "ProjectReference", "../Unrelated/Unrelated.csproj")]);
Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
StringAssert.Contains(diagnostics[0].GetMessage(CultureInfo.InvariantCulture), "Unrelated");
Assert.Contains("Unrelated", diagnostics[0].GetMessage(CultureInfo.InvariantCulture));
}

[TestMethod]
Expand Down Expand Up @@ -977,9 +977,9 @@ public Consumer() : base(""1"") { }
[(provider.Reference, provider.Path, "ProjectReference", "../Provider/Provider.csproj"),
(providerDep.Reference, providerDep.Path, "ProjectReference", "../ProviderDependency/ProviderDependency.csproj"),
(unrelated.Reference, unrelated.Path, "ProjectReference", "../Unrelated/Unrelated.csproj")]);
Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
StringAssert.Contains(diagnostics[0].GetMessage(CultureInfo.InvariantCulture), "Unrelated");
Assert.Contains("Unrelated", diagnostics[0].GetMessage(CultureInfo.InvariantCulture));
}

[TestMethod]
Expand Down Expand Up @@ -1245,9 +1245,9 @@ public void Foo(int i) { }
@"public class Consumer { void M(Provider.P p) { p.Foo(""x""); } }",
[(provider.Reference, provider.Path, "ProjectReference", "../Provider/Provider.csproj"),
(dep.Reference, dep.Path, "ProjectReference", "../Dep/Dep.csproj")]);
Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
StringAssert.Contains(diagnostics[0].GetMessage(CultureInfo.InvariantCulture), "Dep");
Assert.Contains("Dep", diagnostics[0].GetMessage(CultureInfo.InvariantCulture));
}

[TestMethod]
Expand All @@ -1272,9 +1272,9 @@ public void Bar(Dep.T t) { }
@"public class Consumer { void M(Provider.P p) { p.Foo(""x""); } }",
[(provider.Reference, provider.Path, "ProjectReference", "../Provider/Provider.csproj"),
(dep.Reference, dep.Path, "ProjectReference", "../Dep/Dep.csproj")]);
Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
StringAssert.Contains(diagnostics[0].GetMessage(CultureInfo.InvariantCulture), "Dep");
Assert.Contains("Dep", diagnostics[0].GetMessage(CultureInfo.InvariantCulture));
}

[TestMethod]
Expand Down Expand Up @@ -1310,7 +1310,7 @@ public async Task UnusedCompilationReferenceStillReportsDiagnostic(bool useSymbo
[(dependency.Reference, dependency.Path, "ProjectReference", "../Dependency/Dependency.csproj", dependency.Identity)],
useSymbolAnalysis: useSymbolAnalysis);

Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
}

Expand All @@ -1334,7 +1334,7 @@ public async Task CompilationReferenceRequiresFullAssemblyIdentityMatch(bool use
[(dependency.Reference, dependency.Path, "ProjectReference", "../Dependency/Dependency.csproj", differentVersionIdentity)],
useSymbolAnalysis: useSymbolAnalysis);

Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
}

Expand Down Expand Up @@ -1416,9 +1416,9 @@ public async Task DuplicateCompilationReferenceIdentityUnionsTransitiveDependenc
useSymbolAnalysis: useSymbolAnalysis,
disableTransitiveProjectReferences: true);

Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
StringAssert.Contains(diagnostics[0].GetMessage(CultureInfo.InvariantCulture), "../Unrelated/Unrelated.csproj");
Assert.Contains("../Unrelated/Unrelated.csproj", diagnostics[0].GetMessage(CultureInfo.InvariantCulture));
}

[TestMethod]
Expand Down Expand Up @@ -1471,7 +1471,7 @@ public async Task UnparseableProjectAssemblyIdentityDoesNotSuppressPeBackedDiagn
[(dependency.Reference, dependency.Path, "ProjectReference", "../Dependency/Dependency.csproj", "LogicalAssembly, Version=invalid")],
useSymbolAnalysis: useSymbolAnalysis);

Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
}

Expand Down Expand Up @@ -1501,9 +1501,9 @@ public async Task CompilationReferenceDoesNotSuppressPeBackedLegacyDiagnostic(
(unused.Reference, unused.Path, "ProjectReference", "../Unused/Unused.csproj", unusedIdentity)],
useSymbolAnalysis: useSymbolAnalysis);

Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
StringAssert.Contains(diagnostics[0].GetMessage(CultureInfo.InvariantCulture), "../Unused/Unused.csproj");
Assert.Contains("../Unused/Unused.csproj", diagnostics[0].GetMessage(CultureInfo.InvariantCulture));
}

[TestMethod]
Expand Down Expand Up @@ -1533,9 +1533,9 @@ public async Task CompilationReferenceTransitiveChainRetainsDependenciesButNotUn
useSymbolAnalysis: useSymbolAnalysis,
disableTransitiveProjectReferences: true);

Assert.AreEqual(1, diagnostics.Length);
Assert.HasCount(1, diagnostics);
Assert.AreEqual("RT0002", diagnostics[0].Id);
StringAssert.Contains(diagnostics[0].GetMessage(CultureInfo.InvariantCulture), "../C/C.csproj");
Assert.Contains("../C/C.csproj", diagnostics[0].GetMessage(CultureInfo.InvariantCulture));
}

// ──────────────────────────────────────────────────────────────────────
Expand Down
8 changes: 4 additions & 4 deletions src/Tests/CollectDeclaredReferencesTaskTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void ExecuteWithAllNullCollectionInputsDoesNotThrow()
bool result = task.Execute();

Assert.IsTrue(result, "Task should succeed when all collection inputs are null. Errors: " + string.Join("; ", engine.Errors));
Assert.AreEqual(0, engine.Errors.Count, "No errors should be logged. Errors: " + string.Join("; ", engine.Errors));
Assert.IsEmpty(engine.Errors, "No errors should be logged. Errors: " + string.Join("; ", engine.Errors));
}
finally
{
Expand Down Expand Up @@ -65,7 +65,7 @@ public void ExecuteWithNullResolvedReferencesAndUnresolvableReferenceDoesNotThro
bool result = task.Execute();

Assert.IsTrue(result, "Task should succeed even when ResolvedReferences is null. Errors: " + string.Join("; ", engine.Errors));
Assert.AreEqual(0, engine.Errors.Count, "No errors should be logged. Errors: " + string.Join("; ", engine.Errors));
Assert.IsEmpty(engine.Errors, "No errors should be logged. Errors: " + string.Join("; ", engine.Errors));
}
finally
{
Expand Down Expand Up @@ -99,7 +99,7 @@ public void ExecuteWithPackageReferencesAndNullProjectAssetsFileDoesNotThrow()
bool result = task.Execute();

Assert.IsTrue(result, "Task should succeed when ProjectAssetsFile is null. Errors: " + string.Join("; ", engine.Errors));
Assert.AreEqual(0, engine.Errors.Count, "No errors should be logged. Errors: " + string.Join("; ", engine.Errors));
Assert.IsEmpty(engine.Errors, "No errors should be logged. Errors: " + string.Join("; ", engine.Errors));
}
finally
{
Expand Down Expand Up @@ -171,7 +171,7 @@ public void ProjectAssemblyIdentityParticipatesInIncrementalHash()
.SingleOrDefault(include => include?.Contains("_ReferenceTrimmerProjectReferences", StringComparison.Ordinal) == true);

Assert.IsNotNull(projectHashInput);
StringAssert.Contains(projectHashInput, "%(FusionName)");
Assert.Contains("%(FusionName)", projectHashInput);
}

private sealed class MockBuildEngine : IBuildEngine
Expand Down
20 changes: 10 additions & 10 deletions src/Tests/E2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace ReferenceTrimmer.Tests;

[TestClass]
public sealed class E2ETests
public sealed class E2ETests(TestContext testContext)
{
private readonly record struct Warning(string Message, string Project, IEnumerable<string>? AltMessages = null);

Expand All @@ -19,15 +19,15 @@ public sealed class E2ETests
@".+: (warning|error) (?<message>.+) \[(?<project>.+)\]",
RegexOptions.Compiled | RegexOptions.ExplicitCapture);

public TestContext? TestContext { get; set; }

[ClassInitialize]
public static void ClassInitialize(TestContext _)
{
// Delete the package cache to avoid reusing old content
if (Directory.Exists("Packages"))
{
#pragma warning disable MSTEST0077 // Avoid hardcoded or shared filesystem paths in a parallelized test
Directory.Delete("Packages", recursive: true);
#pragma warning restore MSTEST0077 // Avoid hardcoded or shared filesystem paths in a parallelized test
}
}

Expand Down Expand Up @@ -821,14 +821,14 @@ private async Task RunMSBuildAsync(
bool useSymbolAnalysis = false,
IReadOnlyDictionary<string, string>? globalProperties = null)
{
var testDataSourcePath = Path.GetFullPath(Path.Combine("TestData", TestContext?.TestName ?? string.Empty));
var testDataSourcePath = Path.GetFullPath(Path.Combine("TestData", testContext.TestName ?? string.Empty));

string logDirBase = Path.Combine(testDataSourcePath, "Logs");
string binlogFilePath = Path.Combine(logDirBase, Path.GetFileName(projectFile) + ".binlog");
string warningsFilePath = Path.Combine(logDirBase, Path.GetFileName(projectFile) + ".warnings.log");
string errorsFilePath = Path.Combine(logDirBase, Path.GetFileName(projectFile) + ".errors.log");

TestContext?.WriteLine($"Log directory: {logDirBase}");
testContext.WriteLine($"Log directory: {logDirBase}");

string unusedLibraryLogPath = Path.Combine(testDataSourcePath, ForwardingLogger.HelpKeyword + ".json.log");
if (File.Exists(unusedLibraryLogPath))
Expand Down Expand Up @@ -868,19 +868,19 @@ private async Task RunMSBuildAsync(
}.WithVsDevEnvironment());
Assert.IsNotNull(process);

string stdOut = await process.StandardOutput.ReadToEndAsync();
string stdErr = await process.StandardError.ReadToEndAsync();
string stdOut = await process.StandardOutput.ReadToEndAsync(testContext!.CancellationToken);
string stdErr = await process.StandardError.ReadToEndAsync(testContext.CancellationToken);

await process.WaitForExitAsync();
await process.WaitForExitAsync(testContext.CancellationToken);

Assert.AreEqual(0, process.ExitCode, $"Build of {projectFile} was not successful.{Environment.NewLine}StandardError: {stdErr}{Environment.NewLine}StandardOutput: {stdOut}");
Assert.AreEqual(File.Exists(unusedLibraryLogPath), expectUnusedMsvcLibrariesLog);

string errors = await File.ReadAllTextAsync(errorsFilePath);
string errors = await File.ReadAllTextAsync(errorsFilePath, testContext.CancellationToken);
Assert.AreEqual(0, errors.Length, $"Build of {projectFile} was not successful.{Environment.NewLine}Error log: {errors}");

List<Warning> actualWarnings = new();
foreach (string line in await File.ReadAllLinesAsync(warningsFilePath))
foreach (string line in await File.ReadAllLinesAsync(warningsFilePath, testContext.CancellationToken))
{
Match match = WarningErrorRegex.Match(line);
if (match.Success)
Expand Down
6 changes: 3 additions & 3 deletions src/Tests/MsvcLoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace ReferenceTrimmer.Tests;

[TestClass]
public sealed class MsvcLoggerTests
public sealed class MsvcLoggerTests(TestContext testContext)
{
private sealed class MockEventSource : IEventSource
{
Expand Down Expand Up @@ -372,7 +372,7 @@ public async Task CentralLogger_ProcessesUnusedLibEventsFromPrimaryNode()
centralLogger.Shutdown();

Assert.IsTrue(File.Exists(jsonPath));
string json = await File.ReadAllTextAsync(jsonPath);
string json = await File.ReadAllTextAsync(jsonPath, testContext.CancellationToken);
Assert.Contains("user32.lib", json);
}

Expand All @@ -394,7 +394,7 @@ public async Task CentralLogger_JsonOnUnusedLibEvents()
centralLogger.Shutdown();
Assert.IsTrue(File.Exists(jsonPath));
Assert.AreEqual($"[{Environment.NewLine}{{ \"aProp\": \"aValue\" }},{Environment.NewLine}{{ \"aProp2\": \"aValue2\" }}{Environment.NewLine}]{Environment.NewLine}",
await File.ReadAllTextAsync(jsonPath));
await File.ReadAllTextAsync(jsonPath, testContext.CancellationToken));
}

private static void SendLinkTaskStarted(
Expand Down
Loading