Skip to content

Latest commit

Β 

History

History
314 lines (235 loc) Β· 9.7 KB

File metadata and controls

314 lines (235 loc) Β· 9.7 KB

CLAUDE.md

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

Project Overview

Snapshot is a PHP library for directory snapshot testing. It provides functionality for creating, comparing, and applying directory snapshots using a baseline + diff architecture. This is particularly useful for testing code generators, scaffolding tools, or any system that produces file output.

Core Concepts

  • Baseline: A reference directory representing the expected state
  • Snapshot/Scenario: A set of diff files representing changes from baseline
  • Diff: Unified diff format patches for file content changes
  • Index: A scanned representation of a directory's files and content

Architecture

Namespace Structure

  • Source code: AlexSkrypnyk\Snapshot\
  • Tests: AlexSkrypnyk\Snapshot\Tests\
  • Autoloading: PSR-4 via Composer

Component Structure

src/
β”œβ”€β”€ Snapshot.php              # Main facade class with static methods
β”œβ”€β”€ Testing/
β”‚   └── SnapshotTrait.php     # PHPUnit trait for snapshot testing
β”œβ”€β”€ Compare/
β”‚   β”œβ”€β”€ Comparer.php          # Compares two directory indexes
β”‚   β”œβ”€β”€ ComparerInterface.php
β”‚   β”œβ”€β”€ Diff.php              # Represents content differences
β”‚   β”œβ”€β”€ DiffInterface.php
β”‚   └── RenderableInterface.php
β”œβ”€β”€ Index/
β”‚   β”œβ”€β”€ Index.php             # Scans and indexes directory contents
β”‚   β”œβ”€β”€ IndexInterface.php
β”‚   β”œβ”€β”€ IndexedFile.php       # Represents a file in an index
β”‚   β”œβ”€β”€ IndexedFileInterface.php
β”‚   β”œβ”€β”€ Rules.php             # Skip/include/ignore rules for indexing
β”‚   └── RulesInterface.php
β”œβ”€β”€ Patch/
β”‚   β”œβ”€β”€ Patcher.php           # Applies unified diff patches
β”‚   └── PatcherInterface.php
β”œβ”€β”€ Sync/
β”‚   β”œβ”€β”€ Syncer.php            # Copies files from index to destination
β”‚   └── SyncerInterface.php
└── Exception/
    β”œβ”€β”€ PatchException.php
    β”œβ”€β”€ RulesException.php
    └── SnapshotException.php

Key Classes

Class Purpose
Snapshot Static facade for all operations (compare, diff, patch, sync)
SnapshotTrait PHPUnit trait with assertDirectoriesIdentical() and assertSnapshotMatchesBaseline()
Index Scans directories, respects .ignorecontent rules
IndexedFile File representation with content, hash, path info
Rules Configures skip/include/ignore patterns for comparison
Comparer Finds differences between two indexes
Diff Generates unified diff output
Patcher Applies patch files to recreate expected state
Syncer Copies indexed files to destination

Commands

Code Quality

# Run all linters (PHPCS, PHPStan, Rector)
composer lint

# Auto-fix code style issues
composer lint-fix

# Individual tools
./vendor/bin/phpcs      # Check coding standards
./vendor/bin/phpcbf     # Fix coding standards
./vendor/bin/phpstan    # Static analysis (level 9)
./vendor/bin/rector --dry-run  # Check Rector suggestions

Testing

# Run all PHPUnit tests
composer test

# Run with coverage reports
composer test-coverage
# Coverage reports: .logs/.coverage-html/index.html, .logs/cobertura.xml

# Run unit tests only
./vendor/bin/phpunit tests/phpunit/Unit

# Run functional tests only
./vendor/bin/phpunit tests/phpunit/Functional

# Run specific test file
./vendor/bin/phpunit tests/phpunit/Unit/SnapshotTest.php

# Run specific test method
./vendor/bin/phpunit --filter testMethodName

Code Quality Standards

Three-Layer Quality Stack

  1. PHP_CodeSniffer - Drupal coding standards + strict types

    • Config: phpcs.xml
    • Rules: Drupal standard, Generic.PHP.RequireStrictTypes
    • Relaxed rules in test files
  2. PHPStan - Level 9 static analysis

    • Config: phpstan.neon
  3. Rector - PHP 8.2/8.3 modernization

    • Config: rector.php
    • Sets: PHP_82, PHP_83, CODE_QUALITY, CODING_STYLE, DEAD_CODE, TYPE_DECLARATION

Coding Conventions

  • All PHP files must declare strict_types=1
  • Use single quotes for strings (double quotes if containing single quote)
  • All files must end with a newline character
  • Local variables/method arguments: snake_case
  • Method names/class properties: camelCase

Testing Patterns

Test Structure

tests/phpunit/
β”œβ”€β”€ Unit/                    # Unit tests - isolated, fast
β”‚   β”œβ”€β”€ ComparerTest.php
β”‚   β”œβ”€β”€ DiffTest.php
β”‚   β”œβ”€β”€ IndexedFileTest.php
β”‚   β”œβ”€β”€ IndexTest.php
β”‚   β”œβ”€β”€ PatcherTest.php
β”‚   β”œβ”€β”€ RulesTest.php
β”‚   β”œβ”€β”€ SnapshotAssertionsTraitTest.php
β”‚   β”œβ”€β”€ SnapshotTest.php
β”‚   └── SyncerTest.php
β”œβ”€β”€ Functional/              # Integration tests - subprocess testing
β”‚   β”œβ”€β”€ FunctionalTestCase.php
β”‚   └── SnapshotTraitUpdateTest.php
β”œβ”€β”€ Fixtures/                # Test fixture directories
β”‚   β”œβ”€β”€ compare/            # Comparison test fixtures
β”‚   └── diff/               # Diff/patch test fixtures
└── UnitTestCase.php        # Base test case

Writing Tests

  • Use PHPUnit 11 attributes: #[CoversClass()], #[DataProvider()]
  • Data provider method names start with dataProvider
  • Use UnitTestCase as base class (includes SnapshotTrait and LocationsTrait)
  • Functional tests use FunctionalTestCase which adds ProcessTrait

Fixture Directory Structure

For comparison tests (tests/phpunit/Fixtures/compare/):

scenario_name/
β”œβ”€β”€ directory1/          # Left side (baseline/expected)
β”‚   β”œβ”€β”€ .ignorecontent   # Optional ignore rules
β”‚   └── ...files...
└── directory2/          # Right side (actual)
    └── ...files...

For diff/patch tests (tests/phpunit/Fixtures/diff/):

scenario_name/
β”œβ”€β”€ baseline/            # Original state
β”œβ”€β”€ diff/                # Patch files to apply
└── result/              # Expected result after patching

The .ignorecontent File

Controls which files are compared. Supports patterns:

  • *.log - Skip files matching glob pattern
  • dir/ - Skip entire directory
  • !important.txt - Include file (override skip)
  • ^content.txt - Ignore content differences (compare existence only)

SnapshotTrait Usage

The trait provides two main assertions for PHPUnit tests:

use AlexSkrypnyk\Snapshot\Testing\SnapshotTrait;

class MyTest extends TestCase {
    use SnapshotTrait;

    // Compare two directories directly
    public function testOutput(): void {
        $this->assertDirectoriesIdentical($expected, $actual);
    }

    // Compare actual against baseline + diffs
    public function testScenario(): void {
        $this->assertSnapshotMatchesBaseline($actual, $baseline, $diffs);
    }

    // Enable auto-update on failure (call in tearDown)
    protected function tearDown(): void {
        $this->snapshotUpdateOnFailure($snapshots, $actual);
        parent::tearDown();
    }
}

Auto-Update Feature

Set UPDATE_SNAPSHOTS=1 environment variable to automatically update snapshots when tests fail due to directory comparison mismatches:

UPDATE_SNAPSHOTS=1 ./vendor/bin/phpunit

Bulk Snapshot Updates (bin/update-snapshots)

CLI that runs PHPUnit per dataset with UPDATE_SNAPSHOTS=1 (in parallel, with timeouts and retries) to regenerate many snapshots at once:

vendor/bin/update-snapshots testMySnapshot tests/snapshots

Exit-code contract: successfully updating snapshots is the expected outcome and exits 0. The script exits non-zero only when a dataset genuinely cannot be updated - a non-snapshot failure or a timeout.

A per-dataset PHPUnit run still exits non-zero when it updates a snapshot (the assertion fails before tearDown() rewrites it). The script reclassifies such runs as "updated" by detecting SnapshotTrait's [SNAPSHOT] Baseline updated / [SNAPSHOT] Diffs updated completion markers in the captured output - so those marker strings are a contract shared with src/Testing/SnapshotTrait.php; keep them in sync.

Functional tests run the script as a subprocess against fixtures in tests/phpunit/Fixtures/functional_update/. Coverage measures src/ only, so bin/ is not coverage-gated.

Performance Benchmarks

PHPBench benchmarks measure performance of core Snapshot operations.

Commands

# Run benchmarks with baseline comparison (used by CI)
composer benchmark

# Create or update baseline for performance comparison
composer benchmark-baseline

# Run specific benchmark class
./vendor/bin/phpbench run benchmarks/SnapshotBench.php --ref=baseline

# Quick testing: verify benchmark works without full suite
./vendor/bin/phpbench run benchmarks/SnapshotBench.php --iterations=1 --revs=1

Benchmark Coverage

The SnapshotBench class measures:

  • benchCompareIdentical: Comparing identical directories (baseline)
  • benchCompareContentDiffs: Comparing with 20% modified content
  • benchCompareStructuralDiffs: Comparing with missing/extra files
  • benchDiff: Creating diff files from differences
  • benchPatch: Applying patches to baseline
  • benchSync: Syncing directories
  • benchCompareLargeDirectory: Large directory (500 files) comparison

Baseline Management

  • Baseline benchmarks stored in .phpbench/storage/ directory
  • CI compares new benchmarks against baseline with Β±5% threshold
  • Performance regressions exceeding Β±5% will fail CI checks
  • Update baseline manually: composer benchmark-baseline

CI/CD

GitHub Actions workflows test across:

  • PHP versions: 8.2, 8.3
  • Separate jobs: lint, test, coverage upload (Codecov)

Key workflows:

  • .github/workflows/test-php.yml - PHP testing
  • .github/workflows/benchmark-php.yml - Performance benchmarks