Skip to content

Commit cfb7f6b

Browse files
CrispStrobeclaude
andcommitted
Run the lib tests and a CLI smoke test on Windows
Every job that touches CrispSorter's own code runs on Linux. `crisp-cloud-rs` has a windows-2025 leg, but that checks out a different repository, so this repo had never been compiled or executed on Windows by CI. Five defects lived in that gap, each of which passes on Linux by construction: * every CLI verb died with "thread 'main' has overflowed its stack" — building the clap tree costs more than MSVC's 1 MiB main thread where Linux gives 8 MiB. `version`, `doctor`, `index stats`, `--help`: the binary was unusable on Windows, and had been for some time. * `crispsorter --version` launched the GUI: the argv sniff that picks CLI-vs-GUI listed `--help` but not `--version`. * the App-Sandbox spawn guard matched its file list with `ends_with("tts/mod.rs")` against a backslash-separated `Path::display()`, so seven of its eight entries could never match — the "guard passing over an empty set" its own comment warns about. * WebDAV built nested paths with `PathBuf::push` and percent-encoded the separator into the URL: `MKCOL /dav/one%5Ctwo` → 501. * `location.rs` decoded every POSIX `location_uri` into a relative Windows path, so a .cidx or manifest written elsewhere pointed nowhere. The smoke step asserts exactly those, and runs each command under an explicit timeout rather than a plain `run:`. That is not belt-and-braces: the `--version` failure mode was *launching a GUI*, which on a headless runner never exits, so a bare invocation would have burned the job timeout and reported "timed out" instead of naming the cause. Deliberately narrower than the Linux `rust` job — no crispembed / crispasr / translate-*, so no native libraries have to be staged per platform. Those paths are already built on Linux; what was covered nowhere is whether this runs on Windows at all. The siblings are still checked out because crisp-docx-{core,llm} are unconditional path deps and cargo resolves optional ones too, so `cargo metadata` needs the directories present. Verified locally on Windows before landing: 1405 lib tests pass with this feature set, the CLI builds, and the smoke block completes in 8.4 s. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 3417e03 commit cfb7f6b

1 file changed

Lines changed: 166 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,172 @@ jobs:
339339
path: repos/CrispSorter/target/debug/crispsorter
340340
retention-days: 7
341341

342+
# The same lib + CLI, on Windows.
343+
#
344+
# Why this exists: every job above runs the CrispSorter code on Linux only.
345+
# `crisp-cloud-rs` has a windows-2025 leg, but that checks out a *different*
346+
# repository, so nothing in this one had ever been compiled or run on
347+
# Windows by CI. Five defects lived in that gap, all of which pass on Linux
348+
# by construction:
349+
#
350+
# * every CLI verb died with "thread 'main' has overflowed its stack" —
351+
# building the clap tree costs more than MSVC's 1 MiB main thread, where
352+
# Linux hands out 8 MiB. `version`, `doctor`, `index stats`, `--help`:
353+
# all of them, so the binary was unusable on Windows.
354+
# * `crispsorter --version` launched the GUI, because the argv sniff that
355+
# picks CLI-vs-GUI listed `--help` but not `--version`.
356+
# * the App-Sandbox spawn guard matched its file list with
357+
# `ends_with("tts/mod.rs")` against a `\`-separated `Path::display()`,
358+
# so seven of its eight entries could never match.
359+
# * WebDAV built nested paths with `PathBuf::push` and percent-encoded the
360+
# `\` into the URL: `MKCOL /dav/one%5Ctwo` → 501.
361+
# * `location.rs` decoded every POSIX `location_uri` into a relative
362+
# Windows path, so a .cidx or manifest written elsewhere pointed nowhere.
363+
#
364+
# Deliberately narrower than the Linux `rust` job: no `crispembed` /
365+
# `crispasr` / `translate-*`, so no native libraries have to be staged and
366+
# this stays a fast structural check rather than a second full build. The
367+
# feature-gated native paths are already covered on Linux; what is *not*
368+
# covered anywhere else is "does this run on Windows at all".
369+
windows:
370+
name: Windows lib tests + CLI smoke
371+
runs-on: windows-2025
372+
timeout-minutes: 120
373+
env:
374+
CARGO_TERM_COLOR: always
375+
# The clap tree overflows libtest's default worker stack (the same
376+
# reason the Linux job sets this). The shipped binary no longer depends
377+
# on it — `cli::run()` sizes its own thread — but the tests still do.
378+
RUST_MIN_STACK: 16777216
379+
steps:
380+
- name: Check out CrispSorter
381+
uses: actions/checkout@v4
382+
with:
383+
path: repos/CrispSorter
384+
385+
# All three siblings are needed even though this job enables none of
386+
# their features: `crisp-docx-{core,llm}` are unconditional path deps,
387+
# and cargo resolves optional path deps too, so the directories have to
388+
# exist for `cargo metadata` to succeed.
389+
- name: Check out sibling CrispEmbed (cargo path dep)
390+
uses: actions/checkout@v4
391+
with:
392+
repository: ${{ env.CRISPEMBED_REPO }}
393+
ref: ${{ env.CRISPEMBED_REF }}
394+
path: repos/CrispEmbed
395+
396+
- name: Check out sibling CrispASR (cargo path dep)
397+
uses: actions/checkout@v4
398+
with:
399+
repository: ${{ env.CRISPASR_REPO }}
400+
ref: ${{ env.CRISPASR_REF }}
401+
path: repos/CrispASR
402+
403+
- name: Check out sibling crisp-docx (cargo path dep)
404+
uses: actions/checkout@v4
405+
with:
406+
repository: ${{ env.CRISP_DOCX_REPO }}
407+
ref: ${{ env.CRISP_DOCX_REF }}
408+
path: repos/crisp-docx
409+
410+
- uses: dtolnay/rust-toolchain@stable
411+
412+
# `lance-encoding` shells out to protoc from its own build script, so a
413+
# PROTOC set inside ours does not reach it — it has to be on PATH before
414+
# cargo starts. The Linux job gets this from apt.
415+
- uses: arduino/setup-protoc@v3
416+
with:
417+
version: "29.0"
418+
repo-token: ${{ secrets.GITHUB_TOKEN }}
419+
420+
- name: Rust dependency cache
421+
uses: Swatinem/rust-cache@v2
422+
with:
423+
workspaces: repos/CrispSorter -> target
424+
key: windows
425+
426+
- uses: actions/setup-node@v4
427+
with:
428+
node-version: lts/*
429+
cache: npm
430+
cache-dependency-path: repos/CrispSorter/package-lock.json
431+
432+
- name: Build the frontend
433+
# tauri-build reads `frontendDist: ../build`; the directory has to
434+
# exist even though nothing here bundles it.
435+
working-directory: repos/CrispSorter
436+
run: |
437+
npm ci
438+
npm run build
439+
440+
- name: Create bin placeholder for the resources glob
441+
working-directory: repos/CrispSorter
442+
run: |
443+
New-Item -ItemType Directory -Force -Path src-tauri/bin | Out-Null
444+
New-Item -ItemType File -Force -Path src-tauri/bin/.gitkeep | Out-Null
445+
446+
- name: Library tests
447+
working-directory: repos/CrispSorter
448+
run: cargo test -p crispsorter --lib --features desktop,sidecars,pdf-zpdf,drive-filen-native,drive-internxt-native,pdf-render
449+
450+
- name: Build the CLI binary
451+
working-directory: repos/CrispSorter
452+
run: cargo build --bin crispsorter -p crispsorter --features desktop,sidecars,pdf-zpdf,drive-filen-native,drive-internxt-native,pdf-render
453+
454+
- name: CLI smoke test
455+
working-directory: repos/CrispSorter
456+
shell: pwsh
457+
# Every assertion here is a bug that shipped. The timeout is the point
458+
# of the exercise as much as the exit code: the `--version` failure
459+
# mode was *launching the GUI*, which on a headless runner does not
460+
# exit at all. A plain `run:` would have hung until the job timeout
461+
# rather than failing in seconds.
462+
run: |
463+
$exe = "target\debug\crispsorter.exe"
464+
if (-not (Test-Path $exe)) { throw "CLI binary was not produced" }
465+
466+
function Invoke-Cli {
467+
param([string[]]$CliArgs, [int]$TimeoutSec = 120)
468+
$out = New-TemporaryFile; $err = New-TemporaryFile
469+
$p = Start-Process -FilePath $exe -ArgumentList $CliArgs -PassThru `
470+
-NoNewWindow -RedirectStandardOutput $out -RedirectStandardError $err
471+
if (-not $p.WaitForExit($TimeoutSec * 1000)) {
472+
$p.Kill()
473+
throw "TIMEOUT: 'crispsorter $($CliArgs -join ' ')' did not exit in ${TimeoutSec}s. A verb missing from cli::SUBCOMMANDS launches the GUI instead of running."
474+
}
475+
$stdout = Get-Content $out -Raw -EA SilentlyContinue
476+
$stderr = Get-Content $err -Raw -EA SilentlyContinue
477+
Remove-Item $out,$err -Force -EA SilentlyContinue
478+
if ($p.ExitCode -ne 0) {
479+
throw "FAILED (exit $($p.ExitCode)): 'crispsorter $($CliArgs -join ' ')'`n$stderr"
480+
}
481+
Write-Host " ok: crispsorter $($CliArgs -join ' ')"
482+
return $stdout
483+
}
484+
485+
# Clap answers these itself and exits. Both stack-overflowed; the
486+
# first also opened the GUI.
487+
$v = Invoke-Cli @('--version')
488+
if ($v -notmatch 'crispsorter') { throw "--version printed nothing useful: $v" }
489+
Invoke-Cli @('--help') | Out-Null
490+
Invoke-Cli @('version') | Out-Null
491+
Invoke-Cli @('doctor') | Out-Null
492+
493+
# Opening LanceDB + Tantivy under a Windows path. `index init` is
494+
# deliberately not run: it downloads an embedder.
495+
$data = Join-Path $env:RUNNER_TEMP 'crispdata'
496+
New-Item -ItemType Directory -Force -Path $data | Out-Null
497+
$stats = Invoke-Cli @('--format','json','index','--data-dir',$data,'stats') -TimeoutSec 300
498+
if ($stats -notmatch '"docs"') { throw "index stats returned no doc count: $stats" }
499+
500+
# A path that does not exist must fail, not report a successful
501+
# index of nothing — that is how a disconnected share read as OK.
502+
$missing = Join-Path $env:RUNNER_TEMP 'does-not-exist-at-all'
503+
$p = Start-Process -FilePath $exe -PassThru -NoNewWindow -Wait `
504+
-ArgumentList @('index','--data-dir',$data,'ingest',$missing)
505+
if ($p.ExitCode -eq 0) { throw "ingest of a missing path exited 0" }
506+
Write-Host " ok: ingest of a missing path fails loudly (exit $($p.ExitCode))"
507+
342508
cloud-backends:
343509
name: Native cloud crates
344510
runs-on: ubuntu-24.04

0 commit comments

Comments
 (0)