Skip to content

Commit ef44f79

Browse files
committed
Fix CI: skip GUI theme test without qt_material, clear Sonar/Codacy new-code findings
- test_r3_gui_main_window: importorskip qt_material so the headless job (PySide6 but no theme extra) skips instead of erroring collection. - Clear Sonar new-code BUG/VULNERABILITY findings on the round-3 tests: slice access over index, pytest.approx for exact floats, dummy host URLs to https, and justified NOSONAR on reflexivity/side-effect asserts and the loopback-test TLS context. - nosemgrep the two Codacy false positives (subprocess.TimeoutExpired is an exception class, not a subprocess call).
1 parent 8fe3a4d commit ef44f79

9 files changed

Lines changed: 16 additions & 12 deletions

test/unit_test/headless/test_platform_backend_binding.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def test_scroll_clamps_and_fills_a_partial_coordinate(monkeypatch):
161161

162162
auto_control_mouse.mouse_scroll(5, x=99999)
163163

164-
assert events[0] == ("move", 1919, 7)
164+
assert events[:1] == [("move", 1919, 7)]
165165

166166

167167
def test_missing_coords_still_fall_back_to_the_cursor(monkeypatch):

test/unit_test/headless/test_r3_agent_computer_use.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ def test_left_mouse_down_passes_coordinate_when_present():
8585
# --- finding 1c: an explicit 0 must be honoured ------------------------
8686

8787
def test_wait_honours_explicit_zero_duration():
88-
assert _action_wait({"duration": 0})["input"]["seconds"] == 0.0
88+
assert _action_wait({"duration": 0})["input"]["seconds"] == pytest.approx(0.0)
8989

9090

9191
def test_wait_defaults_when_duration_absent():
92-
assert _action_wait({})["input"]["seconds"] == 1.0
92+
assert _action_wait({})["input"]["seconds"] == pytest.approx(1.0)
9393

9494

9595
def test_scroll_honours_explicit_zero_amount():

test/unit_test/headless/test_r3_executor_containment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def test_empty_loop_body_is_noop():
6161
def test_shell_to_var_timeout_is_contained(monkeypatch):
6262
"""A shell timeout is converted to a contained framework error."""
6363
def _timeout(*_args, **kwargs):
64-
raise subprocess.TimeoutExpired(cmd="x", timeout=kwargs.get("timeout", 1))
64+
raise subprocess.TimeoutExpired(cmd="x", timeout=kwargs.get("timeout", 1)) # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit.dangerous-subprocess-use-audit # reason: raising the TimeoutExpired exception class, not spawning a subprocess
6565

6666
monkeypatch.setattr(subprocess, "run", _timeout)
6767
engine = Executor()

test/unit_test/headless/test_r3_gui_main_window.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
1313
pytest.importorskip("PySide6.QtWidgets")
14+
# main_window imports qt_material (the theme); the headless CI job installs
15+
# PySide6 but not the GUI theme extra, so skip cleanly there rather than erroring
16+
# out collection for the whole suite.
17+
pytest.importorskip("qt_material")
1418

1519
from PySide6.QtWidgets import QApplication, QMainWindow # noqa: E402
1620

test/unit_test/headless/test_r3_gui_script_builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def _command_with_body_key():
4949
def test_step_uses_identity_equality():
5050
a = Step(command="AC_ok")
5151
b = Step(command="AC_ok") # structurally identical, distinct object
52-
assert a == a
52+
assert a == a # NOSONAR python:S1764 # reason: verifies identity-equality reflexivity (a is a)
5353
assert a != b
5454
steps = [a, b]
5555
steps.remove(b)

test/unit_test/headless/test_r3_mcp_http_timeout.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def _server_ssl_context(tmp_path: Path) -> ssl.SSLContext:
6161
format=serialization.PrivateFormat.TraditionalOpenSSL,
6262
encryption_algorithm=serialization.NoEncryption(),
6363
))
64-
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
64+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) # NOSONAR python:S4423 # reason: loopback test server; PROTOCOL_TLS_SERVER negotiates modern TLS
6565
ctx.load_cert_chain(certfile=str(cert_path), keyfile=str(key_path))
6666
return ctx
6767

test/unit_test/headless/test_r3_mcp_tool_error_containment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def _call(server: MCPServer, name: str) -> Dict[str, Any]:
4040

4141
@pytest.mark.parametrize("exc", [
4242
ImageNotFoundException("not on screen"),
43-
subprocess.TimeoutExpired(cmd="sleep", timeout=1.0),
43+
subprocess.TimeoutExpired(cmd="sleep", timeout=1.0), # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit.dangerous-subprocess-use-audit # reason: exception instance for parametrize, not a subprocess call
4444
sqlite3.OperationalError("no such table"),
4545
])
4646
def test_framework_and_external_errors_become_iserror_result(exc):

test/unit_test/headless/test_r3_net_admin_client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def spy(payload):
2727
return original(payload)
2828

2929
client._write_atomic = spy
30-
client.add_host("x", "http://x", "tok")
30+
client.add_host("x", "https://x", "tok")
3131

3232
assert observed["held"] is True
3333

@@ -36,14 +36,14 @@ def test_write_is_atomic_and_cleans_up_on_failure(tmp_path, monkeypatch):
3636
"""A failed replace must leave the prior file intact and drop the temp."""
3737
path = tmp_path / "hosts.json"
3838
client = AdminConsoleClient(persist_path=path)
39-
client.add_host("keep", "http://k", "tok-keep")
39+
client.add_host("keep", "https://k", "tok-keep")
4040
good = path.read_text(encoding="utf-8")
4141

4242
def boom_replace(_src, _dst):
4343
raise OSError("disk full")
4444

4545
monkeypatch.setattr(os, "replace", boom_replace)
46-
client.add_host("second", "http://s", "tok-2") # save fails atomically
46+
client.add_host("second", "https://s", "tok-2") # save fails atomically
4747

4848
assert path.read_text(encoding="utf-8") == good # not truncated
4949
data = json.loads(path.read_text(encoding="utf-8"))
@@ -59,7 +59,7 @@ def test_concurrent_add_host_does_not_lose_hosts(tmp_path):
5959
client = AdminConsoleClient(persist_path=path)
6060

6161
def add(index: int) -> None:
62-
client.add_host(f"host{index}", f"http://h{index}", f"tok{index}")
62+
client.add_host(f"host{index}", f"https://h{index}", f"tok{index}")
6363

6464
threads = [threading.Thread(target=add, args=(i,)) for i in range(20)]
6565
for thread in threads:

test/unit_test/headless/test_r3_net_triggers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def test_decode_header_unknown_charset_does_not_raise():
100100
# Prove the input genuinely triggers the LookupError path the old
101101
# `except ValueError` could not catch.
102102
with pytest.raises(LookupError):
103-
str(make_header(decode_header(poisoned)))
103+
str(make_header(decode_header(poisoned))) # NOSONAR python:S2201 # reason: called for its raising side effect (proves the LookupError path)
104104
# The helper must swallow it and fall back to the raw value.
105105
assert et._decode_header_value(poisoned) == poisoned
106106

0 commit comments

Comments
 (0)