Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion ast_monitor/model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import glob
import json
import os
import socket
import socketserver

from PyQt6.QtCore import QThread, pyqtSignal
Expand Down Expand Up @@ -82,6 +83,10 @@ def initialize_GUI(self) -> None:
"""
QMainWindow.__init__(self, flags=Qt.WindowType.FramelessWindowHint)
Ui_MainWindow.__init__(self)
self._cleanup_complete = False
self.timer = None
self.view = None
self.channel = None
self.setupUi(self)

# Map setting (not working on Raspberry Pi
Expand Down Expand Up @@ -123,6 +128,51 @@ def initialize_GUI(self) -> None:
self.timer.timeout.connect(self.render_data)
self.timer.start(250)

def closeEvent(self, event):
"""Release resources that Qt WebEngine keeps outside the widget tree."""
self.cleanup()
super().closeEvent(event)

def cleanup(self) -> None:
"""
Stop background resources owned by the main window.

PyQt's WebEngine objects need an explicit deleteLater/processEvents cycle
in tests, otherwise Qt prints profile-release warnings at interpreter exit.
"""
if self._cleanup_complete:
return
self._cleanup_complete = True

if self.timer is not None:
self.timer.stop()

if hasattr(self, "server_thread") and self.server_thread:
self.server_thread.stop()
self.server_thread.wait(3000)

if self.view is not None:
try:
self.view.loadFinished.disconnect(self.on_load_finished)
except (TypeError, RuntimeError):
pass
try:
self.view.stop()
except RuntimeError:
pass
try:
self.view.page().setWebChannel(None)
except RuntimeError:
pass
self.view.close()
self.view.setParent(None)
self.view.deleteLater()
self.view = None

if self.channel is not None:
self.channel.deleteLater()
self.channel = None

def update_map_component(self, progress: float = None, remaining_distance: float = None,
remaining_ascent: float = None, speed: float = None,
heartrate: int = None, duration=None, distance=None,
Expand Down Expand Up @@ -493,6 +543,7 @@ def convert_time_to_hours_minutes_seconds(self, time: int) -> str:
return time

class CustomTCPServer(socketserver.TCPServer):
allow_reuse_address = True
logging = True

def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True, logging=True):
Expand All @@ -511,17 +562,26 @@ def __init__(self, server_port: int, logging=True):
self.running = True
self.server_port = server_port
self.logging = logging
self.httpd = None

def run(self):
"""
Method that runs the HTTP server.
"""
Handler = CustomHandler
with CustomTCPServer(("", self.server_port), Handler,self.logging) as httpd:
with CustomTCPServer(("", self.server_port), Handler, logging=self.logging) as httpd:
httpd.timeout = 0.2
self.httpd = httpd
self.signal.emit(f"Serving at http://localhost:{self.server_port}")
while self.running:
httpd.handle_request()
self.httpd = None

def stop(self):
"""Method that stops the HTTP server."""
self.running = False
try:
with socket.create_connection(("127.0.0.1", self.server_port), timeout=0.2):
pass
except OSError:
pass
32 changes: 22 additions & 10 deletions tests/test_gui.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
import sys,os,random
import os
import random
import pytest
from PyQt6.QtCore import QEvent
from PyQt6.QtWidgets import QApplication

try:
from ast_monitor.model import AST
except ModuleNotFoundError:
sys.path.append('../')
import sys
sys.path.append("../")
from ast_monitor.model import AST



@pytest.fixture
def widget(qtbot):
hr_data = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'sensor_data', 'hr.txt')
gps_data = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'sensor_data', 'gps.txt')
route_data = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'development', 'routes', 'route.json')
random_port = random.randint(8000, 9000)
window = AST(hr_data, gps_data, route_data,server_port=random_port,logging=False)
hr_data = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "sensor_data", "hr.txt")
gps_data = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "sensor_data", "gps.txt")
route_data = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "development", "routes", "route.json")
random_port = random.randint(8000, 9000)

window = AST(hr_data, gps_data, route_data, server_port=random_port, logging=False)
qtbot.addWidget(window)
return window

yield window


window.cleanup()
window.close()
window.deleteLater()
QApplication.processEvents()
QApplication.sendPostedEvents(None, QEvent.Type.DeferredDelete)
qtbot.wait(100)


def test_window_title(qtbot,widget):
Expand Down Expand Up @@ -56,7 +69,6 @@ def test_start_stop_tracking(qtbot,widget):
assert widget.widget_start_stop.currentIndex() == 0 # Shows start icon, is stopped
widget.server_thread.stop()

@pytest.mark.skip(reason="Skipping this test temporarily")
def test_load_training(qtbot,widget):
widget.btn_move_right.click() # Move to Training page
widget.btn_load_training.click() # Load training
Expand Down
Loading