Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.51.0 to 0.52.0. - [Commits](https://github.com/golang/crypto/compare/v0.51.0...v0.52.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.52.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
E2E Test Suite
End-to-end tests for the oh-my-posh shell integrations themselves (not the Go internals under
src/). They generate real init scripts with the oh-my-posh binary, feed them to the actual
shells, and drive interactive sessions in a pseudo-terminal.
This is a separate Go module (github.com/jandedobbeleer/oh-my-posh/e2e, own go.mod/go.sum)
so its dependencies never leak into src/.
Layers
- Syntax (
syntax_test.go) — generate the init script per shell x config overlay and validate it with the shell's own parser (bash -n,zsh -n,fish --no-execute, aSystem.Management.Automation.Language.Parsercall for pwsh,nu-checkfor nu). - Smoke (
smoke_test.go) — boot each shell interactively in a real pty with the generated init script, assert the prompt renders cleanly, a typed command runs, and the shell exits. - Behavior (
features_test.go) — per-feature scenarios: exit-code propagation, transient prompt, right prompt, styling colors, and FTCS marks.
Both layers 2 and 3 drive the shell through harness.Session, a pty wrapper with a vt10x screen
emulator for rendered-screen assertions and a raw-byte buffer for escape-sequence assertions.
The suite targets the "big five" shells: bash, zsh, fish, pwsh, nu.
Running locally
cd e2e
go test -count=1 ./...
Add -v for per-shell/per-overlay output, or -run <Test>/<shell> to scope to one case.
Every test skips cleanly (t.Skip) when a shell's binary is not on PATH, or when the shell is
not supported on the current platform. Set OMP_E2E_REQUIRE to a comma-separated list of shell
names (e.g. pwsh,nu) to turn those skips into hard failures for the listed shells instead —
useful to catch a shell that's supposed to be installed but silently isn't. It defaults to unset
(everything skips); CI sets it to the shells each job installs so an expected shell hard-fails
instead of skipping quietly.
| Shell | Linux/macOS | Windows |
|---|---|---|
| bash | yes (skip if binary missing) | skipped (msys/WSL bash under ConPTY is not representative) |
| zsh | yes (skip if binary missing) | skipped |
| fish | yes (skip if binary missing) | skipped |
| pwsh | yes (skip if binary missing) | yes (skip if binary missing) |
| nu | yes (skip if binary missing) | yes (skip if binary missing) |
Layer 1 (syntax) still runs its non-empty/no-placeholder assertions unconditionally for every shell; only the parser check itself is skipped when the binary is missing.
Building the omp binary
The suite builds ../src once per test run (guarded by sync.Once) into a temp directory and
uses that binary for every init invocation. Set OMP_E2E_BINARY to an absolute path to skip
the build and reuse a prebuilt binary instead — useful when iterating on tests without rebuilding
oh-my-posh every run.
Isolation
Every omp invocation and shell session gets OMP_CACHE_DIR pointed at a fresh t.TempDir(), so
runs never share or pollute a developer's real cache. Sessions also fix TERM=xterm-256color
and a 120x30 pty size.
Adding a new shell
Add an entry to the Shells table in harness/shells.go. A ShellDef needs:
Name— the omp shell name passed tooh-my-posh init <Name>.Binary— the executable looked up viaLookupShellBinary; missing means skip.SyntaxCheck(scriptPath) *exec.Cmd— a command that parses (not executes) the script and exits non-zero on a syntax error.Launch(t, scriptPath, workDir) (bin string, args []string, env []string)— how to boot the shell interactively withscriptPathsourced (e.g. a temp rc file plus-i).Fail— anExitCommand{Command, Code}: a command line that reliably exits non-zero when typed interactively, and the exit code oh-my-posh should report in the next prompt.
If the shell can't be driven faithfully on every platform, add a case to
ShellDef.SupportedOnHost (see the bash/zsh/fish Windows exclusion for the reasoning).
Layers 1-3 all iterate harness.Shells, so a correctly filled-in entry is picked up everywhere
automatically — no test file needs editing for a new shell on its own.
Adding a new feature scenario
features_test.go drives every scenario through one table-driven test, TestFeatures, whose
subtests are named TestFeatures/<scenario>/<shell>. To add a scenario:
- If the feature needs a config change, add an
Overlayfunction toharness/config.gothat mutates the base config map (seeTransient,RPrompt,Colored,ShellIntegrationfor examples). If the overlay changes the generated init script, register it inoverlaySetsinsyntax_test.goso layer 1 covers it too (Coloreddoesn't change the script, so it's left out of that matrix). - Append a
scenarioentry tofeatureScenariosinfeatures_test.go:overlays— theOverlays to apply, if any.skips— amap[string]stringof shell name to skip reason, for shells that don't support the feature (see the bash entries ontransient/rprompt, or the nu entry onftcs, for the pattern). Leave itnilwhen every shell is expected to pass.run— afunc(t *testing.T, sh harness.ShellDef, s *harness.Session)with the scenario's assertions. TheTestFeaturesrunner has already applied the overlays, skipped unsupported shells, started the session and waited for the first prompt by the timerunis called; drive the rest of the session (SendLine,WaitFor) and assert againstScreen()/ScreenLines()for rendered output,Raw()for escape-sequence assertions, orMarkerColor()for a rendered marker's cell colors.
- Never make a shell silently succeed or fail a feature it doesn't support — add it to
skipswith a comment explaining why instead.
Harness internals
Things the harness does that are easy to break by accident:
- DSR replies — PSReadLine on a Unix pty repeatedly queries the cursor position (
CSI 6n) and blocks rendering until it gets a reply. ConPTY answers this internally on Windows; on Linux/macOS the harness itself replies with the vt10x cursor position from its reader goroutine (harness/session.go). Remove that and pwsh-on-Linux wedges until the 30s timeout. - Single pty reader — exactly one goroutine reads the pty and feeds both the vt10x screen and the raw buffer under one mutex. A second reader silently loses buffered data.
- nu vendor autoload — nu loads every
.nuunder$nu.vendor-autoload-dirsafter--config, so a machine with the real oh-my-posh nu integration installed would clobber the test prompt. Sessions pointXDG_DATA_HOMEat an empty temp directory to prevent this. - Absolute binary paths — go-pty's Windows
Cmdresolves bare executable names relative toCmd.DirwhenDiris set, soStartalways resolves the shell binary to an absolute path first. - bash lookup on Windows — plain
PATHlookup finds System32's WSL launcherbash.exe, which cannot run Windows-style script paths;LookupShellBinaryderives Git Bash's location fromgit.exeinstead.
Known limitations
- bash only renders a transient prompt or right prompt inside a
ble.shsession (gated onBLE_SESSION_ID, seesrc/shell/bash.go); this harness's plainbash --noprofile --rcfile ... -isession doesn't provide one, soTestFeatures/transientandTestFeatures/rpromptskip bash explicitly. - nu never emits any FTCS mark:
Features().Nu()'s switch does list a case forFTCSMarks(seesrc/shell/nu.go), but that case deliberately returns an emptyCode, so the generated script never gets the hook that prints them.TestFeatures/ftcsskips nu explicitly. - cmd, elvish, xonsh and yash are not covered by any layer.
CI
.github/workflows/e2e.yml runs this suite on ubuntu-latest (bash, pwsh preinstalled; zsh and
fish installed via apt; nu installed from a pinned GitHub release) and on windows-latest
(pwsh preinstalled; nu installed from a pinned release; bash/zsh/fish skip by design).