Skip to content

Commit f1afac0

Browse files
authored
Improve attribute constructor handling when calling Type.GetCustomAttributes() (#218)
***NO_CI***
1 parent d320b5e commit f1afac0

File tree

3 files changed

+459
-45
lines changed

3 files changed

+459
-45
lines changed
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
using System;
2+
using nanoFramework.TestFramework;
3+
// ReSharper disable InconsistentNaming
4+
5+
// Enabling nullable here tests the use case where the compiler adds an attribute the isn't implemented in nanoFramework
6+
// This is currently System.Runtime.CompilerServices.NullableContextAttribute
7+
#nullable enable
8+
namespace NFUnitTestAttributes
9+
{
10+
public class ConstructorTests
11+
{
12+
public const int ExpectedIntParameter = 69_420;
13+
public const string ExpectedStringParameter = "A string parameter";
14+
15+
private static void AssertArrayConstructorIsCalled(object[] attributes)
16+
{
17+
Assert.IsNotNull(attributes);
18+
Assert.AreEqual(1, attributes.Length);
19+
Assert.IsInstanceOfType(attributes[0], typeof(SingleParameterConstructorTestAttribute));
20+
21+
var testAttribute = (SingleParameterConstructorTestAttribute)attributes[0];
22+
23+
Assert.IsTrue(testAttribute.ConstructorCalled);
24+
Assert.AreEqual(ExpectedIntParameter, testAttribute.IntProperty);
25+
Assert.AreEqual(ExpectedStringParameter, testAttribute.StringProperty);
26+
}
27+
28+
private static void AssertDefaultConstructorIsCalled(object[] attributes)
29+
{
30+
Assert.IsNotNull(attributes);
31+
Assert.AreEqual(1, attributes.Length);
32+
Assert.IsInstanceOfType(attributes[0], typeof(DefaultConstructorTestAttribute));
33+
34+
var testAttribute = (DefaultConstructorTestAttribute)attributes[0];
35+
36+
Assert.AreEqual(ExpectedIntParameter, testAttribute.IntProperty);
37+
Assert.AreEqual(ExpectedStringParameter, testAttribute.StringProperty);
38+
}
39+
40+
private static void AssertMultiParameterConstructorIsCalled(object[] attributes)
41+
{
42+
Assert.IsNotNull(attributes);
43+
Assert.AreEqual(1, attributes.Length);
44+
Assert.IsInstanceOfType(attributes[0], typeof(MultiParameterConstructorTestAttribute));
45+
46+
var testAttribute = (MultiParameterConstructorTestAttribute)attributes[0];
47+
48+
Assert.IsTrue(testAttribute.ConstructorCalled);
49+
Assert.AreEqual(ExpectedIntParameter, testAttribute.IntProperty);
50+
Assert.AreEqual(ExpectedStringParameter, testAttribute.StringProperty);
51+
}
52+
53+
private static void AssertSingleParameterConstructorIsCalledWithIntValue(object[] attributes)
54+
{
55+
Assert.IsNotNull(attributes);
56+
Assert.AreEqual(1, attributes.Length);
57+
Assert.IsInstanceOfType(attributes[0], typeof(SingleParameterConstructorTestAttribute));
58+
59+
var testAttribute = (SingleParameterConstructorTestAttribute)attributes[0];
60+
61+
Assert.IsTrue(testAttribute.ConstructorCalled);
62+
Assert.AreEqual(ExpectedIntParameter, testAttribute.IntProperty);
63+
Assert.IsNull(testAttribute.StringProperty);
64+
}
65+
66+
private static void AssertSingleParameterConstructorIsCalledWithStringValue(object[] attributes)
67+
{
68+
Assert.IsNotNull(attributes);
69+
Assert.AreEqual(1, attributes.Length);
70+
Assert.IsInstanceOfType(attributes[0], typeof(SingleParameterConstructorTestAttribute));
71+
72+
var testAttribute = (SingleParameterConstructorTestAttribute)attributes[0];
73+
74+
Assert.IsTrue(testAttribute.ConstructorCalled);
75+
Assert.AreEqual(0, testAttribute.IntProperty);
76+
Assert.AreEqual(ExpectedStringParameter, testAttribute.StringProperty);
77+
}
78+
79+
private static void AssertZeroParameterConstructorIsCalled(object[] attributes)
80+
{
81+
Assert.IsNotNull(attributes);
82+
Assert.AreEqual(1, attributes.Length);
83+
Assert.IsInstanceOfType(attributes[0], typeof(SingleParameterConstructorTestAttribute));
84+
85+
var testAttribute = (SingleParameterConstructorTestAttribute)attributes[0];
86+
87+
Assert.IsTrue(testAttribute.ConstructorCalled);
88+
Assert.AreEqual(0, testAttribute.IntProperty);
89+
Assert.IsNull(testAttribute.StringProperty);
90+
}
91+
92+
[TestClass]
93+
public class When_Attribute_is_on_a_class: ConstructorTests
94+
{
95+
private static object[] GetAttributes(Type testCases)
96+
{
97+
return testCases.GetCustomAttributes(true);
98+
}
99+
100+
[TestMethod]
101+
public void Default_constructor_is_called()
102+
{
103+
AssertDefaultConstructorIsCalled(GetAttributes(typeof(DefaultConstructorTestCases)));
104+
}
105+
106+
[TestMethod]
107+
public void Multi_parameter_constructor_is_called_with_array_value()
108+
{
109+
AssertMultiParameterConstructorIsCalled(GetAttributes(typeof(MultiParameterConstructorTestCases)));
110+
}
111+
112+
[TestMethod]
113+
public void Single_parameter_constructor_is_called_with_array_value()
114+
{
115+
AssertArrayConstructorIsCalled(GetAttributes(typeof(ArrayParameterConstructorTestCases)));
116+
}
117+
118+
[TestMethod]
119+
public void Single_parameter_constructor_is_called_with_int_value()
120+
{
121+
AssertSingleParameterConstructorIsCalledWithIntValue(GetAttributes(typeof(IntParameterConstructorTestCases)));
122+
}
123+
124+
[TestMethod]
125+
public void Single_parameter_constructor_is_called_with_string_value()
126+
{
127+
AssertSingleParameterConstructorIsCalledWithStringValue(GetAttributes(typeof(StringParameterConstructorTestCases)));
128+
}
129+
130+
[TestMethod]
131+
public void Zero_parameter_constructor_is_called()
132+
{
133+
AssertZeroParameterConstructorIsCalled(GetAttributes(typeof(ZeroParameterConstructorTestCases)));
134+
}
135+
}
136+
137+
[TestClass]
138+
public class When_Attribute_is_on_a_field : ConstructorTests
139+
{
140+
private static object[] GetAttributes(Type testCases)
141+
{
142+
return testCases.GetField("TestField")!.GetCustomAttributes(true);
143+
}
144+
145+
[TestMethod]
146+
public void Default_constructor_is_called()
147+
{
148+
AssertDefaultConstructorIsCalled(GetAttributes(typeof(DefaultConstructorTestCases)));
149+
}
150+
151+
[TestMethod]
152+
public void Multi_parameter_constructor_is_called_with_array_value()
153+
{
154+
AssertMultiParameterConstructorIsCalled(GetAttributes(typeof(MultiParameterConstructorTestCases)));
155+
}
156+
157+
[TestMethod]
158+
public void Single_parameter_constructor_is_called_with_array_value()
159+
{
160+
AssertArrayConstructorIsCalled(GetAttributes(typeof(ArrayParameterConstructorTestCases)));
161+
}
162+
163+
[TestMethod]
164+
public void Single_parameter_constructor_is_called_with_int_value()
165+
{
166+
AssertSingleParameterConstructorIsCalledWithIntValue(GetAttributes(typeof(IntParameterConstructorTestCases)));
167+
}
168+
169+
[TestMethod]
170+
public void Single_parameter_constructor_is_called_with_string_value()
171+
{
172+
AssertSingleParameterConstructorIsCalledWithStringValue(GetAttributes(typeof(StringParameterConstructorTestCases)));
173+
}
174+
175+
[TestMethod]
176+
public void Zero_parameter_constructor_is_called()
177+
{
178+
AssertZeroParameterConstructorIsCalled(GetAttributes(typeof(ZeroParameterConstructorTestCases)));
179+
}
180+
}
181+
182+
[TestClass]
183+
public class When_Attribute_is_on_a_method : ConstructorTests
184+
{
185+
private static object[] GetAttributes(Type testCases)
186+
{
187+
return testCases.GetMethod("TestMethod")!.GetCustomAttributes(true);
188+
}
189+
190+
[TestMethod]
191+
public void Default_constructor_is_called()
192+
{
193+
AssertDefaultConstructorIsCalled(GetAttributes(typeof(DefaultConstructorTestCases)));
194+
}
195+
196+
[TestMethod]
197+
public void Multi_parameter_constructor_is_called_with_array_value()
198+
{
199+
AssertMultiParameterConstructorIsCalled(GetAttributes(typeof(MultiParameterConstructorTestCases)));
200+
}
201+
202+
[TestMethod]
203+
public void Single_parameter_constructor_is_called_with_array_value()
204+
{
205+
AssertArrayConstructorIsCalled(GetAttributes(typeof(ArrayParameterConstructorTestCases)));
206+
}
207+
208+
[TestMethod]
209+
public void Single_parameter_constructor_is_called_with_int_value()
210+
{
211+
AssertSingleParameterConstructorIsCalledWithIntValue(GetAttributes(typeof(IntParameterConstructorTestCases)));
212+
}
213+
214+
[TestMethod]
215+
public void Single_parameter_constructor_is_called_with_string_value()
216+
{
217+
AssertSingleParameterConstructorIsCalledWithStringValue(GetAttributes(typeof(StringParameterConstructorTestCases)));
218+
}
219+
220+
[TestMethod]
221+
public void Zero_parameter_constructor_is_called()
222+
{
223+
AssertZeroParameterConstructorIsCalled(GetAttributes(typeof(ZeroParameterConstructorTestCases)));
224+
}
225+
}
226+
}
227+
228+
#region Test Attributes
229+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
230+
public class DefaultConstructorTestAttribute : Attribute
231+
{
232+
public int IntProperty { get; set; } = ConstructorTests.ExpectedIntParameter;
233+
public string StringProperty { get; set; } = ConstructorTests.ExpectedStringParameter;
234+
}
235+
236+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
237+
public class MultiParameterConstructorTestAttribute : Attribute
238+
{
239+
public MultiParameterConstructorTestAttribute()
240+
{
241+
242+
}
243+
244+
public MultiParameterConstructorTestAttribute(int intValue, string stringValue)
245+
{
246+
ConstructorCalled = true;
247+
IntProperty = intValue;
248+
StringProperty = stringValue;
249+
}
250+
251+
public bool ConstructorCalled { get; }
252+
253+
public int IntProperty { get; set; }
254+
255+
public string? StringProperty { get; set; }
256+
}
257+
258+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
259+
public class SingleParameterConstructorTestAttribute: Attribute
260+
{
261+
public SingleParameterConstructorTestAttribute()
262+
{
263+
ConstructorCalled = true;
264+
}
265+
266+
public SingleParameterConstructorTestAttribute(int value)
267+
{
268+
ConstructorCalled = true;
269+
IntProperty = value;
270+
}
271+
272+
public SingleParameterConstructorTestAttribute(string parameter)
273+
{
274+
ConstructorCalled = true;
275+
StringProperty = parameter;
276+
}
277+
278+
public SingleParameterConstructorTestAttribute(params object[] parameters)
279+
{
280+
ConstructorCalled = true;
281+
282+
foreach (var parameter in parameters)
283+
{
284+
switch (parameter)
285+
{
286+
case int intParameter:
287+
IntProperty = intParameter;
288+
break;
289+
case string stringParameter:
290+
StringProperty = stringParameter;
291+
break;
292+
}
293+
}
294+
}
295+
296+
public bool ConstructorCalled { get; }
297+
298+
public int IntProperty { get; set; }
299+
300+
public string? StringProperty { get; set; }
301+
}
302+
#endregion
303+
304+
#region Test Cases
305+
[SingleParameterConstructorTest(ConstructorTests.ExpectedIntParameter, ConstructorTests.ExpectedStringParameter)]
306+
public class ArrayParameterConstructorTestCases
307+
{
308+
[SingleParameterConstructorTest(ConstructorTests.ExpectedIntParameter, ConstructorTests.ExpectedStringParameter)]
309+
public object? TestField;
310+
311+
[SingleParameterConstructorTest(ConstructorTests.ExpectedIntParameter, ConstructorTests.ExpectedStringParameter)]
312+
public void TestMethod(string? value = null)
313+
{
314+
315+
}
316+
}
317+
318+
[DefaultConstructorTest]
319+
public class DefaultConstructorTestCases
320+
{
321+
[DefaultConstructorTest]
322+
public object? TestField;
323+
324+
[DefaultConstructorTest]
325+
public void TestMethod(string? value = null)
326+
{
327+
328+
}
329+
}
330+
331+
[SingleParameterConstructorTest(ConstructorTests.ExpectedIntParameter)]
332+
public class IntParameterConstructorTestCases
333+
{
334+
[SingleParameterConstructorTest(ConstructorTests.ExpectedIntParameter)]
335+
public object? TestField;
336+
337+
[SingleParameterConstructorTest(ConstructorTests.ExpectedIntParameter)]
338+
public void TestMethod(string? value = null)
339+
{
340+
341+
}
342+
}
343+
344+
[MultiParameterConstructorTest(ConstructorTests.ExpectedIntParameter, ConstructorTests.ExpectedStringParameter)]
345+
public class MultiParameterConstructorTestCases
346+
{
347+
[MultiParameterConstructorTest(ConstructorTests.ExpectedIntParameter, ConstructorTests.ExpectedStringParameter)]
348+
public object? TestField;
349+
350+
[MultiParameterConstructorTest(ConstructorTests.ExpectedIntParameter, ConstructorTests.ExpectedStringParameter)]
351+
public void TestMethod(string? value = null)
352+
{
353+
354+
}
355+
}
356+
357+
[SingleParameterConstructorTest(ConstructorTests.ExpectedStringParameter)]
358+
public class StringParameterConstructorTestCases
359+
{
360+
[SingleParameterConstructorTest(ConstructorTests.ExpectedStringParameter)]
361+
public object? TestField;
362+
363+
[SingleParameterConstructorTest(ConstructorTests.ExpectedStringParameter)]
364+
public void TestMethod(string? value = null)
365+
{
366+
367+
}
368+
}
369+
370+
[SingleParameterConstructorTest]
371+
public class ZeroParameterConstructorTestCases
372+
{
373+
[SingleParameterConstructorTest]
374+
public object? TestField;
375+
376+
[SingleParameterConstructorTest]
377+
public void TestMethod(string? value = null)
378+
{
379+
380+
}
381+
}
382+
#endregion
383+
}

Tests/NFUnitTestAttributes/NFUnitTestAttributes.nfproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
</PropertyGroup>
2525
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.props" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.props')" />
2626
<ItemGroup>
27+
<Compile Include="ConstructorTests.cs" />
2728
<Compile Include="UnitTestAttributesTest1.cs" />
2829
<Compile Include="Properties\AssemblyInfo.cs" />
2930
</ItemGroup>

0 commit comments

Comments
 (0)