Skip to content

Commit

Permalink
fix AppDomain pollution
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredpar committed Oct 17, 2024
1 parent 13b5395 commit ae90dbb
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 96 deletions.
98 changes: 98 additions & 0 deletions src/Basic.CompilerLog.UnitTests/AppDomainUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#if NETFRAMEWORK
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Xunit.Abstractions;

namespace Basic.CompilerLog.UnitTests;

public static class AppDomainUtils
{
private static readonly object s_lock = new object();
private static bool s_hookedResolve;

public static AppDomain Create(string? name = null, string? basePath = null)
{
name = name ?? "Custom AppDomain";
basePath = basePath ?? Path.GetDirectoryName(typeof(AppDomainUtils).Assembly.Location);

lock (s_lock)
{
if (!s_hookedResolve)
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolve;
s_hookedResolve = true;
}
}

return AppDomain.CreateDomain(name, null, new AppDomainSetup()
{
ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
ApplicationBase = basePath
});
}

/// <summary>
/// When run under xunit without AppDomains all DLLs get loaded via the AssemblyResolve
/// event. In some cases the xunit, AppDomain marshalling, xunit doesn't fully hook
/// the event and we need to do it for our assemblies.
/// </summary>
private static Assembly? OnResolve(object sender, ResolveEventArgs e)
{
var assemblyName = new AssemblyName(e.Name);
var fullPath = Path.Combine(
Path.GetDirectoryName(typeof(AppDomainUtils).Assembly.Location),
assemblyName.Name + ".dll");
if (File.Exists(fullPath))
{
return Assembly.LoadFrom(fullPath);
}

return null;
}
}

public sealed class AppDomainTestOutputHelper : MarshalByRefObject, ITestOutputHelper
{
public ITestOutputHelper TestOutputHelper { get; }

public AppDomainTestOutputHelper(ITestOutputHelper testOutputHelper)
{
TestOutputHelper = testOutputHelper;
}

public void WriteLine(string message) =>
TestOutputHelper.WriteLine(message);

public void WriteLine(string format, params object[] args) =>
TestOutputHelper.WriteLine(format, args);
}

public sealed class InvokeUtil : MarshalByRefObject
{
internal void Invoke<T>(string typeName, string methodName, ITestOutputHelper testOutputHelper, T state)
{
var type = typeof(AppDomainUtils).Assembly.GetType(typeName, throwOnError: false)!;
var member = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)!;

// A static lambda will still be an instance method so we need to create the closure
// here.
var obj = member.IsStatic
? null
: type.Assembly.CreateInstance(typeName);

try
{
member.Invoke(obj, [testOutputHelper, state]);
}
catch (TargetInvocationException ex)
{
throw new Exception(ex.InnerException.Message);
}
}
}

#endif
15 changes: 9 additions & 6 deletions src/Basic.CompilerLog.UnitTests/BinaryLogReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,15 @@ public void VerifyBasicAnalyzerKind(BasicAnalyzerKind basicAnalyzerKind)
[MemberData(nameof(GetSupportedBasicAnalyzerKinds))]
public void GetCompilationSimple(BasicAnalyzerKind basicAnalyzerKind)
{
using var reader = BinaryLogReader.Create(Fixture.Console.Value.BinaryLogPath!, basicAnalyzerKind);
var compilerCall = reader.ReadAllCompilerCalls().First();
var compilationData = reader.ReadCompilationData(compilerCall);
Assert.NotNull(compilationData);
var emitResult = compilationData.EmitToMemory();
Assert.True(emitResult.Success);
RunInContext((FilePath: Fixture.Console.Value.BinaryLogPath!, Kind: basicAnalyzerKind), static (testOutptuHelper, state) =>
{
using var reader = BinaryLogReader.Create(state.FilePath, state.Kind);
var compilerCall = reader.ReadAllCompilerCalls().First();
var compilationData = reader.ReadCompilationData(compilerCall);
Assert.NotNull(compilationData);
var emitResult = compilationData.EmitToMemory();
Assert.True(emitResult.Success);
});
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public CodeAnalysisExtensionsTests(ITestOutputHelper testOutputHelper, CompilerL
[Fact]
public void EmitToMemory()
{
var data = GetCompilationData(Fixture.ClassLib.Value.CompilerLogPath);
var data = GetCompilationData(Fixture.ClassLib.Value.CompilerLogPath, basicAnalyzerKind: BasicAnalyzerKind.None);
var compilation = data.GetCompilationAfterGenerators();
var result = compilation.EmitToMemory(EmitFlags.Default);
AssertEx.Success(TestOutputHelper, result);
Expand Down
150 changes: 81 additions & 69 deletions src/Basic.CompilerLog.UnitTests/CompilationDataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,60 +20,69 @@ public CompilationDataTests(ITestOutputHelper testOutputHelper, CompilerLogFixtu
[Fact]
public void EmitToMemoryCombinations()
{
using var reader = CompilerLogReader.Create(Fixture.ClassLib.Value.CompilerLogPath);
var data = reader.ReadCompilationData(0);

var emitResult = data.EmitToMemory();
Assert.True(emitResult.Success);
AssertEx.HasData(emitResult.AssemblyStream);
AssertEx.HasData(emitResult.PdbStream);
Assert.Null(emitResult.XmlStream);
AssertEx.HasData(emitResult.MetadataStream);

emitResult = data.EmitToMemory(EmitFlags.IncludePdbStream);
Assert.True(emitResult.Success);
AssertEx.HasData(emitResult.AssemblyStream);
AssertEx.HasData(emitResult.PdbStream);
Assert.Null(emitResult.XmlStream);
Assert.Null(emitResult.MetadataStream);

emitResult = data.EmitToMemory(EmitFlags.IncludePdbStream | EmitFlags.IncludeXmlStream);
Assert.True(emitResult.Success);
AssertEx.HasData(emitResult.AssemblyStream);
AssertEx.HasData(emitResult.PdbStream);
AssertEx.HasData(emitResult.XmlStream);
Assert.Null(emitResult.MetadataStream);

emitResult = data.EmitToMemory(EmitFlags.IncludePdbStream | EmitFlags.IncludeXmlStream | EmitFlags.IncludeMetadataStream);
Assert.True(emitResult.Success);
AssertEx.HasData(emitResult.AssemblyStream);
AssertEx.HasData(emitResult.PdbStream);
AssertEx.HasData(emitResult.XmlStream);
AssertEx.HasData(emitResult.MetadataStream);

emitResult = data.EmitToMemory(EmitFlags.MetadataOnly);
Assert.True(emitResult.Success);
AssertEx.HasData(emitResult.AssemblyStream);
Assert.Null(emitResult.PdbStream);
Assert.Null(emitResult.XmlStream);
Assert.Null(emitResult.MetadataStream);
RunInContext(Fixture.ClassLib.Value.CompilerLogPath, static (testOutputHelper, filePath) =>
{
using var reader = CompilerLogReader.Create(filePath);
var data = reader.ReadCompilationData(0);
var emitResult = data.EmitToMemory();
Assert.True(emitResult.Success);
AssertEx.HasData(emitResult.AssemblyStream);
AssertEx.HasData(emitResult.PdbStream);
Assert.Null(emitResult.XmlStream);
AssertEx.HasData(emitResult.MetadataStream);
emitResult = data.EmitToMemory(EmitFlags.IncludePdbStream);
Assert.True(emitResult.Success);
AssertEx.HasData(emitResult.AssemblyStream);
AssertEx.HasData(emitResult.PdbStream);
Assert.Null(emitResult.XmlStream);
Assert.Null(emitResult.MetadataStream);
emitResult = data.EmitToMemory(EmitFlags.IncludePdbStream | EmitFlags.IncludeXmlStream);
Assert.True(emitResult.Success);
AssertEx.HasData(emitResult.AssemblyStream);
AssertEx.HasData(emitResult.PdbStream);
AssertEx.HasData(emitResult.XmlStream);
Assert.Null(emitResult.MetadataStream);
emitResult = data.EmitToMemory(EmitFlags.IncludePdbStream | EmitFlags.IncludeXmlStream | EmitFlags.IncludeMetadataStream);
Assert.True(emitResult.Success);
AssertEx.HasData(emitResult.AssemblyStream);
AssertEx.HasData(emitResult.PdbStream);
AssertEx.HasData(emitResult.XmlStream);
AssertEx.HasData(emitResult.MetadataStream);
emitResult = data.EmitToMemory(EmitFlags.MetadataOnly);
Assert.True(emitResult.Success);
AssertEx.HasData(emitResult.AssemblyStream);
Assert.Null(emitResult.PdbStream);
Assert.Null(emitResult.XmlStream);
Assert.Null(emitResult.MetadataStream);
});
}

[Fact]
public void EmitToMemoryRefOnly()
{
using var reader = CompilerLogReader.Create(Fixture.ClassLibRefOnly.Value.CompilerLogPath);
var data = reader.ReadCompilationData(0);
var result = data.EmitToMemory();
Assert.True(result.Success);
RunInContext(Fixture.ClassLibRefOnly.Value.CompilerLogPath, static (testOutputHelper, filePath) =>
{
using var reader = CompilerLogReader.Create(filePath);
var data = reader.ReadCompilationData(0);
var result = data.EmitToMemory();
Assert.True(result.Success);
});
}

[Fact]
public void GetAnalyzersNormal()
{
using var reader = CompilerLogReader.Create(Fixture.ClassLib.Value.CompilerLogPath);
var data = reader.ReadCompilationData(0);
Assert.NotEmpty(data.GetAnalyzers());
RunInContext(Fixture.ClassLib.Value.CompilerLogPath, static (testOtputHelper, filePath) =>
{
using var reader = CompilerLogReader.Create(filePath);
var data = reader.ReadCompilationData(0);
Assert.NotEmpty(data.GetAnalyzers());
});
}

[Fact]
Expand All @@ -87,48 +96,51 @@ public void GetAnalyzersNoHosting()
[Fact]
public void GetDiagnostics()
{
using var reader = CompilerLogReader.Create(Fixture.ClassLib.Value.CompilerLogPath, BasicAnalyzerHost.DefaultKind);
using var reader = CompilerLogReader.Create(Fixture.ClassLib.Value.CompilerLogPath, BasicAnalyzerKind.None);
var data = reader.ReadCompilationData(0);
Assert.NotEmpty(data.GetDiagnostics());
}

[Fact]
public async Task GetAllDiagnostics()
{
using var reader = CompilerLogReader.Create(Fixture.ClassLib.Value.CompilerLogPath, BasicAnalyzerHost.DefaultKind);
using var reader = CompilerLogReader.Create(Fixture.ClassLib.Value.CompilerLogPath, BasicAnalyzerKind.None);
var data = reader.ReadCompilationData(0);
Assert.NotEmpty(await data.GetAllDiagnosticsAsync());
}

[Fact]
public void GetCompilationAfterGeneratorsDiagnostics()
{
using var reader = CompilerLogReader.Create(
Fixture.Console.Value.CompilerLogPath,
BasicAnalyzerHost.DefaultKind);
var rawData = reader.ReadRawCompilationData(0).Item2;
var analyzers = rawData.Analyzers
.Where(x => x.FileName != "Microsoft.CodeAnalysis.NetAnalyzers.dll")
.ToList();
BasicAnalyzerHost host = DotnetUtil.IsNetCore
? new BasicAnalyzerHostInMemory(reader, analyzers)
: new BasicAnalyzerHostOnDisk(reader, analyzers);
var data = (CSharpCompilationData)reader.ReadCompilationData(0);
data = new CSharpCompilationData(
data.CompilerCall,
data.Compilation,
data.ParseOptions,
data.EmitOptions,
data.EmitData,
data.AdditionalTexts,
host,
data.AnalyzerConfigOptionsProvider);
_ = data.GetCompilationAfterGenerators(out var diagnostics);
Assert.NotEmpty(diagnostics);
RunInContext(Fixture.Console.Value.CompilerLogPath, static (testOutputHelper, logFilePath) =>
{
using var reader = CompilerLogReader.Create(
logFilePath,
BasicAnalyzerHost.DefaultKind);
var rawData = reader.ReadRawCompilationData(0).Item2;
var analyzers = rawData.Analyzers
.Where(x => x.FileName != "Microsoft.CodeAnalysis.NetAnalyzers.dll")
.ToList();
BasicAnalyzerHost host = DotnetUtil.IsNetCore
? new BasicAnalyzerHostInMemory(reader, analyzers)
: new BasicAnalyzerHostOnDisk(reader, analyzers);
var data = (CSharpCompilationData)reader.ReadCompilationData(0);
data = new CSharpCompilationData(
data.CompilerCall,
data.Compilation,
data.ParseOptions,
data.EmitOptions,
data.EmitData,
data.AdditionalTexts,
host,
data.AnalyzerConfigOptionsProvider);
_ = data.GetCompilationAfterGenerators(out var diagnostics);
Assert.NotEmpty(diagnostics);
});
}

[Theory]
[MemberData(nameof(GetSupportedBasicAnalyzerKinds))]
[MemberData(nameof(GetSimpleBasicAnalyzerKinds))]
public void GetGeneratedSyntaxTrees(BasicAnalyzerKind basicAnalyzerKind)
{
using var reader = CompilerLogReader.Create(Fixture.Console.Value.CompilerLogPath, basicAnalyzerKind);
Expand Down
20 changes: 6 additions & 14 deletions src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,19 +179,13 @@ public void AdditionalFiles()
Assert.NotNull(options);
}

[Fact]
public void AnalyzerLoadOptions()
[Theory]
[MemberData(nameof(GetSupportedBasicAnalyzerKinds))]
public void AnalyzerLoadOptions(BasicAnalyzerKind basicAnalyzerKind)
{
var any = false;
foreach (BasicAnalyzerKind kind in Enum.GetValues(typeof(BasicAnalyzerKind)))
RunInContext((FilePath: Fixture.Console.Value.CompilerLogPath, Kind: basicAnalyzerKind), static (testOutputHelper, state) =>
{
if (!BasicAnalyzerHost.IsSupported(kind))
{
continue;
}
any = true;

using var reader = CompilerLogReader.Create(Fixture.Console.Value.CompilerLogPath, kind);
using var reader = CompilerLogReader.Create(state.FilePath, state.Kind);
var data = reader.ReadCompilationData(0);
var compilation = data.GetCompilationAfterGenerators(out var diagnostics);
Assert.Empty(diagnostics);
Expand All @@ -206,9 +200,7 @@ public void AnalyzerLoadOptions()
}
Assert.True(found);
data.BasicAnalyzerHost.Dispose();
}

Assert.True(any);
});
}

[Theory]
Expand Down
3 changes: 2 additions & 1 deletion src/Basic.CompilerLog.UnitTests/SolutionReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Xunit;
Expand Down Expand Up @@ -42,7 +43,7 @@ private Solution GetSolution(string compilerLogFilePath, BasicAnalyzerKind basic
}

[Theory]
[MemberData(nameof(GetSupportedBasicAnalyzerKinds))]
[MemberData(nameof(GetSimpleBasicAnalyzerKinds))]
public async Task DocumentsGeneratedDefaultHost(BasicAnalyzerKind basicAnalyzerKind)
{
var solution = GetSolution(Fixture.Console.Value.CompilerLogPath, basicAnalyzerKind);
Expand Down
Loading

0 comments on commit ae90dbb

Please sign in to comment.