Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/validate-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ jobs:
HISTORY.md
incremental_files_only: false

# Lint the RegressionTests Python tooling with ruff + mypy via uvx (no project install; the
# tooling is stdlib-only). Versions are pinned here in CI (bumpable) for a reproducible gate;
# the VSCode tasks run the latest tools, so local may differ slightly by design. Config lives
# in RegressionTests/pyproject.toml, so run from that directory (mypy resolves config from CWD).
- name: Setup uv step
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2

- name: Lint Python step
working-directory: RegressionTests
run: |
set -euo pipefail
uvx ruff@0.15.22 check .
uvx ruff@0.15.22 format --check .
uvx mypy@2.3.0 .

- name: Lint workflows step
uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0

Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ coverage/

*.log
*.user

# Python tooling (RegressionTests/)
__pycache__/
*.pyc
.mypy_cache/
.ruff_cache/
59 changes: 58 additions & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,60 @@
"clear": false
}
},
{
"label": "Lint: Ruff",
"type": "shell",
"command": "uvx",
"args": [
"ruff",
"check",
"."
],
"options": {
"cwd": "${workspaceFolder}/RegressionTests"
},
"problemMatcher": [],
"presentation": {
"showReuseMessage": false,
"clear": false
}
},
{
"label": "Lint: Ruff Format",
"type": "shell",
"command": "uvx",
"args": [
"ruff",
"format",
"--check",
"."
],
"options": {
"cwd": "${workspaceFolder}/RegressionTests"
},
"problemMatcher": [],
"presentation": {
"showReuseMessage": false,
"clear": false
}
},
{
"label": "Lint: Mypy",
"type": "shell",
"command": "uvx",
"args": [
"mypy",
"."
],
"options": {
"cwd": "${workspaceFolder}/RegressionTests"
},
"problemMatcher": [],
"presentation": {
"showReuseMessage": false,
"clear": false
}
},
{
"label": "Lint: All (CI parity)",
"dependsOrder": "sequence",
Expand All @@ -332,7 +386,10 @@
"Lint: EditorConfig",
"Lint: Workflows",
"Lint: Markdown",
"Lint: Spelling"
"Lint: Spelling",
"Lint: Ruff",
"Lint: Ruff Format",
"Lint: Mypy"
],
"problemMatcher": [],
"presentation": {
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ Applies to code and workflow (`#`) comments alike.

- [`.editorconfig`](./.editorconfig) is the single source of truth for line endings: CRLF for `.md`, `.cs`, XML/`.csproj`/`.props`, non-workflow `.yml`/`.yaml`, `.json`, `.cmd`/`.bat`/`.ps1`; LF for `.sh`, Dockerfiles, and workflow YAML (`.github/workflows/*.{yml,yaml}`). Workflow YAML is pinned LF because Dependabot and Actions rewrite it with LF, so declaring LF keeps it consistent instead of mixed; git still leaves endings alone (`* -text`) and CI's `editorconfig-checker` enforces it. The `[*.cs]`/ReSharper style block applies because this repo ships .NET.
- **Always honor the `.editorconfig` ending.** Create a file with its spec ending; when editing a file, bring the whole file to spec (a file-wide EOL fix alongside the content change is expected, not a violation); if you come across a file with the wrong ending, fix it. [`.gitattributes`](./.gitattributes) (`* -text`) governs git's own normalization - it is not a license to leave a file on the wrong ending. Verify with `file <path>` after writing.
- **Python (`.py`) and `.toml` are CRLF.** They have no `[*.py]`/`[*.toml]` override, so they inherit the `[*]` CRLF default (matching the audited convention that keeps Python on the repo default rather than pinning LF). Only the `.sh` harness is LF.
Comment thread
ptr727 marked this conversation as resolved.

### Quantitative Claims

Expand Down Expand Up @@ -221,6 +222,7 @@ An **expected, recoverable** failure escalates through the standard repair tiers
- **PlexCleanerTests** (`PlexCleanerTests/PlexCleanerTests.csproj`)
- xUnit v3 test suite. Assertions via AwesomeAssertions.
- **`Docker/`** - multi-arch Linux container build (`ubuntu:rolling`, `linux/amd64` + `linux/arm64`); runs as a `nonroot` user, mounts media under `/media`.
- **`RegressionTests/`** - regression harness and tooling: a ZFS-clone Bash harness plus standalone stdlib-only Python utilities (catalog / reduce / locate / audit) that verify processing decisions stay consistent across versions against a curated media collection. The Python tooling is linted with ruff and type-checked with mypy (config in `RegressionTests/pyproject.toml`); it is the only Python in the repo. No media or media filenames are committed - media-specific reduction rules live with the media as an external JSON file, and the repo ships only a synthetic example. See [`RegressionTests/README.md`](./RegressionTests/README.md).
- **Build configuration**:
- Common MSBuild properties (`TargetFramework`, `Nullable`, `ImplicitUsings`, `AnalysisLevel`, etc.) live in `Directory.Build.props` at the solution root. Do not duplicate these in individual `.csproj` files - only add a property to a `.csproj` when it is project-specific or overrides the shared default.
- All NuGet package versions are centralised in `Directory.Packages.props`. `PackageReference` elements in `.csproj` files must not include a `Version` attribute. Asset metadata (`PrivateAssets`, `IncludeAssets`) stays in the `.csproj` `PackageReference` element.
Expand Down
4 changes: 4 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@ Check states with `HasFlag()`, combine with `|=`
- Docker tests: Download Matroska test files from GitHub
- CI: Separate workflows for build tests and Docker tests

### Regression Testing

- Cross-version processing-consistency checks against a curated media collection, with a ZFS-clone harness and Python catalog / reduce / locate / audit tooling under `RegressionTests/`. See [`RegressionTests/README.md`](./RegressionTests/README.md).

## Build and Release

The authoritative release and workflow governance is in [AGENTS.md](./AGENTS.md). This section is a short architectural summary.
Expand Down
1 change: 1 addition & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Utility to optimize media files for Direct Play in Plex, Emby, Jellyfin, etc.
- Handle the `SIGINT`, `SIGTERM`, and `SIGQUIT` termination signals (`docker stop`, `Ctrl+C`) so processing is interrupted gracefully and the summary and exit code are logged before exit. The custom `Ctrl+Q`/`Ctrl+Z` exit keys are removed in favor of the standard signals.
- Normalize `Default` track flags instead of only warning about them: clear the flag on a lone track of a type, keep the preferred audio track as the single default when multiple are flagged, and clear all default flags on subtitle tracks.
- Added a `custom` command that loads a user-provided plugin assembly implementing `IProcessPlugin` and runs it over the media files, reusing the file iteration and processing API for bespoke re-processing or repair. Includes the `MatroskaHeaderCleanup` example plugin. Not available in AOT builds.
- Added a regression test suite and reduced-corpus tooling under `RegressionTests/`: a ZFS-clone harness and Python catalog / reduce / locate / audit utilities that verify processing decisions stay consistent across versions. No application changes.
- Version 3.19:
- Reworked the CI/CD pipeline to a branch-scoped self-publishing model: a weekly scheduled run (and manual dispatch) publishes both `main` (stable, Docker `latest`) and `develop` (prerelease, Docker `develop`) - native executables, the multi-arch Docker image, and the GitHub release - while merges accumulate until the next run. No application changes.
- Added `WORKFLOW.md` (the canonical CI/CD specification) and `repo-config/` (rulesets and repository settings as code).
Expand Down
8 changes: 6 additions & 2 deletions PlexCleaner.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,19 @@
},
"extensions": {
"recommendations": [
"charliermarsh.ruff",
"csharpier.csharpier-vscode",
"davidanson.vscode-markdownlint",
"editorconfig.editorconfig",
"fanaticpythoner.better-todo-tree",
"github.vscode-github-actions",
"ms-azuretools.vscode-docker",
"ms-dotnettools.csdevkit",
"ms-python.mypy-type-checker",
"ms-python.python",
"ms-python.vscode-pylance",
"streetsidesoftware.code-spell-checker",
"yzhang.markdown-all-in-one",
"fanaticpythoner.better-todo-tree"
"yzhang.markdown-all-in-one"
]
}
}
64 changes: 2 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -936,69 +936,9 @@ docker run \

### Regression Testing

Regression testing ensures consistent behavior across versions by comparing processing results on the same media files.
Regression testing ensures consistent behavior across versions by comparing processing results on the same media files, down to the per-file processing decision.

The behavior of the tool is very dependent on the media files being tested, and the following process can facilitate regressions testing, assuring that the process results between versions remain consistent.

- Maintain a collection of troublesome media files that resulted in functional changes.
- Create a ZFS snapshot of the media files to test.
- Process the files, using a known good version, and save the results in JSON format using the `--resultsfile` option.
- Restore the ZFS snapshot allowing repetitive testing using the original files.
- Process the files again using the under test version.
- Compare the JSON results file from the known good version with the version under test.
- Investigate any file comparison discrepancies.

E.g.

```shell
# Copy troublesome files
rsync -av --delete --progress /data/media/Troublesome/. /data/media/test
chown -R nobody:users /data/media/test
chmod -R ug=rwx,o=rx /data/media/test

# Take snapshot
zfs destroy hddpool/media/test@backup
zfs snapshot hddpool/media/test@backup
```

```shell
# Config
PlexCleanerApp=/PlexCleaner/Debug/PlexCleaner
MediaPath=/Test/Media
ConfigPath=/Test/Config

# Test function
RunContainer () {
local Image=$1
local Tag=$2

# Rollback to snapshot
sudo zfs rollback hddpool/media/test@backup

# Process files
docker run \
-it \
--rm \
--pull always \
--name PlexCleaner-Test \
--user nobody:users \
--env TZ=America/Los_Angeles \
--volume /data/media/test:$MediaPath:rw \
--volume /data/media/PlexCleaner:$ConfigPath:rw \
$Image:$Tag \
$PlexCleanerApp process \
--settingsfile=$ConfigPath/PlexCleaner.json \
--logfile=$ConfigPath/PlexCleaner-$Tag.log \
--mediafiles=$MediaPath \
--testsnippets \
--quickscan \
--resultsfile=$ConfigPath/Results-$Tag.json
}

# Test containers
RunContainer docker.io/ptr727/plexcleaner latest
RunContainer docker.io/ptr727/plexcleaner develop
```
The behavior of the tool is very dependent on the media files being tested. A reproducible process and its tooling live under [`RegressionTests/`](./RegressionTests/): a ZFS-clone harness that processes a curated collection of troublesome media through a given image tag, plus utilities that derive a machine-readable issue catalog, build a proven-equivalent reduced collection, and audit physical-error coverage. See [`RegressionTests/README.md`](./RegressionTests/README.md) for details.

## Development Tooling

Expand Down
Loading