Adds signed commit policy and devel manual
This commit is contained in:
3 files changed
+381
-11
No files matched your search
+309
@@ -0,0 +1,309 @@
|
|||||||
|
# Entwicklerhinweise: Anforderungen bei Push und Pull Request
|
||||||
|
|
||||||
|
Dieses Repository verwendet Jenkins-basierte PR- und Branch-Policies.
|
||||||
|
Ziel ist, dass Code bereits **vor dem Merge** bestimmte Qualitätsanforderungen erfüllt.
|
||||||
|
|
||||||
|
Diese Datei beschreibt, **was bei einem Push erwartet wird**, **was in Jenkins geprüft wird** und **wie du Fehler lokal vermeiden kannst**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Überblick
|
||||||
|
|
||||||
|
Bei Pushes und Pull Requests werden automatisierte Policies ausgeführt.
|
||||||
|
|
||||||
|
Je nach Build-Typ gelten leicht unterschiedliche Vergleichsregeln:
|
||||||
|
|
||||||
|
- **bei Pull Requests** wird gegen den Zielbranch verglichen, z. B. `origin/main`
|
||||||
|
- **bei normalen Branch-Builds** wird auf eine lokale Vergleichsbasis zurückgefallen, z. B. `HEAD~1`, falls kein Zielbranch-Ref verfügbar ist
|
||||||
|
|
||||||
|
Die eigentlichen Qualitätsanforderungen bleiben gleich.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Was beim Push / PR geprüft wird
|
||||||
|
|
||||||
|
### 1. `.clang-format` muss vorhanden sein
|
||||||
|
Im Repository-Root muss eine Datei `.clang-format` liegen.
|
||||||
|
|
||||||
|
Ohne diese Datei schlägt der Policy-Check fehl.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Geänderte C/C++-Dateien müssen korrekt formatiert sein
|
||||||
|
Für geänderte C/C++-Dateien wird `clang-format` geprüft.
|
||||||
|
|
||||||
|
Betroffen sind insbesondere Dateien mit diesen Endungen:
|
||||||
|
|
||||||
|
- `.c`
|
||||||
|
- `.cc`
|
||||||
|
- `.cpp`
|
||||||
|
- `.cxx`
|
||||||
|
- `.h`
|
||||||
|
- `.hh`
|
||||||
|
- `.hpp`
|
||||||
|
- `.hxx`
|
||||||
|
|
||||||
|
Wenn eine Datei nicht korrekt formatiert ist, schlägt der Build fehl.
|
||||||
|
|
||||||
|
#### Lokal prüfen
|
||||||
|
```bash
|
||||||
|
clang-format --dry-run --Werror <datei>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lokal automatisch formatieren
|
||||||
|
```bash
|
||||||
|
clang-format -i <datei>
|
||||||
|
```
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
```bash
|
||||||
|
clang-format -i main.cpp tests/test_main.cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Zu geänderten C/C++-Dateien muss ein passender Test existieren
|
||||||
|
Wenn du eine C/C++-Datei änderst, muss ein passender Test vorhanden sein.
|
||||||
|
|
||||||
|
Es wird nach Tests in diesen Verzeichnissen gesucht:
|
||||||
|
|
||||||
|
- `tests/`
|
||||||
|
- `test/`
|
||||||
|
- `qt6-after/qt6-migrated-benchmark/tests/`
|
||||||
|
|
||||||
|
Unterstützte typische Testnamen sind zum Beispiel:
|
||||||
|
|
||||||
|
- `test_<name>.cpp`
|
||||||
|
- `<name>_test.cpp`
|
||||||
|
- `<name>test.cpp`
|
||||||
|
- `<name>gtest.cpp`
|
||||||
|
|
||||||
|
Zusätzlich erkennt die Policy auch ähnliche Dateinamen heuristisch, solange `test` oder `gtest` im Dateinamen vorkommt.
|
||||||
|
|
||||||
|
#### Beispiel
|
||||||
|
Wenn du `main.cpp` änderst, ist z. B. passend:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tests/test_main.cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
Wenn kein passender Test gefunden wird, schlägt der Build fehl.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Nur gültig SSH-signierte Commits sind erlaubt
|
||||||
|
Neue Commits im geprüften Bereich müssen gültig **mit SSH signiert** sein.
|
||||||
|
|
||||||
|
Die Jenkins-Policy prüft Commit-Signaturen gegen eine erlaubte Signiererdatei:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ci/allowed_signers
|
||||||
|
```
|
||||||
|
|
||||||
|
Wenn ein Commit:
|
||||||
|
|
||||||
|
- nicht signiert ist
|
||||||
|
- nicht per SSH signiert ist
|
||||||
|
- mit einem nicht erlaubten SSH-Schlüssel signiert ist
|
||||||
|
|
||||||
|
dann schlägt der Build fehl.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SSH-signierte Commits einrichten
|
||||||
|
|
||||||
|
### 1. SSH-Signing-Key erzeugen
|
||||||
|
Falls du noch keinen dedizierten Signing-Key hast:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_signing -C "dein.name@example.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Git für SSH-Signing konfigurieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config --global gpg.format ssh
|
||||||
|
git config --global user.signingkey ~/.ssh/id_ed25519_signing.pub
|
||||||
|
git config --global commit.gpgsign true
|
||||||
|
```
|
||||||
|
|
||||||
|
Damit werden neue Commits automatisch mit deinem SSH-Key signiert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Öffentlichen Schlüssel zur Allowlist hinzufügen
|
||||||
|
Dein öffentlicher Schlüssel muss in `ci/allowed_signers` eingetragen sein.
|
||||||
|
|
||||||
|
Format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<principal> <public-key>
|
||||||
|
```
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
|
||||||
|
```text
|
||||||
|
max.mustermann@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
|
||||||
|
```
|
||||||
|
|
||||||
|
Ohne Eintrag in `ci/allowed_signers` wird dein signierter Commit in Jenkins trotzdem abgelehnt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Optional: lokale Verifikation aktivieren
|
||||||
|
Wenn du lokal dieselbe Vertrauenskette wie Jenkins verwenden willst:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.config/git
|
||||||
|
cp ci/allowed_signers ~/.config/git/allowed_signers
|
||||||
|
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Signierten Commit erstellen
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -S -m "Meine Änderung"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
Wenn `commit.gpgsign=true` gesetzt ist, signiert Git Commits in der Regel automatisch.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Signatur lokal prüfen
|
||||||
|
```bash
|
||||||
|
git log --show-signature -1
|
||||||
|
git verify-commit HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lokaler Minimal-Check vor dem Push
|
||||||
|
|
||||||
|
Vor dem Push solltest du mindestens diese Schritte ausführen:
|
||||||
|
|
||||||
|
### Format anwenden
|
||||||
|
```bash
|
||||||
|
clang-format -i main.cpp tests/test_main.cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Policy lokal ausführen
|
||||||
|
```bash
|
||||||
|
python3 ci/check_pr_policies.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Projekt lokal bauen
|
||||||
|
```bash
|
||||||
|
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
cmake --build build --parallel
|
||||||
|
ctest --test-dir build --output-on-failure
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verhalten in Jenkins
|
||||||
|
|
||||||
|
Die Jenkins-Pipeline umfasst typischerweise diese Stages:
|
||||||
|
|
||||||
|
1. **Checkout**
|
||||||
|
2. **Policy Checks**
|
||||||
|
3. **Configure**
|
||||||
|
4. **Build**
|
||||||
|
5. **Test**
|
||||||
|
6. **Cleanup**
|
||||||
|
|
||||||
|
Wenn die Policy-Checks fehlschlagen, werden die späteren Build- und Test-Stages nicht mehr ausgeführt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typische Fehlerursachen
|
||||||
|
|
||||||
|
### Formatfehler
|
||||||
|
Beispiel:
|
||||||
|
```text
|
||||||
|
ERROR: Formatfehler in tests/test_main.cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lösung
|
||||||
|
```bash
|
||||||
|
clang-format -i tests/test_main.cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Kein passender Test gefunden
|
||||||
|
Beispiel:
|
||||||
|
```text
|
||||||
|
ERROR: Kein passender Test unter tests/, test/ oder qt6-after/qt6-migrated-benchmark/tests für main.cpp gefunden.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lösung
|
||||||
|
- passenden Test anlegen
|
||||||
|
- Testdatei passend benennen
|
||||||
|
- Test in einem der erlaubten Testverzeichnisse ablegen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Commit nicht gültig SSH-signiert
|
||||||
|
Beispiel:
|
||||||
|
```text
|
||||||
|
ERROR: Commit ist nicht gültig mit erlaubtem SSH-Signierer signiert: <sha>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lösung
|
||||||
|
- SSH-Signing lokal korrekt konfigurieren
|
||||||
|
- prüfen, ob der richtige Schlüssel verwendet wird
|
||||||
|
- prüfen, ob der öffentliche Schlüssel in `ci/allowed_signers` eingetragen ist
|
||||||
|
- Commit neu erstellen oder neu signieren
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `.clang-format` fehlt
|
||||||
|
Beispiel:
|
||||||
|
```text
|
||||||
|
ERROR: .clang-format fehlt im Repository-Root.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lösung
|
||||||
|
- `.clang-format` im Repository-Root anlegen oder wiederherstellen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Empfohlener Workflow
|
||||||
|
|
||||||
|
1. Feature-Branch erstellen
|
||||||
|
2. Änderungen implementieren
|
||||||
|
3. Test ergänzen oder anpassen
|
||||||
|
4. Code formatieren
|
||||||
|
5. Commit SSH-signiert erstellen
|
||||||
|
6. lokal Policy, Build und Tests ausführen
|
||||||
|
7. pushen
|
||||||
|
8. Pull Request öffnen
|
||||||
|
9. Jenkins-Ergebnis prüfen
|
||||||
|
10. erst bei grünem Status mergen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kurz-Checkliste vor jedem Push
|
||||||
|
|
||||||
|
- [ ] C/C++-Dateien sind formatiert
|
||||||
|
- [ ] passender Test existiert
|
||||||
|
- [ ] Commit ist SSH-signiert
|
||||||
|
- [ ] öffentlicher Schlüssel steht in `ci/allowed_signers`
|
||||||
|
- [ ] `python3 ci/check_pr_policies.py` läuft erfolgreich
|
||||||
|
- [ ] Build und Tests laufen lokal
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ziel der Regeln
|
||||||
|
|
||||||
|
Die Policies sollen sicherstellen, dass:
|
||||||
|
|
||||||
|
- Code konsistent formatiert ist
|
||||||
|
- Änderungen nachvollziehbar getestet sind
|
||||||
|
- nur autorisierte Signierer Commits einbringen
|
||||||
|
- PRs frühzeitig automatisiert validiert werden
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
torsten.wierschin@life-maps.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG+sdfrCTkAHvZCwBGPGUaT4VFkX8YvnqprCpCrLajSH
|
||||||
Executable → Regular
+71
-11
@@ -11,6 +11,7 @@ TEST_DIRS = [
|
|||||||
ROOT / "qt6-after" / "qt6-migrated-benchmark" / "tests",
|
ROOT / "qt6-after" / "qt6-migrated-benchmark" / "tests",
|
||||||
]
|
]
|
||||||
CPP_EXTENSIONS = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"}
|
CPP_EXTENSIONS = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"}
|
||||||
|
ALLOWED_SIGNERS_FILE = ROOT / "ci" / "allowed_signers"
|
||||||
|
|
||||||
TEST_PATTERNS = [
|
TEST_PATTERNS = [
|
||||||
"test_{name}.cpp",
|
"test_{name}.cpp",
|
||||||
@@ -23,9 +24,11 @@ TEST_PATTERNS = [
|
|||||||
"{name}gtest.h",
|
"{name}gtest.h",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def run(cmd: str) -> str:
|
def run(cmd: str) -> str:
|
||||||
return subprocess.check_output(cmd, shell=True, text=True).strip()
|
return subprocess.check_output(cmd, shell=True, text=True).strip()
|
||||||
|
|
||||||
|
|
||||||
def git_ref_exists(ref: str) -> bool:
|
def git_ref_exists(ref: str) -> bool:
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
@@ -38,9 +41,11 @@ def git_ref_exists(ref: str) -> bool:
|
|||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_pull_request_build() -> bool:
|
def is_pull_request_build() -> bool:
|
||||||
return bool(os.environ.get("CHANGE_ID"))
|
return bool(os.environ.get("CHANGE_ID"))
|
||||||
|
|
||||||
|
|
||||||
def resolve_target_ref() -> str | None:
|
def resolve_target_ref() -> str | None:
|
||||||
raw = os.environ.get("CHANGE_TARGET", "").strip()
|
raw = os.environ.get("CHANGE_TARGET", "").strip()
|
||||||
|
|
||||||
@@ -61,13 +66,15 @@ def resolve_target_ref() -> str | None:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def changed_files():
|
|
||||||
|
def changed_files_and_base() -> tuple[list[str], str]:
|
||||||
target = resolve_target_ref()
|
target = resolve_target_ref()
|
||||||
|
|
||||||
if target:
|
if target:
|
||||||
print(f"Vergleiche gegen Ziel-Ref: {target}")
|
print(f"Vergleiche gegen Ziel-Ref: {target}")
|
||||||
out = run(f"git diff --name-only {target}...HEAD")
|
out = run(f"git diff --name-only {target}...HEAD")
|
||||||
return [line.strip() for line in out.splitlines() if line.strip()]
|
files = [line.strip() for line in out.splitlines() if line.strip()]
|
||||||
|
return files, target
|
||||||
|
|
||||||
if is_pull_request_build():
|
if is_pull_request_build():
|
||||||
print("ERROR: Kein gültiger Ziel-Branch-Ref für PR-Build gefunden.")
|
print("ERROR: Kein gültiger Ziel-Branch-Ref für PR-Build gefunden.")
|
||||||
@@ -76,23 +83,46 @@ def changed_files():
|
|||||||
if git_ref_exists("HEAD~1"):
|
if git_ref_exists("HEAD~1"):
|
||||||
print("Kein Ziel-Branch-Ref gefunden, verwende HEAD~1 für Branch-Build.")
|
print("Kein Ziel-Branch-Ref gefunden, verwende HEAD~1 für Branch-Build.")
|
||||||
out = run("git diff --name-only HEAD~1..HEAD")
|
out = run("git diff --name-only HEAD~1..HEAD")
|
||||||
return [line.strip() for line in out.splitlines() if line.strip()]
|
files = [line.strip() for line in out.splitlines() if line.strip()]
|
||||||
|
return files, "HEAD~1"
|
||||||
|
|
||||||
print("Keine Vergleichsbasis gefunden, behandle alle versionierten Dateien als geändert.")
|
print("Keine Vergleichsbasis gefunden, behandle alle versionierten Dateien als geändert.")
|
||||||
out = run("git ls-files")
|
out = run("git ls-files")
|
||||||
|
files = [line.strip() for line in out.splitlines() if line.strip()]
|
||||||
|
return files, ""
|
||||||
|
|
||||||
|
|
||||||
|
def commits_in_range(base: str) -> list[str]:
|
||||||
|
if base:
|
||||||
|
if base == "HEAD~1":
|
||||||
|
out = run("git rev-list HEAD~1..HEAD")
|
||||||
|
else:
|
||||||
|
out = run(f"git rev-list {base}..HEAD")
|
||||||
|
else:
|
||||||
|
out = run("git rev-list HEAD")
|
||||||
return [line.strip() for line in out.splitlines() if line.strip()]
|
return [line.strip() for line in out.splitlines() if line.strip()]
|
||||||
|
|
||||||
|
|
||||||
def is_cpp_file(path: Path) -> bool:
|
def is_cpp_file(path: Path) -> bool:
|
||||||
return path.suffix.lower() in CPP_EXTENSIONS
|
return path.suffix.lower() in CPP_EXTENSIONS
|
||||||
|
|
||||||
|
|
||||||
def require_clang_format():
|
def require_clang_format():
|
||||||
if not (ROOT / ".clang-format").exists():
|
if not (ROOT / ".clang-format").exists():
|
||||||
print("ERROR: .clang-format fehlt im Repository-Root.")
|
print("ERROR: .clang-format fehlt im Repository-Root.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def require_allowed_signers():
|
||||||
|
if not ALLOWED_SIGNERS_FILE.exists():
|
||||||
|
print(f"ERROR: {ALLOWED_SIGNERS_FILE} fehlt.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def check_format(path: Path):
|
def check_format(path: Path):
|
||||||
subprocess.check_call(f"clang-format --dry-run --Werror '{path}'", shell=True)
|
subprocess.check_call(f"clang-format --dry-run --Werror '{path}'", shell=True)
|
||||||
|
|
||||||
|
|
||||||
def candidate_names(path: Path):
|
def candidate_names(path: Path):
|
||||||
stem = path.stem.lower()
|
stem = path.stem.lower()
|
||||||
names = {stem}
|
names = {stem}
|
||||||
@@ -101,11 +131,13 @@ def candidate_names(path: Path):
|
|||||||
names.add(stem[:-len(suffix)])
|
names.add(stem[:-len(suffix)])
|
||||||
return sorted(n for n in names if n)
|
return sorted(n for n in names if n)
|
||||||
|
|
||||||
|
|
||||||
def iter_test_files():
|
def iter_test_files():
|
||||||
for test_dir in TEST_DIRS:
|
for test_dir in TEST_DIRS:
|
||||||
if test_dir.exists():
|
if test_dir.exists():
|
||||||
yield from (p for p in test_dir.rglob("*") if p.is_file())
|
yield from (p for p in test_dir.rglob("*") if p.is_file())
|
||||||
|
|
||||||
|
|
||||||
def has_related_test(path: Path) -> bool:
|
def has_related_test(path: Path) -> bool:
|
||||||
test_files = list(iter_test_files())
|
test_files = list(iter_test_files())
|
||||||
if not test_files:
|
if not test_files:
|
||||||
@@ -122,14 +154,46 @@ def has_related_test(path: Path) -> bool:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def verify_commit_signature(commit: str):
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["GIT_CONFIG_COUNT"] = "1"
|
||||||
|
env["GIT_CONFIG_KEY_0"] = "gpg.ssh.allowedSignersFile"
|
||||||
|
env["GIT_CONFIG_VALUE_0"] = str(ALLOWED_SIGNERS_FILE)
|
||||||
|
|
||||||
|
subprocess.check_call(
|
||||||
|
f"git verify-commit {commit}",
|
||||||
|
shell=True,
|
||||||
|
env=env,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
require_clang_format()
|
require_clang_format()
|
||||||
|
require_allowed_signers()
|
||||||
|
|
||||||
files = changed_files()
|
files, base = changed_files_and_base()
|
||||||
if not files:
|
if not files:
|
||||||
print("Keine geänderten Dateien erkannt.")
|
print("Keine geänderten Dateien erkannt.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
commits = commits_in_range(base)
|
||||||
|
if not commits:
|
||||||
|
print("Keine neuen Commits im Vergleichsbereich erkannt.")
|
||||||
|
else:
|
||||||
|
for commit in commits:
|
||||||
|
print(f"Prüfe SSH-Signatur: {commit}")
|
||||||
|
try:
|
||||||
|
verify_commit_signature(commit)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
print(
|
||||||
|
"ERROR: Commit ist nicht gültig mit erlaubtem SSH-Signierer signiert: "
|
||||||
|
f"{commit}"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
for rel in files:
|
for rel in files:
|
||||||
path = ROOT / rel
|
path = ROOT / rel
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
@@ -143,10 +207,6 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if not has_related_test(path):
|
if not has_related_test(path):
|
||||||
print(f"ERROR: Kein passender Test unter tests/, test/ oder qt6-after/qt6-migrated-benchmark/tests für {rel} gefunden.")
|
print(
|
||||||
sys.exit(1)
|
"ERROR: Kein passender Test unter tests/, test/ oder "
|
||||||
|
f
|
||||||
print("Policy-Checks erfolgreich.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Reference in new issue
Block a user