Skip to content

Commit f6b3144

Browse files
Add test cases
1 parent faeae3f commit f6b3144

2 files changed

Lines changed: 366 additions & 0 deletions

File tree

ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@
167167
<Compile Include="TestCases\Pretty\Issue3610.cs" />
168168
<Compile Include="TestCases\Pretty\Issue3611.cs" />
169169
<Compile Include="TestCases\Pretty\Issue3598.cs" />
170+
<Compile Include="Metadata\SignatureBlobComparerTests.cs" />
170171
<None Include="TestCases\Pretty\Issue3684.dep.cs" />
171172
<None Include="TestCases\Pretty\Issue3684.cs" />
172173
<None Include="TestCases\Ugly\NoLocalFunctions.Expected.cs" />
Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
// Copyright (c) 2026 Siegfried Pammer
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4+
// software and associated documentation files (the "Software"), to deal in the Software
5+
// without restriction, including without limitation the rights to use, copy, modify, merge,
6+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7+
// to whom the Software is furnished to do so, subject to the following conditions:
8+
//
9+
// The above copyright notice and this permission notice shall be included in all copies or
10+
// substantial portions of the Software.
11+
//
12+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17+
// DEALINGS IN THE SOFTWARE.
18+
19+
using System.Collections.Generic;
20+
using System.Linq;
21+
using System.Reflection.Metadata;
22+
23+
using ICSharpCode.Decompiler.Metadata;
24+
25+
using NUnit.Framework;
26+
27+
namespace ICSharpCode.Decompiler.Tests.Metadata
28+
{
29+
[TestFixture]
30+
public class SignatureBlobComparerTests
31+
{
32+
static MetadataReader Metadata => TypeSystem.TypeSystemLoaderTests.TestAssembly.Metadata;
33+
34+
#region Helper methods
35+
36+
/// <summary>
37+
/// Finds all MethodDefinitionHandles for methods with the given name in the given type.
38+
/// </summary>
39+
static List<MethodDefinitionHandle> FindMethods(string typeName, string methodName)
40+
{
41+
var results = new List<MethodDefinitionHandle>();
42+
foreach (var typeHandle in Metadata.TypeDefinitions)
43+
{
44+
var typeDef = Metadata.GetTypeDefinition(typeHandle);
45+
if (!Metadata.StringComparer.Equals(typeDef.Name, typeName))
46+
continue;
47+
48+
foreach (var methodHandle in typeDef.GetMethods())
49+
{
50+
var methodDef = Metadata.GetMethodDefinition(methodHandle);
51+
if (Metadata.StringComparer.Equals(methodDef.Name, methodName))
52+
results.Add(methodHandle);
53+
}
54+
}
55+
return results;
56+
}
57+
58+
/// <summary>
59+
/// Finds a single MethodDefinitionHandle for a method with the given name in the given type.
60+
/// </summary>
61+
static MethodDefinitionHandle FindMethod(string typeName, string methodName)
62+
{
63+
var results = FindMethods(typeName, methodName);
64+
Assert.That(results.Count, Is.EqualTo(1),
65+
$"Expected exactly one method '{methodName}' in '{typeName}', found {results.Count}");
66+
return results[0];
67+
}
68+
69+
/// <summary>
70+
/// Finds a MethodDefinitionHandle matching on name and parameter count.
71+
/// </summary>
72+
static MethodDefinitionHandle FindMethod(string typeName, string methodName, int parameterCount)
73+
{
74+
var results = FindMethods(typeName, methodName);
75+
var filtered = results.Where(h => {
76+
var m = Metadata.GetMethodDefinition(h);
77+
return m.GetParameters().Count == parameterCount;
78+
}).ToList();
79+
Assert.That(filtered.Count, Is.EqualTo(1),
80+
$"Expected exactly one method '{methodName}' with {parameterCount} params in '{typeName}', found {filtered.Count}");
81+
return filtered[0];
82+
}
83+
84+
static BlobReader GetSignatureBlob(MethodDefinitionHandle handle)
85+
{
86+
var method = Metadata.GetMethodDefinition(handle);
87+
return Metadata.GetBlobReader(method.Signature);
88+
}
89+
90+
static bool CompareSignatures(MethodDefinitionHandle a, MethodDefinitionHandle b)
91+
{
92+
return SignatureBlobComparer.EqualsMethodSignature(
93+
GetSignatureBlob(a), GetSignatureBlob(b),
94+
Metadata, Metadata);
95+
}
96+
97+
#endregion
98+
99+
#region Identity / reflexive tests
100+
101+
[Test]
102+
public void SameMethod_IsEqual()
103+
{
104+
// SimplePublicClass.Method() compared to itself
105+
var method = FindMethod("SimplePublicClass", "Method");
106+
Assert.That(CompareSignatures(method, method), Is.True);
107+
}
108+
109+
[Test]
110+
public void Constructor_ComparedToItself_IsEqual()
111+
{
112+
var ctor = FindMethod("SimplePublicClass", ".ctor");
113+
Assert.That(CompareSignatures(ctor, ctor), Is.True);
114+
}
115+
116+
[Test]
117+
public void StaticMethod_ComparedToItself_IsEqual()
118+
{
119+
// StaticClass.Extension(this object) — it's static at the IL level
120+
var method = FindMethod("StaticClass", "Extension");
121+
Assert.That(CompareSignatures(method, method), Is.True);
122+
}
123+
124+
#endregion
125+
126+
#region Same name, different signatures
127+
128+
[Test]
129+
public void DifferentParameterTypes_NotEqual()
130+
{
131+
// MethodWithOptionalParameter(int) vs MethodWithParamsArray(object[])
132+
var intParam = FindMethod("ParameterTests", "MethodWithOptionalParameter");
133+
var arrayParam = FindMethod("ParameterTests", "MethodWithParamsArray");
134+
Assert.That(CompareSignatures(intParam, arrayParam), Is.False);
135+
}
136+
137+
[Test]
138+
public void RefVsOut_SameAtSignatureLevel()
139+
{
140+
// ref and out are both ELEMENT_TYPE_BYREF at the blob level;
141+
// the out modifier is only represented via attributes.
142+
var methodRef = FindMethod("ParameterTests", "MethodWithRefParameter");
143+
var methodOut = FindMethod("ParameterTests", "MethodWithOutParameter");
144+
Assert.That(CompareSignatures(methodRef, methodOut), Is.True);
145+
}
146+
147+
[Test]
148+
public void RefVsIn_SameAtSignatureLevel()
149+
{
150+
// ref and in are both ELEMENT_TYPE_BYREF at the blob level;
151+
// the in modifier is only represented via IsReadOnlyAttribute.
152+
var methodRef = FindMethod("ParameterTests", "MethodWithRefParameter");
153+
var methodIn = FindMethod("ParameterTests", "MethodWithInParameter");
154+
Assert.That(CompareSignatures(methodRef, methodIn), Is.True);
155+
}
156+
157+
[Test]
158+
public void ByRefVsNonByRef_NotEqual()
159+
{
160+
// MethodWithRefParameter(ref int) vs MethodWithOptionalParameter(int)
161+
// byref int vs plain int should differ at the blob level
162+
var byRef = FindMethod("ParameterTests", "MethodWithRefParameter");
163+
var plain = FindMethod("ParameterTests", "MethodWithOptionalParameter");
164+
Assert.That(CompareSignatures(byRef, plain), Is.False);
165+
}
166+
167+
[Test]
168+
public void DifferentParameterCount_NotEqual()
169+
{
170+
// Compare a method with 0 params to one with 1 param
171+
var method0 = FindMethod("SimplePublicClass", "Method"); // 0 params
172+
var method1 = FindMethod("ParameterTests", "MethodWithOutParameter"); // 1 param
173+
Assert.That(CompareSignatures(method0, method1), Is.False);
174+
}
175+
176+
[Test]
177+
public void DifferentReturnType_NotEqual()
178+
{
179+
// SimplePublicClass.Method() returns void
180+
// PropertyTest has a getter that returns string (get_Item)
181+
var voidMethod = FindMethod("SimplePublicClass", "Method");
182+
var stringGetter = FindMethod("PropertyTest", "get_Item");
183+
Assert.That(CompareSignatures(voidMethod, stringGetter), Is.False);
184+
}
185+
186+
#endregion
187+
188+
#region Generic methods
189+
190+
[Test]
191+
public void GenericMethod_ComparedToItself_IsEqual()
192+
{
193+
// GenericClass<A,B>.TestMethod<K,V>(string)
194+
var method = FindMethod("GenericClass`2", "TestMethod");
195+
Assert.That(CompareSignatures(method, method), Is.True);
196+
}
197+
198+
[Test]
199+
public void GenericMethod_DifferentArity_NotEqual()
200+
{
201+
// TestMethod<K,V>(string) has 2 type params
202+
// GetIndex<T>(T) has 1 type param
203+
var testMethod = FindMethod("GenericClass`2", "TestMethod");
204+
var getIndex = FindMethod("GenericClass`2", "GetIndex");
205+
Assert.That(CompareSignatures(testMethod, getIndex), Is.False);
206+
}
207+
208+
[Test]
209+
public void GenericMethodWithOneTypeParam_ComparedToItself_IsEqual()
210+
{
211+
var method = FindMethod("GenericClass`2", "GetIndex");
212+
Assert.That(CompareSignatures(method, method), Is.True);
213+
}
214+
215+
[Test]
216+
public void NonGenericVsGenericMethod_NotEqual()
217+
{
218+
// SimplePublicClass.Method() — non-generic
219+
// GenericClass<A,B>.GetIndex<T>(T) — generic
220+
var nonGeneric = FindMethod("SimplePublicClass", "Method");
221+
var generic = FindMethod("GenericClass`2", "GetIndex");
222+
Assert.That(CompareSignatures(nonGeneric, generic), Is.False);
223+
}
224+
225+
#endregion
226+
227+
#region Instance vs static
228+
229+
[Test]
230+
public void InstanceVsStatic_DifferentCallingConvention_NotEqual()
231+
{
232+
// An instance method vs a static method — calling convention differs in the signature header
233+
// StaticClass.Extension is static; SimplePublicClass.Method is instance
234+
var staticMethod = FindMethod("StaticClass", "Extension");
235+
var instanceMethod = FindMethod("SimplePublicClass", "Method");
236+
Assert.That(CompareSignatures(staticMethod, instanceMethod), Is.False);
237+
}
238+
239+
#endregion
240+
241+
#region Methods with same signature across different types
242+
243+
[Test]
244+
public void SameSignatureInDifferentTypes_IsEqual()
245+
{
246+
// Both SimplePublicClass and ParameterTests have parameterless instance constructors
247+
var ctor1 = FindMethod("SimplePublicClass", ".ctor");
248+
var ctor2 = FindMethod("ParameterTests", ".ctor");
249+
Assert.That(CompareSignatures(ctor1, ctor2), Is.True);
250+
}
251+
252+
#endregion
253+
254+
#region Methods involving complex types
255+
256+
[Test]
257+
public void MethodWithArrayParam_ComparedToItself_IsEqual()
258+
{
259+
// ParameterTests.MethodWithParamsArray(params object[])
260+
var method = FindMethod("ParameterTests", "MethodWithParamsArray");
261+
Assert.That(CompareSignatures(method, method), Is.True);
262+
}
263+
264+
[Test]
265+
public void MethodWithOptionalParam_ComparedToNonOptional_IsEqual()
266+
{
267+
// At the signature blob level, optional vs required doesn't matter —
268+
// the signatures are the same if the types match.
269+
// MethodWithOptionalParameter(int) vs MethodWithEnumOptionalParameter(StringComparison)
270+
// These have different parameter types, so they should NOT be equal.
271+
var optionalInt = FindMethod("ParameterTests", "MethodWithOptionalParameter");
272+
var optionalEnum = FindMethod("ParameterTests", "MethodWithEnumOptionalParameter");
273+
Assert.That(CompareSignatures(optionalInt, optionalEnum), Is.False);
274+
}
275+
276+
[Test]
277+
public void VarArgsMethod_ComparedToItself_IsEqual()
278+
{
279+
var method = FindMethod("ParameterTests", "VarArgsMethod");
280+
Assert.That(CompareSignatures(method, method), Is.True);
281+
}
282+
283+
[Test]
284+
public void VarArgsMethod_VsNormalMethod_NotEqual()
285+
{
286+
// VarArgsMethod uses __arglist calling convention
287+
var varArgs = FindMethod("ParameterTests", "VarArgsMethod");
288+
var normal = FindMethod("SimplePublicClass", "Method");
289+
Assert.That(CompareSignatures(varArgs, normal), Is.False);
290+
}
291+
292+
#endregion
293+
294+
#region Indexer accessors
295+
296+
[Test]
297+
public void IndexerGetter_ComparedToItself_IsEqual()
298+
{
299+
var getter = FindMethod("PropertyTest", "get_Item");
300+
Assert.That(CompareSignatures(getter, getter), Is.True);
301+
}
302+
303+
[Test]
304+
public void IndexerGetter_VsSetter_NotEqual()
305+
{
306+
var getter = FindMethod("PropertyTest", "get_Item");
307+
var setter = FindMethod("PropertyTest", "set_Item");
308+
Assert.That(CompareSignatures(getter, setter), Is.False);
309+
}
310+
311+
#endregion
312+
313+
#region DllImport / extern methods
314+
315+
[Test]
316+
public void DllImportMethod_ComparedToItself_IsEqual()
317+
{
318+
var method = FindMethod("NonCustomAttributes", "DllMethod");
319+
Assert.That(CompareSignatures(method, method), Is.True);
320+
}
321+
322+
#endregion
323+
324+
#region Explicit interface implementation
325+
326+
[Test]
327+
public void ExplicitImpl_VsRegularMethod_SameSignature()
328+
{
329+
// ExplicitImplementationTests has both M(int) and IExplicitImplementationTests.M(int)
330+
// Their signatures should be identical at the blob level
331+
var methods = FindMethods("ExplicitImplementationTests", "M");
332+
Assert.That(methods.Count, Is.EqualTo(1),
333+
"Explicit impl has a mangled name, so only one 'M' should be found");
334+
}
335+
336+
#endregion
337+
338+
#region Symmetric property
339+
340+
[Test]
341+
public void Comparison_IsSymmetric()
342+
{
343+
var method1 = FindMethod("SimplePublicClass", "Method");
344+
var method2 = FindMethod("ParameterTests", "MethodWithOutParameter");
345+
346+
bool forward = CompareSignatures(method1, method2);
347+
bool backward = CompareSignatures(method2, method1);
348+
Assert.That(forward, Is.EqualTo(backward), "Comparison should be symmetric");
349+
}
350+
351+
[Test]
352+
public void Comparison_IsSymmetric_WhenEqual()
353+
{
354+
var ctor1 = FindMethod("SimplePublicClass", ".ctor");
355+
var ctor2 = FindMethod("ParameterTests", ".ctor");
356+
357+
bool forward = CompareSignatures(ctor1, ctor2);
358+
bool backward = CompareSignatures(ctor2, ctor1);
359+
Assert.That(forward, Is.True);
360+
Assert.That(backward, Is.True);
361+
}
362+
363+
#endregion
364+
}
365+
}

0 commit comments

Comments
 (0)