Skip to content

Commit

Permalink
Merge pull request #80 from sunyhydralab/printer-refactor
Browse files Browse the repository at this point in the history
Printer refactor
  • Loading branch information
iron768 authored Dec 16, 2024
2 parents e7306cb + 41dde95 commit aec45e1
Show file tree
Hide file tree
Showing 109 changed files with 76,654 additions and 7,173 deletions.
Binary file added .DS_Store
Binary file not shown.
13 changes: 11 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/.vscode

.DS_Store

*.pyc
__pycache__/
Expand All @@ -9,4 +9,13 @@ migrations/
*.db

server/qview3dserver.egg-info/
server/dist
server/dist

printeremu/testserver/node_modules/

.DS_Store
/qodana.yaml
/Tests/logs/
/Tests/server/
/logs/
/server/logs/
365 changes: 365 additions & 0 deletions Tests/conftest.py

Large diffs are not rendered by default.

70 changes: 70 additions & 0 deletions Tests/parallel_test_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import os
import sys
import re
import subprocess
import platform

# Add test root to sys.path if needed
from globals import root_path
if root_path not in sys.path:
sys.path.append(root_path)
serverpath = os.path.join(root_path, "server")
if serverpath not in sys.path:
sys.path.append(serverpath)
testpath = os.path.join(root_path, "Tests")
if testpath not in sys.path:
sys.path.append(testpath)

from server.Classes.Ports import Ports

PORTS = []
# List of available ports for testing
if platform.system() == "Windows":
import winreg
path = "HARDWARE\\DEVICEMAP\\SERIALCOMM"
try:
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path)
for i in range(256):
try:
val = winreg.EnumValue(key, i)
if re.match(r"COM\d+|LPT\d+", val[1]):
PORTS.append(val[1])
except OSError:
break
except FileNotFoundError:
pass
elif platform.system() == "Darwin":
import glob
PORTS = glob.glob("/dev/tty.*")
else:
import glob
PORTS = glob.glob("/dev/tty[A-Za-z]*")



# Function to run pytest for a specific port
testLevel = 10
verbosity = 2
runFlags = 0b010 # 0b001: -s, 0b010: -vvv or -p no:terminal, 0b100: debug or info

def run_tests_for_port(comm_port):
env = os.environ.copy()
env["PORT"] = comm_port
args = ["pytest", testpath, f"--myVerbose={verbosity}", f"--port={comm_port}"]
if runFlags & 0b1: args.append("-s")
args.append("-vv") if runFlags & 0b10 else args.append("-p no:terminal")
env["LEVEL"] = "DEBUG" if runFlags & 0b100 else "INFO"
subprocess.Popen(args, env=env).wait()

if __name__ == "__main__":
from concurrent.futures import ThreadPoolExecutor, as_completed
if len(PORTS) != 0:
with ThreadPoolExecutor(max_workers=len(PORTS)) as executor:
futures = [executor.submit(run_tests_for_port, port) for port in PORTS if
Ports.getPortByName(port) is not None]
for future in as_completed(futures):
try:
future.result()
except Exception as e:
from globals import current_app
current_app.handle_errors_and_logging(e)
146 changes: 146 additions & 0 deletions Tests/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import os
import re
import pytest
from Classes.Logger import Logger
from parallel_test_runner import testLevel

def __desc__(): return "App Tests"

@pytest.mark.dependency()
@pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
def test_db_to_make_sure_it_has_valid_file_path(app):
db_file_no_path = app.config["SQLALCHEMY_DATABASE_URI"].split("/")[-1].split("\\")[-1]
assert db_file_no_path, "database_uri doesn't exist?"
assert db_file_no_path == "hvamc.db", f"database_uri is {db_file_no_path}"
assert os.path.exists(app.config["SQLALCHEMY_DATABASE_URI"].split("sqlite:///")[
-1]), f"Database file {app.config["SQLALCHEMY_DATABASE_URI"].split("sqlite:///")[-1]} does not exist"


@pytest.mark.dependency(depends=["test_db_to_make_sure_it_has_valid_file_path"])
@pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
def test_base_url_for_http_responses_has_valid_format(app):
assert app.config["base_url"], "base_url doesnt exist?"
assert re.match(r"http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}$", app.config["base_url"]) or re.match(
r"http://localhost:\d{1,5}$", app.config["base_url"]), f"base_url is {app.config['base_url']}"


@pytest.mark.dependency(depends=["test_base_url_for_http_responses_has_valid_format"])
@pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
def test_environment_for_development(app):
assert app.config["environment"], "environment doesnt exist?"
assert app.config["environment"] == "development", f"environment is {app.config['environment']}"


@pytest.mark.dependency(depends=["test_environment_for_development"])
@pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
def test_logger_is_custom_implementation_and_exists(app):
assert app.logger, "myLogger doesnt exist?"
assert app.logger.name, "name doesnt exist?"
assert str(app.logger.name) == "Logger_App", f"myLogger is {str(app.logger.name)}"
assert isinstance(app.logger, Logger), "myLogger is not an instance of Logger?"
assert app.logger.fileLogger, "fileLogger doesnt exist?"


@pytest.mark.dependency(depends=["test_logger_is_custom_implementation_and_exists"])
@pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
def test_socketio_exists_and_works(app):
assert app.socketio, "socketio doesnt exist?"
assert app.socketio.async_mode, "async_mode doesnt exist?"
assert app.socketio.async_mode == "threading", f"async_mode is {app.socketio.async_mode}"
socketio_test_client = app.socketio.test_client(app)
assert socketio_test_client.is_connected(), "socketio_test_client is not connected?"
socketio_test_client.emit('my_event', {'data': 'test'})
received = socketio_test_client.get_received()
assert len(received) == 0, "Response received from socketio"


@pytest.mark.dependency(depends=["test_socketio_exists_and_works"])
@pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
def test_handle_errors_and_logging(app):
assert app.handle_errors_and_logging, "handle_errors_and_logging doesnt exist?"
assert callable(app.handle_errors_and_logging), "handle_errors_and_logging is not callable?"
assert app.handle_errors_and_logging(Exception("Test Exception")) is False, "handle_errors_and_logging did not return False?"


@pytest.mark.dependency(depends=["test_handle_errors_and_logging"])
@pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
def test_static_loading_for_client(app):
assert app.static_folder, "static_folder doesnt exist?"
assert os.path.join("client","dist") in app.static_folder, f"static_folder is {app.static_folder}"
assert os.path.exists(app.static_folder), f"static_folder {app.static_folder} does not exist"


@pytest.mark.dependency(depends=["test_static_loading_for_client"])
@pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
def test_index_html_exists_in_the_static_files(app):
assert os.path.exists(os.path.join(app.static_folder, "index.html")), f"index.html does not exist in {app.static_folder}"


@pytest.mark.dependency(depends=["test_index_html_exists_in_the_static_files"])
@pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
def test_main_view_response_is_200(app):
with app.test_client() as client:
response = client.get('/')
assert response.status_code == 200, f"Response status code is {response.status_code}"
assert response.data, "Response data is empty?"
assert b'<!DOCTYPE html>' in response.data, "Response data does not contain <!DOCTYPE html>?"
assert b'<html' in response.data, "Response data does not contain <html>?"
assert b'<head>' in response.data, "Response data does not contain <head>?"
assert b'<title>' in response.data, "Response data does not contain <title>?"
assert b'<body>' in response.data, "Response data does not contain <body>?"
assert b'<div id="app">' in response.data, 'Response data does not contain <div id="app">?'
assert b'<script type="module" crossorigin src=' in response.data, 'Response data does not contain <script type="module" crossorigin src=?'
assert b'<link rel="stylesheet" crossorigin href=' in response.data, 'Response data does not contain <link rel="stylesheet" crossorigin href=?'
assert b'</body>' in response.data, "Response data does not contain </body>?"
assert b'</html>' in response.data, "Response data does not contain </html>?"

# @pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
# def test_queue_view_response(app):
# with app.test_client() as client:
# response = client.get('/queue')
# assert response.status_code == 200, f"Response status code is {response.status_code}"
# assert response.data, "Response data is empty?"
# assert b'<!DOCTYPE html>' in response.data, "Response data does not contain <!DOCTYPE html>?"
# assert b'<html' in response.data, "Response data does not contain <html>?"
# assert b'<head>' in response.data, "Response data does not contain <head>?"
# assert b'<title>' in response.data, "Response data does not contain <title>?"
# assert b'<body>' in response.data, "Response data does not contain <body>?"
# assert b'<div id="app">' in response.data, 'Response data does not contain <div id="app">?'
# assert b'<script type="module" crossorigin src=' in response.data, 'Response data does not contain <script type="module" crossorigin src=?'
# assert b'<link rel="stylesheet" crossorigin href=' in response.data, 'Response data does not contain <link rel="stylesheet" crossorigin href=?'
# assert b'</body>' in response.data, "Response data does not contain </body>?"
# assert b'</html>' in response.data, "Response data does not contain </html>?"
#
# @pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
# def test_registered_view_response(app):
# with app.test_client() as client:
# response = client.get('/registration')
# assert response.status_code == 200, f"Response status code is {response.status_code}"
# assert response.data, "Response data is empty?"
# assert b'<!DOCTYPE html>' in response.data, "Response data does not contain <!DOCTYPE html>?"
# assert b'<html' in response.data, "Response data does not contain <html>?"
# assert b'<head>' in response.data, "Response data does not contain <head>?"
# assert b'<title>' in response.data, "Response data does not contain <title>?"
# assert b'<body>' in response.data, "Response data does not contain <body>?"
# assert b'<div id="app">' in response.data, 'Response data does not contain <div id="app">?'
# assert b'<script type="module" crossorigin src=' in response.data, 'Response data does not contain <script type="module" crossorigin src=?'
# assert b'<link rel="stylesheet" crossorigin href=' in response.data, 'Response data does not contain <link rel="stylesheet" crossorigin href=?'
# assert b'</body>' in response.data, "Response data does not contain </body>?"
# assert b'</html>' in response.data, "Response data does not contain </html>?"
#
# @pytest.mark.skipif(condition=testLevel < 1, reason="Not doing lvl 1 tests")
# def test_error_view_response(app):
# with app.test_client() as client:
# response = client.get('/error')
# assert response.status_code == 200, f"Response status code is {response.status_code}"
# assert response.data, "Response data is empty?"
# assert b'<!DOCTYPE html>' in response.data, "Response data does not contain <!DOCTYPE html>?"
# assert b'<html' in response.data, "Response data does not contain <html>?"
# assert b'<head>' in response.data, "Response data does not contain <head>?"
# assert b'<title>' in response.data, "Response data does not contain <title>?"
# assert b'<body>' in response.data, "Response data does not contain <body>?"
# assert b'<div id="app">' in response.data, 'Response data does not contain <div id="app">?'
# assert b'<script type="module" crossorigin src=' in response.data, 'Response data does not contain <script type="module" crossorigin src=?'
# assert b'<link rel="stylesheet" crossorigin href=' in response.data, 'Response data does not contain <link rel="stylesheet" crossorigin href=?'
# assert b'</body>' in response.data, "Response data does not contain </body>?"
# assert b'</html>' in response.data, "Response data does not contain </html>?"
56 changes: 56 additions & 0 deletions Tests/test_device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import os
import pytest
from Classes.Ports import Ports
from Classes.Vector3 import Vector3
from parallel_test_runner import testLevel

testLevelToRun = testLevel
shortTest = True

def __desc__():
return "Device Tests"

def __repr__():
return f"test_device.py running on port {Ports.getPortByName(os.getenv('PORT'))}"

@pytest.mark.dependency(depends=["test_app.py::test_main_view_response_is_200"], scope="session")
@pytest.mark.skipif(condition=testLevelToRun < 1, reason="Not doing lvl 1 tests")
def test_connection(app, fabricator):
assert fabricator.device is not None, f"No printer connected on {fabricator.device.DESCRIPTION}"
assert fabricator.device.serialConnection is not None, f"No serial connection on {fabricator.device.DESCRIPTION}"
assert fabricator.device.serialConnection.isOpen(), f"Serial connection not open on {fabricator.device.DESCRIPTION}"

@pytest.mark.dependency(depends=["test_connection"])
@pytest.mark.skipif(condition=testLevelToRun < 3, reason="Not doing lvl 3 tests")
def test_home(app, fabricator):
assert fabricator.device.home(), f"Failed to home {fabricator.device.DESCRIPTION}"

@pytest.mark.dependency(depends=["test_home"])
@pytest.mark.skipif(condition=testLevelToRun < 5, reason="Not doing lvl 5 tests")
def test_draw_square(app, fabricator):
squarePasses = 0
for point in [Vector3(50.0, 50.0, 2.0), Vector3(200.0, 50.0, 2.0), Vector3(200.0, 150.0, 2.0),
Vector3(50.0, 150.0, 2.0)]:
squarePasses += 1 if fabricator.device.goTo(point, isVerbose=True) else 0
assert squarePasses == 4, f"Failed to draw square on {fabricator.device.DESCRIPTION}"

@pytest.mark.dependency(depends=["test_home"])
@pytest.mark.skipif(condition=testLevelToRun < 5, reason="Not doing lvl 5 tests")
def test_draw_octagon(app, fabricator):
octagonPasses = 0
for point in [Vector3(50.0, 100.0, 2.0), Vector3(100.0, 50.0, 2.0), Vector3(150.0, 50.0, 2.0),
Vector3(200.0, 100.0, 2), Vector3(200.0, 150.0, 2.0), Vector3(150.0, 200.0, 2),
Vector3(100.0, 200.0, 2.0), Vector3(50.0, 150.0, 2.0)]:
octagonPasses += 1 if fabricator.device.goTo(point) else 0
assert octagonPasses == 8, f"Failed to draw octagon on {fabricator.device.DESCRIPTION}"

@pytest.mark.dependency(depends=["test_home"])
@pytest.mark.skipif(condition=testLevelToRun < 5, reason="Not doing lvl 5 tests")
def test_go_to_center(app, fabricator):
assert fabricator.device.goTo(Vector3(125.0, 100.0, 2.0)), f"Failed to go to location on {fabricator.device.DESCRIPTION}"

@pytest.mark.dependency(depends=["test_go_to_center"])
@pytest.mark.skipif(condition=testLevelToRun < 5, reason="Not doing lvl 5 tests")
def test_draw_circle(app, fabricator):
assert fabricator.device.sendGcode(b"G2 X125 Y100 I25 J0\n"), f"Failed to draw circle on {fabricator.device.DESCRIPTION}"
assert fabricator.device.sendGcode(b"G2 X125 Y100 I-25 J0\n"), f"Failed to draw circle on {fabricator.device.DESCRIPTION}"
Loading

0 comments on commit aec45e1

Please sign in to comment.