"""The licence, and the instructions for installing under it. Both are files a person reads rather than code that runs, which is exactly why they are worth a test: nothing else notices when a licence file goes missing from a package, when the version notice stops naming the terms, or when the install instructions describe a command that no longer exists. """ import re import subprocess import sys from pathlib import Path import pytest import bandsaunter ROOT = Path(__file__).resolve().parent.parent LICENCE = ROOT / "LICENSE" INSTALL = ROOT / "INSTALL.md" README = ROOT / "README.md" # --------------------------------------------------------------------------- # The licence itself # --------------------------------------------------------------------------- def test_the_licence_is_there_and_is_the_gnu_gpl(): assert LICENCE.is_file(), "no LICENSE file" text = LICENCE.read_text() assert "GNU GENERAL PUBLIC LICENSE" in text assert "Version 3, 29 June 2007" in text def test_the_licence_is_the_whole_of_it(): """A truncated licence is not a licence. The GPL ends with the notice it tells you to attach to a program, so if that is present, so is the rest.""" text = LICENCE.read_text() assert "TERMS AND CONDITIONS" in text assert "How to Apply These Terms to Your New Programs" in text assert len(text.splitlines()) > 600 def test_the_packaging_metadata_says_the_same_licence(): body = (ROOT / "pyproject.toml").read_text() assert 'license = {text = "GPL-3.0-or-later"}' in body assert "GNU General Public License v3 or later" in body def test_the_readme_says_which_licence_and_points_at_the_file(): body = README.read_text() assert "GNU General Public License" in body assert "[LICENSE](LICENSE)" in body # --------------------------------------------------------------------------- # What the programs say for themselves # --------------------------------------------------------------------------- @pytest.mark.parametrize("program", ["bandsaunter", "saunterbrowse"]) def test_the_version_notice_names_the_version_and_the_terms(program): notice = bandsaunter.version_notice(program) lines = notice.splitlines() assert lines[0] == f"{program} {bandsaunter.__version__}" assert "Copyright (C)" in notice assert "GNU GPL version 3 or later" in notice assert "NO WARRANTY" in notice def test_the_command_prints_it(): out = subprocess.run([sys.executable, "-m", "bandsaunter", "--version"], capture_output=True, text=True, timeout=60) assert bandsaunter.__version__ in out.stdout assert "GPLv3+" in out.stdout # Four lines under the version, not reflowed into a paragraph by argparse. assert len(out.stdout.strip().splitlines()) == 5 # --------------------------------------------------------------------------- # The manuals # --------------------------------------------------------------------------- @pytest.mark.parametrize("page", ["bandsaunter.1", "saunterbrowse.1"]) def test_both_manuals_have_a_copying_section(page): body = (ROOT / "packaging" / page).read_text() assert ".SH COPYING" in body assert "General\nPublic License" in body or "General Public License" in body # --------------------------------------------------------------------------- # The Debian package carries the licence where policy says it must # --------------------------------------------------------------------------- def test_the_package_ships_the_licence_at_the_path_policy_requires(): script = (ROOT / "packaging" / "build-deb.sh").read_text() assert "usr/share/doc/bandsaunter/copyright" in script assert '"$here/LICENSE"' in script def test_the_package_ships_the_install_instructions_too(): assert 'INSTALL.md' in (ROOT / "packaging" / "build-deb.sh").read_text() # --------------------------------------------------------------------------- # The install instructions # --------------------------------------------------------------------------- def test_the_install_file_exists_and_is_pointed_at_from_the_readme(): assert INSTALL.is_file(), "no INSTALL.md" assert "[INSTALL.md](INSTALL.md)" in README.read_text() def test_the_install_file_covers_both_ways_of_installing(): body = INSTALL.read_text() assert "./packaging/build-deb.sh" in body assert "python3 -m venv" in body assert "pip install ." in body def test_it_warns_about_the_thing_every_debian_user_hits_first(): """pip refuses to install outside a virtual environment on Debian and Fedora. Someone who has not met PEP 668 reads that error as a broken program.""" body = INSTALL.read_text() assert "externally-managed-environment" in body assert "PEP 668" in body def test_it_lists_the_optional_dependencies_and_what_each_one_buys(): body = INSTALL.read_text() for optional in ("espeak-ng", "ffmpeg", "faster-whisper", "vosk", "rtl-sdr", "pyqt6", "openai-whisper", "pocketsphinx"): assert optional in body, optional # What each one buys, and what is lost without it: the two columns are # the point of the table, not the list of names. assert "Gives you" in body and "Without it" in body assert "Optional." in body def test_it_lists_the_required_dependencies_and_who_needs_them(): """Somebody installing from scratch has to be told the whole of it, including the one piece pip cannot bring.""" body = INSTALL.read_text() for required in ("python3-numpy", "python3-scipy", "python3-rich", "python3-yaml", "librtlsdr0"): assert required in body, required assert "Fedora" in body and "Arch" in body def test_it_says_which_dependency_pip_cannot_install(): """librtlsdr is a C library, so no virtual environment brings it and it is the one thing that has to come from the distribution by hand.""" body = INSTALL.read_text() assert "librtlsdr" in body assert "pip` cannot install it" in body or "cannot come from pip" in body assert "dnf install rtl-sdr" in body assert "pacman -S rtl-sdr" in body assert "brew install librtlsdr" in body def test_it_says_what_reaches_a_network_and_where_it_is_cached(): """Nothing is fetched behind anybody's back, and somebody installing this on a metered or air-gapped machine has to be able to see that.""" body = INSTALL.read_text() for source in ("api.adsbdb.com", "hexdb.io", "tile.openstreetmap.org", "overpass-api.de"): assert source in body, source assert "~/.cache/bandsaunter/tiles" in body def test_it_says_how_to_install_the_recogniser_and_the_model(): """The one part with a real download in it, and the part that has to be put in the same environment bandsaunter runs from.""" body = INSTALL.read_text() assert "pip install faster-whisper" in body assert "--user --break-system-packages" in body # the ~/.local layout assert "bandsaunter transcribe --engines" in body # how to check it took assert "Systran/faster-whisper-base.en" in body # what gets downloaded assert "~/.cache/huggingface" in body # and where it lands assert "WhisperModel('base.en'" in body # fetching it on purpose def test_the_model_the_instructions_name_is_the_one_the_program_asks_for(): """The instructions download base.en because that is the default; if the default moves, they are downloading the wrong thing.""" from bandsaunter.config import ScanConfig assert ScanConfig().transcribe_model in INSTALL.read_text() def test_it_names_the_ways_a_model_can_be_supplied_without_a_network(): body = INSTALL.read_text() assert "BANDSAUNTER_MODEL_DIR" in body assert "/usr/share/bandsaunter/models" in body assert "build-repo.sh" in body def test_it_says_where_the_engine_output_switch_is(): """A silent minute on the first transcription reads as a hang.""" assert "BANDSAUNTER_ENGINE_OUTPUT=1" in INSTALL.read_text() def test_the_environment_variables_it_names_are_the_ones_the_code_reads(): from bandsaunter import transcribe as tr body = INSTALL.read_text() for variable in ("BANDSAUNTER_MODEL_DIR", "BANDSAUNTER_ENGINE_OUTPUT"): assert variable in body assert str(tr.MODEL_DIR) in body or "BANDSAUNTER_MODEL_DIR" in body def test_the_settings_it_tells_you_to_change_exist(): from bandsaunter.config import ScanConfig fields = set(ScanConfig().__dict__) told = set(re.findall(r"bandsaunter config ([a-z_]+)=", INSTALL.read_text())) assert told, "the instructions change no settings at all" assert told <= fields, told - fields def test_it_says_what_to_do_when_the_dongle_is_not_found(): body = INSTALL.read_text() assert "dvb_usb_rtl28xxu" in body assert "librtlsdr" in body def test_every_bandsaunter_command_it_tells_you_to_run_exists(): """An instruction to run a command that was renamed is worse than none.""" from bandsaunter.cli import build_parser known = set() for action in build_parser()._subparsers._group_actions: known.update(action.choices) told = set(re.findall(r"^\s*(?:\$ )?bandsaunter ([a-z]+)", INSTALL.read_text(), re.MULTILINE)) assert told, "the instructions run no commands at all" assert told <= known | {"--version"}, told - known def test_the_flags_it_tells_you_to_use_are_real(): from bandsaunter.cli import build_parser parser = build_parser() for line in ("scan -b 2m --simulate", "adsb --simulate --seconds 30", "devices --test", "transcribe --engines"): parser.parse_args(line.split()) # raises SystemExit if not