-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Add tests for assembly name stringification inside Type.GetType #43913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This test uses the AssemblyResolve handler, but it's arguably a reflection test at the core, so I've put it here. Happy to move it elsewhere as appropriate. It currently passes on Mono due to dotnet#43910 but should not, so I've disabled it for now. Once that issue is resolved, I'll push a fix for this and re-enable the test.
|
|
||
| public static IEnumerable<object[]> TypeNamesToVerifyData() | ||
| { | ||
| yield return new object[] { "Some.Assembly.FakeType, Some.Assembly, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", "Some.Assembly, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" }; // full |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a lot of tests for remote executor. Each of these lines will launch a new process. Do we really need to run this inside remote executor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we ought to because it registers the callback and we don't want that to pollute the normal environment? I could use an array with the strings and just run a loop inside a single remote executor thread, but I wanted to try and follow the normal xunit theory pattern. Happy to change it if you think that would be better or have some other suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test assembly name can be unique, the callback can filter out everything except the unique name as the first thing, and the test specific callback can be unregistered at the end of the test in finally block.
The pollution of the environment should be none/minimal with this. (The runtime may cache the failure to load the assembly with the unique name that should be acceptable.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works for me, thanks for the suggestion. Will fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, if the runtime caches the failure this won't work, right? The handler needs to be called each time here.
I don't think in practice either runtime will cache AppDomain.AssemblyResolve, but I'm not sure I like relying on that.
|
yield return new object[] { "Some.Fake.Assembly.FakeType, Some.Fake.Assembly", "Some.Fake.Assembly" }; // simple
yield return new object[] { "Some.Fake.Assembly.FakeType, Some.Fake.Assembly ", "Some.Fake.Assembly" }; // simple w/ whitespace
Edit: never mind, misread the logs, lots of other errors |
|
Assembly name parsing trims whitespaces, and this one is no exception. For example. |
|
Oh, I see why these tests are failing now; this behavior differs significantly between core and framework. It seems on framework only the specified components are printed, whereas on core the Culture and PublicKeyToken are always printed even if defaults and the Version is only printed when specified. Do you know by chance where this changed and if it was intentional? |
|
For this program: using System;
public class Program
{
public static void Main()
{
ResolveEventHandler handler = (sender, arg) =>
{
Console.WriteLine(arg.Name);
return null;
};
AppDomain.CurrentDomain.AssemblyResolve += handler;
var t = Type.GetType("Some.Fake.Assembly.FakeType, Some.Fake.Assembly2");
}
}Framework: I guess for Mono, I'll need to also add some tests to our mono/mono test suite and make the behavior different between the two versions. |
|
I do not know when this changed. |
|
This also seems different from when instantiating an [Fact]
public static void FullName_WithPublicKey()
{
AssemblyName assemblyName = new AssemblyName("MyAssemblyName, Version=1.0.0.0");
assemblyName.SetPublicKey(TheKey);
Assert.Equal("MyAssemblyName, Version=1.0.0.0, PublicKeyToken=b03f5f7f11d50a3a", assemblyName.FullName);
}
[Fact]
public static void Name_WithNullPublicKey()
{
AssemblyName assemblyName = new AssemblyName("noname,PublicKeyToken=null");
Assert.Equal(0, assemblyName.GetPublicKeyToken().Length);
Assert.Equal("noname, PublicKeyToken=null", assemblyName.FullName);
} |
|
@vitek-karas do you know when this behavior changed? Is it documented anywhere? |
|
The assembly name loader is very different between .NET Framework and .NET Core. Also, we had several different implementations of assembly name parsing in .NET Framework. The implementation to use was selected based on compatibility quirks. Some of the behavior changes likely go 10+ years ago the assembly loader was refactored for Silverlight. I think this boils down to differences in how Assembly.Load works and parses assembly names. For example: .NET Core: .NET Framework: .NET Framework + quirks (set |
|
The best way to achieve identical assembly name parsing behavior between the runtimes would be to rewrite it in C# and share the implementation. |
|
Hmm, I see. I agree with you on the assembly name parsing and stringification; it would be very nice to have a shared managed implementation to serve as a source of truth. I'll open an issue about that separately, it's a nice longer-term goal. I would love to get rid of our current managed implementation, especially since it has accidentally deviated from the native one multiple times now. In the short term, I think it's not too difficult for me to change the behavior of the specific cases I've included in this test for our parsers if we can decide on the desired behavior. Is the current CoreCLR behavior intentional and ideal? Is there any possibility of changing it? I want to add tests so that we at least can be sure we agree on the desirable behavior here, though probably the ones I've added here are not even sufficient. This PR was prompted by a community contributor adding a bunch of the runtime work to mono/mono for framework compatibility, and I'd rather not leave them out to dry, but I don't want to merge a bunch of runtime changes that make our dotnet/runtime behavior even worse than now. |
|
I am not aware of any user filled issue complaining about the current behavior of assembly name parsing, so I do not do see a good reason for changing it. |
|
I'm happy to defer to @jkotas on this - he knows a lot more about the history of this code than I do. I do agree on not making changes to this - I haven't seen any complaints about this behavior yet. So while I find it somewhat random at places, it's better to leave as is if we don't have a strong reason to change. Ideally we would:
|
|
That rough plan sounds good to me, with the actual managed version being a longer-term goal. The test suite, however, seems much more immediately valuable, so it would be nice to have someone refine and extend this PR (which only covers some weird stringification examples from framework) to better describe the netcore parsing and stringification behavior. I'll open an issue for the rest of the work, and hopefully we can experiment with the managed parser down the line, it would be really nice to have. @steveisok, is the test suite something your team could reasonably take on? It seems most appropriate there, or if not with someone on the CoreCLR side. cc: @SamMonoRT |
|
Okay, gave this a couple weeks to stew around in my head and want to document my current thoughts. From my perspective, there are four issues that have come up.
This is most easily illustrated with a sample: using System;
using System.Reflection;
namespace HelloWorld
{
internal class Program
{
private static void Main(string[] args)
{
string test_type = "Some.Assembly.FakeType";
string test_assembly = "Some.Assembly, Version=4.0.0.0";
string event_name = "";
ResolveEventHandler handler = (sender, args) =>
{
event_name = args.Name;
return null;
};
AppDomain.CurrentDomain.AssemblyResolve += handler;
var t = Type.GetType(test_type + ", " + test_assembly);
var asm_name = new AssemblyName(test_assembly);
Console.WriteLine(event_name);
Console.WriteLine(asm_name);
}
}
}Output under CoreCLR: I really think this is just a bug we should fix to make behavior consistent throughout the framework. This appears to be a stringification difference rather than a parsing difference, and indeed Jan's example above seems to parse identically under .NET 5 in both the As far as I can tell the reason
I think we should be using a managed formatter here instead of having the runtime do the stringification. If we match the behavior of
Mono has the
Mono currently has a native parser and two managed parsers ( I'll open separate issues for these in a bit. |
This test uses the AssemblyResolve handler, but it's arguably a reflection test at the core, so I've put it here. Happy to move it elsewhere as appropriate. Similarly, if I'm missing useful cases here I'm happy to add them.
It currently passes on Mono due to #43910 but should not, so I've disabled it for now. Once that issue is resolved, I'll push a fix for Mono and re-enable the test. The underlying issue is tracked at mono/mono#19803