Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.29.0] - 2025-12-23

### Added

- Support per-test timeout. (#176)

## [0.28.1] - 2021-01-11

### Changed
Expand Down Expand Up @@ -535,7 +541,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Initial public release.

[Unreleased]: https://github.com/shellspec/shellspec/compare/0.28.1...HEAD
[Unreleased]: https://github.com/shellspec/shellspec/compare/0.29.0...HEAD
[0.29.0]: https://github.com/shellspec/shellspec/compare/0.28.1...0.29.0
[0.28.1]: https://github.com/shellspec/shellspec/compare/0.28.0...0.28.1
[0.28.0]: https://github.com/shellspec/shellspec/compare/0.27.2...0.28.0
[0.27.2]: https://github.com/shellspec/shellspec/compare/0.27.1...0.27.2
Expand Down
206 changes: 206 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

ShellSpec is a full-featured BDD unit testing framework for POSIX shells (dash, bash, ksh, zsh, etc.). It provides code coverage, mocking, parameterized tests, parallel execution, and more. The framework is designed to work across multiple shell implementations and platforms.

## Development Commands

### Running Tests

```bash
# Run all tests
./shellspec

# Run tests on a specific file/directory
./shellspec spec/general_spec.sh
./shellspec spec/core/

# Run a specific example by line number
./shellspec spec/general_spec.sh:42

# Run with coverage (requires kcov)
./shellspec --kcov

# Quick mode (re-run only failed tests)
./shellspec --quick

# Run tests in parallel
./shellspec --jobs 4

# Syntax check specfiles without running
./shellspec --syntax-check

# Show translated specfile (see what the DSL becomes)
./shellspec --translate spec/general_spec.sh
```

### Testing on Multiple Shells

```bash
# Test on all installed shells
contrib/all.sh

# Test in Docker containers with various shells
contrib/test_in_docker.sh dockerfiles/debian.Dockerfile

# Check syntax across the entire project (requires Docker)
contrib/check.sh
```

### Development Tools

```bash
# Initialize a new project
./shellspec --init

# Generate support commands (test helpers)
./shellspec --gen-bin @touch @sed

# Count specfiles and examples
./shellspec --count

# List all specfiles
./shellspec --list specfiles

# List all examples
./shellspec --list examples
```

## Architecture

ShellSpec follows a multi-stage execution model:

1. **shellspec** (main executable) - Parses command-line options
2. **shellspec-runner.sh** - Orchestrates executor and reporter
3. **shellspec-executor.sh** - Manages translation and execution
4. **shellspec-translate.sh** - Translates specfiles from DSL to plain shell script
5. **shellspec-reporter.sh** - Formats and outputs test results

### Key Architectural Principles

- **Translation Process**: Specfiles are NOT executed directly. They are first translated from DSL syntax to regular shell scripts with ShellSpec core libraries included, then executed in a separate process.
- **Scope via Subshells**: Each example group and example block runs in a subshell, providing isolated scopes for variables and functions.
- **Performance Focus**: Core scripts avoid external commands, subshells, pipes, and command substitution as much as possible for performance and portability.
- **Shell Independence**: The framework is designed to work identically across POSIX-compliant shells.

### Directory Structure

```
shellspec # Main executable entry point
libexec/ # Executable components (runner, executor, translator, reporter)
lib/ # Core libraries
├── core/ # Core DSL implementation (subjects, modifiers, matchers)
├── libexec/ # Library support for executables
└── general.sh # General utility functions
spec/ # Test specs for ShellSpec itself
helper/ # Helper files for ShellSpec's own tests
└── spec_helper.sh # Test helper configuration
examples/ # Example specfiles demonstrating features
contrib/ # Development and testing utilities
```

## Code Organization

### Core Components (lib/core/)

- **matchers.sh** - Verification matchers (eq, match, include, etc.)
- **subjects.sh** - Subjects for verification (output, status, variable, etc.)
- **syntax.sh** - DSL syntax definitions
- **statement.sh** - Core statement implementations
- **utils.sh** - Utility functions for core operations
- **verb.sh** - Verb implementations (should, should not)

### Translation System (lib/libexec/)

- **translator.sh** - Main translation logic
- **grammar.sh** - DSL grammar definitions
- **executor.sh** - Test execution management
- **reporter.sh** - Test reporting and formatting

### Execution Modes

- **Serial Execution** - Tests run sequentially (default)
- **Parallel Execution** - Tests run in parallel (`--jobs N`)
- **Coverage Mode** - Integrated with kcov for code coverage (`--kcov`)

## DSL Translation

Specfiles use a DSL that gets translated to shell script:

```bash
Describe 'example' # → function block in subshell
It 'does something' # → function block in subshell
When call func # → execute and capture output/status
The output should eq "expected" # → verification
End
End
```

Use `./shellspec --translate <specfile>` to see the generated code.

## Testing Patterns

### Function-Based vs Command-Based Mocks

- **Function-based mocks**: Fast, defined as shell functions in the specfile
- **Command-based mocks**: Create temporary shell scripts, can mock external commands with invalid function names (e.g., `docker-compose`)

### When to Use Each Evaluation Type

- `When call` - Call shell functions without subshell
- `When run` - Run commands in subshell (most common for commands)
- `When run script` - Run shell script ignoring shebang
- `When run source` - Source script (enables function mocking)

### Coverage Measurement

Coverage only works on:
- Shell scripts loaded by `Include`
- Functions called by `When call`
- Scripts executed by `When run script` or `When run source`

Coverage does NOT work on:
- External commands (even if shell scripts)
- Scripts executed normally via shebang

## Important Constraints

### Code Style Requirements

- **POSIX Compliance**: All code must work across POSIX shells
- **Performance Critical**: Avoid external commands in hot paths
- **No Advanced Features**: Cannot rely on bash/zsh-specific features unless guarded
- **Portability**: Support shells from bash 2.03+, dash 0.5.4+, zsh 3.1.9+, ksh 93r+

### External Command Restrictions

Core scripts (lib/, libexec/) minimize external command usage:
- Allowed: `cat`, `date`, `env`, `ls`, `mkdir`, `od`, `rm`, `sleep`, `sort`, `time`, `printf`, `kill`
- Avoid in hot paths: Any external command that can be done with shell built-ins

### Shell Compatibility

The codebase handles many shell-specific bugs and quirks. Check:
- `SHELLSPEC_DEFECT_*` variables for known shell bugs
- `helper/ksh_workaround.sh` for shell-specific workarounds
- Variable exports and readonly handling vary significantly by shell

## Project-Specific Options

ShellSpec uses itself for testing. Default options in `.shellspec`:
- `--require spec_helper` - Load helper configuration
- `--sandbox` - Force command mocking (security feature for tests)
- `--helperdir helper` - Use `helper/` instead of `spec/` for helpers
- `--skip-message moderate` - Reduce skip message verbosity
- `--fail-no-examples` - Fail if no examples found

## References

- **README.md** - Comprehensive user documentation
- **docs/architecture.md** - Architecture overview
- **docs/references.md** - Complete DSL reference
- **CONTRIBUTING.md** - Developer contribution guide
- **examples/spec/** - Working examples of all features
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,8 @@ Usage: shellspec [ -c ] [-C <directory>] [options...] [files or directories...]
-p, --{no-}profile Enable profiling and list the slowest examples [default: disabled]
--profile-limit N List the top N slowest examples [default: 10]
--{no-}boost Increase the CPU frequency to boost up testing speed [default: disabled]
--timeout SECONDS Specify the default timeout for each test [default: 60]
--no-timeout Disable timeout for all tests
--log-file LOGFILE Log file for %logger directive and trace [default: "/dev/tty"]
--tmpdir TMPDIR Specify temporary directory [default: $TMPDIR, $TMP or "/tmp"]
--keep-tmpdir Do not cleanup temporary directory [default: disabled]
Expand Down
15 changes: 15 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [Ranges (`:LINENO`, `:@ID`) / Filters (`--example`) / Focus (`--focus`)](#ranges-lineno-id--filters---example--focus---focus)
- [Reporter (`--format`) / Generator (`--output`)](#reporter---format--generator---output)
- [Profiler (`--profile`)](#profiler---profile)
- [Timeout (`--timeout`)](#timeout---timeout)
- [Run tests in Docker container (`--docker`)](#run-tests-in-docker-container---docker)
- [Task runner (`--task`)](#task-runner---task)

Expand Down Expand Up @@ -104,6 +105,20 @@ NOTE: Custom formatter is supported (but not documented yet, sorry).

When the `--profile` option is specified, the profiler is enabled and lists the slow examples.

## Timeout (`--timeout`)

You can specify the default timeout for each test with `--timeout` option.
The default timeout is 60 seconds. You can disable timeout by specifying `--no-timeout` option.
Also you can specify timeout per example by using `%timeout` directive.

```sh
shellspec --timeout 5 # 5 seconds
shellspec --timeout 5s # 5 seconds
shellspec --timeout 2m # 2 minutes
shellspec --timeout 1m30s # 1 minute 30 seconds
shellspec --no-timeout # Disable timeout
```

## Run tests in Docker container (`--docker`)

**NOTE: This is an experimental feature and may be changed/removed in the future.**
Expand Down
Loading