|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Test.Utilities
{
public sealed class AssemblyLoadTestFixture : MarshalByRefObject, IDisposable
{
private readonly TempRoot _temp;
private readonly TempDirectory _directory;
public string TempDirectory => _directory.Path;
/// <summary>
/// An assembly with no references, assembly version 1.
/// </summary>
public string Delta1 { get; }
/// <summary>
/// An assembly with no references, assembly version 1, and public signed.
/// </summary>
public string DeltaPublicSigned1 { get; }
/// <summary>
/// An assembly with a reference to <see cref="Delta1"/>.
/// </summary>
public string Gamma { get; }
/// <summary>
/// An assembly with a reference to <see cref="DeltaPublicSigned1"/>.
/// </summary>
public string GammaReferencingPublicSigned { get; }
/// <summary>
/// An assembly with a reference to <see cref="Gamma"/>.
/// </summary>
public string Beta { get; }
/// <summary>
/// An assembly with a reference to <see cref="Gamma"/>.
/// </summary>
public string Alpha { get; }
/// <summary>
/// An assembly with no references, assembly version 2.
/// </summary>
public string Delta2 { get; }
/// <summary>
/// An assembly with no references, assembly version 2, and public signed.
/// </summary>
public string DeltaPublicSigned2 { get; }
/// <summary>
/// An assembly with a reference to <see cref="Delta2"/>.
/// </summary>
public string Epsilon { get; }
/// <summary>
/// An assembly with a reference to <see cref="DeltaPublicSigned2"/>.
/// </summary>
public string EpsilonReferencingPublicSigned { get; }
/// <summary>
/// An assembly with no references, assembly version 2. The implementation however is different than
/// <see cref="Delta2"/> so we can test having two assemblies that look the same but aren't.
/// </summary>
public string Delta2B { get; }
/// <summary>
/// An assembly with no references, assembly version 3.
/// </summary>
public string Delta3 { get; }
public string UserSystemCollectionsImmutable { get; }
/// <summary>
/// An analyzer which uses members in its referenced version of System.Collections.Immutable
/// that are not present in the compiler's version of System.Collections.Immutable.
/// </summary>
public string AnalyzerReferencesSystemCollectionsImmutable1 { get; }
/// <summary>
/// An analyzer which uses members in its referenced version of System.Collections.Immutable
/// which have different behavior than the same members in compiler's version of System.Collections.Immutable.
/// </summary>
public string AnalyzerReferencesSystemCollectionsImmutable2 { get; }
public string AnalyzerReferencesDelta1 { get; }
public string FaultyAnalyzer { get; }
public string AnalyzerWithDependency { get; }
public string AnalyzerDependency { get; }
public string AnalyzerWithNativeDependency { get; }
public string AnalyzerWithFakeCompilerDependency { get; }
public string AnalyzerWithLaterFakeCompilerDependency { get; }
/// <summary>
/// An analyzer that can be used to load a resource
/// </summary>
public string AnalyzerWithLoc { get; }
/// <summary>
/// An analyzer that can be used to load a resource
/// </summary>
public string AnalyzerWithLocResourceEnGB { get; }
public AssemblyLoadTestFixture()
{
_temp = new TempRoot();
_directory = _temp.CreateDirectory();
const string AnalyzerWithLocSource = """
using System;
using System.Resources;
using System.Reflection;
using System.Threading;
using System.Globalization;
[assembly: System.Reflection.AssemblyTitle("AnalyzerWithLoc")]
[assembly: System.Reflection.AssemblyVersion("1.0.0.0")]
namespace AnalyzerWithLoc
{
public static class Util
{
public static void Exec(string culture)
{
try
{
var ci = new CultureInfo(culture);
var rm = new ResourceManager("rmc", typeof(Util).Assembly);
_ = rm.GetString("hello", ci);
}
catch (Exception ex)
{
}
}
}
}
""";
AnalyzerWithLoc = GenerateDll("AnalyzerWithLoc", _directory, AnalyzerWithLocSource);
const string AnalyzerWithLocResourceEnGBSource = @"
[assembly: System.Reflection.AssemblyTitle(""AnalyzerWithLoc"")]
[assembly: System.Reflection.AssemblyVersion(""1.0.0.0"")]
[assembly: System.Reflection.AssemblyCulture(""en-GB"")]
";
AnalyzerWithLocResourceEnGB = GenerateDll("AnalyzerWithLoc.resources", _directory.CreateDirectory("en-GB"), AnalyzerWithLocResourceEnGBSource);
const string Delta1Source = @"
using System.Text;
[assembly: System.Reflection.AssemblyTitle(""Delta"")]
[assembly: System.Reflection.AssemblyVersion(""1.0.0.0"")]
namespace Delta
{
public class D
{
public void Write(StringBuilder sb, string s)
{
sb.AppendLine(""Delta: "" + s);
}
public void M1()
{
}
}
}
";
Delta1 = GenerateDll("Delta", _directory, Delta1Source);
var delta1Reference = MetadataReference.CreateFromFile(Delta1);
DeltaPublicSigned1 = GenerateDll("DeltaPublicSigned", _directory.CreateDirectory("Delta1PublicSigned"), Delta1Source, publicKeyOpt: SigningTestHelpers.PublicKey);
const string Delta2Source = @"
using System.Text;
[assembly: System.Reflection.AssemblyTitle(""Delta"")]
[assembly: System.Reflection.AssemblyVersion(""2.0.0.0"")]
namespace Delta
{
public class D
{
public void Write(StringBuilder sb, string s)
{
sb.AppendLine(""Delta.2: "" + s);
}
public void M2()
{
}
}
}
";
var v2Directory = _directory.CreateDirectory("Version2");
Delta2 = GenerateDll("Delta", v2Directory, Delta2Source);
var v2PublicSignedDirectory = _directory.CreateDirectory("Version2PublicSigned");
DeltaPublicSigned2 = GenerateDll("DeltaPublicSigned", v2PublicSignedDirectory, Delta2Source, publicKeyOpt: SigningTestHelpers.PublicKey);
var delta2Reference = MetadataReference.CreateFromFile(Delta2);
const string GammaSource = @"
using System.Text;
using Delta;
namespace Gamma
{
public class G
{
public void Write(StringBuilder sb, string s)
{
D d = new D();
d.Write(sb, ""Gamma: "" + s);
}
}
}
";
Gamma = GenerateDll("Gamma", _directory, GammaSource, delta1Reference);
GammaReferencingPublicSigned = GenerateDll("GammaReferencingPublicSigned", _directory.CreateDirectory("GammaReferencingPublicSigned"), GammaSource, MetadataReference.CreateFromFile(DeltaPublicSigned1));
var gammaReference = MetadataReference.CreateFromFile(Gamma);
Beta = GenerateDll("Beta", _directory, @"
using System.Text;
using Gamma;
namespace Beta
{
public class B
{
public void Write(StringBuilder sb, string s)
{
G g = new G();
g.Write(sb, ""Beta: "" + s);
}
}
}
", gammaReference);
Alpha = GenerateDll("Alpha", _directory, @"
using System.Text;
using Gamma;
namespace Alpha
{
public class A
{
public void Write(StringBuilder sb, string s)
{
G g = new G();
g.Write(sb, ""Alpha: "" + s);
}
}
}
", gammaReference);
const string EpsilonSource = @"
using System.Text;
using Delta;
namespace Epsilon
{
public class E
{
public void Write(StringBuilder sb, string s)
{
D d = new D();
d.Write(sb, ""Epsilon: "" + s);
}
}
}
";
Epsilon = GenerateDll("Epsilon", v2Directory, EpsilonSource, delta2Reference);
EpsilonReferencingPublicSigned = GenerateDll("EpsilonReferencingPublicSigned", v2PublicSignedDirectory, EpsilonSource, MetadataReference.CreateFromFile(DeltaPublicSigned2));
var v2BDirectory = _directory.CreateDirectory("Version2B");
Delta2B = GenerateDll("Delta", v2BDirectory, @"
using System.Text;
[assembly: System.Reflection.AssemblyTitle(""Delta"")]
[assembly: System.Reflection.AssemblyVersion(""2.0.0.0"")]
namespace Delta
{
public class D
{
public void Write(StringBuilder sb, string s)
{
sb.AppendLine(""Delta.2B: "" + s);
}
}
}
");
var v3Directory = _directory.CreateDirectory("Version3");
Delta3 = GenerateDll("Delta", v3Directory, @"
using System.Text;
[assembly: System.Reflection.AssemblyTitle(""Delta"")]
[assembly: System.Reflection.AssemblyVersion(""3.0.0.0"")]
namespace Delta
{
public class D
{
public void Write(StringBuilder sb, string s)
{
sb.AppendLine(""Delta.3: "" + s);
}
}
}
");
var sciUserDirectory = _directory.CreateDirectory("SCIUser");
var compilerReference = MetadataReference.CreateFromFile(typeof(Microsoft.CodeAnalysis.SyntaxNode).Assembly.Location);
UserSystemCollectionsImmutable = GenerateDll("System.Collections.Immutable", sciUserDirectory, @"
namespace System.Collections.Immutable
{
public static class ImmutableArray
{
public static ImmutableArray<T> Create<T>(T t) => new();
}
public struct ImmutableArray<T>
{
public int Length => 42;
public static int MyMethod() => 42;
}
}
", compilerReference);
var userSystemCollectionsImmutableReference = MetadataReference.CreateFromFile(UserSystemCollectionsImmutable);
AnalyzerReferencesSystemCollectionsImmutable1 = GenerateDll("AnalyzerUsesSystemCollectionsImmutable1", sciUserDirectory, @"
using System.Text;
using System.Collections.Immutable;
public class Analyzer
{
public void Method(StringBuilder sb)
{
sb.Append(ImmutableArray<object>.MyMethod());
}
}
", userSystemCollectionsImmutableReference, compilerReference);
AnalyzerReferencesSystemCollectionsImmutable2 = GenerateDll("AnalyzerUsesSystemCollectionsImmutable2", sciUserDirectory, @"
using System.Text;
using System.Collections.Immutable;
public class Analyzer
{
public void Method(StringBuilder sb)
{
sb.Append(ImmutableArray.Create(""a"").Length);
}
}
", userSystemCollectionsImmutableReference, compilerReference);
var analyzerReferencesDelta1Directory = _directory.CreateDirectory("AnalyzerReferencesDelta1");
var delta1InAnalyzerReferencesDelta1 = analyzerReferencesDelta1Directory.CopyFile(Delta1);
AnalyzerReferencesDelta1 = GenerateDll("AnalyzerReferencesDelta1", _directory, @"
using System.Text;
using Delta;
public class Analyzer
{
public void Method(StringBuilder sb)
{
var d = new D();
d.Write(sb, ""Hello"");
}
}
", MetadataReference.CreateFromFile(delta1InAnalyzerReferencesDelta1.Path), compilerReference);
var faultyAnalyzerDirectory = _directory.CreateDirectory("FaultyAnalyzer");
FaultyAnalyzer = GenerateDll("FaultyAnalyzer", faultyAnalyzerDirectory, @"
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public abstract class TestAnalyzer : DiagnosticAnalyzer
{
}
", compilerReference);
var realSciReference = MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location);
var analyzerWithDependencyDirectory = _directory.CreateDirectory("AnalyzerWithDependency");
AnalyzerDependency = GenerateDll("AnalyzerDependency", analyzerWithDependencyDirectory, @"
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
public abstract class AbstractTestAnalyzer : DiagnosticAnalyzer
{
protected static string SomeString = nameof(SomeString);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { throw new NotImplementedException(); } }
public override void Initialize(AnalysisContext context) { throw new NotImplementedException(); }
}
", realSciReference, compilerReference);
AnalyzerWithDependency = GenerateDll("Analyzer", analyzerWithDependencyDirectory, @"
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class TestAnalyzer : AbstractTestAnalyzer
{
private static string SomeString2 = AbstractTestAnalyzer.SomeString;
}", realSciReference, compilerReference, MetadataReference.CreateFromFile(AnalyzerDependency));
AnalyzerWithNativeDependency = GenerateDll("AnalyzerWithNativeDependency", _directory, @"
using System;
using System.Runtime.InteropServices;
public class Class1
{
[DllImport(""kernel32.dll"", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int GetFileAttributesW(string lpFileName);
public int GetFileAttributes(string path)
{
return GetFileAttributesW(path);
}
}
");
var analyzerWithFakeCompilerDependencyDirectory = _directory.CreateDirectory("AnalyzerWithFakeCompilerDependency");
var fakeCompilerAssembly = GenerateDll("Microsoft.CodeAnalysis", analyzerWithFakeCompilerDependencyDirectory, publicKeyOpt: typeof(SyntaxNode).Assembly.GetName().GetPublicKey()?.ToImmutableArray() ?? default, csSource: @"
using System;
using System.Reflection;
[assembly: AssemblyVersionAttribute(""2.0.0.0"")]
namespace Microsoft.CodeAnalysis.Diagnostics
{
public class DiagnosticAnalyzerAttribute : Attribute
{
public DiagnosticAnalyzerAttribute(string firstLanguage, params string[] additionalLanguages) { }
}
public class DiagnosticAnalyzer
{
}
}
");
var fakeCompilerReference = MetadataReference.CreateFromFile(fakeCompilerAssembly);
AnalyzerWithFakeCompilerDependency = GenerateDll("AnalyzerWithFakeCompilerDependency", analyzerWithFakeCompilerDependencyDirectory, @"
using Microsoft.CodeAnalysis.Diagnostics;
[DiagnosticAnalyzer(""C#"")]
public class Analyzer : DiagnosticAnalyzer
{
}", fakeCompilerReference);
var analyzerWithLaterFakeCompileDirectory = _directory.CreateDirectory("AnalyzerWithLaterFakeCompilerDependency");
var laterFakeCompilerAssembly = GenerateDll("Microsoft.CodeAnalysis", analyzerWithLaterFakeCompileDirectory, publicKeyOpt: typeof(SyntaxNode).Assembly.GetName().GetPublicKey()?.ToImmutableArray() ?? default, csSource: @"
using System;
using System.Reflection;
[assembly: AssemblyVersionAttribute(""100.0.0.0"")]
namespace Microsoft.CodeAnalysis.Diagnostics
{
public class DiagnosticAnalyzerAttribute : Attribute
{
public DiagnosticAnalyzerAttribute(string firstLanguage, params string[] additionalLanguages) { }
}
public class DiagnosticAnalyzer
{
}
}
");
var laterCompilerReference = MetadataReference.CreateFromFile(laterFakeCompilerAssembly);
AnalyzerWithLaterFakeCompilerDependency = GenerateDll("AnalyzerWithLaterFakeCompilerDependency", analyzerWithLaterFakeCompileDirectory, @"
using Microsoft.CodeAnalysis.Diagnostics;
[DiagnosticAnalyzer(""C#"")]
public class Analyzer : DiagnosticAnalyzer
{
}", laterCompilerReference);
}
private static string GenerateDll(string assemblyName, TempDirectory directory, string csSource, params MetadataReference[] additionalReferences)
{
return GenerateDll(assemblyName, directory, csSource, publicKeyOpt: default, additionalReferences);
}
private static string GenerateDll(string assemblyName, TempDirectory directory, string csSource, ImmutableArray<byte> publicKeyOpt, params MetadataReference[] additionalReferences)
{
CSharpCompilationOptions options = new(OutputKind.DynamicallyLinkedLibrary, warningLevel: Diagnostic.MaxWarningLevel);
if (!publicKeyOpt.IsDefault)
{
options = options.WithPublicSign(true).WithCryptoPublicKey(publicKeyOpt);
}
var analyzerDependencyCompilation = CSharpCompilation.Create(
assemblyName: assemblyName,
syntaxTrees: new SyntaxTree[] { SyntaxFactory.ParseSyntaxTree(SourceText.From(csSource, encoding: null, SourceHashAlgorithms.Default)) },
references: (new MetadataReference[]
{
NetStandard20.References.mscorlib,
NetStandard20.References.netstandard,
NetStandard20.References.SystemRuntime
}).Concat(additionalReferences),
options: options);
var tempFile = directory.CreateFile($"{assemblyName}.dll");
tempFile.WriteAllBytes(analyzerDependencyCompilation.EmitToArray());
// Mark the file as read only to prevent mutations. The output of this type is frequently used across
// unit tests boundaries. Need a guardrail to make sure one test doesn't pollute the output of
// another test.
var fileInfo = new FileInfo(tempFile.Path);
fileInfo.IsReadOnly = true;
return tempFile.Path;
}
public void Dispose()
{
_temp.Dispose();
}
}
}
|