2013-08-12 17 views
13

mi piacerebbe provare il seguente flusso di lavoro asincrona (con NUnit + FsUnit):Come ottenere un utile stacktrace durante il test F # async flussi di lavoro

let foo = async { 
    failwith "oops" 
    return 42 
} 

ho scritto il seguente test per esso:

let [<Test>] TestFoo() = 
    foo 
    |> Async.RunSynchronously 
    |> should equal 42 

Da pippo getta ottengo il seguente stackTrace nella unit test runner:

System.Exception : oops 
    at Microsoft.FSharp.Control.CancellationTokenOps.RunSynchronously(CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout) 
    at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously(FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken) 
    at ExplorationTests.TestFoo() in ExplorationTests.fs: line 76 

Purtroppo la cerva stacktrace non dirmi dove è stata sollevata l'eccezione. Si ferma su RunSynchronously.

Da qualche parte ho sentito che Async.Catch ripristina magicamente lo stacktrace, così ho adattato il mio test:

let [<Test>] TestFooWithBetterStacktrace() = 
    foo 
    |> Async.Catch 
    |> Async.RunSynchronously 
    |> fun x -> match x with 
       | Choice1Of2 x -> x |> should equal 42 
       | Choice2Of2 ex -> raise (new System.Exception(null, ex)) 

Ora, questo è brutto, ma almeno produce un utile stacktrace:

System.Exception : Exception of type 'System.Exception' was thrown. 
    ----> System.Exception : oops 
    at Microsoft.FSharp.Core.Operators.Raise(Exception exn) 
    at ExplorationTests.TestFooWithBetterStacktrace() in ExplorationTests.fs: line 86 
--Exception 
    at Microsoft.FSharp.Core.Operators.FailWith(String message) 
    at [email protected](Unit unitVar) in ExplorationTests.fs: line 71 
    at [email protected](AsyncParams`1 args) 

Questo time lo stacktrace mostra esattamente dove si è verificato l'errore: [email protected] 71

C'è un modo per sbarazzarsi di Async.Catch e l'abbinamento tra due scelte pur ottenendo ancora utili stacktraces? C'è un modo migliore per strutturare i test del flusso di lavoro asincrono?

+1

Ho avuto lo stesso problema e Async.Catch è stata l'unica soluzione alternativa che ho trovato –

+0

Ho inviato un'email a Don Syme, il quale ha suggerito che si trattava di una limitazione .NET fondamentale e che "Async.Catch" era l'unica opzione. –

+0

@JohnPalmer mi suona come una risposta – mydogisbox

risposta

5

Dal Async.Catch e rethrowing l'eccezione sembra essere l'unico modo per ottenere uno stacktrace utile mi si avvicinò con il seguente:

type Async with 
    static member Rethrow x = 
    match x with 
     | Choice1Of2 x -> x 
     | Choice2Of2 ex -> ExceptionDispatchInfo.Capture(ex).Throw() 
         failwith "nothing to return, but will never get here" 

Nota "ExceptionDispatchInfo.Capture (ex) .Throw ()". Questo è il modo più bello in cui si può rilanciare un'eccezione senza corrompere il suo stacktrace (lato negativo: disponibile solo da .NET 4.5).

Ora posso riscrivere il test "TestFooWithBetterStacktrace" come quello:

let [<Test>] TestFooWithBetterStacktrace() = 
    foo 
    |> Async.Catch 
    |> Async.RunSynchronously 
    |> Async.Rethrow 
    |> should equal 42 

Il test sembra molto meglio, il codice rethrowing non aspira (tanto quanto prima) e ottengo stacktraces utili nel test corridore quando qualcosa va storto.

+0

+1 Questa sembra una soluzione gradevole. – Daniel

3

Citando alcune e-mail che ho inviato Don Syme un po 'indietro:

L'esperienza di debug dovrebbe migliorare se si prova a impostare "catch First eccezioni Chance" in Debug -> Eccezioni -> CLR Eccezioni . Disabilitare anche lo off "Solo il mio codice".

e

destro. Con async {...}, i calcoli non sono vincolati allo stack, quindi le eccezioni devono essere ridestinate in alcuni punti per riportarle al thread destro .

L'uso giudizioso di Async.Catch o altre operazioni di gestione delle eccezioni possono anche aiutare .

+0

ciao john, la citazione n. 1 che consente di rompere le eccezioni della prima possibilità non aiuta molto poiché non uso quasi mai il debugger. Ho bisogno di stacktraces utili nell'output del runner di test dell'unità. la citazione # 2 significa che lo sto già facendo nel mio secondo test (TestFooWithBetterStacktrace)? – stmax

+0

@stmax - Penso che il tuo secondo test sia buono. –