Skip to content

Commit d147af6

Browse files
committed
Expand cross-platform test coverage
1 parent 4f685d3 commit d147af6

5 files changed

Lines changed: 165 additions & 8 deletions

File tree

.github/workflows/ci.yml

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ name: CI
22

33
on:
44
push:
5-
branches: [ "master", "main" ]
5+
branches: [ "master", "main", "codex/**" ]
66
pull_request:
77
branches: [ "master", "main" ]
8+
workflow_dispatch:
89

910
jobs:
1011
lint:
@@ -32,9 +33,11 @@ jobs:
3233
run: mypy --package wireshark_mcp --ignore-missing-imports --no-namespace-packages
3334

3435
test:
35-
runs-on: ubuntu-latest
36+
runs-on: ${{ matrix.os }}
3637
strategy:
38+
fail-fast: false
3739
matrix:
40+
os: [ubuntu-latest, windows-latest, macos-latest]
3841
python-version: ["3.10", "3.11", "3.12", "3.13"]
3942

4043
steps:
@@ -50,15 +53,34 @@ jobs:
5053
python -m pip install --upgrade pip
5154
pip install .[dev]
5255
56+
- name: Run tests with coverage
57+
run: |
58+
pytest tests/ --cov=wireshark_mcp --cov-report=term-missing -v
59+
60+
- name: Compile check
61+
run: python -m compileall src/
62+
63+
integration-linux:
64+
runs-on: ubuntu-latest
65+
steps:
66+
- uses: actions/checkout@v6
67+
68+
- name: Set up Python
69+
uses: actions/setup-python@v6
70+
with:
71+
python-version: "3.12"
72+
73+
- name: Install dependencies
74+
run: |
75+
python -m pip install --upgrade pip
76+
pip install .[dev]
77+
5378
- name: Install TShark
5479
run: |
5580
sudo apt-get update
5681
sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y tshark
5782
tshark -v
5883
59-
- name: Run tests with coverage
84+
- name: Run Linux TShark integration smoke tests
6085
run: |
61-
pytest tests/ --cov=wireshark_mcp --cov-report=term-missing -v
62-
63-
- name: Compile check
64-
run: python -m compileall src/
86+
pytest tests/test_client.py -k "real_tshark" -v

tests/test_client.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Tests for TSharkClient core functionality."""
22

33
import json
4+
import shutil
45

56
import pytest
67

@@ -106,6 +107,27 @@ def test_client_prefers_env_tool_paths(self, monkeypatch) -> None:
106107
assert client.dumpcap_path == "/opt/wireshark/dumpcap"
107108
assert client.text2pcap_path == "/opt/wireshark/text2pcap"
108109

110+
def test_describe_capabilities_reports_capture_backend_fallback(self, mock_client) -> None:
111+
capabilities = mock_client.describe_capabilities()
112+
assert capabilities["_meta"]["capture_backend"] == "dumpcap"
113+
assert capabilities["dumpcap"]["requirement"] == "optional"
114+
115+
mock_client.dumpcap_path = None
116+
mock_client._tool_paths["dumpcap"] = None
117+
118+
fallback_capabilities = mock_client.describe_capabilities()
119+
assert fallback_capabilities["_meta"]["capture_backend"] == "tshark"
120+
121+
@pytest.mark.asyncio
122+
async def test_check_capabilities_detects_real_tshark_when_installed(self) -> None:
123+
if shutil.which("tshark") is None:
124+
pytest.skip("tshark not installed on this host")
125+
126+
result = await TSharkClient().check_capabilities()
127+
128+
assert result["success"]
129+
assert result["data"]["tshark"]["available"] is True
130+
109131

110132
class TestRunCommand:
111133
"""Tests for _run_command error handling."""
@@ -140,6 +162,21 @@ async def test_binary_whitelist_allows_windows_exe_names_case_insensitive(self,
140162

141163

142164
class TestSuiteBehavior:
165+
@pytest.mark.asyncio
166+
async def test_list_interfaces_prefers_dumpcap_when_available(self, mock_client) -> None:
167+
result = await mock_client.list_interfaces()
168+
assert "dumpcap" in result
169+
assert mock_client._last_cmd[0] == "dumpcap"
170+
171+
@pytest.mark.asyncio
172+
async def test_list_interfaces_falls_back_to_tshark(self, mock_client) -> None:
173+
mock_client.dumpcap_path = None
174+
mock_client._tool_paths["dumpcap"] = None
175+
176+
result = await mock_client.list_interfaces()
177+
assert "tshark" in result
178+
assert mock_client._last_cmd[0] == "tshark"
179+
143180
@pytest.mark.asyncio
144181
async def test_capture_prefers_dumpcap_when_available(self, mock_client) -> None:
145182
result = await mock_client.capture_packets("en0", "/tmp/out.pcapng", duration=10)

tests/test_files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,6 @@ async def test_text2pcap_import_command(self, mock_client: MockTSharkClient) ->
7474
ascii_mode=True,
7575
)
7676
assert "text2pcap" in result
77-
assert '-t %H:%M:%S.%f' in result
77+
assert "-t %H:%M:%S.%f" in result
7878
assert "-a" in result
7979
assert "-E ether" in result

tests/test_installer.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
_get_linux_config_home,
1212
_get_mcp_servers_dict,
1313
_get_python_executable,
14+
_iter_wireshark_search_dirs,
15+
_join_path,
1416
_read_json_config,
1517
_render_codex_toml_block,
1618
_write_json_config,
@@ -276,10 +278,35 @@ def test_uninstall_removes_codex_toml_block(self, tmp_path):
276278

277279

278280
class TestPlatformConfigs:
281+
def test_join_path_uses_target_platform_separators(self):
282+
assert _join_path("/Users/tester", "Library", "Claude") == "/Users/tester/Library/Claude"
283+
assert (
284+
_join_path(r"C:\Users\tester", "AppData", "Roaming", platform="win32") == r"C:\Users\tester\AppData\Roaming"
285+
)
286+
279287
def test_linux_config_home_uses_xdg(self, monkeypatch):
280288
monkeypatch.setenv("XDG_CONFIG_HOME", "/tmp/xdg-config")
281289
assert _get_linux_config_home("/home/tester") == "/tmp/xdg-config"
282290

291+
def test_mac_client_configs_use_application_support(self, monkeypatch):
292+
monkeypatch.setattr("wireshark_mcp.installer.sys.platform", "darwin")
293+
monkeypatch.setattr("wireshark_mcp.installer.os.path.expanduser", lambda _: "/Users/tester")
294+
295+
configs = _get_client_configs()
296+
297+
assert configs["Claude"] == (
298+
"/Users/tester/Library/Application Support/Claude",
299+
"claude_desktop_config.json",
300+
)
301+
assert configs["Zed"] == (
302+
"/Users/tester/Library/Application Support/Zed",
303+
"settings.json",
304+
)
305+
assert configs["VS Code"] == (
306+
"/Users/tester/Library/Application Support/Code/User",
307+
"settings.json",
308+
)
309+
283310
def test_linux_client_configs_use_xdg(self, monkeypatch):
284311
monkeypatch.setattr("wireshark_mcp.installer.sys.platform", "linux")
285312
monkeypatch.setattr("wireshark_mcp.installer.os.path.expanduser", lambda _: "/home/tester")
@@ -306,3 +333,27 @@ def test_windows_client_configs_include_supported_paths(self, monkeypatch):
306333
r"C:\Users\tester\AppData\Roaming\Code\User",
307334
"settings.json",
308335
)
336+
337+
def test_iter_wireshark_search_dirs_for_macos(self, monkeypatch):
338+
monkeypatch.setattr("wireshark_mcp.installer.sys.platform", "darwin")
339+
monkeypatch.setattr("wireshark_mcp.installer.os.path.expanduser", lambda _: "/Users/tester")
340+
341+
search_dirs = _iter_wireshark_search_dirs()
342+
343+
assert "/Applications/Wireshark.app/Contents/MacOS" in search_dirs
344+
assert "/Applications/Wireshark.app/Contents/Helpers" in search_dirs
345+
assert "/Users/tester/Applications/Wireshark.app/Contents/MacOS" in search_dirs
346+
assert "/opt/homebrew/bin" in search_dirs
347+
348+
def test_iter_wireshark_search_dirs_for_windows(self, monkeypatch):
349+
monkeypatch.setattr("wireshark_mcp.installer.sys.platform", "win32")
350+
monkeypatch.setattr("wireshark_mcp.installer.os.path.expanduser", lambda _: r"C:\Users\tester")
351+
monkeypatch.setenv("LOCALAPPDATA", r"C:\Users\tester\AppData\Local")
352+
monkeypatch.setenv("PROGRAMFILES", r"C:\Program Files")
353+
monkeypatch.setenv("PROGRAMFILES(X86)", r"C:\Program Files (x86)")
354+
355+
search_dirs = _iter_wireshark_search_dirs()
356+
357+
assert r"C:\Program Files\Wireshark" in search_dirs
358+
assert r"C:\Program Files (x86)\Wireshark" in search_dirs
359+
assert r"C:\Users\tester\AppData\Local\Programs\Wireshark" in search_dirs

tests/test_server.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Tests for server bootstrap behavior."""
2+
3+
import wireshark_mcp.server as server
4+
5+
6+
class TestWindowsEventLoop:
7+
def test_configure_windows_event_loop_is_noop_off_windows(self, monkeypatch):
8+
applied: list[object] = []
9+
10+
monkeypatch.setattr(server.sys, "platform", "darwin")
11+
monkeypatch.setattr(server.asyncio, "set_event_loop_policy", lambda policy: applied.append(policy))
12+
13+
server._configure_windows_event_loop()
14+
15+
assert applied == []
16+
17+
def test_configure_windows_event_loop_sets_proactor_policy(self, monkeypatch):
18+
class FakePolicy:
19+
pass
20+
21+
applied: list[object] = []
22+
23+
monkeypatch.setattr(server.sys, "platform", "win32")
24+
monkeypatch.setattr(server.asyncio, "WindowsProactorEventLoopPolicy", FakePolicy, raising=False)
25+
monkeypatch.setattr(server.asyncio, "get_event_loop_policy", lambda: object())
26+
monkeypatch.setattr(server.asyncio, "set_event_loop_policy", lambda policy: applied.append(policy))
27+
28+
server._configure_windows_event_loop()
29+
30+
assert len(applied) == 1
31+
assert isinstance(applied[0], FakePolicy)
32+
33+
def test_configure_windows_event_loop_keeps_existing_proactor_policy(self, monkeypatch):
34+
class FakePolicy:
35+
pass
36+
37+
current_policy = FakePolicy()
38+
applied: list[object] = []
39+
40+
monkeypatch.setattr(server.sys, "platform", "win32")
41+
monkeypatch.setattr(server.asyncio, "WindowsProactorEventLoopPolicy", FakePolicy, raising=False)
42+
monkeypatch.setattr(server.asyncio, "get_event_loop_policy", lambda: current_policy)
43+
monkeypatch.setattr(server.asyncio, "set_event_loop_policy", lambda policy: applied.append(policy))
44+
45+
server._configure_windows_event_loop()
46+
47+
assert applied == []

0 commit comments

Comments
 (0)