A game about forced loneliness, made by TACStudios
at master 128 lines 5.1 kB view raw
1using System; 2using NUnit.Framework; 3using NUnit.Framework.Constraints; 4using UnityEngine.Profiling; 5 6namespace UnityEngine.TestTools.Constraints 7{ 8 /// <summary> 9 /// An NUnit test constraint class to test whether a given block of code makes any GC allocations. 10 /// 11 /// Use this class with NUnit's Assert.That() method to make assertions about the GC behaviour of your code. The constraint executes the delegate you provide, and checks if it has caused any GC memory to be allocated. If any GC memory was allocated, the constraint passes; otherwise, the constraint fails. 12 /// 13 /// Usually you negate this constraint to make sure that your delegate does not allocate any GC memory. This is easy to do using the Is class: 14 /// </summary> 15 /// <example> 16 /// <code> 17 /// using NUnit.Framework; 18 /// using UnityEngine.TestTools.Constraints; 19 /// using Is = UnityEngine.TestTools.Constraints.Is; 20 /// 21 /// public class MyTestClass 22 /// { 23 /// [Test] 24 /// public void SettingAVariableDoesNotAllocate() 25 /// { 26 /// Assert.That(() => { 27 /// int a = 0; 28 /// a = 1; 29 /// }, Is.Not.AllocatingGCMemory()); 30 /// } 31 /// } 32 /// </code> 33 /// </example> 34 public class AllocatingGCMemoryConstraint : Constraint 35 { 36 private class AllocatingGCMemoryResult : ConstraintResult 37 { 38 private readonly int diff; 39 public AllocatingGCMemoryResult(IConstraint constraint, object actualValue, int diff) : base(constraint, actualValue, diff > 0) 40 { 41 this.diff = diff; 42 } 43 44 public override void WriteMessageTo(MessageWriter writer) 45 { 46 if (diff == 0) 47 writer.WriteMessageLine("The provided delegate did not make any GC allocations."); 48 else 49 writer.WriteMessageLine("The provided delegate made {0} GC allocation(s).", diff); 50 } 51 } 52 53 private ConstraintResult ApplyTo(Action action, object original) 54 { 55 var recorder = Recorder.Get("GC.Alloc"); 56 57 // The recorder was created enabled, which means it captured the creation of the Recorder object itself, etc. 58 // Disabling it flushes its data, so that we can retrieve the sample block count and have it correctly account 59 // for these initial allocations. 60 recorder.enabled = false; 61 62#if !UNITY_WEBGL 63 recorder.FilterToCurrentThread(); 64#endif 65 66 recorder.enabled = true; 67 68 try 69 { 70 action(); 71 } 72 finally 73 { 74 recorder.enabled = false; 75#if !UNITY_WEBGL 76 recorder.CollectFromAllThreads(); 77#endif 78 } 79 80 return new AllocatingGCMemoryResult(this, original, recorder.sampleBlockCount); 81 } 82 83 /// <summary> 84 /// Applies GC memory constraint to the test. 85 /// </summary> 86 /// <param name="obj">An object to apply the GC constraint to. Should be a <see cref="TestDelegate"/>.</param> 87 /// <returns>A ConstraintResult</returns> 88 /// <exception cref="ArgumentNullException">Throws a <see cref="ArgumentNullException"/> if the provided object is null.</exception> 89 /// <exception cref="ArgumentException">Throws a <see cref="ArgumentException"/> if the provided object is not a <see cref="TestDelegate"/>.</exception> 90 public override ConstraintResult ApplyTo(object obj) 91 { 92 if (obj == null) 93 throw new ArgumentNullException(); 94 95 TestDelegate d = obj as TestDelegate; 96 if (d == null) 97 throw new ArgumentException(string.Format("The actual value must be a TestDelegate but was {0}", 98 obj.GetType())); 99 100 return ApplyTo(() => d.Invoke(), obj); 101 } 102 103 /// <summary> 104 /// Test whether the constraint is satisfied by a given reference. 105 /// The default implementation simply dereferences the value but 106 /// derived classes may override it to provide for delayed processing. 107 /// </summary> 108 /// <typeparam name="TActual">The type of the actual value delegate to be tested.</typeparam> 109 /// <param name="del">A reference to the value delegate to be tested</param> 110 /// <returns>A ConstraintResult</returns> 111 /// <exception cref="ArgumentNullException">Throws a <see cref="ArgumentNullException"/> if the provided delegate is null.</exception> 112 public override ConstraintResult ApplyTo<TActual>(ActualValueDelegate<TActual> del) 113 { 114 if (del == null) 115 throw new ArgumentNullException(); 116 117 return ApplyTo(() => del.Invoke(), del); 118 } 119 120 /// <summary> 121 /// The Description of what this constraint tests, for to use in messages and in the ConstraintResult. 122 /// </summary> 123 public override string Description 124 { 125 get { return "allocates GC memory"; } 126 } 127 } 128}