Skip to content

test: Windows pytest tempdir hardening#56

Closed
Dj-Shortcut wants to merge 14 commits intoAlanRockefeller:mainfrom
Dj-Shortcut:fix/windows-pytest-tempdir
Closed

test: Windows pytest tempdir hardening#56
Dj-Shortcut wants to merge 14 commits intoAlanRockefeller:mainfrom
Dj-Shortcut:fix/windows-pytest-tempdir

Conversation

@Dj-Shortcut
Copy link
Copy Markdown

@Dj-Shortcut Dj-Shortcut commented Mar 20, 2026

Fixes pytest failures on Windows caused by inaccessible legacy temp directories
(e.g. var/pytest-temp, .pytest-local, .pytest-tmp), which can trigger
PermissionError (WinError 5) during cleanup.

This change:

  • forces pytest to use a clean basetemp (tmp/pytest-clean)
  • ignores legacy temp/cache directories via .gitignore and norecursedirs

No changes to application code or test logic.

Split out from the DLL/JPEG PR to keep that review focused.

Summary by CodeRabbit

  • New Features

    • Windows: automatic use of native TurboJPEG for faster JPEG decoding and thumbnails, with transparent Pillow fallback and optional env var override.
  • Documentation

    • Added Windows setup, recommended install path, env var usage example, and troubleshooting guidance for native JPEG loading.
  • Improvements

    • More robust app-data directory selection and graceful fallback to console-only logging if file logging can't be created.
  • Tests

    • Added tests for library discovery and logging fallback.
  • Chores

    • Expanded .gitignore and added pytest.ini for test temp file handling.

related to : #53

Dj-Shortcut and others added 6 commits March 17, 2026 17:40
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Centralized TurboJPEG discovery/initialization into a new helper; updated JPEG and prefetcher modules to use it; made logging setup resilient to unwritable app-data paths; added pytest config and gitignore entries; expanded README with Windows TurboJPEG instructions and troubleshooting.

Changes

Cohort / File(s) Summary
Ignore & Test Config
\.gitignore, pytest.ini
Add pytest/runtime temp dirs and patterns to .gitignore; add pytest.ini with --basetemp=pytest-clean and norecursedirs to exclude pytest/temp/cache directories.
Documentation
README.md
Add Windows-specific notes for PyTurboJPEG (turbojpeg.dll), automatic use/fallback to Pillow, recommended DLL paths, env overrides (FASTSTACK_TURBOJPEG_LIB, TURBOJPEG_LIB), CLI example, and troubleshooting guidance.
TurboJPEG Initialization
faststack/imaging/turbo.py, faststack/imaging/jpeg.py, faststack/thumbnail_view/prefetcher.py
New faststack.imaging.turbo.create_turbojpeg() enumerates deduplicated candidate DLL paths (env overrides, common roots, LOCALAPPDATA, PATH, None) and attempts initialization, returning (decoder, available); jpeg.py and prefetcher.py now call this helper and use its availability flag.
Logging Setup
faststack/logging_setup.py
Add _is_writable_directory() and _can_create_directory(); get_app_data_dir() selects first writable/creatable candidate (with FASTSTACK_APPDATA override and fallbacks); file handler creation wrapped in try/except OSError so file logging is disabled safely if it fails.
Tests
faststack/tests/test_turbo.py
New tests for create_turbojpeg() env-override and fallback behavior, failure logging, app-data directory selection/fallbacks, writability checks, and setup_logging() behavior when file handler creation raises OSError.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Caller as Caller (jpeg / prefetcher)
participant TurboHelper as faststack.imaging.turbo
participant Env as OS Env / PATH
participant TurboLib as TurboJPEG Binding
participant Pillow as Pillow Decoder
participant Logger as Logger

Caller->>TurboHelper: create_turbojpeg()
TurboHelper->>Env: enumerate candidate paths (FASTSTACK_TURBOJPEG_LIB / FASTSTACK_TURBOJPEG_ROOT / common roots / PATH / None)
loop try candidates
    TurboHelper->>TurboLib: TurboJPEG(candidate) or TurboJPEG()
    alt success
        TurboLib-->>TurboHelper: decoder instance
        TurboHelper-->>Logger: log success source
        TurboHelper-->>Caller: (decoder, True)
        break
    else failure
        TurboLib-->>TurboHelper: raise/exception
        TurboHelper-->>Logger: record attempt failure
    end
end
alt all attempts failed
    TurboHelper-->>Logger: warn attempts, "Falling back to Pillow"
    TurboHelper-->>Caller: (None, False)
    Caller->>Pillow: use Pillow decode path
end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'test: Windows pytest tempdir hardening' accurately describes the main change: addressing Windows pytest temporary directory issues through hardening measures in .gitignore and pytest.ini configuration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6dd99dc25

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pytest.ini Outdated
Comment thread faststack/imaging/turbo.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@faststack/logging_setup.py`:
- Around line 22-27: The function _can_create_directory currently treats a
candidate path as creatable if its nearest existing parent is writable, which
incorrectly allows candidate paths that already exist as regular files; update
_can_create_directory to first reject any path that exists and is not a
directory (i.e., if path.exists() and not path.is_dir(): return False), then
proceed with the existing parent-traversal and writable check; this prevents
get_app_data_dir and setup_logging from selecting a file-backed candidate and
avoids later mkdir() failures.

In `@faststack/tests/test_turbo.py`:
- Around line 108-126: Ensure the test exercises the RotatingFileHandler error
path and fully restores logger state: monkeypatch get_app_data_dir as you
already do but also monkeypatch Path.mkdir (or logging_setup.Path.mkdir) to a
no-op so directory creation doesn't raise, while mocking
logging_setup.logging.handlers.RotatingFileHandler to raise OSError("disk
full"); call logging_setup.setup_logging(debug=True) and then assert a
StreamHandler remains; finally restore both root_logger.handlers and
root_logger.level to their original values so later tests are unaffected.

In `@README.md`:
- Around line 91-99: Update the README troubleshooting snippet to match the new
log text emitted by faststack/imaging/turbo.py: replace the two-line old message
("Unable to locate turbojpeg library automatically." / "You may specify the
turbojpeg library path manually.") with the current log "TurboJPEG
initialization failed for all attempted locations. Falling back to Pillow." so
users will see the exact message produced at startup.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3bd0bb55-98c8-416f-9310-093bd6dd10ab

📥 Commits

Reviewing files that changed from the base of the PR and between c65442a and a6dd99d.

📒 Files selected for processing (8)
  • .gitignore
  • README.md
  • faststack/imaging/jpeg.py
  • faststack/imaging/turbo.py
  • faststack/logging_setup.py
  • faststack/tests/test_turbo.py
  • faststack/thumbnail_view/prefetcher.py
  • pytest.ini

Comment thread faststack/logging_setup.py
Comment thread faststack/tests/test_turbo.py Outdated
Comment thread README.md Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
faststack/imaging/turbo.py (2)

82-87: Consider narrowing exception scope (Ruff BLE001).

Catching bare Exception is flagged by static analysis. In this fallback pattern, broad catching is somewhat justified since native library loading can raise varied exceptions (OSError, RuntimeError, library-specific errors). However, if you want to address the linter warning, you could catch a tuple of expected types:

♻️ Proposed refinement
         try:
             decoder = TurboJPEG() if candidate is None else TurboJPEG(candidate)
-        except Exception as exc:
+        except (OSError, RuntimeError, TypeError, ValueError) as exc:
             source = "default loader" if candidate is None else candidate
             failures.append(f"{source}: {exc}")
             continue

Alternatively, keep Exception if you've found the library can raise unpredictable errors—the current approach ensures graceful fallback and logs failures appropriately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/imaging/turbo.py` around lines 82 - 87, The try/except around
TurboJPEG() currently catches a bare Exception; replace it with a narrower
exception tuple to satisfy the linter and still handle expected loader failures
— catch OSError and RuntimeError (and any library-specific error class if known)
in the except clause that logs to failures and uses the same source/candidate
logic; update the except header referencing TurboJPEG, candidate and failures so
only those expected errors are caught while preserving the existing
failures.append(...) and continue behavior.

12-16: Consider clarifying TJPF_RGB export or removing if unused.

TJPF_RGB is imported but not used within this module. If it's intended for re-export (so callers can import from this module instead of turbojpeg directly), consider adding an __all__ to make exports explicit. Otherwise, remove the unused import.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/imaging/turbo.py` around lines 12 - 16, The module imports TJPF_RGB
but never uses it; either remove the unused import or explicitly export it so
callers can import it from this module: update the try/except block to only
import TurboJPEG if TJPF_RGB is not needed, or keep TJPF_RGB and add an explicit
__all__ = ["TurboJPEG", "TJPF_RGB"] (or include whichever names you want to
expose) to make exports explicit; reference the TurboJPEG and TJPF_RGB symbols
and the module-level __all__ when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@faststack/imaging/turbo.py`:
- Around line 82-87: The try/except around TurboJPEG() currently catches a bare
Exception; replace it with a narrower exception tuple to satisfy the linter and
still handle expected loader failures — catch OSError and RuntimeError (and any
library-specific error class if known) in the except clause that logs to
failures and uses the same source/candidate logic; update the except header
referencing TurboJPEG, candidate and failures so only those expected errors are
caught while preserving the existing failures.append(...) and continue behavior.
- Around line 12-16: The module imports TJPF_RGB but never uses it; either
remove the unused import or explicitly export it so callers can import it from
this module: update the try/except block to only import TurboJPEG if TJPF_RGB is
not needed, or keep TJPF_RGB and add an explicit __all__ = ["TurboJPEG",
"TJPF_RGB"] (or include whichever names you want to expose) to make exports
explicit; reference the TurboJPEG and TJPF_RGB symbols and the module-level
__all__ when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67989e61-beaa-41a9-8ecb-e6f865405b25

📥 Commits

Reviewing files that changed from the base of the PR and between 190e1e3 and fad5da1.

📒 Files selected for processing (2)
  • faststack/imaging/turbo.py
  • faststack/tests/test_turbo.py
✅ Files skipped from review due to trivial changes (1)
  • faststack/tests/test_turbo.py

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
faststack/tests/test_turbo.py (1)

153-171: ⚠️ Potential issue | 🟡 Minor

Make this test hit the RotatingFileHandler path and fully restore logger state.

Path("/bad-path") usually makes mkdir() fail before RotatingFileHandler is constructed, so the mock does not verify the branch this test is named for. The assertion is also too broad because FileHandler is a StreamHandler, and setup_logging(debug=True) leaves multiple logger levels mutated after the test.

🧪 Proposed fix
-def test_setup_logging_keeps_console_handler_when_file_logging_fails(monkeypatch):
+def test_setup_logging_keeps_console_handler_when_file_logging_fails(monkeypatch, tmp_path):
     logging_setup = importlib.import_module("faststack.logging_setup")
 
-    monkeypatch.setattr(logging_setup, "get_app_data_dir", lambda: Path("/bad-path"))
+    monkeypatch.setattr(logging_setup, "get_app_data_dir", lambda: tmp_path / "appdata")
+    rotating_file_handler = Mock(side_effect=OSError("disk full"))
     monkeypatch.setattr(
         logging_setup.logging.handlers,
         "RotatingFileHandler",
-        Mock(side_effect=OSError("disk full")),
+        rotating_file_handler,
     )
 
     root_logger = logging.getLogger()
     original_handlers = list(root_logger.handlers)
+    original_root_level = root_logger.level
+    cache_logger = logging.getLogger("faststack.imaging.cache")
+    prefetch_logger = logging.getLogger("faststack.imaging.prefetch")
+    pil_logger = logging.getLogger("PIL")
+    original_cache_level = cache_logger.level
+    original_prefetch_level = prefetch_logger.level
+    original_pil_level = pil_logger.level
     try:
         logging_setup.setup_logging(debug=True)
+        rotating_file_handler.assert_called_once()
         assert any(
-            isinstance(handler, logging.StreamHandler) for handler in root_logger.handlers
+            isinstance(handler, logging.StreamHandler)
+            and not isinstance(handler, logging.FileHandler)
+            for handler in root_logger.handlers
         )
     finally:
         root_logger.handlers = original_handlers
+        root_logger.setLevel(original_root_level)
+        cache_logger.setLevel(original_cache_level)
+        prefetch_logger.setLevel(original_prefetch_level)
+        pil_logger.setLevel(original_pil_level)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@faststack/tests/test_turbo.py` around lines 153 - 171, The test currently
never reaches the RotatingFileHandler construction and doesn't fully restore
logging state; update
test_setup_logging_keeps_console_handler_when_file_logging_fails to monkeypatch
get_app_data_dir to return a writable temporary Path (so mkdir succeeds) and
mock logging_setup.logging.handlers.RotatingFileHandler to raise OSError to
exercise that branch, then before calling
logging_setup.setup_logging(debug=True) capture root_logger.handlers and
root_logger.level (and any other mutated logger levels if setup_logging touches
them) and after the test restore root_logger.handlers and root_logger.level to
their originals to fully revert logger state and avoid leaking level/handler
changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@faststack/logging_setup.py`:
- Around line 48-50: The app-data candidate Paths are being computed eagerly
(via Path.home(), gettempdir(), Path.cwd()) causing exceptions to propagate
before env-var candidates (FASTSTACK_APPDATA, APPDATA) are checked; change
setup_logging() so that candidates is built lazily: replace the precomputed
append calls with a sequence that evaluates each candidate inside the loop (or
use a generator/iterable) and wrap each evaluation in a try/except that ignores
RuntimeError/OSError so only successfully-resolved Paths are considered;
reference the existing candidates list and the functions Path.home(),
gettempdir(), Path.cwd(), and ensure the lazy resolution approach is applied to
the same logic later (the other occurrence noted around the code at the second
mention).

In `@faststack/tests/test_turbo.py`:
- Around line 78-95: The test
test_get_app_data_dir_falls_back_when_appdata_is_not_creatable can be flakey
because get_app_data_dir() prefers FASTSTACK_APPDATA; before setting APPDATA you
should clear FASTSTACK_APPDATA to ensure the code exercises the APPDATA
branch—use monkeypatch.delenv("FASTSTACK_APPDATA", raising=False) (in the same
test) so the test reliably falls back to the blocked APPDATA path and then to
the home-based fallback_dir.

---

Duplicate comments:
In `@faststack/tests/test_turbo.py`:
- Around line 153-171: The test currently never reaches the RotatingFileHandler
construction and doesn't fully restore logging state; update
test_setup_logging_keeps_console_handler_when_file_logging_fails to monkeypatch
get_app_data_dir to return a writable temporary Path (so mkdir succeeds) and
mock logging_setup.logging.handlers.RotatingFileHandler to raise OSError to
exercise that branch, then before calling
logging_setup.setup_logging(debug=True) capture root_logger.handlers and
root_logger.level (and any other mutated logger levels if setup_logging touches
them) and after the test restore root_logger.handlers and root_logger.level to
their originals to fully revert logger state and avoid leaking level/handler
changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 68d0900f-a556-4696-af2f-debf9b77e3ba

📥 Commits

Reviewing files that changed from the base of the PR and between fad5da1 and 34d090d.

📒 Files selected for processing (2)
  • faststack/logging_setup.py
  • faststack/tests/test_turbo.py

Comment thread faststack/logging_setup.py Outdated
Comment thread faststack/tests/test_turbo.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@faststack/logging_setup.py`:
- Around line 46-50: The fallback order in deferred_candidates currently prefers
gettempdir() before the repository-local fallback, which causes
get_app_data_dir() to return a volatile temp location; update
deferred_candidates (and the similar block at the later occurrence) to prefer
Path.cwd() / "var" / "appdata" (or any non-temp persistent local dirs) before
using Path(gettempdir()) / "faststack", so that Path.home() remains first, then
the project-local var/appdata, and only then the system temp directory; modify
the two occurrences (identify by the variable name deferred_candidates)
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9d9b2bbb-7d32-45e7-b6f5-d9b02eb45bf2

📥 Commits

Reviewing files that changed from the base of the PR and between 34d090d and 0ed99d9.

📒 Files selected for processing (3)
  • README.md
  • faststack/logging_setup.py
  • faststack/tests/test_turbo.py
✅ Files skipped from review due to trivial changes (1)
  • README.md

Comment thread faststack/logging_setup.py
@AlanRockefeller
Copy link
Copy Markdown
Owner

The contents of this PR will be merged into main by PR # 58. Thanks for helping out!

@Dj-Shortcut Dj-Shortcut deleted the fix/windows-pytest-tempdir branch March 25, 2026 04:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants