import json,sys
from pathlib import Path
ROOT=Path(__file__).resolve().parent
FIXTURE=json.loads((ROOT/'fixture.json').read_text())
def finish(mode,observations,conditions):
 checks=[name for name,ok in conditions.items() if ok]
 failed=[name for name,ok in conditions.items() if not ok]
 print(json.dumps({'mode':mode,'status':'check_failed' if failed else 'passed','checks':checks,'failed_checks':failed,'error':', '.join(failed),'observations':observations},sort_keys=True))
 sys.exit(2 if failed else 0)
import asyncio
async def run(mode):
 started=asyncio.Event(); release=asyncio.Event(); events=[]; tasks=[]
 async def sibling():
  started.set()
  try:
   await release.wait();events.append('side_effect')
  except asyncio.CancelledError:
   events.append('cancelled')
   if mode=='swallow-cancel':events.append('side_effect')
   else:raise
  finally:
   await asyncio.sleep(0);events.append('cleanup_done')
 async def fail():
  await started.wait();raise ValueError(FIXTURE['error'])
 error=False; boundary=[];failure_kind=None;failure_messages=[]
 if mode in ('fixed','swallow-cancel'):
  try:
   async with asyncio.TaskGroup() as tg:
    tasks=[tg.create_task(sibling()),tg.create_task(fail())]
  except* ValueError as group:
   error=all(str(e)==FIXTURE['error'] for e in group.exceptions)
   failure_kind=type(group).__name__;failure_messages=[str(e) for e in group.exceptions]
  boundary=list(events)
 else:
  tasks=[asyncio.create_task(sibling()),asyncio.create_task(fail())]
  gathered=asyncio.gather(*tasks)
  try:await gathered
  except ValueError as e:
   error=str(e)==FIXTURE['error'];failure_kind=type(e).__name__;failure_messages=[str(e)]
  boundary=list(events)
  if mode=='late-gather-cancel':gathered.cancel()
 release.set();await asyncio.gather(*tasks,return_exceptions=True)
 return {'failure_kind':failure_kind,'failure_messages':failure_messages,'boundary_events':boundary,'final_events':events,'all_tasks_done':all(t.done() for t in tasks)}, {
  'original_failure_reaches_caller':error,
  'sibling_cancelled_at_group_exit':'cancelled' in boundary and tasks[0].cancelled(),
  'async_cleanup_completed_at_group_exit':'cleanup_done' in boundary,
  'no_sibling_side_effect_after_failure':'side_effect' not in events}
mode=sys.argv[1] if len(sys.argv)>1 else 'fixed'
assert mode in ('broken','fixed','late-gather-cancel','swallow-cancel')
observed,conditions=asyncio.run(asyncio.wait_for(run(mode),timeout=3))
finish(mode,observed,conditions)
