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