Skip to content

Commit bb0521e

Browse files
[automated] Merge branch 'main' => 'main-vs-deps' (#77776)
I detected changes in the main branch which have not been merged yet to main-vs-deps. I'm a robot and am configured to help you automatically keep main-vs-deps up to date, so I've opened this PR. This PR merges commits made on main by the following committers: * jaredpar ## Instructions for merging from UI This PR will not be auto-merged. When pull request checks pass, complete this PR by creating a merge commit, *not* a squash or rebase commit. <img alt="merge button instructions" src="https://i.imgur.com/GepcNJV.png" width="300" /> If this repo does not allow creating merge commits from the GitHub UI, use command line instructions. ## Instructions for merging via command line Run these commands to merge this pull request from the command line. ``` sh git fetch git checkout main git pull --ff-only git checkout main-vs-deps git pull --ff-only git merge --no-ff main # If there are merge conflicts, resolve them and then run git merge --continue to complete the merge # Pushing the changes to the PR branch will re-trigger PR validation. git push https://github.com/dotnet/roslyn HEAD:merge/main-to-main-vs-deps ``` <details> <summary>or if you are using SSH</summary> ``` git push git@github.com:dotnet/roslyn HEAD:merge/main-to-main-vs-deps ``` </details> After PR checks are complete push the branch ``` git push ``` ## Instructions for resolving conflicts :warning: If there are merge conflicts, you will need to resolve them manually before merging. You can do this [using GitHub][resolve-github] or using the [command line][resolve-cli]. [resolve-github]: https://help.github.com/articles/resolving-a-merge-conflict-on-github/ [resolve-cli]: https://help.github.com/articles/resolving-a-merge-conflict-using-the-command-line/ ## Instructions for updating this pull request Contributors to this repo have permission update this pull request by pushing to the branch 'merge/main-to-main-vs-deps'. This can be done to resolve conflicts or make other changes to this pull request before it is merged. The provided examples assume that the remote is named 'origin'. If you have a different remote name, please replace 'origin' with the name of your remote. ``` git fetch git checkout -b merge/main-to-main-vs-deps origin/main-vs-deps git pull https://github.com/dotnet/roslyn merge/main-to-main-vs-deps (make changes) git commit -m "Updated PR with my changes" git push https://github.com/dotnet/roslyn HEAD:merge/main-to-main-vs-deps ``` <details> <summary>or if you are using SSH</summary> ``` git fetch git checkout -b merge/main-to-main-vs-deps origin/main-vs-deps git pull git@github.com:dotnet/roslyn merge/main-to-main-vs-deps (make changes) git commit -m "Updated PR with my changes" git push git@github.com:dotnet/roslyn HEAD:merge/main-to-main-vs-deps ``` </details> Contact .NET Core Engineering (dotnet/dnceng) if you have questions or issues. Also, if this PR was generated incorrectly, help us fix it. See https://github.com/dotnet/arcade/blob/main/.github/workflows/scripts/inter-branch-merge.ps1.
2 parents 1def752 + 28b8657 commit bb0521e

File tree

40 files changed

+1524
-944
lines changed

40 files changed

+1524
-944
lines changed

src/Compilers/Core/CodeAnalysisTest/AnalyzerAssemblyLoaderTests.cs

Lines changed: 317 additions & 300 deletions
Large diffs are not rendered by default.

src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceAppDomainTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public override object InitializeLifetimeService()
2626

2727
public Exception LoadAnalyzer(string shadowPath, string analyzerPath)
2828
{
29-
var loader = DefaultAnalyzerAssemblyLoader.CreateNonLockingLoader(shadowPath);
29+
var loader = AnalyzerAssemblyLoader.CreateNonLockingLoader(shadowPath, []);
3030
Exception analyzerLoadException = null;
3131
var analyzerRef = new AnalyzerFileReference(analyzerPath, loader);
3232
analyzerRef.AnalyzerLoadFailed += (s, e) => analyzerLoadException = e.Exception;

src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ private AssemblyLoadTestFixtureCollection() { }
2929
[Collection(AssemblyLoadTestFixtureCollection.Name)]
3030
public class AnalyzerFileReferenceTests : TestBase
3131
{
32-
private static readonly AnalyzerAssemblyLoader s_analyzerLoader = new DefaultAnalyzerAssemblyLoader();
32+
private static readonly AnalyzerAssemblyLoader s_analyzerLoader = new AnalyzerAssemblyLoader();
3333
private readonly AssemblyLoadTestFixture _testFixture;
3434
public AnalyzerFileReferenceTests(AssemblyLoadTestFixture testFixture)
3535
{
@@ -319,13 +319,13 @@ public void AssemblyLoading_ReferencesLaterFakeCompiler_EndToEnd_CSharp()
319319
args: new[] { "/nologo", $@"/analyzer:""{_testFixture.AnalyzerWithLaterFakeCompilerDependency}""", "/nostdlib", $@"/r:""{corlib}""", "/out:something.dll", source.Path },
320320
new BuildPaths(clientDir: directory.Path, workingDir: directory.Path, sdkDir: null, tempDir: null),
321321
additionalReferenceDirectories: null,
322-
new DefaultAnalyzerAssemblyLoader());
322+
new AnalyzerAssemblyLoader());
323323

324324
var writer = new StringWriter();
325325
var result = compiler.Run(writer);
326326
Assert.Equal(0, result);
327327
AssertEx.Equal($"""
328-
warning CS9057: Analyzer assembly '{_testFixture.AnalyzerWithLaterFakeCompilerDependency}' cannot be used because it references version '100.0.0.0' of the compiler, which is newer than the currently running version '{typeof(DefaultAnalyzerAssemblyLoader).Assembly.GetName().Version}'.
328+
warning CS9057: Analyzer assembly '{_testFixture.AnalyzerWithLaterFakeCompilerDependency}' cannot be used because it references version '100.0.0.0' of the compiler, which is newer than the currently running version '42.42.42.42'.
329329
in.cs(1,5): warning CS0219: The variable 'x' is assigned but its value is never used
330330
331331
""", writer.ToString());
@@ -348,7 +348,7 @@ public void DuplicateAnalyzerReference()
348348
args: new[] { "/nologo", $@"/analyzer:""{_testFixture.AnalyzerWithFakeCompilerDependency}""", $@"/analyzer:""{_testFixture.AnalyzerWithFakeCompilerDependency}""", "/nostdlib", $@"/r:""{corlib}""", "/out:something.dll", source.Path },
349349
new BuildPaths(clientDir: directory.Path, workingDir: directory.Path, sdkDir: null, tempDir: null),
350350
additionalReferenceDirectories: null,
351-
new DefaultAnalyzerAssemblyLoader());
351+
new AnalyzerAssemblyLoader());
352352

353353
var writer = new StringWriter();
354354
var result = compiler.Run(writer);

src/Compilers/Core/CodeAnalysisTest/CompilerAnalyzerAssemblyResolverTests.cs

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#if NET
6+
using System;
7+
using System.Linq;
8+
using System.Reflection;
9+
using System.Runtime.Loader;
10+
using Microsoft.CodeAnalysis.Test.Utilities;
11+
using Roslyn.Utilities;
12+
using Xunit;
13+
14+
namespace Microsoft.CodeAnalysis.UnitTests;
15+
16+
public sealed class CompilerResolverTests : IDisposable
17+
{
18+
public TempRoot TempRoot { get; }
19+
public int DefaultLoadContextCount { get; }
20+
public AssemblyLoadContext CompilerContext { get; }
21+
public AssemblyLoadContext ScratchContext { get; }
22+
public Assembly AssemblyInCompilerContext { get; }
23+
internal AnalyzerAssemblyLoader Loader { get; }
24+
25+
public CompilerResolverTests()
26+
{
27+
TempRoot = new TempRoot();
28+
DefaultLoadContextCount = AssemblyLoadContext.Default.Assemblies.Count();
29+
CompilerContext = new AssemblyLoadContext(nameof(CompilerResolverTests), isCollectible: true);
30+
AssemblyInCompilerContext = CompilerContext.LoadFromAssemblyPath(typeof(AnalyzerAssemblyLoader).Assembly.Location);
31+
ScratchContext = new AssemblyLoadContext("Scratch", isCollectible: true);
32+
Loader = new AnalyzerAssemblyLoader([], [AnalyzerAssemblyLoader.DiskAnalyzerAssemblyResolver], CompilerContext);
33+
}
34+
35+
public void Dispose()
36+
{
37+
// This test should not pollute the default load context and hence interfere with other tests.
38+
Assert.Equal(DefaultLoadContextCount, AssemblyLoadContext.Default.Assemblies.Count());
39+
CompilerContext.Unload();
40+
ScratchContext.Unload();
41+
TempRoot.Dispose();
42+
}
43+
44+
[Fact]
45+
public void ResolveReturnsNullForNonHostAssembly()
46+
{
47+
var name = new AssemblyName("NotARealAssembly");
48+
var assembly = Loader.CompilerAnalyzerAssemblyResolver.Resolve(Loader, name, ScratchContext, TempRoot.CreateDirectory().Path);
49+
Assert.Null(assembly);
50+
}
51+
52+
[Fact]
53+
public void ResolveReturnsForHostAssembly()
54+
{
55+
var assembly = Loader.CompilerAnalyzerAssemblyResolver.Resolve(Loader, AssemblyInCompilerContext.GetName(), ScratchContext, TempRoot.CreateDirectory().Path);
56+
Assert.Same(AssemblyInCompilerContext, assembly);
57+
}
58+
}
59+
#endif

src/Compilers/Core/CodeAnalysisTest/InvokeUtil.cs

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,62 @@ namespace Microsoft.CodeAnalysis.UnitTests
3535

3636
public sealed class InvokeUtil
3737
{
38-
internal void Exec(ITestOutputHelper testOutputHelper, AssemblyLoadContext compilerContext, AssemblyLoadTestFixture fixture, AnalyzerTestKind kind, string typeName, string methodName, IAnalyzerAssemblyResolver[] externalResolvers, object? state = null)
38+
internal void Exec(
39+
ITestOutputHelper testOutputHelper,
40+
ImmutableArray<IAnalyzerPathResolver> pathResolvers,
41+
ImmutableArray<IAnalyzerAssemblyResolver> assemblyResolvers,
42+
AssemblyLoadTestFixture fixture,
43+
AnalyzerTestKind kind,
44+
string typeName,
45+
string methodName,
46+
object? state = null)
47+
{
48+
using var tempRoot = new TempRoot();
49+
switch (kind)
50+
{
51+
case AnalyzerTestKind.LoadDirect:
52+
assemblyResolvers = [.. assemblyResolvers, AnalyzerAssemblyLoader.DiskAnalyzerAssemblyResolver];
53+
break;
54+
case AnalyzerTestKind.LoadStream:
55+
assemblyResolvers = [.. assemblyResolvers, AnalyzerAssemblyLoader.StreamAnalyzerAssemblyResolver];
56+
break;
57+
case AnalyzerTestKind.ShadowLoad:
58+
pathResolvers = [.. pathResolvers, new ShadowCopyAnalyzerPathResolver(tempRoot.CreateDirectory().Path)];
59+
assemblyResolvers = [.. assemblyResolvers, AnalyzerAssemblyLoader.DiskAnalyzerAssemblyResolver];
60+
break;
61+
default:
62+
throw ExceptionUtilities.Unreachable();
63+
}
64+
65+
var loader = new AnalyzerAssemblyLoader(pathResolvers, assemblyResolvers, compilerLoadContext: null);
66+
var compilerContextAssemblyCount = loader.CompilerLoadContext.Assemblies.Count();
67+
try
68+
{
69+
Exec(testOutputHelper, fixture, loader, typeName, methodName, state);
70+
}
71+
finally
72+
{
73+
// When using the actual compiler load context (the one shared by all of our unit tests) the test
74+
// did not load any additional assemblies that could interfere with later tests.
75+
Assert.Equal(compilerContextAssemblyCount, loader.CompilerLoadContext.Assemblies.Count());
76+
}
77+
}
78+
79+
internal void Exec(
80+
ITestOutputHelper testOutputHelper,
81+
AssemblyLoadTestFixture fixture,
82+
AnalyzerAssemblyLoader loader,
83+
string typeName,
84+
string methodName,
85+
object? state = null)
3986
{
4087
// Ensure that the test did not load any of the test fixture assemblies into
4188
// the default load context. That should never happen. Assemblies should either
4289
// load into the compiler or directory load context.
4390
//
4491
// Not only is this bad behavior it also pollutes future test results.
4592
var defaultContextCount = AssemblyLoadContext.Default.Assemblies.Count();
46-
var compilerContextCount = compilerContext.Assemblies.Count();
47-
4893
using var tempRoot = new TempRoot();
49-
using AnalyzerAssemblyLoader loader = kind switch
50-
{
51-
AnalyzerTestKind.LoadDirect => new DefaultAnalyzerAssemblyLoader(compilerContext, AnalyzerLoadOption.LoadFromDisk, externalResolvers.ToImmutableArray()),
52-
AnalyzerTestKind.LoadStream => new DefaultAnalyzerAssemblyLoader(compilerContext, AnalyzerLoadOption.LoadFromStream, externalResolvers.ToImmutableArray()),
53-
AnalyzerTestKind.ShadowLoad => new ShadowCopyAnalyzerAssemblyLoader(compilerContext, tempRoot.CreateDirectory().Path, externalResolvers.ToImmutableArray()),
54-
_ => throw ExceptionUtilities.Unreachable()
55-
};
5694

5795
try
5896
{
@@ -71,19 +109,18 @@ internal void Exec(ITestOutputHelper testOutputHelper, AssemblyLoadContext compi
71109
}
72110
}
73111

74-
if (loader is ShadowCopyAnalyzerAssemblyLoader shadowLoader)
112+
if (loader.AnalyzerPathResolvers.OfType<ShadowCopyAnalyzerPathResolver>().FirstOrDefault() is { } shadowResolver)
75113
{
76-
testOutputHelper.WriteLine($"Shadow loader: {shadowLoader.BaseDirectory}");
114+
testOutputHelper.WriteLine($"{nameof(ShadowCopyAnalyzerPathResolver)}: {shadowResolver.BaseDirectory}");
77115
}
78116

79117
testOutputHelper.WriteLine($"Loader path maps");
80118
foreach (var pair in loader.GetPathMapSnapshot())
81119
{
82-
testOutputHelper.WriteLine($"\t{pair.OriginalAssemblyPath} -> {pair.RealAssemblyPath}");
120+
testOutputHelper.WriteLine($"\t{pair.OriginalAssemblyPath} -> {pair.ResolvedAssemblyPath}");
83121
}
84122

85123
Assert.Equal(defaultContextCount, AssemblyLoadContext.Default.Assemblies.Count());
86-
Assert.Equal(compilerContextCount, compilerContext.Assemblies.Count());
87124
}
88125
}
89126
}
@@ -92,16 +129,25 @@ internal void Exec(ITestOutputHelper testOutputHelper, AssemblyLoadContext compi
92129

93130
public sealed class InvokeUtil : MarshalByRefObject
94131
{
95-
internal void Exec(ITestOutputHelper testOutputHelper, AssemblyLoadTestFixture fixture, AnalyzerTestKind kind, string typeName, string methodName, IAnalyzerAssemblyResolver[] externalResolvers, object? state)
132+
internal void Exec(
133+
ITestOutputHelper testOutputHelper,
134+
AssemblyLoadTestFixture fixture,
135+
AnalyzerTestKind kind,
136+
string typeName,
137+
string methodName,
138+
IAnalyzerPathResolver[] pathResolvers,
139+
object? state)
96140
{
97141
using var tempRoot = new TempRoot();
98-
AnalyzerAssemblyLoader loader = kind switch
142+
pathResolvers = kind switch
99143
{
100-
AnalyzerTestKind.LoadDirect => new DefaultAnalyzerAssemblyLoader(externalResolvers.ToImmutableArray()),
101-
AnalyzerTestKind.ShadowLoad => new ShadowCopyAnalyzerAssemblyLoader(tempRoot.CreateDirectory().Path, externalResolvers.ToImmutableArray()),
102-
_ => throw ExceptionUtilities.Unreachable()
144+
AnalyzerTestKind.LoadDirect => pathResolvers,
145+
AnalyzerTestKind.ShadowLoad => [.. pathResolvers, new ShadowCopyAnalyzerPathResolver(tempRoot.CreateDirectory().Path)],
146+
_ => throw ExceptionUtilities.Unreachable(),
103147
};
104148

149+
var loader = new AnalyzerAssemblyLoader(pathResolvers.ToImmutableArray());
150+
105151
try
106152
{
107153
AnalyzerAssemblyLoaderTests.InvokeTestCode(loader, fixture, typeName, methodName, state);
@@ -121,15 +167,15 @@ internal void Exec(ITestOutputHelper testOutputHelper, AssemblyLoadTestFixture f
121167
testOutputHelper.WriteLine($"\t{assembly.FullName} -> {assembly.Location}");
122168
}
123169

124-
if (loader is ShadowCopyAnalyzerAssemblyLoader shadowLoader)
170+
if (loader.AnalyzerPathResolvers.OfType<ShadowCopyAnalyzerPathResolver>().FirstOrDefault() is { } shadowResolver)
125171
{
126-
testOutputHelper.WriteLine($"Shadow loader: {shadowLoader.BaseDirectory}");
172+
testOutputHelper.WriteLine($"{nameof(ShadowCopyAnalyzerPathResolver)}: {shadowResolver.BaseDirectory}");
127173
}
128174

129175
testOutputHelper.WriteLine($"Loader path maps");
130176
foreach (var pair in loader.GetPathMapSnapshot())
131177
{
132-
testOutputHelper.WriteLine($"\t{pair.OriginalAssemblyPath} -> {pair.RealAssemblyPath}");
178+
testOutputHelper.WriteLine($"\t{pair.OriginalAssemblyPath} -> {pair.ResolvedAssemblyPath}");
133179
}
134180
}
135181
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using Microsoft.CodeAnalysis.Test.Utilities;
12+
using Microsoft.CodeAnalysis.UnitTests.Collections;
13+
using Xunit;
14+
15+
namespace Microsoft.CodeAnalysis.UnitTests;
16+
17+
public sealed class ShadowCopyAnalyzerPathResolverTests : IDisposable
18+
{
19+
public TempRoot TempRoot { get; }
20+
internal ShadowCopyAnalyzerPathResolver PathResolver { get; }
21+
22+
public ShadowCopyAnalyzerPathResolverTests()
23+
{
24+
TempRoot = new TempRoot();
25+
PathResolver = new ShadowCopyAnalyzerPathResolver(TempRoot.CreateDirectory().Path);
26+
}
27+
28+
public void Dispose()
29+
{
30+
TempRoot.Dispose();
31+
}
32+
33+
[Fact]
34+
public void IsAnalyzerPathHandled()
35+
{
36+
var analyzerPath = TempRoot.CreateDirectory().CreateFile("analyzer.dll").Path;
37+
Assert.True(PathResolver.IsAnalyzerPathHandled(analyzerPath));
38+
}
39+
40+
/// <summary>
41+
/// Don't create the shadow directory until a copy actually happens
42+
/// </summary>
43+
[Fact]
44+
public void ShadowDirectoryIsDelayCreated()
45+
{
46+
Assert.False(Directory.Exists(PathResolver.ShadowDirectory));
47+
}
48+
49+
/// <summary>
50+
/// A shadow copy of a file that doesn't exist should produce a file that doesn't exist, not throw
51+
/// </summary>
52+
[Fact]
53+
public void GetRealPath_FileDoesNotExist()
54+
{
55+
var analyzerPath = Path.Combine(TempRoot.CreateDirectory().Path, "analyzer.dll");
56+
var shadowPath = PathResolver.GetResolvedAnalyzerPath(analyzerPath);
57+
Assert.False(File.Exists(shadowPath));
58+
}
59+
60+
[Fact]
61+
public void GetRealPath_Copies()
62+
{
63+
var analyzerPath = Path.Combine(TempRoot.CreateDirectory().Path, "analyzer.dll");
64+
File.WriteAllText(analyzerPath, "test");
65+
var shadowPath = PathResolver.GetResolvedAnalyzerPath(analyzerPath);
66+
Assert.True(File.Exists(shadowPath));
67+
Assert.Equal("test", File.ReadAllText(shadowPath));
68+
}
69+
70+
/// <summary>
71+
/// When shadow copying two files in the same directory they should end up in the same shadow
72+
/// directory
73+
/// </summary>
74+
[Fact]
75+
public void GetRealPath_FilesInSameDirectory()
76+
{
77+
var dir = TempRoot.CreateDirectory().Path;
78+
var analyzer1Path = Path.Combine(dir, "analyzer1.dll");
79+
File.WriteAllText(analyzer1Path, "test");
80+
var analyzer2Path = Path.Combine(dir, "analyzer2.dll");
81+
File.WriteAllText(analyzer2Path, "test");
82+
var shadow1Path = PathResolver.GetResolvedAnalyzerPath(analyzer1Path);
83+
var shadow2Path = PathResolver.GetResolvedAnalyzerPath(analyzer2Path);
84+
Assert.Equal(Path.GetDirectoryName(shadow1Path), Path.GetDirectoryName(shadow2Path));
85+
}
86+
87+
[Fact]
88+
public void GetRealPath_GroupOnDirectory()
89+
{
90+
var dir = TempRoot.CreateDirectory().Path;
91+
var group1AnalyzerPath = createAnalyzer("group1", "analyzer.dll");
92+
var group2AnalyzerPath = createAnalyzer("group2", "analyzer.dll");
93+
var group1ShadowPath = PathResolver.GetResolvedAnalyzerPath(group1AnalyzerPath);
94+
var group2ShadowPath = PathResolver.GetResolvedAnalyzerPath(group2AnalyzerPath);
95+
Assert.NotEqual(group1ShadowPath, group2ShadowPath);
96+
Assert.Equal("group1-analyzer.dll", File.ReadAllText(group1ShadowPath));
97+
Assert.Equal("group2-analyzer.dll", File.ReadAllText(group2ShadowPath));
98+
99+
string createAnalyzer(string groupName, string name)
100+
{
101+
var groupDir = Path.Combine(dir, groupName, "analyzers");
102+
_ = Directory.CreateDirectory(groupDir);
103+
var filePath = Path.Combine(groupDir, name);
104+
File.WriteAllText(filePath, $"{Path.GetFileName(groupName)}-{name}");
105+
return filePath;
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)