Refine tests, CLI
This commit is contained in:
129
README.md
129
README.md
@@ -1,48 +1,135 @@
|
|||||||
# FFX
|
# FFX
|
||||||
|
|
||||||
|
FFX is a local CLI and Textual TUI for inspecting TV episode files, storing normalization rules in SQLite, and converting outputs into a predictable stream, metadata, and filename layout.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Linux-like environment
|
||||||
|
- `python3`
|
||||||
|
- `ffmpeg`
|
||||||
|
- `ffprobe`
|
||||||
|
- `cpulimit`
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
per https:
|
FFX uses a two-step local setup flow.
|
||||||
|
|
||||||
|
### 1. Install The Bundle
|
||||||
|
|
||||||
|
This step creates or reuses the persistent bundle virtualenv in `~/.local/share/ffx.venv`, installs FFX into it, and ensures `ffx` is exposed through a shell alias.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pip install https://<URL>/<Releaser>/ffx.git@<Branch>
|
bash tools/setup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
per git:
|
If you also want the Python packages needed for the modern test suite:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pip install git+ssh://<Username>@<URL>/<Releaser>/ffx.git@<Branch>
|
bash tools/setup.sh --with-tests
|
||||||
```
|
```
|
||||||
|
|
||||||
## Version history
|
You can verify the bundle state without changing anything:
|
||||||
|
|
||||||
### 0.1.1
|
```sh
|
||||||
|
bash tools/setup.sh --check
|
||||||
|
```
|
||||||
|
|
||||||
Bugfixes, TMBD identify shows
|
### 2. Prepare System Dependencies And Local User Files
|
||||||
|
|
||||||
### 0.1.2
|
This step installs or verifies workstation dependencies and seeds local config and data directories. It is the step wrapped by the CLI command `ffx configure_workstation`.
|
||||||
|
|
||||||
Bugfixes
|
Run it directly:
|
||||||
|
|
||||||
### 0.1.3
|
```sh
|
||||||
|
bash tools/configure_workstation.sh
|
||||||
|
```
|
||||||
|
|
||||||
Subtitle file imports
|
Or through the installed CLI:
|
||||||
|
|
||||||
### 0.2.0
|
```sh
|
||||||
|
ffx configure_workstation
|
||||||
|
```
|
||||||
|
|
||||||
Tests, Config-File
|
Check-only mode is available in both forms:
|
||||||
|
|
||||||
### 0.2.1
|
```sh
|
||||||
|
bash tools/configure_workstation.sh --check
|
||||||
|
ffx configure_workstation --check
|
||||||
|
```
|
||||||
|
|
||||||
Signature, Tags cleaning, Bugfixes, Refactoring
|
`tools/configure_workstation.sh` does not manage the bundle virtualenv. Python-side test packages belong to `tools/setup.sh --with-tests`.
|
||||||
|
|
||||||
### 0.2.2
|
## Basic Usage
|
||||||
|
|
||||||
CLI-Overrides
|
Examples:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ffx version
|
||||||
|
ffx inspect /path/to/episode.mkv
|
||||||
|
ffx convert /path/to/episode.mkv
|
||||||
|
ffx shows
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modern Tests
|
||||||
|
|
||||||
|
Install Python test packages first:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash tools/setup.sh --with-tests
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the modern automatically discovered test suite:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./tools/test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This runner uses `pytest` and intentionally excludes the legacy harness under `tests/legacy/`.
|
||||||
|
|
||||||
|
## Default Local Paths
|
||||||
|
|
||||||
|
- Config: `~/.local/etc/ffx.json`
|
||||||
|
- Database: `~/.local/var/ffx/ffx.db`
|
||||||
|
- Log file: `~/.local/var/log/ffx.log`
|
||||||
|
- Bundle venv: `~/.local/share/ffx.venv`
|
||||||
|
|
||||||
|
## TMDB
|
||||||
|
|
||||||
|
TMDB-backed metadata enrichment requires `TMDB_API_KEY` to be set in the environment.
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
### 0.2.3
|
### 0.2.3
|
||||||
|
|
||||||
PyPi packaging
|
- PyPI packaging
|
||||||
Templating output filename
|
- output filename templating
|
||||||
Season shiftung
|
- season shifting
|
||||||
DB-Versionierung
|
- DB versioning
|
||||||
|
|
||||||
|
### 0.2.2
|
||||||
|
|
||||||
|
- CLI overrides
|
||||||
|
|
||||||
|
### 0.2.1
|
||||||
|
|
||||||
|
- signature handling
|
||||||
|
- tag cleanup
|
||||||
|
- bugfixes and refactoring
|
||||||
|
|
||||||
|
### 0.2.0
|
||||||
|
|
||||||
|
- tests
|
||||||
|
- config file
|
||||||
|
|
||||||
|
### 0.1.3
|
||||||
|
|
||||||
|
- subtitle file imports
|
||||||
|
|
||||||
|
### 0.1.2
|
||||||
|
|
||||||
|
- bugfixes
|
||||||
|
|
||||||
|
### 0.1.1
|
||||||
|
|
||||||
|
- bugfixes
|
||||||
|
- TMDB show identification
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
- This list is intentionally optimization-oriented rather than bug-oriented. Some items below also improve correctness or maintainability, but they were selected because they can reduce runtime cost, operator friction, or iteration overhead.
|
- This list is intentionally optimization-oriented rather than bug-oriented. Some items below also improve correctness or maintainability, but they were selected because they can reduce runtime cost, operator friction, or iteration overhead.
|
||||||
- A first modern integration slice now exists under [`tests/integration/subtrack_mapping`](/home/osgw/.local/src/codex/ffx/tests/integration/subtrack_mapping). Remaining test-suite cleanup is now mostly about migrating and shrinking the legacy harness surface under [`tests/legacy`](/home/osgw/.local/src/codex/ffx/tests/legacy).
|
- A first modern integration slice now exists under [`tests/integration/subtrack_mapping`](/home/osgw/.local/src/codex/ffx/tests/integration/subtrack_mapping). Remaining test-suite cleanup is now mostly about migrating and shrinking the legacy harness surface under [`tests/legacy`](/home/osgw/.local/src/codex/ffx/tests/legacy).
|
||||||
- FFX logger setup now reuses named handlers, and fallback logger access no longer mutates handlers in ordinary constructors and helpers.
|
- FFX logger setup now reuses named handlers, and fallback logger access no longer mutates handlers in ordinary constructors and helpers.
|
||||||
|
- The process wrapper now uses `subprocess.run(...)` with centralized command formatting plus stable timeout and missing-command error mapping.
|
||||||
|
|
||||||
## Focused Snapshot
|
## Focused Snapshot
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
## Optimization Candidates
|
## Optimization Candidates
|
||||||
|
|
||||||
1. CLI startup and import cost
|
1. CLI startup and import cost
|
||||||
- [`src/ffx/cli.py`](/home/osgw/.local/src/codex/ffx/src/ffx/cli.py) imports a large portion of the application at module import time, even for cheap commands such as `version`, `help`, `setup_dependencies`, and `upgrade`.
|
- [`src/ffx/cli.py`](/home/osgw/.local/src/codex/ffx/src/ffx/cli.py) imports a large portion of the application at module import time, even for cheap commands such as `version`, `help`, `configure_workstation`, and `upgrade`.
|
||||||
- Optimization:
|
- Optimization:
|
||||||
- Move heavy imports into the commands that actually need them.
|
- Move heavy imports into the commands that actually need them.
|
||||||
- Keep the CLI root importable with only core stdlib and Click dependencies.
|
- Keep the CLI root importable with only core stdlib and Click dependencies.
|
||||||
@@ -70,18 +71,8 @@
|
|||||||
- Expected value:
|
- Expected value:
|
||||||
- Lower latency on repeated experimentation.
|
- Lower latency on repeated experimentation.
|
||||||
|
|
||||||
6. Process wrapper lacks stronger execution controls
|
6. Tooling overlap and naming drift
|
||||||
- [`src/ffx/process.py`](/home/osgw/.local/src/codex/ffx/src/ffx/process.py) uses `Popen(...).communicate()` without timeout handling, structured error mapping, or direct missing-command handling.
|
- There are still overlapping workstation-setup entrypoints across [`tools/configure_workstation.sh`](/home/osgw/.local/src/codex/ffx/tools/configure_workstation.sh), [`tools/setup.sh`](/home/osgw/.local/src/codex/ffx/tools/setup.sh), and newer CLI maintenance commands.
|
||||||
- Optimization:
|
|
||||||
- Add timeout support and clearer `FileNotFoundError` handling.
|
|
||||||
- Consider `subprocess.run(..., check=False, text=True)` where streaming is not required.
|
|
||||||
- Centralize return/error formatting.
|
|
||||||
- Expected value:
|
|
||||||
- Better failure diagnosis.
|
|
||||||
- Cleaner process management semantics.
|
|
||||||
|
|
||||||
7. Tooling overlap and naming drift
|
|
||||||
- There are still overlapping prep and setup entrypoints across [`tools/prepare.sh`](/home/osgw/.local/src/codex/ffx/tools/prepare.sh), [`tools/setup.sh`](/home/osgw/.local/src/codex/ffx/tools/setup.sh), and newer CLI maintenance commands.
|
|
||||||
- Optimization:
|
- Optimization:
|
||||||
- Decide which scripts remain canonical.
|
- Decide which scripts remain canonical.
|
||||||
- Replace or remove legacy wrappers once equivalent CLI commands exist.
|
- Replace or remove legacy wrappers once equivalent CLI commands exist.
|
||||||
@@ -90,7 +81,7 @@
|
|||||||
- Less operator confusion.
|
- Less operator confusion.
|
||||||
- Fewer duplicated procedures to maintain.
|
- Fewer duplicated procedures to maintain.
|
||||||
|
|
||||||
8. Placeholder UI surfaces should either ship or disappear
|
7. Placeholder UI surfaces should either ship or disappear
|
||||||
- [`src/ffx/help_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/help_screen.py) and [`src/ffx/settings_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/settings_screen.py) are placeholders.
|
- [`src/ffx/help_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/help_screen.py) and [`src/ffx/settings_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/settings_screen.py) are placeholders.
|
||||||
- Optimization:
|
- Optimization:
|
||||||
- Either remove them from the active UI surface or complete them.
|
- Either remove them from the active UI surface or complete them.
|
||||||
@@ -99,7 +90,7 @@
|
|||||||
- Leaner interface.
|
- Leaner interface.
|
||||||
- Lower UX ambiguity.
|
- Lower UX ambiguity.
|
||||||
|
|
||||||
9. Large Textual screens repeat configuration and controller loading
|
8. Large Textual screens repeat configuration and controller loading
|
||||||
- Screens such as [`src/ffx/media_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/media_details_screen.py), [`src/ffx/pattern_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/pattern_details_screen.py), and [`src/ffx/show_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/show_details_screen.py) repeat setup patterns and local metadata filtering extraction.
|
- Screens such as [`src/ffx/media_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/media_details_screen.py), [`src/ffx/pattern_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/pattern_details_screen.py), and [`src/ffx/show_details_screen.py`](/home/osgw/.local/src/codex/ffx/src/ffx/show_details_screen.py) repeat setup patterns and local metadata filtering extraction.
|
||||||
- Optimization:
|
- Optimization:
|
||||||
- Extract a shared screen base or helper for common config/controller/bootstrap logic.
|
- Extract a shared screen base or helper for common config/controller/bootstrap logic.
|
||||||
@@ -108,7 +99,7 @@
|
|||||||
- Lower maintenance overhead.
|
- Lower maintenance overhead.
|
||||||
- Easier UI iteration.
|
- Easier UI iteration.
|
||||||
|
|
||||||
10. Several helper functions are unfinished or dead-weight
|
9. Several helper functions are unfinished or dead-weight
|
||||||
- [`src/ffx/helper.py`](/home/osgw/.local/src/codex/ffx/src/ffx/helper.py) contains `permutateList(...): pass`.
|
- [`src/ffx/helper.py`](/home/osgw/.local/src/codex/ffx/src/ffx/helper.py) contains `permutateList(...): pass`.
|
||||||
- There are many combinator and conversion placeholders across tests and migrations.
|
- There are many combinator and conversion placeholders across tests and migrations.
|
||||||
- Optimization:
|
- Optimization:
|
||||||
@@ -118,7 +109,7 @@
|
|||||||
- Smaller mental model.
|
- Smaller mental model.
|
||||||
- Less time spent re-evaluating inactive paths.
|
- Less time spent re-evaluating inactive paths.
|
||||||
|
|
||||||
11. Test suite shape is expensive to understand and likely expensive to run
|
10. Test suite shape is expensive to understand and likely expensive to run
|
||||||
- The project still carries a large legacy matrix of combinator files under [`tests/legacy`](/home/osgw/.local/src/codex/ffx/tests/legacy), several placeholder `pass` implementations, and at least one suspicious filename with an embedded space: [`tests/legacy/disposition_combinator_2_3 .py`](/home/osgw/.local/src/codex/ffx/tests/legacy/disposition_combinator_2_3 .py).
|
- The project still carries a large legacy matrix of combinator files under [`tests/legacy`](/home/osgw/.local/src/codex/ffx/tests/legacy), several placeholder `pass` implementations, and at least one suspicious filename with an embedded space: [`tests/legacy/disposition_combinator_2_3 .py`](/home/osgw/.local/src/codex/ffx/tests/legacy/disposition_combinator_2_3 .py).
|
||||||
- A first focused replacement slice now exists in [`tests/integration/subtrack_mapping/test_cli_bundle.py`](/home/osgw/.local/src/codex/ffx/tests/integration/subtrack_mapping/test_cli_bundle.py), so the remaining work is migration and consolidation rather than creating the modern test shape from scratch.
|
- A first focused replacement slice now exists in [`tests/integration/subtrack_mapping/test_cli_bundle.py`](/home/osgw/.local/src/codex/ffx/tests/integration/subtrack_mapping/test_cli_bundle.py), so the remaining work is migration and consolidation rather than creating the modern test shape from scratch.
|
||||||
- Optimization:
|
- Optimization:
|
||||||
@@ -129,7 +120,7 @@
|
|||||||
- Faster contributor onboarding.
|
- Faster contributor onboarding.
|
||||||
- Easier CI adoption later.
|
- Easier CI adoption later.
|
||||||
|
|
||||||
12. Process resource limiting semantics could be clearer
|
11. Process resource limiting semantics could be clearer
|
||||||
- [`src/ffx/process.py`](/home/osgw/.local/src/codex/ffx/src/ffx/process.py) prepends `nice` and `cpulimit` directly when values are set.
|
- [`src/ffx/process.py`](/home/osgw/.local/src/codex/ffx/src/ffx/process.py) prepends `nice` and `cpulimit` directly when values are set.
|
||||||
- Optimization:
|
- Optimization:
|
||||||
- Validate and document effective behavior for combined `nice` + `cpulimit`.
|
- Validate and document effective behavior for combined `nice` + `cpulimit`.
|
||||||
@@ -138,7 +129,7 @@
|
|||||||
- Fewer surprises in production-like runs.
|
- Fewer surprises in production-like runs.
|
||||||
- Easier support for user-reported performance behavior.
|
- Easier support for user-reported performance behavior.
|
||||||
|
|
||||||
13. Import-time dependency coupling makes maintenance commands brittle
|
12. Import-time dependency coupling makes maintenance commands brittle
|
||||||
- Even after recent CLI maintenance additions, the top-level CLI module still imports most application modules before Click dispatch.
|
- Even after recent CLI maintenance additions, the top-level CLI module still imports most application modules before Click dispatch.
|
||||||
- Optimization:
|
- Optimization:
|
||||||
- Push imports for ORM, Textual, TMDB, ffmpeg helpers, and descriptors behind the commands that actually need them.
|
- Push imports for ORM, Textual, TMDB, ffmpeg helpers, and descriptors behind the commands that actually need them.
|
||||||
@@ -146,7 +137,7 @@
|
|||||||
- Maintenance commands such as setup and upgrade stay usable when optional runtime dependencies are broken.
|
- Maintenance commands such as setup and upgrade stay usable when optional runtime dependencies are broken.
|
||||||
- Better separation between media runtime code and maintenance tooling.
|
- Better separation between media runtime code and maintenance tooling.
|
||||||
|
|
||||||
14. Regex and string utility cleanup
|
13. Regex and string utility cleanup
|
||||||
- [`src/ffx/helper.py`](/home/osgw/.local/src/codex/ffx/src/ffx/helper.py) still emits a `SyntaxWarning` for `RICH_COLOR_PATTERN`.
|
- [`src/ffx/helper.py`](/home/osgw/.local/src/codex/ffx/src/ffx/helper.py) still emits a `SyntaxWarning` for `RICH_COLOR_PATTERN`.
|
||||||
- Optimization:
|
- Optimization:
|
||||||
- Convert regex literals to raw strings where appropriate.
|
- Convert regex literals to raw strings where appropriate.
|
||||||
@@ -155,7 +146,7 @@
|
|||||||
- Cleaner runtime output.
|
- Cleaner runtime output.
|
||||||
- Less warning noise during dry-run maintenance commands.
|
- Less warning noise during dry-run maintenance commands.
|
||||||
|
|
||||||
15. Database startup always runs schema creation and version checks
|
14. Database startup always runs schema creation and version checks
|
||||||
- [`src/ffx/database.py`](/home/osgw/.local/src/codex/ffx/src/ffx/database.py) runs `Base.metadata.create_all(...)` and version checks every time a DB-backed context is created.
|
- [`src/ffx/database.py`](/home/osgw/.local/src/codex/ffx/src/ffx/database.py) runs `Base.metadata.create_all(...)` and version checks every time a DB-backed context is created.
|
||||||
- Optimization:
|
- Optimization:
|
||||||
- Measure startup cost and consider separating bootstrapping from ordinary command execution.
|
- Measure startup cost and consider separating bootstrapping from ordinary command execution.
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ ffx = "ffx.cli:ffx"
|
|||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
python_files = ["test_*.py"]
|
python_files = ["test_*.py"]
|
||||||
|
norecursedirs = ["tests/legacy", "tests/support"]
|
||||||
addopts = "-ra"
|
addopts = "-ra"
|
||||||
markers = [
|
markers = [
|
||||||
"integration: exercises the FFX bundle with real ffmpeg/ffprobe processes",
|
"integration: exercises the FFX bundle with real ffmpeg/ffprobe processes",
|
||||||
|
|||||||
@@ -35,7 +35,11 @@
|
|||||||
|
|
||||||
## Functional Requirements
|
## Functional Requirements
|
||||||
|
|
||||||
- The system shall provide a CLI entrypoint named `ffx` with commands for `convert`, `inspect`, `shows`, `unmux`, `cropdetect`, `version`, and `help`.
|
- The system shall provide a CLI entrypoint named `ffx` with commands for `convert`, `inspect`, `shows`, `unmux`, `cropdetect`, `configure_workstation`, `upgrade`, `version`, and `help`.
|
||||||
|
- The system shall support a two-step local installation and preparation flow:
|
||||||
|
- `tools/setup.sh` is the first step and shall own bundle virtualenv creation, package installation, shell alias exposure, and optional Python test-package installation.
|
||||||
|
- `tools/configure_workstation.sh` is the second step and shall own workstation dependency checks and installation plus local config and directory seeding.
|
||||||
|
- The CLI command `ffx configure_workstation` shall act as a wrapper for the second-step preparation flow in `tools/configure_workstation.sh`.
|
||||||
- The system shall persist reusable normalization rules in SQLite for:
|
- The system shall persist reusable normalization rules in SQLite for:
|
||||||
- shows and show formatting digits,
|
- shows and show formatting digits,
|
||||||
- regex-based filename patterns,
|
- regex-based filename patterns,
|
||||||
@@ -65,7 +69,7 @@
|
|||||||
- The system should stay understandable as a small local tool: controllers, descriptors, models, and screens should remain separate enough for contributors to trace a workflow end to end.
|
- The system should stay understandable as a small local tool: controllers, descriptors, models, and screens should remain separate enough for contributors to trace a workflow end to end.
|
||||||
- The system should produce predictable output for the same database rules, CLI overrides, and source files.
|
- The system should produce predictable output for the same database rules, CLI overrides, and source files.
|
||||||
- The system should preserve a lightweight operational footprint: local SQLite state, local log file, no mandatory background services.
|
- The system should preserve a lightweight operational footprint: local SQLite state, local log file, no mandatory background services.
|
||||||
- The system should be testable through the existing combinatorial CLI-oriented test harness and through isolated logic in descriptors and controllers.
|
- The system should be testable through modern automatically discovered tests and through remaining legacy harness coverage during migration.
|
||||||
- The system should expose enough logging to diagnose failed probes, failed conversions, and rule mismatches without requiring a debugger.
|
- The system should expose enough logging to diagnose failed probes, failed conversions, and rule mismatches without requiring a debugger.
|
||||||
|
|
||||||
## Constraints And Assumptions
|
## Constraints And Assumptions
|
||||||
@@ -84,6 +88,9 @@
|
|||||||
- Third-party dependencies:
|
- Third-party dependencies:
|
||||||
- `ffmpeg`, `ffprobe`, and `cpulimit`.
|
- `ffmpeg`, `ffprobe`, and `cpulimit`.
|
||||||
- TMDB API access through `TMDB_API_KEY` for metadata enrichment.
|
- TMDB API access through `TMDB_API_KEY` for metadata enrichment.
|
||||||
|
- Installation assumptions:
|
||||||
|
- The Python-side bundle install step and optional Python test extras are managed by `tools/setup.sh`.
|
||||||
|
- The workstation-preparation step is managed separately by `tools/configure_workstation.sh` or `ffx configure_workstation`.
|
||||||
|
|
||||||
## Acceptance Scope
|
## Acceptance Scope
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ def ffx(ctx, database_file, verbose, dry_run):
|
|||||||
|
|
||||||
ctx.obj = {}
|
ctx.obj = {}
|
||||||
|
|
||||||
if ctx.invoked_subcommand in ('setup_dependencies', 'upgrade'):
|
if ctx.invoked_subcommand in ('configure_workstation', 'upgrade'):
|
||||||
ctx.obj['dry_run'] = dry_run
|
ctx.obj['dry_run'] = dry_run
|
||||||
ctx.obj['verbosity'] = verbose
|
ctx.obj['verbosity'] = verbose
|
||||||
return
|
return
|
||||||
@@ -104,8 +104,8 @@ def getRepoRootPath():
|
|||||||
return os.path.dirname(os.path.dirname(os.path.dirname(currentFilePath)))
|
return os.path.dirname(os.path.dirname(os.path.dirname(currentFilePath)))
|
||||||
|
|
||||||
|
|
||||||
def getPrepareScriptPath():
|
def getConfigureWorkstationScriptPath():
|
||||||
return os.path.join(getRepoRootPath(), 'tools', 'prepare.sh')
|
return os.path.join(getRepoRootPath(), 'tools', 'configure_workstation.sh')
|
||||||
|
|
||||||
|
|
||||||
def getBundleVenvDirectory():
|
def getBundleVenvDirectory():
|
||||||
@@ -120,22 +120,23 @@ def getBundleRepoPath():
|
|||||||
return getRepoRootPath()
|
return getRepoRootPath()
|
||||||
|
|
||||||
|
|
||||||
@ffx.command(name='setup_dependencies')
|
@ffx.command(name='configure_workstation')
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@click.option('--check', is_flag=True, default=False, help='Only verify dependency readiness')
|
@click.option('--check', is_flag=True, default=False, help='Only verify workstation-configuration readiness')
|
||||||
@click.argument('prepare_args', nargs=-1, type=click.UNPROCESSED)
|
@click.argument('configure_args', nargs=-1, type=click.UNPROCESSED)
|
||||||
def setup_dependencies(ctx, check, prepare_args):
|
def configure_workstation(ctx, check, configure_args):
|
||||||
prepareScriptPath = getPrepareScriptPath()
|
"""Prepare workstation dependencies and local config after bundle install."""
|
||||||
|
configureScriptPath = getConfigureWorkstationScriptPath()
|
||||||
|
|
||||||
if not os.path.isfile(prepareScriptPath):
|
if not os.path.isfile(configureScriptPath):
|
||||||
raise click.ClickException(f"Preparation script not found at {prepareScriptPath}")
|
raise click.ClickException(f"Workstation configuration script not found at {configureScriptPath}")
|
||||||
|
|
||||||
commandSequence = ['bash', prepareScriptPath]
|
commandSequence = ['bash', configureScriptPath]
|
||||||
|
|
||||||
if check:
|
if check:
|
||||||
commandSequence.append('--check')
|
commandSequence.append('--check')
|
||||||
|
|
||||||
commandSequence += list(prepare_args)
|
commandSequence += list(configure_args)
|
||||||
|
|
||||||
if ctx.obj.get('dry_run', False):
|
if ctx.obj.get('dry_run', False):
|
||||||
click.echo(' '.join(commandSequence))
|
click.echo(' '.join(commandSequence))
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ class FfxController():
|
|||||||
self.__logger: Logger = context['logger']
|
self.__logger: Logger = context['logger']
|
||||||
|
|
||||||
|
|
||||||
|
def executeCommandSequence(self, commandSequence):
|
||||||
|
out, err, rc = executeProcess(commandSequence, context=self.__context)
|
||||||
|
if rc:
|
||||||
|
raise click.ClickException(f"Command resulted in error: rc={rc} error={err}")
|
||||||
|
return out, err, rc
|
||||||
|
|
||||||
|
|
||||||
def generateAV1Tokens(self, quality, preset, subIndex : int = 0):
|
def generateAV1Tokens(self, quality, preset, subIndex : int = 0):
|
||||||
|
|
||||||
return [f"-c:v:{int(subIndex)}", 'libsvtav1',
|
return [f"-c:v:{int(subIndex)}", 'libsvtav1',
|
||||||
@@ -288,9 +295,7 @@ class FfxController():
|
|||||||
self.__logger.debug("FfxController.runJob(): Running command sequence")
|
self.__logger.debug("FfxController.runJob(): Running command sequence")
|
||||||
|
|
||||||
if not self.__context['dry_run']:
|
if not self.__context['dry_run']:
|
||||||
out, err, rc = executeProcess(commandSequence, context=self.__context)
|
self.executeCommandSequence(commandSequence)
|
||||||
if rc:
|
|
||||||
raise click.ClickException(f"Command resulted in error: rc={rc} error={err}")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if videoEncoder == VideoEncoder.AV1:
|
if videoEncoder == VideoEncoder.AV1:
|
||||||
@@ -320,7 +325,7 @@ class FfxController():
|
|||||||
self.__logger.debug(f"FfxController.runJob(): Running command sequence")
|
self.__logger.debug(f"FfxController.runJob(): Running command sequence")
|
||||||
|
|
||||||
if not self.__context['dry_run']:
|
if not self.__context['dry_run']:
|
||||||
executeProcess(commandSequence, context = self.__context)
|
self.executeCommandSequence(commandSequence)
|
||||||
|
|
||||||
|
|
||||||
if videoEncoder == VideoEncoder.H264:
|
if videoEncoder == VideoEncoder.H264:
|
||||||
@@ -350,7 +355,7 @@ class FfxController():
|
|||||||
self.__logger.debug(f"FfxController.runJob(): Running command sequence")
|
self.__logger.debug(f"FfxController.runJob(): Running command sequence")
|
||||||
|
|
||||||
if not self.__context['dry_run']:
|
if not self.__context['dry_run']:
|
||||||
executeProcess(commandSequence, context = self.__context)
|
self.executeCommandSequence(commandSequence)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -382,7 +387,7 @@ class FfxController():
|
|||||||
self.__logger.debug(f"FfxController.runJob(): Running command sequence 1")
|
self.__logger.debug(f"FfxController.runJob(): Running command sequence 1")
|
||||||
|
|
||||||
if not self.__context['dry_run']:
|
if not self.__context['dry_run']:
|
||||||
executeProcess(commandSequence1, context = self.__context)
|
self.executeCommandSequence(commandSequence1)
|
||||||
|
|
||||||
commandSequence2 = (commandTokens
|
commandSequence2 = (commandTokens
|
||||||
+ self.__targetMediaDescriptor.getImportFileTokens()
|
+ self.__targetMediaDescriptor.getImportFileTokens()
|
||||||
@@ -409,9 +414,7 @@ class FfxController():
|
|||||||
self.__logger.debug(f"FfxController.runJob(): Running command sequence 2")
|
self.__logger.debug(f"FfxController.runJob(): Running command sequence 2")
|
||||||
|
|
||||||
if not self.__context['dry_run']:
|
if not self.__context['dry_run']:
|
||||||
out, err, rc = executeProcess(commandSequence2, context = self.__context)
|
self.executeCommandSequence(commandSequence2)
|
||||||
if rc:
|
|
||||||
raise click.ClickException(f"Command resulted in error: rc={rc} error={err}")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -436,4 +439,4 @@ class FfxController():
|
|||||||
str(length),
|
str(length),
|
||||||
path]
|
path]
|
||||||
|
|
||||||
out, err, rc = executeProcess(commandTokens, context = self.__context)
|
self.executeCommandSequence(commandTokens)
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import List
|
from typing import Iterable, List
|
||||||
|
|
||||||
from .logging_utils import get_ffx_logger
|
from .logging_utils import get_ffx_logger
|
||||||
|
|
||||||
def executeProcess(commandSequence: List[str], directory: str = None, context: dict = None):
|
COMMAND_TIMED_OUT_RETURN_CODE = 124
|
||||||
|
COMMAND_NOT_FOUND_RETURN_CODE = 127
|
||||||
|
|
||||||
|
|
||||||
|
def formatCommandSequence(commandSequence: Iterable[str]) -> str:
|
||||||
|
return shlex.join([str(token) for token in commandSequence])
|
||||||
|
|
||||||
|
|
||||||
|
def getWrappedCommandSequence(commandSequence: List[str], context: dict = None) -> List[str]:
|
||||||
"""
|
"""
|
||||||
niceness -20 bis +19
|
niceness -20 bis +19
|
||||||
cpu_percent: 1 bis 99
|
cpu_percent: 1 bis 99
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if context is None:
|
|
||||||
logger = get_ffx_logger()
|
|
||||||
else:
|
|
||||||
logger = context['logger']
|
|
||||||
|
|
||||||
niceSequence = []
|
niceSequence = []
|
||||||
|
|
||||||
niceness = int((context or {}).get('resource_limits', {}).get('niceness', 99))
|
niceness = int((context or {}).get('resource_limits', {}).get('niceness', 99))
|
||||||
@@ -24,11 +28,72 @@ def executeProcess(commandSequence: List[str], directory: str = None, context: d
|
|||||||
if cpu_percent >= 1:
|
if cpu_percent >= 1:
|
||||||
niceSequence += ['cpulimit', '-l', str(cpu_percent), '--']
|
niceSequence += ['cpulimit', '-l', str(cpu_percent), '--']
|
||||||
|
|
||||||
niceCommand = niceSequence + commandSequence
|
return niceSequence + [str(token) for token in commandSequence]
|
||||||
|
|
||||||
logger.debug(f"executeProcess() command sequence: {' '.join(niceCommand)}")
|
|
||||||
|
|
||||||
process = subprocess.Popen(niceCommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', cwd = directory)
|
def getProcessTimeoutSeconds(context: dict = None, timeoutSeconds: float = None):
|
||||||
output, error = process.communicate()
|
if timeoutSeconds is None:
|
||||||
|
timeoutSeconds = (context or {}).get('resource_limits', {}).get('timeout_seconds')
|
||||||
|
|
||||||
return output, error, process.returncode
|
if timeoutSeconds is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
timeoutSeconds = float(timeoutSeconds)
|
||||||
|
|
||||||
|
return timeoutSeconds if timeoutSeconds > 0 else None
|
||||||
|
|
||||||
|
|
||||||
|
def executeProcess(
|
||||||
|
commandSequence: List[str],
|
||||||
|
directory: str = None,
|
||||||
|
context: dict = None,
|
||||||
|
timeoutSeconds: float = None,
|
||||||
|
):
|
||||||
|
|
||||||
|
logger = context['logger'] if context is not None and 'logger' in context else get_ffx_logger()
|
||||||
|
wrappedCommandSequence = getWrappedCommandSequence(commandSequence, context=context)
|
||||||
|
timeoutSeconds = getProcessTimeoutSeconds(context=context, timeoutSeconds=timeoutSeconds)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"executeProcess() cwd=%s timeout=%s command=%s",
|
||||||
|
directory or '.',
|
||||||
|
timeoutSeconds if timeoutSeconds is not None else 'none',
|
||||||
|
formatCommandSequence(wrappedCommandSequence),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
completed = subprocess.run(
|
||||||
|
wrappedCommandSequence,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
cwd=directory,
|
||||||
|
timeout=timeoutSeconds,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
except FileNotFoundError as ex:
|
||||||
|
error = (
|
||||||
|
"Command not found while running "
|
||||||
|
+ f"{formatCommandSequence(wrappedCommandSequence)}: {ex.filename or ex}"
|
||||||
|
)
|
||||||
|
logger.error(error)
|
||||||
|
return '', error, COMMAND_NOT_FOUND_RETURN_CODE
|
||||||
|
except subprocess.TimeoutExpired as ex:
|
||||||
|
stdout = ex.stdout or ''
|
||||||
|
stderr = ex.stderr or ''
|
||||||
|
error = (
|
||||||
|
f"Command timed out after {timeoutSeconds} seconds while running "
|
||||||
|
+ formatCommandSequence(wrappedCommandSequence)
|
||||||
|
)
|
||||||
|
if stderr:
|
||||||
|
error = f"{error}\n{stderr}"
|
||||||
|
logger.error(error)
|
||||||
|
return stdout, error, COMMAND_TIMED_OUT_RETURN_CODE
|
||||||
|
|
||||||
|
if completed.returncode != 0:
|
||||||
|
logger.warning(
|
||||||
|
"executeProcess() rc=%s command=%s",
|
||||||
|
completed.returncode,
|
||||||
|
formatCommandSequence(wrappedCommandSequence),
|
||||||
|
)
|
||||||
|
|
||||||
|
return completed.stdout, completed.stderr, completed.returncode
|
||||||
|
|||||||
52
tests/unit/test_process.py
Normal file
52
tests/unit/test_process.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
SRC_ROOT = Path(__file__).resolve().parents[2] / "src"
|
||||||
|
|
||||||
|
if str(SRC_ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(SRC_ROOT))
|
||||||
|
|
||||||
|
|
||||||
|
from ffx.process import ( # noqa: E402
|
||||||
|
COMMAND_NOT_FOUND_RETURN_CODE,
|
||||||
|
COMMAND_TIMED_OUT_RETURN_CODE,
|
||||||
|
executeProcess,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessTests(unittest.TestCase):
|
||||||
|
def test_execute_process_returns_stdout_for_success(self):
|
||||||
|
out, err, rc = executeProcess(
|
||||||
|
[sys.executable, "-c", "print('hello from process')"]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(0, rc)
|
||||||
|
self.assertEqual("", err)
|
||||||
|
self.assertEqual("hello from process\n", out)
|
||||||
|
|
||||||
|
def test_execute_process_maps_missing_command_to_stable_error(self):
|
||||||
|
out, err, rc = executeProcess(["ffx-command-that-does-not-exist"])
|
||||||
|
|
||||||
|
self.assertEqual("", out)
|
||||||
|
self.assertEqual(COMMAND_NOT_FOUND_RETURN_CODE, rc)
|
||||||
|
self.assertIn("Command not found while running", err)
|
||||||
|
self.assertIn("ffx-command-that-does-not-exist", err)
|
||||||
|
|
||||||
|
def test_execute_process_maps_timeout_to_stable_error(self):
|
||||||
|
out, err, rc = executeProcess(
|
||||||
|
[sys.executable, "-c", "import time; time.sleep(0.2)"],
|
||||||
|
timeoutSeconds=0.05,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual("", out)
|
||||||
|
self.assertEqual(COMMAND_TIMED_OUT_RETURN_CODE, rc)
|
||||||
|
self.assertIn("Command timed out", err)
|
||||||
|
self.assertIn(sys.executable, err)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
|
|
||||||
CONFIG_DIR="${FFX_CONFIG_DIR:-${HOME}/.local/etc}"
|
CONFIG_DIR="${FFX_CONFIG_DIR:-${HOME}/.local/etc}"
|
||||||
CONFIG_FILE="${FFX_CONFIG_FILE:-${CONFIG_DIR}/ffx.json}"
|
CONFIG_FILE="${FFX_CONFIG_FILE:-${CONFIG_DIR}/ffx.json}"
|
||||||
VAR_DIR="${FFX_VAR_DIR:-${HOME}/.local/var/ffx}"
|
VAR_DIR="${FFX_VAR_DIR:-${HOME}/.local/var/ffx}"
|
||||||
@@ -11,6 +9,7 @@ LOG_DIR="${FFX_LOG_DIR:-${HOME}/.local/var/log}"
|
|||||||
DATABASE_FILE="${FFX_DATABASE_FILE:-${VAR_DIR}/ffx.db}"
|
DATABASE_FILE="${FFX_DATABASE_FILE:-${VAR_DIR}/ffx.db}"
|
||||||
|
|
||||||
CHECK_ONLY=0
|
CHECK_ONLY=0
|
||||||
|
WITH_TESTS=0
|
||||||
|
|
||||||
MUTATIONS=0
|
MUTATIONS=0
|
||||||
INSTALL_FAILURES=0
|
INSTALL_FAILURES=0
|
||||||
@@ -33,12 +32,13 @@ fi
|
|||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $(basename "$0") [--check] [--help]
|
Usage: $(basename "$0") [--check] [--with-tests] [--help]
|
||||||
|
|
||||||
Prepare the local FFX development environment for this repository.
|
Prepare the local workstation environment for an already installed FFX bundle.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--check Report readiness only. Do not create, install, or modify.
|
--check Report readiness only. Do not create, install, or modify.
|
||||||
|
--with-tests Include test-related notes while preparing system dependencies and local config.
|
||||||
--help Show this help text.
|
--help Show this help text.
|
||||||
|
|
||||||
Environment overrides:
|
Environment overrides:
|
||||||
@@ -47,6 +47,11 @@ Environment overrides:
|
|||||||
FFX_VAR_DIR Override the default data directory.
|
FFX_VAR_DIR Override the default data directory.
|
||||||
FFX_LOG_DIR Override the default log directory.
|
FFX_LOG_DIR Override the default log directory.
|
||||||
FFX_DATABASE_FILE Override the database path written into a newly seeded config.
|
FFX_DATABASE_FILE Override the database path written into a newly seeded config.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- tools/setup.sh is the first installation step and owns bundle venv setup.
|
||||||
|
- This script is the second step and owns system dependencies plus local config.
|
||||||
|
- Python test packages are installed by tools/setup.sh --with-tests, not here.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +233,16 @@ print_seeded_file_status() {
|
|||||||
report_seeded_component "ffx config" "ffx-config" "optional"
|
report_seeded_component "ffx config" "ffx-config" "optional"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print_test_package_status() {
|
||||||
|
if [ "${WITH_TESTS}" -eq 0 ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Test environment notes:"
|
||||||
|
report_component ok "system test dependencies" "no extra system packages beyond the standard runtime toolchain"
|
||||||
|
report_component ok "Python test packages" "install via tools/setup.sh --with-tests"
|
||||||
|
}
|
||||||
|
|
||||||
detect_package_manager() {
|
detect_package_manager() {
|
||||||
if command_exists apt-get; then
|
if command_exists apt-get; then
|
||||||
printf 'apt-get\n'
|
printf 'apt-get\n'
|
||||||
@@ -380,6 +395,9 @@ parse_args() {
|
|||||||
--check)
|
--check)
|
||||||
CHECK_ONLY=1
|
CHECK_ONLY=1
|
||||||
;;
|
;;
|
||||||
|
--with-tests)
|
||||||
|
WITH_TESTS=1
|
||||||
|
;;
|
||||||
--help|-h)
|
--help|-h)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
@@ -409,10 +427,17 @@ main() {
|
|||||||
echo
|
echo
|
||||||
print_seeded_file_status
|
print_seeded_file_status
|
||||||
|
|
||||||
|
echo
|
||||||
|
print_test_package_status
|
||||||
|
|
||||||
if [ "${CHECK_ONLY}" -eq 0 ]; then
|
if [ "${CHECK_ONLY}" -eq 0 ]; then
|
||||||
seed_default_config
|
seed_default_config
|
||||||
echo
|
echo
|
||||||
|
print_dependency_status
|
||||||
|
echo
|
||||||
print_seeded_file_status
|
print_seeded_file_status
|
||||||
|
echo
|
||||||
|
print_test_package_status
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
@@ -14,6 +14,7 @@ ALIAS_BLOCK_END="# <<< ffx alias <<<"
|
|||||||
ALIAS_LINE="alias ffx=\"${VENV_FFX}\""
|
ALIAS_LINE="alias ffx=\"${VENV_FFX}\""
|
||||||
|
|
||||||
CHECK_ONLY=0
|
CHECK_ONLY=0
|
||||||
|
WITH_TESTS=0
|
||||||
READINESS_FAILURES=0
|
READINESS_FAILURES=0
|
||||||
INSTALL_FAILURES=0
|
INSTALL_FAILURES=0
|
||||||
|
|
||||||
@@ -31,19 +32,25 @@ fi
|
|||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $(basename "$0") [--check] [--help]
|
Usage: $(basename "$0") [--check] [--with-tests] [--help]
|
||||||
|
|
||||||
Prepare the persistent FFX bundle virtualenv at:
|
Prepare the persistent FFX bundle installation at:
|
||||||
${VENV_DIR}
|
${VENV_DIR}
|
||||||
|
|
||||||
Actions:
|
Actions:
|
||||||
- create or reuse ${VENV_DIR}
|
- create or reuse ${VENV_DIR}
|
||||||
- install this repository into the venv with pip --editable
|
- install this repository into the venv with pip --editable
|
||||||
- ensure ${BASHRC_FILE} exposes alias ffx -> ${VENV_FFX}
|
- ensure ${BASHRC_FILE} exposes alias ffx -> ${VENV_FFX}
|
||||||
|
- optionally install Python packages required for modern tests
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--check Report readiness only. Do not create or modify anything.
|
--check Report readiness only. Do not create or modify anything.
|
||||||
--help Show this help text.
|
--with-tests Also install and verify Python packages required for modern tests.
|
||||||
|
--help Show this help text.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- This is the first installation step.
|
||||||
|
- tools/configure_workstation.sh is the second step and configures system dependencies plus local user files.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +107,10 @@ check_venv_ffx() {
|
|||||||
[ -x "${VENV_FFX}" ]
|
[ -x "${VENV_FFX}" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_venv_pytest() {
|
||||||
|
check_venv_dir && "${VENV_PYTHON}" -m pytest --version >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
check_bashrc_file() {
|
check_bashrc_file() {
|
||||||
[ -f "${BASHRC_FILE}" ]
|
[ -f "${BASHRC_FILE}" ]
|
||||||
}
|
}
|
||||||
@@ -136,6 +147,14 @@ detail_venv_ffx() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
detail_venv_pytest() {
|
||||||
|
if check_venv_pytest; then
|
||||||
|
"${VENV_PYTHON}" -m pytest --version 2>/dev/null | head -n 1
|
||||||
|
else
|
||||||
|
printf 'missing pytest in %s' "${VENV_DIR}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
detail_bashrc_file() {
|
detail_bashrc_file() {
|
||||||
if check_bashrc_file; then
|
if check_bashrc_file; then
|
||||||
printf '%s' "${BASHRC_FILE}"
|
printf '%s' "${BASHRC_FILE}"
|
||||||
@@ -186,6 +205,17 @@ print_status_report() {
|
|||||||
READINESS_FAILURES=$((READINESS_FAILURES + 1))
|
READINESS_FAILURES=$((READINESS_FAILURES + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "${WITH_TESTS}" -eq 1 ]; then
|
||||||
|
echo
|
||||||
|
echo "Bundle test package status:"
|
||||||
|
if check_venv_pytest; then
|
||||||
|
report_component ok "bundle pytest" "$(detail_venv_pytest)"
|
||||||
|
else
|
||||||
|
report_component failed "bundle pytest" "$(detail_venv_pytest)"
|
||||||
|
READINESS_FAILURES=$((READINESS_FAILURES + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Shell exposure status:"
|
echo "Shell exposure status:"
|
||||||
if check_bashrc_file; then
|
if check_bashrc_file; then
|
||||||
@@ -220,11 +250,23 @@ ensure_bundle_venv() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf 'Installing FFX package into %s...\n' "${VENV_DIR}"
|
if [ "${WITH_TESTS}" -eq 1 ]; then
|
||||||
if ! "${VENV_PIP}" install --editable "${ROOT_DIR}"; then
|
printf 'Installing FFX package and test extras into %s...\n' "${VENV_DIR}"
|
||||||
printf 'Failed to install FFX package into %s.\n' "${VENV_DIR}" >&2
|
if ! (
|
||||||
INSTALL_FAILURES=$((INSTALL_FAILURES + 1))
|
cd "${ROOT_DIR}" &&
|
||||||
return 1
|
"${VENV_PIP}" install --editable '.[test]'
|
||||||
|
); then
|
||||||
|
printf 'Failed to install FFX package and test extras into %s.\n' "${VENV_DIR}" >&2
|
||||||
|
INSTALL_FAILURES=$((INSTALL_FAILURES + 1))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf 'Installing FFX package into %s...\n' "${VENV_DIR}"
|
||||||
|
if ! "${VENV_PIP}" install --editable "${ROOT_DIR}"; then
|
||||||
|
printf 'Failed to install FFX package into %s.\n' "${VENV_DIR}" >&2
|
||||||
|
INSTALL_FAILURES=$((INSTALL_FAILURES + 1))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -300,6 +342,9 @@ parse_args() {
|
|||||||
--check)
|
--check)
|
||||||
CHECK_ONLY=1
|
CHECK_ONLY=1
|
||||||
;;
|
;;
|
||||||
|
--with-tests)
|
||||||
|
WITH_TESTS=1
|
||||||
|
;;
|
||||||
--help|-h)
|
--help|-h)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
22
tools/test.sh
Executable file
22
tools/test.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
|
||||||
|
PYTHON_BIN="${FFX_PYTHON:-${HOME}/.local/share/ffx.venv/bin/python}"
|
||||||
|
|
||||||
|
if [[ ! -x "${PYTHON_BIN}" ]]; then
|
||||||
|
echo "Missing Python interpreter: ${PYTHON_BIN}" >&2
|
||||||
|
echo "Set FFX_PYTHON to a suitable interpreter if needed." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "${REPO_ROOT}"
|
||||||
|
|
||||||
|
exec "${PYTHON_BIN}" -m pytest \
|
||||||
|
--ignore=tests/legacy \
|
||||||
|
--ignore=tests/support \
|
||||||
|
tests \
|
||||||
|
"$@"
|
||||||
Reference in New Issue
Block a user