Skip to content

feat: [Finally] section + later assert modifier for EXIT NEVER entries #19

@sleipi

Description

@sleipi

Summary

Two new features for EXIT NEVER (background process) entries:

  1. [Finally] section — send a signal to the process at file-end and assert exit behavior
  2. later assert modifier — defer assertion evaluation until file-end

later modifier

Asserts marked with later are not evaluated during polling. They remain open and are checked against the accumulated output at file-end (before [Finally]).

@timeout 5000
php -S localhost:8080
EXIT NEVER
[Asserts]
stderr contains "Development Server"
stderr contains later "[404]: GET / - No such file or directory"

The 404 log is produced by a later entry (e.g. curl localhost:8080). later decouples the assertion from polling timing.

[Finally] section

Entry-scoped section for EXIT NEVER entries. Executed at file-end in LIFO order (before @defer).

Syntax:

[Finally]
<SIGNAL> EXIT <code> [timeout <ms>]
<optional asserts>

Examples:

[Finally]
TERM EXIT 0 timeout 3000
stderr contains "graceful shutdown... bye"
[Finally]
KILL EXIT 137
[Finally]
TERM EXIT 0

Semantics

  • SIGNAL — signal name to send (TERM, KILL, INT, HUP, QUIT, etc.)
  • EXIT <code> — expected exit code (explicit, because processes may catch signals and exit cleanly)
  • timeout <ms> — max wait time for process to die (default: 1000ms)
  • If timeout reached → FAIL (no automatic SIGKILL escalation)
  • Asserts after the signal line evaluate against the process output after signal delivery

Signals and exit codes

Signal Unhandled exit code
TERM 143 (128+15)
KILL 137 (128+9)
INT 130 (128+2)
HUP 129 (128+1)
QUIT 131 (128+3)

Processes with signal handlers may exit with any code (e.g. EXIT 0 for graceful shutdown).

Execution order at file-end

  1. All regular entries complete
  2. later asserts evaluated (against accumulated output)
  3. [Finally] sections executed (LIFO)
  4. @defer entries executed (LIFO, cleanup fallback)

Full example

# Start web server
@timeout 5000
php -S localhost:8080
EXIT NEVER
[Asserts]
stderr contains "Development Server"
stderr contains later "[404]: GET / - No such file or directory"
[Finally]
TERM EXIT 0 timeout 3000
stderr contains "graceful shutdown... bye"

# Trigger 404 log
curl localhost:8080
EXIT 0

Design decisions

  • @timeout only controls assert polling deadline (unchanged semantics)
  • later ignores @timeout — it waits until file-end
  • [Finally] is decoupled from @timeout — always triggers at file-end
  • timeout in [Finally] is a keyword on the signal line, not a directive (directives only appear before commands)
  • Default Finally timeout: 1000ms
  • No automatic SIGKILL fallback on timeout — @defer handles cleanup if needed

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions