From 7e75c0d3dae897defcc0bf68460f70370b38d2a9 Mon Sep 17 00:00:00 2001 From: Roman Konecny Date: Mon, 25 Jan 2021 20:32:54 +0100 Subject: [PATCH 1/8] Initial code changes - not verified yet --- src/Shared/AssemblyNameExtension.cs | 35 +++- src/Shared/TranslatorHelpers.cs | 169 +++++++++++++++++- .../ResolveAssemblyReference.cs | 28 ++- src/Tasks/Microsoft.Build.Tasks.csproj | 11 +- src/Tasks/SystemState.cs | 159 +++++++++++++++- src/Tasks/TaskTranslatorHelpers.cs | 35 ++++ 6 files changed, 421 insertions(+), 16 deletions(-) create mode 100644 src/Tasks/TaskTranslatorHelpers.cs diff --git a/src/Shared/AssemblyNameExtension.cs b/src/Shared/AssemblyNameExtension.cs index 1d4f2a4bf71..0bd6dbc004f 100644 --- a/src/Shared/AssemblyNameExtension.cs +++ b/src/Shared/AssemblyNameExtension.cs @@ -9,6 +9,7 @@ using System.Configuration.Assemblies; using System.Runtime.Serialization; using System.IO; +using Microsoft.Build.BackEnd; #if FEATURE_ASSEMBLYLOADCONTEXT using System.Reflection.PortableExecutable; using System.Reflection.Metadata; @@ -54,7 +55,7 @@ internal enum PartialComparisonFlags : int /// between the two is done lazily on demand. /// [Serializable] - internal sealed class AssemblyNameExtension : ISerializable, IEquatable + internal sealed class AssemblyNameExtension : ISerializable, IEquatable, ITranslatable { private AssemblyName asAssemblyName = null; private string asString = null; @@ -173,6 +174,14 @@ private AssemblyNameExtension(SerializationInfo info, StreamingContext context) remappedFrom = (HashSet) info.GetValue("remapped", typeof(HashSet)); } + /// + /// Ctor for deserializing from state file (custom binary serialization) using translator. + /// + internal AssemblyNameExtension(ITranslator translator) : this() + { + Translate(translator); + } + /// /// To be used as a delegate. Gets the AssemblyName of the given file. /// @@ -251,10 +260,18 @@ private void InitializeRemappedFrom() { if (remappedFrom == null) { - remappedFrom = new HashSet(AssemblyNameComparer.GenericComparerConsiderRetargetable); + remappedFrom = CreateRemappedFrom(); } } + /// + /// Create remappedFrom HashSet. Used by deserialization as well. + /// + private static HashSet CreateRemappedFrom() + { + return new HashSet(AssemblyNameComparer.GenericComparerConsiderRetargetable); + } + /// /// Assume there is a string version, create the AssemblyName version. /// @@ -993,5 +1010,19 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) info.AddValue("immutable", immutable); info.AddValue("remapped", remappedFrom); } + + public void Translate(ITranslator translator) + { + translator.Translate(ref asAssemblyName); + translator.Translate(ref asString); + translator.Translate(ref isSimpleName); + translator.Translate(ref hasProcessorArchitectureInFusionName); + translator.Translate(ref immutable); + + // TODO: consider some kind of protection against infinite loop during serialization, hint: pre serialize check for cycle in graph + translator.TranslateHashSet(ref remappedFrom, + (ITranslator t) => new AssemblyNameExtension(t), + (int capacity) => CreateRemappedFrom()); + } } } diff --git a/src/Shared/TranslatorHelpers.cs b/src/Shared/TranslatorHelpers.cs index 130ad05d9cd..c7b0481f173 100644 --- a/src/Shared/TranslatorHelpers.cs +++ b/src/Shared/TranslatorHelpers.cs @@ -1,7 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; +using System.Configuration.Assemblies; +using System.Globalization; +using System.Reflection; namespace Microsoft.Build.BackEnd { @@ -40,12 +44,12 @@ internal static class TranslatorHelpers static ObjectTranslator AdaptFactory(NodePacketValueFactory valueFactory) where T : ITranslatable { - void Translate(ITranslator translator, ref T objectToTranslate) + void TranslateUsingValueFactory(ITranslator translator, ref T objectToTranslate) { - TranslatorHelpers.Translate(translator, ref objectToTranslate, valueFactory); + translator.Translate(ref objectToTranslate, valueFactory); } - return Translate; + return TranslateUsingValueFactory; } public static void Translate( @@ -102,5 +106,164 @@ void Translate(ITranslator translator, ref T objectToTranslate) { translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory), collectionCreator); } + + public static void TranslateHashSet( + this ITranslator translator, + ref HashSet hashSet, + NodePacketValueFactory valueFactory, + NodePacketCollectionCreator> collectionFactory) where T : class, ITranslatable + { + if (!translator.TranslateNullable(hashSet)) + return; + + int count = hashSet.Count; + translator.Translate(ref count); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + hashSet = collectionFactory(count); + for (int i = 0; i < count; i++) + { + T value = default(T); + translator.Translate(ref value, valueFactory); + hashSet.Add(value); + } + } + + if (translator.Mode == TranslationDirection.WriteToStream) + { + foreach (T item in hashSet) + { + T value = item; + translator.Translate(ref value, valueFactory); + } + } + } + + public static void Translate(this ITranslator translator, ref AssemblyName assemblyName) + { + if (!translator.TranslateNullable(assemblyName)) + return; + + string name = null; + Version version = null; + AssemblyNameFlags flags = default; + ProcessorArchitecture processorArchitecture = default; + CultureInfo cultureInfo = null; + System.Configuration.Assemblies.AssemblyHashAlgorithm hashAlgorithm = default; + AssemblyVersionCompatibility versionCompatibility = default; + string codeBase = null; + + byte[] publicKey = null; + byte[] publicKeyToken = null; + + if (translator.Mode == TranslationDirection.WriteToStream) + { + name = assemblyName.Name; + version = assemblyName.Version; + flags = assemblyName.Flags; + processorArchitecture = assemblyName.ProcessorArchitecture; + cultureInfo = assemblyName.CultureInfo; + hashAlgorithm = assemblyName.HashAlgorithm; + versionCompatibility = assemblyName.VersionCompatibility; + codeBase = assemblyName.CodeBase; + + publicKey = assemblyName.GetPublicKey(); // TODO: no need to serialize, public key is not used anywhere in context of RAR, only public key token + publicKeyToken = assemblyName.GetPublicKeyToken(); + } + + translator.Translate(ref name); + translator.Translate(ref version); + translator.TranslateEnum(ref flags, (int)flags); + translator.TranslateEnum(ref processorArchitecture, (int)processorArchitecture); + translator.Translate(ref cultureInfo); + translator.TranslateEnum(ref hashAlgorithm, (int)hashAlgorithm); + translator.TranslateEnum(ref versionCompatibility, (int)versionCompatibility); + translator.Translate(ref codeBase); + + translator.Translate(ref publicKey); + translator.Translate(ref publicKeyToken); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + assemblyName = new AssemblyName + { + Name = name, + Version = version, + Flags = flags, + ProcessorArchitecture = processorArchitecture, + CultureInfo = cultureInfo, + HashAlgorithm = hashAlgorithm, + VersionCompatibility = versionCompatibility, + CodeBase = codeBase, + // AssemblyName.KeyPair is not used anywhere, additionally StrongNameKeyPair is not supported in .net core 5- + // and throws platform not supported exception when serialized or deserialized + KeyPair = null, + }; + + assemblyName.SetPublicKey(publicKey); + assemblyName.SetPublicKeyToken(publicKeyToken); + } + } + + public static void Translate(this ITranslator translator, ref CultureInfo cultureInfo) + { + if (!translator.TranslateNullable(cultureInfo)) + return; + + int lcid = default; + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + lcid = cultureInfo.LCID; + } + + translator.Translate(ref lcid); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + cultureInfo = new CultureInfo(lcid); + } + } + + public static void Translate(this ITranslator translator, ref Version version) + { + if (!translator.TranslateNullable(version)) + return; + + int major = 0; + int minor = 0; + int build = 0; + int revision = 0; + + if (translator.Mode == TranslationDirection.WriteToStream) + { + major = version.Major; + minor = version.Minor; + build = version.Build; + revision = version.Revision; + } + + translator.Translate(ref major); + translator.Translate(ref minor); + translator.Translate(ref build); + translator.Translate(ref revision); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + if (build < 0) + { + version = new Version(major, minor); + } + else if (revision < 0) + { + version = new Version(major, minor, build); + } + else + { + version = new Version(major, minor, build, revision); + } + } + } } } diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs index b7e72734291..d94d8460812 100644 --- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs +++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs @@ -1886,7 +1886,7 @@ private void LogConflict(Reference reference, string fusionName, StringBuilder l /// private void ReadStateFile() { - _cache = (SystemState)StateFileBase.DeserializeCache(_stateFile, Log, typeof(SystemState)); + _cache = SystemState.DeserializeCacheByTranslator(_stateFile, Log); // Construct the cache if necessary. if (_cache == null) @@ -1899,6 +1899,32 @@ private void ReadStateFile() /// Write out the state file if a state name was supplied and the cache is dirty. /// private void WriteStateFile() + { + if (!string.IsNullOrEmpty(_stateFile) && _cache.IsDirty) + { + _cache.SerializeCacheByTranslator(_stateFile, Log); + } + } + + /// + /// TODO: to be deleted + /// + private void ReadStateFileBinaryFormatter() + { + _cache = (SystemState)StateFileBase.DeserializeCache(_stateFile, Log, typeof(SystemState)); + + // Construct the cache if necessary. + if (_cache == null) + { + _cache = new SystemState(); + } + } + + /// + /// Write out the state file if a state name was supplied and the cache is dirty. + /// TODO: to be deleted + /// + private void WriteStateFileBinaryFormatter() { if (!string.IsNullOrEmpty(_stateFile) && _cache.IsDirty) { diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index 97daccd3aae..cb35f0b945b 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -27,6 +27,14 @@ System.Design.resources + + + + + + + + @@ -81,7 +89,7 @@ NGen.cs - + PropertyParser.cs True @@ -515,6 +523,7 @@ true + true diff --git a/src/Tasks/SystemState.cs b/src/Tasks/SystemState.cs index 1dd51c92c86..9a9d47d64ed 100644 --- a/src/Tasks/SystemState.cs +++ b/src/Tasks/SystemState.cs @@ -8,11 +8,15 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security.Permissions; +using Microsoft.Build.BackEnd; using Microsoft.Build.Shared; +using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Tasks.AssemblyDependency; +using Microsoft.Build.Utilities; namespace Microsoft.Build.Tasks { @@ -20,8 +24,11 @@ namespace Microsoft.Build.Tasks /// Class is used to cache system state. /// [Serializable] - internal sealed class SystemState : StateFileBase, ISerializable + internal sealed class SystemState : StateFileBase, ISerializable, ITranslatable { + private static readonly byte[] TranslateContractSignature = new []{(byte)'M', (byte)'B', (byte)'R', (byte)'S', (byte)'C', }; // Microsoft Build Rar State Cache + private static readonly byte TranslateContractVersion = 0x01; + /// /// Cache at the SystemState instance level. Has the same contents as . /// It acts as a flag to enforce that an entry has been checked for staleness only once. @@ -31,7 +38,7 @@ internal sealed class SystemState : StateFileBase, ISerializable /// /// Cache at the SystemState instance level. It is serialized and reused between instances. /// - private Hashtable instanceLocalFileStateCache = new Hashtable(StringComparer.OrdinalIgnoreCase); + private Dictionary instanceLocalFileStateCache = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// LastModified information is purely instance-local. It doesn't make sense to @@ -108,7 +115,7 @@ internal sealed class SystemState : StateFileBase, ISerializable /// Class that holds the current file state. /// [Serializable] - private sealed class FileState : ISerializable + private sealed class FileState : ISerializable, ITranslatable { /// /// The last modified time for this file. @@ -148,6 +155,14 @@ internal FileState(DateTime lastModified) this.lastModified = lastModified; } + /// + /// Ctor for translator deserialization + /// + internal FileState(ITranslator translator) + { + Translate(translator); + } + /// /// Deserializing constuctor. /// @@ -192,6 +207,23 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) } } + /// + /// Reads/writes this class + /// + public void Translate(ITranslator translator) + { + ErrorUtilities.VerifyThrowArgumentNull(translator, nameof(translator)); + + translator.Translate(ref lastModified); + translator.Translate(ref assemblyName, + (ITranslator t) => new AssemblyNameExtension(t)); + translator.TranslateArray(ref dependencies, + (ITranslator t) => new AssemblyNameExtension(t)); + translator.Translate(ref scatterFiles); + translator.Translate(ref runtimeVersion); + translator.Translate(ref frameworkName); + } + /// /// Gets the last modified date. /// @@ -246,7 +278,14 @@ internal SystemState(SerializationInfo info, StreamingContext context) { ErrorUtilities.VerifyThrowArgumentNull(info, nameof(info)); - instanceLocalFileStateCache = (Hashtable)info.GetValue("fileState", typeof(Hashtable)); + var localFilesAsHashTable = (Hashtable)info.GetValue("fileState", typeof(Hashtable)); + + instanceLocalFileStateCache = localFilesAsHashTable.Cast() + .ToDictionary( + kvp => (string)kvp.Key, + kvp => (FileState)kvp.Value, + StringComparer.OrdinalIgnoreCase); + isDirty = false; } @@ -264,6 +303,93 @@ AssemblyTableInfo[] installedAssemblyTableInfos redistList = RedistList.GetRedistList(installedAssemblyTableInfos); } + /// + /// Writes the contents of this object out to the specified file. + /// TODO: once all derived classes from StateFileBase adopt new serialization, we shall consider to mode this into base class + /// + internal void SerializeCacheByTranslator(string stateFile, TaskLoggingHelper log) + { + try + { + if (!string.IsNullOrEmpty(stateFile)) + { + if (FileSystems.Default.FileExists(stateFile)) + { + File.Delete(stateFile); + } + + using var s = new FileStream(stateFile, FileMode.CreateNew); + var translator = BinaryTranslator.GetWriteTranslator(s); + + // write file signature + translator.Writer.Write(TranslateContractSignature); + translator.Writer.Write(TranslateContractVersion); + + Translate(translator); + } + } + catch (Exception e) + { + // If there was a problem writing the file (like it's read-only or locked on disk, for + // example), then eat the exception and log a warning. Otherwise, rethrow. + if (ExceptionHandling.NotExpectedSerializationException(e)) + throw; + + // Not being able to serialize the cache is not an error, but we let the user know anyway. + // Don't want to hold up processing just because we couldn't read the file. + log.LogWarningWithCodeFromResources("General.CouldNotWriteStateFile", stateFile, e.Message); + } + } + + /// + /// Read the contents of this object out to the specified file. + /// TODO: once all derived classes from StateFileBase adopt new serialization, we shall consider to mode this into base class + /// + internal static SystemState DeserializeCacheByTranslator(string stateFile, TaskLoggingHelper log) + { + // First, we read the cache from disk if one exists, or if one does not exist + // then we create one. + try + { + if (!string.IsNullOrEmpty(stateFile) && FileSystems.Default.FileExists(stateFile)) + { + using FileStream s = new FileStream(stateFile, FileMode.Open); + var translator = BinaryTranslator.GetReadTranslator(s, buffer:null); // TODO: shared buffering? + + // verify file signature + var contractSignature = translator.Reader.ReadBytes(TranslateContractSignature.Length); + var contractVersion = translator.Reader.ReadByte(); + + if (!contractSignature.SequenceEqual(TranslateContractSignature) || contractVersion != TranslateContractVersion) + { + log.LogMessageFromResources("General.CouldNotReadStateFileMessage", stateFile, log.FormatResourceString("General.IncompatibleStateFileType")); + return null; + } + + SystemState systemState = new SystemState(); + systemState.Translate(translator); + + return systemState; + } + } + catch (Exception e) + { + if (ExceptionHandling.IsCriticalException(e)) + { + throw; + } + + // The deserialization process seems like it can throw just about + // any exception imaginable. Catch them all here. + // Not being able to deserialize the cache is not an error, but we let the user know anyway. + // Don't want to hold up processing just because we couldn't read the file. + log.LogWarningWithCodeFromResources("General.CouldNotReadStateFile", stateFile, e.Message); + return null; + } + + return null; + } + /// /// Serialize the contents of the class. /// @@ -272,7 +398,24 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) { ErrorUtilities.VerifyThrowArgumentNull(info, nameof(info)); - info.AddValue("fileState", instanceLocalFileStateCache); + var localFilesAsHashtable = new Hashtable(); + foreach (var pair in instanceLocalFileStateCache) + { + localFilesAsHashtable.Add(pair.Key, pair.Value); + } + + info.AddValue("fileState", localFilesAsHashtable); + } + + public void Translate(ITranslator translator) + { + if (instanceLocalFileStateCache is null) + throw new NullReferenceException(nameof(instanceLocalFileStateCache)); + + translator.TranslateDictionary( + ref instanceLocalFileStateCache, + StringComparer.OrdinalIgnoreCase, + (ITranslator t) => new FileState(t)); } /// @@ -378,10 +521,8 @@ private FileState GetFileState(string path) private FileState ComputeFileStateFromCachesAndDisk(string path) { DateTime lastModified = GetAndCacheLastModified(path); - FileState cachedInstanceFileState = (FileState)instanceLocalFileStateCache[path]; - bool isCachedInInstance = cachedInstanceFileState != null; - bool isCachedInProcess = - s_processWideFileStateCache.TryGetValue(path, out FileState cachedProcessFileState); + bool isCachedInInstance = instanceLocalFileStateCache.TryGetValue(path, out FileState cachedInstanceFileState); + bool isCachedInProcess = s_processWideFileStateCache.TryGetValue(path, out FileState cachedProcessFileState); bool isInstanceFileStateUpToDate = isCachedInInstance && lastModified == cachedInstanceFileState.LastModified; bool isProcessFileStateUpToDate = isCachedInProcess && lastModified == cachedProcessFileState.LastModified; diff --git a/src/Tasks/TaskTranslatorHelpers.cs b/src/Tasks/TaskTranslatorHelpers.cs new file mode 100644 index 00000000000..1d4b58a7ede --- /dev/null +++ b/src/Tasks/TaskTranslatorHelpers.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.Versioning; +using Microsoft.Build.BackEnd; + +namespace Microsoft.Build.Tasks +{ + internal static class TaskTranslatorHelpers + { + public static void Translate(this ITranslator translator, ref FrameworkName frameworkName) + { + if (!translator.TranslateNullable(frameworkName)) + return; + + string identifier = null; + Version version = null; + string profile = null; + + if (translator.Mode == TranslationDirection.WriteToStream) + { + identifier = frameworkName.Identifier; + version = frameworkName.Version; + profile = frameworkName.Profile; + } + + translator.Translate(ref identifier); + translator.Translate(ref version); + translator.Translate(ref profile); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + frameworkName = new FrameworkName(identifier, version, profile); + } + } + } +} From a1f100133a47c8d583ed03400ee399186443ff1e Mon Sep 17 00:00:00 2001 From: Roman Konecny Date: Wed, 27 Jan 2021 23:14:49 +0100 Subject: [PATCH 2/8] Unit tests part 1 --- scripts/Deploy-MSBuild.ps1 | 2 +- .../BackEnd/BinaryTranslator_Tests.cs | 212 ++++++++++++++++-- src/Shared/TranslatorHelpers.cs | 126 ++++++----- ...olveAssemblyReferenceCacheSerialization.cs | 39 ++++ .../Microsoft.Build.Tasks.UnitTests.csproj | 1 - src/Tasks/SystemState.cs | 2 +- 6 files changed, 303 insertions(+), 79 deletions(-) create mode 100644 src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs diff --git a/scripts/Deploy-MSBuild.ps1 b/scripts/Deploy-MSBuild.ps1 index 47eec2ccff8..b7bf08ced01 100644 --- a/scripts/Deploy-MSBuild.ps1 +++ b/scripts/Deploy-MSBuild.ps1 @@ -48,7 +48,7 @@ Write-Host "Existing MSBuild assemblies backed up to $BackupFolder" if ($runtime -eq "Desktop") { $targetFramework = "net472" } else { - $targetFramework = "netcoreapp2.1" + $targetFramework = "net5.0" } $bootstrapBinDirectory = "artifacts\bin\MSBuild.Bootstrap\$configuration\$targetFramework" diff --git a/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs b/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs index 87c18c4b5b9..950e93323a5 100644 --- a/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.Configuration.Assemblies; +using System.Globalization; using Microsoft.Build.BackEnd; using System.IO; +using System.Reflection; using Xunit; namespace Microsoft.Build.UnitTests.BackEnd @@ -45,8 +48,8 @@ public void TestSerializeBool() public void TestSerializeByte() { byte val = 0x55; - HelperTestSimpleType((byte)0, val); - HelperTestSimpleType(val, (byte)0); + HelperTestSimpleType((byte) 0, val); + HelperTestSimpleType(val, (byte) 0); } /// @@ -56,8 +59,8 @@ public void TestSerializeByte() public void TestSerializeShort() { short val = 0x55AA; - HelperTestSimpleType((short)0, val); - HelperTestSimpleType(val, (short)0); + HelperTestSimpleType((short) 0, val); + HelperTestSimpleType(val, (short) 0); } /// @@ -67,8 +70,8 @@ public void TestSerializeShort() public void TestSerializeLong() { long val = 0x55AABBCCDDEE; - HelperTestSimpleType((long)0, val); - HelperTestSimpleType(val, (long)0); + HelperTestSimpleType((long) 0, val); + HelperTestSimpleType(val, (long) 0); } /// @@ -78,8 +81,8 @@ public void TestSerializeLong() public void TestSerializeDouble() { double val = 3.1416; - HelperTestSimpleType((double)0, val); - HelperTestSimpleType(val, (double)0); + HelperTestSimpleType((double) 0, val); + HelperTestSimpleType(val, (double) 0); } /// @@ -100,8 +103,8 @@ public void TestSerializeTimeSpan() public void TestSerializeInt() { int val = 0x55AA55AA; - HelperTestSimpleType((int)0, val); - HelperTestSimpleType(val, (int)0); + HelperTestSimpleType((int) 0, val); + HelperTestSimpleType(val, (int) 0); } /// @@ -122,7 +125,7 @@ public void TestSerializeString() public void TestSerializeStringArray() { HelperTestArray(new string[] { }, StringComparer.Ordinal); - HelperTestArray(new string[] { "foo", "bar" }, StringComparer.Ordinal); + HelperTestArray(new string[] {"foo", "bar"}, StringComparer.Ordinal); HelperTestArray(null, StringComparer.Ordinal); } @@ -157,10 +160,10 @@ public void TestSerializeDateTime() public void TestSerializeEnum() { TranslationDirection value = TranslationDirection.ReadFromStream; - TranslationHelpers.GetWriteTranslator().TranslateEnum(ref value, (int)value); + TranslationHelpers.GetWriteTranslator().TranslateEnum(ref value, (int) value); TranslationDirection deserializedValue = TranslationDirection.WriteToStream; - TranslationHelpers.GetReadTranslator().TranslateEnum(ref deserializedValue, (int)deserializedValue); + TranslationHelpers.GetReadTranslator().TranslateEnum(ref deserializedValue, (int) deserializedValue); Assert.Equal(value, deserializedValue); } @@ -262,7 +265,7 @@ public void TestSerializeWithFactoryNull() [Fact] public void TestSerializeArray() { - DerivedClass[] value = new DerivedClass[] { new DerivedClass(1, 2), new DerivedClass(3, 4) }; + DerivedClass[] value = new DerivedClass[] {new DerivedClass(1, 2), new DerivedClass(3, 4)}; TranslationHelpers.GetWriteTranslator().TranslateArray(ref value); DerivedClass[] deserializedValue = null; @@ -292,7 +295,7 @@ public void TestSerializeArrayNull() [Fact] public void TestSerializeArrayWithFactory() { - BaseClass[] value = new BaseClass[] { new BaseClass(1), new BaseClass(2) }; + BaseClass[] value = new BaseClass[] {new BaseClass(1), new BaseClass(2)}; TranslationHelpers.GetWriteTranslator().TranslateArray(ref value, BaseClass.FactoryForDeserialization); BaseClass[] deserializedValue = null; @@ -431,6 +434,163 @@ public void TestSerializeDictionaryStringTNoComparerNull() Assert.Equal(value, deserializedValue); } + [Theory] + [InlineData("en")] + [InlineData("en-US")] + [InlineData("en-CA")] + [InlineData("zh-HK")] + [InlineData("sr-Cyrl-CS")] + public void CultureInfo(string name) + { + CultureInfo value = new CultureInfo(name); + TranslationHelpers.GetWriteTranslator().Translate(ref value); + + CultureInfo deserializedValue = null; + TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); + + Assert.Equal(value, deserializedValue); + } + + [Fact] + public void CultureInfoAsNull() + { + CultureInfo value = null; + TranslationHelpers.GetWriteTranslator().Translate(ref value); + + CultureInfo deserializedValue = null; + TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); + + Assert.Null(deserializedValue); + } + + [Theory] + [InlineData("1.2")] + [InlineData("1.2.3")] + [InlineData("1.2.3.4")] + public void Version(string version) + { + Version value = new Version(version); + TranslationHelpers.GetWriteTranslator().Translate(ref value); + + Version deserializedValue = null; + TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); + + Assert.Equal(value, deserializedValue); + } + + [Fact] + public void VersionAsNull() + { + Version value = null; + TranslationHelpers.GetWriteTranslator().Translate(ref value); + + Version deserializedValue = null; + TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); + + Assert.Null(deserializedValue); + } + + [Fact] + public void HashSetOfT() + { + HashSet value = new() + { + new BaseClass(1), + new BaseClass(2), + null + }; + TranslationHelpers.GetWriteTranslator().TranslateHashSet(ref value, BaseClass.FactoryForDeserialization, capacity => new ()); + + HashSet deserializedValue = null; + TranslationHelpers.GetReadTranslator().TranslateHashSet(ref deserializedValue, BaseClass.FactoryForDeserialization, capacity => new ()); + + Assert.Equal(value, deserializedValue, BaseClass.EqualityComparer); + } + + [Fact] + public void HashSetOfTAsNull() + { + HashSet value = null; + TranslationHelpers.GetWriteTranslator().TranslateHashSet(ref value, BaseClass.FactoryForDeserialization, capacity => new()); + + HashSet deserializedValue = null; + TranslationHelpers.GetReadTranslator().TranslateHashSet(ref deserializedValue, BaseClass.FactoryForDeserialization, capacity => new()); + + Assert.Null(deserializedValue); + } + + [Fact] + public void AssemblyNameAsNull() + { + AssemblyName value = null; + TranslationHelpers.GetWriteTranslator().Translate(ref value); + + AssemblyName deserializedValue = null; + TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); + + Assert.Null(deserializedValue); + } + + [Fact] + public void AssemblyNameWithAllFields() + { + AssemblyName value = new() + { + Name = "a", + Version = new Version(1, 2, 3), + Flags = AssemblyNameFlags.PublicKey, + ProcessorArchitecture = ProcessorArchitecture.X86, + CultureInfo = new CultureInfo("zh-HK"), + HashAlgorithm = System.Configuration.Assemblies.AssemblyHashAlgorithm.SHA256, + VersionCompatibility = AssemblyVersionCompatibility.SameMachine, + CodeBase = "C:\\src", + KeyPair = new StrongNameKeyPair(new byte[] { 4, 3, 2, 1 }), + ContentType = AssemblyContentType.WindowsRuntime, + CultureName = "zh-HK", + }; + value.SetPublicKey(new byte[]{ 3, 2, 1}); + value.SetPublicKeyToken(new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 }); + + TranslationHelpers.GetWriteTranslator().Translate(ref value); + + AssemblyName deserializedValue = null; + TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); + + HelperAssertAssemblyNameEqual(value, deserializedValue); + } + + [Fact] + public void AssemblyNameWithMinimalFields() + { + AssemblyName value = new(); + + TranslationHelpers.GetWriteTranslator().Translate(ref value); + + AssemblyName deserializedValue = null; + TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); + + HelperAssertAssemblyNameEqual(value, deserializedValue); + } + + /// + /// Assert two AssemblyName objects values are same. + /// Ignoring KeyPair, ContentType, CultureName as those are not serialized + /// + private static void HelperAssertAssemblyNameEqual(AssemblyName expected, AssemblyName actual) + { + Assert.Equal(expected.Name, actual.Name); + Assert.Equal(expected.Version, actual.Version); + Assert.Equal(expected.Flags, actual.Flags); + Assert.Equal(expected.ProcessorArchitecture, actual.ProcessorArchitecture); + Assert.Equal(expected.CultureInfo, actual.CultureInfo); + Assert.Equal(expected.HashAlgorithm, actual.HashAlgorithm); + Assert.Equal(expected.VersionCompatibility, actual.VersionCompatibility); + Assert.Equal(expected.CodeBase, actual.CodeBase); + + Assert.Equal(expected.GetPublicKey(), actual.GetPublicKey()); + Assert.Equal(expected.GetPublicKeyToken(), actual.GetPublicKeyToken()); + } + /// /// Helper for bool serialization. /// @@ -618,6 +778,11 @@ static public IComparer Comparer get { return new BaseClassComparer(); } } + static public IEqualityComparer EqualityComparer + { + get { return new BaseClassEqualityComparer(); } + } + /// /// Gets the value. /// @@ -676,6 +841,23 @@ public int Compare(BaseClass x, BaseClass y) } #endregion } + + private class BaseClassEqualityComparer : IEqualityComparer + { + public bool Equals(BaseClass x, BaseClass y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + if (x.GetType() != y.GetType()) return false; + return x._baseValue == y._baseValue; + } + + public int GetHashCode(BaseClass obj) + { + return obj._baseValue; + } + } } /// diff --git a/src/Shared/TranslatorHelpers.cs b/src/Shared/TranslatorHelpers.cs index c7b0481f173..5524d802659 100644 --- a/src/Shared/TranslatorHelpers.cs +++ b/src/Shared/TranslatorHelpers.cs @@ -116,7 +116,11 @@ void TranslateUsingValueFactory(ITranslator translator, ref T objectToTranslate) if (!translator.TranslateNullable(hashSet)) return; - int count = hashSet.Count; + int count = default; + if (translator.Mode == TranslationDirection.WriteToStream) + { + count = hashSet.Count; + } translator.Translate(ref count); if (translator.Mode == TranslationDirection.ReadFromStream) @@ -140,6 +144,66 @@ void TranslateUsingValueFactory(ITranslator translator, ref T objectToTranslate) } } + public static void Translate(this ITranslator translator, ref CultureInfo cultureInfo) + { + if (!translator.TranslateNullable(cultureInfo)) + return; + + int lcid = default; + + if (translator.Mode == TranslationDirection.WriteToStream) + { + lcid = cultureInfo.LCID; + } + + translator.Translate(ref lcid); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + cultureInfo = new CultureInfo(lcid); + } + } + + public static void Translate(this ITranslator translator, ref Version version) + { + if (!translator.TranslateNullable(version)) + return; + + int major = 0; + int minor = 0; + int build = 0; + int revision = 0; + + if (translator.Mode == TranslationDirection.WriteToStream) + { + major = version.Major; + minor = version.Minor; + build = version.Build; + revision = version.Revision; + } + + translator.Translate(ref major); + translator.Translate(ref minor); + translator.Translate(ref build); + translator.Translate(ref revision); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + if (build < 0) + { + version = new Version(major, minor); + } + else if (revision < 0) + { + version = new Version(major, minor, build); + } + else + { + version = new Version(major, minor, build, revision); + } + } + } + public static void Translate(this ITranslator translator, ref AssemblyName assemblyName) { if (!translator.TranslateNullable(assemblyName)) @@ -205,65 +269,5 @@ public static void Translate(this ITranslator translator, ref AssemblyName assem assemblyName.SetPublicKeyToken(publicKeyToken); } } - - public static void Translate(this ITranslator translator, ref CultureInfo cultureInfo) - { - if (!translator.TranslateNullable(cultureInfo)) - return; - - int lcid = default; - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - lcid = cultureInfo.LCID; - } - - translator.Translate(ref lcid); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - cultureInfo = new CultureInfo(lcid); - } - } - - public static void Translate(this ITranslator translator, ref Version version) - { - if (!translator.TranslateNullable(version)) - return; - - int major = 0; - int minor = 0; - int build = 0; - int revision = 0; - - if (translator.Mode == TranslationDirection.WriteToStream) - { - major = version.Major; - minor = version.Minor; - build = version.Build; - revision = version.Revision; - } - - translator.Translate(ref major); - translator.Translate(ref minor); - translator.Translate(ref build); - translator.Translate(ref revision); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - if (build < 0) - { - version = new Version(major, minor); - } - else if (revision < 0) - { - version = new Version(major, minor, build); - } - else - { - version = new Version(major, minor, build, revision); - } - } - } } } diff --git a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs new file mode 100644 index 00000000000..fa1585b557a --- /dev/null +++ b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using Microsoft.Build.Tasks; +using Microsoft.Build.Utilities; +using Xunit; + +namespace Microsoft.Build.UnitTests.ResolveAssemblyReference_Tests +{ + public class ResolveAssemblyReferenceCacheSerialization + { + [Fact] + public void RoundTripEmptyState() + { + string rarCacheFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".UnitTest.RarCache"); + var taskLoggingHelper = new TaskLoggingHelper(new MockEngine(), "TaskA"); + + SystemState systemState = new(); + + systemState.SerializeCacheByTranslator(rarCacheFile, taskLoggingHelper); + + var deserialized = SystemState.DeserializeCacheByTranslator(rarCacheFile, taskLoggingHelper); + + Assert.NotNull(deserialized); + } + + [Fact] + public void RoundTripFullFileState() + { + // read old file + // white as TR + // read as TR + // write as BF + // compare old and new BF + + string rarCacheFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".UnitTest.RarCache"); + var taskLoggingHelper = new TaskLoggingHelper(new MockEngine(), "TaskA"); + } + } +} diff --git a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj index eab4bd23cd8..406ce641895 100644 --- a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj +++ b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj @@ -36,7 +36,6 @@ - true diff --git a/src/Tasks/SystemState.cs b/src/Tasks/SystemState.cs index 9a9d47d64ed..9b289dfd75e 100644 --- a/src/Tasks/SystemState.cs +++ b/src/Tasks/SystemState.cs @@ -322,7 +322,7 @@ internal void SerializeCacheByTranslator(string stateFile, TaskLoggingHelper log var translator = BinaryTranslator.GetWriteTranslator(s); // write file signature - translator.Writer.Write(TranslateContractSignature); + translator.Writer.Write(TranslateContractSignature); translator.Writer.Write(TranslateContractVersion); Translate(translator); From 292e88541c99663e5867e8d55d40d43339a228b2 Mon Sep 17 00:00:00 2001 From: Roman Konecny Date: Tue, 2 Feb 2021 09:41:01 +0100 Subject: [PATCH 3/8] Intermediate version with BinaryFormatter still in and special unit tests. --- eng/Packages.props | 1 + ...olveAssemblyReferenceCacheSerialization.cs | 220 +++++++++++++++++- .../Microsoft.Build.Tasks.UnitTests.csproj | 16 ++ .../ResolveAssemblyReference.cs | 8 +- src/Tasks/SystemState.cs | 35 ++- 5 files changed, 255 insertions(+), 25 deletions(-) diff --git a/eng/Packages.props b/eng/Packages.props index 30ae007906c..2f3f2796438 100644 --- a/eng/Packages.props +++ b/eng/Packages.props @@ -11,6 +11,7 @@ + diff --git a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs index fa1585b557a..4c7f04f2b13 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs @@ -1,39 +1,237 @@ using System; using System.IO; +using System.Reflection; +using System.Runtime.Versioning; +using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; +using Newtonsoft.Json; using Xunit; namespace Microsoft.Build.UnitTests.ResolveAssemblyReference_Tests { - public class ResolveAssemblyReferenceCacheSerialization + public class ResolveAssemblyReferenceCacheSerialization : IDisposable { + // Maintain this two in sync with the constant in SystemState + private static readonly byte[] TranslateContractSignature = new[] { (byte)'M', (byte)'B', (byte)'R', (byte)'S', (byte)'C', }; // Microsoft Build Rar State Cache + private static readonly byte TranslateContractVersion = 0x01; + + private string _tempPath; + private string _rarCacheFile; + private TaskLoggingHelper _taskLoggingHelper; + + public ResolveAssemblyReferenceCacheSerialization() + { + _tempPath = Path.GetTempPath(); + _rarCacheFile = Path.Combine(_tempPath, Guid.NewGuid() + ".UnitTest.RarCache"); + _taskLoggingHelper = new TaskLoggingHelper(new MockEngine(), "TaskA") + { + TaskResources = AssemblyResources.PrimaryResources + }; + } + + public void Dispose() + { + if (File.Exists(_rarCacheFile)) + { + FileUtilities.DeleteNoThrow(_rarCacheFile); + } + } + [Fact] public void RoundTripEmptyState() { - string rarCacheFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".UnitTest.RarCache"); - var taskLoggingHelper = new TaskLoggingHelper(new MockEngine(), "TaskA"); - SystemState systemState = new(); - systemState.SerializeCacheByTranslator(rarCacheFile, taskLoggingHelper); + systemState.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); - var deserialized = SystemState.DeserializeCacheByTranslator(rarCacheFile, taskLoggingHelper); + var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); Assert.NotNull(deserialized); } [Fact] - public void RoundTripFullFileState() + public void WrongFileSignature() + { + SystemState systemState = new(); + + for (int i = 0; i < TranslateContractSignature.Length; i++) + { + systemState.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); + using (var cacheStream = new FileStream(_rarCacheFile, FileMode.Open, FileAccess.ReadWrite)) + { + cacheStream.Seek(i, SeekOrigin.Begin); + cacheStream.WriteByte(0); + cacheStream.Close(); + } + + var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); + Assert.Null(deserialized); + } + } + + [Fact] + public void WrongFileVersion() + { + SystemState systemState = new(); + + systemState.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); + using (var cacheStream = new FileStream(_rarCacheFile, FileMode.Open, FileAccess.ReadWrite)) + { + cacheStream.Seek(TranslateContractSignature.Length, SeekOrigin.Begin); + cacheStream.WriteByte((byte) (TranslateContractVersion + 1)); + cacheStream.Close(); + } + + var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); + Assert.Null(deserialized); + } + + [Fact] + public void CorrectFileSignature() + { + SystemState systemState = new(); + + for (int i = 0; i < TranslateContractSignature.Length; i++) + { + systemState.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); + using (var cacheStream = new FileStream(_rarCacheFile, FileMode.Open, FileAccess.ReadWrite)) + { + cacheStream.Seek(i, SeekOrigin.Begin); + cacheStream.WriteByte(TranslateContractSignature[i]); + cacheStream.Close(); + } + + var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); + Assert.NotNull(deserialized); + } + } + + [Fact] + public void CorrectFileVersion() { + SystemState systemState = new(); + + systemState.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); + using (var cacheStream = new FileStream(_rarCacheFile, FileMode.Open, FileAccess.ReadWrite)) + { + cacheStream.Seek(TranslateContractSignature.Length, SeekOrigin.Begin); + cacheStream.WriteByte(TranslateContractVersion); + cacheStream.Close(); + } + + var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); + Assert.NotNull(deserialized); + } + + [Theory] + [InlineData("Microsoft.VisualStudio.LanguageServices.Implementation.csprojAssemblyReference.cache")] + [InlineData("Microsoft.CodeAnalysis.csprojAssemblyReference.cache")] + [InlineData("Microsoft.CodeAnalysis.VisualBasic.Emit.UnitTests.vbprojAssemblyReference.cache")] + [InlineData("Roslyn.Compilers.Extension.csprojAssemblyReference.cache")] + public void RoundTripSampleFileState(string sampleName) + { + var fileSample = GetTestPayloadFileName($@"AssemblyDependency\CacheFileSamples\BinaryFormatter\{sampleName}"); // read old file + var deserialized = SystemState.DeserializeCacheByBinaryFormatter(fileSample, _taskLoggingHelper); // white as TR + deserialized.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); // read as TR - // write as BF - // compare old and new BF + var deserializedByTranslator = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); + + var sOld = JsonConvert.SerializeObject(deserialized, Formatting.Indented); + var sNew = JsonConvert.SerializeObject(deserializedByTranslator, Formatting.Indented); + Assert.Equal(sOld, sNew); + } - string rarCacheFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".UnitTest.RarCache"); - var taskLoggingHelper = new TaskLoggingHelper(new MockEngine(), "TaskA"); + [Fact] + public void VerifySampleStateDeserialization() + { + // This test might also fail when binary format is modified. + // Any change in SystemState and child class ITranslatable implementation will most probably make this fail. + // To fix it, file referred by 'sampleName' needs to be recaptured and constant bellow modified to reflect + // the content of that cache. + // This sample was captured by compiling https://github.com/dotnet/roslyn/commit/f8107de2a94a01e96ac3d7c1f225acbb61e18830 + const string sampleName = "Microsoft.VisualStudio.LanguageServices.Implementation.csprojAssemblyReference.cache"; + const string expectedAssemblyPath = @"C:\Users\rokon\.nuget\packages\microsoft.visualstudio.codeanalysis.sdk.ui\15.8.27812-alpha\lib\net46\Microsoft.VisualStudio.CodeAnalysis.Sdk.UI.dll"; + const long expectedAssemblyLastWriteTimeTicks = 636644382480000000; + const string expectedAssemblyName = "Microsoft.VisualStudio.CodeAnalysis.Sdk.UI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; + const string expectedFrameworkName = ".NETFramework,Version=v4.5"; + var expectedDependencies = new[] + { + "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.CodeAnalysis, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.DeveloperTools, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.CodeAnalysis.Sdk, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.Build.Framework, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.Text.Logic, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.Text.UI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.Text.Data, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.Text.UI.Wpf, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.ComponentModelHost, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.VSHelp, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.Shell.Interop.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.VCProjectEngine, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.Shell.15.0, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "EnvDTE80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "Microsoft.VisualStudio.VirtualTreeGrid, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + "Microsoft.VisualStudio.Editor, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", + }; + + + var fileSample = GetTestPayloadFileName($@"AssemblyDependency\CacheFileSamples\{sampleName}"); + var deserializedByTranslator = SystemState.DeserializeCacheByTranslator(fileSample, _taskLoggingHelper); + + deserializedByTranslator.SetGetLastWriteTime(path => + { + if (path != expectedAssemblyPath) + throw new InvalidOperationException("Unexpected file name for this test case"); + + return new DateTime(expectedAssemblyLastWriteTimeTicks, DateTimeKind.Utc); + }); + + GetAssemblyName getAssemblyName = deserializedByTranslator.CacheDelegate((GetAssemblyName)null); + GetAssemblyMetadata getAssemblyMetadata = deserializedByTranslator.CacheDelegate((GetAssemblyMetadata)null); + + var assemblyName = getAssemblyName(expectedAssemblyPath); + getAssemblyMetadata(expectedAssemblyPath, null, + out AssemblyNameExtension[] dependencies, + out string[] scatterFiles, + out FrameworkName frameworkNameAttribute); + + Assert.NotNull(assemblyName); + Assert.Equal( + new AssemblyNameExtension(expectedAssemblyName, false), + assemblyName); + Assert.Empty(scatterFiles); + Assert.Equal( + new FrameworkName(expectedFrameworkName), + frameworkNameAttribute); + + Assert.NotNull(dependencies); + Assert.Equal(expectedDependencies.Length, dependencies.Length); + foreach (var expectedDependency in expectedDependencies) + { + Assert.Contains(new AssemblyNameExtension(expectedDependency), dependencies); + } + } + + private static string GetTestPayloadFileName(string name) + { + var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().CodeBase); + var codeBasePath = Uri.UnescapeDataString(codeBaseUrl.AbsolutePath); + var dirPath = Path.GetDirectoryName(codeBasePath) ?? string.Empty; + return Path.Combine(dirPath, name); } } } diff --git a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj index 406ce641895..8f337f1b61e 100644 --- a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj +++ b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj @@ -12,6 +12,7 @@ + @@ -138,6 +139,21 @@ + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs index d94d8460812..b987cebdd6b 100644 --- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs +++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs @@ -1911,13 +1911,7 @@ private void WriteStateFile() /// private void ReadStateFileBinaryFormatter() { - _cache = (SystemState)StateFileBase.DeserializeCache(_stateFile, Log, typeof(SystemState)); - - // Construct the cache if necessary. - if (_cache == null) - { - _cache = new SystemState(); - } + _cache = SystemState.DeserializeCacheByBinaryFormatter(_stateFile, Log); } /// diff --git a/src/Tasks/SystemState.cs b/src/Tasks/SystemState.cs index 9b289dfd75e..f89eb5b239e 100644 --- a/src/Tasks/SystemState.cs +++ b/src/Tasks/SystemState.cs @@ -39,6 +39,7 @@ internal sealed class SystemState : StateFileBase, ISerializable, ITranslatable /// Cache at the SystemState instance level. It is serialized and reused between instances. /// private Dictionary instanceLocalFileStateCache = new Dictionary(StringComparer.OrdinalIgnoreCase); + private Hashtable instanceLocalFileStateCacheForBfDeserialize = new Hashtable(StringComparer.OrdinalIgnoreCase); /// /// LastModified information is purely instance-local. It doesn't make sense to @@ -278,17 +279,37 @@ internal SystemState(SerializationInfo info, StreamingContext context) { ErrorUtilities.VerifyThrowArgumentNull(info, nameof(info)); - var localFilesAsHashTable = (Hashtable)info.GetValue("fileState", typeof(Hashtable)); - - instanceLocalFileStateCache = localFilesAsHashTable.Cast() - .ToDictionary( - kvp => (string)kvp.Key, - kvp => (FileState)kvp.Value, - StringComparer.OrdinalIgnoreCase); + instanceLocalFileStateCacheForBfDeserialize = (Hashtable)info.GetValue("fileState", typeof(Hashtable)); isDirty = false; } + /// + /// Deserialize cache of this class using BinaryFormatter + /// + internal static SystemState DeserializeCacheByBinaryFormatter(string stateFile, TaskLoggingHelper log) + { + SystemState systemSate = (SystemState)StateFileBase.DeserializeCache(stateFile, log, typeof(SystemState)); + + // Construct the cache if necessary. + if (systemSate == null) + { + systemSate = new SystemState(); + } + + if (systemSate.instanceLocalFileStateCacheForBfDeserialize != null) + { + foreach (DictionaryEntry entry in systemSate.instanceLocalFileStateCacheForBfDeserialize) + { + systemSate.instanceLocalFileStateCache.Add((string)entry.Key, (FileState)entry.Value); + } + + systemSate.instanceLocalFileStateCacheForBfDeserialize = null; + } + + return systemSate; + } + /// /// Set the target framework paths. /// This is used to optimize IO in the case of files requested from one From 44e685ed63b1f87d7f4acf786a5cb7df9c8367f6 Mon Sep 17 00:00:00 2001 From: Roman Konecny Date: Tue, 2 Feb 2021 23:44:43 +0100 Subject: [PATCH 4/8] Cleaning old serialization code. Additional unit tests. --- eng/Packages.props | 1 - .../BackEnd/BinaryTranslator_Tests.cs | 30 ++--- src/Shared/AssemblyNameExtension.cs | 4 + src/Shared/TranslatorHelpers.cs | 3 +- src/Shared/UnitTests/AssemblyNameEx_Tests.cs | 51 +++++++- ...olveAssemblyReferenceCacheSerialization.cs | 34 +----- .../TaskTranslatorHelpers.cs | 63 ++++++++++ .../Microsoft.Build.Tasks.UnitTests.csproj | 13 -- .../ResolveAssemblyReference.cs | 20 ---- src/Tasks/Microsoft.Build.Tasks.csproj | 2 +- src/Tasks/SystemState.cs | 111 ++---------------- 11 files changed, 147 insertions(+), 185 deletions(-) create mode 100644 src/Tasks.UnitTests/AssemblyDependency/TaskTranslatorHelpers.cs diff --git a/eng/Packages.props b/eng/Packages.props index 2f3f2796438..30ae007906c 100644 --- a/eng/Packages.props +++ b/eng/Packages.props @@ -11,7 +11,6 @@ - diff --git a/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs b/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs index 950e93323a5..ae1d70e5aa7 100644 --- a/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs @@ -48,8 +48,8 @@ public void TestSerializeBool() public void TestSerializeByte() { byte val = 0x55; - HelperTestSimpleType((byte) 0, val); - HelperTestSimpleType(val, (byte) 0); + HelperTestSimpleType((byte)0, val); + HelperTestSimpleType(val, (byte)0); } /// @@ -59,8 +59,8 @@ public void TestSerializeByte() public void TestSerializeShort() { short val = 0x55AA; - HelperTestSimpleType((short) 0, val); - HelperTestSimpleType(val, (short) 0); + HelperTestSimpleType((short)0, val); + HelperTestSimpleType(val, (short)0); } /// @@ -70,8 +70,8 @@ public void TestSerializeShort() public void TestSerializeLong() { long val = 0x55AABBCCDDEE; - HelperTestSimpleType((long) 0, val); - HelperTestSimpleType(val, (long) 0); + HelperTestSimpleType((long)0, val); + HelperTestSimpleType(val, (long)0); } /// @@ -81,8 +81,8 @@ public void TestSerializeLong() public void TestSerializeDouble() { double val = 3.1416; - HelperTestSimpleType((double) 0, val); - HelperTestSimpleType(val, (double) 0); + HelperTestSimpleType((double)0, val); + HelperTestSimpleType(val, (double)0); } /// @@ -103,8 +103,8 @@ public void TestSerializeTimeSpan() public void TestSerializeInt() { int val = 0x55AA55AA; - HelperTestSimpleType((int) 0, val); - HelperTestSimpleType(val, (int) 0); + HelperTestSimpleType((int)0, val); + HelperTestSimpleType(val, (int)0); } /// @@ -125,7 +125,7 @@ public void TestSerializeString() public void TestSerializeStringArray() { HelperTestArray(new string[] { }, StringComparer.Ordinal); - HelperTestArray(new string[] {"foo", "bar"}, StringComparer.Ordinal); + HelperTestArray(new string[] { "foo", "bar" }, StringComparer.Ordinal); HelperTestArray(null, StringComparer.Ordinal); } @@ -160,10 +160,10 @@ public void TestSerializeDateTime() public void TestSerializeEnum() { TranslationDirection value = TranslationDirection.ReadFromStream; - TranslationHelpers.GetWriteTranslator().TranslateEnum(ref value, (int) value); + TranslationHelpers.GetWriteTranslator().TranslateEnum(ref value, (int)value); TranslationDirection deserializedValue = TranslationDirection.WriteToStream; - TranslationHelpers.GetReadTranslator().TranslateEnum(ref deserializedValue, (int) deserializedValue); + TranslationHelpers.GetReadTranslator().TranslateEnum(ref deserializedValue, (int)deserializedValue); Assert.Equal(value, deserializedValue); } @@ -265,7 +265,7 @@ public void TestSerializeWithFactoryNull() [Fact] public void TestSerializeArray() { - DerivedClass[] value = new DerivedClass[] {new DerivedClass(1, 2), new DerivedClass(3, 4)}; + DerivedClass[] value = new DerivedClass[] { new DerivedClass(1, 2), new DerivedClass(3, 4) }; TranslationHelpers.GetWriteTranslator().TranslateArray(ref value); DerivedClass[] deserializedValue = null; @@ -295,7 +295,7 @@ public void TestSerializeArrayNull() [Fact] public void TestSerializeArrayWithFactory() { - BaseClass[] value = new BaseClass[] {new BaseClass(1), new BaseClass(2)}; + BaseClass[] value = new BaseClass[] { new BaseClass(1), new BaseClass(2) }; TranslationHelpers.GetWriteTranslator().TranslateArray(ref value, BaseClass.FactoryForDeserialization); BaseClass[] deserializedValue = null; diff --git a/src/Shared/AssemblyNameExtension.cs b/src/Shared/AssemblyNameExtension.cs index 0bd6dbc004f..8c62a178e05 100644 --- a/src/Shared/AssemblyNameExtension.cs +++ b/src/Shared/AssemblyNameExtension.cs @@ -1011,6 +1011,10 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) info.AddValue("remapped", remappedFrom); } + /// + /// Reads/writes this class + /// + /// public void Translate(ITranslator translator) { translator.Translate(ref asAssemblyName); diff --git a/src/Shared/TranslatorHelpers.cs b/src/Shared/TranslatorHelpers.cs index 5524d802659..1b316dcd564 100644 --- a/src/Shared/TranslatorHelpers.cs +++ b/src/Shared/TranslatorHelpers.cs @@ -6,6 +6,7 @@ using System.Configuration.Assemblies; using System.Globalization; using System.Reflection; +using AssemblyHashAlgorithm = System.Configuration.Assemblies.AssemblyHashAlgorithm; namespace Microsoft.Build.BackEnd { @@ -214,7 +215,7 @@ public static void Translate(this ITranslator translator, ref AssemblyName assem AssemblyNameFlags flags = default; ProcessorArchitecture processorArchitecture = default; CultureInfo cultureInfo = null; - System.Configuration.Assemblies.AssemblyHashAlgorithm hashAlgorithm = default; + AssemblyHashAlgorithm hashAlgorithm = default; AssemblyVersionCompatibility versionCompatibility = default; string codeBase = null; diff --git a/src/Shared/UnitTests/AssemblyNameEx_Tests.cs b/src/Shared/UnitTests/AssemblyNameEx_Tests.cs index 2c804fa0320..120adbb9ed4 100644 --- a/src/Shared/UnitTests/AssemblyNameEx_Tests.cs +++ b/src/Shared/UnitTests/AssemblyNameEx_Tests.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization.Formatters.Binary; +using Microsoft.Build.BackEnd; using Microsoft.Build.Shared; using Shouldly; using Xunit; @@ -740,9 +741,55 @@ public void VerifyAssemblyNameExSerializationWithRemappedFrom() assemblyNameDeserialized.RemappedFromEnumerator.Count().ShouldBe(1); assemblyNameDeserialized.RemappedFromEnumerator.First().ShouldBe(assemblyRemappedFrom); } - } -} + [Theory] + [InlineData("System.Xml")] + [InlineData("System.XML, Version=2.0.0.0")] + [InlineData("System.Xml, Culture=de-DE")] + [InlineData("System.Xml, Version=10.0.0.0, Culture=en, PublicKeyToken=b03f5f7f11d50a3a, Retargetable=Yes")] + [InlineData("System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + public void VerifyAssemblyNameExSerializationByTranslator(string assemblyName) + { + AssemblyNameExtension assemblyNameOriginal = new AssemblyNameExtension(assemblyName); + AssemblyNameExtension assemblyNameDeserialized = null; + + MemoryStream serializationStream = new MemoryStream(); + ITranslator writeTranslator = BinaryTranslator.GetWriteTranslator(serializationStream); + + writeTranslator.Translate(ref assemblyNameOriginal, (ITranslator t) => new AssemblyNameExtension(t)); + + serializationStream.Seek(0, SeekOrigin.Begin); + ITranslator readTranslator = BinaryTranslator.GetReadTranslator(serializationStream, null); + + readTranslator.Translate(ref assemblyNameDeserialized, (ITranslator t) => new AssemblyNameExtension(t)); + + assemblyNameDeserialized.ShouldBe(assemblyNameOriginal); + } + + [Fact] + public void VerifyAssemblyNameExSerializationWithRemappedFromByTranslator() + { + AssemblyNameExtension assemblyNameOriginal = new AssemblyNameExtension("System.Xml, Version=10.0.0.0, Culture=en, PublicKeyToken=b03f5f7f11d50a3a"); + AssemblyNameExtension assemblyRemappedFrom = new AssemblyNameExtension("System.Xml, Version=9.0.0.0, Culture=en, PublicKeyToken=b03f5f7f11d50a3a"); + assemblyRemappedFrom.MarkImmutable(); + assemblyNameOriginal.AddRemappedAssemblyName(assemblyRemappedFrom); + assemblyNameOriginal.RemappedFromEnumerator.Count().ShouldBe(1); + + AssemblyNameExtension assemblyNameDeserialized = null; + MemoryStream serializationStream = new MemoryStream(); + ITranslator writeTranslator = BinaryTranslator.GetWriteTranslator(serializationStream); + writeTranslator.Translate(ref assemblyNameOriginal, (ITranslator t) => new AssemblyNameExtension(t)); + serializationStream.Seek(0, SeekOrigin.Begin); + ITranslator readTranslator = BinaryTranslator.GetReadTranslator(serializationStream, null); + + readTranslator.Translate(ref assemblyNameDeserialized, (ITranslator t) => new AssemblyNameExtension(t)); + + assemblyNameDeserialized.Equals(assemblyNameOriginal).ShouldBeTrue(); + assemblyNameDeserialized.RemappedFromEnumerator.Count().ShouldBe(1); + assemblyNameDeserialized.RemappedFromEnumerator.First().ShouldBe(assemblyRemappedFrom); + } + } +} diff --git a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs index 4c7f04f2b13..6ea128680d9 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs @@ -5,7 +5,6 @@ using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; -using Newtonsoft.Json; using Xunit; namespace Microsoft.Build.UnitTests.ResolveAssemblyReference_Tests @@ -13,17 +12,16 @@ namespace Microsoft.Build.UnitTests.ResolveAssemblyReference_Tests public class ResolveAssemblyReferenceCacheSerialization : IDisposable { // Maintain this two in sync with the constant in SystemState - private static readonly byte[] TranslateContractSignature = new[] { (byte)'M', (byte)'B', (byte)'R', (byte)'S', (byte)'C', }; // Microsoft Build Rar State Cache + private static readonly byte[] TranslateContractSignature = { (byte)'M', (byte)'B', (byte)'R', (byte)'S', (byte)'C' }; // Microsoft Build RAR State Cache private static readonly byte TranslateContractVersion = 0x01; - private string _tempPath; - private string _rarCacheFile; - private TaskLoggingHelper _taskLoggingHelper; + private readonly string _rarCacheFile; + private readonly TaskLoggingHelper _taskLoggingHelper; public ResolveAssemblyReferenceCacheSerialization() { - _tempPath = Path.GetTempPath(); - _rarCacheFile = Path.Combine(_tempPath, Guid.NewGuid() + ".UnitTest.RarCache"); + var tempPath = Path.GetTempPath(); + _rarCacheFile = Path.Combine(tempPath, Guid.NewGuid() + ".UnitTest.RarCache"); _taskLoggingHelper = new TaskLoggingHelper(new MockEngine(), "TaskA") { TaskResources = AssemblyResources.PrimaryResources @@ -124,26 +122,6 @@ public void CorrectFileVersion() Assert.NotNull(deserialized); } - [Theory] - [InlineData("Microsoft.VisualStudio.LanguageServices.Implementation.csprojAssemblyReference.cache")] - [InlineData("Microsoft.CodeAnalysis.csprojAssemblyReference.cache")] - [InlineData("Microsoft.CodeAnalysis.VisualBasic.Emit.UnitTests.vbprojAssemblyReference.cache")] - [InlineData("Roslyn.Compilers.Extension.csprojAssemblyReference.cache")] - public void RoundTripSampleFileState(string sampleName) - { - var fileSample = GetTestPayloadFileName($@"AssemblyDependency\CacheFileSamples\BinaryFormatter\{sampleName}"); - // read old file - var deserialized = SystemState.DeserializeCacheByBinaryFormatter(fileSample, _taskLoggingHelper); - // white as TR - deserialized.SerializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); - // read as TR - var deserializedByTranslator = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); - - var sOld = JsonConvert.SerializeObject(deserialized, Formatting.Indented); - var sNew = JsonConvert.SerializeObject(deserializedByTranslator, Formatting.Indented); - Assert.Equal(sOld, sNew); - } - [Fact] public void VerifySampleStateDeserialization() { @@ -228,7 +206,7 @@ public void VerifySampleStateDeserialization() private static string GetTestPayloadFileName(string name) { - var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().CodeBase); + var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().Location); var codeBasePath = Uri.UnescapeDataString(codeBaseUrl.AbsolutePath); var dirPath = Path.GetDirectoryName(codeBasePath) ?? string.Empty; return Path.Combine(dirPath, name); diff --git a/src/Tasks.UnitTests/AssemblyDependency/TaskTranslatorHelpers.cs b/src/Tasks.UnitTests/AssemblyDependency/TaskTranslatorHelpers.cs new file mode 100644 index 00000000000..96314391e09 --- /dev/null +++ b/src/Tasks.UnitTests/AssemblyDependency/TaskTranslatorHelpers.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Versioning; +using Microsoft.Build.BackEnd; +using Microsoft.Build.Tasks; +using Xunit; + +namespace Microsoft.Build.UnitTests.ResolveAssemblyReference_Tests +{ + public class TaskTranslatorHelpers + { + MemoryStream _serializationStream; + + [Fact] + public void NullFrameworkName() + { + FrameworkName value = null; + + GetWriteTranslator().Translate(ref value); + GetReadTranslator().Translate(ref value); + + Assert.Null(value); + } + + [Theory] + [MemberData(nameof(SampleFrameworkNames))] + public void ValidFrameworkName(FrameworkName value) + { + FrameworkName deserialized = null; + + GetWriteTranslator().Translate(ref value); + GetReadTranslator().Translate(ref deserialized); + + Assert.NotNull(deserialized); + Assert.Equal(value, deserialized); + } + + public static IEnumerable SampleFrameworkNames => + new List + { + new object[] { new FrameworkName("X, Version=3.4.5") }, + new object[] { new FrameworkName("X, Version=3.4, Profile=Compact") }, + new object[] { new FrameworkName("Y", new Version(1, 2, 3)) }, + new object[] { new FrameworkName("Z", new Version(1, 2, 3), "P") }, + }; + + private ITranslator GetReadTranslator() + { + if (_serializationStream == null) + throw new InvalidOperationException("GetWriteTranslator has to be called before GetReadTranslator"); + + _serializationStream.Seek(0, SeekOrigin.Begin); + return BinaryTranslator.GetReadTranslator(_serializationStream, null); + } + + private ITranslator GetWriteTranslator() + { + _serializationStream = new MemoryStream(); + return BinaryTranslator.GetWriteTranslator(_serializationStream); + } + } +} diff --git a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj index 8f337f1b61e..796c77744d9 100644 --- a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj +++ b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj @@ -12,7 +12,6 @@ - @@ -139,18 +138,6 @@ - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs index b987cebdd6b..e4ea1eba602 100644 --- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs +++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs @@ -1905,26 +1905,6 @@ private void WriteStateFile() _cache.SerializeCacheByTranslator(_stateFile, Log); } } - - /// - /// TODO: to be deleted - /// - private void ReadStateFileBinaryFormatter() - { - _cache = SystemState.DeserializeCacheByBinaryFormatter(_stateFile, Log); - } - - /// - /// Write out the state file if a state name was supplied and the cache is dirty. - /// TODO: to be deleted - /// - private void WriteStateFileBinaryFormatter() - { - if (!string.IsNullOrEmpty(_stateFile) && _cache.IsDirty) - { - _cache.SerializeCache(_stateFile, Log); - } - } #endregion #region App.config diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index cb35f0b945b..8d8a5ad5dc6 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -89,7 +89,7 @@ NGen.cs - + PropertyParser.cs True diff --git a/src/Tasks/SystemState.cs b/src/Tasks/SystemState.cs index f89eb5b239e..288947b495a 100644 --- a/src/Tasks/SystemState.cs +++ b/src/Tasks/SystemState.cs @@ -2,16 +2,13 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Runtime.Serialization; using System.Runtime.Versioning; -using System.Security.Permissions; using Microsoft.Build.BackEnd; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; @@ -24,9 +21,9 @@ namespace Microsoft.Build.Tasks /// Class is used to cache system state. /// [Serializable] - internal sealed class SystemState : StateFileBase, ISerializable, ITranslatable + internal sealed class SystemState : StateFileBase, ITranslatable { - private static readonly byte[] TranslateContractSignature = new []{(byte)'M', (byte)'B', (byte)'R', (byte)'S', (byte)'C', }; // Microsoft Build Rar State Cache + private static readonly byte[] TranslateContractSignature = { (byte) 'M', (byte) 'B', (byte) 'R', (byte) 'S', (byte) 'C'}; // Microsoft Build RAR State Cache private static readonly byte TranslateContractVersion = 0x01; /// @@ -39,7 +36,6 @@ internal sealed class SystemState : StateFileBase, ISerializable, ITranslatable /// Cache at the SystemState instance level. It is serialized and reused between instances. /// private Dictionary instanceLocalFileStateCache = new Dictionary(StringComparer.OrdinalIgnoreCase); - private Hashtable instanceLocalFileStateCacheForBfDeserialize = new Hashtable(StringComparer.OrdinalIgnoreCase); /// /// LastModified information is purely instance-local. It doesn't make sense to @@ -116,7 +112,7 @@ internal sealed class SystemState : StateFileBase, ISerializable, ITranslatable /// Class that holds the current file state. /// [Serializable] - private sealed class FileState : ISerializable, ITranslatable + private sealed class FileState : ITranslatable { /// /// The last modified time for this file. @@ -164,50 +160,6 @@ internal FileState(ITranslator translator) Translate(translator); } - /// - /// Deserializing constuctor. - /// - internal FileState(SerializationInfo info, StreamingContext context) - { - ErrorUtilities.VerifyThrowArgumentNull(info, nameof(info)); - - lastModified = new DateTime(info.GetInt64("mod"), (DateTimeKind)info.GetInt32("modk")); - assemblyName = (AssemblyNameExtension)info.GetValue("an", typeof(AssemblyNameExtension)); - dependencies = (AssemblyNameExtension[])info.GetValue("deps", typeof(AssemblyNameExtension[])); - scatterFiles = (string[])info.GetValue("sfiles", typeof(string[])); - runtimeVersion = (string)info.GetValue("rtver", typeof(string)); - if (info.GetBoolean("fn")) - { - var frameworkNameVersion = (Version) info.GetValue("fnVer", typeof(Version)); - var frameworkIdentifier = info.GetString("fnId"); - var frameworkProfile = info.GetString("fmProf"); - frameworkName = new FrameworkName(frameworkIdentifier, frameworkNameVersion, frameworkProfile); - } - } - - /// - /// Serialize the contents of the class. - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - ErrorUtilities.VerifyThrowArgumentNull(info, nameof(info)); - - info.AddValue("mod", lastModified.Ticks); - info.AddValue("modk", (int)lastModified.Kind); - info.AddValue("an", assemblyName); - info.AddValue("deps", dependencies); - info.AddValue("sfiles", scatterFiles); - info.AddValue("rtver", runtimeVersion); - info.AddValue("fn", frameworkName != null); - if (frameworkName != null) - { - info.AddValue("fnVer", frameworkName.Version); - info.AddValue("fnId", frameworkName.Identifier); - info.AddValue("fmProf", frameworkName.Profile); - } - } - /// /// Reads/writes this class /// @@ -272,44 +224,6 @@ internal SystemState() { } - /// - /// Deserialize the contents of the class. - /// - internal SystemState(SerializationInfo info, StreamingContext context) - { - ErrorUtilities.VerifyThrowArgumentNull(info, nameof(info)); - - instanceLocalFileStateCacheForBfDeserialize = (Hashtable)info.GetValue("fileState", typeof(Hashtable)); - - isDirty = false; - } - - /// - /// Deserialize cache of this class using BinaryFormatter - /// - internal static SystemState DeserializeCacheByBinaryFormatter(string stateFile, TaskLoggingHelper log) - { - SystemState systemSate = (SystemState)StateFileBase.DeserializeCache(stateFile, log, typeof(SystemState)); - - // Construct the cache if necessary. - if (systemSate == null) - { - systemSate = new SystemState(); - } - - if (systemSate.instanceLocalFileStateCacheForBfDeserialize != null) - { - foreach (DictionaryEntry entry in systemSate.instanceLocalFileStateCacheForBfDeserialize) - { - systemSate.instanceLocalFileStateCache.Add((string)entry.Key, (FileState)entry.Value); - } - - systemSate.instanceLocalFileStateCacheForBfDeserialize = null; - } - - return systemSate; - } - /// /// Set the target framework paths. /// This is used to optimize IO in the case of files requested from one @@ -347,6 +261,7 @@ internal void SerializeCacheByTranslator(string stateFile, TaskLoggingHelper log translator.Writer.Write(TranslateContractVersion); Translate(translator); + isDirty = false; } } catch (Exception e) @@ -389,6 +304,7 @@ internal static SystemState DeserializeCacheByTranslator(string stateFile, TaskL SystemState systemState = new SystemState(); systemState.Translate(translator); + systemState.isDirty = false; return systemState; } @@ -412,22 +328,9 @@ internal static SystemState DeserializeCacheByTranslator(string stateFile, TaskL } /// - /// Serialize the contents of the class. + /// Reads/writes this class. + /// Used for serialization and deserialization of this class persistent cache. /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - ErrorUtilities.VerifyThrowArgumentNull(info, nameof(info)); - - var localFilesAsHashtable = new Hashtable(); - foreach (var pair in instanceLocalFileStateCache) - { - localFilesAsHashtable.Add(pair.Key, pair.Value); - } - - info.AddValue("fileState", localFilesAsHashtable); - } - public void Translate(ITranslator translator) { if (instanceLocalFileStateCache is null) From 6ab488fc594dcc20854e3fc18552129ccf6b21c4 Mon Sep 17 00:00:00 2001 From: Roman Konecny Date: Thu, 4 Feb 2021 14:33:22 +0100 Subject: [PATCH 5/8] Review comments #1 --- .../BackEnd/BinaryTranslator_Tests.cs | 83 +++++++++---------- src/Shared/TranslatorHelpers.cs | 2 +- ...olveAssemblyReferenceCacheSerialization.cs | 38 ++++----- .../TaskTranslatorHelpers.cs | 7 +- src/Tasks/StateFileBase.cs | 3 +- src/Tasks/SystemState.cs | 20 +---- src/Tasks/TaskTranslatorHelpers.cs | 3 + 7 files changed, 71 insertions(+), 85 deletions(-) diff --git a/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs b/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs index ae1d70e5aa7..e6ac8089ea8 100644 --- a/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs @@ -8,6 +8,7 @@ using Microsoft.Build.BackEnd; using System.IO; using System.Reflection; +using Shouldly; using Xunit; namespace Microsoft.Build.UnitTests.BackEnd @@ -448,7 +449,7 @@ public void CultureInfo(string name) CultureInfo deserializedValue = null; TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); - Assert.Equal(value, deserializedValue); + deserializedValue.ShouldBe(value); } [Fact] @@ -460,7 +461,7 @@ public void CultureInfoAsNull() CultureInfo deserializedValue = null; TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); - Assert.Null(deserializedValue); + deserializedValue.ShouldBeNull(); } [Theory] @@ -475,7 +476,7 @@ public void Version(string version) Version deserializedValue = null; TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); - Assert.Equal(value, deserializedValue); + deserializedValue.ShouldBe(value); } [Fact] @@ -487,24 +488,24 @@ public void VersionAsNull() Version deserializedValue = null; TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); - Assert.Null(deserializedValue); + deserializedValue.ShouldBeNull(); } [Fact] public void HashSetOfT() { - HashSet value = new() + HashSet values = new() { new BaseClass(1), new BaseClass(2), null }; - TranslationHelpers.GetWriteTranslator().TranslateHashSet(ref value, BaseClass.FactoryForDeserialization, capacity => new ()); + TranslationHelpers.GetWriteTranslator().TranslateHashSet(ref values, BaseClass.FactoryForDeserialization, capacity => new()); - HashSet deserializedValue = null; - TranslationHelpers.GetReadTranslator().TranslateHashSet(ref deserializedValue, BaseClass.FactoryForDeserialization, capacity => new ()); + HashSet deserializedValues = null; + TranslationHelpers.GetReadTranslator().TranslateHashSet(ref deserializedValues, BaseClass.FactoryForDeserialization, capacity => new()); - Assert.Equal(value, deserializedValue, BaseClass.EqualityComparer); + deserializedValues.ShouldBe(values, ignoreOrder: true); } [Fact] @@ -516,7 +517,7 @@ public void HashSetOfTAsNull() HashSet deserializedValue = null; TranslationHelpers.GetReadTranslator().TranslateHashSet(ref deserializedValue, BaseClass.FactoryForDeserialization, capacity => new()); - Assert.Null(deserializedValue); + deserializedValue.ShouldBeNull(); } [Fact] @@ -528,7 +529,7 @@ public void AssemblyNameAsNull() AssemblyName deserializedValue = null; TranslationHelpers.GetReadTranslator().Translate(ref deserializedValue); - Assert.Null(deserializedValue); + deserializedValue.ShouldBeNull(); } [Fact] @@ -578,17 +579,17 @@ public void AssemblyNameWithMinimalFields() /// private static void HelperAssertAssemblyNameEqual(AssemblyName expected, AssemblyName actual) { - Assert.Equal(expected.Name, actual.Name); - Assert.Equal(expected.Version, actual.Version); - Assert.Equal(expected.Flags, actual.Flags); - Assert.Equal(expected.ProcessorArchitecture, actual.ProcessorArchitecture); - Assert.Equal(expected.CultureInfo, actual.CultureInfo); - Assert.Equal(expected.HashAlgorithm, actual.HashAlgorithm); - Assert.Equal(expected.VersionCompatibility, actual.VersionCompatibility); - Assert.Equal(expected.CodeBase, actual.CodeBase); + actual.Name.ShouldBe(expected.Name); + actual.Version.ShouldBe(expected.Version); + actual.Flags.ShouldBe(expected.Flags); + actual.ProcessorArchitecture.ShouldBe(expected.ProcessorArchitecture); + actual.CultureInfo.ShouldBe(expected.CultureInfo); + actual.HashAlgorithm.ShouldBe(expected.HashAlgorithm); + actual.VersionCompatibility.ShouldBe(expected.VersionCompatibility); + actual.CodeBase.ShouldBe(expected.CodeBase); - Assert.Equal(expected.GetPublicKey(), actual.GetPublicKey()); - Assert.Equal(expected.GetPublicKeyToken(), actual.GetPublicKeyToken()); + actual.GetPublicKey().ShouldBe(expected.GetPublicKey()); + actual.GetPublicKeyToken().ShouldBe(expected.GetPublicKeyToken()); } /// @@ -770,6 +771,24 @@ protected BaseClass() { } + protected bool Equals(BaseClass other) + { + return _baseValue == other._baseValue; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((BaseClass) obj); + } + + public override int GetHashCode() + { + return _baseValue; + } + /// /// Gets a comparer. /// @@ -778,11 +797,6 @@ static public IComparer Comparer get { return new BaseClassComparer(); } } - static public IEqualityComparer EqualityComparer - { - get { return new BaseClassEqualityComparer(); } - } - /// /// Gets the value. /// @@ -841,23 +855,6 @@ public int Compare(BaseClass x, BaseClass y) } #endregion } - - private class BaseClassEqualityComparer : IEqualityComparer - { - public bool Equals(BaseClass x, BaseClass y) - { - if (ReferenceEquals(x, y)) return true; - if (ReferenceEquals(x, null)) return false; - if (ReferenceEquals(y, null)) return false; - if (x.GetType() != y.GetType()) return false; - return x._baseValue == y._baseValue; - } - - public int GetHashCode(BaseClass obj) - { - return obj._baseValue; - } - } } /// diff --git a/src/Shared/TranslatorHelpers.cs b/src/Shared/TranslatorHelpers.cs index 1b316dcd564..9cab3485c97 100644 --- a/src/Shared/TranslatorHelpers.cs +++ b/src/Shared/TranslatorHelpers.cs @@ -129,7 +129,7 @@ void TranslateUsingValueFactory(ITranslator translator, ref T objectToTranslate) hashSet = collectionFactory(count); for (int i = 0; i < count; i++) { - T value = default(T); + T value = default; translator.Translate(ref value, valueFactory); hashSet.Add(value); } diff --git a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs index 6ea128680d9..80c610e5ab7 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs @@ -5,6 +5,7 @@ using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; +using Shouldly; using Xunit; namespace Microsoft.Build.UnitTests.ResolveAssemblyReference_Tests @@ -45,7 +46,7 @@ public void RoundTripEmptyState() var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); - Assert.NotNull(deserialized); + deserialized.ShouldNotBeNull(); } [Fact] @@ -64,7 +65,8 @@ public void WrongFileSignature() } var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); - Assert.Null(deserialized); + + deserialized.ShouldBeNull(); } } @@ -82,7 +84,8 @@ public void WrongFileVersion() } var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); - Assert.Null(deserialized); + + deserialized.ShouldBeNull(); } [Fact] @@ -101,7 +104,8 @@ public void CorrectFileSignature() } var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); - Assert.NotNull(deserialized); + + deserialized.ShouldNotBeNull(); } } @@ -119,7 +123,8 @@ public void CorrectFileVersion() } var deserialized = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); - Assert.NotNull(deserialized); + + deserialized.ShouldNotBeNull(); } [Fact] @@ -169,6 +174,7 @@ public void VerifySampleStateDeserialization() var fileSample = GetTestPayloadFileName($@"AssemblyDependency\CacheFileSamples\{sampleName}"); var deserializedByTranslator = SystemState.DeserializeCacheByTranslator(fileSample, _taskLoggingHelper); + deserializedByTranslator.ShouldNotBeNull(); deserializedByTranslator.SetGetLastWriteTime(path => { @@ -187,21 +193,13 @@ public void VerifySampleStateDeserialization() out string[] scatterFiles, out FrameworkName frameworkNameAttribute); - Assert.NotNull(assemblyName); - Assert.Equal( - new AssemblyNameExtension(expectedAssemblyName, false), - assemblyName); - Assert.Empty(scatterFiles); - Assert.Equal( - new FrameworkName(expectedFrameworkName), - frameworkNameAttribute); - - Assert.NotNull(dependencies); - Assert.Equal(expectedDependencies.Length, dependencies.Length); - foreach (var expectedDependency in expectedDependencies) - { - Assert.Contains(new AssemblyNameExtension(expectedDependency), dependencies); - } + + assemblyName.ShouldNotBeNull(); + assemblyName.ShouldBe(new AssemblyNameExtension(expectedAssemblyName, false)); + scatterFiles.ShouldBeEmpty(); + frameworkNameAttribute.ShouldBe(new FrameworkName(expectedFrameworkName)); + dependencies.ShouldNotBeNull(); + expectedDependencies.ShouldBe(expectedDependencies, ignoreOrder: true); } private static string GetTestPayloadFileName(string name) diff --git a/src/Tasks.UnitTests/AssemblyDependency/TaskTranslatorHelpers.cs b/src/Tasks.UnitTests/AssemblyDependency/TaskTranslatorHelpers.cs index 96314391e09..bfb9dd55cb6 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/TaskTranslatorHelpers.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/TaskTranslatorHelpers.cs @@ -4,6 +4,7 @@ using System.Runtime.Versioning; using Microsoft.Build.BackEnd; using Microsoft.Build.Tasks; +using Shouldly; using Xunit; namespace Microsoft.Build.UnitTests.ResolveAssemblyReference_Tests @@ -20,7 +21,7 @@ public void NullFrameworkName() GetWriteTranslator().Translate(ref value); GetReadTranslator().Translate(ref value); - Assert.Null(value); + value.ShouldBeNull(); } [Theory] @@ -32,8 +33,8 @@ public void ValidFrameworkName(FrameworkName value) GetWriteTranslator().Translate(ref value); GetReadTranslator().Translate(ref deserialized); - Assert.NotNull(deserialized); - Assert.Equal(value, deserialized); + deserialized.ShouldNotBeNull(); + deserialized.ShouldBe(value); } public static IEnumerable SampleFrameworkNames => diff --git a/src/Tasks/StateFileBase.cs b/src/Tasks/StateFileBase.cs index 3d17a4967a4..cbeac2a38cf 100644 --- a/src/Tasks/StateFileBase.cs +++ b/src/Tasks/StateFileBase.cs @@ -68,8 +68,7 @@ internal static StateFileBase DeserializeCache(string stateFile, TaskLoggingHelp { StateFileBase retVal = null; - // First, we read the cache from disk if one exists, or if one does not exist - // then we create one. + // First, we read the cache from disk if one exists, or if one does not exist, we create one. try { if (!string.IsNullOrEmpty(stateFile) && FileSystems.Default.FileExists(stateFile)) diff --git a/src/Tasks/SystemState.cs b/src/Tasks/SystemState.cs index 288947b495a..b4f422959a2 100644 --- a/src/Tasks/SystemState.cs +++ b/src/Tasks/SystemState.cs @@ -264,13 +264,8 @@ internal void SerializeCacheByTranslator(string stateFile, TaskLoggingHelper log isDirty = false; } } - catch (Exception e) + catch (Exception e) when (!ExceptionHandling.NotExpectedSerializationException(e)) { - // If there was a problem writing the file (like it's read-only or locked on disk, for - // example), then eat the exception and log a warning. Otherwise, rethrow. - if (ExceptionHandling.NotExpectedSerializationException(e)) - throw; - // Not being able to serialize the cache is not an error, but we let the user know anyway. // Don't want to hold up processing just because we couldn't read the file. log.LogWarningWithCodeFromResources("General.CouldNotWriteStateFile", stateFile, e.Message); @@ -279,12 +274,11 @@ internal void SerializeCacheByTranslator(string stateFile, TaskLoggingHelper log /// /// Read the contents of this object out to the specified file. - /// TODO: once all derived classes from StateFileBase adopt new serialization, we shall consider to mode this into base class + /// TODO: once all classes derived from StateFileBase adopt the new serialization, we should consider moving this into the base class /// internal static SystemState DeserializeCacheByTranslator(string stateFile, TaskLoggingHelper log) { - // First, we read the cache from disk if one exists, or if one does not exist - // then we create one. + // First, we read the cache from disk if one exists, or if one does not exist, we create one. try { if (!string.IsNullOrEmpty(stateFile) && FileSystems.Default.FileExists(stateFile)) @@ -309,19 +303,13 @@ internal static SystemState DeserializeCacheByTranslator(string stateFile, TaskL return systemState; } } - catch (Exception e) + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { - if (ExceptionHandling.IsCriticalException(e)) - { - throw; - } - // The deserialization process seems like it can throw just about // any exception imaginable. Catch them all here. // Not being able to deserialize the cache is not an error, but we let the user know anyway. // Don't want to hold up processing just because we couldn't read the file. log.LogWarningWithCodeFromResources("General.CouldNotReadStateFile", stateFile, e.Message); - return null; } return null; diff --git a/src/Tasks/TaskTranslatorHelpers.cs b/src/Tasks/TaskTranslatorHelpers.cs index 1d4b58a7ede..7db48cf61ae 100644 --- a/src/Tasks/TaskTranslatorHelpers.cs +++ b/src/Tasks/TaskTranslatorHelpers.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + using System; using System.Runtime.Versioning; using Microsoft.Build.BackEnd; From 944c4e7de87891cb4defb5385e659e045556c1f1 Mon Sep 17 00:00:00 2001 From: Roman Konecny Date: Thu, 4 Feb 2021 14:56:19 +0100 Subject: [PATCH 6/8] Attempt to get more info why unit tests fails. --- .../ResolveAssemblyReferenceCacheSerialization.cs | 3 +++ src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs index 80c610e5ab7..1d205321431 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs @@ -173,6 +173,9 @@ public void VerifySampleStateDeserialization() var fileSample = GetTestPayloadFileName($@"AssemblyDependency\CacheFileSamples\{sampleName}"); + if (!File.Exists(fileSample)) + throw new InvalidOperationException($"File '{fileSample}' needed for this test case does not exists."); + var deserializedByTranslator = SystemState.DeserializeCacheByTranslator(fileSample, _taskLoggingHelper); deserializedByTranslator.ShouldNotBeNull(); diff --git a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj index 796c77744d9..229840abb88 100644 --- a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj +++ b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj @@ -139,7 +139,7 @@ - PreserveNewest + Always PreserveNewest From 501db6e722335165ad8c50af12851cc86cb68530 Mon Sep 17 00:00:00 2001 From: Roman Konecny Date: Thu, 4 Feb 2021 16:34:48 +0100 Subject: [PATCH 7/8] Fix unit test by embedded resources approach --- ...olveAssemblyReferenceCacheSerialization.cs | 21 +++++++++++-------- .../Microsoft.Build.Tasks.UnitTests.csproj | 13 +++++++++--- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs index 1d205321431..77a9bf0452a 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceCacheSerialization.cs @@ -172,11 +172,9 @@ public void VerifySampleStateDeserialization() }; - var fileSample = GetTestPayloadFileName($@"AssemblyDependency\CacheFileSamples\{sampleName}"); - if (!File.Exists(fileSample)) - throw new InvalidOperationException($"File '{fileSample}' needed for this test case does not exists."); + CopyResourceSampleFileIntoRarCacheFile($@"AssemblyDependency\CacheFileSamples\{sampleName}"); - var deserializedByTranslator = SystemState.DeserializeCacheByTranslator(fileSample, _taskLoggingHelper); + var deserializedByTranslator = SystemState.DeserializeCacheByTranslator(_rarCacheFile, _taskLoggingHelper); deserializedByTranslator.ShouldNotBeNull(); deserializedByTranslator.SetGetLastWriteTime(path => @@ -205,12 +203,17 @@ public void VerifySampleStateDeserialization() expectedDependencies.ShouldBe(expectedDependencies, ignoreOrder: true); } - private static string GetTestPayloadFileName(string name) + private void CopyResourceSampleFileIntoRarCacheFile(string name) { - var codeBaseUrl = new Uri(Assembly.GetExecutingAssembly().Location); - var codeBasePath = Uri.UnescapeDataString(codeBaseUrl.AbsolutePath); - var dirPath = Path.GetDirectoryName(codeBasePath) ?? string.Empty; - return Path.Combine(dirPath, name); + Assembly asm = this.GetType().Assembly; + var resource = string.Format($"{asm.GetName().Name}.{name.Replace("\\", ".")}"); + using Stream resourceStream = asm.GetManifestResourceStream(resource); + if (resourceStream == null) + throw new InvalidOperationException($"Resource '{resource}' has not been found."); + + using FileStream rarCacheFile = new FileStream(_rarCacheFile, FileMode.CreateNew); + + resourceStream.CopyTo(rarCacheFile); } } } diff --git a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj index 229840abb88..0f52a88849f 100644 --- a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj +++ b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj @@ -11,6 +11,10 @@ $(DefineConstants);MICROSOFT_BUILD_TASKS_UNITTESTS + + + + @@ -132,15 +136,18 @@ + + + Never + + + - - Always - PreserveNewest From 02cb3fb44aac693d4efd7cfea6a30b50237b6f19 Mon Sep 17 00:00:00 2001 From: Roman Konecny Date: Thu, 4 Feb 2021 17:11:47 +0100 Subject: [PATCH 8/8] Add ignored file :-( --- ....Implementation.csprojAssemblyReference.cache | Bin 0 -> 3302 bytes .../Microsoft.Build.Tasks.UnitTests.csproj | 7 +------ 2 files changed, 1 insertion(+), 6 deletions(-) create mode 100644 src/Tasks.UnitTests/AssemblyDependency/CacheFileSamples/Microsoft.VisualStudio.LanguageServices.Implementation.csprojAssemblyReference.cache diff --git a/src/Tasks.UnitTests/AssemblyDependency/CacheFileSamples/Microsoft.VisualStudio.LanguageServices.Implementation.csprojAssemblyReference.cache b/src/Tasks.UnitTests/AssemblyDependency/CacheFileSamples/Microsoft.VisualStudio.LanguageServices.Implementation.csprojAssemblyReference.cache new file mode 100644 index 0000000000000000000000000000000000000000..b9afa33ceb9f8601cf981e67e01eb750077d977d GIT binary patch literal 3302 zcmb_eTWHfz7|zk%*qrnBpm?G4uGEuu7oF20npGXHPHeZSL?vrZyJJst%1P2XADo~d z`l2`$R0KhMQSkQStApXQ3KIkcg^5!H^+{AjPtt95+Kfm%CFw~*zVH9O{QrMWWXtZT z1ON~O0rUf@S?B3(}7IHNjE7%+=BME-@}o`sD4bj_R3M-SYo z8N3);zTosM3C;DTANDsy_C4(f6+EW9n-2EP9I9*vntO$QUnkEtOtlazV?e1_IJNZ|1u)+_0Z@_FeTpcjx;CQs~ac{yRfFxoDR2AmexI^P9H^WVM-2b z$$!Ed^Z55Dm$0PMk&!f)0E}C`6o|`eYvF$;YNE9 zA+S|55ThATZW+Lj%vH;Z!f)nn+e0o!O2$30Ulnv9~O)ib_tGW-_BP&_j%o{WJncU_r;XO2}4 z91=}usD?Bn!q-`{o$7{Xs)Y_zozd-xNz!5I%N68f@A))-r3~SIUo{qXP)P0EYsNssI20 literal 0 HcmV?d00001 diff --git a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj index 0f52a88849f..99814b84913 100644 --- a/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj +++ b/src/Tasks.UnitTests/Microsoft.Build.Tasks.UnitTests.csproj @@ -64,6 +64,7 @@ + App.config @@ -136,12 +137,6 @@ - - - Never - - -