b9bfec961c
Some titles never show up in a Steam/Lutris/Heroic scan — standalone mod
launchers like SPT (Single-Player Tarkov), itch.io downloads, hand-installed
executables. Add a user-authored custom-games list (core/customgames.py) shown
alongside the other sources in `rigdoctor games` and the GUI.
Each entry can carry a launch command and a log directory:
- `rigdoctor games add "SPT" --command .../tarkov.sh` (logs/ auto-detected)
- `rigdoctor games play "SPT"` launches it under the crash-capture wrapper
(wrap.run gains an explicit game-name override, since there's no SteamAppId)
- the diagnostic now feeds the game's own logs to the analysis: gamelogs
.collect(game=...) tails the registered log dir (SPT's server/launcher logs)
alongside the kernel log, freshness-scoped by mtime.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
108 lines
4.5 KiB
Python
108 lines
4.5 KiB
Python
"""Tests for M14 game/Proton/Steam log collection."""
|
|
|
|
import os
|
|
import tempfile
|
|
import time
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
from rigdoctor.core import gamelogs
|
|
|
|
|
|
class TailTests(unittest.TestCase):
|
|
def test_tail_returns_last_bytes(self):
|
|
path = Path(tempfile.mkdtemp()) / "x.log"
|
|
path.write_text("A" * 100 + "TAIL")
|
|
out = gamelogs._tail(path, 4)
|
|
self.assertEqual(out, "TAIL")
|
|
|
|
def test_tail_short_file(self):
|
|
path = Path(tempfile.mkdtemp()) / "x.log"
|
|
path.write_text("short")
|
|
self.assertEqual(gamelogs._tail(path, 9999), "short")
|
|
|
|
def test_tail_missing(self):
|
|
self.assertEqual(gamelogs._tail(Path("/nope/x.log"), 10), "")
|
|
|
|
|
|
class CollectTests(unittest.TestCase):
|
|
def test_collect_includes_proton_and_steam(self):
|
|
tmp = Path(tempfile.mkdtemp())
|
|
proton = tmp / "steam-570.log"
|
|
proton.write_text("err: vkd3d device lost")
|
|
console = tmp / "console-linux.txt"
|
|
console.write_text("Game removed AppID 570 ... exit")
|
|
with mock.patch.object(gamelogs, "_proton_logs", return_value=[proton]), \
|
|
mock.patch.object(gamelogs, "_steam_console", return_value=console):
|
|
out = gamelogs.collect()
|
|
self.assertIn("Proton log", out)
|
|
self.assertIn("vkd3d", out)
|
|
self.assertIn("Steam log", out)
|
|
self.assertIn("exit", out)
|
|
|
|
def test_collect_empty_when_none(self):
|
|
with mock.patch.object(gamelogs, "_proton_logs", return_value=[]), \
|
|
mock.patch.object(gamelogs, "_steam_console", return_value=None):
|
|
self.assertEqual(gamelogs.collect(), "")
|
|
|
|
|
|
class CustomGameLogTests(unittest.TestCase):
|
|
def test_collect_includes_custom_game_logs(self):
|
|
tmp = Path(tempfile.mkdtemp())
|
|
(tmp / "tarkov-latest.log").write_text(">>> Tarkov gone. clean exit")
|
|
(tmp / "server-latest.log").write_text("SPT server error: mod failed to load")
|
|
with mock.patch.object(gamelogs, "_proton_logs", return_value=[]), \
|
|
mock.patch.object(gamelogs, "_steam_console", return_value=None), \
|
|
mock.patch("rigdoctor.core.customgames.log_dir", return_value=str(tmp)):
|
|
out = gamelogs.collect(game="SPT")
|
|
self.assertIn("SPT log", out)
|
|
self.assertIn("server-latest.log", out)
|
|
self.assertIn("mod failed to load", out)
|
|
|
|
def test_custom_logs_skipped_when_stale(self):
|
|
tmp = Path(tempfile.mkdtemp())
|
|
old = tmp / "tarkov-latest.log"
|
|
old.write_text("an earlier session")
|
|
old_mtime = time.time() - 3600
|
|
os.utime(old, (old_mtime, old_mtime))
|
|
with mock.patch.object(gamelogs, "_proton_logs", return_value=[]), \
|
|
mock.patch.object(gamelogs, "_steam_console", return_value=None), \
|
|
mock.patch("rigdoctor.core.customgames.log_dir", return_value=str(tmp)):
|
|
self.assertEqual(gamelogs.collect(since=time.time() - 60, game="SPT"), "")
|
|
|
|
def test_no_game_means_no_custom_logs(self):
|
|
with mock.patch.object(gamelogs, "_proton_logs", return_value=[]), \
|
|
mock.patch.object(gamelogs, "_steam_console", return_value=None):
|
|
self.assertEqual(gamelogs.collect(), "") # game=None → custom lookup skipped
|
|
|
|
|
|
class SinceScopingTests(unittest.TestCase):
|
|
def test_since_filter_keeps_window_only(self):
|
|
text = (
|
|
"[2026-05-22 13:00:00] old session line\n"
|
|
"[2026-05-22 13:00:01] another old line\n"
|
|
"[2026-05-22 14:30:00] new session launch\n"
|
|
"[2026-05-22 14:30:05] new session error\n"
|
|
)
|
|
since = time.mktime(time.strptime("2026-05-22 14:00:00", "%Y-%m-%d %H:%M:%S"))
|
|
out = gamelogs._since_filter(text, since)
|
|
self.assertIn("new session launch", out)
|
|
self.assertIn("new session error", out)
|
|
self.assertNotIn("old session", out)
|
|
|
|
def test_collect_skips_stale_proton_log(self):
|
|
tmp = Path(tempfile.mkdtemp())
|
|
proton = tmp / "steam-9999.log"
|
|
proton.write_text("stale proton output from an earlier game")
|
|
old_mtime = time.time() - 3600
|
|
os.utime(proton, (old_mtime, old_mtime))
|
|
since = time.time() - 60 # session started a minute ago
|
|
with mock.patch.object(gamelogs, "_proton_logs", return_value=[proton]), \
|
|
mock.patch.object(gamelogs, "_steam_console", return_value=None):
|
|
self.assertEqual(gamelogs.collect(since=since), "") # stale log excluded
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|