tricu

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

arboricx_deployment.py (3911B)


      1 #!/usr/bin/env python3
      2 """Haskell-only deployment runner smoke test (fixed port 8080 must be free)."""
      3 import hashlib
      4 from pathlib import Path
      5 import socket
      6 import subprocess
      7 import sys
      8 import tempfile
      9 import time
     10 import urllib.error
     11 import urllib.request
     12 
     13 
     14 def main():
     15     runner = str(Path(sys.argv[1]).resolve())
     16     opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
     17 
     18     def request(path, data=None, status=200):
     19         req = urllib.request.Request('http://127.0.0.1:8080' + path, data=data)
     20         try:
     21             response = opener.open(req, timeout=10)
     22         except urllib.error.HTTPError as error:
     23             response = error
     24         with response:
     25             assert response.status == status, (path, response.status)
     26             return response.read()
     27 
     28     with tempfile.TemporaryDirectory(prefix='arboricx-deployment-') as temporary:
     29         root = Path(temporary)
     30         # Startup failures must exit nonzero rather than return an ignored Result.
     31         (root / 'store').write_text('not a directory')
     32         failed = subprocess.run([runner], cwd=root, capture_output=True, timeout=30)
     33         assert failed.returncode != 0, failed
     34         (root / 'store').unlink()
     35         with socket.socket() as occupied:
     36             occupied.bind(('0.0.0.0', 8080))
     37             occupied.listen()
     38             failed = subprocess.run([runner], cwd=root, capture_output=True, timeout=30)
     39             assert failed.returncode != 0, failed
     40 
     41         payload = b'opaque registry bytes\x00\xff\n'
     42         digest = hashlib.sha256(payload).hexdigest()
     43         route = '/_arboricx/bundle/hash/' + digest
     44         # Second start checks persistence without a compiler cache in the cwd.
     45         for iteration in range(2):
     46             with (root / 'server.log').open('w+') as log:
     47                 proc = subprocess.Popen([runner], cwd=root, stdout=log, stderr=log)
     48                 try:
     49                     for _ in range(100):
     50                         if proc.poll() is not None:
     51                             log.seek(0)
     52                             raise AssertionError(log.read())
     53                         try:
     54                             assert request('/_arboricx/health') == b'OK\n'
     55                             break
     56                         except (OSError, urllib.error.URLError):
     57                             time.sleep(.1)
     58                     else:
     59                         raise AssertionError('server startup timeout')
     60                     assert b'Arboricx Server' in request('/')
     61                     if iteration == 0:
     62                         reply = request('/_arboricx/bundle', payload, status=201)
     63                         assert reply.strip() == digest.encode(), reply
     64                     assert request(route) == payload
     65                     request('/_arboricx/bundle/hash/not-a-hash', status=400)
     66                     request('/_arboricx/bundle/hash/' + '0' * 64, status=404)
     67                     request('/_arboricx/package/../bad', status=400)
     68                     package = root / 'store/packages/example'
     69                     package.mkdir(parents=True, exist_ok=True)
     70                     record = b'arboricx.package-release.v1\nexample\t1.0\t' + b'a' * 64 + b'\t' + digest.encode() + b'\n'
     71                     (package / '1.0').write_bytes(record)
     72                     assert request('/_arboricx/package/example/1.0') == record
     73                     request('/_arboricx/package/example/missing', status=404)
     74                 finally:
     75                     proc.terminate()
     76                     try:
     77                         proc.wait(timeout=5)
     78                     except subprocess.TimeoutExpired:
     79                         proc.kill()
     80                         proc.wait()
     81         assert {p.name for p in root.iterdir()} == {'store', 'server.log'}
     82     print('PASS: deployment startup failures, health, binary upload/download, path controls, packages, restart')
     83 
     84 
     85 if __name__ == '__main__':
     86     main()