Skip to content

Commit 40c11bb

Browse files
authored
Merge pull request #1 from LC-Linkous/tests_and_polish
Tests and polish
2 parents e2c685f + cf569f0 commit 40c11bb

62 files changed

Lines changed: 1891 additions & 435 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Bug report
2+
description: Something isn't working as documented
3+
labels: ["bug"]
4+
body:
5+
- type: markdown
6+
attributes:
7+
value: |
8+
Thanks for the report. This library wraps the `hackrf-tools` binaries, so
9+
please include tool/OS details — most issues are environment-specific.
10+
11+
If this involves **transmitting**, do not include instructions that would
12+
help someone operate illegally; describe the software behavior only.
13+
- type: textarea
14+
id: what-happened
15+
attributes:
16+
label: What happened
17+
description: What did you expect, and what happened instead?
18+
validations:
19+
required: true
20+
- type: textarea
21+
id: repro
22+
attributes:
23+
label: Steps to reproduce
24+
description: The exact command or minimal code. Include `print_cmd=True` / `--print-cmd` output if relevant.
25+
render: shell
26+
validations:
27+
required: true
28+
- type: input
29+
id: os
30+
attributes:
31+
label: Operating system
32+
placeholder: "e.g. Windows 11 23H2 / Ubuntu 24.04 / macOS 14"
33+
validations:
34+
required: true
35+
- type: input
36+
id: python
37+
attributes:
38+
label: Python version
39+
placeholder: "output of: python --version"
40+
validations:
41+
required: true
42+
- type: input
43+
id: hackrfpy
44+
attributes:
45+
label: hackrfpy version
46+
placeholder: "output of: pip show hackrfpy | grep Version"
47+
validations:
48+
required: true
49+
- type: textarea
50+
id: tools
51+
attributes:
52+
label: hackrf-tools / firmware version
53+
description: Paste the output of `hackrf_info` (redact serial numbers if you wish).
54+
render: shell
55+
validations:
56+
required: false
57+
- type: dropdown
58+
id: hardware
59+
attributes:
60+
label: Was a HackRF board connected?
61+
options:
62+
- "Yes, a real board was connected"
63+
- "No, this reproduces without hardware"
64+
- "Not sure"
65+
validations:
66+
required: true
67+
- type: textarea
68+
id: logs
69+
attributes:
70+
label: Logs / traceback
71+
description: Full traceback or stderr output. Run with `verbose=True` / `-v` if possible.
72+
render: shell

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
blank_issues_enabled: false
2+
contact_links:
3+
- name: Official HackRF documentation
4+
url: https://hackrf.readthedocs.io/
5+
about: Authoritative reference for device behavior, valid ranges, and TX safety. Check here first.
6+
- name: HackRF firmware & tools (Great Scott Gadgets)
7+
url: https://github.com/greatscottgadgets/hackrf
8+
about: Report firmware, libhackrf, or hackrf-tools issues upstream — not here.
9+
- name: Question / usage help
10+
url: https://github.com/LC-Linkous/hackRF_python/discussions
11+
about: For "how do I..." questions, open a Discussion rather than an issue.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Feature request
2+
description: Suggest a new capability or improvement
3+
labels: ["enhancement"]
4+
body:
5+
- type: markdown
6+
attributes:
7+
value: |
8+
Before filing: **signal processing (demod, FFTs, waterfalls) is out of
9+
scope** for this repo — it stops at delivering `complex64`. See
10+
`project_summary.md` for the split with the planned DSP project.
11+
- type: textarea
12+
id: problem
13+
attributes:
14+
label: Problem / use case
15+
description: What are you trying to do that the library doesn't currently support?
16+
validations:
17+
required: true
18+
- type: textarea
19+
id: proposal
20+
attributes:
21+
label: Proposed solution
22+
description: What would the method / CLI command / behavior look like? A sketch of the signature is helpful.
23+
validations:
24+
required: false
25+
- type: dropdown
26+
id: area
27+
attributes:
28+
label: Area
29+
options:
30+
- Receive / capture
31+
- Sweep
32+
- Transmit
33+
- Info / preflight / detect
34+
- Device management (clock/operacake/spiflash/debug)
35+
- Decoding / file helpers / SigMF
36+
- CLI
37+
- Docs / examples
38+
- Other
39+
validations:
40+
required: true
41+
- type: textarea
42+
id: alternatives
43+
attributes:
44+
label: Alternatives considered
45+
description: Existing workarounds, or why the passthrough methods (`clock`/`operacake`/`debug`) don't cover it.
46+
validations:
47+
required: false

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!-- Thanks for contributing to hackrfpy. Keep PRs focused: one logical change. -->
2+
3+
## Summary
4+
5+
<!-- What does this change and why? Link the issue it closes. -->
6+
Closes #
7+
8+
## Type of change
9+
10+
- [ ] Bug fix
11+
- [ ] New command / feature
12+
- [ ] Docs / examples
13+
- [ ] Test / CI / tooling
14+
- [ ] Refactor (no behavior change)
15+
16+
## How it was tested
17+
18+
- [ ] `uv run pytest -m "not hardware"` passes locally
19+
- [ ] `uv run ruff check .` and `uv run ruff format --check .` pass
20+
- [ ] Tested against **real hardware** (describe below)
21+
- [ ] Tested with **stubs only** (no board)
22+
23+
<!-- If real hardware: which board, firmware, and hackrf-tools version? -->
24+
25+
## Checklist for new/changed commands
26+
27+
- [ ] Binary invocation goes through `_run(argv, mode=...)` (no direct subprocess)
28+
- [ ] Added a command-construction test asserting the exact `hackrf_*` argv via `print_cmd`
29+
- [ ] Added a validation-error test asserting **nothing runs** on bad input
30+
- [ ] Any new envelope limits live in `constants.py`, not hard-coded in the method
31+
- [ ] Process-lifecycle changes covered with the cross-platform stub factory (`tests/conftest.py`)
32+
- [ ] README Method Reference updated; example added under `examples/` if it's a public method
33+
34+
## Notes for reviewers
35+
36+
<!-- Anything tricky, follow-ups intentionally left out, etc. -->

.github/workflows/tests.yml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
workflow_dispatch:
8+
9+
# Cancel superseded runs on the same ref (saves CI minutes on rapid pushes).
10+
concurrency:
11+
group: tests-${{ github.ref }}
12+
cancel-in-progress: true
13+
14+
defaults:
15+
run:
16+
# The installable project lives in the hackrfpy/ subdirectory (the one with
17+
# pyproject.toml + the pytest config + the `hardware` marker).
18+
working-directory: hackrfpy
19+
20+
jobs:
21+
test:
22+
name: pytest (${{ matrix.os }}, py${{ matrix.python-version }})
23+
runs-on: ${{ matrix.os }}
24+
strategy:
25+
fail-fast: false
26+
matrix:
27+
# windows-latest is first because it is the platform this library
28+
# targets; the cross-platform lifecycle stubs must pass there.
29+
os: [windows-latest, ubuntu-latest, macos-latest]
30+
python-version: ["3.11", "3.12", "3.13"]
31+
32+
steps:
33+
- uses: actions/checkout@v4
34+
35+
- name: Install uv
36+
uses: astral-sh/setup-uv@v5
37+
with:
38+
enable-cache: true
39+
python-version: ${{ matrix.python-version }}
40+
41+
- name: Sync environment (numpy + dev group, editable install)
42+
run: uv sync
43+
44+
- name: Run test suite (hardware tests deselected)
45+
run: uv run pytest -m "not hardware" --cov=hackrfpy --cov-report=xml --cov-report=term-missing
46+
47+
- name: Upload coverage to Codecov
48+
# Optional: only runs once linked at https://codecov.io. Tokenless for
49+
# public repos. Safe to leave in before linking - it just no-ops/soft-fails.
50+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
51+
uses: codecov/codecov-action@v5
52+
with:
53+
files: hackrfpy/coverage.xml
54+
fail_ci_if_error: false
55+
56+
lint:
57+
name: ruff + mypy
58+
runs-on: ubuntu-latest
59+
steps:
60+
- uses: actions/checkout@v4
61+
62+
- name: Install uv
63+
uses: astral-sh/setup-uv@v5
64+
with:
65+
enable-cache: true
66+
python-version: "3.12"
67+
68+
- name: Sync environment
69+
run: uv sync
70+
71+
- name: Ruff lint
72+
run: uv run ruff check .
73+
74+
- name: Mypy
75+
# Blocking. The package ships a py.typed marker, which promises
76+
# downstream type-checkers that these annotations are real; this gate is
77+
# what keeps that promise true.
78+
run: uv run mypy

CHANGELOG.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Changelog
2+
3+
All notable changes to hackrfpy are documented here.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
Work on the current development branch. Entries move to a versioned section on
11+
release.
12+
13+
### Added
14+
- Type annotations across the entire shipped package, and `mypy` promoted to a
15+
blocking CI gate (`disallow_untyped_defs`). The package has always shipped a
16+
`py.typed` marker, which tells downstream type-checkers the inline annotations
17+
are real; previously only 1 of ~114 definitions was annotated, so that marker
18+
was a false promise. It is now enforced.
19+
- `HostOps` protocol (`_host.py`) making the contract between `HackRF` and the
20+
command mixins explicit and type-checkable. The mixins call ~27 methods on
21+
`self` that live on the host class; that dependency was previously implicit in
22+
a comment. Runtime composition and MRO are unchanged.
23+
- Continuous integration: GitHub Actions workflow running the test suite across
24+
Windows, Linux, and macOS on Python 3.11-3.13, plus a ruff + mypy lint job.
25+
- CLI test suite covering argument parsing, the mode state file, preset
26+
resolution, `info`/`detect`/`doctor`, `rx`/`tx`/`sweep` dispatch, and the
27+
module-level exit-code mapping. Overall coverage raised from 73% to 86%
28+
(`cli.py` from 12% to ~98%).
29+
- Community health files: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`,
30+
`SECURITY.md`, issue templates, and a pull request template.
31+
32+
### Changed
33+
- Library diagnostics now go through the standard `logging` module instead of
34+
`print()`. Records are emitted on the `hackrfpy` logger: warnings at
35+
`WARNING`, verbose progress messages at `INFO`. A consumer can now route,
36+
reformat, or silence hackrfpy's output like any other library.
37+
- **Diagnostics no longer touch stdout.** `print_message` previously wrote to
38+
stdout, so `hrf sweep -v > out.csv` prepended `[*] mode: rx` into the CSV.
39+
stdout is now reserved for data (sweep CSV, IQ on `-r -`) and explicitly
40+
requested output (`--print-cmd`). Diagnostics go to stderr.
41+
- Console behavior is unchanged for scripts and the CLI: warnings still appear
42+
with no setup at all, and `verbose=True` / `-v` still prints progress. If the
43+
host application has configured logging, hackrfpy stays out of the way and
44+
simply propagates records to it.
45+
- Packaging classifiers: removed the contradictory `Operating System ::
46+
OS Independent` (the library shells out to the Windows `hackrf-tools`
47+
binaries and Linux/macOS operation is unverified), leaving `Operating System
48+
:: Microsoft :: Windows`. Development status raised from `3 - Alpha` to
49+
`5 - Production/Stable` to match the 1.0.0 release.
50+
- Ruff configuration added (`line-length = 100`, `select = ["E", "F", "W"]`), and
51+
the codebase made lint-clean so the CI lint job is meaningful.
52+
53+
### Fixed
54+
- The `atexit` backstop never reaped an orphaned `PersistentReceiver`.
55+
`PersistentReceiver` registers itself in the live-handle registry, whose
56+
shutdown hook calls `if h.is_alive(): h.stop()` -- but `is_alive()` did not
57+
exist on the class, so the resulting `AttributeError` was swallowed by the
58+
hook's bare `except Exception` and the receiver was silently left running.
59+
Found by the typing pass; `is_alive()` added and pinned with a regression test.
60+
- Reading from a closed `PersistentReceiver` raised a bare
61+
`TypeError: 'NoneType' object is not iterable` instead of a typed error; it now
62+
raises `HackRFDeviceError` with an actionable message.
63+
- `sweep_stream(..., print_cmd=True)` would hand `StreamCtx` a `None` and crash;
64+
it now raises `HackRFValueError` pointing at `sweep(..., print_cmd=True)`.
65+
- Unbounded memory growth on long-lived RX/TX handles: `_Process` drained child
66+
stdout/stderr into lists that were never trimmed, so an open-ended capture or
67+
repeat transmit retained every per-second stats line for the life of the
68+
process. Both drain paths now share a 64 KB cap (`_DRAIN_CAP`).
69+
- `transmit()` now verifies the source file exists *before* arming TX and
70+
spawning `hackrf_transfer`, raising `HackRFEnvironmentError` instead of
71+
failing with a generic non-zero exit from the tool. The TX-mode gate is still
72+
checked first, and `print_cmd` dry runs skip the check.
73+
- SigMF sidecars now declare the `hackrf` namespace in `core:extensions`.
74+
Previously the `hackrf:*` gain keys were written without declaring the
75+
extension, which strict SigMF validators reject.
76+
- Removed unreachable dead code in `core.py` (an orphaned `return load_iq(...)`
77+
after a `return`, referencing three undefined names).
78+
- The test suite no longer writes a stray `capture.sigmf-meta` into the working
79+
directory on every run.
80+
- `CITATION.cff` version and release date corrected to `1.0.0` / `2026-06-16`,
81+
aligning the citation metadata with `pyproject.toml` and the tagged release.
82+
83+
<!--
84+
Roadmap for this branch (append entries above as each lands):
85+
- Added: type annotations across the public API; mypy promoted to a CI gate
86+
(makes the shipped py.typed marker accurate).
87+
- CI: --cov-fail-under=85; confirm the Windows leg exercises the CTRL_BREAK
88+
lifecycle path.
89+
- Docs: note that a single HackRF instance is not safe to share across
90+
threads (deferred pending broader testing).
91+
-->
92+
93+
## [1.0.0] - 2026-06-16
94+
95+
Initial public release.
96+
97+
### Added
98+
- Python wrapper and non-GUI command-line tool (`hrf`) for the HackRF One,
99+
driving the `hackrf-tools` binaries directly (no libhackrf bindings).
100+
- Receive: bounded and streaming capture, with decode to `complex64` and
101+
self-describing SigMF (`.sigmf-meta`) sidecars.
102+
- Spectrum sweep with streaming CSV parsing.
103+
- Transmit: file playback and constant-wave source, guarded by an explicit
104+
operating-mode gate (transmit refuses unless the instance is switched to TX
105+
mode, which emits a one-time safety banner) and a TX gain ceiling.
106+
- Device management passthroughs (clock, Opera Cake, SPI flash, debug) and
107+
preflight `info` / `detect` / `doctor` helpers.
108+
- Cross-platform process lifecycle handling with best-effort clean interrupt
109+
(SIGINT on POSIX, CTRL_BREAK on Windows) and an atexit backstop for live
110+
RX/TX handles.
111+
- Validation layer with hard-range checks, gain snapping to real device steps,
112+
and a parameter readback (`last_params`) reflecting the values actually used.
113+
114+
[Unreleased]: https://github.com/LC-Linkous/hackRF_python/compare/V1.0.0...HEAD
115+
[1.0.0]: https://github.com/LC-Linkous/hackRF_python/releases/tag/V1.0.0

CITATION.cff

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ authors:
1313
- family-names: "Linkous"
1414
given-names: "Lauren"
1515
orcid: "https://orcid.org/0000-0001-9945-5125"
16-
version: "0.1.0"
17-
date-released: "2026-06-15"
16+
version: "1.0.1"
17+
date-released: "2026-06-16"
1818
license: GPL-2.0-or-later
1919
repository-code: "https://github.com/LC-Linkous/hackRF_python"
2020
url: "https://github.com/LC-Linkous/hackRF_python"
@@ -26,4 +26,4 @@ keywords:
2626
- rf
2727
- spectrum
2828
- sigmf
29-
- instrumentation
29+
- instrumentation

0 commit comments

Comments
 (0)