You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

107 line
3.3KB

  1. from pathlib import Path
  2. from contextlib import contextmanager
  3. import json
  4. from http.server import SimpleHTTPRequestHandler, HTTPServer
  5. from typing import NamedTuple, Any, Iterator
  6. from concurrent.futures import ThreadPoolExecutor
  7. from functools import partial
  8. import tempfile
  9. import sys
  10. import subprocess
  11. import pytest
  12. class DirectoryServingHTTPRequestHandler(SimpleHTTPRequestHandler):
  13. """
  14. A simple HTTP request handler that simply serves files from a directory given to the constructor.
  15. """
  16. def __init__(self, *args: Any, **kwargs: Any) -> None:
  17. self.dir = kwargs.pop('dir')
  18. super().__init__(*args, **kwargs)
  19. def translate_path(self, path: str) -> str:
  20. # Convert the given URL path to a path relative to the directory we are serving
  21. abspath = Path(super().translate_path(path)) # type: ignore
  22. relpath = abspath.relative_to(Path.cwd())
  23. return str(self.dir / relpath)
  24. class ServerInfo(NamedTuple):
  25. """
  26. Information about an HTTP server fixture
  27. """
  28. base_url: str
  29. root: Path
  30. @contextmanager
  31. def run_http_server(dirpath: Path, port: int) -> Iterator[ServerInfo]:
  32. """
  33. Context manager that spawns an HTTP server that serves thegiven directory on
  34. the given TCP port.
  35. """
  36. handler = partial(DirectoryServingHTTPRequestHandler, dir=dirpath)
  37. addr = ('127.0.0.1', port)
  38. pool = ThreadPoolExecutor()
  39. with HTTPServer(addr, handler) as httpd:
  40. pool.submit(lambda: httpd.serve_forever(poll_interval=0.1))
  41. try:
  42. print('Serving at', addr)
  43. yield ServerInfo(f'http://127.0.0.1:{port}', dirpath)
  44. finally:
  45. httpd.shutdown()
  46. @pytest.yield_fixture() # type: ignore
  47. def http_tmp_dir_server(tmp_path: Path, unused_tcp_port: int) -> Iterator[ServerInfo]:
  48. """
  49. Creates an HTTP server that serves the contents of a new
  50. temporary directory.
  51. """
  52. with run_http_server(tmp_path, unused_tcp_port) as s:
  53. yield s
  54. class RepoFixture:
  55. """
  56. A fixture handle to a dds HTTP repository, including a path and URL.
  57. """
  58. def __init__(self, dds_exe: Path, info: ServerInfo) -> None:
  59. self.server = info
  60. self.url = info.base_url
  61. self.dds_exe = dds_exe
  62. def import_json_data(self, data: Any) -> None:
  63. """
  64. Import some packages into the repo for the given JSON data. Uses
  65. mkrepo.py
  66. """
  67. with tempfile.NamedTemporaryFile(delete=False) as f:
  68. f.write(json.dumps(data).encode())
  69. f.close()
  70. self.import_json_file(Path(f.name))
  71. Path(f.name).unlink()
  72. def import_json_file(self, fpath: Path) -> None:
  73. """
  74. Import some package into the repo for the given JSON file. Uses mkrepo.py
  75. """
  76. subprocess.check_call([
  77. sys.executable,
  78. str(Path.cwd() / 'tools/mkrepo.py'),
  79. f'--dds-exe={self.dds_exe}',
  80. f'--dir={self.server.root}',
  81. f'--spec={fpath}',
  82. ])
  83. @pytest.yield_fixture() # type: ignore
  84. def http_repo(dds_exe: Path, http_tmp_dir_server: ServerInfo) -> Iterator[RepoFixture]:
  85. """
  86. Fixture that creates a new empty dds repository and an HTTP server to serve
  87. it.
  88. """
  89. subprocess.check_call([str(dds_exe), 'repoman', 'init', str(http_tmp_dir_server.root)])
  90. yield RepoFixture(dds_exe, http_tmp_dir_server)