Skip to content

Commit bea4c7e

Browse files
committed
More generic FailWhen and WhenError implementation, refined tests and docs
1 parent eec7bda commit bea4c7e

File tree

5 files changed

+105
-36
lines changed

5 files changed

+105
-36
lines changed

XakeLibTests/ActionTests.fs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -243,19 +243,12 @@ let ``exception handling with 'try finally'``() =
243243

244244
phony "main" (action {
245245
note "before try"
246-
247246
try
248247
printfn "Body executed"
249248
do! anote "try"
250249
finally
251250
printfn "Finally executed"
252251
do note "finally"
253-
254-
//try
255-
// failwith "ee"
256-
//with e ->
257-
// do note e.Message
258-
259252
do note "4"
260253
})
261254
}
@@ -335,13 +328,32 @@ let ``WhenError function to handle exceptions within actions``() =
335328
WhenError (fun _ -> excCount := 1) <|
336329
action {
337330
printfn "Some useful job"
338-
do! taskReturn 3 |> FailWhen ((=) 3) "err"
331+
do! taskReturn 3 |> FailWhen ((=) 3) "err" |> Action.Ignore
339332
printfn "This wont run"
340333
})
341334
]
342335
}
343336

344337
Assert.AreEqual(1, !excCount)
345338

339+
[<Test>]
340+
let ``try/with for the whole script body``() =
341+
let excCount = ref 0
342+
do xake DebugOptions {
343+
rules [
344+
"main" =>
345+
action {
346+
try
347+
printfn "Some useful job"
348+
do 3/0 |> ignore
349+
printfn "This wont run"
350+
with _ ->
351+
excCount := 1
352+
}
353+
]
354+
}
355+
356+
Assert.AreEqual(1, !excCount)
357+
346358
// TODO use!, try with exception within action
347359

XakeLibTests/MiscTests.fs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ let ``resource set instantiation``() =
8585
let ``script exits with errorlevel on script failure``() =
8686

8787
let errorCode = ref 0
88-
System.IO.Directory.CreateDirectory("1")
88+
System.IO.Directory.CreateDirectory("1") |> ignore
8989

9090
do xake {xakeOptions with Threads = 1; FileLog="exits-with-errorlevel.log"; FileLogLevel = Verbosity.Diag; Targets = ["one"] } {
9191
rules [
@@ -125,7 +125,7 @@ let ``failif is a short circuit for task result``() =
125125
do xake {xakeOptions with Threads = 1; FileLog="failf.log"} {
126126
rules [
127127
"main" => (action {
128-
do! taskReturn 3 |> FailWhen ((=) 3) "err"
128+
do! taskReturn 3 |> FailWhen ((=) 3) "err" |> Action.Ignore
129129
} |> WhenError (fun _ -> excCount := 1))
130130
]
131131
}
@@ -136,13 +136,28 @@ let ``failif is a short circuit for task result``() =
136136
let ``WhenError handler intercepts the error``() =
137137

138138
let ex = ref 0
139-
do xake {xakeOptions with Threads = 1; FileLog="failf.log"} {
140139

140+
// pipe result, and provide fallback value in case of error
141+
do xake {xakeOptions with Threads = 1; FileLog="failf.log"} {
142+
rules [
143+
"main" => action {
144+
do! taskReturn 3
145+
|> FailWhen ((=) 3) "fail"
146+
|> WhenError (fun _ -> ex := 1; 0)
147+
|> Action.Ignore
148+
}
149+
]
150+
}
151+
// intercept error for resultless action
152+
do xake {xakeOptions with Threads = 1; FileLog="failf.log"} {
141153
rules [
142154
"main" => action {
143-
do! taskReturn 3 |> FailWhen ((=) 3) "fail" |> WhenError (fun _ -> ex := 1)
155+
do! taskReturn 3
156+
|> FailWhen ((=) 3) "fail"
157+
|> Action.Ignore
158+
|> WhenError (fun _ -> ex := !ex + 1)
144159
}
145160
]
146161
}
147162

148-
Assert.AreEqual(1, !ex)
163+
Assert.AreEqual(2, !ex)

core/ActionFunctions.fs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ module ActionFuncs =
3939
/// </summary>
4040
/// <param name="cond"></param>
4141
/// <param name="act"></param>
42-
let FailWhen cond err act = Action (fun (r,c) ->
43-
async {
44-
let! (r',c') = A.runAction act (r,c)
45-
if cond c' then failwith err
46-
return (r',())
47-
})
42+
let FailWhen cond err (act: Action<_,_>) =
43+
action {
44+
let! b = act
45+
if cond b then failwith err
46+
return b
47+
}
4848

4949
/// <summary>
5050
/// Supplemental for FailWhen to verify errorlevel set by system command.
@@ -61,13 +61,10 @@ module ActionFuncs =
6161
/// Wraps action so that exceptions occured while executing action are ignored.
6262
/// </summary>
6363
/// <param name="act"></param>
64-
let WhenError h (act:Action<'a,unit>) =
65-
Action (fun (r,a) -> async {
64+
let WhenError handler (act:Action<_,_>) =
65+
action {
6666
try
67-
let! (r',_) = A.runAction act (r,a)
68-
return (r',())
69-
with
70-
e ->
71-
do h e
72-
return (r,())
73-
})
67+
let! r = act
68+
return r
69+
with e -> return handler e
70+
}

docs/implnotes.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,31 @@ Error handling assumes the following system behavior:
2929
* idea: dump the whole trace to the target
3030
* setting error-code for the fsi sessions
3131

32-
### Ideas in progress
33-
Idea #1: WhenError function which intercepts any failures and lets define a custom handler
32+
### Implemented ideas
33+
34+
#### WhenError function
35+
Intercepts errors (exceptions) and allows to define a custom handler.
3436
```
3537
phony "main" (action {
3638
do! trace Message "The exception thrown below will be silently ignored"
3739
failwith "some error"
3840
} |> WhenError ignore)
3941
```
4042

41-
Idea #2 (orthogonal): special directive to fail next command on non-zero result
43+
#### FailWhen
44+
Raises the exception if action's result meet specified condition.
45+
E.g. the following code raises error in case errorlevel (result of shell command execution) gets non-zero value.
4246
```
43-
fail_if ((<>) 0) _system [shellcmd] "dir"
44-
fail_on_errorlevel _system [shellcmd] "dir"
45-
// where shellcmd and fail_on_error are functions
46-
47-
// or just function:
4847
do! _system [shellcmd] "dir" |> FailWhen ((<>) 0) "Failed to list files in folder"
4948
// or just
5049
do! _system [shellcmd] "dir" |> CheckErrorLevel
50+
```
51+
52+
#### try/with/finally exception handling
53+
`action` computation expression supports try/with and try/finally blocks. In most cases these constructs are more
54+
elegant than using WhenError.
5155

56+
### Other ideas
5257

5358
// or even that:
5459
_system [fail_on_error; shellcmd] "dir"
@@ -61,6 +66,7 @@ do! _system [fail_on_error; shellcmd; startin "./bin"] "dir"
6166
// where shellcmd and fail_on_error are functions
6267
```
6368
69+
Let action be provided as finally argument.
6470
6571
### Ideas
6672
Implemented IgnoreErrors.

docs/overview.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,45 @@ If the task (action) returns a value which you do not need use Action.Ignore:
294294
}
295295
```
296296

297+
### Exception handling
298+
`action` block allows to handle exceptions with idiomatic try/with and try/finally blocks.
299+
```fsharp
300+
phony "main" (action {
301+
do! trace Level.Info "before try" // trace is standard action reporting to file/screen log
302+
try
303+
try
304+
do! trace Level.Info "try"
305+
failwith "Ouch"
306+
with e ->
307+
printfn "Error '%s' occured" e.Message
308+
finally
309+
printfn "Finally executed"
310+
311+
printfn "execution continues after try blocks"
312+
})
313+
```
314+
Notice `trace` function just like any other actions cannot be used inside `with` and `finally` blocks due to language limitations.
315+
316+
`WhenError` function is another option to handle errors.
317+
```fsharp
318+
action {
319+
printfn "Some useful job"
320+
do! action {failwith "err"} |> WhenError (fun _ -> printfn "caught the error")
321+
}
322+
```
323+
or this way
324+
```fsharp
325+
rules [
326+
"main" => (
327+
WhenError ignore <| // ignore is a standard f# function accepting one argument
328+
action {
329+
printfn "Some useful job"
330+
failwith "File IO error!"
331+
printfn "This wont run"
332+
})
333+
]
334+
```
335+
297336
### need
298337

299338
`need` function is widely used internally and it is a key element for dependency tracking. Calling `need` ensures the requested files are built according to rules.

0 commit comments

Comments
 (0)