Skip to content

Commit 12de58d

Browse files
committed
NUnitExtensions: Add a NUnitLogger
Add an NUnitLogger, based on the example given in [1], and documentation from Microsoft in [2]. When logging, it might be tempting to use `.AddConsole()`, but NUnit can't log the console reliably, and results in missing output, or output in the wrong test case. Using NUnit.TestContext.Write() resolves this problem and allows your test cases to log to NUnit. [1] nunit/nunit#3919 (comment) [2] https://docs.microsoft.com/en-us/dotnet/core/extensions/custom-logging-provider The code itself doesn't have a direct dependency to NUnit.Framework, but pulls it in via reflection. This allows the user to have the library automaticall adapt to NUnit 2.x or NUnit 3.x (they're similar, but not compatible and otherwise both can't be used at the same time). Issue: DOTNET-382
1 parent 595be53 commit 12de58d

10 files changed

+748
-92
lines changed

CodeQuality/NUnitExtensions/Deploy.cs

+4-22
Original file line numberDiff line numberDiff line change
@@ -88,24 +88,6 @@ public static class Deploy
8888
private const int CopyWaitInterval = 250;
8989
private const int CopyWaitAttempts = 4;
9090

91-
private readonly static object s_TestContextLock = new object();
92-
private static TestContextAccessor s_TestContextAccessor;
93-
94-
private static TestContextAccessor TestContext
95-
{
96-
get
97-
{
98-
if (s_TestContextAccessor == null) {
99-
lock (s_TestContextLock) {
100-
if (s_TestContextAccessor == null) {
101-
s_TestContextAccessor = TestContextAccessor.GetTestContext();
102-
}
103-
}
104-
}
105-
return s_TestContextAccessor;
106-
}
107-
}
108-
10991
/// <summary>
11092
/// Gets the NUnit test directory.
11193
/// </summary>
@@ -124,7 +106,7 @@ private static TestContextAccessor TestContext
124106
/// </exception>
125107
public static string TestDirectory
126108
{
127-
get { return TestContext.TestDirectory ?? string.Empty; }
109+
get { return TestContextAccessor.Instance.TestDirectory ?? string.Empty; }
128110
}
129111

130112
/// <summary>
@@ -145,7 +127,7 @@ public static string TestDirectory
145127
/// </exception>
146128
public static string WorkDirectory
147129
{
148-
get { return TestContext.WorkDirectory ?? string.Empty; }
130+
get { return TestContextAccessor.Instance.WorkDirectory ?? string.Empty; }
149131
}
150132

151133
private static string WorkDirectoryAbsolute
@@ -164,7 +146,7 @@ private static string WorkDirectoryAbsolute
164146
/// <value>The name of the test.</value>
165147
public static string TestName
166148
{
167-
get { return TestContext.TestName ?? string.Empty; }
149+
get { return TestContextAccessor.Instance.TestName ?? string.Empty; }
168150
}
169151

170152
/// <summary>
@@ -173,7 +155,7 @@ public static string TestName
173155
/// <value>The full name of the test.</value>
174156
public static string TestFullName
175157
{
176-
get { return TestContext.TestFullName ?? string.Empty; }
158+
get { return TestContextAccessor.Instance.TestFullName ?? string.Empty; }
177159
}
178160

179161
/// <summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
namespace RJCP.CodeQuality.NUnitExtensions
2+
{
3+
using System;
4+
5+
internal partial class TestContextAccessor
6+
{
7+
private static class WriteConsole
8+
{
9+
public static void Write(ulong value) { Console.Write(value); }
10+
11+
public static void Write(uint value) { Console.Write(value); }
12+
13+
public static void Write(string value) { Console.Write(value); }
14+
15+
public static void Write(float value) { Console.Write(value); }
16+
17+
public static void Write(object value) { Console.Write(value); }
18+
19+
public static void Write(decimal value) { Console.Write(value); }
20+
21+
public static void Write(long value) { Console.Write(value); }
22+
23+
public static void Write(int value) { Console.Write(value); }
24+
25+
public static void Write(double value) { Console.Write(value); }
26+
27+
public static void Write(char[] value) { Console.Write(value); }
28+
29+
public static void Write(char value) { Console.Write(value); }
30+
31+
public static void Write(bool value) { Console.Write(value); }
32+
33+
public static void Write(string format, object arg1) { Console.Write(format, arg1); }
34+
35+
public static void Write(string format, object arg1, object arg2) { Console.Write(format, arg1, arg2); }
36+
37+
public static void Write(string format, object arg1, object arg2, object arg3) { Console.Write(format, arg1, arg2, arg3); }
38+
39+
public static void Write(string format, params object[] args) { Console.Write(format, args); }
40+
41+
public static void WriteLine() { Console.WriteLine(); }
42+
43+
public static void WriteLine(ulong value) { Console.WriteLine(value); }
44+
45+
public static void WriteLine(uint value) { Console.WriteLine(value); }
46+
47+
public static void WriteLine(string value) { Console.WriteLine(value); }
48+
49+
public static void WriteLine(float value) { Console.WriteLine(value); }
50+
51+
public static void WriteLine(object value) { Console.WriteLine(value); }
52+
53+
public static void WriteLine(decimal value) { Console.WriteLine(value); }
54+
55+
public static void WriteLine(long value) { Console.WriteLine(value); }
56+
57+
public static void WriteLine(int value) { Console.WriteLine(value); }
58+
59+
public static void WriteLine(double value) { Console.WriteLine(value); }
60+
61+
public static void WriteLine(char[] value) { Console.WriteLine(value); }
62+
63+
public static void WriteLine(char value) { Console.WriteLine(value); }
64+
65+
public static void WriteLine(bool value) { Console.WriteLine(value); }
66+
67+
public static void WriteLine(string format, object arg1) { Console.WriteLine(format, arg1); }
68+
69+
public static void WriteLine(string format, object arg1, object arg2) { Console.WriteLine(format, arg1, arg2); }
70+
71+
public static void WriteLine(string format, object arg1, object arg2, object arg3) { Console.WriteLine(format, arg1, arg2, arg3); }
72+
73+
public static void WriteLine(string format, params object[] args) { Console.WriteLine(format, args); }
74+
}
75+
}
76+
}

CodeQuality/NUnitExtensions/TestContextAccessor.cs

+214-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,27 @@
55
using System.IO;
66
using System.Reflection;
77

8-
internal class TestContextAccessor
8+
internal partial class TestContextAccessor
99
{
10-
public static TestContextAccessor GetTestContext()
10+
private static readonly object s_Lock = new object();
11+
private static TestContextAccessor s_TestContext;
12+
13+
public static TestContextAccessor Instance
14+
{
15+
get
16+
{
17+
if (s_TestContext == null) {
18+
lock (s_Lock) {
19+
if (s_TestContext == null) {
20+
s_TestContext = Create();
21+
}
22+
}
23+
}
24+
return s_TestContext;
25+
}
26+
}
27+
28+
private static TestContextAccessor Create()
1129
{
1230
Assembly nUnitAssembly;
1331

@@ -102,6 +120,8 @@ private TestContextAccessor(Assembly nUnitAssembly)
102120
}
103121
}
104122
}
123+
124+
InitWriteAccessor(currentContext);
105125
}
106126

107127
private static PrivateObject GetCurrentContext(Assembly nUnitAssembly)
@@ -131,5 +151,197 @@ private TestAccessor Test
131151
public string TestName { get { return Test.Name; } }
132152

133153
public string TestFullName { get { return Test.FullName; } }
154+
155+
#region Write via Reflection
156+
private Action<ulong> m_WriteUlong;
157+
private Action<uint> m_WriteUint;
158+
private Action<string> m_WriteString;
159+
private Action<float> m_WriteFloat;
160+
private Action<object> m_WriteObject;
161+
private Action<decimal> m_WriteDecimal;
162+
private Action<long> m_WriteLong;
163+
private Action<int> m_WriteInt;
164+
private Action<double> m_WriteDouble;
165+
private Action<char[]> m_WriteCharArray;
166+
private Action<char> m_WriteChar;
167+
private Action<bool> m_WriteBool;
168+
private Action<string, object> m_WriteFormat1;
169+
private Action<string, object, object> m_WriteFormat2;
170+
private Action<string, object, object, object> m_WriteFormat3;
171+
private Action<string, object[]> m_WriteFormatS;
172+
173+
private Action m_WriteLine;
174+
private Action<ulong> m_WriteLineUlong;
175+
private Action<uint> m_WriteLineUint;
176+
private Action<string> m_WriteLineString;
177+
private Action<float> m_WriteLineFloat;
178+
private Action<object> m_WriteLineObject;
179+
private Action<decimal> m_WriteLineDecimal;
180+
private Action<long> m_WriteLineLong;
181+
private Action<int> m_WriteLineInt;
182+
private Action<double> m_WriteLineDouble;
183+
private Action<char[]> m_WriteLineCharArray;
184+
private Action<char> m_WriteLineChar;
185+
private Action<bool> m_WriteLineBool;
186+
private Action<string, object> m_WriteLineFormat1;
187+
private Action<string, object, object> m_WriteLineFormat2;
188+
private Action<string, object, object, object> m_WriteLineFormat3;
189+
private Action<string, object[]> m_WriteLineFormatS;
190+
191+
private void InitWriteAccessor(PrivateObject currentContext)
192+
{
193+
m_WriteUlong = GetDelegate<ulong>(currentContext.RealType, nameof(Write));
194+
m_WriteUint = GetDelegate<uint>(currentContext.RealType, nameof(Write));
195+
m_WriteString = GetDelegate<string>(currentContext.RealType, nameof(Write));
196+
m_WriteFloat = GetDelegate<float>(currentContext.RealType, nameof(Write));
197+
m_WriteObject = GetDelegate<object>(currentContext.RealType, nameof(Write));
198+
m_WriteDecimal = GetDelegate<decimal>(currentContext.RealType, nameof(Write));
199+
m_WriteLong = GetDelegate<long>(currentContext.RealType, nameof(Write));
200+
m_WriteInt = GetDelegate<int>(currentContext.RealType, nameof(Write));
201+
m_WriteDouble = GetDelegate<double>(currentContext.RealType, nameof(Write));
202+
m_WriteCharArray = GetDelegate<char[]>(currentContext.RealType, nameof(Write));
203+
m_WriteChar = GetDelegate<char>(currentContext.RealType, nameof(Write));
204+
m_WriteBool = GetDelegate<bool>(currentContext.RealType, nameof(Write));
205+
206+
MethodInfo methodInfoArgs1 = currentContext.RealType.GetMethod(nameof(Write), new Type[] { typeof(string), typeof(object) });
207+
if (methodInfoArgs1 == null)
208+
methodInfoArgs1 = typeof(WriteConsole).GetMethod(nameof(Write), new Type[] { typeof(string), typeof(object) });
209+
m_WriteFormat1 = (Action<string, object>)Delegate.CreateDelegate(typeof(Action<string, object>), methodInfoArgs1);
210+
211+
MethodInfo methodInfoArgs2 = currentContext.RealType.GetMethod(nameof(Write), new Type[] { typeof(string), typeof(object), typeof(object) });
212+
if (methodInfoArgs2 == null)
213+
methodInfoArgs2 = typeof(WriteConsole).GetMethod(nameof(Write), new Type[] { typeof(string), typeof(object), typeof(object) });
214+
m_WriteFormat2 = (Action<string, object, object>)Delegate.CreateDelegate(typeof(Action<string, object, object>), methodInfoArgs2);
215+
216+
MethodInfo methodInfoArgs3 = currentContext.RealType.GetMethod(nameof(Write), new Type[] { typeof(string), typeof(object), typeof(object), typeof(object) });
217+
if (methodInfoArgs3 == null)
218+
methodInfoArgs3 = typeof(WriteConsole).GetMethod(nameof(Write), new Type[] { typeof(string), typeof(object), typeof(object), typeof(object) });
219+
m_WriteFormat3 = (Action<string, object, object, object>)Delegate.CreateDelegate(typeof(Action<string, object, object, object>), methodInfoArgs3);
220+
221+
MethodInfo methodInfoArgsS = currentContext.RealType.GetMethod(nameof(Write), new Type[] { typeof(string), typeof(object[]) });
222+
if (methodInfoArgsS == null)
223+
methodInfoArgsS = typeof(WriteConsole).GetMethod(nameof(Write), new Type[] { typeof(string), typeof(object[]) });
224+
m_WriteFormatS = (Action<string, object[]>)Delegate.CreateDelegate(typeof(Action<string, object[]>), methodInfoArgs1);
225+
226+
m_WriteLineUlong = GetDelegate<ulong>(currentContext.RealType, nameof(WriteLine));
227+
m_WriteLineUint = GetDelegate<uint>(currentContext.RealType, nameof(WriteLine));
228+
m_WriteLineString = GetDelegate<string>(currentContext.RealType, nameof(WriteLine));
229+
m_WriteLineFloat = GetDelegate<float>(currentContext.RealType, nameof(WriteLine));
230+
m_WriteLineObject = GetDelegate<object>(currentContext.RealType, nameof(WriteLine));
231+
m_WriteLineDecimal = GetDelegate<decimal>(currentContext.RealType, nameof(WriteLine));
232+
m_WriteLineLong = GetDelegate<long>(currentContext.RealType, nameof(WriteLine));
233+
m_WriteLineInt = GetDelegate<int>(currentContext.RealType, nameof(WriteLine));
234+
m_WriteLineDouble = GetDelegate<double>(currentContext.RealType, nameof(WriteLine));
235+
m_WriteLineCharArray = GetDelegate<char[]>(currentContext.RealType, nameof(WriteLine));
236+
m_WriteLineChar = GetDelegate<char>(currentContext.RealType, nameof(WriteLine));
237+
m_WriteLineBool = GetDelegate<bool>(currentContext.RealType, nameof(WriteLine));
238+
239+
MethodInfo methodInfoLineArgs1 = currentContext.RealType.GetMethod(nameof(WriteLine), new Type[] { typeof(string), typeof(object) });
240+
if (methodInfoLineArgs1 == null)
241+
methodInfoLineArgs1 = typeof(WriteConsole).GetMethod(nameof(WriteLine), new Type[] { typeof(string), typeof(object) });
242+
m_WriteLineFormat1 = (Action<string, object>)Delegate.CreateDelegate(typeof(Action<string, object>), methodInfoLineArgs1);
243+
244+
MethodInfo methodInfoLineArgs2 = currentContext.RealType.GetMethod(nameof(WriteLine), new Type[] { typeof(string), typeof(object), typeof(object) });
245+
if (methodInfoLineArgs2 == null)
246+
methodInfoLineArgs2 = typeof(WriteConsole).GetMethod(nameof(WriteLine), new Type[] { typeof(string), typeof(object), typeof(object) });
247+
m_WriteLineFormat2 = (Action<string, object, object>)Delegate.CreateDelegate(typeof(Action<string, object, object>), methodInfoLineArgs2);
248+
249+
MethodInfo methodInfoLineArgs3 = currentContext.RealType.GetMethod(nameof(WriteLine), new Type[] { typeof(string), typeof(object), typeof(object), typeof(object) });
250+
if (methodInfoLineArgs3 == null)
251+
methodInfoLineArgs3 = typeof(WriteConsole).GetMethod(nameof(WriteLine), new Type[] { typeof(string), typeof(object), typeof(object), typeof(object) });
252+
m_WriteLineFormat3 = (Action<string, object, object, object>)Delegate.CreateDelegate(typeof(Action<string, object, object, object>), methodInfoLineArgs3);
253+
254+
MethodInfo methodInfoLineArgsS = currentContext.RealType.GetMethod(nameof(WriteLine), new Type[] { typeof(string), typeof(object[]) });
255+
if (methodInfoLineArgsS == null)
256+
methodInfoLineArgsS = typeof(WriteConsole).GetMethod(nameof(WriteLine), new Type[] { typeof(string), typeof(object[]) });
257+
m_WriteLineFormatS = (Action<string, object[]>)Delegate.CreateDelegate(typeof(Action<string, object[]>), methodInfoLineArgs1);
258+
259+
#if NETFRAMEWORK
260+
MethodInfo methodInfoLine = currentContext.RealType.GetMethod(nameof(WriteLine), new Type[] { });
261+
if (methodInfoLine == null)
262+
methodInfoLine = typeof(WriteConsole).GetMethod(nameof(WriteLine), new Type[] { });
263+
#else
264+
MethodInfo methodInfoLine = currentContext.RealType.GetMethod(nameof(WriteLine), Array.Empty<Type>());
265+
if (methodInfoLine == null)
266+
methodInfoLine = typeof(WriteConsole).GetMethod(nameof(WriteLine), Array.Empty<Type>());
267+
#endif
268+
m_WriteLine = (Action)Delegate.CreateDelegate(typeof(Action), methodInfoLine);
269+
}
270+
271+
private static Action<T> GetDelegate<T>(Type type, string methodName)
272+
{
273+
MethodInfo methodInfo = type.GetMethod(methodName, new Type[] { typeof(T) });
274+
if (methodInfo == null)
275+
methodInfo = typeof(WriteConsole).GetMethod(methodName, new Type[] { typeof(T) });
276+
277+
return (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), methodInfo);
278+
}
279+
280+
public void Write(ulong value) { m_WriteUlong(value); }
281+
282+
public void Write(uint value) { m_WriteUint(value); }
283+
284+
public void Write(string value) { m_WriteString(value); }
285+
286+
public void Write(float value) { m_WriteFloat(value); }
287+
288+
public void Write(object value) { m_WriteObject(value); }
289+
290+
public void Write(decimal value) { m_WriteDecimal(value); }
291+
292+
public void Write(long value) { m_WriteLong(value); }
293+
294+
public void Write(int value) { m_WriteInt(value); }
295+
296+
public void Write(double value) { m_WriteDouble(value); }
297+
298+
public void Write(char[] value) { m_WriteCharArray(value); }
299+
300+
public void Write(char value) { m_WriteChar(value); }
301+
302+
public void Write(bool value) { m_WriteBool(value); }
303+
304+
public void Write(string format, object arg1) { m_WriteFormat1(format, arg1); }
305+
306+
public void Write(string format, object arg1, object arg2) { m_WriteFormat2(format, arg1, arg2); }
307+
308+
public void Write(string format, object arg1, object arg2, object arg3) { m_WriteFormat3(format, arg1, arg2, arg3); }
309+
310+
public void Write(string format, params object[] args) { m_WriteFormatS(format, args); }
311+
312+
public void WriteLine() { m_WriteLine(); }
313+
314+
public void WriteLine(ulong value) { m_WriteLineUlong(value); }
315+
316+
public void WriteLine(uint value) { m_WriteLineUint(value); }
317+
318+
public void WriteLine(string value) { m_WriteLineString(value); }
319+
320+
public void WriteLine(float value) { m_WriteLineFloat(value); }
321+
322+
public void WriteLine(object value) { m_WriteLineObject(value); }
323+
324+
public void WriteLine(decimal value) { m_WriteLineDecimal(value); }
325+
326+
public void WriteLine(long value) { m_WriteLineLong(value); }
327+
328+
public void WriteLine(int value) { m_WriteLineInt(value); }
329+
330+
public void WriteLine(double value) { m_WriteLineDouble(value); }
331+
332+
public void WriteLine(char[] value) { m_WriteLineCharArray(value); }
333+
334+
public void WriteLine(char value) { m_WriteLineChar(value); }
335+
336+
public void WriteLine(bool value) { m_WriteLineBool(value); }
337+
338+
public void WriteLine(string format, object arg1) { m_WriteLineFormat1(format, arg1); }
339+
340+
public void WriteLine(string format, object arg1, object arg2) { m_WriteLineFormat2(format, arg1, arg2); }
341+
342+
public void WriteLine(string format, object arg1, object arg2, object arg3) { m_WriteLineFormat3(format, arg1, arg2, arg3); }
343+
344+
public void WriteLine(string format, params object[] args) { m_WriteLineFormatS(format, args); }
345+
#endregion
134346
}
135347
}

0 commit comments

Comments
 (0)