Skip to content

Add null-checks for each function that takes an IAsyncEnumerable or otherwise nullable type #127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,8 @@ The following are the current surface area of the `TaskSeq` utility functions, o

```f#
module TaskSeq =
val append: source1: #taskSeq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T>
val appendSeq: source1: #taskSeq<'T> -> source2: #seq<'T> -> taskSeq<'T>
val append: source1: taskSeq<'T> -> source2: taskSeq<'T> -> taskSeq<'T>
val appendSeq: source1: taskSeq<'T> -> source2: seq<'T> -> taskSeq<'T>
val box: source: taskSeq<'T> -> taskSeq<obj>
val cast: source: taskSeq<obj> -> taskSeq<'T>
val choose: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> taskSeq<'U>
Expand Down Expand Up @@ -522,7 +522,7 @@ module TaskSeq =
val ofTaskSeq: source: seq<#Task<'T>> -> taskSeq<'T>
val pick: chooser: ('T -> 'U option) -> source: taskSeq<'T> -> Task<'U>
val pickAsync: chooser: ('T -> #Task<'U option>) -> source: taskSeq<'T> -> Task<'U>
val prependSeq: source1: #seq<'T> -> source2: #taskSeq<'T> -> taskSeq<'T>
val prependSeq: source1: seq<'T> -> source2: taskSeq<'T> -> taskSeq<'T>
val singleton: source: 'T -> taskSeq<'T>
val tail: source: taskSeq<'T> -> Task<taskSeq<'T>>
val takeWhile: predicate: ('T -> bool) -> source: taskSeq<'T> -> Task<taskSeq<'T>>
Expand Down
3 changes: 3 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/Nunit.Extensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,6 @@ module ExtraCustomMatchers =
$"Throws %s{ex.Name} (Below, XUnit does not show actual value properly)",
(fun fn -> (testForThrowing (fn :?> (unit -> Task))).Result)
)

let inline assertThrows ty (f: unit -> 'U) = f >> ignore |> should throw ty
let inline assertNullArg f = assertThrows typeof<ArgumentNullException> f
11 changes: 11 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Append.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,18 @@ let validateSequence ts =
|> Task.map (String.concat "")
|> Task.map (should equal "1234567891012345678910")


module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg
<| fun () -> TaskSeq.empty |> TaskSeq.append null

assertNullArg
<| fun () -> null |> TaskSeq.append TaskSeq.empty

assertNullArg <| fun () -> null |> TaskSeq.append null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-append both args empty`` variant =
Gen.getEmptyVariant variant
Expand Down
6 changes: 6 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Cast.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ let validateSequence ts =
|> Task.map (should equal "12345678910")

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg <| fun () -> TaskSeq.box null
assertNullArg <| fun () -> TaskSeq.unbox null
assertNullArg <| fun () -> TaskSeq.cast null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-box empty`` variant = Gen.getEmptyVariant variant |> TaskSeq.box |> verifyEmpty

Expand Down
14 changes: 10 additions & 4 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Choose.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ open FsToolkit.ErrorHandling
open FSharp.Control

//
// TaskSeq.map
// TaskSeq.mapi
// TaskSeq.mapAsync
// TaskSeq.mapiAsync
// TaskSeq.choose
// TaskSeq.chooseAsync
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg
<| fun () -> TaskSeq.choose (fun _ -> None) null

assertNullArg
<| fun () -> TaskSeq.chooseAsync (fun _ -> Task.fromResult None) null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-choose`` variant = task {
let! empty =
Expand Down
7 changes: 7 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Collect.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ open FSharp.Control
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg
<| fun () -> TaskSeq.collect (fun _ -> TaskSeq.empty) null

assertNullArg
<| fun () -> TaskSeq.collectAsync (fun _ -> Task.fromResult TaskSeq.empty) null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-collect collecting emptiness`` variant =
Expand Down
3 changes: 3 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Concat.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ let validateSequence ts =
|> Task.map (should equal "123456789101234567891012345678910")

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () = assertNullArg <| fun () -> TaskSeq.concat null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-concat with empty sequences`` variant =
taskSeq {
Expand Down
3 changes: 3 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Contains.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ open FSharp.Control
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () = assertNullArg <| fun () -> TaskSeq.contains 42 null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-contains returns false`` variant =
Gen.getEmptyVariant variant
Expand Down
26 changes: 26 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Empty.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,29 @@ let ``TaskSeq-empty multiple times in a taskSeq context`` () = task {

Array.isEmpty sq |> should be True
}

[<Fact>]
let ``TaskSeq-empty multiple times with side effects`` () = task {
let mutable x = 0

let sq = taskSeq {
yield! TaskSeq.empty<string>
yield! TaskSeq.empty<string>
x <- x + 1
yield! TaskSeq.empty<string>
x <- x + 1
yield! TaskSeq.empty<string>
x <- x + 1
yield! TaskSeq.empty<string>
x <- x + 1
x <- x + 1
}

// executing side effects once
(TaskSeq.toArray >> Array.isEmpty) sq |> should be True
x |> should equal 5

// twice
(TaskSeq.toArray >> Array.isEmpty) sq |> should be True
x |> should equal 10
}
5 changes: 5 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.ExactlyOne.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ open FSharp.Control
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg <| fun () -> TaskSeq.exactlyOne null
assertNullArg <| fun () -> TaskSeq.tryExactlyOne null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-exactlyOne throws`` variant = task {
fun () ->
Expand Down
14 changes: 14 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Except.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ open FSharp.Control


module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg <| fun () -> TaskSeq.except null TaskSeq.empty
assertNullArg <| fun () -> TaskSeq.except TaskSeq.empty null
assertNullArg <| fun () -> TaskSeq.except null null

assertNullArg
<| fun () -> TaskSeq.exceptOfSeq null TaskSeq.empty

assertNullArg
<| fun () -> TaskSeq.exceptOfSeq Seq.empty null

assertNullArg <| fun () -> TaskSeq.exceptOfSeq null null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-except`` variant =
Gen.getEmptyVariant variant
Expand Down
8 changes: 8 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Exists.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ open FSharp.Control
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg
<| fun () -> TaskSeq.exists (fun _ -> false) null

assertNullArg
<| fun () -> TaskSeq.existsAsync (fun _ -> Task.fromResult false) null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-exists returns false`` variant =
Gen.getEmptyVariant variant
Expand Down
9 changes: 9 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Filter.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ open FSharp.Control


module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg
<| fun () -> TaskSeq.filter (fun _ -> false) null

assertNullArg
<| fun () -> TaskSeq.filterAsync (fun _ -> Task.fromResult false) null


[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-filter has no effect`` variant =
Gen.getEmptyVariant variant
Expand Down
14 changes: 14 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Find.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ open System.Collections.Generic
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg
<| fun () -> TaskSeq.find (fun _ -> false) null

assertNullArg
<| fun () -> TaskSeq.findAsync (fun _ -> Task.fromResult false) null

assertNullArg
<| fun () -> TaskSeq.tryFind (fun _ -> false) null

assertNullArg
<| fun () -> TaskSeq.tryFindAsync (fun _ -> Task.fromResult false) null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-find raises KeyNotFoundException`` variant =
fun () ->
Expand Down
14 changes: 14 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.FindIndex.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ open System.Collections.Generic
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg
<| fun () -> TaskSeq.findIndex (fun _ -> false) null

assertNullArg
<| fun () -> TaskSeq.findIndexAsync (fun _ -> Task.fromResult false) null

assertNullArg
<| fun () -> TaskSeq.tryFindIndex (fun _ -> false) null

assertNullArg
<| fun () -> TaskSeq.tryFindIndexAsync (fun _ -> Task.fromResult false) null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-findIndex raises KeyNotFoundException`` variant =
fun () ->
Expand Down
8 changes: 8 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Fold.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ open FSharp.Control
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg
<| fun () -> TaskSeq.fold (fun _ _ -> 42) 0 null

assertNullArg
<| fun () -> TaskSeq.foldAsync (fun _ _ -> Task.fromResult 42) 0 null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-fold takes state when empty`` variant = task {
let! empty =
Expand Down
4 changes: 4 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Head.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ open FSharp.Control
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg <| fun () -> TaskSeq.head null
assertNullArg <| fun () -> TaskSeq.tryHead null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-head throws`` variant = task {
Expand Down
3 changes: 3 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Indexed.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ open FSharp.Control
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () = assertNullArg <| fun () -> TaskSeq.indexed null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-indexed on empty`` variant =
Gen.getEmptyVariant variant
Expand Down
21 changes: 21 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.IsEmpty.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ open FsToolkit.ErrorHandling

open FSharp.Control

//
// TaskSeq.isEmpty
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () = assertNullArg <| fun () -> TaskSeq.head null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-isEmpty returns true for empty`` variant =
Expand Down Expand Up @@ -60,6 +66,21 @@ module SideEffects =
|> Task.map (should be True)
|> Task.map (fun () -> i |> should equal 2)

[<Fact>]
let ``TaskSeq-isEmpty executes side effects each time`` () =
let mutable i = 0

taskSeq {
i <- i + 1
i <- i + 1
}
|>> TaskSeq.isEmpty
|>> TaskSeq.isEmpty
|>> TaskSeq.isEmpty
|> TaskSeq.isEmpty // 4th time: 8
|> Task.map (should be True)
|> Task.map (fun () -> i |> should equal 8)

[<Theory; ClassData(typeof<TestSideEffectTaskSeq>)>]
let ``TaskSeq-isEmpty returns false for non-empty`` variant =
Gen.getSeqWithSideEffect variant
Expand Down
5 changes: 5 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Item.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ open FSharp.Control
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg <| fun () -> TaskSeq.item 42 null
assertNullArg <| fun () -> TaskSeq.tryItem 42 null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-item throws on empty sequences`` variant = task {
fun () -> Gen.getEmptyVariant variant |> TaskSeq.item 0 |> Task.ignore
Expand Down
13 changes: 13 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Iter.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ open FSharp.Control
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg <| fun () -> TaskSeq.iter (fun _ -> ()) null

assertNullArg
<| fun () -> TaskSeq.iteri (fun _ _ -> ()) null

assertNullArg
<| fun () -> TaskSeq.iterAsync (fun _ -> Task.fromResult ()) null

assertNullArg
<| fun () -> TaskSeq.iteriAsync (fun _ _ -> Task.fromResult ()) null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-iteri does nothing on empty sequences`` variant = task {
let tq = Gen.getEmptyVariant variant
Expand Down
4 changes: 4 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Last.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ open FSharp.Control
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg <| fun () -> TaskSeq.last null
assertNullArg <| fun () -> TaskSeq.tryLast null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-last throws`` variant = task {
Expand Down
10 changes: 10 additions & 0 deletions src/FSharp.Control.TaskSeq.Test/TaskSeq.Length.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ open FSharp.Control
//

module EmptySeq =
[<Fact>]
let ``Null source is invalid`` () =
assertNullArg <| fun () -> TaskSeq.length null

assertNullArg
<| fun () -> TaskSeq.lengthBy (fun _ -> false) null

assertNullArg
<| fun () -> TaskSeq.lengthByAsync (fun _ -> Task.fromResult false) null

[<Theory; ClassData(typeof<TestEmptyVariants>)>]
let ``TaskSeq-length returns zero on empty sequences`` variant = task {
let! len = Gen.getEmptyVariant variant |> TaskSeq.length
Expand Down
Loading