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 }