A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System;
5using System.Diagnostics;
6using System.Linq;
7using System.Reflection;
8using NUnit.Framework;
9using NUnit.Framework.Internal;
10
11namespace osu.Framework.Development
12{
13 public static class DebugUtils
14 {
15 public static bool IsNUnitRunning => is_nunit_running.Value;
16
17 private static readonly Lazy<bool> is_nunit_running = new Lazy<bool>(() =>
18 {
19 var entry = Assembly.GetEntryAssembly();
20
21 // when running under nunit + netcore, entry assembly becomes nunit itself (testhost, Version=15.0.0.0), which isn't what we want.
22 // when running under nunit + Rider > 2020.2 EAP6, entry assembly becomes ReSharperTestRunner[32|64], which isn't what we want.
23 bool entryIsKnownTestAssembly = entry != null && (entry.Location.Contains("testhost") || entry.Location.Contains("ReSharperTestRunner"));
24
25 // null assembly can indicate nunit, but it can also indicate native code (e.g. android).
26 // to distinguish nunit runs from android launches, check the class name of the current test.
27 // if no actual test is running, nunit will make up an ad-hoc test context, which we can match on
28 // to eliminate such false positives.
29 bool nullEntryWithActualTestContext = entry == null && TestContext.CurrentContext.Test.ClassName != typeof(TestExecutionContext.AdhocContext).FullName;
30
31 return entryIsKnownTestAssembly || nullEntryWithActualTestContext;
32 }
33 );
34
35 private static readonly Lazy<Assembly> nunit_test_assembly = new Lazy<Assembly>(() =>
36 {
37 Debug.Assert(IsNUnitRunning);
38
39 var testName = TestContext.CurrentContext.Test.ClassName;
40 return AppDomain.CurrentDomain.GetAssemblies().First(asm => asm.GetType(testName) != null);
41 }
42 );
43
44 public static bool IsDebugBuild => is_debug_build.Value;
45
46 private static readonly Lazy<bool> is_debug_build = new Lazy<bool>(() =>
47 isDebugAssembly(typeof(DebugUtils).Assembly) || isDebugAssembly(GetEntryAssembly())
48 );
49
50 /// <summary>
51 /// Whether the framework is currently logging performance issues.
52 /// This should be used only when a configuration is not available via DI or otherwise (ie. in a static context).
53 /// </summary>
54 public static bool LogPerformanceIssues { get; internal set; }
55
56 // https://stackoverflow.com/a/2186634
57 private static bool isDebugAssembly(Assembly assembly) => assembly?.GetCustomAttributes(false).OfType<DebuggableAttribute>().Any(da => da.IsJITTrackingEnabled) ?? false;
58
59 /// <summary>
60 /// Gets the entry assembly, or calling assembly otherwise.
61 /// When running under NUnit, the assembly of the current test will be returned instead.
62 /// </summary>
63 /// <returns>The entry assembly (usually obtained via <see cref="Assembly.GetEntryAssembly()"/>.</returns>
64 public static Assembly GetEntryAssembly()
65 {
66 if (IsNUnitRunning)
67 return nunit_test_assembly.Value;
68
69 return Assembly.GetEntryAssembly() ?? Assembly.GetCallingAssembly();
70 }
71
72 /// <summary>
73 /// Gets the absolute path to the directory containing the assembly determined by <see cref="GetEntryAssembly"/>.
74 /// </summary>
75 /// <returns>The entry path (usually obtained via the entry assembly's <see cref="Assembly.Location"/> directory.</returns>
76 [Obsolete("Use AppContext.BaseDirectory instead")] // Can be removed 20220211
77 public static string GetEntryPath() => AppContext.BaseDirectory;
78 }
79}