Skip to content

Commit 052932f

Browse files
committed
Fix resolving assemblies from frameworks not referenced by coverlet itself
1 parent e8ed061 commit 052932f

File tree

13 files changed

+241
-40
lines changed

13 files changed

+241
-40
lines changed

coverlet.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "coverlet.tests.projectsampl
6060
EndProject
6161
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.tests.projectsample.netframework", "test\coverlet.tests.projectsample.netframework\coverlet.tests.projectsample.netframework.csproj", "{E69D68C9-78ED-4076-A14B-D07295A4B2A5}"
6262
EndProject
63+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.tests.projectsample.aspnet5", "test\coverlet.tests.projectsample.aspnet5\coverlet.tests.projectsample.aspnet5.csproj", "{1C3CA3F8-DF8C-433F-8A56-69102D2BBDF6}"
64+
EndProject
65+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.tests.projectsample.aspnet5.tests", "test\coverlet.tests.projectsample.aspnet5.tests\coverlet.tests.projectsample.aspnet5.tests.csproj", "{8EC065A4-7700-45E6-8B90-0182E3649DEA}"
66+
EndProject
6367
Global
6468
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6569
Debug|Any CPU = Debug|Any CPU
@@ -134,6 +138,14 @@ Global
134138
{E69D68C9-78ED-4076-A14B-D07295A4B2A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
135139
{E69D68C9-78ED-4076-A14B-D07295A4B2A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
136140
{E69D68C9-78ED-4076-A14B-D07295A4B2A5}.Release|Any CPU.Build.0 = Release|Any CPU
141+
{1C3CA3F8-DF8C-433F-8A56-69102D2BBDF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
142+
{1C3CA3F8-DF8C-433F-8A56-69102D2BBDF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
143+
{1C3CA3F8-DF8C-433F-8A56-69102D2BBDF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
144+
{1C3CA3F8-DF8C-433F-8A56-69102D2BBDF6}.Release|Any CPU.Build.0 = Release|Any CPU
145+
{8EC065A4-7700-45E6-8B90-0182E3649DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
146+
{8EC065A4-7700-45E6-8B90-0182E3649DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
147+
{8EC065A4-7700-45E6-8B90-0182E3649DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
148+
{8EC065A4-7700-45E6-8B90-0182E3649DEA}.Release|Any CPU.Build.0 = Release|Any CPU
137149
EndGlobalSection
138150
GlobalSection(SolutionProperties) = preSolution
139151
HideSolutionNode = FALSE
@@ -157,6 +169,8 @@ Global
157169
{1CBF6966-2A67-4D2C-8598-D174B83072F4} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
158170
{E69D68C9-78ED-4076-A14B-D07295A4B2A5} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
159171
{C9B7DC34-3E04-4F20-AED4-73791AF8020D} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
172+
{1C3CA3F8-DF8C-433F-8A56-69102D2BBDF6} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
173+
{8EC065A4-7700-45E6-8B90-0182E3649DEA} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
160174
EndGlobalSection
161175
GlobalSection(ExtensibilityGlobals) = postSolution
162176
SolutionGuid = {9CA57C02-97B0-4C38-A027-EA61E8741F10}

src/coverlet.core/Instrumentation/CecilAssemblyResolver.cs

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
using Microsoft.Extensions.DependencyModel;
1111
using Microsoft.Extensions.DependencyModel.Resolution;
1212
using Mono.Cecil;
13+
using Newtonsoft.Json;
14+
using Newtonsoft.Json.Linq;
1315

1416
namespace Coverlet.Core.Instrumentation
1517
{
@@ -70,14 +72,14 @@ public NetstandardAwareAssemblyResolver(string modulePath, ILogger logger)
7072
_modulePath = modulePath;
7173
_logger = logger;
7274

73-
// this is lazy because we cannot create AspNetCoreSharedFrameworkResolver if not on .NET Core runtime,
75+
// this is lazy because we cannot create NetCoreSharedFrameworkResolver if not on .NET Core runtime,
7476
// runtime folders are different
7577
_compositeResolver = new Lazy<CompositeCompilationAssemblyResolver>(() => new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[]
7678
{
7779
new AppBaseCompilationAssemblyResolver(),
78-
new ReferenceAssemblyPathResolver(),
7980
new PackageCompilationAssemblyResolver(),
80-
new AspNetCoreSharedFrameworkResolver(_logger)
81+
new NetCoreSharedFrameworkResolver(modulePath, _logger),
82+
new ReferenceAssemblyPathResolver(),
8183
}), true);
8284
}
8385

@@ -216,23 +218,37 @@ internal AssemblyDefinition TryWithCustomResolverOnDotNetCore(AssemblyNameRefere
216218
}
217219
}
218220

219-
internal class AspNetCoreSharedFrameworkResolver : ICompilationAssemblyResolver
221+
internal class NetCoreSharedFrameworkResolver : ICompilationAssemblyResolver
220222
{
221-
private readonly string[] _aspNetSharedFrameworkDirs;
223+
private readonly List<string> _aspNetSharedFrameworkDirs = new();
222224
private readonly ILogger _logger;
223225

224-
public AspNetCoreSharedFrameworkResolver(ILogger logger)
226+
public NetCoreSharedFrameworkResolver(string modulePath, ILogger logger)
225227
{
226228
_logger = logger;
227-
string runtimeRootPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
228-
string runtimeVersion = runtimeRootPath.Substring(runtimeRootPath.LastIndexOf(Path.DirectorySeparatorChar) + 1);
229-
_aspNetSharedFrameworkDirs = new string[]
229+
230+
string runtimeConfigFile = Path.Combine(
231+
Path.GetDirectoryName(modulePath)!,
232+
Path.GetFileNameWithoutExtension(modulePath) + ".runtimeconfig.json");
233+
if (!File.Exists(runtimeConfigFile))
234+
{
235+
return;
236+
}
237+
238+
var reader = new RuntimeConfigurationReader(runtimeConfigFile);
239+
IEnumerable<(string Name, string Version)> referencedFrameworks = reader.GetFrameworks();
240+
string runtimePath = Path.GetDirectoryName(typeof(object).Assembly.Location);
241+
string runtimeRootPath = Path.Combine(runtimePath!, "../..");
242+
foreach ((string frameworkName, string frameworkVersion) in referencedFrameworks)
230243
{
231-
Path.GetFullPath(Path.Combine(runtimeRootPath,"../../Microsoft.AspNetCore.All", runtimeVersion)),
232-
Path.GetFullPath(Path.Combine(runtimeRootPath, "../../Microsoft.AspNetCore.App", runtimeVersion))
233-
};
244+
var majorVersion = string.Join(".", frameworkVersion.Split('.').Take(2)) + ".";
245+
var directory = new DirectoryInfo(Path.Combine(runtimeRootPath, frameworkName));
246+
var latestVersion = directory.GetDirectories().Where(x => x.Name.StartsWith(majorVersion))
247+
.Select(x => Convert.ToUInt32(x.Name.Substring(majorVersion.Length))).Max();
248+
_aspNetSharedFrameworkDirs.Add(Path.Combine(directory.FullName, majorVersion + latestVersion));
249+
}
234250

235-
_logger.LogVerbose("AspNetCoreSharedFrameworkResolver search paths:");
251+
_logger.LogVerbose("NetCoreSharedFrameworkResolver search paths:");
236252
foreach (string searchPath in _aspNetSharedFrameworkDirs)
237253
{
238254
_logger.LogVerbose(searchPath);
@@ -250,7 +266,8 @@ public bool TryResolveAssemblyPaths(CompilationLibrary library, List<string> ass
250266
continue;
251267
}
252268

253-
foreach (string file in Directory.GetFiles(sharedFrameworkPath))
269+
string[] files = Directory.GetFiles(sharedFrameworkPath);
270+
foreach (string file in files)
254271
{
255272
if (Path.GetFileName(file).Equals(dllName, StringComparison.OrdinalIgnoreCase))
256273
{
@@ -264,4 +281,36 @@ public bool TryResolveAssemblyPaths(CompilationLibrary library, List<string> ass
264281
return false;
265282
}
266283
}
284+
285+
internal class RuntimeConfigurationReader
286+
{
287+
private readonly string _runtimeConfigFile;
288+
289+
public RuntimeConfigurationReader(string runtimeConfigFile)
290+
{
291+
_runtimeConfigFile = runtimeConfigFile;
292+
}
293+
294+
public IEnumerable<(string Name, string Version)> GetFrameworks()
295+
{
296+
JObject configuration =
297+
new JsonSerializer().Deserialize<JObject>(
298+
new JsonTextReader(new StringReader(File.ReadAllText(_runtimeConfigFile))));
299+
300+
JToken runtimeOptions = configuration["runtimeOptions"];
301+
JToken framework = runtimeOptions?["framework"];
302+
if (framework != null)
303+
{
304+
return new[] {(framework["name"].Value<string>(), framework["version"].Value<string>())};
305+
}
306+
307+
JToken frameworks = runtimeOptions?["frameworks"];
308+
if (frameworks != null)
309+
{
310+
return frameworks.Select(x => (x["name"].Value<string>(), x["version"].Value<string>()));
311+
}
312+
313+
throw new InvalidOperationException($"Unable to read runtime configuration from {_runtimeConfigFile}.");
314+
}
315+
}
267316
}

src/coverlet.core/Properties/AssemblyInfo.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,7 @@
1313
[assembly: InternalsVisibleTo("coverlet.core.tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100757cf9291d78a82e5bb58a827a3c46c2f959318327ad30d1b52e918321ffbd847fb21565b8576d2a3a24562a93e86c77a298b564a0f1b98f63d7a1441a3a8bcc206da3ed09d5dacc76e122a109a9d3ac608e21a054d667a2bae98510a1f0f653c0e6f58f42b4b3934f6012f5ec4a09b3dfd3e14d437ede1424bdb722aead64ad")]
1414
[assembly: InternalsVisibleTo("coverlet.collector.tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ed0ed6af9693182615b8dcadc83c918b8d36312f86cefc69539d67d4189cd1b89420e7c3871802ffef7f5ca7816c68ad856c77bf7c230cc07824d96aa5d1237eebd30e246b9a14e22695fb26b40c800f74ea96619092cbd3a5d430d6c003fc7a82e8ccd1e315b935105d9232fe9e99e8d7ff54bba6f191959338d4a3169df9b3")]
1515
[assembly: InternalsVisibleTo("coverlet.integration.tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010001d24efbe9cbc2dc49b7a3d2ae34ca37cfb69b4f450acd768a22ce5cd021c8a38ae7dc68b2809a1ac606ad531b578f192a5690b2986990cbda4dd84ec65a3a4c1c36f6d7bb18f08592b93091535eaee2f0c8e48763ed7f190db2008e1f9e0facd5c0df5aaab74febd3430e09a428a72e5e6b88357f92d78e47512d46ebdc3cbb")]
16+
[assembly: InternalsVisibleTo("coverlet.tests.projectsample.aspnet5.tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100757cf9291d78a82e5bb58a827a3c46c2f959318327ad30d1b52e918321ffbd847fb21565b8576d2a3a24562a93e86c77a298b564a0f1b98f63d7a1441a3a8bcc206da3ed09d5dacc76e122a109a9d3ac608e21a054d667a2bae98510a1f0f653c0e6f58f42b4b3934f6012f5ec4a09b3dfd3e14d437ede1424bdb722aead64ad")]
17+
1618
// Needed to mock internal type https://github.com/Moq/moq4/wiki/Quickstart#advanced-features
17-
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
19+
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -619,24 +619,6 @@ public int SampleMethod()
619619
if (expectedExcludes) { loggerMock.Verify(l => l.LogVerbose(It.IsAny<string>())); }
620620
}
621621

622-
[Fact]
623-
public void TestInstrument_AspNetCoreSharedFrameworkResolver()
624-
{
625-
var resolver = new AspNetCoreSharedFrameworkResolver(_mockLogger.Object);
626-
var compilationLibrary = new CompilationLibrary(
627-
"package",
628-
"Microsoft.Extensions.Logging.Abstractions",
629-
"2.2.0",
630-
"sha512-B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A==",
631-
Enumerable.Empty<string>(),
632-
Enumerable.Empty<Dependency>(),
633-
true);
634-
635-
var assemblies = new List<string>();
636-
Assert.True(resolver.TryResolveAssemblyPaths(compilationLibrary, assemblies));
637-
Assert.NotEmpty(assemblies);
638-
}
639-
640622
[Fact]
641623
public void TestInstrument_NetstandardAwareAssemblyResolver_PreserveCompilationContext()
642624
{
@@ -740,15 +722,15 @@ public void TestReachabilityHelper()
740722
new[]
741723
{
742724
// Throws
743-
7, 8,
725+
7, 8,
744726
// NoBranches
745-
12, 13, 14, 15, 16,
727+
12, 13, 14, 15, 16,
746728
// If
747-
19, 20, 22, 23, 24, 25, 26, 27, 29, 30,
729+
19, 20, 22, 23, 24, 25, 26, 27, 29, 30,
748730
// Switch
749-
33, 34, 36, 39, 40, 41, 42, 44, 45, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 64, 65, 68, 69,
731+
33, 34, 36, 39, 40, 41, 42, 44, 45, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 64, 65, 68, 69,
750732
// Subtle
751-
72, 73, 75, 78, 79, 80, 82, 83, 86, 87, 88, 91, 92, 95, 96, 98, 99, 101, 102, 103,
733+
72, 73, 75, 78, 79, 80, 82, 83, 86, 87, 88, 91, 92, 95, 96, 98, 99, 101, 102, 103,
752734
// UnreachableBranch
753735
106, 107, 108, 110, 111, 112, 113, 114,
754736
// ThrowsGeneric
@@ -774,7 +756,7 @@ public void TestReachabilityHelper()
774756
// Switch
775757
41, 42,
776758
// Subtle
777-
79, 80, 88, 96, 98, 99,
759+
79, 80, 88, 96, 98, 99,
778760
// UnreachableBranch
779761
110, 111, 112, 113, 114,
780762
// CallsGenericMethodDoesNotReturn
@@ -822,7 +804,7 @@ public void Instrumenter_MethodsWithoutReferenceToSource_AreSkipped()
822804

823805
var instrumenter = new Instrumenter(Path.Combine(directory.FullName, Path.GetFileName(module)), "_coverlet_tests_projectsample_vbmynamespace", parameters,
824806
loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(Path.Combine(directory.FullName, Path.GetFileName(module)), loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
825-
807+
826808
instrumentationHelper.BackupOriginalModule(Path.Combine(directory.FullName, Path.GetFileName(module)), "_coverlet_tests_projectsample_vbmynamespace");
827809

828810
InstrumenterResult result = instrumenter.Instrument();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) Toni Solarin-Sodara
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Reflection;
5+
6+
[assembly: AssemblyKeyFile("coverlet.tests.projectsample.aspnet5.tests.snk")]
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) Toni Solarin-Sodara
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Reflection;
7+
using Coverlet.Core.Abstractions;
8+
using Coverlet.Core.Instrumentation;
9+
using Microsoft.Extensions.DependencyModel;
10+
using Moq;
11+
using Xunit;
12+
13+
namespace coverlet.tests.projectsample.aspnet5.tests
14+
{
15+
public class ResolverTests
16+
{
17+
[Fact]
18+
public void TestInstrument_NetCoreSharedFrameworkResolver()
19+
{
20+
Assembly assembly = GetType().Assembly;
21+
var mockLogger = new Mock<ILogger>();
22+
var resolver = new NetCoreSharedFrameworkResolver(assembly.Location, mockLogger.Object);
23+
var compilationLibrary = new CompilationLibrary(
24+
"package",
25+
"Microsoft.Extensions.Logging.Abstractions",
26+
"2.2.0",
27+
"sha512-B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A==",
28+
Enumerable.Empty<string>(),
29+
Enumerable.Empty<Dependency>(),
30+
true);
31+
32+
var assemblies = new List<string>();
33+
Assert.True(resolver.TryResolveAssemblyPaths(compilationLibrary, assemblies));
34+
Assert.NotEmpty(assemblies);
35+
}
36+
}
37+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net5.0</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
10+
<PackageReference Include="Moq" />
11+
<PackageReference Include="xunit" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\..\src\coverlet.core\coverlet.core.csproj" />
16+
<ProjectReference Include="..\coverlet.tests.projectsample.aspnet5\coverlet.tests.projectsample.aspnet5.csproj" />
17+
</ItemGroup>
18+
19+
20+
</Project>
596 Bytes
Binary file not shown.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Toni Solarin-Sodara
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Microsoft.AspNetCore.Hosting;
5+
using Microsoft.Extensions.Hosting;
6+
7+
namespace coverlet.tests.projectsample.aspnet5
8+
{
9+
public class Program
10+
{
11+
public static void Main(string[] args)
12+
{
13+
CreateHostBuilder(args).Build().Run();
14+
}
15+
16+
public static IHostBuilder CreateHostBuilder(string[] args) =>
17+
Host.CreateDefaultBuilder(args)
18+
.ConfigureWebHostDefaults(webBuilder =>
19+
{
20+
webBuilder.UseStartup<Startup>();
21+
});
22+
}
23+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) Toni Solarin-Sodara
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Microsoft.AspNetCore.Builder;
5+
using Microsoft.AspNetCore.Hosting;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Hosting;
9+
10+
namespace coverlet.tests.projectsample.aspnet5
11+
{
12+
public class Startup
13+
{
14+
// This method gets called by the runtime. Use this method to add services to the container.
15+
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
16+
public void ConfigureServices(IServiceCollection services)
17+
{
18+
}
19+
20+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
21+
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
22+
{
23+
if (env.IsDevelopment())
24+
{
25+
app.UseDeveloperExceptionPage();
26+
}
27+
28+
app.UseRouting();
29+
30+
app.UseEndpoints(endpoints =>
31+
{
32+
endpoints.MapGet("/", async context =>
33+
{
34+
await context.Response.WriteAsync("Hello World!");
35+
});
36+
});
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)