1 // *********************************************************************** 2 // Author: 3 // Alexander Kyte <alexander.kyte@xamarin.com> 4 // 5 // Copyright (c) 2016 Xamarin, Inc 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining 8 // a copy of this software and associated documentation files (the 9 // "Software"), to deal in the Software without restriction, including 10 // without limitation the rights to use, copy, modify, merge, publish, 11 // distribute, sublicense, and/or sell copies of the Software, and to 12 // permit persons to whom the Software is furnished to do so, subject to 13 // the following conditions: 14 // 15 // The above copyright notice and this permission notice shall be 16 // included in all copies or substantial portions of the Software. 17 // 18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 // *********************************************************************** 26 27 using System; 28 using System.Diagnostics; 29 using System.Threading; 30 using NUnit.Framework.Api; 31 using System.Collections.Generic; 32 using System.Runtime.Remoting.Messaging; 33 34 namespace NUnit.Framework.Internal 35 { 36 [Serializable] 37 class Container : ILogicalThreadAffinative { 38 public Guid guid; Container(Guid guid)39 public Container(Guid guid) { 40 this.guid = guid; 41 } 42 } 43 44 public class FinallyDelegate 45 { 46 // If our test spawns a thread that throws, we will bubble 47 // up to the top level. To handle this, we need to have the 48 // failure logic in a place that can be called from the top level. 49 // The UnhandledException callback should run on a thread that isn't 50 // the one dispatching the tests so this should work to ensure 51 // progress in the face of exceptions on other threads 52 // 53 // Essentially this is a poor-man's finally clause that's 54 // guaranteed to run because we pin it to the UnhandledException 55 // callback 56 57 // Because of CompositeWorkItem, we have a runtime stack of work items 58 // so we need a stack of finally delegate continuations 59 Stack<Tuple<TestExecutionContext, long, TestResult>> testStack; 60 61 Dictionary<Guid, TestResult> lookupTable; 62 63 // Why is CallContext used? Consider the following scenario: 64 // 65 // * say Test_A runs in Thread_1 66 // * Test_A spawns another Thread_2 67 // * Thread_1 finishes with Test_A, and moves on to Test_B 68 // * Thread_2 isn't done yet really, crashes and causes an unhandled exception 69 // * we need a way to map this unhandled exception to Test_A, although Test_B is the currently running test 70 // 71 // => what we need is some sort of "thread local" that gets inherited: when 72 // Thread_1 creates Thread_2, it needs to have the same "thread local" values. 73 // that is achieved with `CallContext`. 74 // 75 // Unfortunately, remoting isn't available on every platform (it will 76 // throw PlatformNotSupportedexception), thus we can't support this 77 // scenario. Luckily, this scenario is very rare. 78 79 Container container = null; 80 private static readonly string CONTEXT_KEY = "TestResultName"; 81 FinallyDelegate()82 public FinallyDelegate () { 83 this.testStack = new Stack<Tuple<TestExecutionContext, long, TestResult>>(); 84 this.lookupTable = new Dictionary<Guid, TestResult>(); 85 } 86 Set(TestExecutionContext context, long startTicks, TestResult result)87 public void Set (TestExecutionContext context, long startTicks, TestResult result) { 88 var frame = new Tuple<TestExecutionContext, long, TestResult>(context, startTicks, result); 89 90 /* keep name in LogicalCallContext, because this will be inherited by 91 * Threads spawned by the test case */ 92 var guid = Guid.NewGuid(); 93 try { 94 CallContext.SetData(CONTEXT_KEY, new Container(guid)); 95 } catch { 96 container = new Container (guid); 97 } 98 99 this.lookupTable.Add(guid, result); 100 this.testStack.Push(frame); 101 } 102 HandleUnhandledExc(Exception ex)103 public void HandleUnhandledExc (Exception ex) { 104 Container c = container ?? (Container) CallContext.GetData(CONTEXT_KEY); 105 TestResult result = this.lookupTable [c.guid]; 106 result.RecordException(ex); 107 result.ThreadCrashFail = true; 108 } 109 Complete()110 public void Complete () { 111 var frame = this.testStack.Pop(); 112 113 TestExecutionContext context = frame.Item1; 114 long startTicks = frame.Item2; 115 TestResult result = frame.Item3; 116 117 #if (CLR_2_0 || CLR_4_0) && !SILVERLIGHT && !NETCF_2_0 118 long tickCount = Stopwatch.GetTimestamp() - startTicks; 119 double seconds = (double)tickCount / Stopwatch.Frequency; 120 result.Duration = TimeSpan.FromSeconds(seconds); 121 #else 122 result.Duration = DateTime.Now - Context.StartTime; 123 #endif 124 125 result.AssertCount = context.AssertCount; 126 127 context.Listener.TestFinished(result); 128 129 context = context.Restore(); 130 context.AssertCount += result.AssertCount; 131 } 132 } 133 } 134