"""Exercise the CLI with the working directory absent from Python's import path."""
import http.client
import os
from pathlib import Path
import subprocess
import sys
import time
import pytest
import skirr
def cli_env():
# Console scripts start with their bin directory on sys.path. -P gives the
# subprocess the same constraint without relying on an installed launcher.
return dict(os.environ, PYTHONPATH=str(Path(skirr.__file__).resolve().parent.parent))
@pytest.mark.parametrize('directory', ['cwd', 'explicit'])
def test_cli_serves_application_from_selected_directory(tmp_path, unused_tcp_port, directory):
project = tmp_path / 'project with spaces'
project.mkdir()
(project / 'app.py').write_text(
"async def app(scope, receive, send):\n"
" await send({'type': 'http.response.start', 'status': 200})\n"
" await send({'type': 'http.response.body', 'body': b'local-app'})\n"
)
cwd = project if directory == 'cwd' else tmp_path
arguments = ['app:app', '--port', str(unused_tcp_port), '--lifespan', 'off']
if directory == 'explicit':
(cwd / 'app.py').write_text("raise RuntimeError('Imported the wrong app directory')\n")
arguments += ['--app-dir', str(project.relative_to(cwd))]
process = subprocess.Popen(
[sys.executable, '-P', '-c', 'from skirr import main; main()', *arguments],
cwd=cwd, env=cli_env(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True,
)
try:
deadline = time.monotonic() + 5
while True:
assert process.poll() is None, process.communicate()[0]
client = http.client.HTTPConnection('127.0.0.1', unused_tcp_port, timeout=.5)
try:
client.request('GET', '/')
response = client.getresponse()
assert response.status == 200
assert response.read() == b'local-app'
break
except (OSError, http.client.HTTPException):
if time.monotonic() >= deadline:
raise
time.sleep(.02)
finally:
client.close()
finally:
if process.poll() is None:
process.terminate()
try:
output, _ = process.communicate(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
process.communicate()
raise
assert process.returncode == 0, output
@pytest.mark.parametrize('reference', ['app', 'app:', ':app'])
def test_cli_reports_malformed_application_reference(tmp_path, reference):
result = subprocess.run(
[sys.executable, '-P', '-c', 'from skirr import main; main()', reference],
cwd=tmp_path, env=cli_env(), capture_output=True, text=True, timeout=5,
)
assert result.returncode == 2
assert 'module:attribute' in result.stderr
assert 'Traceback' not in result.stderr