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 subprocess,tempfile,time,hashlib

def launch(kind,mode):
 with tempfile.TemporaryDirectory(prefix='lab-pipe-') as temp:
  ready=Path(temp)/'ready'
  p=subprocess.Popen([sys.executable,'-I',str(ROOT/'child.py'),str(ready),kind],stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.DEVNULL)
  out=err=b'';timed_out=False;alive_at_timeout=False;settled_before_guard=False
  try:
   deadline=time.monotonic()+2
   while not ready.exists() and p.poll() is None and time.monotonic()<deadline:time.sleep(.005)
   if not ready.exists():raise RuntimeError('Fixture child did not start')
   if kind=='output' and mode=='broken':
    try:p.wait(timeout=.25)
    except subprocess.TimeoutExpired:
     timed_out=True;out,err=p.communicate(timeout=2)
   else:
    try:out,err=p.communicate(timeout=.25 if kind=='hang' else 2)
    except subprocess.TimeoutExpired:
     timed_out=True;alive_at_timeout=p.poll() is None
     if mode!='timeout-only':
      p.kill();out,err=p.communicate(timeout=2)
   settled_before_guard=p.poll() is not None
   observed={'timed_out':timed_out,'alive_at_timeout':alive_at_timeout,'reaped_before_guard':settled_before_guard,'stdout_bytes':len(out),'stderr_bytes':len(err),'stdout_sha256':hashlib.sha256(out).hexdigest(),'stderr_sha256':hashlib.sha256(err).hexdigest(),'returncode':p.returncode}
  finally:
   if p.poll() is None:p.kill()
   p.communicate(timeout=2)
   p.stdout.close();p.stderr.close()
  observed['guard_child_reaped']=p.returncode is not None
  return observed,out,err
mode=sys.argv[1] if len(sys.argv)>1 else 'fixed'
assert mode in ('broken','fixed','timeout-only')
a,out,err=launch('output',mode)
b,partial,_=launch('hang',mode)
finish(mode,{'finite_output':a,'timeout_child':b,'output_limit_bytes':524288,'child_self_exit_bound_seconds':3},{
 'finite_child_completes_during_selected_wait_strategy':not a['timed_out'] and a['returncode']==0,
 'stdout_drained_exactly':out==b'O'*FIXTURE['stream_bytes'],
 'stderr_drained_exactly':err==b'E'*FIXTURE['stream_bytes'],
 'timeout_child_killed_and_reaped_before_return':b['timed_out'] and b['alive_at_timeout'] and b['reaped_before_guard'] and b['returncode']!=0,
 'partial_stdout_preserved_after_timeout':partial==b'LAB_partial'})
