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
34 changes: 34 additions & 0 deletions docs/development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,40 @@ follow their `guide about avoiding ruining git blame`_:
.. _black: https://black.readthedocs.io/
.. _guide about avoiding ruining git blame: https://black.readthedocs.io/en/stable/guides/introducing_black_to_your_project.html#avoiding-ruining-git-blame

Using assert
------------

Use ``assert`` only for conditions that can be false only if borg itself has a bug:
internal invariants, pre- and postconditions between borg's own functions, types of
arguments passed by borg's own callers and unreachable branches. A traceback is the
wanted outcome there.

Anything that can be false without a bug in borg must be checked with
``if ...: raise ...``, e.g.:

- repository/store objects (authenticated does not mean well-formed),
- local cache, config and key files,
- legacy borg 1.x data,
- responses from a remote server,
- user input (command line arguments, environment variables, patterns, paths),
- results from the OS and the filesystem.

Prefer borg's own exception classes there (``IntegrityError``, ``Error`` subclasses with an
exit code), so that users get an error message and a meaningful return code instead of a
traceback. Raise ``ValueError`` or ``TypeError`` only where the callers catch them.

Validate such data at the boundary where it enters borg (where it is parsed). Deeper helpers
that only get the validated data may then keep using ``assert`` for their invariants.

Never use ``assert`` for security decisions (e.g. path sanitizing, authentication, permission
checks) and never put side effects into an ``assert`` expression.

Borg refuses to start if assertions are disabled (``python -O`` or ``PYTHONOPTIMIZE``). The
guard for that is at the top of ``src/borg/archiver/__init__.py``, so it only covers code
paths that import ``borg.archiver``.

The test suite uses plain pytest-style ``assert`` statements.

Continuous Integration
----------------------

Expand Down
25 changes: 25 additions & 0 deletions src/borg/testsuite/archiver/version_cmd_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import json
import os
import subprocess
import sys

import pytest

from borg import __version__
from borg.helpers import EXIT_ERROR
from borg.version import format_version, parse_version
from borg.testsuite.archiver import Archiver, cmd

Expand Down Expand Up @@ -31,3 +38,21 @@ def test_from_borg1_default_off():
args = archiver.parse_args(["-r", "/mnt/backup", "version"])
assert args.func == archiver.do_version
assert args.v1_legacy is False


@pytest.mark.parametrize(
"python_opts, pythonoptimize", [(["-O"], None), ([], "1")], ids=["option-O", "env-PYTHONOPTIMIZE"]
)
def test_refuses_to_run_without_assertions(python_opts, pythonoptimize):
"""borg must not run with assertions disabled (see the guard at the top of borg.archiver)."""
env = os.environ.copy()
env.pop("PYTHONOPTIMIZE", None)
if pythonoptimize is not None:
env["PYTHONOPTIMIZE"] = pythonoptimize
python = sys.executable or "python3"
result = subprocess.run(
[python, *python_opts, "-m", "borg", "--version"], env=env, capture_output=True, text=True, timeout=60
)
assert result.returncode == EXIT_ERROR
assert "Borg requires working assertions" in result.stderr
assert result.stdout == ""
Loading