fix: write output files to user's CWD, not install directory

The bash shim uses `uv run --directory <install_dir>` which changes the
process CWD to the install directory. _ensure_hashfile_in_cwd() and the
Hashview download path used os.getcwd() to determine the target directory
for output files (.out, .nt, etc.), causing them to land in the install
directory instead of where the user ran the command.

Add orig_cwd() helper that reads HATE_CRACK_ORIG_CWD (set by the shim)
and use it in _ensure_hashfile_in_cwd(), the Hashview download path, and
the potfile fallback path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Justin Bollinger
2026-04-08 11:43:51 -04:00
co-authored by Claude Opus 4.6
parent d0bfb015a6
commit fa07d37a84
6 changed files with 76 additions and 42 deletions
+14 -25
View File
@@ -774,14 +774,8 @@ class TestHashviewAPI:
content = f.read()
assert content == b"hash1\nhash2\n"
def test_hashfile_orig_path_preservation(self, tmp_path):
def test_hashfile_orig_path_preservation(self, tmp_path, monkeypatch):
"""Test that original hashfile path is preserved before _ensure_hashfile_in_cwd"""
import sys
import os
sys.path.insert(
0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
)
from hate_crack.main import _ensure_hashfile_in_cwd
# Create a test hashfile in a different directory
@@ -792,27 +786,22 @@ class TestHashviewAPI:
original_path = str(test_file)
# Save current directory
orig_cwd = os.getcwd()
try:
# Change to tmp_path
os.chdir(str(tmp_path))
# Set HATE_CRACK_ORIG_CWD so _ensure_hashfile_in_cwd targets tmp_path
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(tmp_path))
# Call _ensure_hashfile_in_cwd
result_path = _ensure_hashfile_in_cwd(original_path)
# Call _ensure_hashfile_in_cwd
result_path = _ensure_hashfile_in_cwd(original_path)
# The result should be different from original (in cwd now)
# But original_path should still exist and be unchanged
assert os.path.exists(original_path), "Original file should still exist"
assert os.path.exists(result_path), "Result file should exist"
# The result should be different from original (in cwd now)
# But original_path should still exist and be unchanged
assert os.path.exists(original_path), "Original file should still exist"
assert os.path.exists(result_path), "Result file should exist"
# If they're different, result should be in cwd
if result_path != original_path:
assert os.path.dirname(result_path) == str(tmp_path), (
"Result should be in cwd"
)
finally:
os.chdir(orig_cwd)
# If they're different, result should be in cwd
if result_path != original_path:
assert os.path.dirname(result_path) == str(tmp_path), (
"Result should be in cwd"
)
if __name__ == "__main__":
+30 -9
View File
@@ -93,14 +93,14 @@ class TestEnsureHashfileInCwd:
result = main_module._ensure_hashfile_in_cwd("relative/path.txt")
assert result == "relative/path.txt"
def test_already_in_cwd(self, main_module, tmp_path):
def test_already_in_cwd(self, main_module, tmp_path, monkeypatch):
target = tmp_path / "hashfile.txt"
target.write_text("hashes")
with patch("os.getcwd", return_value=str(tmp_path)):
result = main_module._ensure_hashfile_in_cwd(str(target))
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(tmp_path))
result = main_module._ensure_hashfile_in_cwd(str(target))
assert result == str(target)
def test_different_dir_existing_file_in_cwd(self, main_module, tmp_path):
def test_different_dir_existing_file_in_cwd(self, main_module, tmp_path, monkeypatch):
# File exists in a different directory
other_dir = tmp_path / "other"
other_dir.mkdir()
@@ -113,11 +113,11 @@ class TestEnsureHashfileInCwd:
cwd_copy = cwd_dir / "hashes.txt"
cwd_copy.write_text("cwd version")
with patch("os.getcwd", return_value=str(cwd_dir)):
result = main_module._ensure_hashfile_in_cwd(str(source_file))
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(cwd_dir))
result = main_module._ensure_hashfile_in_cwd(str(source_file))
assert result == str(cwd_copy)
def test_different_dir_creates_symlink(self, main_module, tmp_path):
def test_different_dir_creates_symlink(self, main_module, tmp_path, monkeypatch):
# Source file in a different directory, nothing in cwd
other_dir = tmp_path / "other"
other_dir.mkdir()
@@ -127,13 +127,34 @@ class TestEnsureHashfileInCwd:
cwd_dir = tmp_path / "cwd"
cwd_dir.mkdir()
with patch("os.getcwd", return_value=str(cwd_dir)):
result = main_module._ensure_hashfile_in_cwd(str(source_file))
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(cwd_dir))
result = main_module._ensure_hashfile_in_cwd(str(source_file))
expected = str(cwd_dir / "hashes.txt")
assert result == expected
assert os.path.exists(expected)
def test_uses_orig_cwd_not_process_cwd(self, main_module, tmp_path, monkeypatch):
"""Output files go to HATE_CRACK_ORIG_CWD, not the process CWD."""
# Simulate: process CWD is the install dir, orig CWD is the user's dir
install_dir = tmp_path / "install"
install_dir.mkdir()
user_dir = tmp_path / "user"
user_dir.mkdir()
other_dir = tmp_path / "other"
other_dir.mkdir()
source_file = other_dir / "hashes.txt"
source_file.write_text("hashes")
monkeypatch.chdir(install_dir)
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(user_dir))
result = main_module._ensure_hashfile_in_cwd(str(source_file))
# Should land in user_dir, NOT install_dir
assert result == str(user_dir / "hashes.txt")
assert os.path.exists(user_dir / "hashes.txt")
assert not os.path.exists(install_dir / "hashes.txt")
class TestRunHashcatShow:
def _make_mock_result(self, stdout_bytes):
+12
View File
@@ -8,6 +8,18 @@ from hate_crack import cli
from hate_crack import formatting
def test_orig_cwd_uses_env_var(monkeypatch, tmp_path):
"""orig_cwd() returns HATE_CRACK_ORIG_CWD when set."""
monkeypatch.setenv("HATE_CRACK_ORIG_CWD", str(tmp_path))
assert cli.orig_cwd() == str(tmp_path)
def test_orig_cwd_falls_back_to_getcwd(monkeypatch):
"""orig_cwd() falls back to os.getcwd() when env var is unset."""
monkeypatch.delenv("HATE_CRACK_ORIG_CWD", raising=False)
assert cli.orig_cwd() == os.getcwd()
def test_resolve_path_none_and_expand():
assert cli.resolve_path("") is None
resolved = cli.resolve_path("~")