/ dashboard / utils / coverage_server.py
coverage_server.py
 1  """
 2  Simple HTTP server for serving coverage reports.
 3  
 4  This allows the coverage HTML report to be displayed in an iframe
 5  without browser security restrictions on file:// URLs.
 6  """
 7  
 8  import http.server
 9  import socketserver
10  import threading
11  import os
12  from pathlib import Path
13  
14  
15  class CoverageServer:
16      """Singleton HTTP server for serving coverage reports."""
17  
18      _instance = None
19      _server = None
20      _thread = None
21      PORT = 8503
22  
23      def __new__(cls):
24          if cls._instance is None:
25              cls._instance = super().__new__(cls)
26          return cls._instance
27  
28      def start(self, coverage_dir: str = "coverage"):
29          """Start the HTTP server if not already running."""
30          if self._server is not None:
31              return  # Already running
32  
33          # Get absolute path to coverage directory
34          coverage_path = Path(coverage_dir).resolve()
35  
36          if not coverage_path.exists():
37              raise FileNotFoundError(f"Coverage directory not found: {coverage_path}")
38  
39          # Change to coverage directory
40          original_dir = os.getcwd()
41  
42          def serve():
43              try:
44                  os.chdir(coverage_path)
45                  handler = http.server.SimpleHTTPRequestHandler
46                  handler.extensions_map.update({
47                      ".js": "application/javascript",
48                      ".css": "text/css",
49                      ".html": "text/html",
50                  })
51  
52                  with socketserver.TCPServer(("", self.PORT), handler) as httpd:
53                      self._server = httpd
54                      print(f"Coverage server started on http://localhost:{self.PORT}")
55                      httpd.serve_forever()
56              except OSError as e:
57                  if e.errno == 98:  # Address already in use
58                      print(f"Coverage server port {self.PORT} already in use - reusing existing server")
59                      # Mark as running even though we didn't start it (assumes another instance is serving)
60                      self._server = True
61                  else:
62                      raise
63  
64          # Start server in background thread
65          self._thread = threading.Thread(target=serve, daemon=True)
66          self._thread.start()
67  
68      def get_url(self, path: str = "index.html") -> str:
69          """Get the URL for accessing a coverage file."""
70          return f"http://localhost:{self.PORT}/{path}"
71  
72      def is_running(self) -> bool:
73          """Check if the server is running."""
74          return self._server is not None
75  
76  
77  # Global instance
78  _coverage_server = CoverageServer()
79  
80  
81  def get_coverage_server() -> CoverageServer:
82      """Get the global coverage server instance."""
83      return _coverage_server