Skip to content

Commit 75b031f

Browse files
committed
test(incremental): document bugs in shared deferred work
Replicates graphql/graphql-js@90daa8a
1 parent a8a04e8 commit 75b031f

3 files changed

Lines changed: 657 additions & 5 deletions

File tree

‎tests/execution/incremental/test_defer.py‎

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2376,6 +2376,245 @@ async def some_field(_info):
23762376
},
23772377
]
23782378

2379+
@pytest.mark.xfail(strict=True, reason="unannounced error completion")
2380+
async def announces_a_nested_fragment_before_completing_its_shared_failure():
2381+
"""Announces a nested fragment before completing its shared failure"""
2382+
document = parse(
2383+
"""
2384+
{
2385+
hero {
2386+
... @defer(label: "R") {
2387+
nonNullName
2388+
}
2389+
... @defer(label: "P") {
2390+
name
2391+
... @defer(label: "C") {
2392+
nonNullName
2393+
}
2394+
}
2395+
}
2396+
}
2397+
"""
2398+
)
2399+
result = await complete(
2400+
document, {"hero": {"name": "Luke", "nonNullName": None}}
2401+
)
2402+
2403+
assert result == [
2404+
{
2405+
"data": {"hero": {}},
2406+
"pending": [
2407+
{"id": "0", "path": ["hero"], "label": "R"},
2408+
{"id": "1", "path": ["hero"], "label": "P"},
2409+
],
2410+
"hasNext": True,
2411+
},
2412+
{
2413+
"pending": [{"id": "2", "path": ["hero"], "label": "C"}],
2414+
"incremental": [{"data": {"name": "Luke"}, "id": "1"}],
2415+
"completed": [
2416+
{
2417+
"id": "0",
2418+
"errors": [
2419+
{
2420+
"message": "Cannot return null"
2421+
" for non-nullable field Hero.nonNullName.",
2422+
"locations": [
2423+
{"line": 5, "column": 19},
2424+
{"line": 10, "column": 21},
2425+
],
2426+
"path": ["hero", "nonNullName"],
2427+
},
2428+
],
2429+
},
2430+
{"id": "1"},
2431+
{
2432+
"id": "2",
2433+
"errors": [
2434+
{
2435+
"message": "Cannot return null"
2436+
" for non-nullable field Hero.nonNullName.",
2437+
"locations": [
2438+
{"line": 5, "column": 19},
2439+
{"line": 10, "column": 21},
2440+
],
2441+
"path": ["hero", "nonNullName"],
2442+
},
2443+
],
2444+
},
2445+
],
2446+
"hasNext": False,
2447+
},
2448+
]
2449+
2450+
@pytest.mark.xfail(strict=True, reason="unannounced error completion")
2451+
async def reports_one_cached_shared_failure_when_a_nested_fragment_is_announced():
2452+
"""Reports one cached shared failure when a nested fragment is announced"""
2453+
document = parse(
2454+
"""
2455+
{
2456+
hero {
2457+
... @defer(label: "R1") {
2458+
first: nonNullName
2459+
}
2460+
... @defer(label: "R2") {
2461+
second: nonNullName
2462+
}
2463+
... @defer(label: "P") {
2464+
name
2465+
... @defer(label: "C") {
2466+
first: nonNullName
2467+
second: nonNullName
2468+
}
2469+
}
2470+
}
2471+
}
2472+
"""
2473+
)
2474+
result = await complete(
2475+
document, {"hero": {"name": "Luke", "nonNullName": None}}
2476+
)
2477+
2478+
assert result == [
2479+
{
2480+
"data": {"hero": {}},
2481+
"pending": [
2482+
{"id": "0", "path": ["hero"], "label": "R1"},
2483+
{"id": "1", "path": ["hero"], "label": "R2"},
2484+
{"id": "2", "path": ["hero"], "label": "P"},
2485+
],
2486+
"hasNext": True,
2487+
},
2488+
{
2489+
"pending": [{"id": "3", "path": ["hero"], "label": "C"}],
2490+
"incremental": [{"data": {"name": "Luke"}, "id": "2"}],
2491+
"completed": [
2492+
{
2493+
"id": "0",
2494+
"errors": [
2495+
{
2496+
"message": "Cannot return null"
2497+
" for non-nullable field Hero.nonNullName.",
2498+
"locations": [
2499+
{"line": 5, "column": 19},
2500+
{"line": 13, "column": 21},
2501+
],
2502+
"path": ["hero", "first"],
2503+
},
2504+
],
2505+
},
2506+
{
2507+
"id": "1",
2508+
"errors": [
2509+
{
2510+
"message": "Cannot return null"
2511+
" for non-nullable field Hero.nonNullName.",
2512+
"locations": [
2513+
{"line": 8, "column": 19},
2514+
{"line": 14, "column": 21},
2515+
],
2516+
"path": ["hero", "second"],
2517+
},
2518+
],
2519+
},
2520+
{"id": "2"},
2521+
{
2522+
"id": "3",
2523+
"errors": [
2524+
{
2525+
"message": "Cannot return null"
2526+
" for non-nullable field Hero.nonNullName.",
2527+
"locations": [
2528+
{"line": 5, "column": 19},
2529+
{"line": 13, "column": 21},
2530+
],
2531+
"path": ["hero", "first"],
2532+
},
2533+
],
2534+
},
2535+
],
2536+
"hasNext": False,
2537+
},
2538+
]
2539+
2540+
@pytest.mark.xfail(strict=True, reason="lost shared result")
2541+
@pytest.mark.parametrize("early_execution", [False, True])
2542+
async def delivers_a_buffered_shared_value_after_its_other_fragment_fails(
2543+
early_execution,
2544+
):
2545+
"""Delivers a buffered shared value after its other fragment fails"""
2546+
document = parse(
2547+
"""
2548+
{
2549+
hero {
2550+
... @defer(label: "R") {
2551+
nonNullName
2552+
id
2553+
}
2554+
... @defer(label: "P") {
2555+
name
2556+
... @defer(label: "C") {
2557+
id
2558+
}
2559+
}
2560+
}
2561+
}
2562+
"""
2563+
)
2564+
name_future: Future[str] = Future()
2565+
2566+
result = experimental_execute_incrementally(
2567+
schema,
2568+
document,
2569+
{"hero": {"id": 1, "name": name_future, "nonNullName": None}},
2570+
enable_early_execution=early_execution,
2571+
)
2572+
assert isinstance(result, ExperimentalIncrementalExecutionResults)
2573+
2574+
assert result.initial_result.formatted == {
2575+
"data": {"hero": {}},
2576+
"pending": [
2577+
{"id": "0", "path": ["hero"], "label": "R"},
2578+
{"id": "1", "path": ["hero"], "label": "P"},
2579+
],
2580+
"hasNext": True,
2581+
}
2582+
2583+
iterator = result.subsequent_results
2584+
2585+
assert (await anext(iterator)).formatted == {
2586+
"completed": [
2587+
{
2588+
"id": "0",
2589+
"errors": [
2590+
{
2591+
"message": "Cannot return null"
2592+
" for non-nullable field Hero.nonNullName.",
2593+
"locations": [{"line": 5, "column": 19}],
2594+
"path": ["hero", "nonNullName"],
2595+
},
2596+
],
2597+
},
2598+
],
2599+
"hasNext": True,
2600+
}
2601+
2602+
# The shared id has settled, but C still waits for P's name.
2603+
name_future.set_result("Luke")
2604+
2605+
assert (await anext(iterator)).formatted == {
2606+
"pending": [{"id": "2", "path": ["hero"], "label": "C"}],
2607+
"incremental": [
2608+
{"data": {"name": "Luke"}, "id": "1"},
2609+
{"data": {"id": "1"}, "id": "2"},
2610+
],
2611+
"completed": [{"id": "1"}, {"id": "2"}],
2612+
"hasNext": False,
2613+
}
2614+
2615+
with pytest.raises(StopAsyncIteration): # pragma: no cover
2616+
await anext(iterator)
2617+
23792618
async def handles_cancelling_child_deferred_fragments_if_parent_fragment_fails():
23802619
"""Handles cancelling child deferred fragments if parent fragment fails"""
23812620
document = parse(

‎tests/execution/incremental/test_stream.py‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,6 +2151,104 @@ def nested_friend_list(_info):
21512151
await sleep(0)
21522152
assert name_future.cancelled()
21532153

2154+
@pytest.mark.xfail(strict=True, reason="lost shared result")
2155+
async def releases_a_buffered_shared_stream_after_its_other_fragment_fails():
2156+
"""Releases a buffered shared stream after its other fragment fails"""
2157+
document = parse(
2158+
"""
2159+
{
2160+
nestedObject {
2161+
... @defer(label: "R") {
2162+
nonNullScalarField
2163+
...Friends
2164+
}
2165+
... @defer(label: "P") {
2166+
scalarField
2167+
... @defer(label: "C") {
2168+
...Friends
2169+
}
2170+
}
2171+
}
2172+
}
2173+
fragment Friends on NestedObject {
2174+
nestedFriendList @stream(initialCount: 0) {
2175+
name
2176+
}
2177+
}
2178+
"""
2179+
)
2180+
scalar_future: Future[str] = Future()
2181+
2182+
result = experimental_execute_incrementally(
2183+
schema,
2184+
document,
2185+
{
2186+
"nestedObject": {
2187+
"nonNullScalarField": None,
2188+
"scalarField": scalar_future,
2189+
"nestedFriendList": friends,
2190+
}
2191+
},
2192+
)
2193+
assert isinstance(result, ExperimentalIncrementalExecutionResults)
2194+
2195+
assert result.initial_result.formatted == {
2196+
"data": {"nestedObject": {}},
2197+
"pending": [
2198+
{"id": "0", "path": ["nestedObject"], "label": "R"},
2199+
{"id": "1", "path": ["nestedObject"], "label": "P"},
2200+
],
2201+
"hasNext": True,
2202+
}
2203+
2204+
iterator = result.subsequent_results
2205+
2206+
assert (await anext(iterator)).formatted == {
2207+
"completed": [
2208+
{
2209+
"id": "0",
2210+
"errors": [
2211+
{
2212+
"message": "Cannot return null for non-nullable field"
2213+
" NestedObject.nonNullScalarField.",
2214+
"locations": [{"line": 5, "column": 19}],
2215+
"path": ["nestedObject", "nonNullScalarField"],
2216+
},
2217+
],
2218+
},
2219+
],
2220+
"hasNext": True,
2221+
}
2222+
2223+
scalar_future.set_result("parent")
2224+
2225+
assert (await anext(iterator)).formatted == {
2226+
"pending": [
2227+
{"id": "2", "path": ["nestedObject"], "label": "C"},
2228+
{"id": "3", "path": ["nestedObject", "nestedFriendList"]},
2229+
],
2230+
"incremental": [
2231+
{"data": {"scalarField": "parent"}, "id": "1"},
2232+
{"data": {"nestedFriendList": []}, "id": "2"},
2233+
],
2234+
"completed": [{"id": "1"}, {"id": "2"}],
2235+
"hasNext": True,
2236+
}
2237+
2238+
assert (await anext(iterator)).formatted == {
2239+
"incremental": [
2240+
{
2241+
"items": [{"name": "Luke"}, {"name": "Han"}, {"name": "Leia"}],
2242+
"id": "3",
2243+
},
2244+
],
2245+
"completed": [{"id": "3"}],
2246+
"hasNext": False,
2247+
}
2248+
2249+
with pytest.raises(StopAsyncIteration): # pragma: no cover
2250+
await anext(iterator)
2251+
21542252
async def filters_stream_payloads_that_are_nulled_in_a_deferred_payload():
21552253
"""Filters stream payloads that are nulled in a deferred payload"""
21562254
document = parse(

0 commit comments

Comments
 (0)