Skip to content

CrossPlatform SmokeTests

Bradley Brown edited this page Aug 13, 2026 · 1 revision

Cross-Platform Manual Smoke Tests

Companion: Cross-Platform Implementation Plan · Cross-Platform Roadmap · Technical Index

Created 2026-06-12 for branch feature/cross-platform-full. Lists the manual user-visible checks that round out each phase's exit criteria. CI builds every assembly on the Linux + macOS legs; these procedures verify the runtime end-to-end on a real desktop before each phase is closed.

Run them on every supported host before the corresponding phase PR merges to main. The host matrix matches the roadmap: linux-x64 (Ubuntu 24.04 GNOME Wayland + X11), linux-arm64 (Raspberry Pi OS), osx-arm64 (macOS Sonoma on Apple Silicon), and osx-x64 (Intel macOS). win-x64 is covered implicitly by the WinExe regression and does not need re-running here.


Phase X.A — Image I/O SkiaSharp swap

A.S1 — PNG export, headless

  1. dotnet run --project FracturingFog.App -- --batch --image --out /tmp/smoke.png --width 800 --height 600 --maxiter 256.
  2. Confirm exit code 0 and /tmp/smoke.png exists.
  3. Open the file in the host's default image viewer; confirm the Mandelbrot render looks correct (no inverted alpha, no swapped colour channels).
  4. file /tmp/smoke.png reports PNG image data with the expected dimensions.

A.S2 — Slideshow frame capture, headless

  1. dotnet run --project FracturingFog.App -- --batch --slideshow --out /tmp/slides --frames 8 --width 640 --height 480.
  2. Confirm eight frame_NNNNNN.png files land in /tmp/slides/ with strictly increasing colours / view drift.
  3. Spot-check file /tmp/slides/frame_000001.png for sane metadata.

A.S3 — Watermark composition

  1. Interactive: open the App, enable Watermark from the menu, render a frame.
  2. Confirm the watermark text is legible (font fell back to Inter or a system sans-serif), outline + fill render in the configured colours.
  3. Save the frame via "Save Image…" — confirm the saved PNG has the watermark baked in.

Phase X.B — Audio capture abstraction

B.S1 — Source picker visible, system loopback greyed

  1. Launch the App. Open the audio-reactive slideshow settings dialog.
  2. Confirm the source picker is present and System loopback + Microphone appear in the list with reduced opacity (dim, not removed).
  3. Confirm the yellow #FFCC00 banner at the top of the dialog reads "System audio capture is not supported on this OS." (or the localised variant) — verifying the colourblind-safe warning hue.

B.S2 — File playback drives the analyzer

  1. Pick File as the source; browse to a known-good WAV or MP3 (NAudio handles both cross-platform via the file-decode path).
  2. Start the audio-reactive slideshow.
  3. Confirm the slideshow advances on beat detections (theme/region switches coincide with audible kicks). The BPM readout in the settings dialog should show a non-zero value within ~5 s of playback start.
  4. Stop the slideshow — confirm clean shutdown (no zombie audio threads; ps / top shows the process CPU drops to idle).

B.S3 — Synth source (analyzer-only)

  1. Pick Synth as the source.
  2. Start the audio-reactive slideshow.
  3. Confirm the slideshow advances on the synthesised beat pattern; no speaker output is expected (the noop backend routes the synth into the analyzer only).
  4. Stop and confirm clean shutdown.

Phase X.1 — Palette engine

1.S1 — PNG sheet round-trip

  1. Open FracturingFog.App (or PaletteBuilder standalone). Open the palette builder; load a source image (PNG / JPEG from Resources/Samples/ works).
  2. Extract a palette (any method).
  3. Export → choose PNG sheet, save to /tmp/palette.png.
  4. Open /tmp/palette.png in a viewer; confirm the 1-column strip of swatch tiles renders with legible #HEX RGB(r, g, b) labels in luma-aware contrast (white text on dark swatches, black on light). Fallback font is acceptable if the host lacks Consolas.

1.S2 — PDF export round-trip

  1. Same source image + extracted palette as 1.S1.
  2. Export → choose PDF document. Open the PDF settings dialog.
  3. Tick every option (cover page, source thumbnail, comparison page, gradient strip, swatch metadata, CVD rows). Pick A4 portrait, 2 columns.
  4. Save to /tmp/palette.pdf.
  5. Open /tmp/palette.pdf in the host's default PDF viewer.
    • Cover page: source thumbnail centred, "Method:" + settings dump readable.
    • Comparison page (if multiple extractors ran): one row per method, swatch strip + gradient under it.
    • Swatch grid pages: 2-column tile layout, RGB / hex plate centred on each swatch, metadata block under each, CVD strip (Proto / Deut / Trito) under that, gradient strip footer on every page, "page N of M" in the header.
  6. Confirm no QuestPDF licence-violation watermark appears (Community licence set in the static ctor before any document renders).

1.S3 — Empty palette graceful path

  1. Export PDF with zero swatches (extract from a uniform-colour image with the count knob set to 0 if the UI allows, or remove all rows manually).
  2. Confirm <PaletteName> — (empty) page renders; PDF opens without error.

Phase X.2 — Video export (placeholder)

Procedures land with Phase X.2 implementation. Outline:

  • 1.S1 with --video flag: 100-frame slideshow renders to MP4 via FfmpegVideoWriter on Linux/macOS when ffmpeg is on PATH.
  • 1.S2: "Install ffmpeg" instructions panel appears when ffmpeg is missing; rescan PATH button reflects post-install state without an app restart.

Phase X.6 — Packaging

6.S1 — dotnet publish per RID

Run from a clean checkout:

dotnet publish FracturingFog.App -c Release -p:PublishProfile=linux-x64
dotnet publish FracturingFog.App -c Release -p:PublishProfile=linux-arm64
dotnet publish FracturingFog.App -c Release -p:PublishProfile=osx-arm64
dotnet publish FracturingFog.App -c Release -p:PublishProfile=osx-x64
dotnet publish FracturingFog.App -c Release -p:PublishProfile=win-x64

Each command emits a self-contained single-file archive under FracturingFog.App/publish/<rid>/. Confirm:

  1. Archive present + non-empty (Win archives bundle ffmpeg.exe per Slice 2.4).
  2. Quick sanity launch on the matching host: ./FracturingFog.App opens the Avalonia shell; ./FracturingFog.App --batch --image --out /tmp/smoke.png --width 320 --height 240 round-trips a PNG.

Resolved publish blocker (CalculatorGen + ColorGen NETSDK1150).

Historical: until CalculatorGen and ColorGen were split into Lib + Cli sibling projects, the App's self-contained publish tripped NETSDK1150 because both Exe projects were referenced transitively as libraries via UI.Avalonia. The split landed via:

  1. CalculatorGen.Lib + ColorGen.Lib sibling library projects hold the *Api, *HotLoad, Parser/, Emitters/ source plus the embedded templates. Source physically still lives under the original CalculatorGen/ and ColorGen/ directories; the Lib csproj pulls it via Compile Include="..\<dir>\**\*.cs".
  2. CalculatorGen.csproj + ColorGen.csproj are now thin CLI Exes that disable the default Compile glob and pull only Program.cs, ProjectReference the sibling Lib.
  3. UI.Avalonia.csproj + FracturingFogCLD.csproj reference the Lib projects, not the Exes.

dotnet publish FracturingFog.App -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true now succeeds end-to-end on a Windows host. Publish artifacts ship via the CI release workflow (Slice 6.4) where the GitHub runner builds against a clean restore and the publish profile drives a fresh single-RID closure.

6.S2 — Linux AppImage

After dotnet publish -p:PublishProfile=linux-x64:

Tools/Packaging/build-appimage.sh linux-x64

Confirm dist/FracturingFog-linux-x64.AppImage exists and is executable (./dist/FracturingFog-linux-x64.AppImage opens the shell).

6.S3 — macOS .app bundle

After dotnet publish -p:PublishProfile=osx-arm64:

Tools/Packaging/build-mac-app.sh osx-arm64

Confirm dist/FracturingFog.app/Contents/MacOS/FracturingFog.App is executable and Info.plist parses (plutil -lint dist/FracturingFog.app/Contents/Info.plist). Code-signing is a separate manual step until Apple Developer cert lands.


Known CI infra gaps

These Silk smoke runs are marked continue-on-error: true in .github/workflows/cross-platform-build.yml so the leg's build signal still gates merges. Each is a runner-infrastructure gap, not a code defect:

  • Windows / Silk smokeresolved (Phase X.7). Previously failed with GlfwException: ApiUnavailable: WGL: The driver does not appear to support OpenGL because the windows-latest runner ships no OpenGL ICD. The build workflow now fetches Mesa3D for Windows (pal1000/mesa-dist-win llvmpipe build) and overrides C:\Windows\System32\opengl32.dll before invoking the smoke; new processes pick up Mesa's GL 4.6 software path via the normal LoadLibrary("opengl32.dll") resolution. continue-on-error is off on the Windows leg so the smoke now gates the leg.
  • Linux Wayland / Silk smoke — segfault inside the Silk EGL adapter while running under weston --backend=headless. The Silk smoke successfully passes on the same runner under xvfb + GLFW + X11, so the regression is specific to the EGL adapter Wayland path. Fix path: isolate whether the segfault is upstream in libwayland-egl1 / Mesa or in the EGL adapter wiring.
  • macOS / Silk smoke — segfault inside the CGL context init on Apple Silicon. Suspected Silk.NET native-library mismatch with arm64; the offscreen path that should work on the macos-latest headless WindowServer fails before glReadPixels. Fix path: probe with a tiny pure-Objective-C harness to confirm CGL works, then bisect Silk.NET versions.

The X11 (xvfb) leg + the Linux + Windows + macOS build legs all pass end-to-end; the gaps above land only at the smoke runtime.


Reporting failures

When a smoke test fails:

  1. Capture the host OS + version (uname -a on Linux/macOS).
  2. Capture dotnet --info output.
  3. Capture the App's stderr — every smoke run can pipe through 2>&1 | tee /tmp/smoke.log.
  4. File against the corresponding phase in the implementation plan; the bug belongs on feature/cross-platform-full until the phase merges.

Clone this wiki locally