1 // ***********************************************************************
2 // Copyright (c) 2011 Charlie Poole
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
23 
24 using System;
25 using System.Reflection;
26 using System.Text.RegularExpressions;
27 using System.Threading;
28 using NUnit.Framework.Api;
29 
30 namespace NUnit.Framework.Internal.Commands
31 {
32     /// <summary>
33     /// TODO: Documentation needed for class
34     /// </summary>
35     public class ExpectedExceptionCommand : DelegatingTestCommand
36     {
37         private ExpectedExceptionData exceptionData;
38 
39         /// <summary>
40         /// Initializes a new instance of the <see cref="ExpectedExceptionCommand"/> class.
41         /// </summary>
42         /// <param name="innerCommand">The inner command.</param>
43         /// <param name="exceptionData">The exception data.</param>
ExpectedExceptionCommand(TestCommand innerCommand, ExpectedExceptionData exceptionData)44         public ExpectedExceptionCommand(TestCommand innerCommand, ExpectedExceptionData exceptionData)
45             : base(innerCommand)
46         {
47             this.exceptionData = exceptionData;
48         }
49 
50 
51         /// <summary>
52         /// Runs the test, saving a TestResult in the supplied TestExecutionContext
53         /// </summary>
54         /// <param name="context">The context in which the test is to be run.</param>
55         /// <returns>A TestResult</returns>
Execute(TestExecutionContext context)56         public override TestResult Execute(TestExecutionContext context)
57         {
58             try
59             {
60                 context.CurrentResult = innerCommand.Execute(context);
61 
62                 if (context.CurrentResult.ResultState == ResultState.Success)
63                     ProcessNoException(context);
64             }
65             catch (Exception ex)
66             {
67 #if !NETCF && !SILVERLIGHT && !__TVOS__ && !__WATCHOS__
68                 if (ex is ThreadAbortException)
69                     Thread.ResetAbort();
70 #endif
71                 ProcessException(ex, context);
72             }
73 
74             return context.CurrentResult;
75         }
76 
77         /// <summary>
78         /// Handles processing when no exception was thrown.
79         /// </summary>
80         /// <param name="context">The execution context.</param>
ProcessNoException(TestExecutionContext context)81         public void ProcessNoException(TestExecutionContext context)
82         {
83             context.CurrentResult.SetResult(ResultState.Failure, NoExceptionMessage());
84         }
85 
86         /// <summary>
87         /// Handles processing when an exception was thrown.
88         /// </summary>
89         /// <param name="exception">The exception.</param>
90         /// <param name="context">The execution context.</param>
ProcessException(Exception exception, TestExecutionContext context)91         public void ProcessException(Exception exception, TestExecutionContext context)
92         {
93             if (exception is NUnitException)
94                 exception = exception.InnerException;
95 
96             if (IsExpectedExceptionType(exception))
97             {
98                 if (IsExpectedMessageMatch(exception))
99                 {
100                     if (context.TestObject != null)
101                     {
102                         MethodInfo exceptionMethod = exceptionData.GetExceptionHandler(context.TestObject.GetType());
103                         if (exceptionMethod != null)
104                         {
105                             Reflect.InvokeMethod(exceptionMethod, context.TestObject, exception);
106                         }
107                         else
108                         {
109                             IExpectException handler = context.TestObject as IExpectException;
110                             if (handler != null)
111                                 handler.HandleException(exception);
112                         }
113                     }
114 
115                     context.CurrentResult.SetResult(ResultState.Success);
116                 }
117                 else
118                 {
119                     context.CurrentResult.SetResult(ResultState.Failure, WrongTextMessage(exception), GetStackTrace(exception));
120                 }
121             }
122             else
123             {
124                 context.CurrentResult.RecordException(exception);
125 
126                 // If it shows as an error, change it to a failure due to the wrong type
127                 if (context.CurrentResult.ResultState == ResultState.Error)
128                     context.CurrentResult.SetResult(ResultState.Failure, WrongTypeMessage(exception), GetStackTrace(exception));
129             }
130         }
131 
132         #region Helper Methods
133 
IsExpectedExceptionType(Exception exception)134         private bool IsExpectedExceptionType(Exception exception)
135         {
136             return exceptionData.ExpectedExceptionName == null ||
137                 exceptionData.ExpectedExceptionName.Equals(exception.GetType().FullName);
138         }
139 
IsExpectedMessageMatch(Exception exception)140         private bool IsExpectedMessageMatch(Exception exception)
141         {
142             if (exceptionData.ExpectedMessage == null)
143                 return true;
144 
145             switch (exceptionData.MatchType)
146             {
147                 case MessageMatch.Exact:
148                 default:
149                     return exceptionData.ExpectedMessage.Equals(exception.Message);
150                 case MessageMatch.Contains:
151                     return exception.Message.IndexOf(exceptionData.ExpectedMessage) >= 0;
152                 case MessageMatch.Regex:
153                     return Regex.IsMatch(exception.Message, exceptionData.ExpectedMessage);
154                 case MessageMatch.StartsWith:
155                     return exception.Message.StartsWith(exceptionData.ExpectedMessage);
156             }
157         }
158 
NoExceptionMessage()159         private string NoExceptionMessage()
160         {
161             string expectedType = exceptionData.ExpectedExceptionName == null ? "An Exception" : exceptionData.ExpectedExceptionName;
162             return CombineWithUserMessage(expectedType + " was expected");
163         }
164 
WrongTypeMessage(Exception exception)165         private string WrongTypeMessage(Exception exception)
166         {
167             return CombineWithUserMessage(
168                 "An unexpected exception type was thrown" + Env.NewLine +
169                 "Expected: " + exceptionData.ExpectedExceptionName + Env.NewLine +
170                 " but was: " + exception.GetType().FullName + " : " + exception.Message);
171         }
172 
WrongTextMessage(Exception exception)173         private string WrongTextMessage(Exception exception)
174         {
175             string expectedText;
176             switch (exceptionData.MatchType)
177             {
178                 default:
179                 case MessageMatch.Exact:
180                     expectedText = "Expected: ";
181                     break;
182                 case MessageMatch.Contains:
183                     expectedText = "Expected message containing: ";
184                     break;
185                 case MessageMatch.Regex:
186                     expectedText = "Expected message matching: ";
187                     break;
188                 case MessageMatch.StartsWith:
189                     expectedText = "Expected message starting: ";
190                     break;
191             }
192 
193             return CombineWithUserMessage(
194                 "The exception message text was incorrect" + Env.NewLine +
195                 expectedText + exceptionData.ExpectedMessage + Env.NewLine +
196                 " but was: " + exception.Message);
197         }
198 
CombineWithUserMessage(string message)199         private string CombineWithUserMessage(string message)
200         {
201             if (exceptionData.UserMessage == null)
202                 return message;
203             return exceptionData.UserMessage + Env.NewLine + message;
204         }
205 
GetStackTrace(Exception exception)206         private string GetStackTrace(Exception exception)
207         {
208             try
209             {
210                 return exception.StackTrace;
211             }
212             catch (Exception)
213             {
214                 return "No stack trace available";
215             }
216         }
217 
218         #endregion
219     }
220 }