# -*- coding: utf-8 -*- """graders.tree_hash crashes if an agent leaves a file named `nul` in the build. `nul` is a reserved DOS device name. A shell command like `... >nul 2>&1` run under a POSIX-flavoured shell (Git Bash / MSYS, which is what `run_command` reaches on this machine) creates a REAL FILE called nul instead of discarding output. os.walk then hands that path to os.path.relpath, which reports it on mount \\\\.\\nul and raises ValueError. Effect: the whole versus run dies mid-session with an unhandled exception, after the API spend for that turn has already happened. No API calls. Creates the file via the \\\\?\\ prefix, which bypasses the reserved-name check the same way MSYS does. """ import os import shutil import sys import tempfile import traceback sys.path.insert(0, r'C:\xampp\htdocs\axium\python') from versus import graders as G # noqa: E402 build = tempfile.mkdtemp(prefix='nulprobe-') os.makedirs(os.path.join(build, 'shop'), exist_ok=True) with open(os.path.join(build, 'shop', 'pricing.py'), 'w') as f: f.write('X = 1\n') print('build:', build) print() print('1. tree_hash on a clean build') try: h = G.tree_hash(build) print(' OK %d files: %s' % (len(h), sorted(h))) except Exception as e: # noqa: BLE001 print(' FAILED unexpectedly: %r' % e) # create the reserved name the way a redirect under MSYS does nul = '\\\\?\\' + os.path.join(build, 'nul') made = False try: with open(nul, 'wb') as f: f.write(b'') made = True print('\n2. created a file named `nul` in the build') except OSError as e: print('\n2. could not create `nul` via \\\\?\\ (%s)' % e) if made: print('\n3. tree_hash on the same build, one extra file') try: h = G.tree_hash(build) print(' OK %d files -> no crash: %s' % (len(h), sorted(h))) except Exception: # noqa: BLE001 line = traceback.format_exc().strip().splitlines()[-1] print(' CRASH %s' % line) print(' -> versus.runner dies mid-session; the turn is paid for and lost.') print('\n4. the one-line fix, demonstrated') orig = os.path.relpath def safe_walk(root): out = {} for dirpath, dirs, files in os.walk(root): dirs[:] = [d for d in dirs if d not in G.IGNORE_DIRS] for fn in files: if fn.endswith(G.IGNORE_SUFFIX): continue p = os.path.join(dirpath, fn) try: rel = orig(p, root).replace('\\', '/') except ValueError: # reserved device name (nul, con, aux, prn...) — not a project file continue out[rel] = 'hashed' return out print(' guarded version returns %s' % sorted(safe_walk(build))) try: if made: os.remove(nul) shutil.rmtree(build, ignore_errors=True) except OSError: pass