io_v2_demo_servers.py (5515B)
1 #!/usr/bin/env python3 2 """Actual migrated TCP/HTTP/registry demo lifetimes and independent wire bytes.""" 3 import argparse,json,socket,subprocess,threading,hashlib,selectors 4 from pathlib import Path 5 from source_order import Runner 6 from http_headers import number,tree_list 7 from io_v2_text import text 8 ROOT=Path(__file__).resolve().parent.parent 9 10 def main(): 11 p=argparse.ArgumentParser(description=__doc__);p.add_argument('tricu',type=Path);p.add_argument('--directory',type=Path,required=True) 12 a=p.parse_args();d=a.directory.resolve();d.mkdir(parents=True,exist_ok=False);r=Runner(a.tricu,d);checks=[];pins={} 13 def build(name): 14 bundle=d/(name+'.arboricx');r.run(name+'-compile',['compile','--file',ROOT/f'demos/interactionTrees/{name}.tri','--store',r.store,'--output',bundle,'--compile-timeout','600'],seconds=660) 15 pins[name]=r.imported(bundle,name+'-import');return pins[name] 16 def command(name,pin,values): 17 command=[str(r.exe),'arboricx','run','--module',pin,'--name','server','--store',str(r.store),'--io','--no-result'] 18 for i,value in enumerate(values): 19 f=d/f'{name}-{i}.arg';f.write_bytes(value);command+=['--arg-tree-file',str(f)] 20 return command 21 def start(name,pin,values): 22 proc=subprocess.Popen(command(name,pin,values),stdout=subprocess.PIPE,stderr=subprocess.PIPE) 23 with selectors.DefaultSelector() as sel: 24 sel.register(proc.stdout,selectors.EVENT_READ);assert sel.select(15),'no listen announcement' 25 port=int(proc.stdout.readline());return proc,port 26 def close(proc,port): 27 out,err=proc.communicate(timeout=20);assert proc.returncode==0,(out,err) 28 try: socket.create_connection(('127.0.0.1',port),timeout=1).close() 29 except OSError: return 30 raise AssertionError('listener outlived owning scope') 31 def endpoint(port=0):return tree_list([text('ipv4'),tree_list([number(x) for x in [127,0,0,1]]),number(port)]) 32 def request(port,data): 33 with socket.create_connection(('127.0.0.1',port),timeout=20) as s: 34 s.sendall(data);s.shutdown(socket.SHUT_WR);parts=[] 35 while True: 36 b=s.recv(4096) 37 if not b:return b''.join(parts) 38 parts.append(b) 39 proc,port=start('echo',build('echoServer'),[endpoint(),number(2)]) 40 try: 41 for data in [b'',bytes(range(256))*1024]: 42 with socket.create_connection(('127.0.0.1',port),timeout=20) as s: 43 failures=[] 44 def send(): 45 try:s.sendall(data);s.shutdown(socket.SHUT_WR) 46 except Exception as e:failures.append(e) 47 thread=threading.Thread(target=send);thread.start();out=[] 48 while True: 49 chunk=s.recv(4096) 50 if not chunk:break 51 out.append(chunk) 52 thread.join(5);assert not thread.is_alive() and not failures 53 assert b''.join(out)==data 54 close(proc,port);checks.append('echo-binary-and-scope-close') 55 finally: 56 if proc.poll() is None:proc.kill();proc.wait() 57 cases=[(b'GET /hello HTTP/1.1\r\nHost: test\r\n\r\n',200,b'Hello from /hello\n'),(b'HEAD /hello HTTP/1.1\r\nHost: test\r\n\r\n',200,b''),(b'POST /hello HTTP/1.1\r\nHost: test\r\nContent-Length: 0\r\n\r\n',405,b'Method not allowed\n'),(b'bad\r\n\r\n',400,b'Bad Request\n')] 58 proc,port=start('http',build('httpServer'),[endpoint(),number(len(cases))]) 59 try: 60 for payload,status,body in cases: 61 reply=request(port,payload);head,actual=reply.split(b'\r\n\r\n',1) 62 assert head.startswith(f'HTTP/1.1 {status} '.encode()) and actual==body,(head,actual) 63 length=len(b'Hello from /hello\n') if payload.startswith(b'HEAD') else len(body) 64 assert f'Content-Length: {length}\r\n'.encode() in head+b'\r\n' 65 close(proc,port);checks.append('http-methods-framing-head-and-scope-close') 66 finally: 67 if proc.poll() is None:proc.kill();proc.wait() 68 root=d/'registry';data=b'\0\xffregistry-demo';digest=hashlib.sha256(data).hexdigest() 69 proc,port=start('registry',build('arboricxServer'),[text(str(root)),endpoint(),number(3)]) 70 try: 71 assert request(port,b'GET /_arboricx/health HTTP/1.1\r\nHost: test\r\n\r\n').endswith(b'OK\n') 72 response=request(port,b'POST /_arboricx/bundle HTTP/1.1\r\nHost: test\r\nContent-Length: '+str(len(data)).encode()+b'\r\n\r\n'+data) 73 assert response.startswith(b'HTTP/1.1 201 ') and response.endswith(digest.encode()),response 74 assert request(port,f'GET /_arboricx/bundle/hash/{digest} HTTP/1.1\r\nHost: test\r\n\r\n'.encode()).endswith(b'\r\n\r\n'+data) 75 close(proc,port);checks.append('registry-binary-upload-download-and-scope-close') 76 finally: 77 if proc.poll() is None:proc.kill();proc.wait() 78 for name,values in [('echoServer',[endpoint(),number(0)]),('httpServer',[endpoint(),number(0)]),('arboricxServer',[text(str(d/'failed-ready-store')),endpoint(),number(0)])]: 79 with open('/dev/full','wb') as full: 80 failed=subprocess.run(command(name+'-failed-ready',pins[name],values),stdout=full,stderr=subprocess.PIPE,timeout=20) 81 assert failed.returncode!=0,(name,failed.stderr) 82 checks.append(name+'-announcement-failure') 83 (d/'report.json').write_text(json.dumps(dict(passed=checks,records=r.records),indent=2)+'\n');print('PASS migrated server demo wire/lifecycle controls') 84 if __name__=='__main__':main()