diff --git a/Directory.Packages.props b/Directory.Packages.props index a17e2fe..bc94040 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ - + @@ -43,12 +43,12 @@ - - + + - - + + diff --git a/global.json b/global.json index bd1ddef..e3f13c3 100644 --- a/global.json +++ b/global.json @@ -4,7 +4,7 @@ "rollForward": "latestFeature" }, "msbuild-sdks": { - "MSTest.Sdk": "4.1.0" + "MSTest.Sdk": "4.4.0" }, "test": { "runner": "Microsoft.Testing.Platform" diff --git a/src/AzurePipelines.Tests/SelectorPublicationTests.cs b/src/AzurePipelines.Tests/SelectorPublicationTests.cs index cfafb50..fa9bace 100644 --- a/src/AzurePipelines.Tests/SelectorPublicationTests.cs +++ b/src/AzurePipelines.Tests/SelectorPublicationTests.cs @@ -112,15 +112,14 @@ async Task TryPublishAsync( Assert.IsTrue(await firstPublication); Assert.IsTrue(await secondPublication); - CollectionAssert.AreEquivalent( - new[] { existingSelector, firstSelector, secondSelector }, - finalWinner!.Selectors.ToArray()); + Assert.AreSequenceEqual( + new[] { existingSelector, firstSelector, secondSelector }, finalWinner!.Selectors.ToArray(), SequenceOrder.InAnyOrder); string initialWriteKey = PipelineCachingCacheClient.ComputeSelectorsWriteKey(Universe, weakFingerprint, initial.Id); - CollectionAssert.AreEqual(new[] { initialWriteKey }, conflictQueries.ToArray()); + Assert.AreSequenceEqual(new[] { initialWriteKey }, conflictQueries.ToArray()); Assert.AreEqual( PipelineCachingCacheClient.ComputeSelectorsWriteKey(Universe, weakFingerprint, firstWinner!.Id), finalWriteKey); - StringAssert.StartsWith(latestKey, "selector6|", StringComparison.Ordinal); + Assert.StartsWith("selector6|", latestKey, StringComparison.Ordinal); } [TestMethod] @@ -136,9 +135,9 @@ public void OutputKeyIncludesSelectorOutput() string secondKey = PipelineCachingCacheClient.ComputeOutputKey("universe", second, forWrite: false, writeId: 0); Assert.AreNotEqual(firstKey, secondKey); - StringAssert.StartsWith(firstKey, "outputs6|", StringComparison.Ordinal); - StringAssert.Contains(firstKey, "|01|", StringComparison.Ordinal); - StringAssert.Contains(secondKey, "|02|", StringComparison.Ordinal); + Assert.StartsWith("outputs6|", firstKey, StringComparison.Ordinal); + Assert.Contains("|01|", firstKey, StringComparison.Ordinal); + Assert.Contains("|02|", secondKey, StringComparison.Ordinal); } private static Selector CreateSelector(byte output) diff --git a/src/Common.Tests/CasCacheClientTests.cs b/src/Common.Tests/CasCacheClientTests.cs index cf2cd0d..80f25d3 100644 --- a/src/Common.Tests/CasCacheClientTests.cs +++ b/src/Common.Tests/CasCacheClientTests.cs @@ -57,7 +57,7 @@ public async Task AddNodeDoesNotUploadContentWhenRemoteCacheIsReadOnly() Assert.AreEqual(0, remoteSession.PutFileCallCount, "Content must not be uploaded when the remote cache is read-only."); // The node still needs to be written to the local cache. - Assert.IsTrue(localSession.PutStreamCallCount > 0, "The node metadata must still be written to the local cache."); + Assert.IsGreaterThan(0, localSession.PutStreamCallCount, "The node metadata must still be written to the local cache."); Assert.AreEqual(1, localSession.AddOrGetContentHashListCallCount, "The content hash list must still be added to the local cache."); } @@ -71,9 +71,9 @@ public async Task AddNodeUploadsContentWhenRemoteCacheIsWritable() await cacheClient.AddNodeInternalAsync(CreateNodeContext(), pathSet: null, CreateNodeBuildResult(), CancellationToken.None); } - Assert.IsTrue(remoteSession.PinCallCount > 0, "The remote session should be pinned to determine what to upload."); - Assert.IsTrue(remoteSession.PutStreamCallCount > 0, "Content should be uploaded when the remote cache is writable."); - Assert.IsTrue(localSession.PutStreamCallCount > 0, "The node metadata should be written to the local cache."); + Assert.IsGreaterThan(0, remoteSession.PinCallCount, "The remote session should be pinned to determine what to upload."); + Assert.IsGreaterThan(0, remoteSession.PutStreamCallCount, "Content should be uploaded when the remote cache is writable."); + Assert.IsGreaterThan(0, localSession.PutStreamCallCount, "The node metadata should be written to the local cache."); } private static (CasCacheClient CacheClient, RecordingCacheSession LocalSession, RecordingCacheSession RemoteSession) CreateCacheClient(bool remoteCacheIsReadOnly) diff --git a/src/Common.Tests/FingerprintFactoryTests.cs b/src/Common.Tests/FingerprintFactoryTests.cs index 7c008c6..1d45bcb 100644 --- a/src/Common.Tests/FingerprintFactoryTests.cs +++ b/src/Common.Tests/FingerprintFactoryTests.cs @@ -46,7 +46,7 @@ public void WeakFingerprintFlagSegregation() FingerprintEntry onFlagEntry = entriesOn.Single(e => e.Description.Contains(nameof(PluginSettings.EnableProbeAndEnumerationFingerprinting), StringComparison.Ordinal)); Assert.AreNotEqual(offFlagEntry.Description, onFlagEntry.Description, "Flag descriptions should differ."); - CollectionAssert.AreNotEqual(offFlagEntry.Hash, onFlagEntry.Hash, "Flag entry hashes should differ."); + Assert.AreNotSequenceEqual(offFlagEntry.Hash, onFlagEntry.Hash, "Flag entry hashes should differ."); // And no other entry should differ between the two factories — the flag is the only setting toggled. List commonOff = entriesOff @@ -55,7 +55,7 @@ public void WeakFingerprintFlagSegregation() List commonOn = entriesOn .Where(e => !e.Description.Contains(nameof(PluginSettings.EnableProbeAndEnumerationFingerprinting), StringComparison.Ordinal)) .Select(e => e.Description).ToList(); - CollectionAssert.AreEqual(commonOff, commonOn); + Assert.AreSequenceEqual(commonOff, commonOn); } /// @@ -74,8 +74,8 @@ public void SentinelsAreDeterministicForHashType(HashType hashType) FingerprintFactory a = CreateFactory(hasher); FingerprintFactory b = CreateFactory(hasher); - CollectionAssert.AreEqual(a.AbsentFileSentinel, b.AbsentFileSentinel, $"AbsentFileSentinel not deterministic for {hashType}."); - CollectionAssert.AreEqual(a.ZeroHash, b.ZeroHash, $"ZeroHash not deterministic for {hashType}."); + Assert.AreSequenceEqual(a.AbsentFileSentinel, b.AbsentFileSentinel, $"AbsentFileSentinel not deterministic for {hashType}."); + Assert.AreSequenceEqual(a.ZeroHash, b.ZeroHash, $"ZeroHash not deterministic for {hashType}."); } /// @@ -91,7 +91,7 @@ public void SentinelsAreDistinct(HashType hashType) using IContentHasher hasher = HashInfoLookup.Find(hashType).CreateContentHasher(); FingerprintFactory factory = CreateFactory(hasher); - CollectionAssert.AreNotEqual(factory.AbsentFileSentinel, factory.ZeroHash, "AbsentFileSentinel and ZeroHash must not collide."); + Assert.AreNotSequenceEqual(factory.AbsentFileSentinel, factory.ZeroHash, "AbsentFileSentinel and ZeroHash must not collide."); } /// @@ -108,8 +108,8 @@ public void SentinelsMatchHasherByteLength(HashType hashType) int expectedLength = hasher.Info.ByteLength; FingerprintFactory factory = CreateFactory(hasher); - Assert.AreEqual(expectedLength, factory.AbsentFileSentinel.Length, $"AbsentFileSentinel size mismatch for {hashType}."); - Assert.AreEqual(expectedLength, factory.ZeroHash.Length, $"ZeroHash size mismatch for {hashType}."); + Assert.HasCount(expectedLength, factory.AbsentFileSentinel, $"AbsentFileSentinel size mismatch for {hashType}."); + Assert.HasCount(expectedLength, factory.ZeroHash, $"ZeroHash size mismatch for {hashType}."); } /// @@ -132,8 +132,7 @@ public async Task StrongFingerprintDistinguishesAbsentFromExistent() Assert.IsNotNull(fpAbsent); Assert.IsNotNull(fpExisting); - CollectionAssert.AreNotEqual(fpAbsent.Hash, fpExisting.Hash, - "AbsentPathProbe and ExistingProbe of the same path must produce different strong fingerprints."); + Assert.AreNotSequenceEqual(fpAbsent.Hash, fpExisting.Hash, "AbsentPathProbe and ExistingProbe of the same path must produce different strong fingerprints."); } /// @@ -169,7 +168,7 @@ public async Task StrongFingerprintDistinguishesProbeFromContentRead() Assert.IsNotNull(fpProbe); Assert.IsNotNull(fpRead); - CollectionAssert.AreNotEqual(fpProbe.Hash, fpRead.Hash); + Assert.AreNotSequenceEqual(fpProbe.Hash, fpRead.Hash); } /// @@ -198,8 +197,7 @@ public async Task StrongFingerprintExistingProbeIgnoresContentChange() Assert.IsNotNull(fpA); Assert.IsNotNull(fpB); - CollectionAssert.AreEqual(fpA.Hash, fpB.Hash, - "ExistingProbe must NOT incorporate file content into the strong fingerprint."); + Assert.AreSequenceEqual(fpA.Hash, fpB.Hash, "ExistingProbe must NOT incorporate file content into the strong fingerprint."); } /// @@ -235,8 +233,7 @@ public async Task StrongFingerprintDirectoryMemberHashDetectsAddition() Assert.IsNotNull(fpBefore); Assert.IsNotNull(fpAfter); - CollectionAssert.AreNotEqual(fpBefore.Hash, fpAfter.Hash, - "PathSets that differ only in DirectoryEnumeration.Members must produce different strong fingerprints."); + Assert.AreNotSequenceEqual(fpBefore.Hash, fpAfter.Hash, "PathSets that differ only in DirectoryEnumeration.Members must produce different strong fingerprints."); } /// @@ -269,7 +266,7 @@ public async Task StrongFingerprintDirectoryMemberHashDetectsRemoval() Assert.IsNotNull(fpBefore); Assert.IsNotNull(fpAfter); - CollectionAssert.AreNotEqual(fpBefore.Hash, fpAfter.Hash); + Assert.AreNotSequenceEqual(fpBefore.Hash, fpAfter.Hash); } /// @@ -305,8 +302,7 @@ public async Task StrongFingerprintDirectoryMemberHashIgnoresMemberContentChange Assert.IsNotNull(fpBefore); Assert.IsNotNull(fpAfter); - CollectionAssert.AreEqual(fpBefore.Hash, fpAfter.Hash, - "DirectoryEnumeration must NOT depend on member file contents — only on the member-name list."); + Assert.AreSequenceEqual(fpBefore.Hash, fpAfter.Hash, "DirectoryEnumeration must NOT depend on member file contents — only on the member-name list."); } /// @@ -338,7 +334,7 @@ public async Task StrongFingerprintDirectoryEnumerationMissingDirIsAbsent() // The two should still differ because the Type tag differs, but the underlying member-hash payload // for the missing-directory case is AbsentFileSentinel (same payload bytes as the AbsentPathProbe). // Different Type entries → different overall fingerprint. - CollectionAssert.AreNotEqual(fpMissingDir.Hash, fpAbsent.Hash); + Assert.AreNotSequenceEqual(fpMissingDir.Hash, fpAbsent.Hash); } /// @@ -370,8 +366,7 @@ public async Task StrongFingerprintIsDeterministic() Assert.IsNotNull(fp1); Assert.IsNotNull(fp2); - CollectionAssert.AreEqual(fp1.Hash, fp2.Hash, - "Strong fingerprint must be deterministic across factory instances."); + Assert.AreSequenceEqual(fp1.Hash, fp2.Hash, "Strong fingerprint must be deterministic across factory instances."); } // ========================================================================================= @@ -457,8 +452,7 @@ public async Task MatchesCurrentStateUnchangedHitsAndFingerprintMatches() Fingerprint? lookupFp = await factoryAtLookup.GetStrongFingerprintAsync(cachedPathSet); Assert.IsNotNull(populateFp); Assert.IsNotNull(lookupFp); - CollectionAssert.AreEqual(populateFp.Hash, lookupFp.Hash, - "Cached strong FP must match recomputed FP when state is unchanged."); + Assert.AreSequenceEqual(populateFp.Hash, lookupFp.Hash, "Cached strong FP must match recomputed FP when state is unchanged."); } /// @@ -496,8 +490,7 @@ public async Task MatchesCurrentStateCleanDirtyCleanCycleRecoversHit() Assert.IsNotNull(build1Fp); Assert.IsNotNull(build3Fp); - CollectionAssert.AreEqual(build1Fp.Hash, build3Fp.Hash, - "Build 3 (clean again) must produce the Build 1 fingerprint so the cache hit is recovered."); + Assert.AreSequenceEqual(build1Fp.Hash, build3Fp.Hash, "Build 3 (clean again) must produce the Build 1 fingerprint so the cache hit is recovered."); } /// @@ -771,7 +764,7 @@ public void EnumerateAndSubtractUsesWin32StarDotStarSemantics() FingerprintFactory.EnumerateAndSubtract(tempDir.Path, "*.*", writtenMembersToSubtract: null); Assert.IsNotNull(result); - CollectionAssert.AreEquivalent(new[] { "README", "a.cs" }, result.ToArray()); + Assert.AreSequenceEqual(new[] { "README", "a.cs" }, result.ToArray(), SequenceOrder.InAnyOrder); } [TestMethod] @@ -789,7 +782,7 @@ public void EnumerateAndSubtractExcludesIgnoredMembers() ignoredInputPatterns: new[] { Glob.Parse(ignoredPath) }); Assert.IsNotNull(result); - CollectionAssert.AreEqual(new[] { "stable.input" }, result.ToArray()); + Assert.AreSequenceEqual(new[] { "stable.input" }, result.ToArray()); } /// @@ -827,7 +820,7 @@ public void EnumerateAndSubtractIsCaseInsensitive() IReadOnlyList? result = FingerprintFactory.EnumerateAndSubtract(tempDir.Path, enumerationPattern: "*.cs", writtenMembersToSubtract: null); Assert.IsNotNull(result); - CollectionAssert.AreEquivalent(new[] { "Foo.CS", "bar.cs" }, result.ToArray()); + Assert.AreSequenceEqual(new[] { "Foo.CS", "bar.cs" }, result.ToArray(), SequenceOrder.InAnyOrder); } // ========================================================================================= @@ -851,7 +844,7 @@ public void FoldPathSetEntriesFlagOffIgnoresObservations() }, enableProbeAndEnumeration: false); - Assert.AreEqual(1, entries.Count); + Assert.HasCount(1, entries); Assert.AreEqual("a.cs", entries[0].Path); Assert.AreEqual(ObservationType.FileContentRead, entries[0].Type); } @@ -870,7 +863,7 @@ public void FoldPathSetEntriesPrecedenceKeepsContentReadOverProbe() }, enableProbeAndEnumeration: true); - Assert.AreEqual(1, entries.Count); + Assert.HasCount(1, entries); Assert.AreEqual(ObservationType.FileContentRead, entries[0].Type); } @@ -890,7 +883,7 @@ public void FoldPathSetEntriesPrecedenceReplacesProbeWithRead() }, enableProbeAndEnumeration: true); - Assert.AreEqual(1, entries.Count); + Assert.HasCount(1, entries); Assert.AreEqual(ObservationType.FileContentRead, entries[0].Type); } @@ -910,10 +903,9 @@ public void FoldPathSetEntriesDirectoryEnumerationKeepsDistinctPatterns() }, enableProbeAndEnumeration: true); - Assert.AreEqual(2, entries.Count, "Same-path DirectoryEnumeration with different patterns must produce TWO entries."); - CollectionAssert.AreEqual( - new[] { "*.cs", "*.dll" }, - entries.Select(e => e.EnumerationPattern).ToArray()); + Assert.HasCount(2, entries, "Same-path DirectoryEnumeration with different patterns must produce TWO entries."); + Assert.AreSequenceEqual( + new[] { "*.cs", "*.dll" }, entries.Select(e => e.EnumerationPattern).ToArray()); Assert.IsTrue(entries.All(e => e.Type == ObservationType.DirectoryEnumeration)); Assert.IsTrue(entries.All(e => e.Path == "dir")); } @@ -934,7 +926,7 @@ public void FoldPathSetEntriesDirectoryEnumerationFoldsDuplicatePatterns() }, enableProbeAndEnumeration: true); - Assert.AreEqual(1, entries.Count); + Assert.HasCount(1, entries); Assert.AreEqual("*.cs", entries[0].EnumerationPattern); } @@ -953,7 +945,7 @@ public void FoldPathSetEntriesContentReadOutranksEnumeration() }, enableProbeAndEnumeration: true); - Assert.AreEqual(1, entries.Count); + Assert.HasCount(1, entries); Assert.AreEqual(ObservationType.FileContentRead, entries[0].Type); Assert.IsNull(entries[0].EnumerationPattern); } @@ -974,7 +966,7 @@ public void FoldPathSetEntriesContentReadReplacesMultiPatternEnumeration() }, enableProbeAndEnumeration: true); - Assert.AreEqual(1, entries.Count); + Assert.HasCount(1, entries); Assert.AreEqual(ObservationType.FileContentRead, entries[0].Type); } @@ -995,7 +987,7 @@ public void FoldPathSetEntriesProducesCanonicalSort() }, enableProbeAndEnumeration: true); - Assert.AreEqual(4, entries.Count); + Assert.HasCount(4, entries); Assert.AreEqual("alpha", entries[0].Path); Assert.AreEqual(ObservationType.ExistingProbe, entries[0].Type); Assert.AreEqual("mike", entries[1].Path); @@ -1031,10 +1023,10 @@ public void FilterObservationsFcrIncludedWhenHasherCanHash() TestNormalizer, EmptyIgnoredPatterns); - Assert.AreEqual(1, included.Count); + Assert.HasCount(1, included); Assert.AreEqual(@"{RepoRoot}src\foo.cs", included[0].Path); Assert.AreEqual(ObservationType.FileContentRead, included[0].Type); - Assert.AreEqual(0, excluded.Count); + Assert.IsEmpty(excluded); } /// @@ -1051,8 +1043,8 @@ public void FilterObservationsFcrExcludedWhenHasherCannotHash() TestNormalizer, EmptyIgnoredPatterns); - Assert.AreEqual(0, included.Count); - Assert.AreEqual(1, excluded.Count); + Assert.IsEmpty(included); + Assert.HasCount(1, excluded); Assert.AreEqual(@"{RepoRoot}src\foo.cs", excluded[0]); } @@ -1069,10 +1061,10 @@ public void FilterObservationsKeepsInScopeProbe() TestNormalizer, EmptyIgnoredPatterns); - Assert.AreEqual(1, included.Count); + Assert.HasCount(1, included); Assert.AreEqual(@"{RepoRoot}src\foo.cs", included[0].Path); Assert.AreEqual(ObservationType.ExistingProbe, included[0].Type); - Assert.AreEqual(0, excluded.Count); + Assert.IsEmpty(excluded); } /// @@ -1088,8 +1080,8 @@ public void FilterObservationsDropsOutOfScopeProbe() TestNormalizer, EmptyIgnoredPatterns); - Assert.AreEqual(0, included.Count); - Assert.AreEqual(0, excluded.Count); + Assert.IsEmpty(included); + Assert.IsEmpty(excluded); } /// @@ -1118,8 +1110,8 @@ public void FilterObservationsDropsPredictedInputs() TestNormalizer, EmptyIgnoredPatterns); - Assert.AreEqual(0, included.Count); - Assert.AreEqual(0, excluded.Count); + Assert.IsEmpty(included); + Assert.IsEmpty(excluded); } /// @@ -1143,11 +1135,11 @@ public void FilterObservationsPreservesDirectoryEnumerationFields() TestNormalizer, EmptyIgnoredPatterns); - Assert.AreEqual(1, included.Count); + Assert.HasCount(1, included); Assert.AreEqual(ObservationType.DirectoryEnumeration, included[0].Type); Assert.AreEqual("*.cs", included[0].EnumerationPattern); - CollectionAssert.AreEqual(new[] { "a.cs", "b.cs" }, included[0].Members?.ToArray()); - CollectionAssert.AreEqual(new[] { "Foo.dll" }, included[0].WrittenMembers?.ToArray()); + Assert.AreSequenceEqual(new[] { "a.cs", "b.cs" }, included[0].Members?.ToArray()); + Assert.AreSequenceEqual(new[] { "Foo.dll" }, included[0].WrittenMembers?.ToArray()); } /// @@ -1163,8 +1155,8 @@ public void FilterObservationsEmptyInput() TestNormalizer, EmptyIgnoredPatterns); - Assert.AreEqual(0, included.Count); - Assert.AreEqual(0, excluded.Count); + Assert.IsEmpty(included); + Assert.IsEmpty(excluded); } /// @@ -1193,9 +1185,9 @@ public void FilterObservationsDropsIgnoredInputPatterns() TestNormalizer, ignored); - Assert.AreEqual(1, included.Count); + Assert.HasCount(1, included); Assert.AreEqual(@"{RepoRoot}src\foo.cs", included[0].Path); - Assert.AreEqual(0, excluded.Count, "Ignored paths must not appear in the excluded debug list either."); + Assert.IsEmpty(excluded, "Ignored paths must not appear in the excluded debug list either."); } private static FingerprintFactory CreateFactory(IContentHasher hasher, IInputHasher? inputHasher = null, PathNormalizer? pathNormalizer = null) diff --git a/src/Common.Tests/Hashing/DirectoryFileHasherTests.cs b/src/Common.Tests/Hashing/DirectoryFileHasherTests.cs index 341c143..f5d823e 100644 --- a/src/Common.Tests/Hashing/DirectoryFileHasherTests.cs +++ b/src/Common.Tests/Hashing/DirectoryFileHasherTests.cs @@ -47,14 +47,14 @@ public async Task ComputeHash(string relativePath, bool expectedToHaveHash) #if NETFRAMEWORK File.WriteAllText(absolutePath, fileContent); #else - await File.WriteAllTextAsync(absolutePath, fileContent); + await File.WriteAllTextAsync(absolutePath, fileContent, TestContext.CancellationToken); #endif byte[]? hash = await hasher.GetHashAsync(absolutePath); if (expectedToHaveHash) { byte[] expectedHash = ContentHasher.GetContentHash(Encoding.Default.GetBytes(fileContent)).ToHashByteArray(); - CollectionAssert.AreEqual(expectedHash, hash); + Assert.AreSequenceEqual(expectedHash, hash); } else { diff --git a/src/Common.Tests/Hashing/HashingExtensionsTests.cs b/src/Common.Tests/Hashing/HashingExtensionsTests.cs index 323ae58..728a1e3 100644 --- a/src/Common.Tests/Hashing/HashingExtensionsTests.cs +++ b/src/Common.Tests/Hashing/HashingExtensionsTests.cs @@ -25,7 +25,7 @@ public void CombineHashes() // This doesn't mean anything to a human; it's just intended to exercise the code byte[] expectedHash = new byte[] { 0x77, 0x8a, 0xaa, 0x14, 0x80, 0x06, 0x5d, 0xf8, 0x87, 0xe0, 0xab, 0xb5, 0x59, 0xd8, 0x26, 0xc5 }; - CollectionAssert.AreEqual(expectedHash, ContentHasher.CombineHashes(hashes)); + Assert.AreSequenceEqual(expectedHash, ContentHasher.CombineHashes(hashes)); } [TestMethod] @@ -50,9 +50,8 @@ public void CombineHashesNullHashes() null, }; - CollectionAssert.AreEqual( - ContentHasher.CombineHashes(hashesWithGaps), - ContentHasher.CombineHashes(hashes)); + Assert.AreSequenceEqual( + ContentHasher.CombineHashes(hashesWithGaps), ContentHasher.CombineHashes(hashes)); } [TestMethod] diff --git a/src/Common.Tests/Hashing/OutputHasherTests.cs b/src/Common.Tests/Hashing/OutputHasherTests.cs index ed046b2..75d365e 100644 --- a/src/Common.Tests/Hashing/OutputHasherTests.cs +++ b/src/Common.Tests/Hashing/OutputHasherTests.cs @@ -33,7 +33,7 @@ public async Task ComputeHash() #if NETFRAMEWORK File.WriteAllText(file, "someContent"); #else - await File.WriteAllTextAsync(file, "someContent"); + await File.WriteAllTextAsync(file, "someContent", TestContext.CancellationToken); #endif ContentHash hash = await hasher.ComputeHashAsync(file, CancellationToken.None); diff --git a/src/Common.Tests/MSBuildCachePluginBaseTests.cs b/src/Common.Tests/MSBuildCachePluginBaseTests.cs index ae27d66..3c6488c 100644 --- a/src/Common.Tests/MSBuildCachePluginBaseTests.cs +++ b/src/Common.Tests/MSBuildCachePluginBaseTests.cs @@ -56,7 +56,7 @@ public void CheckForDuplicateOutputsDetectsConflictingDuplicateAfterUniqueOutput Assert.AreSame(currentNode, outputProducer[TrailingUniqueOutputPath]); Assert.HasCount(2, logger.LogEntries); Assert.AreEqual(PluginLogLevel.Error, logger.LogEntries[1].LogLevel); - StringAssert.Contains(logger.LogEntries[1].Message, "with a different hash", StringComparison.Ordinal); + Assert.Contains("with a different hash", logger.LogEntries[1].Message, StringComparison.Ordinal); } [TestMethod] @@ -81,7 +81,7 @@ public void CheckForDuplicateOutputsAllowsOrderedIdenticalDuplicateAfterUniqueOu Assert.AreSame(currentNode, outputProducer[TrailingUniqueOutputPath]); Assert.HasCount(2, logger.LogEntries); Assert.AreEqual(PluginLogLevel.Message, logger.LogEntries[1].LogLevel); - StringAssert.Contains(logger.LogEntries[1].Message, "Allowing as content is the same", StringComparison.Ordinal); + Assert.Contains("Allowing as content is the same", logger.LogEntries[1].Message, StringComparison.Ordinal); } [TestMethod] @@ -106,7 +106,7 @@ public void CheckForDuplicateOutputsWarnsForUnorderedIdenticalDuplicateAfterUniq Assert.AreSame(currentNode, outputProducer[TrailingUniqueOutputPath]); Assert.HasCount(2, logger.LogEntries); Assert.AreEqual(PluginLogLevel.Warning, logger.LogEntries[1].LogLevel); - StringAssert.Contains(logger.LogEntries[1].Message, "there is no ordering between the two nodes", StringComparison.Ordinal); + Assert.Contains("there is no ordering between the two nodes", logger.LogEntries[1].Message, StringComparison.Ordinal); } private static void CheckForDuplicateOutputs( diff --git a/src/Common.Tests/NodeBuildResultTests.cs b/src/Common.Tests/NodeBuildResultTests.cs index 921b0d8..92de0cc 100644 --- a/src/Common.Tests/NodeBuildResultTests.cs +++ b/src/Common.Tests/NodeBuildResultTests.cs @@ -28,7 +28,7 @@ public void SortWorksConsistently() foreach (IList permutation in names.Permutations()) { var ordinal_sorted = new SortedSet(permutation, StringComparer.OrdinalIgnoreCase); - CollectionAssert.AreEqual(baseline, ordinal_sorted); + Assert.AreSequenceEqual(baseline, ordinal_sorted); } } @@ -55,7 +55,7 @@ public void SortWorksConsistentlyAcrossJson() string serialized = JsonSerializer.Serialize(nodeBuildResult, SourceGenerationContext.Default.NodeBuildResult); NodeBuildResult deserialized = JsonSerializer.Deserialize(serialized, SourceGenerationContext.Default.NodeBuildResult)!; - CollectionAssert.AreEqual(expected.Keys, deserialized.Outputs.Keys, "\n" + + Assert.AreSequenceEqual(expected.Keys, deserialized.Outputs.Keys, "\n" + "Permutation: " + string.Join(", ", permutation) + "\n" + "Serialized: " + serialized + "\n" + "Deserialized: " + string.Join(", ", deserialized.Outputs.Keys) + "\n" + diff --git a/src/Common.Tests/ObservationFilterTests.cs b/src/Common.Tests/ObservationFilterTests.cs index a51496c..5197d04 100644 --- a/src/Common.Tests/ObservationFilterTests.cs +++ b/src/Common.Tests/ObservationFilterTests.cs @@ -104,12 +104,12 @@ public void BuildEverWrittenOrAncestorSetIncludesAllAncestors() }); // Must include the file itself plus every ancestor up to drive root. - Assert.IsTrue(result.Contains(@"X:\Repo\bin\Debug\net9.0\TestProject.dll")); - Assert.IsTrue(result.Contains(@"X:\Repo\bin\Debug\net9.0")); - Assert.IsTrue(result.Contains(@"X:\Repo\bin\Debug")); - Assert.IsTrue(result.Contains(@"X:\Repo\bin")); - Assert.IsTrue(result.Contains(@"X:\Repo")); - Assert.IsTrue(result.Contains(@"X:\")); + Assert.Contains(@"X:\Repo\bin\Debug\net9.0\TestProject.dll", result); + Assert.Contains(@"X:\Repo\bin\Debug\net9.0", result); + Assert.Contains(@"X:\Repo\bin\Debug", result); + Assert.Contains(@"X:\Repo\bin", result); + Assert.Contains(@"X:\Repo", result); + Assert.Contains(@"X:\", result); } [TestMethod] @@ -142,7 +142,7 @@ public void BuildEverWrittenOrAncestorSetCaseInsensitive() // Both files plus shared ancestor chain @ "X:\Repo\BIN\Debug" + "X:\Repo\BIN" + "X:\Repo" + "X:\" // First write's ancestors get added with their casing; second write's ancestors are deduped via // OrdinalIgnoreCase. - Assert.AreEqual(6, result.Count); + Assert.HasCount(6, result); } [TestMethod] @@ -156,19 +156,19 @@ public void BuildEverWrittenOrAncestorSetTrimsTrailingSeparator() @"X:\Repo\bin\Debug\net9.0\", }); - Assert.IsTrue(result.Contains(@"X:\Repo\bin\Debug\net9.0")); - Assert.IsFalse(result.Contains(@"X:\Repo\bin\Debug\net9.0\")); - Assert.IsTrue(result.Contains(@"X:\Repo\bin\Debug")); - Assert.IsTrue(result.Contains(@"X:\Repo\bin")); - Assert.IsTrue(result.Contains(@"X:\Repo")); - Assert.IsTrue(result.Contains(@"X:\")); + Assert.Contains(@"X:\Repo\bin\Debug\net9.0", result); + Assert.DoesNotContain(@"X:\Repo\bin\Debug\net9.0\", result); + Assert.Contains(@"X:\Repo\bin\Debug", result); + Assert.Contains(@"X:\Repo\bin", result); + Assert.Contains(@"X:\Repo", result); + Assert.Contains(@"X:\", result); } [TestMethod] public void BuildEverWrittenOrAncestorSetEmptyInput() { HashSet result = FileAccessRepository.BuildEverWrittenOrAncestorSet(new List()); - Assert.AreEqual(0, result.Count); + Assert.IsEmpty(result); } [TestMethod] diff --git a/src/Common.Tests/PathSetTests.cs b/src/Common.Tests/PathSetTests.cs index 47bbbeb..781c2ca 100644 --- a/src/Common.Tests/PathSetTests.cs +++ b/src/Common.Tests/PathSetTests.cs @@ -27,7 +27,7 @@ public void DeserializingPayloadWithoutEntriesDoesNotThrow() Assert.IsNotNull(deserialized); Assert.IsNotNull(deserialized!.Entries, "Entries must never be null; it is hashed before it can be checked."); - Assert.AreEqual(0, deserialized.Entries.Count); + Assert.IsEmpty(deserialized.Entries); // Both must be callable, since the type is used as a cache key. _ = deserialized.GetHashCode(); @@ -153,7 +153,7 @@ public void JsonRoundTripAllObservationTypes() Assert.IsNotNull(deserialized); Assert.AreEqual(original, deserialized); // Sanity-check that every type round-tripped: equality covers it, but be explicit about the schema field. - Assert.AreEqual(4, deserialized!.Entries.Count); + Assert.HasCount(4, deserialized!.Entries); Assert.AreEqual(ObservationType.FileContentRead, deserialized.Entries[0].Type); Assert.AreEqual(ObservationType.DirectoryEnumeration, deserialized.Entries[1].Type); Assert.AreEqual("*.cs", deserialized.Entries[1].EnumerationPattern); diff --git a/src/Common.Tests/PluginSettingsExtensibilityTests.cs b/src/Common.Tests/PluginSettingsExtensibilityTests.cs index 073bf2e..40321a1 100644 --- a/src/Common.Tests/PluginSettingsExtensibilityTests.cs +++ b/src/Common.Tests/PluginSettingsExtensibilityTests.cs @@ -35,7 +35,7 @@ public void EffectiveSettingsLogging() { // All properties are { get; init; } Assert.IsTrue(property.CanRead); - Assert.IsTrue(property.GetSetMethod()!.ReturnParameter.GetRequiredCustomModifiers().Any(t => t.Name.Equals("IsExternalInit", StringComparison.Ordinal))); + Assert.Contains(t => t.Name.Equals("IsExternalInit", StringComparison.Ordinal), property.GetSetMethod()!.ReturnParameter.GetRequiredCustomModifiers()); // RepoRoot isn't included in the logging. bool shouldBeLogged = !property.Name.Equals(nameof(PluginSettings.RepoRoot), StringComparison.Ordinal); @@ -80,17 +80,17 @@ public void DefaultValue() Assert.AreEqual(DefaultMockPluginSettings.GlobSetting.ToString(), pluginSettings.GlobSetting.ToString()); - CollectionAssert.AreEqual(DefaultMockPluginSettings.ArraySetting, pluginSettings.ArraySetting); + Assert.AreSequenceEqual(DefaultMockPluginSettings.ArraySetting, pluginSettings.ArraySetting); - CollectionAssert.AreEqual(DefaultMockPluginSettings.ListSetting, pluginSettings.ListSetting); - CollectionAssert.AreEqual(DefaultMockPluginSettings.IListSetting.ToList(), pluginSettings.IListSetting.ToList()); - CollectionAssert.AreEqual(DefaultMockPluginSettings.ICollectionSetting.ToList(), pluginSettings.ICollectionSetting.ToList()); - CollectionAssert.AreEqual(DefaultMockPluginSettings.IEnumerableSetting.ToList(), pluginSettings.IEnumerableSetting.ToList()); - CollectionAssert.AreEqual(DefaultMockPluginSettings.IReadOnlyListSetting.ToList(), pluginSettings.IReadOnlyListSetting.ToList()); - CollectionAssert.AreEqual(DefaultMockPluginSettings.IReadOnlyCollectionSetting.ToList(), pluginSettings.IReadOnlyCollectionSetting.ToList()); + Assert.AreSequenceEqual(DefaultMockPluginSettings.ListSetting, pluginSettings.ListSetting); + Assert.AreSequenceEqual(DefaultMockPluginSettings.IListSetting.ToList(), pluginSettings.IListSetting.ToList()); + Assert.AreSequenceEqual(DefaultMockPluginSettings.ICollectionSetting.ToList(), pluginSettings.ICollectionSetting.ToList()); + Assert.AreSequenceEqual(DefaultMockPluginSettings.IEnumerableSetting.ToList(), pluginSettings.IEnumerableSetting.ToList()); + Assert.AreSequenceEqual(DefaultMockPluginSettings.IReadOnlyListSetting.ToList(), pluginSettings.IReadOnlyListSetting.ToList()); + Assert.AreSequenceEqual(DefaultMockPluginSettings.IReadOnlyCollectionSetting.ToList(), pluginSettings.IReadOnlyCollectionSetting.ToList()); - CollectionAssert.AreEquivalent(DefaultMockPluginSettings.HashSetSetting.ToList(), pluginSettings.HashSetSetting.ToList()); - CollectionAssert.AreEquivalent(DefaultMockPluginSettings.ISetSetting.ToList(), pluginSettings.ISetSetting.ToList()); + Assert.AreSequenceEqual(DefaultMockPluginSettings.HashSetSetting.ToList(), pluginSettings.HashSetSetting.ToList(), SequenceOrder.InAnyOrder); + Assert.AreSequenceEqual(DefaultMockPluginSettings.ISetSetting.ToList(), pluginSettings.ISetSetting.ToList(), SequenceOrder.InAnyOrder); AssertNotLogged(logger, PluginLogLevel.Warning, "has invalid value"); AssertNotLogged(logger, PluginLogLevel.Warning, "has unsupported type"); @@ -172,7 +172,7 @@ void AssertInvalidValueHandled(string settingName, Func(string settingName, Func> valueAccessor) { AssertLogged(logger, PluginLogLevel.Warning, $"'{settingName}' has invalid value"); - CollectionAssert.AreEqual(valueAccessor(DefaultMockPluginSettings).ToList(), valueAccessor(pluginSettings).ToList()); + Assert.AreSequenceEqual(valueAccessor(DefaultMockPluginSettings).ToList(), valueAccessor(pluginSettings).ToList()); } } @@ -232,17 +232,17 @@ public void ExplicitValues() Assert.AreEqual(@"X:\Repo\**\b.*", pluginSettings.GlobSetting.ToString()); - CollectionAssert.AreEqual(new[] { 4, 5, 6 }, pluginSettings.ArraySetting); + Assert.AreSequenceEqual(new[] { 4, 5, 6 }, pluginSettings.ArraySetting); - CollectionAssert.AreEqual(new[] { 4, 5, 6 }, pluginSettings.ListSetting); - CollectionAssert.AreEqual(new[] { 4, 5, 6 }, pluginSettings.IListSetting.ToList()); - CollectionAssert.AreEqual(new[] { 4, 5, 6 }, pluginSettings.ICollectionSetting.ToList()); - CollectionAssert.AreEqual(new[] { 4, 5, 6 }, pluginSettings.IEnumerableSetting.ToList()); - CollectionAssert.AreEqual(new[] { 4, 5, 6 }, pluginSettings.IReadOnlyListSetting.ToList()); - CollectionAssert.AreEqual(new[] { 4, 5, 6 }, pluginSettings.IReadOnlyCollectionSetting.ToList()); + Assert.AreSequenceEqual(new[] { 4, 5, 6 }, pluginSettings.ListSetting); + Assert.AreSequenceEqual(new[] { 4, 5, 6 }, pluginSettings.IListSetting.ToList()); + Assert.AreSequenceEqual(new[] { 4, 5, 6 }, pluginSettings.ICollectionSetting.ToList()); + Assert.AreSequenceEqual(new[] { 4, 5, 6 }, pluginSettings.IEnumerableSetting.ToList()); + Assert.AreSequenceEqual(new[] { 4, 5, 6 }, pluginSettings.IReadOnlyListSetting.ToList()); + Assert.AreSequenceEqual(new[] { 4, 5, 6 }, pluginSettings.IReadOnlyCollectionSetting.ToList()); - CollectionAssert.AreEquivalent(new[] { 4, 5, 6 }, pluginSettings.HashSetSetting.ToList()); - CollectionAssert.AreEquivalent(new[] { 4, 5, 6 }, pluginSettings.ISetSetting.ToList()); + Assert.AreSequenceEqual(new[] { 4, 5, 6 }, pluginSettings.HashSetSetting.ToList(), SequenceOrder.InAnyOrder); + Assert.AreSequenceEqual(new[] { 4, 5, 6 }, pluginSettings.ISetSetting.ToList(), SequenceOrder.InAnyOrder); AssertNotLogged(logger, PluginLogLevel.Warning, "has invalid value"); AssertNotLogged(logger, PluginLogLevel.Warning, "has unsupported type"); diff --git a/src/Common.Tests/PluginSettingsTests.cs b/src/Common.Tests/PluginSettingsTests.cs index ccbdfb0..5fd6c5c 100644 --- a/src/Common.Tests/PluginSettingsTests.cs +++ b/src/Common.Tests/PluginSettingsTests.cs @@ -41,7 +41,7 @@ public void EffectiveSettingsLogging() { // All properties are { get; init; } Assert.IsTrue(property.CanRead); - Assert.IsTrue(property.GetSetMethod()!.ReturnParameter.GetRequiredCustomModifiers().Any(t => t.Name.Equals("IsExternalInit", StringComparison.Ordinal))); + Assert.Contains(t => t.Name.Equals("IsExternalInit", StringComparison.Ordinal), property.GetSetMethod()!.ReturnParameter.GetRequiredCustomModifiers()); // RepoRoot isn't included in the logging. bool isLogged = !property.Name.Equals(nameof(PluginSettings.RepoRoot), StringComparison.Ordinal); @@ -135,18 +135,18 @@ public void ProbeAndEnumerationFingerprintingForcedOffWithoutCapability(bool req "The setting must be forced off when the host MSBuild cannot report the required file access fields, " + "even when the user explicitly asked for it."); - Assert.IsTrue( - logger.LogEntries.Any(entry => entry.Message.Contains( + Assert.Contains( + entry => entry.Message.Contains( nameof(PluginSettings.EnableProbeAndEnumerationFingerprinting), StringComparison.Ordinal) - && entry.Message.Contains("forced to false", StringComparison.Ordinal)), + && entry.Message.Contains("forced to false", StringComparison.Ordinal), logger.LogEntries, "Forcing the setting off must be logged so the cache-behavior change is diagnosable."); // Naming the running version is what makes the message actionable — otherwise a user is told the // feature is off but not what they are on or that upgrading would fix it. if (FileAccessDataCapabilities.MSBuildVersion is string msbuildVersion) { - Assert.IsTrue( - logger.LogEntries.Any(entry => entry.Message.Contains(msbuildVersion, StringComparison.Ordinal)), + Assert.Contains( + entry => entry.Message.Contains(msbuildVersion, StringComparison.Ordinal), logger.LogEntries, $"The message must name the running MSBuild version ('{msbuildVersion}')."); } } @@ -267,11 +267,11 @@ public void AllowFileAccessAfterProjectFinishFilePatternsSupportsMachineLocalCat supportsProbeAndEnumerationCapture: true); IReadOnlyCollection patterns = pluginSettings.AllowFileAccessAfterProjectFinishFilePatterns; - Assert.IsTrue(patterns.Any(pattern => pattern.IsMatch(@"C:\Program Files\Telemetry\ApplicationInsights.config"))); - Assert.IsTrue(patterns.Any(pattern => pattern.IsMatch(@"C:\Users\Test\AppData\Local\Microsoft\VSApplicationInsights\config.json"))); - Assert.IsTrue(patterns.Any(pattern => pattern.IsMatch(@"C:\Users\Test\AppData\Local\Microsoft\Windows\INetCache\IE\ABC\dyntelconfig[2].cache"))); - Assert.IsTrue(patterns.Any(pattern => pattern.IsMatch(@"C:\Windows\System32\ci.dll"))); - Assert.IsFalse(patterns.Any(pattern => pattern.IsMatch(@"X:\Repo\src\Program.cs"))); + Assert.Contains(pattern => pattern.IsMatch(@"C:\Program Files\Telemetry\ApplicationInsights.config"), patterns); + Assert.Contains(pattern => pattern.IsMatch(@"C:\Users\Test\AppData\Local\Microsoft\VSApplicationInsights\config.json"), patterns); + Assert.Contains(pattern => pattern.IsMatch(@"C:\Users\Test\AppData\Local\Microsoft\Windows\INetCache\IE\ABC\dyntelconfig[2].cache"), patterns); + Assert.Contains(pattern => pattern.IsMatch(@"C:\Windows\System32\ci.dll"), patterns); + Assert.DoesNotContain(pattern => pattern.IsMatch(@"X:\Repo\src\Program.cs"), patterns); } [TestMethod] @@ -403,7 +403,7 @@ private static void TestStringListSetting( RepoRoot, supportsProbeAndEnumerationCapture: true); - CollectionAssert.AreEqual(testCase.ExpectedValues.ToList(), valueAccessor(pluginSettings).ToList()); + Assert.AreSequenceEqual(testCase.ExpectedValues.ToList(), valueAccessor(pluginSettings).ToList()); } public static IEnumerable GlobTestCases diff --git a/src/Common.Tests/SourceControl/GitFileHashProviderTest.cs b/src/Common.Tests/SourceControl/GitFileHashProviderTest.cs index 0829147..08e92b1 100644 Binary files a/src/Common.Tests/SourceControl/GitFileHashProviderTest.cs and b/src/Common.Tests/SourceControl/GitFileHashProviderTest.cs differ diff --git a/src/Common.Tests/WarningPolicyTests.cs b/src/Common.Tests/WarningPolicyTests.cs index 7b72553..827a2ed 100644 --- a/src/Common.Tests/WarningPolicyTests.cs +++ b/src/Common.Tests/WarningPolicyTests.cs @@ -55,8 +55,8 @@ public async Task DefaultDiagnosticIsVisibleAsWarning() { BuildInvocationResult result = await RunBuildAsync(logAsMessage: false, warnAsError: false, emitUnrelatedWarning: false); - StringAssert.Contains(result.Output, "Build succeeded.", StringComparison.Ordinal); - StringAssert.Contains(result.Output, WarningPolicyTestPlugin.DiagnosticMessage, StringComparison.Ordinal); + Assert.Contains("Build succeeded.", result.Output, StringComparison.Ordinal); + Assert.Contains(WarningPolicyTestPlugin.DiagnosticMessage, result.Output, StringComparison.Ordinal); } [TestMethod] @@ -64,8 +64,8 @@ public async Task WarnAsErrorEscalatesDefaultDiagnostic() { BuildInvocationResult result = await RunBuildAsync(logAsMessage: false, warnAsError: true, emitUnrelatedWarning: false); - StringAssert.Contains(result.Output, "Build FAILED.", StringComparison.Ordinal); - StringAssert.Contains(result.Output, WarningPolicyTestPlugin.DiagnosticMessage, StringComparison.Ordinal); + Assert.Contains("Build FAILED.", result.Output, StringComparison.Ordinal); + Assert.Contains(WarningPolicyTestPlugin.DiagnosticMessage, result.Output, StringComparison.Ordinal); } [TestMethod] @@ -73,8 +73,8 @@ public async Task MessageSettingAvoidsWarnAsErrorEscalation() { BuildInvocationResult result = await RunBuildAsync(logAsMessage: true, warnAsError: true, emitUnrelatedWarning: false); - StringAssert.Contains(result.Output, "Build succeeded.", StringComparison.Ordinal); - StringAssert.Contains(result.Output, WarningPolicyTestPlugin.DiagnosticMessage, StringComparison.Ordinal); + Assert.Contains("Build succeeded.", result.Output, StringComparison.Ordinal); + Assert.Contains(WarningPolicyTestPlugin.DiagnosticMessage, result.Output, StringComparison.Ordinal); } [TestMethod] @@ -82,8 +82,8 @@ public async Task MessageSettingDoesNotWeakenOtherWarnings() { BuildInvocationResult result = await RunBuildAsync(logAsMessage: true, warnAsError: true, emitUnrelatedWarning: true); - StringAssert.Contains(result.Output, "Build FAILED.", StringComparison.Ordinal); - StringAssert.Contains(result.Output, "Unrelated warning", StringComparison.Ordinal); + Assert.Contains("Build FAILED.", result.Output, StringComparison.Ordinal); + Assert.Contains("Unrelated warning", result.Output, StringComparison.Ordinal); } private async Task RunBuildAsync(bool logAsMessage, bool warnAsError, bool emitUnrelatedWarning) @@ -111,7 +111,7 @@ private async Task RunBuildAsync(bool logAsMessage, bool """; - await File.WriteAllTextAsync(projectPath, projectContents); + await File.WriteAllTextAsync(projectPath, projectContents, TestContext.CancellationToken); ProcessStartInfo startInfo = new() { @@ -139,9 +139,9 @@ private async Task RunBuildAsync(bool logAsMessage, bool } using Process process = Process.Start(startInfo)!; - Task standardOutput = process.StandardOutput.ReadToEndAsync(); - Task standardError = process.StandardError.ReadToEndAsync(); - await process.WaitForExitAsync(); + Task standardOutput = process.StandardOutput.ReadToEndAsync(TestContext.CancellationToken); + Task standardError = process.StandardError.ReadToEndAsync(TestContext.CancellationToken); + await process.WaitForExitAsync(TestContext.CancellationToken); string output = await standardOutput + await standardError; return new BuildInvocationResult(output);