SwensenSoftware / unquote

Write F# unit test assertions as quoted expressions, get step-by-step failure messages for free
http://www.swensensoftware.com/unquote
Apache License 2.0
287 stars 25 forks source link

When using pipe and testing for exception "raises" finds reflection exception instead #63

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?

                 member this.RaiseException(message:string)=
                        raise (NotSupportedException(message))

        [<Test>] member this.``Bad Unquote Returning it's own reflection exception`` ()=
                        raises<NotSupportedException> <@ "Should be a NotSupportedException" |> this.RaiseException  @>

What is the expected output? 
the Test passing.

What do you see instead?

Expected NotSupportedException but got TargetInvocationException

"Should be a NotSupportedException" |> UnitTest.FSharp.Tests.Module1+Basic 
Dynamic Operator Tests.RaiseException
System.Reflection.TargetInvocationException: Exception has been thrown by the 
target of an invocation. ---> System.NotSupportedException: Should be a 
NotSupportedException
   at UnitTest.FSharp.Tests.Module1.TestCase Dynamic Operator Tests.RaiseException[a](String message) in C:\Documents and Settings\username\My Documents\Visual Studio 2010\Projects\testproject\UnitTest.FSharp\TestCase.fs:line 30
   --- End of inner exception stack trace ---

Server stack trace: 
   at System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
   at System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at <StartupCode$FSharp-Core>.$Reflect.Invoke@617-4.Invoke(T1 inp)

Exception rethrown at [0]: 
   at Swensen.Unquote.Evaluation.eval(FSharpMap`2 env, FSharpExpr expr)
   at Swensen.Unquote.Reduction.reduce(FSharpMap`2 env, FSharpExpr expr)
   at Swensen.Unquote.Reduction.loop@119(FSharpMap`2 env, FSharpExpr expr, FSharpList`1 acc)

What version of the product are you using? On what operating system?

Unquote.2.0.2

Please provide any additional information below.

Original issue reported on code.google.com by tech.ekon.us on 3 Aug 2011 at 2:23

GoogleCodeExporter commented 9 years ago
Thank you for reporting this very interesting and very important bug!

While Unquote did take measures to strip outermost reflective 
TargetInvocationExceptions, it did not account for a unique case in F# where 
there may be nested TargetInvocationExceptions. 

The case is this: sometimes when core operators such as raises are invoked from 
assemblies which are not compiled by F# (such as through the .NET reflection 
assemblies, an implementation detail of Unquote's quotation evaluator), the 
operators themselves are exposed as reflective calls, so in the case of this 
bug we get to levels of reflective calls and therefore two nested 
TargetInvocationExceptions.

This issue has been fixed in trunk and will be available in a new release very 
soon.

Note that stripping nested TargetInvocationExceptions in this way forces us 
into a compromise: we effectively cannot test for 
raises<TargetInvocationException> because we can't always tell whether the 
intention is to preserve or strip those reflective exceptions.

While fixing this issue I also decided to use a different strategy for 
preserving stacktraces when extracting inner exceptions from (possibly nested) 
TargetInvocationExceptions. Instead of using the PrepForRemoting() non-public 
method on the exception, which in addition to preserving the stack trace also 
produces a verbose message as you've seen, I am now using a strategy which only 
preserves the stack trace and uses the original message. I think this is closer 
to what folks would expect if F# provided native "eval", reflection is just an 
implementation detail.

Original comment by stephen....@gmail.com on 6 Aug 2011 at 3:18

GoogleCodeExporter commented 9 years ago

Original comment by stephen....@gmail.com on 7 Aug 2011 at 6:04