dnf builddep shadow-utils
```
-An alternative would be to take a look at the CI workflow [file](../../.github/workflows/runner.yml)
+An alternative would be to take a look at the CI workflow
+[file](../../.github/workflows/runner.yml)
and get the package names from there. This has the advantage that it
also includes new dependencies needed for the development version
which might have not been present in the last release.
## Containers
-Alternatively, you can use any of the preconfigured container images builders
-to build and install shadow.
+Alternatively, you can use the preconfigured container builders
+to build, install, and test shadow in an isolated environment.
-You can either generate a single image by running the following command from
-the root folder of the project (i.e. Alpine):
+The container workflow supports two execution modes:
+
+- Unprivileged (default)
+- Privileged (opt-in, required for BTRFS, mounts, and similar tests)
+
+### Single distribution build
```
-ansible-playbook share/ansible/playbook.yml -i share/ansible/inventory.ini -e 'distribution=alpine'
+ansible-playbook share/ansible/playbook.yml \
+ -i share/ansible/inventory.ini \
+ -e 'distribution=alpine'
```
**Note**: you'll need to install ansible to run this automation.
Or generate all of the images with the `container-build.sh` script, as if you
were running some of the CI checks locally:
+### Multiple distributions
+
```
share/container-build.sh
```
+## Privileged builds and tests
+
+Most development and CI scenarios should use the default (unprivileged)
+mode. It is sufficient for validating core functionality and avoids
+unnecessary exposure to elevated capabilities.
+
+Privileged mode is available for tests that require kernel-level
+operations such as BTRFS subvolumes, loop devices, or filesystem mounts.
+The correct topology is selected automatically based on the mode.
+
+To run privileged builds across all distributions:
+
+```bash
+sudo share/container-build.sh --privileged
+```
+
+Or for a single distribution:
+
+```bash
+sudo ansible-playbook share/ansible/playbook.yml \
+ -i share/ansible/inventory.ini \
+ -e "distribution=fedora" \
+ -e "privileged_mode=true"
+```
+
+Privileged containers should be used in disposable environments (for
+example, a VM or a temporary development system). While they are useful
+for certain scenarios, most development workflows do not require them.
+
### Container troubleshooting
When working with containers for testing or development,
that existed when tests ran.
**Container management:**
+If using a privileged container, you will need to be root to execute these commands.
- **List containers**: `docker ps -a` to see all containers and their status.
- **Access container**: `docker exec -it <container-name> bash` to get shell access.
- **Container logs**: `docker logs <container-name>` to view container output.
• **Cross-distribution compatibility**: works on all distributions in CI
(Alpine, Debian, Fedora and openSUSE)
unlike Bash tests which only run on Ubuntu.
+
• **Proper environment management**: ensures clean setup and teardown
with environment restoration even when tests fail.
+
• **Improved test maintainability**: provides a rich, high-level API
that reduces complex test code
and makes tests easier to understand.
+
• **Automated artifact collection**: automatically collects logs and artifacts
when tests finish (even if they fail).
+
• **Flexible infrastructure**: supports both VMs and containers for testing
with local execution capabilities.
+
• **Future extensibility**: enables potential filtering by ticket
or importance and other advanced features.
**For all new contributions,
we recommend using the Python system test framework.**
+## Privilege levels
+
+Python system tests are divided into two categories.
+
+### Unprivileged tests (default)
+
+These tests run with standard container permissions and do not require
+elevated capabilities. They are suitable for CI and should be the
+preferred choice for new test development.
+
+### Privileged tests (opt-in)
+
+These tests require elevated capabilities and may perform operations
+such as:
+
+- Mounting filesystems
+- Creating loop devices
+- Working with BTRFS subvolumes
+- Modifying kernel-visible system state
+
+These capabilities operate at the kernel boundary. If isolation is
+misconfigured, they may affect the host system directly.
+
+Concrete risks include:
+
+- Accessing and modifying any file on the host system through bind
+ mounts
+- Interfering with host services by consuming loop devices
+- Filling host disk space by creating large filesystem images
+- Leaving orphaned mount points that prevent clean system shutdown
+
+Privileged tests are useful when validating functionality that
+fundamentally depends on those capabilities. However, they should be
+introduced only when no reasonable unprivileged alternative exists.
+
+When writing new tests:
+
+- Prefer unprivileged designs.
+- Consider whether the behavior can be validated without mounts or
+ device manipulation.
+- Use privileged tests as a targeted tool, not the default approach.
+
+Privileged tests should be executed **only** inside:
+
+- A disposable virtual machine, or
+- A dedicated privileged container created for testing
+
+They are not intended to run directly on a host system.
+
#### Contribution guidance
The framework is under active development
3. **Missing features**: if you're comfortable implementing missing pytest-mh features,
contributions are encouraged.
+When adding new coverage, prioritize unprivileged test designs.
+Privileged tests should be added only when the feature under test cannot
+be meaningfully validated otherwise.
+
+Review examples in `tests/system/tests/privileged` before introducing
+new privileged scenarios.
+
#### Running tests
##### Environment setup
##### Execution
-Run all Python system tests:
+Run all unprivileged Python system tests:
```bash
pytest --mh-config=mhc.yaml --mh-lazy-ssh -v
```
+Privileged tests are automatically excluded when using `mhc.yaml` because the
+`shadow-privileged` topology is not provisioned. No additional flags are needed.
+
Run tests in a file:
```bash
pytest --mh-config=mhc.yaml --mh-lazy-ssh -v -k test_useradd__add_user
```
+To run privileged tests, ensure you are **inside a disposable VM or a privileged
+container**, then run:
+
+```bash
+pytest --mh-config=mhc-privileged.yaml --mh-lazy-ssh -v
+```
+
**Command options explained:**
- `--mh-config=mhc.yaml`: specifies the multihost configuration file
- `--mh-lazy-ssh`: enables lazy SSH connections for better performance
- Use containers or VMs to avoid system state pollution.
- Ensure proper cleanup in the framework between test runs.
- Verify container/VM has necessary privileges for user/group operations.
+- Running privileged tests on the host system, even for local development is a **bad idea**.
For additional information about the testing framework,
see the [pytest-mh documentation](https://pytest-mh.readthedocs.io).