tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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()