Skip to main content

CHANGELOG.md

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.0] - 2026-05-19

### Added
- **`wait_for_exit/2` function** — monitor-based replacement for `Process.sleep/1`
  calls that wait for a process to die. Returns `:ok` once the target pid is
  no longer alive, or `{:error, :timeout}` after the configurable budget
  (default 1000ms). Handles already-dead pids correctly (monitor fires
  immediately with `:noproc`).
- **`LetItCrash.Async` module** — new public surface for testing async work.
  - `observe_async/1,2` — wrap a block of test code and collect a
    `%LetItCrash.Async.Report{}` describing telemetry exception events
    that fired inside it (Task, Oban, LiveView `handle_async/3`). Handlers
    are isolated per `observe_async` block via `$callers` lineage tracking,
    so two concurrent observers running under `async: true` do not
    cross-pollute their reports.
  - `assert_no_silent_swallow/1,2` — fail when a Task raised but nobody
    noticed.
  - `assert_all_completed/2` — fail when async work did not finish within
    the supplied `:within` wall-clock budget.
  - `assert_idempotent/2` — fail when calling a 0-arity function twice
    produces different observable state. Takes a function (NOT a Report),
    because idempotency by definition requires a second execution. The
    user supplies a `:state` 0-arity snapshot function.
  - `LetItCrash.Async.Report` struct — pure data describing observed
    async work. Fields: `:spawned`, `:completed`, `:crashed`, `:exceptions`,
    `:warnings`, `:duration_ms`, `:started_at`, `:ended_at`.
- **`:telemetry ~> 1.0` runtime dependency** — required for the Async
  observer module. Loosened from a tighter pin so host apps on any
  `1.x` line of telemetry can adopt cleanly.
- **Dependabot config**`.github/dependabot.yml` opens weekly PRs for
  mix and GitHub Actions updates.
- **AGENTS.md alias style guide** — one alias per line; no
  `alias Foo.{Bar, Baz}` brace form.

### Changed
- **`start_tracking/0`** is now race-safe under concurrent `async: true`
  tests. Previously a TOCTOU window between `:ets.whereis/1` and
  `:ets.new/2` could raise `ArgumentError`; that race is now rescued
  internally (benign — the table exists either way).
- **`use LetItCrash`** now also imports `LetItCrash.Async`, so the new
  observer + assertions are available unqualified in test modules.
- **README leads with a Phoenix + Oban example** showing `observe_async`
  + `assert_no_silent_swallow` + `assert_all_completed` +
  `assert_idempotent`. The supervisor-focused examples remain below.

### Internal
- All seven `Process.sleep(10)` calls in the test suite have been
  replaced with `wait_for_exit/2`. The test suite is now fully
  deterministic with respect to process termination.

### Notes
- `assert_idempotent/2` accepts a `fun`, not a `Report` — this differs
  from an earlier draft because idempotency requires re-execution by
  definition. See `LetItCrash.Async.assert_idempotent/2` docs.
- v0.5.0 does **not** capture `Logger` output, so Tasks that log errors
  but recover gracefully are still considered "completed normally".
- Broadway / GenStage observer support is deferred to a future release.
- The `:sandbox` option on `observe_async/2` is documentation-only in
  this release.

## [0.4.0] - 2026-01-19

### Added
- **`wait_for_process/2` function** - Waits for a registered process to exist and be alive
  - Useful in test setup when ensuring a process is available before interacting with it
  - Configurable `:timeout` (default: 1000ms) and `:interval` (default: 50ms) options
  - Returns `:ok` when process is found, `{:error, :timeout}` otherwise
  - Particularly helpful after starting supervisors or during async initialization

## [0.3.0] - 2025-10-21

### Changed (Breaking)
- **Refactored `crash` API** - Following Elixir best practices, removed `crash!/1` function in favor of `crash/2`
  - `crash!/1` has been removed (the `!` suffix is conventionally reserved for functions that raise exceptions)
  - New signature: `crash(process, type \\ :shutdown)` where `type` can be `:shutdown` or `:kill`
  - Follows the same convention as `Process.exit/2` with the process as the first argument
  - Enables easy piping: `Process.whereis(:name) |> LetItCrash.crash(:kill)`
  - `crash(pid)` - default behavior (`:shutdown` signal)
  - `crash(pid, :kill)` - guarantees termination (cannot be trapped)
  - Maintains support for both PID and registered name with automatic PID tracking
  - Tests updated to use the new API

### Migration Guide
- Replace `crash!(process)` with `crash(process, :kill)`
- `crash(process)` continues to work the same way (uses `:shutdown` by default)
- Argument order follows `Process.exit/2`: process first, type second

## [0.2.0] - 2025-10-01

### Added
- **`crash!/1` function** - "Bang" version that uses `:kill` signal for guaranteed process termination
  - Works with processes that have `Process.flag(:trap_exit, true)`
  - Cannot be trapped by the target process
  - Particularly useful for testing GenServers with cleanup logic in `handle_info({:EXIT, ...})`
  - Supports both PID and registered name with automatic PID tracking
  - Complete test suite including `TrapExitServer` demonstration

### Changed
- Updated module documentation to explain the difference between `crash/1` and `crash!/1`
- Enhanced README with "When to use `crash!/1`?" section and practical examples
- Added comparison table between `:shutdown` and `:kill` exit signals

### Technical Details
- `:kill` exit signal bypasses process trapping mechanisms
- Maintains same API signature as `crash/1` for consistency
- Includes integration tests with supervised trap_exit processes

## [0.1.0] - 2025-01-03

### Core Functions
- `crash/1` - Crashes processes by PID or registered name with automatic PID tracking
- `recovered?/1,2,3` - Detects process recovery after crashes with multiple signatures
  - Automatic PID tracking when crashing by name
  - Configurable timeout and interval options
  - Manual PID comparison support
- `test_restart/2,3` - Tests complete crash/recovery workflow by running functions before and after

### Advanced Testing Functions
- `assert_clean_registry/2,3` - Verifies Registry entries are properly cleaned up on crash and recreated on recovery
- `verify_ets_cleanup/2,3` - Monitors ETS table entries for proper cleanup during process crashes
  - Support for `expect_cleanup` and `expect_recreate` options
  - Configurable timeout for verification
  - Detects resource leaks and improper state management

### Development Infrastructure  
- **Code Quality**: Credo static code analysis integration with strict mode (0 issues)
- **CI/CD Pipeline**: GitHub Actions with comprehensive testing
  - Tests on Elixir 1.17.2 + OTP 26.0 
  - Automated formatting, compilation warnings, and Credo checks
  - 15 tests covering all functionality, 0 failures
- **Documentation**: ExDoc integration with HTML output
  - Complete API documentation with practical examples
  - Advanced usage examples for Registry and ETS testing
  - README and CHANGELOG integration

### Technical Features
- ✅ Safe process crashes (automatic unlink to prevent test failures)
- ✅ Real recovery detection via PID comparison
- ✅ Supervised process support (GenServers, Agents, custom processes)
- ✅ Resource cleanup validation (Registry entries, ETS tables)
- ✅ Simple and intuitive API with comprehensive error handling
- ✅ Zero external runtime dependencies
- ✅ Automatic tracking system using ETS for PID management

### Project Setup
- MIT License with complete contribution guidelines
- Project badges for CI status, license, and Elixir compatibility
- Issue and PR templates for community contributions
- Comprehensive test coverage with realistic usage examples

[0.5.0]: https://github.com/volcov/let_it_crash/releases/tag/v0.5.0
[0.4.0]: https://github.com/volcov/let_it_crash/releases/tag/v0.4.0
[0.3.0]: https://github.com/volcov/let_it_crash/releases/tag/v0.3.0
[0.2.0]: https://github.com/volcov/let_it_crash/releases/tag/v0.2.0
[0.1.0]: https://github.com/volcov/let_it_crash/releases/tag/v0.1.0