Removed AI generated test suites

This commit is contained in:
2026-07-27 12:07:37 +02:00
parent 8b45674cd2
commit 36051e1577
16 changed files with 1 additions and 846 deletions

1
.gitignore vendored
View File

@@ -4,7 +4,6 @@ __pycache__/
*.egg-info/
build/
dist/
.pytest_cache/
.vscode/*
!.vscode/launch.json
!.vscode/tasks.json

27
.vscode/tasks.json vendored
View File

@@ -1,31 +1,6 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Tests: Run pytest",
"type": "shell",
"command": "${command:python.interpreterPath}",
"args": [
"-m",
"pytest"
],
"options": {
"cwd": "${workspaceFolder}",
"env": {
"PYTHONPATH": "${workspaceFolder}/src"
}
},
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"clear": true,
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": []
},
{
"label": "Qt: Open Designer",
"type": "shell",
@@ -69,4 +44,4 @@
"problemMatcher": []
}
]
}
}

View File

@@ -21,17 +21,8 @@ bedit = "bedit_gui.application:main"
[project.optional-dependencies]
dev = [
"pytest>=8",
"ruff>=0.5",
]
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
addopts = "--strict-config --strict-markers -ra"
testpaths = ["tests"]
markers = [
"unit: fast tests without GUI event-loop interaction",
"gui: tests that create or interact with Qt objects",
]

View File

@@ -1,37 +0,0 @@
"""Shared pytest fixtures for bedit tests.
Qt-specific fixtures can be added here later when GUI testing starts. Keeping
the model fixtures independent from Qt lets the core suite stay lightweight.
"""
from __future__ import annotations
import pytest
from bedit_core.models import (
Component,
ComponentID,
Document,
Graph,
GraphImplementation,
ID,
Interface,
)
@pytest.fixture
def minimal_document() -> Document:
"""Return the smallest useful graph document for core tests."""
root_id = ComponentID("root")
root = Component(
name="Root",
interface=Interface(),
parameters={},
implementation=GraphImplementation(Graph()),
)
return Document(
format_version=1,
id=ID("document"),
name="Test document",
root={root_id: root},
)

View File

@@ -1,96 +0,0 @@
from __future__ import annotations
import logging
from pathlib import Path
import pytest
from PySide6.QtCore import Qt
from PySide6.QtGui import QUndoCommand
from PySide6.QtWidgets import QApplication
from bedit_gui.controllers.document_controller import DocumentController
from bedit_gui.controllers.log_controller import LogController
from bedit_gui.controllers.undo_controller import UndoController
from bedit_gui.documents import Document
from bedit_gui.services.application_logging import get_logger, set_log_level
from bedit_gui.views.dialogs.document_dialogs import SaveChangesChoice
from bedit_gui.views.main_window import MainWindow
class FakeDialogs:
def __init__(self, path: Path) -> None:
self.path = path
def choose_open_path(self, _current_path: Path | None) -> Path:
return self.path
def choose_save_path(self, _current_path: Path | None) -> Path:
return self.path
def ask_save_changes(self) -> SaveChangesChoice:
return SaveChangesChoice.DISCARD
def show_file_error(self, _title: str, error: Exception) -> None:
raise AssertionError("unexpected file error") from error
@pytest.fixture(scope="module")
def qt_app() -> QApplication:
return QApplication.instance() or QApplication([])
@pytest.mark.unit
def test_log_level_and_qt_log_model(qt_app: QApplication) -> None:
window = MainWindow()
controller = LogController(window, logging.WARNING)
logger = get_logger("tests")
logger.info("hidden message")
logger.warning("visible message")
qt_app.processEvents()
assert controller.model.rowCount() == 1
assert "WARNING visible message" in controller.model.data(
controller.model.index(0),
Qt.ItemDataRole.DisplayRole,
)
set_log_level(logging.INFO)
logger.info("now visible")
qt_app.processEvents()
assert controller.model.rowCount() == 2
@pytest.mark.unit
def test_document_and_undo_actions_are_logged(
qt_app: QApplication,
tmp_path: Path,
) -> None:
path = tmp_path / "document.json"
window = MainWindow()
log_controller = LogController(window)
document = Document(qt_app)
dialogs = FakeDialogs(path)
document_controller = DocumentController(document, window, dialogs)
undo_controller = UndoController(document, window)
document_controller.new_document()
document_controller.save_document_as()
document_controller.save_document()
document_controller.open_document()
document.undo_stack.push(QUndoCommand("test change"))
undo_controller.undo()
undo_controller.redo()
qt_app.processEvents()
messages = [
log_controller.model.data(log_controller.model.index(row))
for row in range(log_controller.model.rowCount())
]
assert any("Created new document" in message for message in messages)
assert any("Opened document:" in message for message in messages)
assert any("Saved document:" in message for message in messages)
assert any("Saved document as:" in message for message in messages)
assert any("Undo: test change" in message for message in messages)
assert any("Redo: test change" in message for message in messages)

View File

@@ -1,88 +0,0 @@
from __future__ import annotations
import pytest
from bedit_core.bondgraph import flatten_bondgraph
from bedit_core.models import (
BondConnection,
BondPort,
Component,
ComponentID,
ConnectionID,
EquationImplementation,
Graph,
GraphImplementation,
Interface,
PortID,
SignalDirection,
)
def _leaf(name: str, port_id: PortID) -> Component:
return Component(
name=name,
interface=Interface(
{port_id: BondPort(name="p", direction=SignalDirection.INPUT)}
),
parameters={},
implementation=EquationImplementation(),
)
@pytest.mark.unit
def test_flattens_bonds_across_a_component_boundary() -> None:
boundary_id = PortID("boundary")
inner_id = PortID("inner")
outer_id = PortID("outer")
inner = _leaf("Inner", inner_id)
subsystem = Component(
name="Subsystem",
interface=Interface(
{
boundary_id: BondPort(
name="boundary",
direction=SignalDirection.INPUT,
)
}
),
parameters={},
implementation=GraphImplementation(
Graph(
components={ComponentID("inner"): inner},
connections={
ConnectionID("inside"): BondConnection(
boundary_id,
inner_id,
)
},
)
),
)
outer = _leaf("Outer", outer_id)
root = Component(
name="Root",
interface=Interface(),
parameters={},
implementation=GraphImplementation(
Graph(
components={
ComponentID("subsystem"): subsystem,
ComponentID("outer"): outer,
},
connections={
ConnectionID("outside"): BondConnection(
outer_id,
boundary_id,
)
},
)
),
)
network = flatten_bondgraph(root)
boundary = next(port for port in network.ports if port.port_id == boundary_id)
assert len(network.components) == 4
assert len(network.ports) == 3
assert len(network.bonds) == 2
assert len(network.bonds_for(boundary)) == 2

View File

@@ -1,79 +0,0 @@
from __future__ import annotations
from pathlib import Path
import pytest
from PySide6.QtGui import QUndoCommand
from PySide6.QtWidgets import QApplication
from bedit_gui.controllers.document_controller import DocumentController
from bedit_gui.documents import Document
from bedit_gui.views.dialogs.document_dialogs import SaveChangesChoice
from bedit_gui.views.main_window import MainWindow
class FakeDialogs:
def __init__(self) -> None:
self.open_path: Path | None = None
self.save_path: Path | None = None
self.save_choice = SaveChangesChoice.CANCEL
self.errors: list[tuple[str, Exception]] = []
def choose_open_path(self, _current_path: Path | None) -> Path | None:
return self.open_path
def choose_save_path(self, _current_path: Path | None) -> Path | None:
return self.save_path
def ask_save_changes(self) -> SaveChangesChoice:
return self.save_choice
def show_file_error(self, title: str, error: Exception) -> None:
self.errors.append((title, error))
@pytest.fixture(scope="module")
def qt_app() -> QApplication:
return QApplication.instance() or QApplication([])
@pytest.mark.unit
def test_new_document_respects_unsaved_changes(qt_app: QApplication) -> None:
document = Document(qt_app)
window = MainWindow()
dialogs = FakeDialogs()
controller = DocumentController(document, window, dialogs)
original_id = document.model.id
document.undo_stack.push(QUndoCommand("change"))
controller.new_document()
assert document.model.id == original_id
dialogs.save_choice = SaveChangesChoice.DISCARD
controller.new_document()
assert document.model.id != original_id
assert not document.modified
@pytest.mark.unit
def test_save_as_then_open_document(
qt_app: QApplication,
tmp_path: Path,
) -> None:
document = Document(qt_app)
window = MainWindow()
dialogs = FakeDialogs()
controller = DocumentController(document, window, dialogs)
original_id = document.model.id
path = tmp_path / "document.json"
dialogs.save_path = path
assert controller.save_document_as()
document.new()
dialogs.open_path = path
controller.open_document()
assert document.model.id == original_id
assert document.path == path
assert not dialogs.errors

View File

@@ -1,14 +0,0 @@
from __future__ import annotations
import pytest
from bedit_core.models import Document
from bedit_util.graphviz import document_to_dot
@pytest.mark.unit
def test_dot_contains_component_name_path(minimal_document: Document) -> None:
dot = document_to_dot(minimal_document, "Root")
assert 'label="Root"' in dot
assert 'xlabel="Root"' in dot

View File

@@ -1,37 +0,0 @@
from __future__ import annotations
from pathlib import Path
import pytest
from PySide6.QtGui import QUndoCommand
from bedit_gui.documents import Document
@pytest.mark.unit
@pytest.mark.parametrize("suffix", [".beb", ".json"])
def test_document_save_and_open(tmp_path: Path, suffix: str) -> None:
path = tmp_path / f"document{suffix}"
document = Document()
original_id = document.model.id
document.save_as(path)
document.new()
assert document.model.id != original_id
document.open(path)
assert document.model.id == original_id
assert document.path == path
assert not document.modified
@pytest.mark.unit
def test_document_modified_state_uses_undo_stack() -> None:
document = Document()
document.undo_stack.push(QUndoCommand("change"))
assert document.modified
document.undo_stack.setClean()
assert not document.modified

View File

@@ -1,14 +0,0 @@
"""Basic smoke tests for the core model."""
from __future__ import annotations
import pytest
from bedit_core.models import Document, GraphImplementation
@pytest.mark.unit
def test_minimal_document_has_graph_root(minimal_document: Document) -> None:
root = next(iter(minimal_document.root.values()))
assert isinstance(root.implementation, GraphImplementation)

View File

@@ -1,115 +0,0 @@
from __future__ import annotations
from copy import deepcopy
import pytest
from PySide6.QtWidgets import QApplication
from bedit_core import models
from bedit_core.models import (
BondPort,
Component,
Interface,
PortID,
SignalDirection,
SignalPort,
ValueType,
)
from bedit_gui.documents import Document
from bedit_gui.views.port_editor_widget import PortEditorWidget
@pytest.fixture(scope="module")
def qt_app() -> QApplication:
return QApplication.instance() or QApplication([])
@pytest.mark.unit
def test_port_editor_uses_a_detached_draft(qt_app: QApplication) -> None:
port_id = PortID("input")
original = {
port_id: SignalPort(
name="Input",
direction=SignalDirection.INPUT,
)
}
editor = PortEditorWidget()
editor.set_ports(original)
editor.ui.nameEdit.setText("Changed")
editor.ui.nameEdit.textEdited.emit("Changed")
assert original[port_id].name == "Input"
assert editor.ports()[port_id].name == "Changed"
@pytest.mark.unit
def test_port_type_controls_option_visibility(qt_app: QApplication) -> None:
editor = PortEditorWidget()
editor.set_ports(
{
PortID("port"): SignalPort(
name="Port",
direction=SignalDirection.INPUT,
value_type=ValueType.REAL,
)
}
)
assert not editor.ui.signalTypeComboBox.isHidden()
assert editor.ui.domainComboBox.isHidden()
editor.ui.typeBond.setChecked(True)
assert editor.ui.signalTypeComboBox.isHidden()
assert not editor.ui.domainComboBox.isHidden()
@pytest.mark.unit
def test_bond_causality_is_loaded_and_changed(qt_app: QApplication) -> None:
port_id = PortID("bond")
editor = PortEditorWidget()
editor.set_ports({port_id: BondPort("Bond", SignalDirection.INPUT, causality_preference=models.PortCausality.PREFERRED_FLOW_OUT)})
assert editor.ui.causalityComboBox.currentData() is models.PortCausality.PREFERRED_FLOW_OUT
index = editor.ui.causalityComboBox.findData(models.PortCausality.FIXED_EFFORT_OUT)
editor.ui.causalityComboBox.setCurrentIndex(index)
assert editor.ports()[port_id].causality_preference is models.PortCausality.FIXED_EFFORT_OUT
@pytest.mark.unit
def test_port_changes_are_one_undoable_operation(
qt_app: QApplication,
minimal_document,
) -> None:
component: Component = next(iter(minimal_document.root.values()))
removed_id = PortID("removed")
changed_id = PortID("changed")
added_id = PortID("added")
component.interface = Interface(
{
removed_id: SignalPort("Remove", SignalDirection.INPUT),
changed_id: SignalPort("Before", SignalDirection.INPUT),
}
)
original = deepcopy(component.interface.ports)
updated = {
changed_id: BondPort(
"After",
SignalDirection.OUTPUT,
domain="electrical",
),
added_id: SignalPort("Added", SignalDirection.INPUT),
}
document = Document(qt_app)
document.update_component_ports(component, updated)
assert component.interface.ports == updated
document.undo_stack.undo()
assert component.interface.ports == original
document.undo_stack.redo()
assert component.interface.ports == updated

View File

@@ -1,17 +0,0 @@
"""Basic smoke tests for document serialization."""
from __future__ import annotations
import pytest
from bedit_core.models import Document
from bedit_core.serialization import load, save
@pytest.mark.unit
def test_json_round_trip(tmp_path, minimal_document: Document) -> None:
path = tmp_path / "document.json"
save(minimal_document, path)
assert load(path) == minimal_document

View File

@@ -1,72 +0,0 @@
from __future__ import annotations
import logging
from pathlib import Path
import pytest
from PySide6.QtCore import QSettings
from PySide6.QtWidgets import QApplication, QDialog
from bedit_gui.controllers.settings_controller import SettingsController
from bedit_gui.services.application_logging import get_logger
from bedit_gui.services.application_settings import ApplicationSettings
from bedit_gui.views.main_window import MainWindow
class FakeSettingsDialog:
def __init__(self, log_level: int) -> None:
self.initial_log_level = log_level
self.log_level = logging.ERROR
def exec(self) -> QDialog.DialogCode:
return QDialog.DialogCode.Accepted
@pytest.fixture(scope="module")
def qt_app() -> QApplication:
return QApplication.instance() or QApplication([])
@pytest.mark.unit
def test_log_level_is_persisted(tmp_path: Path) -> None:
path = tmp_path / "settings.ini"
backend = QSettings(str(path), QSettings.Format.IniFormat)
settings = ApplicationSettings(backend)
settings.log_level = logging.DEBUG
backend.sync()
reloaded = ApplicationSettings(
QSettings(str(path), QSettings.Format.IniFormat)
)
assert reloaded.log_level == logging.DEBUG
@pytest.mark.unit
def test_settings_action_applies_log_level(
qt_app: QApplication,
tmp_path: Path,
) -> None:
backend = QSettings(
str(tmp_path / "settings.ini"),
QSettings.Format.IniFormat,
)
settings = ApplicationSettings(backend)
window = MainWindow()
dialogs: list[FakeSettingsDialog] = []
def make_dialog(
log_level: int,
_window: MainWindow,
) -> FakeSettingsDialog:
dialog = FakeSettingsDialog(log_level)
dialogs.append(dialog)
return dialog
SettingsController(window, settings, make_dialog)
window.ui.actionSettings.trigger()
assert dialogs[0].initial_log_level == logging.INFO
assert settings.log_level == logging.ERROR
assert get_logger("tests").isEnabledFor(logging.ERROR)
assert not get_logger("tests").isEnabledFor(logging.WARNING)

View File

@@ -1,174 +0,0 @@
from __future__ import annotations
import asyncio
import re
import socket
import threading
from pathlib import Path
from typing import Mapping, Sequence
import pytest
from bedit_simulation import (
OpenModelicaRunner,
ProcessResult,
Simulation,
SimulationOptions,
SimulationStateError,
)
def _fake_omc(
command: Sequence[str],
working_directory: Path,
timeout: float | None,
environment: Mapping[str, str] | None,
) -> ProcessResult:
del timeout, environment
script = Path(command[-1]).read_text(encoding="utf-8")
if "buildModel(Example" in script:
(working_directory / "Example").touch()
if "system(" in script:
port_match = re.search(r"-port=(\d+)", script)
assert port_match is not None
with socket.create_connection(
("127.0.0.1", int(port_match.group(1)))
) as connection:
connection.sendall(
b'<status phase="integration" currentStepSize="0.1" '
b'time="1" progress="50"/>\n'
)
(working_directory / "Example_res.csv").write_text(
'"time","x"\n0,1\n1,2\n',
encoding="utf-8",
)
return ProcessResult(
tuple(command),
0,
'"Check of Example completed successfully.\n'
'Class Example has 1 equation(s) and 1 variable(s)."\n'
'""\n',
"",
)
@pytest.mark.unit
def test_runs_omc_through_configured_command(tmp_path: Path) -> None:
runner = OpenModelicaRunner(
["./run-omc-in-docker"],
executor=_fake_omc,
)
async def run() -> object:
simulation = Simulation(runner)
await simulation.load_modelica(
"model Example\nend Example;\n",
"Example",
)
await simulation.build(tmp_path)
return await simulation.run(
SimulationOptions(stop_time=2, number_of_intervals=20),
)
result = asyncio.run(run())
assert result.data == {"time": [0.0, 1.0], "x": [1.0, 2.0]}
assert result.result_file == tmp_path / "Example_res.csv"
script = (tmp_path / "run.mos").read_text(encoding="utf-8")
assert f'cd("{tmp_path}")' in script
assert "stepSize=0.1" in script
assert "-logFormat=xmltcp" in script
assert "simulate(Example" not in script
@pytest.mark.unit
def test_rejects_invalid_time_range() -> None:
with pytest.raises(ValueError, match="stop_time"):
SimulationOptions(start_time=1, stop_time=1)
@pytest.mark.unit
def test_run_requires_a_built_model() -> None:
simulation = Simulation(OpenModelicaRunner("omc", executor=_fake_omc))
async def run() -> None:
await simulation.load_modelica(
"model Example\nend Example;\n",
"Example",
)
with pytest.raises(SimulationStateError, match=r"call build\(\) first"):
await simulation.run()
asyncio.run(run())
@pytest.mark.unit
def test_checks_existing_modelica_source(tmp_path: Path) -> None:
simulation = Simulation(OpenModelicaRunner("omc", executor=_fake_omc))
async def check() -> object:
await simulation.load_modelica(
"model Example\nend Example;\n",
"Example",
)
return await simulation.check(working_directory=tmp_path)
result = asyncio.run(check())
assert result.successful
assert result.output == [
"Check of Example completed successfully.",
"Class Example has 1 equation(s) and 1 variable(s).",
"",
]
script = (tmp_path / "model-command.mos").read_text(encoding="utf-8")
assert "checkModel(Example);" in script
@pytest.mark.unit
def test_async_execution_uses_a_worker_thread(tmp_path: Path) -> None:
caller_thread = threading.get_ident()
execution_threads: list[int] = []
def executor(
command: Sequence[str],
working_directory: Path,
timeout: float | None,
environment: Mapping[str, str] | None,
) -> ProcessResult:
del working_directory, timeout, environment
execution_threads.append(threading.get_ident())
return ProcessResult(tuple(command), 0, '"OpenModelica test"', "")
simulation = Simulation(OpenModelicaRunner("omc", executor=executor))
result = asyncio.run(
simulation.execute(
"getVersion()",
working_directory=tmp_path,
)
)
assert result.return_code == 0
assert len(execution_threads) == 1
assert execution_threads[0] != caller_thread
@pytest.mark.unit
def test_requires_and_retains_a_loaded_model(tmp_path: Path) -> None:
simulation = Simulation(OpenModelicaRunner("omc", executor=_fake_omc))
async def use_session() -> None:
with pytest.raises(SimulationStateError, match="no model is loaded"):
await simulation.check()
await simulation.load_modelica(
"model Example\nend Example;\n",
"Example",
)
check = await simulation.check(working_directory=tmp_path)
assert simulation.model_name == "Example"
assert simulation.last_check is check
asyncio.run(use_session())

View File

@@ -1,29 +0,0 @@
from __future__ import annotations
import matplotlib
import pytest
from bedit_simulation import SimulationResult
from bedit_util.simulate import create_results_figure
matplotlib.use("Agg")
@pytest.mark.unit
def test_creates_result_figure_with_trace_controls() -> None:
import matplotlib.pyplot as plt
result = SimulationResult(
model_name="Example",
data={
"time": [0.0, 1.0],
"x": [1.0, 2.0],
"y": [3.0, 4.0],
},
)
figure = create_results_figure(result)
assert {line.get_label() for line in figure.axes[0].lines} == {"x", "y"}
assert len(figure._bedit_widgets) == 3 # type: ignore[attr-defined]
plt.close(figure)

View File

@@ -1,38 +0,0 @@
from __future__ import annotations
import pytest
from PySide6.QtWidgets import QApplication
from bedit_gui.controllers.view_menu_controller import ViewMenuController
from bedit_gui.views.main_window import MainWindow
@pytest.fixture(scope="module")
def qt_app() -> QApplication:
return QApplication.instance() or QApplication([])
@pytest.mark.unit
def test_view_submenus_contain_visibility_actions(
qt_app: QApplication,
) -> None:
window = MainWindow()
controller = ViewMenuController(window)
assert window.ui.actionPanels.menu() is controller.panels_menu
assert window.ui.actionToolbars.menu() is controller.toolbars_menu
assert [action.text() for action in controller.panels_menu.actions()] == [
"Document Tree",
"Log",
]
assert [action.text() for action in controller.toolbars_menu.actions()] == [
"File",
"Undo",
]
assert all(
action.isCheckable()
for action in (
controller.panels_menu.actions()
+ controller.toolbars_menu.actions()
)
)