mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-08 17:57:44 +00:00
964 lines
57 KiB
PowerShell
964 lines
57 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Tests build_win.bat's option handling and the commands it generates.
|
|
|
|
.DESCRIPTION
|
|
Cases run the script with --dry-run, so nothing is configured, built or
|
|
deleted and the suite finishes in seconds. Each case asserts on the exit
|
|
code and on the command lines the script echoes.
|
|
|
|
Adding a case means adding one row to $cases. A bare string starts a new
|
|
group. Defaults: ExpectExit is 0 and --dry-run is appended, so a row only
|
|
states what is unusual about it.
|
|
|
|
Name what the case proves, in words
|
|
Args arguments, as an array
|
|
ExpectExit expected exit code (default 0)
|
|
DryRun append --dry-run (default $true)
|
|
First regex the first output line must match
|
|
Env environment for this case only
|
|
Contains literal strings the output must have
|
|
NotContains literal strings it must not have
|
|
Match regexes; each must match at least one output line
|
|
NotMatch regexes; none may match any output line
|
|
NotExists paths that must not exist after the case runs
|
|
|
|
.PARAMETER Name
|
|
Run only the cases whose name matches this regex. Headings with no
|
|
matching case are not printed, and a pattern that matches nothing is a
|
|
failure rather than an empty pass.
|
|
|
|
.EXAMPLE
|
|
powershell -File scripts/test_build_win.ps1
|
|
|
|
.EXAMPLE
|
|
powershell -File scripts/test_build_win.ps1 -Name solution
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[string] $Script,
|
|
[string] $Name
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
if (-not $Script) {
|
|
$here = $PSScriptRoot
|
|
if (-not $here) { $here = Split-Path -Parent $MyInvocation.MyCommand.Path }
|
|
$Script = Join-Path (Split-Path -Parent $here) 'build_win.bat'
|
|
}
|
|
if (-not (Test-Path $Script)) { throw "build_win.bat not found at $Script" }
|
|
|
|
# cmd resumes a batch file by byte offset after `call :label`, and with LF
|
|
# endings that offset lands wrong and the label lookup fails. .gitattributes
|
|
# pins CRLF; this catches a checkout or an editor that did not honour it.
|
|
if ((Get-Content -Raw $Script) -match "(?<!`r)`n") {
|
|
throw "$Script has LF line endings; cmd needs CRLF to resume after call :label"
|
|
}
|
|
$Script = (Resolve-Path $Script).Path
|
|
|
|
# Read the long options out of the script itself, so this cannot go stale when
|
|
# an option is added. Field order is: add_arg <var> <type> <short> <long>.
|
|
$longFlags = @(
|
|
Select-String -Path $Script -Pattern '^call :add_arg \S+ \S+ \S+ (\S+) ' |
|
|
ForEach-Object { '--' + $_.Matches[0].Groups[1].Value }
|
|
)
|
|
if ($longFlags.Count -lt 20) { throw "only found $($longFlags.Count) options in $Script; the parser above is wrong" }
|
|
|
|
# A winget that always fails, so the prerequisite failure path runs without
|
|
# touching the machine. It has to be an .exe: a .bat invoked without `call`
|
|
# transfers control and never comes back, which would end the script instead.
|
|
# where.exe returns 1 when its patterns match nothing and never prompts.
|
|
$fixtures = Join-Path ([IO.Path]::GetTempPath()) 'build_win_test_fixtures'
|
|
New-Item -ItemType Directory -Force -Path $fixtures | Out-Null
|
|
Copy-Item "$env:SystemRoot\System32\where.exe" (Join-Path $fixtures 'winget.exe') -Force
|
|
$stubPath = "$fixtures;C:\Windows\system32;C:\Windows"
|
|
|
|
# Stand-in ninjas that only report a version, so the 1.12 boundary in the
|
|
# progress format can be exercised on a machine whose real ninja is newer.
|
|
# A dry run skips the dev shell, so PATH here is what the script sees.
|
|
$ninjaPaths = @{}
|
|
foreach ($v in @{ old = '1.11.1'; new = '1.12.0' }.GetEnumerator()) {
|
|
$d = Join-Path $fixtures "ninja-$($v.Key)"
|
|
New-Item -ItemType Directory -Force -Path $d | Out-Null
|
|
Set-Content -Path (Join-Path $d 'ninja.bat') -Encoding ascii -Value @('@echo off', "echo $($v.Value)")
|
|
$ninjaPaths[$v.Key] = "$d;$env:PATH"
|
|
}
|
|
|
|
# A clang-cl earlier on PATH than the Visual Studio one, which is what the
|
|
# compiler used to resolve to. Nothing runs it; the script only locates it.
|
|
$clangDir = Join-Path $fixtures 'clang'
|
|
New-Item -ItemType Directory -Force -Path $clangDir | Out-Null
|
|
Copy-Item "$env:SystemRoot\System32\where.exe" (Join-Path $clangDir 'clang-cl.exe') -Force
|
|
$clangOnPath = "$clangDir;$env:PATH"
|
|
|
|
# A ccache that only has to exist. Nothing runs it; the script only locates it.
|
|
$cacheDir = Join-Path $fixtures 'cache'
|
|
New-Item -ItemType Directory -Force -Path $cacheDir | Out-Null
|
|
Copy-Item "$env:SystemRoot\System32\where.exe" (Join-Path $cacheDir 'ccache.exe') -Force
|
|
$ccacheOnPath = "$cacheDir;$env:PATH"
|
|
|
|
# ProgramFiles(x86) is where the script looks for vswhere, so an empty one
|
|
# stands in for a machine whose Visual Studio has no clang toolset.
|
|
$noVs = Join-Path $fixtures 'no-vs'
|
|
New-Item -ItemType Directory -Force -Path $noVs | Out-Null
|
|
|
|
# A build directory that already holds a classic solution, for the case where
|
|
# what is on disk disagrees with what the generator would write.
|
|
$slnDir = Join-Path $fixtures 'sln'
|
|
New-Item -ItemType Directory -Force -Path $slnDir | Out-Null
|
|
Set-Content -Path (Join-Path $slnDir 'OrcaSlicer.sln') -Value '' -Encoding ascii
|
|
|
|
# The pack stamp is checked against real dates, so a locale-dependent parse
|
|
# in the script cannot pass by looking date-shaped. Yesterday is accepted too,
|
|
# so a run that crosses midnight does not flake.
|
|
$dateStamps = @((Get-Date -Format 'yyyyMMdd'), (Get-Date).AddDays(-1).ToString('yyyyMMdd'))
|
|
$stampPattern = '_(' + ($dateStamps -join '|') + ')\.zip$'
|
|
|
|
$cases = @(
|
|
'argument handling'
|
|
@{ Name = 'no arguments prints help'; Args = @(); DryRun = $false
|
|
Contains = @('Usage: build_win.bat [options]', '--clang-cl') }
|
|
@{ Name = "--help lists all $($longFlags.Count) options the script defines"; Args = @('--help'); DryRun = $false
|
|
Contains = $longFlags }
|
|
@{ Name = 'help is grouped and shows usage, examples and environment'; Args = @('--help'); DryRun = $false
|
|
Contains = @('Usage: build_win.bat [options]', 'Actions:', 'Build configuration:', 'Toolchain:',
|
|
'How much gets rebuilt:', 'Paths and extra arguments:', 'Diagnostics:',
|
|
'Examples:', 'Environment:') }
|
|
@{ Name = 'the environment section shows what to set'; Args = @('--help'); DryRun = $false
|
|
Contains = @('ORCA_DEPS_CMAKE_ARGS', 'ORCA_SLICER_CMAKE_ARGS', 'ORCA_UPDATER_SIG_KEY', 'NINJA_STATUS',
|
|
'set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_BUILD_SANDBOXES=ON', '(PowerShell)', 'debugscript') }
|
|
@{ Name = 'section headers do not widen the flag column'; Args = @('--help'); DryRun = $false
|
|
Match = @('^ -d, --deps +Download') }
|
|
# Windows Terminal opens at 120 columns and wraps at 120, so 119 is the
|
|
# limit. Anyone still on the old conhost gets 80 and will see wrapping.
|
|
@{ Name = 'every help line fits a 120 column console'; Args = @('--help'); DryRun = $false
|
|
NotMatch = @('^.{120,}$') }
|
|
@{ Name = 'an unknown long option is rejected'; Args = @('--nonsense'); ExpectExit = 1
|
|
Contains = @('Failed to find arg') }
|
|
@{ Name = 'an unknown short option is rejected'; Args = @('-Z'); ExpectExit = 1
|
|
Contains = @('Failed to find arg') }
|
|
@{ Name = 'a bare argument is rejected'; Args = @('deps'); ExpectExit = 1
|
|
Contains = @('Unknown argument') }
|
|
@{ Name = 'an unknown architecture is rejected'; Args = @('-d', '--arch', 'sparc'); ExpectExit = 1
|
|
Contains = @('Unknown architecture') }
|
|
@{ Name = 'a string option without a value is rejected'; Args = @('-d', '--arch'); ExpectExit = 1
|
|
Contains = @('requires a value') }
|
|
@{ Name = 'short options can be bundled'; Args = @('-dx')
|
|
Contains = @('-G "Ninja Multi-Config"', '--target deps') }
|
|
|
|
'generator and compiler selection'
|
|
@{ Name = 'deps default to the Visual Studio generator'; Args = @('-d')
|
|
Contains = @('-G "Visual Studio', '-A x64', '--target deps')
|
|
NotContains = @('Ninja', 'clang-cl') }
|
|
@{ Name = '-x selects Ninja without changing compiler'; Args = @('-d', '-x')
|
|
Contains = @('-G "Ninja Multi-Config"')
|
|
NotContains = @('clang-cl', '-A x64') }
|
|
@{ Name = '-l -x builds with clang-cl under Ninja'; Args = @('-d', '-l', '-x')
|
|
Contains = @('-G "Ninja Multi-Config"')
|
|
Match = @('-DCMAKE_C_COMPILER="[^"]+/clang-cl\.exe"', '-DCMAKE_CXX_COMPILER="[^"]+/clang-cl\.exe"') }
|
|
# PATH order used to decide the compiler. VsDevCmd appends the Visual
|
|
# Studio LLVM directory to the end of PATH, so a standalone LLVM already
|
|
# there was resolved instead, and an old one failed the compiler check.
|
|
@{ Name = 'the compiler is resolved from Visual Studio, not PATH'; Args = @('-s', '-l', '-x')
|
|
Env = @{ PATH = $clangOnPath }
|
|
Match = @('^Compiler: .*/VC/Tools/Llvm/[^/]+/bin/clang-cl\.exe$') }
|
|
@{ Name = 'msvc names no compiler, having resolved none'; Args = @('-s')
|
|
NotContains = @('Compiler: ') }
|
|
@{ Name = '-l without -x names none either, the toolset picks it'; Args = @('-s', '-l')
|
|
NotContains = @('Compiler: ') }
|
|
# An empty ProgramFiles(x86) puts vswhere out of reach, which is a machine
|
|
# whose Visual Studio has no clang toolset.
|
|
@{ Name = 'without a Visual Studio clang the one on PATH is used and named'; Args = @('-s', '-l', '-x')
|
|
Env = @{ 'ProgramFiles(x86)' = $noVs; PATH = $clangOnPath }
|
|
Contains = @('Visual Studio has no clang-cl')
|
|
Match = @('^Compiler: .*/clang/clang-cl\.exe$') }
|
|
@{ Name = 'no clang-cl anywhere stops before configuring'; Args = @('-s', '-l', '-x'); ExpectExit = 1
|
|
Env = @{ 'ProgramFiles(x86)' = $noVs; PATH = 'C:\Windows\system32;C:\Windows' }
|
|
Contains = @('No clang-cl found', '--install-vs ide -l')
|
|
NotContains = @('cmake -B') }
|
|
# Only a configure passes the compiler to CMake, so an action that does
|
|
# not configure resolves none, and cannot start needing one installed.
|
|
@{ Name = 'packing resolves no compiler'; Args = @('-p', '-l', '-x')
|
|
Contains = @('Packing the dependencies')
|
|
NotContains = @('Compiler: ') }
|
|
@{ Name = '--no-configure resolves none either'; Args = @('-s', '-l', '-x', '--no-configure')
|
|
Contains = @('cmake --build "build-clang"')
|
|
NotContains = @('Compiler: ') }
|
|
@{ Name = '-l alone uses the ClangCL toolset on the VS generator'; Args = @('-d', '-l')
|
|
Contains = @('-G "Visual Studio', '-T ClangCL')
|
|
NotContains = @('-DCMAKE_C_COMPILER') }
|
|
# --msvc and --msbuild name the defaults, so a caller can be explicit and a
|
|
# contradictory pair can be caught rather than silently resolved.
|
|
@{ Name = '--msvc is the compiler default spelled out'; Args = @('-d', '--msvc')
|
|
Contains = @('-G "Visual Studio', '-A x64')
|
|
NotContains = @('clang-cl') }
|
|
@{ Name = '--msbuild is the generator default spelled out'; Args = @('-d', '--msbuild')
|
|
Contains = @('-G "Visual Studio')
|
|
NotContains = @('Ninja') }
|
|
@{ Name = '--msvc with -x gives Ninja driving cl'; Args = @('-d', '--msvc', '-x')
|
|
Contains = @('-G "Ninja Multi-Config"')
|
|
NotContains = @('clang-cl') }
|
|
@{ Name = '--msbuild with -l gives the VS generator and the ClangCL toolset'; Args = @('-d', '--msbuild', '-l')
|
|
Contains = @('-G "Visual Studio', '-T ClangCL') }
|
|
# A developer with a standalone LLVM points at it, and the path is passed
|
|
# with forward slashes so CMake cannot read a backslash as an escape.
|
|
@{ Name = '--clang-path names the compiler, quoted for its spaces'; Args = @('-d', '-x', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe')
|
|
Contains = @('-DCMAKE_C_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe"',
|
|
'-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe"') }
|
|
@{ Name = '--clang-path beats the Visual Studio clang'; Args = @('-s', '-x', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe')
|
|
Contains = @('Compiler: C:/Program Files/LLVM/bin/clang-cl.exe') }
|
|
@{ Name = '--clang-path is a clang request on its own'; Args = @('-d', '-x', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe')
|
|
Contains = @('deps/build-clang') }
|
|
@{ Name = '--clang-path needs Ninja to take effect'; Args = @('-d', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe'); ExpectExit = 1
|
|
Contains = @('needs the Ninja generator') }
|
|
# `exist` is true for a directory too, and a directory would reach CMake
|
|
# as the compiler.
|
|
@{ Name = '--clang-path must name the exe, not its folder'; Args = @('-d', '-x', '--clang-path', 'C:\Program Files\LLVM'); ExpectExit = 1
|
|
Contains = @('is a directory') }
|
|
@{ Name = 'a clang-cl that is not there is caught early'; Args = @('-d', '-x', '--clang-path', 'C:\nope\clang-cl.exe'); ExpectExit = 1
|
|
Contains = @('No clang-cl at')
|
|
NotContains = @('cmake -S deps') }
|
|
@{ Name = '--clang-path contradicting --msvc is rejected'; Args = @('-d', '-x', '--msvc', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe'); ExpectExit = 1
|
|
Contains = @('select different compilers') }
|
|
@{ Name = '--clang-cl and --msvc together are rejected'; Args = @('-d', '-l', '--msvc'); ExpectExit = 1
|
|
Contains = @('select different compilers') }
|
|
@{ Name = '--ninja and --msbuild together are rejected'; Args = @('-d', '-x', '--msbuild'); ExpectExit = 1
|
|
Contains = @('select different generators') }
|
|
# One option with a value, driven by a table, rather than a flag per
|
|
# release. Adding a release should not need a new flag.
|
|
@{ Name = '--vs 2019 pins that release and skips autodetect'; Args = @('-d', '--vs', '2019')
|
|
Contains = @('-G "Visual Studio 16 2019"')
|
|
NotContains = @('Detecting Visual Studio') }
|
|
@{ Name = '--vs 2022 pins that release'; Args = @('-d', '--vs', '2022')
|
|
Contains = @('-G "Visual Studio 17 2022"') }
|
|
@{ Name = '--vs 2026 pins that release'; Args = @('-d', '--vs', '2026')
|
|
Contains = @('-G "Visual Studio 18 2026"') }
|
|
@{ Name = 'an unknown release is rejected and the known ones listed'; Args = @('-d', '--vs', '2015'); ExpectExit = 1
|
|
Contains = @('Unknown Visual Studio release', '2019, 2022, 2026') }
|
|
@{ Name = '--vs and --ninja together are rejected'; Args = @('-d', '--vs', '2022', '-x'); ExpectExit = 1
|
|
Contains = @('select different generators') }
|
|
@{ Name = 'without --vs the release is autodetected'; Args = @('-d')
|
|
Contains = @('Detecting Visual Studio') }
|
|
|
|
'architecture'
|
|
@{ Name = 'x64 is the default'; Args = @('-d')
|
|
Contains = @('Configuration: Release, x64')
|
|
NotContains = @('build-arm64') }
|
|
@{ Name = 'arm64 sets the generator platform and deps tree'; Args = @('-d', '--arch', 'arm64')
|
|
Contains = @('-A ARM64', 'deps/build-arm64') }
|
|
@{ Name = 'the architecture is matched case-insensitively'; Args = @('-d', '--arch', 'ARM64')
|
|
Contains = @('-A ARM64', 'deps/build-arm64') }
|
|
@{ Name = 'arm64 under Ninja has no -A but keeps the arm64 tree'; Args = @('-d', '--arch', 'arm64', '-x', '-l')
|
|
Contains = @('deps/build-clang-arm64')
|
|
Match = @('-DCMAKE_C_COMPILER="[^"]+/clang-cl\.exe"')
|
|
NotContains = @('-A ') }
|
|
|
|
'build configurations'
|
|
# One option with a value, driven by a table, so Release is named rather
|
|
# than being whatever is left when no flag is passed.
|
|
@{ Name = 'release is the default'; Args = @('-s')
|
|
Contains = @('-DCMAKE_BUILD_TYPE=Release', 'cmake -B "build" ') }
|
|
@{ Name = '--config release is the default spelled out'; Args = @('-s', '--config', 'release')
|
|
Contains = @('-DCMAKE_BUILD_TYPE=Release', 'cmake -B "build" ') }
|
|
@{ Name = '--config debug builds into build-dbg'; Args = @('-s', '--config', 'debug')
|
|
Contains = @('-DCMAKE_BUILD_TYPE=Debug', 'cmake -B "build-dbg" ') }
|
|
@{ Name = '--config relwithdebinfo builds into build-dbginfo'; Args = @('-s', '--config', 'relwithdebinfo')
|
|
Contains = @('-DCMAKE_BUILD_TYPE=RelWithDebInfo', 'cmake -B "build-dbginfo" ') }
|
|
@{ Name = '--config minsizerel builds into build-minsize'; Args = @('-s', '--config', 'minsizerel')
|
|
Contains = @('-DCMAKE_BUILD_TYPE=MinSizeRel', 'cmake -B "build-minsize" ') }
|
|
# Batch variable names are case-insensitive, so the table lookup is too.
|
|
@{ Name = 'the configuration name is matched case-insensitively'; Args = @('-s', '--config', 'RelWithDebInfo')
|
|
Contains = @('-DCMAKE_BUILD_TYPE=RelWithDebInfo') }
|
|
@{ Name = 'an unknown configuration is rejected and the known ones listed'; Args = @('-s', '--config', 'bogus'); ExpectExit = 1
|
|
Contains = @('Unknown configuration', 'release, debug, relwithdebinfo, minsizerel') }
|
|
# Release, RelWithDebInfo and MinSizeRel all link the /MD dependencies, so
|
|
# one tree serves all three. Debug is /MDd and cannot share.
|
|
@{ Name = 'minsizerel builds against the release deps'; Args = @('-d', '-s', '--config', 'minsizerel')
|
|
Contains = @('cmake -S deps -B "deps/build"', 'cmake -B "build-minsize" ')
|
|
NotContains = @('deps/build-minsize') }
|
|
@{ Name = 'relwithdebinfo builds against them too'; Args = @('-d', '-s', '-l', '--config', 'relwithdebinfo')
|
|
Contains = @('cmake -S deps -B "deps/build-clang"', 'cmake -B "build-dbginfo-clang" ')
|
|
NotContains = @('deps/build-dbginfo') }
|
|
@{ Name = 'debug keeps a dependency tree of its own'; Args = @('-d', '--config', 'debug')
|
|
Contains = @('cmake -S deps -B "deps/build-dbg"') }
|
|
# The build tree and the deps tree now have different names, so the
|
|
# derivation from the binary directory name cannot work and it is named.
|
|
@{ Name = 'a shared deps tree is named outright'; Args = @('-s', '-l', '--config', 'relwithdebinfo')
|
|
Match = @('-DDEP_BUILD_DIR="[A-Za-z]:\\.*\\deps\\build-clang"') }
|
|
@{ Name = 'configuration and arch combine into build-dbg-arm64'; Args = @('-s', '--config', 'debug', '--arch', 'arm64')
|
|
Contains = @('cmake -B "build-dbg-arm64" ') }
|
|
|
|
'what gets built'
|
|
@{ Name = 'the deps target defaults to deps'; Args = @('-d')
|
|
Contains = @('--target deps') }
|
|
@{ Name = '-t overrides the deps target'; Args = @('-d', '-t', 'dep_Boost')
|
|
Contains = @('--target dep_Boost') }
|
|
@{ Name = 'unit tests are off unless asked for'; Args = @('-s')
|
|
Contains = @('-DBUILD_TESTS=OFF') }
|
|
@{ Name = '--tests turns the unit tests on'; Args = @('-s', '--tests')
|
|
Contains = @('-DBUILD_TESTS=ON') }
|
|
@{ Name = '-a enables ASAN for the slicer'; Args = @('-s', '-a')
|
|
Contains = @('-DSLIC3R_ASAN=ON') }
|
|
@{ Name = '--no-pch turns the precompiled header off'; Args = @('-s', '--no-pch')
|
|
Contains = @('-DSLIC3R_PCH=OFF') }
|
|
@{ Name = '--no-pch says so in the banner'; Args = @('-s', '--no-pch')
|
|
Contains = @('Precompiled header: off') }
|
|
@{ Name = 'the precompiled header is on unless asked'; Args = @('-s')
|
|
NotContains = @('SLIC3R_PCH') }
|
|
@{ Name = '--no-pch works without a cache'; Args = @('-s', '--no-pch')
|
|
NotContains = @('COMPILER_LAUNCHER') }
|
|
@{ Name = 'the slicer build runs gettext'; Args = @('-s')
|
|
Contains = @('run_gettext.bat') }
|
|
# tools\7z.exe needs a 7z.dll beside it, which the repo does not carry,
|
|
# so the pack falls back to the bsdtar Windows ships. Either is correct;
|
|
# what matters is that one is chosen and handed the right names.
|
|
@{ Name = '-p packs the deps tree with whichever archiver is usable'; Args = @('-d', '-p')
|
|
Match = @('^\+ .*(7z\.exe a|tar\.exe -a -c -f) OrcaSlicer_dep_win-\S+\.zip OrcaSlicer_dep$') }
|
|
# The bundle is only good for what built it, so it carries the same three
|
|
# axes as the tree. Release x64 on cl keeps the historical plain name.
|
|
@{ Name = 'the bundle name carries compiler and deps flavour'; Args = @('-p', '-l', '--config', 'debug', '--arch', 'arm64')
|
|
Contains = @('OrcaSlicer_dep_win-ARM64-clang-dbg_') }
|
|
@{ Name = 'a relwithdebinfo pack is the release bundle'; Args = @('-p', '--config', 'relwithdebinfo')
|
|
Contains = @('OrcaSlicer_dep_win-x64_')
|
|
NotContains = @('-dbg', 'dbginfo') }
|
|
@{ Name = 'a plain release bundle keeps its old name'; Args = @('-p')
|
|
Contains = @('OrcaSlicer_dep_win-x64_')
|
|
NotContains = @('-clang', '-Release') }
|
|
@{ Name = 'the bundle is stamped with today, not a shuffled date'; Args = @('-p')
|
|
Match = @($stampPattern) }
|
|
# powershell.exe is not in System32 itself, so a trimmed PATH used to
|
|
# leave the stamp empty and the bundle named OrcaSlicer_dep_win-x64_.zip.
|
|
@{ Name = 'the bundle is stamped even with a bare PATH'; Args = @('-p')
|
|
Env = @{ PATH = 'C:\Windows\system32;C:\Windows' }
|
|
Match = @($stampPattern) }
|
|
@{ Name = '-p packs without rebuilding'; Args = @('-p')
|
|
Match = @('^\+ .*(7z\.exe a|tar\.exe -a -c -f) ')
|
|
NotContains = @('cmake -S deps') }
|
|
@{ Name = 'deps and slicer build in one invocation'; Args = @('-d', '-s', '-x', '-l')
|
|
Contains = @('cmake -S deps', 'cmake -B "build-clang" ') }
|
|
|
|
'the compiler cache'
|
|
@{ Name = '--cache needs clang-cl and Ninja'; Args = @('-s', '--cache', 'ccache'); ExpectExit = 1
|
|
Contains = @('needs clang-cl and Ninja') }
|
|
# cl.exe is out of scope, since ccache refuses every compile under /Zi.
|
|
@{ Name = '--cache under Ninja still needs clang-cl'; Args = @('-s', '-x', '--cache', 'ccache'); ExpectExit = 1
|
|
Contains = @('needs clang-cl and Ninja') }
|
|
@{ Name = 'an unknown --cache value is rejected'; Args = @('-s', '-x', '--cache', 'nope'); ExpectExit = 1
|
|
Contains = @('Expected ccache, sccache or off') }
|
|
# A bare PATH, since the machine running the tests may have sccache installed.
|
|
@{ Name = 'a --cache tool that is not there is caught early'; Args = @('-s', '-l', '-x', '--cache', 'sccache'); ExpectExit = 1
|
|
Env = @{ PATH = 'C:\Windows\system32;C:\Windows' }
|
|
Contains = @('is not on PATH') }
|
|
@{ Name = '--cache takes any casing'; Args = @('-s', '-l', '-x', '--cache', 'CCACHE')
|
|
Env = @{ PATH = $ccacheOnPath }
|
|
Contains = @('ccache.exe') }
|
|
@{ Name = '--cache off asks for no launcher'; Args = @('-s', '-x', '--cache', 'off')
|
|
NotContains = @('COMPILER_LAUNCHER') }
|
|
@{ Name = 'no --cache asks for no launcher'; Args = @('-s', '-x')
|
|
NotContains = @('COMPILER_LAUNCHER') }
|
|
@{ Name = '--cache turns the precompiled header off'; Args = @('-s', '-l', '-x', '--cache', 'ccache')
|
|
Env = @{ PATH = $ccacheOnPath }
|
|
Contains = @('-DSLIC3R_PCH=OFF', 'COMPILER_LAUNCHER') }
|
|
# Without it the objects name the build directory and only that tree can use them.
|
|
@{ Name = '--cache asks for relative debug paths'; Args = @('-s', '-l', '-x', '--cache', 'ccache')
|
|
Env = @{ PATH = $ccacheOnPath }
|
|
Contains = @('-DSLIC3R_RELATIVE_DEBUG_PATHS=ON') }
|
|
@{ Name = 'no --cache leaves the debug paths alone'; Args = @('-s', '-l', '-x')
|
|
NotContains = @('SLIC3R_RELATIVE_DEBUG_PATHS') }
|
|
# The resolved path, not the bare name, so PATH cannot change it later.
|
|
@{ Name = '--cache names the resolved path in the banner'; Args = @('-s', '-l', '-x', '--cache', 'ccache')
|
|
Env = @{ PATH = $ccacheOnPath }
|
|
Match = @('^Compiler cache: .*/ccache\.exe$') }
|
|
@{ Name = '--cache reaches the dependency configure too'; Args = @('-d', '-l', '-x', '--cache', 'ccache')
|
|
Env = @{ PATH = $ccacheOnPath }
|
|
Contains = @('-DCMAKE_C_COMPILER_LAUNCHER=') }
|
|
# Nothing records a launcher without a configure, so the tool is not needed.
|
|
# Reaching the cmake check on a bare PATH is what proves it was skipped.
|
|
@{ Name = '--no-configure asks for no cache tool'; Args = @('-s', '-l', '-x', '--no-configure', '--cache', 'ccache'); ExpectExit = 1
|
|
Env = @{ PATH = 'C:\Windows\system32;C:\Windows' }
|
|
Contains = @('CMake was not found')
|
|
NotContains = @('is not on PATH') }
|
|
|
|
'the developer loop'
|
|
@{ Name = '--slicer-target builds one target'; Args = @('-s', '--slicer-target', 'libslic3r')
|
|
Contains = @('--config Release --target libslic3r') }
|
|
@{ Name = '--no-configure skips the slicer configure'; Args = @('-s', '--no-configure')
|
|
Contains = @('cmake --build "build"')
|
|
NotContains = @('cmake -B "build" ') }
|
|
@{ Name = '--no-configure skips the deps configure'; Args = @('-d', '--no-configure')
|
|
Contains = @('cmake --build "deps/build"')
|
|
NotContains = @('cmake -S deps') }
|
|
@{ Name = '--no-gettext skips the translation step'; Args = @('-s', '--no-gettext')
|
|
Contains = @('cmake --build "build"')
|
|
NotContains = @('run_gettext') }
|
|
# Installing copies the whole tree again for a layout only releases need,
|
|
# so it is asked for rather than assumed.
|
|
@{ Name = 'nothing is installed unless asked'; Args = @('-s')
|
|
Contains = @('run_gettext')
|
|
NotContains = @('--target install') }
|
|
@{ Name = '-i adds the install step'; Args = @('-s', '-i')
|
|
Contains = @('--target install') }
|
|
# install(TARGETS OrcaSlicer) has no OPTIONAL, so this would fail partway
|
|
# through a build instead of before it.
|
|
@{ Name = 'installing a tree with no executable is refused'; Args = @('-s', '-i', '--slicer-target', 'glad'); ExpectExit = 1
|
|
Contains = @('--install needs the executable')
|
|
NotContains = @('cmake --build') }
|
|
# Without -s there is no install step, so there is nothing to refuse.
|
|
@{ Name = 'the same flags without a slicer build are left alone'; Args = @('-d', '-i', '--slicer-target', 'glad')
|
|
Contains = @('cmake -S deps')
|
|
NotContains = @('--install needs the executable') }
|
|
@{ Name = 'naming the executable target is allowed'; Args = @('-s', '-i', '--slicer-target', 'OrcaSlicer')
|
|
Contains = @('--target install') }
|
|
# Ninja counts compilers, so -j goes on the command line. MSBuild's -j
|
|
# counts projects while /MP still runs one cl per core inside each, so the
|
|
# cap goes to CL_MPCount in the environment instead. A dry run can only
|
|
# show that no -j is passed.
|
|
@{ Name = '-j on Ninja passes -j to cmake'; Args = @('-s', '-x', '-j', '4')
|
|
Contains = @('Parallel jobs: 4', '--config Release -j 4') }
|
|
@{ Name = '-j on Ninja reaches the deps build'; Args = @('-d', '-x', '-j', '2')
|
|
Contains = @('--target deps -j 2') }
|
|
@{ Name = '-j on MSBuild does not pass -j, which would count projects'; Args = @('-s', '-j', '4')
|
|
Contains = @('Parallel jobs: 4')
|
|
NotContains = @('-j 4') }
|
|
@{ Name = '-j on MSBuild leaves the deps build without -j too'; Args = @('-d', '-j', '2')
|
|
Contains = @('--target deps')
|
|
NotContains = @('-j 2') }
|
|
@{ Name = 'no -j means no job limit'; Args = @('-s')
|
|
NotContains = @('parallel jobs', '-j ') }
|
|
@{ Name = 'a non-numeric -j is rejected'; Args = @('-s', '-j', 'abc'); ExpectExit = 1
|
|
Contains = @('Invalid --jobs value') }
|
|
@{ Name = 'a zero -j is rejected'; Args = @('-s', '-j', '0'); ExpectExit = 1
|
|
Contains = @('Invalid --jobs value') }
|
|
@{ Name = 'the loop options combine into a single build command'; Args = @('-s', '-x', '--no-configure', '--no-gettext', '--slicer-target', 'libslic3r_tests', '-j', '8')
|
|
Contains = @('--target libslic3r_tests -j 8')
|
|
NotContains = @('cmake -B "build" ', 'run_gettext', '--target install') }
|
|
|
|
'one tree per configuration, compiler and architecture'
|
|
# CMake resets its cache and carries on when the compiler changes under an
|
|
# existing tree, leaving stamps from the old toolchain. Give each its own.
|
|
@{ Name = 'clang builds land in their own trees'; Args = @('-d', '-s', '-l')
|
|
Contains = @('cmake -S deps -B "deps/build-clang"', 'cmake -B "build-clang" ') }
|
|
@{ Name = 'MSVC keeps the historical plain names'; Args = @('-d', '-s', '--msvc')
|
|
Contains = @('cmake -S deps -B "deps/build"', 'cmake -B "build" ')
|
|
NotContains = @('build-clang') }
|
|
@{ Name = 'configuration, compiler and arch all name the tree'; Args = @('-d', '-s', '-l', '--config', 'debug', '--arch', 'arm64')
|
|
Contains = @('cmake -S deps -B "deps/build-dbg-clang-arm64"', 'cmake -B "build-dbg-clang-arm64" ') }
|
|
|
|
'locating the dependency tree'
|
|
# Without --deps-dir nothing is passed, so CMakeLists derives the path from
|
|
# the binary directory name as it always has.
|
|
@{ Name = 'the deps path is left to CMake by default'; Args = @('-s')
|
|
NotContains = @('-DDEP_BUILD_DIR') }
|
|
@{ Name = '--deps-dir tells the slicer where the deps are'; Args = @('-s', '--deps-dir', 'D:\orca-deps')
|
|
Contains = @('-DDEP_BUILD_DIR="D:\orca-deps"') }
|
|
@{ Name = '--deps-dir also redirects the deps build'; Args = @('-d', '--deps-dir', 'D:\orca-deps')
|
|
Contains = @('cmake -S deps -B "D:\orca-deps"', 'cmake --build "D:\orca-deps"')
|
|
NotContains = @('deps/build') }
|
|
# Paths are quoted throughout, so one with spaces survives the whole run.
|
|
@{ Name = 'a deps path with spaces survives'; Args = @('-d', '-s', '--deps-dir', 'C:\Program Files\deps')
|
|
Contains = @('-B "C:\Program Files\deps"', '-DDEP_BUILD_DIR="C:\Program Files\deps"') }
|
|
@{ Name = '--deps-dir also redirects the pack'; Args = @('-p', '--deps-dir', 'D:\orca-deps')
|
|
Contains = @('cd /d "D:\orca-deps"') }
|
|
@{ Name = 'pack uses an absolute default path'; Args = @('-p')
|
|
Match = @('^\+ cd /d "[A-Za-z]:\\.*\\deps\\build"') }
|
|
# CMakeLists derives DEP_BUILD_DIR from the build directory's name, so
|
|
# pointing the build elsewhere has to name the deps tree outright.
|
|
@{ Name = '--build-dir moves the slicer build'; Args = @('-s', '-l', '-x', '--build-dir', 'out/build/x64-clang')
|
|
Contains = @('cmake -B "out/build/x64-clang" ') }
|
|
@{ Name = '--build-dir still names the deps tree'; Args = @('-s', '-l', '-x', '--build-dir', 'out/build/x64-clang')
|
|
Match = @('-DDEP_BUILD_DIR="[A-Za-z]:\\.*\\deps\\build-clang"') }
|
|
@{ Name = '--deps-dir wins over the derived tree'; Args = @('-s', '--build-dir', 'out/build/x64-clang', '--deps-dir', 'D:\orca-deps')
|
|
Contains = @('-DDEP_BUILD_DIR="D:\orca-deps"') }
|
|
# A value ending in a backslash escapes the closing quote it is spliced
|
|
# into, so cmake would receive D:\tree" and swallow the next argument.
|
|
@{ Name = 'a trailing backslash is trimmed off a path'; Args = @('-s', '--build-dir', 'D:\tree\')
|
|
Contains = @('cmake -B "D:\tree" ') }
|
|
@{ Name = 'a build directory with spaces survives'; Args = @('-s', '--build-dir', 'C:\Program Files\tree')
|
|
Contains = @('cmake -B "C:\Program Files\tree" ') }
|
|
@{ Name = 'the default build names no deps tree'; Args = @('-s')
|
|
NotContains = @('DEP_BUILD_DIR') }
|
|
|
|
'saying what mode and toolchain are in play'
|
|
@{ Name = 'a dry run announces itself before the first command'; Args = @('-u')
|
|
First = '^Dry run: printing commands without running them\.$' }
|
|
@{ Name = 'the announcement leads even a full build'; Args = @('-ds')
|
|
First = '^Dry run: ' }
|
|
# Autodetect only runs for the Visual Studio generator, and only when no
|
|
# release was pinned, so it needs a Visual Studio on the machine.
|
|
@{ Name = 'the detected Visual Studio names its release year'; Args = @('-s')
|
|
Match = @('^Detected Visual Studio \d+ \(20\d\d\)$') }
|
|
@{ Name = 'pinning a release skips detection'; Args = @('-s', '--vs', '2022')
|
|
NotContains = @('Detected Visual Studio') }
|
|
|
|
'an action has to be asked for'
|
|
# Neither can happen without building the slicer, so they stand alone the
|
|
# way --install-vs does.
|
|
@{ Name = '--run-tests is an action on its own'; Args = @('--run-tests')
|
|
Contains = @('-DBUILD_TESTS=ON', 'ctest --test-dir')
|
|
NotContains = @('Nothing to do') }
|
|
@{ Name = '--tests is too'; Args = @('--tests')
|
|
Contains = @('-DBUILD_TESTS=ON')
|
|
NotContains = @('Nothing to do', 'ctest --test-dir') }
|
|
# Naming an action means that action, not a fuller build.
|
|
@{ Name = 'they do not add a slicer build to one already asked for'; Args = @('-d', '--tests')
|
|
Contains = @('cmake -S deps')
|
|
NotContains = @('cmake -B "build"') }
|
|
@{ Name = 'shaping options alone are not an action'; Args = @('--config', 'debug'); ExpectExit = 1
|
|
Contains = @('Nothing to do.')
|
|
NotContains = @('Build completed') }
|
|
@{ Name = '-j alone is not an action either'; Args = @('-j', '4'); ExpectExit = 1
|
|
Contains = @('Nothing to do.') }
|
|
@{ Name = '--install-vs counts as an action on its own'; Args = @('--install-vs', 'ide')
|
|
NotContains = @('Nothing to do.') }
|
|
|
|
'diagnostics'
|
|
@{ Name = '-v asks cmake for the command lines'; Args = @('-s', '-v')
|
|
Contains = @('--verbose') }
|
|
@{ Name = '-v applies to the deps build too'; Args = @('-d', '-v')
|
|
Contains = @('--target deps', '--verbose') }
|
|
# ninja's own default shows neither a percentage nor a time. %w and %W
|
|
# need ninja 1.12, and an unknown placeholder is fatal, so the format is
|
|
# chosen from the version rather than hardcoded.
|
|
@{ Name = '-v names the ninja progress format'; Args = @('-s', '-x', '-v')
|
|
Match = @('^Ninja progress format: \[.*%p.*\]') }
|
|
@{ Name = 'a ninja older than 1.12 gets the format it understands'; Args = @('-s', '-x', '-v')
|
|
Env = @{ PATH = $ninjaPaths['old'] }
|
|
Contains = @('Ninja progress format: [%s/%t %p :: %e]') }
|
|
@{ Name = 'ninja 1.12 gets elapsed and remaining'; Args = @('-s', '-x', '-v')
|
|
Env = @{ PATH = $ninjaPaths['new'] }
|
|
Contains = @('Ninja progress format: [%f/%t %p :: %w / %W]') }
|
|
@{ Name = 'a format you set yourself is left alone'; Args = @('-s', '-x', '-v')
|
|
Env = @{ NINJA_STATUS = '[mine] ' }
|
|
Contains = @('Ninja progress format: [mine]') }
|
|
@{ Name = 'MSBuild builds mention no ninja format'; Args = @('-s', '-v')
|
|
NotContains = @('Ninja progress format') }
|
|
@{ Name = 'builds are quiet without -v'; Args = @('-s')
|
|
NotContains = @('--verbose') }
|
|
|
|
'installing prerequisites'
|
|
# -u installs CMake, Perl and Git. Visual Studio is a separate ask, because
|
|
# most people already have it, and which one you want is a real choice.
|
|
@{ Name = '-u alone installs no Visual Studio'; Args = @('-u')
|
|
Contains = @('Kitware.CMake', 'StrawberryPerl', 'Git.Git')
|
|
NotContains = @('Microsoft.VisualStudio') }
|
|
# Asking for Visual Studio is asking to install prerequisites, so it does
|
|
# not also need -u; requiring both meant --install-vs alone did nothing.
|
|
@{ Name = '--install-vs works without -u'; Args = @('--install-vs', 'ide')
|
|
Contains = @('id=Microsoft.VisualStudio.Community', 'Kitware.CMake', 'Git.Git') }
|
|
@{ Name = '--install-vs buildtools asks for the build tools'; Args = @('-u', '--install-vs', 'buildtools')
|
|
Contains = @('id=Microsoft.VisualStudio.BuildTools')
|
|
NotContains = @('VC.CoreIde') }
|
|
@{ Name = '--install-vs ide asks for Community with the IDE component'; Args = @('-u', '--install-vs', 'ide')
|
|
Contains = @('id=Microsoft.VisualStudio.Community', 'VC.CoreIde') }
|
|
@{ Name = 'an unknown edition is rejected and the known ones listed'; Args = @('-u', '--install-vs', 'bogus'); ExpectExit = 1
|
|
Contains = @('Unknown Visual Studio edition', 'buildtools, ide') }
|
|
@{ Name = '--vs picks which release to install'; Args = @('-u', '--vs', '2022', '--install-vs', 'buildtools')
|
|
Contains = @('id=Microsoft.VisualStudio.2022.BuildTools') }
|
|
@{ Name = '-l adds the clang compiler and the MSBuild toolset'; Args = @('-u', '--install-vs', 'buildtools', '-l')
|
|
Contains = @('VC.Llvm.Clang ', 'VC.Llvm.ClangToolset') }
|
|
# CMake 4.x breaks Boost.Context on ARM64, so the installer pins 3.31 there
|
|
# and leaves x64 on the current release.
|
|
@{ Name = 'CMake is pinned to 3.31 when installing for arm64'; Args = @('-u', '--arch', 'arm64')
|
|
Contains = @('Kitware.CMake --version 3.31.8') }
|
|
@{ Name = 'CMake is not pinned for x64'; Args = @('-u', '--arch', 'x64')
|
|
Contains = @('Kitware.CMake')
|
|
NotContains = @('--version') }
|
|
@{ Name = 'installing for arm64 asks for the ARM64 toolset'; Args = @('--install-vs', 'buildtools', '--arch', 'arm64')
|
|
Contains = @('Microsoft.VisualStudio.Component.VC.Tools.ARM64') }
|
|
@{ Name = 'an x64 install asks only for the x64 toolset'; Args = @('--install-vs', 'buildtools')
|
|
Contains = @('Microsoft.VisualStudio.Component.VC.Tools.x86.x64')
|
|
NotContains = @('VC.Tools.ARM64') }
|
|
|
|
'dry run changes nothing'
|
|
# clean_tree resolves the path before removing it, so the line echoed is
|
|
# absolute. It is the last thing printed before a directory goes.
|
|
@{ Name = '-c echoes the deps rmdir rather than running it'; Args = @('-d', '-c')
|
|
Match = @('^\+ rmdir /S /Q "[A-Za-z]:\\.*\\deps\\build"$') }
|
|
@{ Name = '-c removes the tree --deps-dir named, not the default one'; Args = @('-d', '-c', '--deps-dir', 'D:\orca-deps')
|
|
Match = @('^\+ rmdir /S /Q "D:\\orca-deps"$')
|
|
NotContains = @('\deps\build') }
|
|
@{ Name = '-c echoes the slicer rmdir rather than running it'; Args = @('-s', '-c')
|
|
Match = @('^\+ rmdir /S /Q "[A-Za-z]:\\.*\\build"$') }
|
|
@{ Name = 'cleaning a slicer build leaves the deps tree alone'; Args = @('-s', '-c')
|
|
NotMatch = @('rmdir.*\\deps\\') }
|
|
@{ Name = 'cleaning both builds removes both trees'; Args = @('-d', '-s', '-c')
|
|
Match = @('^\+ rmdir /S /Q "[A-Za-z]:\\[^"]*\\deps\\build"$',
|
|
'^\+ rmdir /S /Q "[A-Za-z]:\\(?!.*\\deps\\)[^"]*\\build"$') }
|
|
|
|
# Nothing below should be reachable. They are a floor under a bug that
|
|
# hands clean_tree a path far shorter than it looks.
|
|
@{ Name = 'a configuration whose lookup comes back empty is rejected'; Args = @('-d', '-c', '--config', 'release '); ExpectExit = 1
|
|
Contains = @('Unknown configuration')
|
|
NotContains = @('rmdir') }
|
|
@{ Name = 'a drive root is refused'; Args = @('-d', '-c', '--deps-dir', 'D:\'); ExpectExit = 1
|
|
Contains = @('that is a drive root')
|
|
NotContains = @('+ rmdir') }
|
|
@{ Name = 'the repository itself is refused'; Args = @('-d', '-c', '--deps-dir', '.'); ExpectExit = 1
|
|
Contains = @('that is the repository itself')
|
|
NotContains = @('+ rmdir') }
|
|
@{ Name = '-k echoes the taskkills rather than running them'; Args = @('-k')
|
|
Contains = @('+ taskkill /F /IM MSBuild.exe', '+ taskkill /F /IM cl.exe') }
|
|
@{ Name = '-k also covers the Ninja toolchain'; Args = @('-k')
|
|
Contains = @('+ taskkill /F /IM ninja.exe', '+ taskkill /F /IM clang-cl.exe') }
|
|
@{ Name = '-k announces the dry run like every other action'; Args = @('-k')
|
|
First = '^Dry run: ' }
|
|
@{ Name = '-u echoes the winget installs rather than running them'; Args = @('-u')
|
|
Contains = @('+ winget install', 'Kitware.CMake')
|
|
NotContains = @('cmake -S deps') }
|
|
# Not a dry run: winget is a stub that fails, so nothing is installed.
|
|
@{ Name = 'a failed install is reported, not claimed as success'; Args = @('-u')
|
|
DryRun = $false; ExpectExit = 1
|
|
Env = @{ PATH = $stubPath }
|
|
Contains = @('Failed to install:', 'CMake', 'Perl', 'Git')
|
|
NotContains = @('Installed the prerequisites') }
|
|
# The reason belongs inside the frame. Every command this path ran had
|
|
# already succeeded, so naming the last one would point at the wrong thing.
|
|
@{ Name = 'a failed install names the reason, not the last command'; Args = @('-u')
|
|
DryRun = $false; ExpectExit = 1
|
|
Env = @{ PATH = $stubPath }
|
|
Contains = @('####', 'Failed to install:')
|
|
NotContains = @('Failed: winget') }
|
|
@{ Name = 'a dry run does not claim the install happened'; Args = @('-u')
|
|
Contains = @('Dry run: nothing was installed.')
|
|
NotContains = @('are in place') }
|
|
@{ Name = '-u with a build defers that build, not a fuller one'; Args = @('-u', '-d')
|
|
Contains = @('build_win.bat -d')
|
|
NotContains = @('build_win.bat -ds') }
|
|
@{ Name = '-u on its own suggests the whole build'; Args = @('-u')
|
|
Contains = @('build_win.bat -ds') }
|
|
@{ Name = 'a dry run only echoes, it never configures'; Args = @('-d')
|
|
Contains = @('+ cmake')
|
|
NotContains = @('CMake Error', 'Configuring done') }
|
|
|
|
'extra configure arguments'
|
|
# --deps-args and --slicer-args are declared "rawstring", so a value that
|
|
# looks like an option is allowed through. Plain string options still
|
|
# reject one, since there it almost always means a forgotten value.
|
|
@{ Name = '--deps-args reaches the deps configure'; Args = @('-d', '--deps-args', 'FOO')
|
|
Contains = @('-DCMAKE_BUILD_TYPE=Release FOO') }
|
|
@{ Name = '--slicer-args reaches the slicer configure'; Args = @('-s', '--slicer-args', 'BAZ')
|
|
Contains = @('BAZ') }
|
|
@{ Name = '--deps-args accepts a value that starts with a dash'; Args = @('-d', '--deps-args', '-DFOO')
|
|
Contains = @('-DFOO') }
|
|
@{ Name = 'a plain string option still rejects a dash-leading value'; Args = @('-d', '--deps-target', '--tests'); ExpectExit = 1
|
|
Contains = @('looks like another option') }
|
|
# cmd splits arguments on "=" as well as spaces, so an unquoted -D reaches
|
|
# the script as two arguments however it was invoked.
|
|
@{ Name = 'an unquoted value containing = is rejected'; Args = @('-d', '--deps-args', 'FOO=BAR'); ExpectExit = 1
|
|
Contains = @('Unknown argument') }
|
|
# The environment overrides exist for that reason; nothing tokenises them.
|
|
@{ Name = 'ORCA_DEPS_CMAKE_ARGS reaches the deps configure'; Args = @('-d')
|
|
Env = @{ ORCA_DEPS_CMAKE_ARGS = '-DFOO=BAR -DBAZ=QUX' }
|
|
Contains = @('-DFOO=BAR -DBAZ=QUX') }
|
|
@{ Name = 'ORCA_SLICER_CMAKE_ARGS reaches the slicer configure'; Args = @('-s')
|
|
Env = @{ ORCA_SLICER_CMAKE_ARGS = '-DWANTED=1' }
|
|
Contains = @('-DWANTED=1') }
|
|
@{ Name = 'the deps override does not leak into the slicer configure'; Args = @('-s')
|
|
Env = @{ ORCA_DEPS_CMAKE_ARGS = '-DDEPSONLY=1' }
|
|
NotContains = @('-DDEPSONLY=1') }
|
|
@{ Name = 'the help points at the environment for a spaced argument'; Args = @('--help'); DryRun = $false
|
|
Contains = @('Neither form supports a value containing an ampersand') }
|
|
@{ Name = 'the help lists the environment overrides'; Args = @('--help'); DryRun = $false
|
|
Contains = @('Environment:', 'ORCA_DEPS_CMAKE_ARGS', 'ORCA_SLICER_CMAKE_ARGS') }
|
|
|
|
'running the unit tests'
|
|
@{ Name = '--tests builds them without running them'; Args = @('-s', '--tests')
|
|
Contains = @('-DBUILD_TESTS=ON')
|
|
NotContains = @('ctest') }
|
|
@{ Name = '--run-tests builds and runs them'; Args = @('-s', '--run-tests')
|
|
Contains = @('-DBUILD_TESTS=ON', 'ctest --test-dir "build/tests" -C Release --output-on-failure') }
|
|
@{ Name = '--run-tests follows the build type and directory'; Args = @('-s', '--run-tests', '--config', 'debug')
|
|
Contains = @('ctest --test-dir "build-dbg/tests" -C Debug') }
|
|
@{ Name = 'no tests are run by default'; Args = @('-s')
|
|
NotContains = @('ctest') }
|
|
|
|
'failures are reported'
|
|
@{ Name = 'a missing cmake is caught and exits non-zero'; Args = @('-d'); ExpectExit = 1
|
|
Env = @{ PATH = 'C:\Windows\system32;C:\Windows' }
|
|
Contains = @('CMake was not found') }
|
|
@{ Name = 'packing does not need cmake, only an archiver'; Args = @('-p')
|
|
Env = @{ PATH = 'C:\Windows\system32;C:\Windows' }
|
|
NotContains = @('CMake was not found') }
|
|
# Not a dry run: a cd to a missing drive is a real failure inside a
|
|
# parenthesised block, which is where exit /b silently loses its code.
|
|
# Without the jump to :die this exits 0 and a failed build reads as a
|
|
# successful one.
|
|
@{ Name = 'a failure inside a build block reaches the caller'; Args = @('-p', '--deps-dir', 'Z:\nope')
|
|
DryRun = $false; ExpectExit = 1
|
|
Contains = @('Exit code 1.', '####')
|
|
NotContains = @('Build completed', 'Try') }
|
|
# The retry follows the stage that failed. Offering it for the whole run
|
|
# would clean a dependency tree that was not at fault.
|
|
@{ Name = 'a failure names a retry scoped to the stage that failed'; Args = @('-d', '-s', '--deps-dir', 'Z:\nope')
|
|
DryRun = $false; ExpectExit = 1
|
|
Contains = @('build_win.bat -d --deps-dir "Z:\nope" -c')
|
|
NotContains = @('build_win.bat -ds') }
|
|
# CMake's own failure here is hundreds of lines about package resolution.
|
|
@{ Name = 'a missing dependency tree is named, not left to CMake'; Args = @('-s', '--deps-dir', 'Z:\nope')
|
|
DryRun = $false; ExpectExit = 1
|
|
Contains = @('Dependencies not found at', 'Build them with build_win.bat -d --deps-dir "Z:\nope"')
|
|
NotContains = @('cmake -B', 'Try') }
|
|
# Every other suggestion carries the flags that reproduce the run; a bare
|
|
# -d would point at the MSVC tree after a clang build.
|
|
@{ Name = 'the missing-deps hint names this toolchain'; Args = @('-s', '-l', '-x', '--deps-dir', 'deps/not-built')
|
|
DryRun = $false; ExpectExit = 1
|
|
Contains = @('Build them with build_win.bat -d -l -x --deps-dir "deps/not-built"')
|
|
NotExists = @('deps/not-built') }
|
|
# A dry run configures nothing, so it must not depend on which trees happen
|
|
# to exist on the machine running the suite.
|
|
@{ Name = 'a dry run does not check for the deps tree'; Args = @('-s', '--deps-dir', 'Z:\nope')
|
|
Contains = @('cmake -B "build"')
|
|
NotContains = @('Dependencies not found') }
|
|
# -d is about to build them, so there is nothing to report yet.
|
|
@{ Name = 'building the deps in the same run skips the check'; Args = @('-d', '-s', '--deps-dir', 'Z:\nope')
|
|
DryRun = $false; ExpectExit = 1
|
|
NotContains = @('Dependencies not found') }
|
|
# A configure fails before any compiler runs, so -v has nothing to show.
|
|
@{ Name = 'a configure failure is not offered a verbose rebuild'; Args = @('-d', '--deps-dir', 'Z:\nope')
|
|
DryRun = $false; ExpectExit = 1
|
|
Contains = @('-c discard that tree')
|
|
NotContains = @('-v show the failing') }
|
|
# A bare --no-configure succeeds on a machine that already has a usable
|
|
# build tree, so name one that cannot exist instead.
|
|
@{ Name = 'a build failure is'; Args = @('-s', '--no-configure', '--build-dir', 'deps/no-such-tree')
|
|
DryRun = $false; ExpectExit = 1
|
|
Contains = @('-v show the failing')
|
|
NotExists = @('deps/no-such-tree') }
|
|
# --build-dir names the slicer tree, which a deps failure has nothing to do
|
|
# with. --deps-dir stays, because that is the tree that failed.
|
|
@{ Name = 'a deps retry leaves out the slicer tree'; Args = @('-d', '-s', '--deps-dir', 'Z:\nope', '--build-dir', 'D:\b')
|
|
DryRun = $false; ExpectExit = 1
|
|
Contains = @('build_win.bat -d --deps-dir "Z:\nope" -c')
|
|
NotContains = @('--build-dir') }
|
|
@{ Name = 'an unknown configuration stays a single line'; Args = @('-s', '--config', 'bogus'); ExpectExit = 1
|
|
Contains = @('Unknown configuration')
|
|
NotContains = @('####', 'Try') }
|
|
@{ Name = 'a bad --jobs value is not framed either'; Args = @('-s', '-j', 'x'); ExpectExit = 1
|
|
Contains = @('Invalid --jobs value')
|
|
NotContains = @('####') }
|
|
|
|
'the summary says what to do next'
|
|
# Every suggested command carries the flags that reproduce this run.
|
|
@{ Name = 'a deps build points at the slicer build'; Args = @('-d', '-l')
|
|
Contains = @('Build the slicer build_win.bat -s -l')
|
|
NotContains = @('Run it') }
|
|
@{ Name = 'a ninja slicer build offers a single target'; Args = @('-s', '-l', '-x')
|
|
Contains = @('Rebuild after edits build_win.bat -s -l -x --no-configure', 'Rebuild one target')
|
|
NotContains = @('Solution', 'Open in Visual Studio') }
|
|
@{ Name = 'a visual studio build names the solution instead'; Args = @('-s')
|
|
Contains = @('Solution ', 'Open in Visual Studio build\OrcaSlicer.sln',
|
|
'Rebuild after edits build_win.bat -s --no-configure')
|
|
NotContains = @('Rebuild one target') }
|
|
@{ Name = 'the configuration and architecture come back'; Args = @('-s', '-l', '-x', '--config', 'debug', '--arch', 'arm64')
|
|
Contains = @('build_win.bat -s -l -x --config debug --arch arm64 --no-configure') }
|
|
@{ Name = 'the tree overrides come back quoted'; Args = @('-s', '--deps-dir', 'D:\d', '--build-dir', 'D:\b')
|
|
Contains = @('--deps-dir "D:\d" --build-dir "D:\b"') }
|
|
@{ Name = 'a pinned visual studio release comes back'; Args = @('-s', '--vs', '2022')
|
|
Contains = @('build_win.bat -s --vs 2022 --no-configure') }
|
|
# Autodetection writes what it found into the same variable, so a detected
|
|
# release must not come back as though it had been asked for.
|
|
@{ Name = 'a detected release does not'; Args = @('-s')
|
|
NotContains = @('--vs') }
|
|
@{ Name = 'the binary is named in the build tree it was built in'; Args = @('-s', '-l', '-x')
|
|
Contains = @('build-clang\src\Release\orca-slicer.exe') }
|
|
@{ Name = 'installing names the installed copy instead'; Args = @('-s', '-l', '-x', '-i')
|
|
Contains = @('build-clang\OrcaSlicer\orca-slicer.exe') }
|
|
# -i changes where the binary lands, so a rebuild that dropped it would
|
|
# leave the path above pointing at a stale copy.
|
|
@{ Name = 'the rebuild suggestion keeps -i'; Args = @('-s', '-l', '-x', '-i')
|
|
Contains = @('Rebuild after edits build_win.bat -s -l -x -i --no-configure') }
|
|
# A deps retry has no install step to repeat.
|
|
@{ Name = 'a deps retry drops it'; Args = @('-d', '-s', '-i', '--deps-dir', 'Z:\nope')
|
|
DryRun = $false; ExpectExit = 1
|
|
Contains = @('build_win.bat -d --deps-dir "Z:\nope" -c')
|
|
NotContains = @('-d -i') }
|
|
# Naming a target builds it and its dependencies, not its dependents, so
|
|
# the binary on disk is whatever the last full build left there.
|
|
@{ Name = 'a single-target build does not claim the whole binary'; Args = @('-s', '-l', '-x', '--slicer-target', 'glad')
|
|
Contains = @('Target glad', 'Relink the binary build_win.bat -s -l -x --no-configure')
|
|
NotContains = @('Run it', 'orca-slicer.exe', 'Rebuild after edits') }
|
|
# The executable has a target of its own, and naming that one does relink.
|
|
@{ Name = 'naming the executable target still claims the binary'; Args = @('-s', '-l', '-x', '--slicer-target', 'OrcaSlicer')
|
|
Contains = @('Run it', 'orca-slicer.exe', 'Rebuild after edits')
|
|
NotContains = @('Target OrcaSlicer', 'Relink the binary') }
|
|
@{ Name = '--run-tests offers the ctest line'; Args = @('-s', '-l', '-x', '--run-tests')
|
|
Contains = @('Re-run the tests ctest --test-dir build-clang/tests -C Release') }
|
|
@{ Name = 'packing names the bundle and how to use it'; Args = @('-p', '-l')
|
|
Contains = @('Bundle ', 'Share the bundle') }
|
|
# The line supplies its own tree, so the one this run used must not ride
|
|
# along and contradict it.
|
|
@{ Name = 'the bundle line names one tree, not two'; Args = @('-s', '-p', '-l', '-x', '--deps-dir', 'D:\shared')
|
|
Contains = @('then build_win.bat -s -l -x --deps-dir <path>')
|
|
NotContains = @('--deps-dir "D:\shared" --deps-dir') }
|
|
@{ Name = 'installing prerequisites suggests the build that follows'; Args = @('-u', '-l')
|
|
Contains = @('Restart this shell', 'build_win.bat -ds -l')
|
|
NotContains = @('Run it') }
|
|
# Everything below the header line is worked out the same way in either
|
|
# run, which is why a dry run can cover it.
|
|
@{ Name = 'a dry run does not claim a build happened'; Args = @('-s', '-l', '-x')
|
|
Contains = @('Dry run: nothing was built.')
|
|
NotContains = @('Build completed in') }
|
|
# --no-configure is the iteration loop and still gets the block; four
|
|
# lines after a rebuild is not enough to be worth suppressing.
|
|
@{ Name = '--no-configure still gets the summary'; Args = @('-s', '-l', '-x', '--no-configure')
|
|
Contains = @('Next', 'Rebuild after edits') }
|
|
|
|
'pointing at the solution'
|
|
# The extension follows the generator, so these two pin the release and a
|
|
# build directory that cannot already hold a solution of either kind.
|
|
@{ Name = 'the 2026 generator gets the XML solution'; Args = @('-s', '--vs', '2026', '--build-dir', 'D:\tree')
|
|
Contains = @('Solution D:\tree\OrcaSlicer.slnx', 'Open in Visual Studio D:\tree\OrcaSlicer.slnx') }
|
|
@{ Name = 'the releases before it get the classic one'; Args = @('-s', '--vs', '2022', '--build-dir', 'D:\tree')
|
|
Contains = @('Solution D:\tree\OrcaSlicer.sln', 'Open in Visual Studio D:\tree\OrcaSlicer.sln') }
|
|
@{ Name = 'a solution already on disk wins over the generator'; Args = @('-s', '--vs', '2026', '--build-dir', $slnDir)
|
|
Match = @('^ Solution .*\\OrcaSlicer\.sln$') }
|
|
# Extension-agnostic from here: these cases are about the directory, and
|
|
# the release is whatever is installed.
|
|
@{ Name = 'the VS generator says where the solution is'; Args = @('-s')
|
|
Match = @('^ Solution .*\\build\\OrcaSlicer\.slnx?$') }
|
|
@{ Name = 'the solution path follows the configuration'; Args = @('-s', '--config', 'debug')
|
|
Match = @('^ Solution .*\\build-dbg\\OrcaSlicer\.slnx?$') }
|
|
@{ Name = 'the solution line survives an install'; Args = @('-s', '-i')
|
|
Contains = @(' Solution ') }
|
|
# The path is resolved, not pasted onto the repository root, so it is
|
|
# right whether --build-dir came absolute or with forward slashes.
|
|
@{ Name = 'a moved build still prints one real path'; Args = @('-s', '--build-dir', 'out/build/x64-clang')
|
|
Match = @('^ Solution [A-Za-z]:\\[^/]+\\OrcaSlicer\.slnx?$') }
|
|
@{ Name = 'an absolute --build-dir is not glued onto the repo root'; Args = @('-s', '--build-dir', 'D:\tree')
|
|
Match = @('^ Solution D:\\tree\\OrcaSlicer\.slnx?$') }
|
|
)
|
|
|
|
function Invoke-BuildScript {
|
|
param([string[]] $Arguments, [hashtable] $Environment)
|
|
|
|
$saved = @{}
|
|
if ($Environment) {
|
|
foreach ($key in $Environment.Keys) {
|
|
$saved[$key] = [Environment]::GetEnvironmentVariable($key)
|
|
Set-Item -Path "env:$key" -Value $Environment[$key]
|
|
}
|
|
}
|
|
try {
|
|
# 'Stop' turns a native command's stderr into a terminating error, and
|
|
# a case that exercises a real failure writes to stderr. Let the output
|
|
# through and judge the run by its exit code instead. The assignment is
|
|
# scoped to this function, so the rest of the suite keeps 'Stop'.
|
|
$ErrorActionPreference = 'Continue'
|
|
if ($Arguments.Count -eq 0) {
|
|
$out = & $Script 2>&1 | Out-String
|
|
} else {
|
|
$out = & $Script @Arguments 2>&1 | Out-String
|
|
}
|
|
return [pscustomobject]@{ Output = $out; Exit = $LASTEXITCODE }
|
|
} finally {
|
|
foreach ($key in $saved.Keys) {
|
|
if ($null -eq $saved[$key]) { Remove-Item -Path "env:$key" -ErrorAction SilentlyContinue }
|
|
else { Set-Item -Path "env:$key" -Value $saved[$key] }
|
|
}
|
|
}
|
|
}
|
|
|
|
$knownFields = @(
|
|
'Name', 'Args', 'ExpectExit', 'DryRun', 'First', 'Env',
|
|
'Contains', 'NotContains', 'Match', 'NotMatch', 'NotExists'
|
|
)
|
|
|
|
function Test-Case {
|
|
param([hashtable] $Case)
|
|
|
|
# Read fields with the indexer, not dot notation. A hashtable exposes its
|
|
# own members too, so $Case.Contains returns the Contains *method* whenever
|
|
# the case has no key by that name.
|
|
$argv = @($Case['Args'])
|
|
if (-not $Case.ContainsKey('DryRun') -or $Case['DryRun']) { $argv += '--dry-run' }
|
|
|
|
$expect = 0
|
|
if ($Case.ContainsKey('ExpectExit')) { $expect = $Case['ExpectExit'] }
|
|
|
|
$result = Invoke-BuildScript -Arguments $argv -Environment $Case['Env']
|
|
|
|
$problems = @()
|
|
|
|
# A misspelled field is silently ignored by the checks below, which
|
|
# leaves the case asserting nothing at all and passing.
|
|
foreach ($field in $Case.Keys) {
|
|
if ($knownFields -notcontains $field) { $problems += "unknown field '$field'" }
|
|
}
|
|
|
|
if ($result.Exit -ne $expect) { $problems += "exit $($result.Exit), expected $expect" }
|
|
foreach ($needle in $Case['Contains']) {
|
|
if (-not $result.Output.Contains($needle)) { $problems += "missing '$needle'" }
|
|
}
|
|
foreach ($needle in $Case['NotContains']) {
|
|
if ($result.Output.Contains($needle)) { $problems += "unexpected '$needle'" }
|
|
}
|
|
$lines = $result.Output -split "`r?`n"
|
|
if ($Case['First'] -and $lines[0] -notmatch $Case['First']) {
|
|
$problems += "first line was '$($lines[0])'"
|
|
}
|
|
foreach ($pattern in $Case['Match']) {
|
|
if (@($lines | Where-Object { $_ -match $pattern }).Count -eq 0) {
|
|
$problems += "no line matching /$pattern/"
|
|
}
|
|
}
|
|
foreach ($pattern in $Case['NotMatch']) {
|
|
foreach ($line in @($lines | Where-Object { $_ -match $pattern })) {
|
|
$problems += "line matches /$pattern/: $line"
|
|
}
|
|
}
|
|
# Output cannot show what a run did not create.
|
|
foreach ($path in $Case['NotExists']) {
|
|
$full = Join-Path (Split-Path -Parent $Script) $path
|
|
if (Test-Path $full) {
|
|
$problems += "created '$path'"
|
|
}
|
|
}
|
|
return ,$problems
|
|
}
|
|
|
|
$pass = 0
|
|
$failed = @()
|
|
# Held back so a filtered run does not print headings for groups it skipped.
|
|
$heading = $null
|
|
|
|
foreach ($case in $cases) {
|
|
if ($case -is [string]) {
|
|
$heading = $case
|
|
continue
|
|
}
|
|
if ($Name -and $case['Name'] -notmatch $Name) { continue }
|
|
if ($heading) {
|
|
Write-Host ''
|
|
Write-Host $heading -ForegroundColor Cyan
|
|
$heading = $null
|
|
}
|
|
|
|
$problems = Test-Case -Case $case
|
|
if ($problems.Count -eq 0) {
|
|
$pass++
|
|
Write-Host (' ok ' + $case['Name'])
|
|
} else {
|
|
$failed += $case['Name']
|
|
Write-Host (' FAIL ' + $case['Name']) -ForegroundColor Red
|
|
foreach ($problem in $problems) { Write-Host (' ' + $problem) -ForegroundColor Red }
|
|
Write-Host (' args: ' + (@($case['Args']) -join ' '))
|
|
}
|
|
}
|
|
|
|
Remove-Item -Recurse -Force $fixtures -ErrorAction SilentlyContinue
|
|
|
|
Write-Host ''
|
|
# A pattern that matched nothing has proved nothing, so do not report it as
|
|
# a clean run.
|
|
if ($Name -and $pass -eq 0 -and $failed.Count -eq 0) {
|
|
Write-Host "no case matched /$Name/" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
Write-Host "$pass passed, $($failed.Count) failed"
|
|
if ($failed.Count -gt 0) {
|
|
foreach ($name in $failed) { Write-Host " failed: $name" -ForegroundColor Red }
|
|
exit 1
|
|
}
|
|
exit 0
|