Skip to content

fix(cas): dreimal still gekuerzt, einmal der Heap (MF-1040) #976

fix(cas): dreimal still gekuerzt, einmal der Heap (MF-1040)

fix(cas): dreimal still gekuerzt, einmal der Heap (MF-1040) #976

Workflow file for this run

# ============================================================================
# UnifiedFloppyTool - CI Build
#
# Fixes in diesem File:
# ROOT CAUSE: Qt 6.7.3 (C++17) vs Qt 6.10.1 (C++20) Mismatch behoben
# → Matrix-Build testet BEIDE Standards
# FAILURE-001: macos-14 arch mismatch → clang_64 entfernt
# FAILURE-002: IQTA_TOOLS MinGW → add-tools-to-path: true
# FAILURE-007: cmake --target all → entfernt
# ============================================================================
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# ══════════════════════════════════════════════════════════════════════════
# Consistency gate — fast pre-build check that mirrors the local pre-push
# hook (scripts/git-hooks/pre-push). Catches dangling #include refs,
# missing target_sources paths, and undefined TEST_LIBS targets BEFORE
# any compiler runs. Devs who installed the hook see the same failure
# locally; this gate catches everyone else.
# ══════════════════════════════════════════════════════════════════════════
consistency:
# MF-432: without this GitHub's default is 360 min. On 2026-08-19
# five Linux jobs hung in the Qt/apt download and would have burned
# six hours each. A hang now dies here instead.
timeout-minutes: 10
name: Consistency check
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v7
- name: SSOT parity (verify_build_sources.py)
run: python3 scripts/verify_build_sources.py
- name: Consistency (check_consistency.py)
run: python3 scripts/check_consistency.py
# ══════════════════════════════════════════════════════════════════════════
# gw-Paritaet (MF-798)
#
# `tests/differential/test_gw_parity.py` dekodiert DENSELBEN von gw
# erzeugten Flussstrom zweimal — einmal mit `gw convert`, einmal mit UFTs
# Flussmaschine — und verlangt BYTEIDENTISCHE Sektorabbilder. Sechs
# Diskettenklassen: IBM MFM DD/HD, Atari ST MFM, Commodore 1541 GCR,
# Apple II GCR, AmigaDOS MFM.
#
# Das ist das staerkste Stueck Beweisfuehrung im Baum, und es lief bis
# MF-798 NIE: keiner der sechs Arbeitsablaeufe rief `pytest` auf oder
# installierte `gw`. Der Test uebersprang sich sauber — und war damit
# eine einmal behauptete Aussage statt einer bei jedem Push erneuerten.
#
# EIN SKIP MACHT DIESEN JOB ROT. Das ist die Lehre aus MF-598: ein
# Skip, der einen Bruch verdeckt, ist schlimmer als ein roter Test. Die
# gw-Paritaet meldete jahrelang „6 skipped", und dahinter lag ein Ziel,
# das sich nicht einmal uebersetzen liess.
# ══════════════════════════════════════════════════════════════════════════
gw-parity:
timeout-minutes: 20
needs: consistency
name: gw parity (differential)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v7
- name: Install Greaseweazle host tools
run: |
set -euo pipefail
# gw steht nicht auf PyPI. Unlicense (Public Domain) — die
# Installation ist unbedenklich, und wir binden NICHTS davon
# ein: gw laeuft als eigener Prozess, als Oracle.
#
# GEPINNT AUF DEN COMMIT, nicht auf den Tag `v1.23`. Ein Tag
# laesst sich verschieben; dann waere das Oracle still ein
# anderes als das, gegen das die Aussagen gemessen wurden.
# Der Hash ist der von `v1.23`, nachgesehen ueber die
# GitHub-API am 2026-09-02.
python3 -m pip install --upgrade pip
python3 -m pip install "git+https://github.com/keirf/greaseweazle@a0ae343d7e2603b9f3fdc0149ef8e89de5399f58"
python3 -m pip install pytest
# `gw --version` gibt es NICHT (MF-776) — die Option faellt auf
# den Usage-Text zurueck, in dem die Zahl aus dem INSTALLATIONS-
# PFAD steht. `gw info` ist die richtige Quelle und laeuft ohne
# Geraet.
#
# EINMAL AUSFUEHREN, IN EINE DATEI (MF-805). Hier stand
# `gw info 2>&1 | grep -qE ...` — und das ist mit
# `set -o pipefail` ein WETTLAUF: `grep -q` steigt beim ersten
# Treffer aus und schliesst die Pipe, der Erzeuger bekommt
# SIGPIPE und endet mit 141, und pipefail macht daraus einen
# Fehlschlag der ganzen Pipeline — obwohl der Treffer da war.
#
# Der erste CI-Lauf ist genau daran gescheitert: das Protokoll
# zeigt `Host Tools: 1.23` UND darunter „gw meldet nicht 1.23".
# Lokal lief dieselbe Zeile gruen, weil gws Ausgabe klein genug
# ist, um vor dem Schliessen durchzulaufen. Nachgestellt:
# `set -o pipefail; yes | grep -q y` gibt 141.
gw info > gwinfo.txt 2>&1 || true
echo "gw-Fassung, wie das Oracle-Register sie fuehrt:"
grep -i "host tools" gwinfo.txt || true
# Auf der DATEI greppen, nicht auf einer Pipe: kein SIGPIPE,
# kein zweiter Lauf, und die Ausgabe steht fuer die
# Fehlermeldung noch zur Verfuegung.
if ! grep -qiE "Host Tools:[[:space:]]*1\.23" gwinfo.txt; then
echo "::error::gw meldet nicht 1.23 — das Oracle ist ein anderes"
echo "Was gw tatsaechlich ausgegeben hat:"
cat gwinfo.txt
exit 1
fi
- name: Parity suite (a skip fails this job)
run: |
set -uo pipefail
# MF-817: `set -e` NICHT ueber dieser Pipe.
#
# Bei einem echten Testfehlschlag beendete `set -e` den Schritt
# sofort an der Pipe — und die Skip- und Pass-Diagnostik
# darunter lief nie. Man sah einen nackten roten Schritt,
# obwohl der Job Namen und Gruende haette ausgeben koennen.
# Derselbe Fallstrick wie `grep -q` in MF-805, nur andersherum.
#
# Deshalb: Rueckgabewert von pytest EXPLIZIT aufheben
# (`PIPESTATUS[0]`, nicht der von `tee`), erst alle Pruefungen
# laufen lassen, dann entscheiden.
python3 -m pytest tests/differential/test_gw_parity.py \
-v -rs 2>&1 | tee parity.log
rc=${PIPESTATUS[0]}
set -e
# `pytest` gibt bei durchweg uebersprungenen Tests 0 zurueck.
# Genau daran ist diese Aussage jahrelang vorbeigelaufen.
if grep -qiE '[0-9]+ skipped' parity.log; then
echo "::error::Die gw-Paritaet hat uebersprungen statt zu pruefen."
echo "Ein Skip ist hier KEIN Erfolg — siehe MF-598. Gruende:"
grep -iE 'SKIPPED' parity.log || true
exit 1
fi
if ! grep -qE '[0-9]+ passed' parity.log; then
echo "::error::Kein einziger Paritaetsfall ist gelaufen."
exit 1
fi
# Erst JETZT ueber pytests eigenen Rueckgabewert entscheiden —
# nach den Pruefungen, damit ihre Meldungen im Protokoll stehen.
if [ "$rc" -ne 0 ]; then
echo "::error::pytest endete mit $rc — Paritaet NICHT erreicht."
grep -E 'FAILED|assert' parity.log | head -20 || true
exit 1
fi
echo "gw-Paritaet: byteidentisch, nachgewiesen statt behauptet."
- name: Upload parity log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: gw-parity-log-${{ github.sha }}
path: parity.log
retention-days: 7
# ══════════════════════════════════════════════════════════════════════════
# Matrix: C++17 (Qt 6.7.3) UND C++20 (Qt 6.10.1) — beide müssen grün sein
# ══════════════════════════════════════════════════════════════════════════
build-linux:
# MF-432: without this GitHub's default is 360 min. On 2026-08-19
# five Linux jobs hung in the Qt/apt download and would have burned
# six hours each. A hang now dies here instead.
timeout-minutes: 45
needs: consistency
name: Linux (${{ matrix.qt_version }}, ${{ matrix.cxx_std }})
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
include:
- qt_version: '6.7.3'
cxx_std: 'c++17'
qmake_extra: ''
- qt_version: '6.10.1'
cxx_std: 'c++20'
qmake_extra: 'CONFIG+=cxx20'
steps:
- uses: actions/checkout@v7
- name: Install Qt ${{ matrix.qt_version }}
uses: jurplel/install-qt-action@v4
with:
version: ${{ matrix.qt_version }}
host: linux
target: desktop
modules: 'qtserialport'
cache: true
- name: Install system dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y libusb-1.0-0-dev libgl1-mesa-dev
- name: Build (qmake, ${{ matrix.cxx_std }})
run: |
mkdir build && cd build
qmake ../UnifiedFloppyTool.pro CONFIG+=release ${{ matrix.qmake_extra }}
make -j$(nproc)
- name: Smoke test
run: |
file build/UnifiedFloppyTool
ldd build/UnifiedFloppyTool | grep -v "not found" || true
- name: Build tests (CMake)
id: buildtests
# qmake is the canonical build (above). CMake test infra is
# keep-going: one unbuildable target must not abort the parallel
# build and leave ~150 others as ctest "Not Run" (KNOWN_ISSUES
# CI-1). Independent test executables still build.
#
# MF-667: hier stand `|| true` — und der Kommentar am ctest-Schritt
# behauptete gleichzeitig, "a build breakage surfaces in the build
# step". Beides zusammen ging nicht auf: der Schritt KONNTE nicht
# rot werden.
#
# Gemessen wurde die Folge: VIER Qt-Ziele bauten laengere Zeit
# nicht, und niemand sah es. ctest blieb gruen, weil die
# betroffenen Ziele nicht liefen; lokal blieb es unsichtbar, weil
# ausfuehrbare Dateien aus frueheren Baeufen herumlagen.
#
# Der Rueckgabewert wird jetzt FESTGEHALTEN statt verworfen. Der
# Schritt bleibt gruen, damit ctest danach noch laeuft und man die
# Ergebnisse der bauenden Ziele sieht — die Bewertung faellt am
# Ende, mit Namen.
run: |
set +e
cmake -B build-tests \
-DCMAKE_BUILD_TYPE=Release \
-DUFT_BUILD_TESTS=ON
echo "configure_rc=$?" >> "$GITHUB_OUTPUT"
cmake --build build-tests --parallel $(nproc) -- -k 2>&1 \
| tee build-tests.log
echo "build_rc=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
exit 0
- name: Run tests
# GATED (KNOWN_ISSUES CI-1 resolved): no more `|| true`. A test that
# builds and then FAILS now reddens CI. The env defects that had
# been masked — Linux uft_ufi_linux_ops link + headless-Qt offscreen,
# Windows libusb mock + /tmp path — are fixed; all three test jobs
# verified 163/163 green before this gate was armed.
# `--no-tests=ignore` (CMake >= 3.20) still tolerates a fully-failed
# test build (exit 0) so a build breakage surfaces as a build
# breakage, not as a confusing "no tests" ctest error.
#
# MF-667: dieser Satz stand hier schon und war UNWAHR — der
# Bau-Schritt trug `|| true` und konnte nicht rot werden. Bewertet
# wird er jetzt von einem eigenen Schritt direkt darunter.
run: |
cd build-tests && ctest --output-on-failure --timeout 120 --no-tests=ignore
- name: Der Pruefstand muss BAUEN, nicht nur bestehen (MF-667)
# Laeuft NACH ctest, damit die Ergebnisse der bauenden Ziele
# sichtbar bleiben — und faellt trotzdem, wenn irgendein Ziel
# nicht uebersetzt.
#
# "Tests gruen" und "Bau gruen" sind ZWEI Aussagen. Bis MF-667
# wurde nur die erste geprueft; die zweite war mit `|| true`
# verworfen, und vier Ziele bauten laengere Zeit unbemerkt nicht.
if: always()
run: |
if [ "${{ steps.buildtests.outputs.configure_rc }}" != "0" ]; then
echo "::error::cmake configure fuer den Pruefstand ist gescheitert."
exit 1
fi
rc="${{ steps.buildtests.outputs.build_rc }}"
if [ "$rc" = "0" ]; then
echo "Pruefstand baut vollstaendig."
exit 0
fi
echo "::error::Der Pruefstand baut NICHT vollstaendig (rc=$rc)."
echo "Ziele mit Fehlern:"
grep -oE 'CMakeFiles/[A-Za-z0-9_]+\.dir' build-tests.log \
| sed 's|CMakeFiles/||; s|\.dir$||' | sort -u | head -40
echo
echo "Erste Uebersetzungsfehler:"
grep -E '^[^ ]+:[0-9]+:[0-9]+: (error|fatal error):' \
build-tests.log | head -20
exit 1
- name: Upload build logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: linux-build-logs-${{ matrix.qt_version }}-${{ github.sha }}
path: |
build/*.log
build-tests/CMakeFiles/*.log
retention-days: 3
# ══════════════════════════════════════════════════════════════════════════
# macOS — ARM64, Qt 6.10.1
# ══════════════════════════════════════════════════════════════════════════
build-macos:
# MF-432: without this GitHub's default is 360 min. On 2026-08-19
# five Linux jobs hung in the Qt/apt download and would have burned
# six hours each. A hang now dies here instead.
timeout-minutes: 45
needs: consistency
runs-on: macos-14
steps:
- uses: actions/checkout@v7
- name: Install Qt
# FIX-001: kein arch: clang_64 — macos-14 ist ARM64
uses: jurplel/install-qt-action@v4
with:
version: '6.10.1'
host: mac
target: desktop
modules: 'qtserialport'
cache: true
- name: Build
run: |
mkdir build && cd build
qmake ../UnifiedFloppyTool.pro CONFIG+=release CONFIG+=cxx20
make -j$(sysctl -n hw.ncpu)
- name: Smoke test
run: |
file build/UnifiedFloppyTool.app/Contents/MacOS/UnifiedFloppyTool
- name: Upload build logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: macos-build-logs-${{ github.sha }}
path: build/*.log
retention-days: 3
# ══════════════════════════════════════════════════════════════════════════
# Windows — MinGW, Qt 6.10.1
# ══════════════════════════════════════════════════════════════════════════
build-windows:
# MF-432: without this GitHub's default is 360 min. On 2026-08-19
# five Linux jobs hung in the Qt/apt download and would have burned
# six hours each. A hang now dies here instead.
timeout-minutes: 60
needs: consistency
runs-on: windows-2022
steps:
- uses: actions/checkout@v7
- name: Install Qt + MinGW
# FIX-002: add-tools-to-path: true — kein manuelles IQTA_TOOLS
uses: jurplel/install-qt-action@v4
with:
version: '6.10.1'
host: windows
target: desktop
arch: win64_mingw
modules: 'qtserialport'
tools: 'tools_mingw1310'
add-tools-to-path: true
cache: true
- name: Verify toolchain
shell: bash
run: |
which mingw32-make || (echo "ERROR: mingw32-make not found" && exit 1)
which qmake || (echo "ERROR: qmake not found" && exit 1)
- name: Build
shell: bash
run: |
mkdir build && cd build
qmake ../UnifiedFloppyTool.pro CONFIG+=release CONFIG+=cxx20
mingw32-make -j$(nproc)
- name: Smoke test
shell: bash
run: |
file build/release/UnifiedFloppyTool.exe
- name: Build tests (CMake)
id: buildtests
# Same best-effort policy as the Linux job: partial CMake test
# builds are acceptable since qmake (above) is the canonical
# production build. The Windows build of test_gw_protocol
# (MF-135) is the one we actually want to keep green here -
# protocol regressions on the platform where the FB-bug
# appeared should fail the CI on that platform.
shell: bash
run: |
set +e
cmake -B build-tests -G "MinGW Makefiles" \
-DCMAKE_BUILD_TYPE=Release \
-DUFT_BUILD_TESTS=ON
echo "configure_rc=$?" >> "$GITHUB_OUTPUT"
# keep-going (`-- -k`): see Linux job — one unbuildable target must
# not abort the whole build (KNOWN_ISSUES CI-1).
cmake --build build-tests --parallel $(nproc) -- -k 2>&1 \
| tee build-tests.log
echo "build_rc=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT"
exit 0
- name: Run tests
# GATED (KNOWN_ISSUES CI-1 resolved): no `|| true`. The Windows env
# defects (libusb mock header + /tmp temp path) are fixed; this job
# verified 163/163 green before the gate was armed.
shell: bash
run: |
cd build-tests && ctest --output-on-failure --timeout 120 --no-tests=ignore
- name: Der Pruefstand muss BAUEN, nicht nur bestehen (MF-667)
# Wie im Linux-Auftrag: nach ctest, damit die Ergebnisse der
# bauenden Ziele sichtbar bleiben — und trotzdem rot, wenn eines
# nicht uebersetzt. "Tests gruen" und "Bau gruen" sind zwei
# Aussagen; bis MF-667 wurde nur die erste geprueft.
if: always()
shell: bash
run: |
if [ "${{ steps.buildtests.outputs.configure_rc }}" != "0" ]; then
echo "::error::cmake configure fuer den Pruefstand ist gescheitert."
exit 1
fi
rc="${{ steps.buildtests.outputs.build_rc }}"
if [ "$rc" = "0" ]; then
echo "Pruefstand baut vollstaendig."
exit 0
fi
echo "::error::Der Pruefstand baut NICHT vollstaendig (rc=$rc)."
echo "Ziele mit Fehlern:"
grep -oE 'CMakeFiles/[A-Za-z0-9_]+[.]dir' build-tests.log \
| sed 's|CMakeFiles/||; s|[.]dir$||' | sort -u | head -40
echo
echo "Erste Uebersetzungsfehler:"
grep -E '^[^ ]+:[0-9]+:[0-9]+: (error|fatal error):' \
build-tests.log | head -20
exit 1
- name: Upload build logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: windows-build-logs-${{ github.sha }}
path: build/*.log
retention-days: 3