Phase B2 — Conventions opérationnelles
Document de setup phase 0 — Version 1.0
Vue d’ensemble
La Phase B2 couvre les conventions qu’un contributeur rencontre à chaque PR : comment tester, comment gérer les erreurs en couches supérieures, comment formater les commits et les pull requests.
Section 1 : Conventions de tests
Organisation physique
Trois types de tests coexistent dans Air :
Tests unitaires : dans le même fichier que le code testé, dans un module #[cfg(test)] mod tests. Testent les fonctions privées et les détails d’implémentation.
Tests d’intégration de crate : dans le dossier tests/ à côté de src/. Testent l’API publique.
Tests cross-crate : dans le dossier tests/ à la racine du workspace. Testent les interactions entre plusieurs crates Air.
Nommage des tests
Convention subject_action_expectation.
#![allow(unused)]
fn main() {
#[test]
fn open_existing_file_returns_owned_fd() { /* ... */ }
#[test]
fn open_nonexistent_file_returns_enoent() { /* ... */ }
}
Catégories de tests à couvrir
Pour les couches 0 et 1 (Principe 1 : test exhaustif), chaque fonction publique a au minimum :
- Test du cas nominal.
- Tests des chemins d’erreur (un par variant documenté).
- Tests des limites.
- Tests des invariants.
Pour les fonctions complexes, s’ajoutent :
- Property-based tests (via
proptestouquickcheck). - Tests adversariaux.
- Tests de concurrence (via
loomsi nécessaire). - Fuzzing via
cargo-fuzz.
Harnais spécifiques
Harnais subprocess : pour les tests qui modifient l’état du processus (seccomp, prctl, etc.). Fork un sous-processus, exécute la fonction, propage le résultat.
Harnais tempdir : pour les tests filesystem. Chaque test obtient son propre dossier temporaire.
Harnais io_uring_test_ring : pour les tests io_uring. Crée un ring de test, gère le cleanup.
Harnais signal_isolation : pour les tests qui manipulent les signaux. Restaure le state après.
Tests qui demandent des privilèges
#![allow(unused)]
fn main() {
#[test]
#[cfg_attr(not(privileged_test), ignore = "requires CAP_SYS_ADMIN")]
fn unshare_creates_new_namespace() { /* ... */ }
}
Le CI exécute ces tests dans un environnement privilégié séparé.
Tests bench-comme
Outillage : criterion. Les benchmarks vivent dans benches/. Le CI les exécute sur les machines de référence (ADR-014) et détecte les régressions :
- Augmentation > 10% : alerte.
- Augmentation > 25% : échec CI.
Couverture de tests
100% sur les couches 0 et 1. Outillage : cargo-tarpaulin ou cargo-llvm-cov.
Une baisse de couverture sur les couches 0 et 1 fait échouer le CI.
Section 2 : Conventions de gestion des erreurs en couches supérieures
Application de l’ADR-019
Couche 0 : Errno minimal. Couches 1+ : type Error enrichi par crate, via thiserror.
Définition d’un type Error par crate
#![allow(unused)]
fn main() {
use thiserror::Error;
use air_sys_types::Errno;
#[derive(Debug, Error)]
pub enum IoError {
#[error("syscall failed: {source}")]
Syscall {
#[from]
source: Errno,
},
#[error("io_uring submission failed: queue full")]
SubmissionQueueFull,
#[error("invalid operation parameters: {reason}")]
InvalidParameters { reason: &'static str },
#[error("operation not supported on this kernel: {0:?}")]
OperationNotSupported(IoUringOpcode),
#[error("internal runtime error: {0}")]
Internal(InternalError),
}
}
Conventions :
- Un variant par catégorie d’erreur logique.
#[from]pour les conversions automatiques.- Messages d’erreur informatifs.
- Pas de
Stringdans les messages sauf nécessité (préférer&'static str).
Attachement de contexte
Le contexte est attaché au moment de la propagation :
#![allow(unused)]
fn main() {
fn open_config(path: &Path) -> Result<Config, ConfigError> {
let fd = openat2(DirFd::Cwd, path.as_cstr(), OpenHow::read_only())
.map_err(|errno| ConfigError::OpenFailed {
path: path.to_path_buf(),
source: errno,
})?;
// ...
}
}
Hiérarchie des erreurs
Les erreurs se propagent en chaîne via source() :
#![allow(unused)]
fn main() {
#[derive(Debug, Error)]
pub enum TransportError {
#[error("io error: {0}")]
Io(#[from] air_runtime_io::IoError),
#[error("connection refused by peer")]
ConnectionRefused,
#[error("invalid wire format: {reason}")]
ProtocolViolation { reason: &'static str },
}
}
Politique sur les panic
Panic acceptable : violations d’invariants internes (bugs Air).
Panic inacceptable : input utilisateur invalide, condition normale d’erreur, erreur kernel.
Documentation des erreurs
La section # Errors de chaque fonction publique liste les variants d’erreur possibles avec leur contexte.
Section 3 : Templates de PR et de commits
Format des messages de commit
Variation simplifiée de Conventional Commits :
<type>(<scope>): <short description>
<optional body>
<optional footer>
Types reconnus : feat, fix, docs, style, refactor, perf, test, chore, adr.
Scope : nom de la crate ou du module touché.
Description courte : mode impératif, présent, sans point final, minuscule.
Exemple :
feat(air-sys-syscall): add pidfd_open wrapper
Implements the pidfd_open syscall as part of the process management
family. Includes RAII handle (PidFd), error mapping, and full test
coverage including post-mortem usage.
Resolves: AIR-127
Pas de commits “wip” ou “fix typo”
Les commits sont des unités logiques cohérentes. Squashés ou réordonnés avant le merge.
Template de PR
## Summary
<!-- One or two sentences describing what this PR does. -->
## Motivation
<!-- Why is this change needed? Link to issues or RFCs. -->
## Changes
-
## Testing
-
## Documentation
-
## Dependencies
<!-- Did this PR add, remove, or modify dependencies?
Link to DEPENDENCIES.md changes per ADR-024. -->
## Breaking changes
<!-- Note that breaking changes on air-stable symbols
require a major version bump per ADR-012. -->
## Engineering Principles checklist
- [ ] Test coverage maintained (100% for layers 0 and 1, > 90% elsewhere)
- [ ] Defensive arithmetic conventions followed
- [ ] String/buffer/encoding handling explicit
- [ ] Inputs validated upfront, invariants documented
- [ ] No unsafe code added without SAFETY comments and review
- [ ] If new dependency added: DEPENDENCIES.md updated per ADR-024
- [ ] If new public API: rustdoc complete
- [ ] ABI stability respected on `air-stable` symbols
## Related ADRs
<!-- List any ADRs that motivated or relate to this change. -->
Workflow de revue
- Auto-vérification par le contributeur (checklist).
- Revue par CI automatique.
- Revue humaine par un mainteneur.
- Revue spécialisée si nécessaire :
unsafemodifié : revue par mainteneur senior systèmes.- ADR ajouté : revue par BDFL/TC.
- ABI publique modifiée : revue ABI dédiée.
- Approbation et merge.
Pas de merge sans approbation explicite. Pas d’auto-merge.
Préférer squash-and-merge pour PR avec plusieurs commits intermédiaires.
Branches
Trunk-based development.
- Une seule branche stable :
main. - Les PRs sont mergées dans
maindirectement. - Pas de branches
develop,release, ou autres. - Les releases sont des tags Git sur des commits de
main. - Les backports vers les versions majeures supportées (cf. ADR-012) se font sur des branches
release/N.0.
Section 4 : Documents de référence à créer
CONTRIBUTING.md: guide général.STYLE_GUIDE.md: conventions de code (Phase B1).TESTING_GUIDE.md: conventions de tests (Phase B2 section 1).ERROR_HANDLING.md: conventions d’erreurs (Phase B2 section 2).COMMIT_CONVENTIONS.md: format des messages de commit.PR_REVIEW_GUIDE.md: workflow de revue.
Récapitulatif Phase B2
| Domaine | Convention principale |
|---|---|
| Tests unitaires | Dans src/, #[cfg(test)] mod tests |
| Tests intégration | Dans tests/ au niveau crate |
| Tests cross-crate | Dans tests/ au niveau workspace |
| Harnais spéciaux | subprocess, tempdir, io_uring_test_ring, etc. |
| Erreurs couches sup | Type par crate via thiserror, contexte au point d’attachement |
| Format commits | Variation Conventional Commits, scope par crate |
| Template PR | Checklist Principes d’ingénierie obligatoire |
| Branches | Trunk-based, main unique |
| Documents | Plusieurs guides à la racine |
Licence du document : MPL 2.0 Statut : Document de setup phase 0.