diff --git a/.paket/Paket.Restore.targets b/.paket/Paket.Restore.targets
index b4f593eb8..52f41c608 100644
--- a/.paket/Paket.Restore.targets
+++ b/.paket/Paket.Restore.targets
@@ -62,6 +62,9 @@
true
true
+
+
+ True
@@ -102,7 +105,11 @@
true
-
+
+
true
@@ -183,6 +190,7 @@
runtime
runtime
true
+ true
diff --git a/build.fsx b/build.fsx
index 57d3819cb..3f1e42214 100644
--- a/build.fsx
+++ b/build.fsx
@@ -205,10 +205,12 @@ Target "Build" (fun _ ->
)
Target "RunTests" (fun _ ->
+ Console.WriteLine("Number of processors: {0}", Environment.ProcessorCount)
DotNetCli.Test (fun p -> { p with
#if MONO
Framework = "netcoreapp2.0"
#endif
+ AdditionalArgs = [ "--no-build"; "-v=normal" ]
Configuration = "Release"
Project = "tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj" })
)
diff --git a/src/FSharp.Data.GraphQL.Server/Execution.fs b/src/FSharp.Data.GraphQL.Server/Execution.fs
index c52e3ebc3..efb3230ff 100644
--- a/src/FSharp.Data.GraphQL.Server/Execution.fs
+++ b/src/FSharp.Data.GraphQL.Server/Execution.fs
@@ -259,7 +259,7 @@ type ResolverTree =
| ResolverListNode l -> l.Value
and ResolverLeaf = { Name: string; Value: obj option }
and ResolverError = { Name: string; Message: string; PathToOrigin: obj list }
-and ResolverNode = { Name: string; Value: obj option; Children: AsyncVal [] }
+and ResolverNode = { Name: string; Value: obj option; Children: AsyncVal seq }
module ResolverTree =
let rec pathFold leafOp errorOp nodeOp listOp =
@@ -279,7 +279,7 @@ module ResolverTree =
let! c' = c
return helper path' c'
}
- let ts = node.Children |> Array.map mapper
+ let ts = node.Children |> Seq.map mapper
nodeOp path' node.Name node.Value ts
| ResolverListNode node ->
let path' = (node.Name :> obj)::path
@@ -287,13 +287,13 @@ module ResolverTree =
let! c' = c
return helper ((box i)::path') c'
}
- let ts = node.Children |> Array.mapi mapper
+ let ts = node.Children |> Seq.mapi mapper
listOp path' node.Name node.Value ts
helper []
-let private foldChildren (children : AsyncVal * Error list>> []) =
+let private foldChildren (children : AsyncVal * Error list>> seq) =
children
- |> Array.fold (fun (kvpsErrs : AsyncVal list * Error list>) child -> asyncVal {
+ |> Seq.fold (fun (kvpsErrs : AsyncVal list * Error list>) child -> asyncVal {
let! kvps, errs = kvpsErrs
let! c = child
let! c, e = c
@@ -345,7 +345,7 @@ let private treeToStream (streamOptions : BufferedStreamOptions) tree =
function
| ResolverListNode list ->
list.Children
- |> Array.mapi (fun i x ->
+ |> Seq.mapi (fun i x ->
asyncVal {
let! x' = x
return i, x'
@@ -404,7 +404,7 @@ let rec private buildResolverTree (returnDef: OutputDef) (ctx: ResolveFieldConte
| Some v -> buildObjectFields fields objdef ctx fieldExecuteMap name v
| None ->
if ctx.ExecutionInfo.IsNullable
- then asyncVal { return ResolverObjectNode { Name = name; Value = None; Children = [| |] } }
+ then asyncVal { return ResolverObjectNode { Name = name; Value = None; Children = [||] } }
else nullResolverError name
| kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind
| Scalar scalardef ->
@@ -424,28 +424,25 @@ let rec private buildResolverTree (returnDef: OutputDef) (ctx: ResolveFieldConte
match kind with
| ResolveCollection innerPlan -> { ctx with ExecutionInfo = innerPlan }
| kind -> failwithf "Unexpected value of ctx.ExecutionPlan.Kind: %A" kind
- let rec build acc (items: obj list) =
- match items with
- | value::xs ->
- if not innerCtx.ExecutionInfo.IsNullable && isNull value
- then nullResolverError innerCtx.ExecutionInfo.Identifier
+ let build (items : seq) =
+ let children = seq {
+ for item in items do
+ if not innerCtx.ExecutionInfo.IsNullable && isNull item
+ then yield nullResolverError innerCtx.ExecutionInfo.Identifier
else
- let t = asyncVal {
- let! res = buildResolverTree innerdef innerCtx fieldExecuteMap (toOption value)
+ yield asyncVal {
+ let! res = buildResolverTree innerdef innerCtx fieldExecuteMap (toOption item)
match res with
| ResolverError e when not innerCtx.ExecutionInfo.IsNullable -> return! propagateError name e
- | _ -> return res
- }
- build (t::acc) xs
- | [] -> asyncVal { return ResolverListNode { Name = name; Value = value; Children = acc |> List.map (AsyncVal.map (fun x -> x)) |> List.rev |> List.toArray } }
+ | _ -> return res } }
+ asyncVal { return ResolverListNode { Name = name; Value = value; Children = children } }
match value with
| None when not ctx.ExecutionInfo.IsNullable -> nullResolverError name
- | None -> asyncVal { return ResolverListNode { Name = name; Value = None; Children = [| |] } }
+ | None -> asyncVal { return ResolverListNode { Name = name; Value = None; Children = [||] } }
| ObjectOption (:? System.Collections.IEnumerable as enumerable) ->
enumerable
|> Seq.cast
- |> Seq.toList
- |> build []
+ |> build
| _ -> raise <| GraphQLException (sprintf "Expected to have enumerable value in field '%s' but got '%O'" ctx.ExecutionInfo.Identifier (value.GetType()))
| Nullable (Output innerdef) ->
// Stop propagation of null values
@@ -465,7 +462,7 @@ let rec private buildResolverTree (returnDef: OutputDef) (ctx: ResolveFieldConte
| None -> asyncVal { return ResolverError { Name = name; Message = ctx.Schema.ParseError (GraphQLException (sprintf "GraphQL Interface '%s' is not implemented by the type '%s'" idef.Name resolvedDef.Name)); PathToOrigin = [] } }
| None ->
if ctx.ExecutionInfo.IsNullable
- then asyncVal { return ResolverObjectNode { Name = name; Value = None; Children = [| |] } }
+ then asyncVal { return ResolverObjectNode { Name = name; Value = None; Children = [||] } }
else nullResolverError name
| Union udef ->
let possibleTypesFn = ctx.Schema.GetPossibleTypes
@@ -488,13 +485,13 @@ let rec private buildResolverTree (returnDef: OutputDef) (ctx: ResolveFieldConte
| None -> asyncVal { return ResolverError { Name = name; Message = ctx.Schema.ParseError (GraphQLException (sprintf "GraphQL Union '%s' is not implemented by the type '%s'" udef.Name resolvedDef.Name)); PathToOrigin = [] } }
| None ->
if ctx.ExecutionInfo.IsNullable
- then asyncVal { return ResolverObjectNode { Name = name; Value = None; Children = [| |] } }
+ then asyncVal { return ResolverObjectNode { Name = name; Value = None; Children = [||] } }
else nullResolverError name
| _ -> failwithf "Unexpected value of returnDef: %O" returnDef
match ctx.ExecutionInfo.Kind, returnDef, ctx.ExecutionInfo.IsDeferred with
| ResolveDeferred _, (Scalar _ | Enum _ | Nullable _), false -> asyncVal { return ResolverLeaf { Name = name; Value = None } }
- | ResolveDeferred _, (Object _ | Interface _ | Union _), false -> asyncVal { return ResolverObjectNode { Name = name; Value = None; Children = [| |] } }
- | ResolveDeferred _, List _, false -> asyncVal { return ResolverListNode { Name = name; Value = Some (upcast [ ]); Children = [| |] } }
+ | ResolveDeferred _, (Object _ | Interface _ | Union _), false -> asyncVal { return ResolverObjectNode { Name = name; Value = None; Children = [||] } }
+ | ResolveDeferred _, List _, false -> asyncVal { return ResolverListNode { Name = name; Value = Some (upcast [ ]); Children = [||] } }
| _ -> resolveDirect ctx.ExecutionInfo.Kind
and buildObjectFields (fields: ExecutionInfo list) (objdef: ObjectDef) (ctx: ResolveFieldContext) (fieldExecuteMap: FieldExecuteMap) (name: string) (value: obj): AsyncVal =
@@ -680,7 +677,7 @@ let private executeQueryOrMutation (resultSet: (string * ExecutionInfo) []) (ctx
let mapLive (tree : ResolverTree) (path : obj list) (d : DeferredExecutionInfo) (fieldCtx : ResolveFieldContext) = asyncVal {
let getFieldName (node : ResolverNode) = asyncVal {
- match node.Children |> Array.tryHead with
+ match node.Children |> Seq.tryHead with
| Some c ->
let! res = c
return res.Name
diff --git a/src/FSharp.Data.GraphQL.Shared/AsyncVal.fs b/src/FSharp.Data.GraphQL.Shared/AsyncVal.fs
index d3da2aff0..dabfeeba0 100644
--- a/src/FSharp.Data.GraphQL.Shared/AsyncVal.fs
+++ b/src/FSharp.Data.GraphQL.Shared/AsyncVal.fs
@@ -115,6 +115,10 @@ module AsyncVal =
/// Returned array maintain order of values.
/// If the array contains a Failure, then the entire array will not resolve
let collectSequential (values: AsyncVal<'T> []) : AsyncVal<'T []> =
+ let mapper =
+ function
+ | Value v -> v
+ | other -> failwithf "Expected a synchronous value, but got %O" other
if values.Length = 0 then Value [||]
elif values |> Array.exists isAsync then
Async(async {
@@ -129,7 +133,7 @@ module AsyncVal =
| Failure f ->
results.[i] <- raise f
return results })
- else Value (values |> Array.map (fun (Value v) -> v))
+ else Value (values |> Array.map mapper)
@@ -197,12 +201,6 @@ module AsyncExtensions =
/// Computation expression for working on AsyncVals.
let asyncVal = AsyncValBuilder ()
-
- /// Active pattern used for checking if AsyncVal contains immediate value.
- let (|Immediate|_|) (x: AsyncVal<'T>) = match x with | Value v -> Some v | _ -> None
-
- /// Active patter used for checking if AsyncVal wraps an Async computation.
- let (|Async|_|) (x: AsyncVal<'T>) = match x with | Async a -> Some a | _ -> None
type Microsoft.FSharp.Control.AsyncBuilder with
diff --git a/tests/FSharp.Data.GraphQL.Tests/DeferredTests.fs b/tests/FSharp.Data.GraphQL.Tests/DeferredTests.fs
index 884b932fb..4d23de4d3 100644
--- a/tests/FSharp.Data.GraphQL.Tests/DeferredTests.fs
+++ b/tests/FSharp.Data.GraphQL.Tests/DeferredTests.fs
@@ -7,12 +7,22 @@ open FSharp.Data.GraphQL
open FSharp.Data.GraphQL.Parser
open FSharp.Data.GraphQL.Execution
open System.Threading
-open System.Collections.Generic
-open System.Collections.Concurrent
open FSharp.Data.GraphQL.Types
#nowarn "40"
+let ms x =
+ let factor =
+ match Environment.ProcessorCount with
+ | x when x >= 8 -> 1
+ | x when x >= 4 -> 4
+ | _ -> 6
+ x * factor
+
+let delay time x = async {
+ do! Async.Sleep(ms time)
+ return x }
+
type TestSubject = {
id: string
a: string
@@ -157,11 +167,6 @@ let DataType =
Define.Field("bufferedList", ListOf AsyncDataType, (fun _ d -> d.bufferedList))
])
-let delay ms x = async {
- do! Async.Sleep(ms)
- return x
-}
-
let data = {
id = "1"
a = "Apple"
@@ -629,9 +634,9 @@ let ``Each live result should be sent as soon as it is computed`` () =
// The second result is a delayed async field, which is set to compute the value for 5 seconds.
// The first result should come as soon as the live value is updated, which sould be almost instantly.
// Therefore, let's assume that if it does not come in at least 3 seconds, test has failed.
- if TimeSpan.FromSeconds(float 3) |> mre1.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 3)) |> mre1.WaitOne |> not
then fail "Timeout while waiting for first deferred result"
- if TimeSpan.FromSeconds(float 30) |> mre2.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 10)) |> mre2.WaitOne |> not
then fail "Timeout while waiting for second deferred result"
sub.Received
|> Seq.cast
@@ -1282,13 +1287,16 @@ let ``Should buffer stream list correctly by timing information``() =
]
"path", upcast [box "testData"; upcast "bufferedList"; upcast [0]]
]
- let query = parse """{
- testData {
- bufferedList @stream(interval : 3000) {
- value
+ let query =
+ ms 3000
+ |> sprintf """{
+ testData {
+ bufferedList @stream(interval : %i) {
+ value
+ }
}
- }
- }"""
+ }"""
+ |> parse
use mre1 = new ManualResetEvent(false)
use mre2 = new ManualResetEvent(false)
let result = query |> executor.AsyncExecute |> sync
@@ -1306,11 +1314,11 @@ let ``Should buffer stream list correctly by timing information``() =
// to buffer results 3 and 2 (in this order), as together they take less than 3 seconds to compute,
// and send them together on the first batch.
// First result should come in a second batch, as it takes 5 seconds to compute, more than the time limit of the buffer.
- if TimeSpan.FromSeconds(float 4) |> mre1.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 4)) |> mre1.WaitOne |> not
then fail "Timeout while waiting for first Deferred GQLResponse"
- if TimeSpan.FromSeconds(float 30) |> mre2.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 10)) |> mre2.WaitOne |> not
then fail "Timeout while waiting for second Deferred GQLResponse"
- sub.WaitCompleted()
+ sub.WaitCompleted(timeout = ms 10)
sub.Received
|> Seq.cast
|> itemEquals 0 expectedDeferred1
@@ -1372,11 +1380,11 @@ let ``Should buffer stream list correctly by count information``() =
// and send them together on the first batch.
// First result should come in a second batch, as it takes 5 seconds to compute, which should be enough
// to put the two other results in a batch with the preferred size.
- if TimeSpan.FromSeconds(float 4) |> mre1.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 4)) |> mre1.WaitOne |> not
then fail "Timeout while waiting for first Deferred GQLResponse"
- if TimeSpan.FromSeconds(float 30) |> mre2.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 10)) |> mre2.WaitOne |> not
then fail "Timeout while waiting for second Deferred GQLResponse"
- sub.WaitCompleted()
+ sub.WaitCompleted(timeout = ms 10)
sub.Received
|> Seq.cast
|> itemEquals 0 expectedDeferred1
@@ -1469,11 +1477,11 @@ let ``Each deferred result should be sent as soon as it is computed``() =
// The second result is a delayed async field, which is set to compute the value for 5 seconds.
// The first result should come almost instantly, as it is not a delayed computed field.
// Therefore, let's assume that if it does not come in at least 3 seconds, test has failed.
- if TimeSpan.FromSeconds(float 3) |> mre1.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 3)) |> mre1.WaitOne |> not
then fail "Timeout while waiting for first deferred result"
- if TimeSpan.FromSeconds(float 30) |> mre2.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 10)) |> mre2.WaitOne |> not
then fail "Timeout while waiting for second deferred result"
- sub.WaitCompleted()
+ sub.WaitCompleted(timeout = ms 10)
sub.Received
|> Seq.cast
|> itemEquals 0 expectedDeferred1
@@ -1526,11 +1534,11 @@ let ``Each deferred result of a list should be sent as soon as it is computed``
// The first result is a delayed async field, which is set to compute the value for 5 seconds.
// The second result should come first, almost instantly, as it is not a delayed computed field.
// Therefore, let's assume that if it does not come in at least 4 seconds, test has failed.
- if TimeSpan.FromSeconds(float 4) |> mre1.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 4)) |> mre1.WaitOne |> not
then fail "Timeout while waiting for first deferred result"
- if TimeSpan.FromSeconds(float 30) |> mre2.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 10)) |> mre2.WaitOne |> not
then fail "Timeout while waiting for second deferred result"
- sub.WaitCompleted()
+ sub.WaitCompleted(timeout = ms 10)
sub.Received
|> Seq.cast
|> itemEquals 0 expectedDeferred1
@@ -1539,7 +1547,7 @@ let ``Each deferred result of a list should be sent as soon as it is computed``
| _ -> fail "Expected Deferred GQLRespnse"
[]
-let ``Each streamed result should be sent as soon as it is computed``() =
+let ``Each streamed result should be sent as soon as it is computed - async seq``() =
let expectedDirect =
NameValueLookup.ofList [
"testData", upcast NameValueLookup.ofList [
@@ -1584,11 +1592,11 @@ let ``Each streamed result should be sent as soon as it is computed``() =
// The first result is a delayed async field, which is set to compute the value for 5 seconds.
// The second result should come first, almost instantly, as it is not a delayed computed field.
// Therefore, let's assume that if it does not come in at least 4 seconds, test has failed.
- if TimeSpan.FromSeconds(float 4) |> mre1.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 4)) |> mre1.WaitOne |> not
then fail "Timeout while waiting for first deferred result"
- if TimeSpan.FromSeconds(float 30) |> mre2.WaitOne |> not
+ if TimeSpan.FromSeconds(float (ms 10)) |> mre2.WaitOne |> not
then fail "Timeout while waiting for second deferred result"
- sub.WaitCompleted()
+ sub.WaitCompleted(timeout = ms 10)
sub.Received
|> Seq.cast
|> itemEquals 0 expectedDeferred1
diff --git a/tests/FSharp.Data.GraphQL.Tests/Helpers.fs b/tests/FSharp.Data.GraphQL.Tests/Helpers.fs
index 8141d4a0b..367634541 100644
--- a/tests/FSharp.Data.GraphQL.Tests/Helpers.fs
+++ b/tests/FSharp.Data.GraphQL.Tests/Helpers.fs
@@ -129,12 +129,15 @@ type TestObserver<'T>(obs : IObservable<'T>, ?onReceived : TestObserver<'T> -> '
do subscription <- obs.Subscribe(this)
member __.Received
with get() = received.AsEnumerable()
- member __.WaitCompleted() =
- wait mre "Timeout waiting for OnCompleted"
- member x.WaitCompleted(expectedItemCount) =
- x.WaitCompleted()
- if received.Count < expectedItemCount
- then failwithf "Expected to receive %i items, but received %i" expectedItemCount received.Count
+ member __.WaitCompleted(?expectedItemCount, ?timeout) =
+ let ms = defaultArg timeout 30
+ if TimeSpan.FromSeconds(float ms) |> mre.WaitOne |> not
+ then fail "Timeout waiting for OnCompleted"
+ match expectedItemCount with
+ | Some x ->
+ if received.Count < x
+ then failwithf "Expected to receive %i items, but received %i" x received.Count
+ | None -> ()
member __.WaitForItems(expectedItemCount) =
let errorMsg = sprintf "Expected to receive least %i items, but received %i" expectedItemCount received.Count
waitFor (fun () -> received.Count = expectedItemCount) (expectedItemCount * 100) errorMsg
diff --git a/tests/FSharp.Data.GraphQL.Tests/MiddlewaresTests.fs b/tests/FSharp.Data.GraphQL.Tests/MiddlewaresTests.fs
index 00d854032..bdaebe915 100644
--- a/tests/FSharp.Data.GraphQL.Tests/MiddlewaresTests.fs
+++ b/tests/FSharp.Data.GraphQL.Tests/MiddlewaresTests.fs
@@ -194,16 +194,13 @@ let ``Deferred queries : Should pass when below threshold``() =
"path", upcast [ "A" :> obj; "subjects" :> obj ]
]
let result = execute query
- use mre = new ManualResetEvent(false)
- let actualDeferred = ConcurrentBag