├── .cargo └── config.toml ├── .clippy.toml ├── .github ├── renovate.json5 ├── settings.yml └── workflows │ ├── audit.yml │ ├── ci.yml │ ├── committed.yml │ ├── pre-commit.yml │ ├── rust-next.yml │ └── spelling.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── committed.toml ├── deny.toml ├── examples └── failure.rs ├── release.toml ├── src ├── assert.rs ├── color.rs ├── fixture │ ├── child.rs │ ├── dir.rs │ ├── errors.rs │ ├── file.rs │ ├── mod.rs │ └── tools.rs └── lib.rs └── tests ├── assert.rs └── fixture ├── .gitattributes └── hello.txt /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [resolver] 2 | incompatible-rust-versions = "fallback" 3 | -------------------------------------------------------------------------------- /.clippy.toml: -------------------------------------------------------------------------------- 1 | allow-print-in-tests = true 2 | allow-expect-in-tests = true 3 | allow-unwrap-in-tests = true 4 | allow-dbg-in-tests = true 5 | disallowed-methods = [ 6 | { path = "std::option::Option::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, 7 | { path = "std::option::Option::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, 8 | { path = "std::result::Result::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, 9 | { path = "std::result::Result::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, 10 | { path = "std::iter::Iterator::for_each", reason = "prefer `for` for side-effects" }, 11 | { path = "std::iter::Iterator::try_for_each", reason = "prefer `for` for side-effects" }, 12 | ] 13 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | schedule: [ 3 | 'before 5am on the first day of the month', 4 | ], 5 | semanticCommits: 'enabled', 6 | commitMessageLowerCase: 'never', 7 | configMigration: true, 8 | dependencyDashboard: true, 9 | customManagers: [ 10 | { 11 | customType: 'regex', 12 | managerFilePatterns: [ 13 | '/^rust-toolchain\\.toml$/', 14 | '/Cargo.toml$/', 15 | '/clippy.toml$/', 16 | '/\\.clippy.toml$/', 17 | '/^\\.github/workflows/ci.yml$/', 18 | '/^\\.github/workflows/rust-next.yml$/', 19 | ], 20 | matchStrings: [ 21 | 'STABLE.*?(?\\d+\\.\\d+(\\.\\d+)?)', 22 | '(?\\d+\\.\\d+(\\.\\d+)?).*?STABLE', 23 | ], 24 | depNameTemplate: 'STABLE', 25 | packageNameTemplate: 'rust-lang/rust', 26 | datasourceTemplate: 'github-releases', 27 | }, 28 | ], 29 | packageRules: [ 30 | { 31 | commitMessageTopic: 'Rust Stable', 32 | matchManagers: [ 33 | 'custom.regex', 34 | ], 35 | matchDepNames: [ 36 | 'STABLE', 37 | ], 38 | extractVersion: '^(?\\d+\\.\\d+)', // Drop the patch version 39 | schedule: [ 40 | '* * * * *', 41 | ], 42 | automerge: true, 43 | }, 44 | // Goals: 45 | // - Keep version reqs low, ignoring compatible normal/build dependencies 46 | // - Take advantage of latest dev-dependencies 47 | // - Rollup safe upgrades to reduce CI runner load 48 | // - Help keep number of versions down by always using latest breaking change 49 | // - Have lockfile and manifest in-sync 50 | { 51 | matchManagers: [ 52 | 'cargo', 53 | ], 54 | matchDepTypes: [ 55 | 'build-dependencies', 56 | 'dependencies', 57 | ], 58 | matchCurrentVersion: '>=0.1.0', 59 | matchUpdateTypes: [ 60 | 'patch', 61 | ], 62 | enabled: false, 63 | }, 64 | { 65 | matchManagers: [ 66 | 'cargo', 67 | ], 68 | matchDepTypes: [ 69 | 'build-dependencies', 70 | 'dependencies', 71 | ], 72 | matchCurrentVersion: '>=1.0.0', 73 | matchUpdateTypes: [ 74 | 'minor', 75 | 'patch', 76 | ], 77 | enabled: false, 78 | }, 79 | { 80 | matchManagers: [ 81 | 'cargo', 82 | ], 83 | matchDepTypes: [ 84 | 'dev-dependencies', 85 | ], 86 | matchCurrentVersion: '>=0.1.0', 87 | matchUpdateTypes: [ 88 | 'patch', 89 | ], 90 | automerge: true, 91 | groupName: 'compatible (dev)', 92 | }, 93 | { 94 | matchManagers: [ 95 | 'cargo', 96 | ], 97 | matchDepTypes: [ 98 | 'dev-dependencies', 99 | ], 100 | matchCurrentVersion: '>=1.0.0', 101 | matchUpdateTypes: [ 102 | 'minor', 103 | 'patch', 104 | ], 105 | automerge: true, 106 | groupName: 'compatible (dev)', 107 | }, 108 | ], 109 | } 110 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # These settings are synced to GitHub by https://probot.github.io/apps/settings/ 2 | 3 | repository: 4 | description: "Filesystem fixtures and assertions." 5 | homepage: "docs.rs/assert_fs" 6 | topics: "rust fs test" 7 | has_issues: true 8 | has_projects: false 9 | has_wiki: false 10 | has_downloads: true 11 | default_branch: master 12 | 13 | # Preference: people do clean commits 14 | allow_merge_commit: true 15 | # Backup in case we need to clean up commits 16 | allow_squash_merge: true 17 | # Not really needed 18 | allow_rebase_merge: false 19 | 20 | allow_auto_merge: true 21 | delete_branch_on_merge: true 22 | 23 | squash_merge_commit_title: "PR_TITLE" 24 | squash_merge_commit_message: "PR_BODY" 25 | merge_commit_message: "PR_BODY" 26 | 27 | labels: 28 | # Type 29 | - name: bug 30 | color: '#b60205' 31 | description: "Not as expected" 32 | - name: enhancement 33 | color: '#1d76db' 34 | description: "Improve the expected" 35 | # Flavor 36 | - name: question 37 | color: "#cc317c" 38 | description: "Uncertainty is involved" 39 | - name: breaking-change 40 | color: "#e99695" 41 | - name: good first issue 42 | color: '#c2e0c6' 43 | description: "Help wanted!" 44 | 45 | # This serves more as documentation. 46 | # Branch protection API was replaced by rulesets but settings isn't updated. 47 | # See https://github.com/repository-settings/app/issues/825 48 | # 49 | # branches: 50 | # - name: master 51 | # protection: 52 | # required_pull_request_reviews: null 53 | # required_conversation_resolution: true 54 | # required_status_checks: 55 | # # Required. Require branches to be up to date before merging. 56 | # strict: false 57 | # contexts: ["CI", "Spell Check with Typos"] 58 | # enforce_admins: false 59 | # restrictions: null 60 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - '**/Cargo.toml' 10 | - '**/Cargo.lock' 11 | push: 12 | branches: 13 | - master 14 | 15 | env: 16 | RUST_BACKTRACE: 1 17 | CARGO_TERM_COLOR: always 18 | CLICOLOR: 1 19 | 20 | concurrency: 21 | group: "${{ github.workflow }}-${{ github.ref }}" 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | security_audit: 26 | permissions: 27 | issues: write # to create issues (actions-rs/audit-check) 28 | checks: write # to create check (actions-rs/audit-check) 29 | runs-on: ubuntu-latest 30 | # Prevent sudden announcement of a new advisory from failing ci: 31 | continue-on-error: true 32 | steps: 33 | - name: Checkout repository 34 | uses: actions/checkout@v4 35 | - uses: actions-rs/audit-check@v1 36 | with: 37 | token: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | cargo_deny: 40 | permissions: 41 | issues: write # to create issues (actions-rs/audit-check) 42 | checks: write # to create check (actions-rs/audit-check) 43 | runs-on: ubuntu-latest 44 | strategy: 45 | matrix: 46 | checks: 47 | - bans licenses sources 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: EmbarkStudios/cargo-deny-action@v2 51 | with: 52 | command: check ${{ matrix.checks }} 53 | rust-version: stable 54 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches: 10 | - master 11 | 12 | env: 13 | RUST_BACKTRACE: 1 14 | CARGO_TERM_COLOR: always 15 | CLICOLOR: 1 16 | 17 | concurrency: 18 | group: "${{ github.workflow }}-${{ github.ref }}" 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | ci: 23 | permissions: 24 | contents: none 25 | name: CI 26 | needs: [test, msrv, lockfile, docs, rustfmt, clippy, minimal-versions] 27 | runs-on: ubuntu-latest 28 | if: "always()" 29 | steps: 30 | - name: Failed 31 | run: exit 1 32 | if: "contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')" 33 | test: 34 | name: Test 35 | strategy: 36 | matrix: 37 | os: ["ubuntu-latest", "windows-latest", "macos-latest"] 38 | rust: ["stable"] 39 | continue-on-error: ${{ matrix.rust != 'stable' }} 40 | runs-on: ${{ matrix.os }} 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v4 44 | - name: Install Rust 45 | uses: dtolnay/rust-toolchain@stable 46 | with: 47 | toolchain: ${{ matrix.rust }} 48 | - uses: Swatinem/rust-cache@v2 49 | - uses: taiki-e/install-action@cargo-hack 50 | - name: Build 51 | run: cargo test --workspace --no-run 52 | - name: Test 53 | run: cargo hack test --each-feature --workspace 54 | msrv: 55 | name: "Check MSRV" 56 | runs-on: ubuntu-latest 57 | steps: 58 | - name: Checkout repository 59 | uses: actions/checkout@v4 60 | - name: Install Rust 61 | uses: dtolnay/rust-toolchain@stable 62 | with: 63 | toolchain: stable 64 | - uses: Swatinem/rust-cache@v2 65 | - uses: taiki-e/install-action@cargo-hack 66 | - name: Default features 67 | run: cargo hack check --each-feature --locked --rust-version --ignore-private --workspace --all-targets --keep-going 68 | minimal-versions: 69 | name: Minimal versions 70 | runs-on: ubuntu-latest 71 | steps: 72 | - name: Checkout repository 73 | uses: actions/checkout@v4 74 | - name: Install stable Rust 75 | uses: dtolnay/rust-toolchain@stable 76 | with: 77 | toolchain: stable 78 | - name: Install nightly Rust 79 | uses: dtolnay/rust-toolchain@stable 80 | with: 81 | toolchain: nightly 82 | - name: Downgrade dependencies to minimal versions 83 | run: cargo +nightly generate-lockfile -Z minimal-versions 84 | - name: Compile with minimal versions 85 | run: cargo +stable check --workspace --all-features --locked --keep-going 86 | lockfile: 87 | runs-on: ubuntu-latest 88 | steps: 89 | - name: Checkout repository 90 | uses: actions/checkout@v4 91 | - name: Install Rust 92 | uses: dtolnay/rust-toolchain@stable 93 | with: 94 | toolchain: stable 95 | - uses: Swatinem/rust-cache@v2 96 | - name: "Is lockfile updated?" 97 | run: cargo update --workspace --locked 98 | docs: 99 | name: Docs 100 | runs-on: ubuntu-latest 101 | steps: 102 | - name: Checkout repository 103 | uses: actions/checkout@v4 104 | - name: Install Rust 105 | uses: dtolnay/rust-toolchain@stable 106 | with: 107 | toolchain: "1.87" # STABLE 108 | - uses: Swatinem/rust-cache@v2 109 | - name: Check documentation 110 | env: 111 | RUSTDOCFLAGS: -D warnings 112 | run: cargo doc --workspace --all-features --no-deps --document-private-items --keep-going 113 | rustfmt: 114 | name: rustfmt 115 | runs-on: ubuntu-latest 116 | steps: 117 | - name: Checkout repository 118 | uses: actions/checkout@v4 119 | - name: Install Rust 120 | uses: dtolnay/rust-toolchain@stable 121 | with: 122 | toolchain: "1.87" # STABLE 123 | components: rustfmt 124 | - uses: Swatinem/rust-cache@v2 125 | - name: Check formatting 126 | run: cargo fmt --all -- --check 127 | clippy: 128 | name: clippy 129 | runs-on: ubuntu-latest 130 | permissions: 131 | security-events: write # to upload sarif results 132 | steps: 133 | - name: Checkout repository 134 | uses: actions/checkout@v4 135 | - name: Install Rust 136 | uses: dtolnay/rust-toolchain@stable 137 | with: 138 | toolchain: "1.87" # STABLE 139 | components: clippy 140 | - uses: Swatinem/rust-cache@v2 141 | - name: Install SARIF tools 142 | run: cargo install clippy-sarif --locked 143 | - name: Install SARIF tools 144 | run: cargo install sarif-fmt --locked 145 | - name: Check 146 | run: > 147 | cargo clippy --workspace --all-features --all-targets --message-format=json 148 | | clippy-sarif 149 | | tee clippy-results.sarif 150 | | sarif-fmt 151 | continue-on-error: true 152 | - name: Upload 153 | uses: github/codeql-action/upload-sarif@v3 154 | with: 155 | sarif_file: clippy-results.sarif 156 | wait-for-processing: true 157 | - name: Report status 158 | run: cargo clippy --workspace --all-features --all-targets --keep-going -- -D warnings --allow deprecated 159 | coverage: 160 | name: Coverage 161 | runs-on: ubuntu-latest 162 | steps: 163 | - name: Checkout repository 164 | uses: actions/checkout@v4 165 | - name: Install Rust 166 | uses: dtolnay/rust-toolchain@stable 167 | with: 168 | toolchain: stable 169 | - uses: Swatinem/rust-cache@v2 170 | - name: Install cargo-tarpaulin 171 | run: cargo install cargo-tarpaulin 172 | - name: Gather coverage 173 | run: cargo tarpaulin --output-dir coverage --out lcov 174 | - name: Publish to Coveralls 175 | uses: coverallsapp/github-action@master 176 | with: 177 | github-token: ${{ secrets.GITHUB_TOKEN }} 178 | -------------------------------------------------------------------------------- /.github/workflows/committed.yml: -------------------------------------------------------------------------------- 1 | # Not run as part of pre-commit checks because they don't handle sending the correct commit 2 | # range to `committed` 3 | name: Lint Commits 4 | on: [pull_request] 5 | 6 | permissions: 7 | contents: read 8 | 9 | env: 10 | RUST_BACKTRACE: 1 11 | CARGO_TERM_COLOR: always 12 | CLICOLOR: 1 13 | 14 | concurrency: 15 | group: "${{ github.workflow }}-${{ github.ref }}" 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | committed: 20 | name: Lint Commits 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout Actions Repository 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | - name: Lint Commits 28 | uses: crate-ci/committed@master 29 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | permissions: {} # none 4 | 5 | on: 6 | pull_request: 7 | push: 8 | branches: [master] 9 | 10 | env: 11 | RUST_BACKTRACE: 1 12 | CARGO_TERM_COLOR: always 13 | CLICOLOR: 1 14 | 15 | concurrency: 16 | group: "${{ github.workflow }}-${{ github.ref }}" 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | pre-commit: 21 | permissions: 22 | contents: read 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-python@v5 27 | with: 28 | python-version: '3.x' 29 | - uses: pre-commit/action@v3.0.1 30 | -------------------------------------------------------------------------------- /.github/workflows/rust-next.yml: -------------------------------------------------------------------------------- 1 | name: rust-next 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | schedule: 8 | - cron: '6 6 6 * *' 9 | 10 | env: 11 | RUST_BACKTRACE: 1 12 | CARGO_TERM_COLOR: always 13 | CLICOLOR: 1 14 | 15 | concurrency: 16 | group: "${{ github.workflow }}-${{ github.ref }}" 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | test: 21 | name: Test 22 | strategy: 23 | matrix: 24 | os: ["ubuntu-latest", "windows-latest", "macos-latest"] 25 | rust: ["stable", "beta"] 26 | include: 27 | - os: ubuntu-latest 28 | rust: "nightly" 29 | continue-on-error: ${{ matrix.rust != 'stable' }} 30 | runs-on: ${{ matrix.os }} 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v4 34 | - name: Install Rust 35 | uses: dtolnay/rust-toolchain@stable 36 | with: 37 | toolchain: ${{ matrix.rust }} 38 | - uses: Swatinem/rust-cache@v2 39 | - uses: taiki-e/install-action@cargo-hack 40 | - name: Build 41 | run: cargo test --workspace --no-run 42 | - name: Test 43 | run: cargo hack test --each-feature --workspace 44 | latest: 45 | name: "Check latest dependencies" 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Checkout repository 49 | uses: actions/checkout@v4 50 | - name: Install Rust 51 | uses: dtolnay/rust-toolchain@stable 52 | with: 53 | toolchain: stable 54 | - uses: Swatinem/rust-cache@v2 55 | - uses: taiki-e/install-action@cargo-hack 56 | - name: Update dependencies 57 | run: cargo update 58 | - name: Build 59 | run: cargo test --workspace --no-run 60 | - name: Test 61 | run: cargo hack test --each-feature --workspace 62 | -------------------------------------------------------------------------------- /.github/workflows/spelling.yml: -------------------------------------------------------------------------------- 1 | name: Spelling 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: [pull_request] 7 | 8 | env: 9 | RUST_BACKTRACE: 1 10 | CARGO_TERM_COLOR: always 11 | CLICOLOR: 1 12 | 13 | concurrency: 14 | group: "${{ github.workflow }}-${{ github.ref }}" 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | spelling: 19 | name: Spell Check with Typos 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout Actions Repository 23 | uses: actions/checkout@v4 24 | - name: Spell Check Repo 25 | uses: crate-ci/typos@master 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_install_hook_types: ["pre-commit", "commit-msg"] 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: check-yaml 7 | - id: check-json 8 | - id: check-toml 9 | - id: check-merge-conflict 10 | - id: check-case-conflict 11 | - id: detect-private-key 12 | - repo: https://github.com/crate-ci/typos 13 | rev: v1.32.0 14 | hooks: 15 | - id: typos 16 | - repo: https://github.com/crate-ci/committed 17 | rev: v1.1.7 18 | hooks: 19 | - id: committed 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | 8 | ## [Unreleased] - ReleaseDate 9 | 10 | ## [1.1.3] - 2025-05-02 11 | 12 | ### Features 13 | 14 | - Add `TempDir::new_in/with_prefix/with_prefix_in` 15 | 16 | ## [1.1.2] - 2024-07-25 17 | 18 | ### Compatibility 19 | 20 | - Update MSRV to 1.74.0 21 | 22 | ## [1.1.1] - 2024-01-12 23 | 24 | ### Internal 25 | 26 | - Dependency update 27 | 28 | ## [1.1.0] - 2023-12-27 29 | 30 | ### Compatibility 31 | 32 | - Update MSRV to 1.70.0 33 | 34 | ### Documentation 35 | 36 | - Update a stale reference 37 | 38 | ## [1.0.13] - 2023-04-13 39 | 40 | ### Internal 41 | 42 | - Dependency update 43 | 44 | ## [1.0.12] - 2023-03-16 45 | 46 | ### Internal 47 | 48 | - Dependency update 49 | 50 | ## [1.0.11] - 2023-03-14 51 | 52 | ### Fixes 53 | 54 | - Correctly handle `CLICOLOR=1` 55 | - Correctly handle `NO_COLOR=` 56 | - Auto-enable color on CI 57 | 58 | ## [1.0.10] - 2022-12-02 59 | 60 | ## [1.0.9] - 2022-11-07 61 | 62 | ### Fixes 63 | 64 | - Add `Debug` impls for temp dir / file 65 | 66 | ## [1.0.8] - 2022-11-04 67 | 68 | ### Fixes 69 | 70 | - Hide internal-only optional dependencies 71 | 72 | ## [1.0.7] - 2022-01-11 73 | 74 | ## [1.0.6] - 2021-10-07 75 | 76 | ## [1.0.5] - 2021-09-06 77 | 78 | #### Fixes 79 | 80 | - Show caller, rather than `assert_fs`, as cause of panics 81 | 82 | ## [1.0.4] - 2021-08-30 83 | 84 | #### Features 85 | 86 | - File and directory symlinks 87 | 88 | ## [1.0.3] - 2021-07-03 89 | 90 | ## [1.0.2] - 2021-04-22 91 | 92 | #### Fixes 93 | 94 | * Explicitly stated that `assert` may `panic` 95 | 96 | #### Features 97 | 98 | * Built-in fixtures now interoperate as `&Path` 99 | 100 | ## [1.0.1] - 2021-02-01 101 | 102 | ## 1.0.0 - 2020-03-26 103 | 104 | Stable release! 105 | 106 | ## 0.13.1 - 2019-12-01 107 | 108 | 109 | #### Features 110 | 111 | * **assert:** Support more string types in shortcut 112 | 113 | 114 | 115 | ## 0.13.0 - 2019-11-29 116 | 117 | 118 | #### Breaking Changes 119 | 120 | * **persist:** Clarify API behavior 121 | * `copy_from` now expects globs relative to the root (so change `*` to `**`) 122 | 123 | #### Bug Fixes 124 | 125 | * **persist:** Clarify API behavior 126 | * `copy_from` now expects globs relative to the root (so change `*` to `**`) 127 | 128 | 129 | 130 | ## 0.11.3 - 2019-01-29 131 | 132 | 133 | #### Features 134 | 135 | * **fixture:** Another subdir route 136 | 137 | 138 | 139 | ## 0.11.2 - 2019-01-29 140 | 141 | 142 | #### Features 143 | 144 | * **assert:** Support NamedTempFile 145 | 146 | 147 | 148 | ## 0.11.1 - 2019-01-29 149 | 150 | 151 | #### Features 152 | 153 | * **fixture:** Shorten route to Temp File 154 | 155 | 156 | 157 | ## 0.11.0 - 2019-01-29 158 | 159 | 160 | #### Breaking Changes 161 | 162 | * **fixture:** 163 | * Unify on error type 164 | * Newtype for TempDir 165 | 166 | #### Features 167 | 168 | * **fixture:** 169 | * Debug persistence support 170 | * Auto-create directories 171 | * NamedTempFile support 172 | * Copy a file 173 | * Support creating dirs 174 | 175 | 176 | 177 | ## 0.10.1 - 2019-01-07 178 | 179 | 180 | #### Bug Fixes 181 | 182 | * **assert:** Show why assert failed 183 | 184 | 185 | 186 | ## 0.10.0 - 2018-10-26 187 | 188 | 189 | #### Breaking Changes 190 | 191 | * Re-structure API 192 | 193 | #### Bug Fixes 194 | 195 | * Expose errors where relevant in the API 196 | * Re-structure API 197 | 198 | #### Features 199 | 200 | * **assert:** Accept Predicate 201 | 202 | 203 | 204 | ## 0.9.0 - 2018-08-02 205 | 206 | 207 | #### Breaking Changes 208 | 209 | * Bury errors in the docs 210 | * Remove failure from API 211 | * Rename traits to clarify intent 212 | 213 | #### Features 214 | 215 | * **assert:** 216 | * Show cause of failure 217 | * Support assert(bytes) shorthand 218 | * Use DifferencePredicate for str 219 | * Predicate<[u8]> acts on file contents 220 | 221 | #### Bug Fixes 222 | 223 | * Bury errors in the docs 224 | * Remove failure from API 225 | * Rename traits to clarify intent 226 | * **assert:** 227 | * Isolate API details 228 | 229 | 230 | 231 | ## 0.3.0 - 2018-06-27 232 | 233 | 234 | #### Features 235 | 236 | * **assert:** 237 | * Support `assert(bytes)` shorthand 238 | * Support `assert(str)` shorthand 239 | * **fixture:** 240 | * copy_from now uses gitignore globs 241 | * Improve fixture error reporting 242 | 243 | #### Bug Fixes 244 | 245 | * **fixture:** 246 | * `copy_from(".")` failed 247 | 248 | #### Breaking Changes 249 | 250 | * Rename traits to clarify intent 251 | 252 | 253 | 254 | ## 0.2.1 - 2018-06-13 255 | 256 | 257 | * Documentation updates 258 | 259 | 260 | ## 0.2.0 - 2018-05-30 261 | 262 | #### Bug Fixes 263 | 264 | * **fixtures:** copy_from now works 265 | 266 | #### Features 267 | 268 | * Filesystem assertions 269 | 270 | #### Breaking Changes 271 | 272 | * Update version of `predicates-rs` to v0.5.0. 273 | 274 | ## 0.1.1 - 2018-05-28 275 | 276 | 277 | #### Features 278 | 279 | * Add a prelude 280 | 281 | 282 | 283 | [Unreleased]: https://github.com/assert-rs/assert_fs/compare/v1.1.3...HEAD 284 | [1.1.3]: https://github.com/assert-rs/assert_fs/compare/v1.1.2...v1.1.3 285 | [1.1.2]: https://github.com/assert-rs/assert_fs/compare/v1.1.1...v1.1.2 286 | [1.1.1]: https://github.com/assert-rs/assert_fs/compare/v1.1.0...v1.1.1 287 | [1.1.0]: https://github.com/assert-rs/assert_fs/compare/v1.0.13...v1.1.0 288 | [1.0.13]: https://github.com/assert-rs/assert_fs/compare/v1.0.12...v1.0.13 289 | [1.0.12]: https://github.com/assert-rs/assert_fs/compare/v1.0.11...v1.0.12 290 | [1.0.11]: https://github.com/assert-rs/assert_fs/compare/v1.0.10...v1.0.11 291 | [1.0.10]: https://github.com/assert-rs/assert_fs/compare/v1.0.9...v1.0.10 292 | [1.0.9]: https://github.com/assert-rs/assert_fs/compare/v1.0.8...v1.0.9 293 | [1.0.8]: https://github.com/assert-rs/assert_fs/compare/v1.0.7...v1.0.8 294 | [1.0.7]: https://github.com/assert-rs/assert_fs/compare/v1.0.6...v1.0.7 295 | [1.0.6]: https://github.com/assert-rs/assert_fs/compare/v1.0.5...v1.0.6 296 | [1.0.5]: https://github.com/assert-rs/assert_fs/compare/v1.0.4...v1.0.5 297 | [1.0.4]: https://github.com/assert-rs/assert_fs/compare/v1.0.3...v1.0.4 298 | [1.0.3]: https://github.com/assert-rs/assert_fs/compare/v1.0.2...v1.0.3 299 | [1.0.2]: https://github.com/assert-rs/assert_fs/compare/v1.0.1...v1.0.2 300 | [1.0.1]: https://github.com/assert-rs/assert_fs/compare/v1.0.0...v1.0.1 301 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to stager 2 | 3 | Thanks for wanting to contribute! There are many ways to contribute and we 4 | appreciate any level you're willing to do. 5 | 6 | ## Feature Requests 7 | 8 | Need some new functionality to help? You can let us know by opening an 9 | [issue][new issue]. It's helpful to look through [all issues][all issues] in 10 | case it's already being talked about. 11 | 12 | ## Bug Reports 13 | 14 | Please let us know about what problems you run into, whether in behavior or 15 | ergonomics of API. You can do this by opening an [issue][new issue]. It's 16 | helpful to look through [all issues][all issues] in case it's already being 17 | talked about. 18 | 19 | ## Pull Requests 20 | 21 | Looking for an idea? Check our [issues][issues]. If the issue looks open ended, 22 | it is probably best to post on the issue how you are thinking of resolving the 23 | issue so you can get feedback early in the process. We want you to be 24 | successful and it can be discouraging to find out a lot of re-work is needed. 25 | 26 | Already have an idea? It might be good to first [create an issue][new issue] 27 | to propose it so we can make sure we are aligned and lower the risk of having 28 | to re-work some of it and the discouragement that goes along with that. 29 | 30 | ### Process 31 | 32 | As a heads up, we'll be running your PR through the following gauntlet: 33 | - warnings turned to compile errors 34 | - `cargo test` 35 | - `rustfmt` 36 | - `clippy` 37 | - `rustdoc` 38 | - [`committed`](https://github.com/crate-ci/committed) as we use [Conventional](https://www.conventionalcommits.org) commit style 39 | - [`typos`](https://github.com/crate-ci/typos) to check spelling 40 | 41 | Not everything can be checked automatically though. 42 | 43 | We request that the commit history gets cleaned up. 44 | 45 | We ask that commits are atomic, meaning they are complete and have a single responsibility. 46 | A complete commit should build, pass tests, update documentation and tests, and not have dead code. 47 | 48 | PRs should tell a cohesive story, with refactor and test commits that keep the 49 | fix or feature commits simple and clear. 50 | 51 | Specifically, we would encourage 52 | - File renames be isolated into their own commit 53 | - Add tests in a commit before their feature or fix, showing the current behavior (i.e. they should pass). 54 | The diff for the feature/fix commit will then show how the behavior changed, 55 | making the commit's intent clearer to reviewers and the community, and showing people that the 56 | test is verifying the expected state. 57 | - e.g. [clap#5520](https://github.com/clap-rs/clap/pull/5520) 58 | 59 | Note that we are talking about ideals. 60 | We understand having a clean history requires more advanced git skills; 61 | feel free to ask us for help! 62 | We might even suggest where it would work to be lax. 63 | We also understand that editing some early commits may cause a lot of churn 64 | with merge conflicts which can make it not worth editing all of the history. 65 | 66 | For code organization, we recommend 67 | - Grouping `impl` blocks next to their type (or trait) 68 | - Grouping private items after the `pub` item that uses them. 69 | - The intent is to help people quickly find the "relevant" details, allowing them to "dig deeper" as needed. Or put another way, the `pub` items serve as a table-of-contents. 70 | - The exact order is fuzzy; do what makes sense 71 | 72 | ## Releasing 73 | 74 | Pre-requisites 75 | - Running `cargo login` 76 | - A member of `rust-cli:Maintainers` 77 | - Push permission to the repo 78 | - [`cargo-release`](https://github.com/crate-ci/cargo-release/) 79 | 80 | When we're ready to release, a project owner should do the following 81 | 1. Update the changelog (see `cargo release changes` for ideas) 82 | 2. Determine what the next version is, according to semver 83 | 3. Run [`cargo release -x `](https://github.com/crate-ci/cargo-release) 84 | 85 | [issues]: https://github.com/assert-rs/assert_fs/issues 86 | [new issue]: https://github.com/assert-rs/assert_fs/issues/new 87 | [all issues]: https://github.com/assert-rs/assert_fs/issues?utf8=%E2%9C%93&q=is%3Aissue 88 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.19" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.7" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "utf8parse", 26 | ] 27 | 28 | [[package]] 29 | name = "anstyle" 30 | version = "1.0.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" 33 | 34 | [[package]] 35 | name = "anstyle-parse" 36 | version = "0.2.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" 39 | dependencies = [ 40 | "utf8parse", 41 | ] 42 | 43 | [[package]] 44 | name = "anstyle-query" 45 | version = "1.0.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 48 | dependencies = [ 49 | "windows-sys 0.48.0", 50 | ] 51 | 52 | [[package]] 53 | name = "anstyle-wincon" 54 | version = "3.0.1" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" 57 | dependencies = [ 58 | "anstyle", 59 | "windows-sys 0.48.0", 60 | ] 61 | 62 | [[package]] 63 | name = "assert_fs" 64 | version = "1.1.3" 65 | dependencies = [ 66 | "anstream", 67 | "anstyle", 68 | "automod", 69 | "globwalk", 70 | "predicates", 71 | "predicates-core", 72 | "predicates-tree", 73 | "tempfile", 74 | ] 75 | 76 | [[package]] 77 | name = "automod" 78 | version = "1.0.15" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "ebb4bd301db2e2ca1f5be131c24eb8ebf2d9559bc3744419e93baf8ddea7e670" 81 | dependencies = [ 82 | "proc-macro2", 83 | "quote", 84 | "syn", 85 | ] 86 | 87 | [[package]] 88 | name = "bitflags" 89 | version = "1.3.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 92 | 93 | [[package]] 94 | name = "bitflags" 95 | version = "2.4.1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 98 | 99 | [[package]] 100 | name = "bstr" 101 | version = "0.2.17" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 104 | dependencies = [ 105 | "memchr", 106 | ] 107 | 108 | [[package]] 109 | name = "cfg-if" 110 | version = "1.0.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 113 | 114 | [[package]] 115 | name = "colorchoice" 116 | version = "1.0.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 119 | 120 | [[package]] 121 | name = "crossbeam-utils" 122 | version = "0.8.12" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" 125 | dependencies = [ 126 | "cfg-if", 127 | ] 128 | 129 | [[package]] 130 | name = "difflib" 131 | version = "0.4.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 134 | 135 | [[package]] 136 | name = "either" 137 | version = "1.8.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 140 | 141 | [[package]] 142 | name = "errno" 143 | version = "0.3.11" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 146 | dependencies = [ 147 | "libc", 148 | "windows-sys 0.59.0", 149 | ] 150 | 151 | [[package]] 152 | name = "fastrand" 153 | version = "2.3.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 156 | 157 | [[package]] 158 | name = "fnv" 159 | version = "1.0.7" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 162 | 163 | [[package]] 164 | name = "globset" 165 | version = "0.4.9" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" 168 | dependencies = [ 169 | "aho-corasick", 170 | "bstr", 171 | "fnv", 172 | "log", 173 | "regex", 174 | ] 175 | 176 | [[package]] 177 | name = "globwalk" 178 | version = "0.9.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "baba373693f105316dff9ebdae694118df59cf8fb3fc21b2acf1e294a3893794" 181 | dependencies = [ 182 | "bitflags 2.4.1", 183 | "ignore", 184 | "walkdir", 185 | ] 186 | 187 | [[package]] 188 | name = "ignore" 189 | version = "0.4.18" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" 192 | dependencies = [ 193 | "crossbeam-utils", 194 | "globset", 195 | "lazy_static", 196 | "log", 197 | "memchr", 198 | "regex", 199 | "same-file", 200 | "thread_local", 201 | "walkdir", 202 | "winapi-util", 203 | ] 204 | 205 | [[package]] 206 | name = "itertools" 207 | version = "0.10.5" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 210 | dependencies = [ 211 | "either", 212 | ] 213 | 214 | [[package]] 215 | name = "lazy_static" 216 | version = "1.4.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 219 | 220 | [[package]] 221 | name = "libc" 222 | version = "0.2.172" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 225 | 226 | [[package]] 227 | name = "linux-raw-sys" 228 | version = "0.4.15" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 231 | 232 | [[package]] 233 | name = "log" 234 | version = "0.4.17" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 237 | dependencies = [ 238 | "cfg-if", 239 | ] 240 | 241 | [[package]] 242 | name = "memchr" 243 | version = "2.5.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 246 | 247 | [[package]] 248 | name = "once_cell" 249 | version = "1.21.3" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 252 | 253 | [[package]] 254 | name = "predicates" 255 | version = "3.0.3" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" 258 | dependencies = [ 259 | "anstyle", 260 | "difflib", 261 | "itertools", 262 | "predicates-core", 263 | ] 264 | 265 | [[package]] 266 | name = "predicates-core" 267 | version = "1.0.6" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" 270 | 271 | [[package]] 272 | name = "predicates-tree" 273 | version = "1.0.8" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "2cfd78a1cd6926a74e7f263eb466b765c79409b176be05c3676fac27df378e96" 276 | dependencies = [ 277 | "predicates-core", 278 | "termtree", 279 | ] 280 | 281 | [[package]] 282 | name = "proc-macro2" 283 | version = "1.0.79" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 286 | dependencies = [ 287 | "unicode-ident", 288 | ] 289 | 290 | [[package]] 291 | name = "quote" 292 | version = "1.0.35" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 295 | dependencies = [ 296 | "proc-macro2", 297 | ] 298 | 299 | [[package]] 300 | name = "redox_syscall" 301 | version = "0.3.5" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 304 | dependencies = [ 305 | "bitflags 1.3.2", 306 | ] 307 | 308 | [[package]] 309 | name = "regex" 310 | version = "1.6.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 313 | dependencies = [ 314 | "aho-corasick", 315 | "memchr", 316 | "regex-syntax", 317 | ] 318 | 319 | [[package]] 320 | name = "regex-syntax" 321 | version = "0.6.27" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 324 | 325 | [[package]] 326 | name = "rustix" 327 | version = "0.38.44" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 330 | dependencies = [ 331 | "bitflags 2.4.1", 332 | "errno", 333 | "libc", 334 | "linux-raw-sys", 335 | "windows-sys 0.59.0", 336 | ] 337 | 338 | [[package]] 339 | name = "same-file" 340 | version = "1.0.6" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 343 | dependencies = [ 344 | "winapi-util", 345 | ] 346 | 347 | [[package]] 348 | name = "syn" 349 | version = "2.0.57" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" 352 | dependencies = [ 353 | "proc-macro2", 354 | "quote", 355 | "unicode-ident", 356 | ] 357 | 358 | [[package]] 359 | name = "tempfile" 360 | version = "3.8.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" 363 | dependencies = [ 364 | "cfg-if", 365 | "fastrand", 366 | "redox_syscall", 367 | "rustix", 368 | "windows-sys 0.48.0", 369 | ] 370 | 371 | [[package]] 372 | name = "termtree" 373 | version = "0.4.1" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" 376 | 377 | [[package]] 378 | name = "thread_local" 379 | version = "1.1.4" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 382 | dependencies = [ 383 | "once_cell", 384 | ] 385 | 386 | [[package]] 387 | name = "unicode-ident" 388 | version = "1.0.12" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 391 | 392 | [[package]] 393 | name = "utf8parse" 394 | version = "0.2.1" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 397 | 398 | [[package]] 399 | name = "walkdir" 400 | version = "2.3.2" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 403 | dependencies = [ 404 | "same-file", 405 | "winapi", 406 | "winapi-util", 407 | ] 408 | 409 | [[package]] 410 | name = "winapi" 411 | version = "0.3.9" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 414 | dependencies = [ 415 | "winapi-i686-pc-windows-gnu", 416 | "winapi-x86_64-pc-windows-gnu", 417 | ] 418 | 419 | [[package]] 420 | name = "winapi-i686-pc-windows-gnu" 421 | version = "0.4.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 424 | 425 | [[package]] 426 | name = "winapi-util" 427 | version = "0.1.5" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 430 | dependencies = [ 431 | "winapi", 432 | ] 433 | 434 | [[package]] 435 | name = "winapi-x86_64-pc-windows-gnu" 436 | version = "0.4.0" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 439 | 440 | [[package]] 441 | name = "windows-sys" 442 | version = "0.48.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 445 | dependencies = [ 446 | "windows-targets 0.48.0", 447 | ] 448 | 449 | [[package]] 450 | name = "windows-sys" 451 | version = "0.59.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 454 | dependencies = [ 455 | "windows-targets 0.52.6", 456 | ] 457 | 458 | [[package]] 459 | name = "windows-targets" 460 | version = "0.48.0" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 463 | dependencies = [ 464 | "windows_aarch64_gnullvm 0.48.0", 465 | "windows_aarch64_msvc 0.48.0", 466 | "windows_i686_gnu 0.48.0", 467 | "windows_i686_msvc 0.48.0", 468 | "windows_x86_64_gnu 0.48.0", 469 | "windows_x86_64_gnullvm 0.48.0", 470 | "windows_x86_64_msvc 0.48.0", 471 | ] 472 | 473 | [[package]] 474 | name = "windows-targets" 475 | version = "0.52.6" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 478 | dependencies = [ 479 | "windows_aarch64_gnullvm 0.52.6", 480 | "windows_aarch64_msvc 0.52.6", 481 | "windows_i686_gnu 0.52.6", 482 | "windows_i686_gnullvm", 483 | "windows_i686_msvc 0.52.6", 484 | "windows_x86_64_gnu 0.52.6", 485 | "windows_x86_64_gnullvm 0.52.6", 486 | "windows_x86_64_msvc 0.52.6", 487 | ] 488 | 489 | [[package]] 490 | name = "windows_aarch64_gnullvm" 491 | version = "0.48.0" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 494 | 495 | [[package]] 496 | name = "windows_aarch64_gnullvm" 497 | version = "0.52.6" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 500 | 501 | [[package]] 502 | name = "windows_aarch64_msvc" 503 | version = "0.48.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 506 | 507 | [[package]] 508 | name = "windows_aarch64_msvc" 509 | version = "0.52.6" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 512 | 513 | [[package]] 514 | name = "windows_i686_gnu" 515 | version = "0.48.0" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 518 | 519 | [[package]] 520 | name = "windows_i686_gnu" 521 | version = "0.52.6" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 524 | 525 | [[package]] 526 | name = "windows_i686_gnullvm" 527 | version = "0.52.6" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 530 | 531 | [[package]] 532 | name = "windows_i686_msvc" 533 | version = "0.48.0" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 536 | 537 | [[package]] 538 | name = "windows_i686_msvc" 539 | version = "0.52.6" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 542 | 543 | [[package]] 544 | name = "windows_x86_64_gnu" 545 | version = "0.48.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 548 | 549 | [[package]] 550 | name = "windows_x86_64_gnu" 551 | version = "0.52.6" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 554 | 555 | [[package]] 556 | name = "windows_x86_64_gnullvm" 557 | version = "0.48.0" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 560 | 561 | [[package]] 562 | name = "windows_x86_64_gnullvm" 563 | version = "0.52.6" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 566 | 567 | [[package]] 568 | name = "windows_x86_64_msvc" 569 | version = "0.48.0" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 572 | 573 | [[package]] 574 | name = "windows_x86_64_msvc" 575 | version = "0.52.6" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 578 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | [workspace.package] 5 | repository = "https://github.com/assert-rs/assert_fs.git" 6 | license = "MIT OR Apache-2.0" 7 | edition = "2021" 8 | rust-version = "1.74" # MSRV 9 | include = [ 10 | "build.rs", 11 | "src/**/*", 12 | "Cargo.toml", 13 | "Cargo.lock", 14 | "LICENSE*", 15 | "README.md", 16 | "examples/**/*" 17 | ] 18 | 19 | [workspace.lints.rust] 20 | rust_2018_idioms = { level = "warn", priority = -1 } 21 | unnameable_types = "warn" 22 | unreachable_pub = "warn" 23 | unsafe_op_in_unsafe_fn = "warn" 24 | unused_lifetimes = "warn" 25 | unused_macro_rules = "warn" 26 | unused_qualifications = "warn" 27 | 28 | [workspace.lints.clippy] 29 | bool_assert_comparison = "allow" 30 | branches_sharing_code = "allow" 31 | checked_conversions = "warn" 32 | collapsible_else_if = "allow" 33 | create_dir = "warn" 34 | dbg_macro = "warn" 35 | debug_assert_with_mut_call = "warn" 36 | doc_markdown = "warn" 37 | empty_enum = "warn" 38 | enum_glob_use = "warn" 39 | expl_impl_clone_on_copy = "warn" 40 | explicit_deref_methods = "warn" 41 | explicit_into_iter_loop = "warn" 42 | fallible_impl_from = "warn" 43 | filter_map_next = "warn" 44 | flat_map_option = "warn" 45 | float_cmp_const = "warn" 46 | fn_params_excessive_bools = "warn" 47 | from_iter_instead_of_collect = "warn" 48 | if_same_then_else = "allow" 49 | implicit_clone = "warn" 50 | imprecise_flops = "warn" 51 | inconsistent_struct_constructor = "warn" 52 | inefficient_to_string = "warn" 53 | infinite_loop = "warn" 54 | invalid_upcast_comparisons = "warn" 55 | large_digit_groups = "warn" 56 | large_stack_arrays = "warn" 57 | large_types_passed_by_value = "warn" 58 | let_and_return = "allow" # sometimes good to name what you are returning 59 | linkedlist = "warn" 60 | lossy_float_literal = "warn" 61 | macro_use_imports = "warn" 62 | mem_forget = "warn" 63 | mutex_integer = "warn" 64 | needless_continue = "allow" 65 | needless_for_each = "warn" 66 | negative_feature_names = "warn" 67 | path_buf_push_overwrite = "warn" 68 | ptr_as_ptr = "warn" 69 | rc_mutex = "warn" 70 | redundant_feature_names = "warn" 71 | ref_option_ref = "warn" 72 | rest_pat_in_fully_bound_structs = "warn" 73 | result_large_err = "allow" 74 | same_functions_in_if_condition = "warn" 75 | self_named_module_files = "warn" 76 | semicolon_if_nothing_returned = "warn" 77 | str_to_string = "warn" 78 | string_add = "warn" 79 | string_add_assign = "warn" 80 | string_lit_as_bytes = "warn" 81 | string_to_string = "warn" 82 | todo = "warn" 83 | trait_duplication_in_bounds = "warn" 84 | uninlined_format_args = "warn" 85 | verbose_file_reads = "warn" 86 | wildcard_imports = "warn" 87 | zero_sized_map_values = "warn" 88 | 89 | [profile.dev] 90 | panic = "abort" 91 | 92 | [profile.release] 93 | panic = "abort" 94 | codegen-units = 1 95 | lto = true 96 | # debug = "line-tables-only" # requires Cargo 1.71 97 | 98 | [package] 99 | name = "assert_fs" 100 | version = "1.1.3" 101 | authors = ["Ed Page "] 102 | description = "Filesystem fixtures and assertions for testing." 103 | homepage = "https://github.com/assert-rs/assert_fs" 104 | documentation = "http://docs.rs/assert_fs/" 105 | readme = "README.md" 106 | categories = ["development-tools::testing"] 107 | keywords = ["filesystem", "test", "assert", "fixture"] 108 | repository.workspace = true 109 | license.workspace = true 110 | edition.workspace = true 111 | rust-version.workspace = true 112 | include.workspace = true 113 | 114 | [package.metadata.docs.rs] 115 | all-features = true 116 | rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] 117 | 118 | [package.metadata.release] 119 | pre-release-replacements = [ 120 | {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, 121 | {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, 122 | {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, 123 | {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, 124 | {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/assert-rs/assert_fs/compare/{{tag_name}}...HEAD", exactly=1}, 125 | ] 126 | 127 | [features] 128 | color = ["dep:anstream", "predicates/color"] 129 | color-auto = ["color"] 130 | 131 | [dependencies] 132 | tempfile = "3.8" 133 | globwalk = "0.9" 134 | predicates = { version = "3.0.1", default-features = false, features = ["diff"] } 135 | predicates-core = "1.0.6" 136 | predicates-tree = "1.0.1" 137 | anstyle = "1.0.0" 138 | anstream = { version = "0.6.7", optional = true } 139 | 140 | [dev-dependencies] 141 | automod = "1.0.14" 142 | 143 | [lints] 144 | workspace = true 145 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) Individual contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # assert_fs 2 | 3 | > **Assert Filesystems** - Filesystem fixtures and assertions for testing. 4 | 5 | [![Documentation](https://img.shields.io/badge/docs-master-blue.svg)][Documentation] 6 | ![License](https://img.shields.io/crates/l/assert_fs.svg) 7 | [![Crates Status](https://img.shields.io/crates/v/assert_fs.svg)][Crates.io] 8 | 9 | `assert_fs` aims to simplify 10 | - Setting up files for your tests to consume 11 | - Asserting on files produced by your tests 12 | 13 | ## Example 14 | 15 | Here is a trivial example: 16 | 17 | ```rust 18 | use assert_fs::prelude::*; 19 | use predicates::prelude::*; 20 | 21 | let temp = assert_fs::TempDir::new().unwrap(); 22 | let input_file = temp.child("foo.txt"); 23 | input_file.touch().unwrap(); 24 | // ... do something with input_file ... 25 | input_file.assert(""); 26 | temp.child("bar.txt").assert(predicate::path::missing()); 27 | temp.close().unwrap(); 28 | ``` 29 | 30 | See the [documentation](https://docs.rs/assert_fs) for more information. 31 | 32 | ## License 33 | 34 | Licensed under either of 35 | 36 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 37 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 38 | 39 | at your option. 40 | 41 | ### Contribution 42 | 43 | Unless you explicitly state otherwise, any contribution intentionally 44 | submitted for inclusion in the work by you, as defined in the Apache-2.0 45 | license, shall be dual-licensed as above, without any additional terms or 46 | conditions. 47 | 48 | [Crates.io]: https://crates.io/crates/assert_fs 49 | [Documentation]: https://docs.rs/assert_fs 50 | -------------------------------------------------------------------------------- /committed.toml: -------------------------------------------------------------------------------- 1 | style="conventional" 2 | ignore_author_re="(dependabot|renovate)" 3 | merge_commit = false 4 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # Note that all fields that take a lint level have these possible values: 2 | # * deny - An error will be produced and the check will fail 3 | # * warn - A warning will be produced, but the check will not fail 4 | # * allow - No warning or error will be produced, though in some cases a note 5 | # will be 6 | 7 | # Root options 8 | 9 | # The graph table configures how the dependency graph is constructed and thus 10 | # which crates the checks are performed against 11 | [graph] 12 | # If 1 or more target triples (and optionally, target_features) are specified, 13 | # only the specified targets will be checked when running `cargo deny check`. 14 | # This means, if a particular package is only ever used as a target specific 15 | # dependency, such as, for example, the `nix` crate only being used via the 16 | # `target_family = "unix"` configuration, that only having windows targets in 17 | # this list would mean the nix crate, as well as any of its exclusive 18 | # dependencies not shared by any other crates, would be ignored, as the target 19 | # list here is effectively saying which targets you are building for. 20 | targets = [ 21 | # The triple can be any string, but only the target triples built in to 22 | # rustc (as of 1.40) can be checked against actual config expressions 23 | #"x86_64-unknown-linux-musl", 24 | # You can also specify which target_features you promise are enabled for a 25 | # particular target. target_features are currently not validated against 26 | # the actual valid features supported by the target architecture. 27 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 28 | ] 29 | # When creating the dependency graph used as the source of truth when checks are 30 | # executed, this field can be used to prune crates from the graph, removing them 31 | # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate 32 | # is pruned from the graph, all of its dependencies will also be pruned unless 33 | # they are connected to another crate in the graph that hasn't been pruned, 34 | # so it should be used with care. The identifiers are [Package ID Specifications] 35 | # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) 36 | #exclude = [] 37 | # If true, metadata will be collected with `--all-features`. Note that this can't 38 | # be toggled off if true, if you want to conditionally enable `--all-features` it 39 | # is recommended to pass `--all-features` on the cmd line instead 40 | all-features = false 41 | # If true, metadata will be collected with `--no-default-features`. The same 42 | # caveat with `all-features` applies 43 | no-default-features = false 44 | # If set, these feature will be enabled when collecting metadata. If `--features` 45 | # is specified on the cmd line they will take precedence over this option. 46 | #features = [] 47 | 48 | # The output table provides options for how/if diagnostics are outputted 49 | [output] 50 | # When outputting inclusion graphs in diagnostics that include features, this 51 | # option can be used to specify the depth at which feature edges will be added. 52 | # This option is included since the graphs can be quite large and the addition 53 | # of features from the crate(s) to all of the graph roots can be far too verbose. 54 | # This option can be overridden via `--feature-depth` on the cmd line 55 | feature-depth = 1 56 | 57 | # This section is considered when running `cargo deny check advisories` 58 | # More documentation for the advisories section can be found here: 59 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 60 | [advisories] 61 | # The path where the advisory databases are cloned/fetched into 62 | #db-path = "$CARGO_HOME/advisory-dbs" 63 | # The url(s) of the advisory databases to use 64 | #db-urls = ["https://github.com/rustsec/advisory-db"] 65 | # A list of advisory IDs to ignore. Note that ignored advisories will still 66 | # output a note when they are encountered. 67 | ignore = [ 68 | #"RUSTSEC-0000-0000", 69 | #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, 70 | #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish 71 | #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, 72 | ] 73 | # If this is true, then cargo deny will use the git executable to fetch advisory database. 74 | # If this is false, then it uses a built-in git library. 75 | # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. 76 | # See Git Authentication for more information about setting up git authentication. 77 | #git-fetch-with-cli = true 78 | 79 | # This section is considered when running `cargo deny check licenses` 80 | # More documentation for the licenses section can be found here: 81 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 82 | [licenses] 83 | # List of explicitly allowed licenses 84 | # See https://spdx.org/licenses/ for list of possible licenses 85 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. 86 | allow = [ 87 | "MIT", 88 | "MIT-0", 89 | "Apache-2.0", 90 | "BSD-2-Clause", 91 | "BSD-3-Clause", 92 | "MPL-2.0", 93 | "Unicode-DFS-2016", 94 | "CC0-1.0", 95 | "ISC", 96 | "OpenSSL", 97 | ] 98 | # The confidence threshold for detecting a license from license text. 99 | # The higher the value, the more closely the license text must be to the 100 | # canonical license text of a valid SPDX license file. 101 | # [possible values: any between 0.0 and 1.0]. 102 | confidence-threshold = 0.8 103 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 104 | # aren't accepted for every possible crate as with the normal allow list 105 | exceptions = [ 106 | # Each entry is the crate and version constraint, and its specific allow 107 | # list 108 | #{ allow = ["Zlib"], crate = "adler32" }, 109 | ] 110 | 111 | # Some crates don't have (easily) machine readable licensing information, 112 | # adding a clarification entry for it allows you to manually specify the 113 | # licensing information 114 | [[licenses.clarify]] 115 | # The package spec the clarification applies to 116 | crate = "ring" 117 | # The SPDX expression for the license requirements of the crate 118 | expression = "MIT AND ISC AND OpenSSL" 119 | # One or more files in the crate's source used as the "source of truth" for 120 | # the license expression. If the contents match, the clarification will be used 121 | # when running the license check, otherwise the clarification will be ignored 122 | # and the crate will be checked normally, which may produce warnings or errors 123 | # depending on the rest of your configuration 124 | license-files = [ 125 | # Each entry is a crate relative path, and the (opaque) hash of its contents 126 | { path = "LICENSE", hash = 0xbd0eed23 } 127 | ] 128 | 129 | [licenses.private] 130 | # If true, ignores workspace crates that aren't published, or are only 131 | # published to private registries. 132 | # To see how to mark a crate as unpublished (to the official registry), 133 | # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. 134 | ignore = true 135 | # One or more private registries that you might publish crates to, if a crate 136 | # is only published to private registries, and ignore is true, the crate will 137 | # not have its license(s) checked 138 | registries = [ 139 | #"https://sekretz.com/registry 140 | ] 141 | 142 | # This section is considered when running `cargo deny check bans`. 143 | # More documentation about the 'bans' section can be found here: 144 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 145 | [bans] 146 | # Lint level for when multiple versions of the same crate are detected 147 | multiple-versions = "warn" 148 | # Lint level for when a crate version requirement is `*` 149 | wildcards = "allow" 150 | # The graph highlighting used when creating dotgraphs for crates 151 | # with multiple versions 152 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 153 | # * simplest-path - The path to the version with the fewest edges is highlighted 154 | # * all - Both lowest-version and simplest-path are used 155 | highlight = "all" 156 | # The default lint level for `default` features for crates that are members of 157 | # the workspace that is being checked. This can be overridden by allowing/denying 158 | # `default` on a crate-by-crate basis if desired. 159 | workspace-default-features = "allow" 160 | # The default lint level for `default` features for external crates that are not 161 | # members of the workspace. This can be overridden by allowing/denying `default` 162 | # on a crate-by-crate basis if desired. 163 | external-default-features = "allow" 164 | # List of crates that are allowed. Use with care! 165 | allow = [ 166 | #"ansi_term@0.11.0", 167 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, 168 | ] 169 | # List of crates to deny 170 | deny = [ 171 | #"ansi_term@0.11.0", 172 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, 173 | # Wrapper crates can optionally be specified to allow the crate when it 174 | # is a direct dependency of the otherwise banned crate 175 | #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, 176 | ] 177 | 178 | # List of features to allow/deny 179 | # Each entry the name of a crate and a version range. If version is 180 | # not specified, all versions will be matched. 181 | #[[bans.features]] 182 | #crate = "reqwest" 183 | # Features to not allow 184 | #deny = ["json"] 185 | # Features to allow 186 | #allow = [ 187 | # "rustls", 188 | # "__rustls", 189 | # "__tls", 190 | # "hyper-rustls", 191 | # "rustls", 192 | # "rustls-pemfile", 193 | # "rustls-tls-webpki-roots", 194 | # "tokio-rustls", 195 | # "webpki-roots", 196 | #] 197 | # If true, the allowed features must exactly match the enabled feature set. If 198 | # this is set there is no point setting `deny` 199 | #exact = true 200 | 201 | # Certain crates/versions that will be skipped when doing duplicate detection. 202 | skip = [ 203 | #"ansi_term@0.11.0", 204 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, 205 | ] 206 | # Similarly to `skip` allows you to skip certain crates during duplicate 207 | # detection. Unlike skip, it also includes the entire tree of transitive 208 | # dependencies starting at the specified crate, up to a certain depth, which is 209 | # by default infinite. 210 | skip-tree = [ 211 | #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies 212 | #{ crate = "ansi_term@0.11.0", depth = 20 }, 213 | ] 214 | 215 | # This section is considered when running `cargo deny check sources`. 216 | # More documentation about the 'sources' section can be found here: 217 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 218 | [sources] 219 | # Lint level for what to happen when a crate from a crate registry that is not 220 | # in the allow list is encountered 221 | unknown-registry = "deny" 222 | # Lint level for what to happen when a crate from a git repository that is not 223 | # in the allow list is encountered 224 | unknown-git = "deny" 225 | # List of URLs for allowed crate registries. Defaults to the crates.io index 226 | # if not specified. If it is specified but empty, no registries are allowed. 227 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 228 | # List of URLs for allowed Git repositories 229 | allow-git = [] 230 | 231 | [sources.allow-org] 232 | # 1 or more github.com organizations to allow git sources for 233 | github = [] 234 | # 1 or more gitlab.com organizations to allow git sources for 235 | gitlab = [] 236 | # 1 or more bitbucket.org organizations to allow git sources for 237 | bitbucket = [] 238 | -------------------------------------------------------------------------------- /examples/failure.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unwrap_used)] 2 | 3 | use assert_fs::prelude::*; 4 | 5 | fn main() { 6 | let temp = assert_fs::TempDir::new().unwrap(); 7 | let input_file = temp.child("foo.txt"); 8 | 9 | input_file.write_str("Hello\nWorld!").unwrap(); 10 | 11 | input_file.assert("Goodbye\nWorld!"); 12 | 13 | temp.close().unwrap(); 14 | } 15 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | dependent-version = "fix" 2 | allow-branch = ["master"] 3 | -------------------------------------------------------------------------------- /src/assert.rs: -------------------------------------------------------------------------------- 1 | //! Filesystem assertions. 2 | //! 3 | //! See [`PathAssert`]. 4 | //! 5 | //! # Examples 6 | //! 7 | //! ```rust 8 | //! use assert_fs::prelude::*; 9 | //! use predicates::prelude::*; 10 | //! 11 | //! let temp = assert_fs::TempDir::new().unwrap(); 12 | //! let input_file = temp.child("foo.txt"); 13 | //! input_file.touch().unwrap(); 14 | //! 15 | //! // ... do something with input_file ... 16 | //! 17 | //! input_file.assert(""); 18 | //! temp.child("bar.txt").assert(predicate::path::missing()); 19 | //! 20 | //! temp.close().unwrap(); 21 | //! ``` 22 | //! 23 | 24 | use std::fmt; 25 | use std::path; 26 | 27 | #[cfg(feature = "color")] 28 | use anstream::panic; 29 | use predicates::path::PredicateFileContentExt; 30 | use predicates::str::PredicateStrExt; 31 | use predicates_tree::CaseTreeExt; 32 | 33 | use crate::fixture; 34 | 35 | /// Assert the state of files within [`TempDir`]. 36 | /// 37 | /// This uses [`IntoPathPredicate`] to provide short-hands for common cases, accepting: 38 | /// - `Predicate` for validating a path. 39 | /// - `Predicate` for validating the content of the file. 40 | /// - `&[u8]` or `&str` representing the content of the file. 41 | /// 42 | /// See [`predicates`] for more predicates. 43 | /// 44 | /// # Examples 45 | /// 46 | /// ```rust 47 | /// use assert_fs::prelude::*; 48 | /// use predicates::prelude::*; 49 | /// 50 | /// let temp = assert_fs::TempDir::new().unwrap(); 51 | /// let input_file = temp.child("foo.txt"); 52 | /// input_file.touch().unwrap(); 53 | /// 54 | /// // ... do something with input_file ... 55 | /// 56 | /// input_file.assert(""); 57 | /// temp.child("bar.txt").assert(predicate::path::missing()); 58 | /// 59 | /// temp.close().unwrap(); 60 | /// ``` 61 | /// 62 | /// [`TempDir`]: super::TempDir 63 | pub trait PathAssert { 64 | /// Assert the state of files within [`TempDir`]. 65 | /// 66 | /// This uses [`IntoPathPredicate`] to provide short-hands for common cases, accepting: 67 | /// - `Predicate` for validating a path. 68 | /// - `Predicate` for validating the content of the file. 69 | /// - `&[u8]` or `&str` representing the content of the file. 70 | /// 71 | /// See [`predicates`] for more predicates. 72 | /// 73 | /// # Panic 74 | /// 75 | /// Will panic if the condition is not satisfied 76 | /// 77 | /// # Examples 78 | /// 79 | /// ```rust 80 | /// use assert_fs::prelude::*; 81 | /// use predicates::prelude::*; 82 | /// 83 | /// let temp = assert_fs::TempDir::new().unwrap(); 84 | /// let input_file = temp.child("foo.txt"); 85 | /// input_file.touch().unwrap(); 86 | /// 87 | /// // ... do something with input_file ... 88 | /// 89 | /// input_file.assert(""); 90 | /// // or 91 | /// input_file.assert(predicate::str::is_empty()); 92 | /// 93 | /// temp.child("bar.txt").assert(predicate::path::missing()); 94 | /// 95 | /// temp.close().unwrap(); 96 | /// ``` 97 | /// 98 | /// [`TempDir`]: super::TempDir 99 | #[track_caller] 100 | fn assert(&self, pred: I) -> &Self 101 | where 102 | I: IntoPathPredicate

, 103 | P: predicates_core::Predicate; 104 | } 105 | 106 | impl PathAssert for fixture::TempDir { 107 | #[track_caller] 108 | fn assert(&self, pred: I) -> &Self 109 | where 110 | I: IntoPathPredicate

, 111 | P: predicates_core::Predicate, 112 | { 113 | assert(self.path(), pred); 114 | self 115 | } 116 | } 117 | 118 | impl PathAssert for fixture::NamedTempFile { 119 | #[track_caller] 120 | fn assert(&self, pred: I) -> &Self 121 | where 122 | I: IntoPathPredicate

, 123 | P: predicates_core::Predicate, 124 | { 125 | assert(self.path(), pred); 126 | self 127 | } 128 | } 129 | 130 | impl PathAssert for fixture::ChildPath { 131 | #[track_caller] 132 | fn assert(&self, pred: I) -> &Self 133 | where 134 | I: IntoPathPredicate

, 135 | P: predicates_core::Predicate, 136 | { 137 | assert(self.path(), pred); 138 | self 139 | } 140 | } 141 | 142 | #[track_caller] 143 | fn assert(path: &path::Path, pred: I) 144 | where 145 | I: IntoPathPredicate

, 146 | P: predicates_core::Predicate, 147 | { 148 | let pred = pred.into_path(); 149 | if let Some(case) = pred.find_case(false, path) { 150 | let palette = crate::Palette::color(); 151 | panic!( 152 | "Unexpected file, failed {:#}\n{:#}={:#}", 153 | case.tree(), 154 | palette.key("path"), 155 | palette.value(path.display()) 156 | ); 157 | } 158 | } 159 | 160 | /// Used by [`PathAssert`] to convert Self into the needed [`predicates_core::Predicate`]. 161 | /// 162 | /// # Examples 163 | /// 164 | /// ```rust 165 | /// use std::path; 166 | /// 167 | /// use assert_fs::prelude::*; 168 | /// use predicates::prelude::*; 169 | /// 170 | /// let temp = assert_fs::TempDir::new().unwrap(); 171 | /// 172 | /// // ... do something with input_file ... 173 | /// 174 | /// temp.child("bar.txt").assert(predicate::path::missing()); // Uses IntoPathPredicate 175 | /// 176 | /// temp.close().unwrap(); 177 | /// ``` 178 | pub trait IntoPathPredicate

179 | where 180 | P: predicates_core::Predicate, 181 | { 182 | /// The type of the predicate being returned. 183 | type Predicate; 184 | 185 | /// Convert to a predicate for testing a path. 186 | fn into_path(self) -> P; 187 | } 188 | 189 | impl

IntoPathPredicate

for P 190 | where 191 | P: predicates_core::Predicate, 192 | { 193 | type Predicate = P; 194 | 195 | fn into_path(self) -> Self::Predicate { 196 | self 197 | } 198 | } 199 | 200 | /// Keep `predicates` concrete Predicates out of our public API. 201 | /// [`predicates_core::Predicate`] used by [`IntoPathPredicate`] for bytes. 202 | /// 203 | /// # Example 204 | /// 205 | /// ```rust 206 | /// use assert_fs::prelude::*; 207 | /// 208 | /// let temp = assert_fs::TempDir::new().unwrap(); 209 | /// let input_file = temp.child("foo.txt"); 210 | /// input_file.touch().unwrap(); 211 | /// 212 | /// // ... do something with input_file ... 213 | /// 214 | /// input_file.assert(b"" as &[u8]); // uses BytesContentPathPredicate 215 | /// 216 | /// temp.close().unwrap(); 217 | /// ``` 218 | #[derive(Debug)] 219 | pub struct BytesContentPathPredicate( 220 | predicates::path::FileContentPredicate>, 221 | ); 222 | 223 | impl BytesContentPathPredicate { 224 | pub(crate) fn new(value: &'static [u8]) -> Self { 225 | let pred = predicates::ord::eq(value).from_file_path(); 226 | BytesContentPathPredicate(pred) 227 | } 228 | } 229 | 230 | impl predicates_core::reflection::PredicateReflection for BytesContentPathPredicate { 231 | fn parameters<'a>( 232 | &'a self, 233 | ) -> Box> + 'a> { 234 | self.0.parameters() 235 | } 236 | 237 | /// Nested `Predicate`s of the current `Predicate`. 238 | fn children<'a>( 239 | &'a self, 240 | ) -> Box> + 'a> { 241 | self.0.children() 242 | } 243 | } 244 | 245 | impl predicates_core::Predicate for BytesContentPathPredicate { 246 | fn eval(&self, item: &path::Path) -> bool { 247 | self.0.eval(item) 248 | } 249 | 250 | fn find_case<'a>( 251 | &'a self, 252 | expected: bool, 253 | variable: &path::Path, 254 | ) -> Option> { 255 | self.0.find_case(expected, variable) 256 | } 257 | } 258 | 259 | impl fmt::Display for BytesContentPathPredicate { 260 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 261 | self.0.fmt(f) 262 | } 263 | } 264 | 265 | impl IntoPathPredicate for &'static [u8] { 266 | type Predicate = BytesContentPathPredicate; 267 | 268 | fn into_path(self) -> Self::Predicate { 269 | Self::Predicate::new(self) 270 | } 271 | } 272 | 273 | /// Keep `predicates` concrete Predicates out of our public API. 274 | /// [`predicates_core::Predicate`] used by `IntoPathPredicate` for `str`. 275 | /// 276 | /// # Example 277 | /// 278 | /// ```rust 279 | /// use assert_fs::prelude::*; 280 | /// 281 | /// let temp = assert_fs::TempDir::new().unwrap(); 282 | /// let input_file = temp.child("foo.txt"); 283 | /// input_file.touch().unwrap(); 284 | /// 285 | /// // ... do something with input_file ... 286 | /// 287 | /// input_file.assert(""); // Uses StrContentPathPredicate 288 | /// 289 | /// temp.close().unwrap(); 290 | /// ``` 291 | #[derive(Debug, Clone)] 292 | pub struct StrContentPathPredicate( 293 | predicates::path::FileContentPredicate< 294 | predicates::str::Utf8Predicate, 295 | >, 296 | ); 297 | 298 | impl StrContentPathPredicate { 299 | pub(crate) fn new(value: String) -> Self { 300 | let pred = predicates::str::diff(value).from_utf8().from_file_path(); 301 | StrContentPathPredicate(pred) 302 | } 303 | } 304 | 305 | impl predicates_core::reflection::PredicateReflection for StrContentPathPredicate { 306 | fn parameters<'a>( 307 | &'a self, 308 | ) -> Box> + 'a> { 309 | self.0.parameters() 310 | } 311 | 312 | /// Nested `Predicate`s of the current `Predicate`. 313 | fn children<'a>( 314 | &'a self, 315 | ) -> Box> + 'a> { 316 | self.0.children() 317 | } 318 | } 319 | 320 | impl predicates_core::Predicate for StrContentPathPredicate { 321 | fn eval(&self, item: &path::Path) -> bool { 322 | self.0.eval(item) 323 | } 324 | 325 | fn find_case<'a>( 326 | &'a self, 327 | expected: bool, 328 | variable: &path::Path, 329 | ) -> Option> { 330 | self.0.find_case(expected, variable) 331 | } 332 | } 333 | 334 | impl fmt::Display for StrContentPathPredicate { 335 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 336 | self.0.fmt(f) 337 | } 338 | } 339 | 340 | impl IntoPathPredicate for String { 341 | type Predicate = StrContentPathPredicate; 342 | 343 | fn into_path(self) -> Self::Predicate { 344 | Self::Predicate::new(self) 345 | } 346 | } 347 | 348 | impl IntoPathPredicate for &str { 349 | type Predicate = StrContentPathPredicate; 350 | 351 | fn into_path(self) -> Self::Predicate { 352 | Self::Predicate::new(self.to_owned()) 353 | } 354 | } 355 | 356 | impl IntoPathPredicate for &String { 357 | type Predicate = StrContentPathPredicate; 358 | 359 | fn into_path(self) -> Self::Predicate { 360 | Self::Predicate::new(self.to_owned()) 361 | } 362 | } 363 | 364 | /// Keep `predicates` concrete Predicates out of our public API. 365 | /// [`predicates_core::Predicate`] used by `IntoPathPredicate` for `str` predicates. 366 | /// 367 | /// # Example 368 | /// 369 | /// ```rust 370 | /// use assert_fs::prelude::*; 371 | /// use predicates::prelude::*; 372 | /// 373 | /// let temp = assert_fs::TempDir::new().unwrap(); 374 | /// let input_file = temp.child("foo.txt"); 375 | /// input_file.touch().unwrap(); 376 | /// 377 | /// // ... do something with input_file ... 378 | /// 379 | /// input_file.assert(predicate::str::is_empty()); // Uses StrPathPredicate 380 | /// 381 | /// temp.close().unwrap(); 382 | /// ``` 383 | #[derive(Debug, Clone)] 384 | pub struct StrPathPredicate>( 385 | predicates::path::FileContentPredicate>, 386 | ); 387 | 388 | impl

StrPathPredicate

389 | where 390 | P: predicates_core::Predicate, 391 | { 392 | pub(crate) fn new(value: P) -> Self { 393 | let pred = value.from_utf8().from_file_path(); 394 | StrPathPredicate(pred) 395 | } 396 | } 397 | 398 | impl

predicates_core::reflection::PredicateReflection for StrPathPredicate

399 | where 400 | P: predicates_core::Predicate, 401 | { 402 | fn parameters<'a>( 403 | &'a self, 404 | ) -> Box> + 'a> { 405 | self.0.parameters() 406 | } 407 | 408 | /// Nested `Predicate`s of the current `Predicate`. 409 | fn children<'a>( 410 | &'a self, 411 | ) -> Box> + 'a> { 412 | self.0.children() 413 | } 414 | } 415 | 416 | impl

predicates_core::Predicate for StrPathPredicate

417 | where 418 | P: predicates_core::Predicate, 419 | { 420 | fn eval(&self, item: &path::Path) -> bool { 421 | self.0.eval(item) 422 | } 423 | 424 | fn find_case<'a>( 425 | &'a self, 426 | expected: bool, 427 | variable: &path::Path, 428 | ) -> Option> { 429 | self.0.find_case(expected, variable) 430 | } 431 | } 432 | 433 | impl

fmt::Display for StrPathPredicate

434 | where 435 | P: predicates_core::Predicate, 436 | { 437 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 438 | self.0.fmt(f) 439 | } 440 | } 441 | 442 | impl

IntoPathPredicate> for P 443 | where 444 | P: predicates_core::Predicate, 445 | { 446 | type Predicate = StrPathPredicate

; 447 | 448 | fn into_path(self) -> Self::Predicate { 449 | Self::Predicate::new(self) 450 | } 451 | } 452 | 453 | #[cfg(test)] 454 | mod test { 455 | use super::*; 456 | 457 | use predicates::prelude::*; 458 | 459 | // Since IntoPathPredicate exists solely for conversion, test it under that scenario to ensure 460 | // it works as expected. 461 | fn convert_path(pred: I) -> P 462 | where 463 | I: IntoPathPredicate

, 464 | P: Predicate, 465 | { 466 | pred.into_path() 467 | } 468 | 469 | #[test] 470 | fn into_path_from_pred() { 471 | let pred = convert_path(predicate::eq(path::Path::new("hello.md"))); 472 | let case = pred.find_case(false, path::Path::new("hello.md")); 473 | println!("Failing case: {case:?}"); 474 | assert!(case.is_none()); 475 | } 476 | 477 | #[test] 478 | fn into_path_from_bytes() { 479 | let pred = convert_path(b"hello\n" as &[u8]); 480 | let case = pred.find_case(false, path::Path::new("tests/fixture/hello.txt")); 481 | println!("Failing case: {case:?}"); 482 | assert!(case.is_none()); 483 | } 484 | 485 | #[test] 486 | fn into_path_from_str() { 487 | let pred = convert_path("hello\n"); 488 | let case = pred.find_case(false, path::Path::new("tests/fixture/hello.txt")); 489 | println!("Failing case: {case:?}"); 490 | assert!(case.is_none()); 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, Default)] 2 | pub(crate) struct Palette { 3 | key: anstyle::Style, 4 | value: anstyle::Style, 5 | } 6 | 7 | impl Palette { 8 | pub(crate) fn color() -> Self { 9 | if cfg!(feature = "color") { 10 | Self { 11 | key: anstyle::AnsiColor::Blue.on_default() | anstyle::Effects::BOLD, 12 | value: anstyle::AnsiColor::Yellow.on_default() | anstyle::Effects::BOLD, 13 | } 14 | } else { 15 | Self::plain() 16 | } 17 | } 18 | 19 | pub(crate) fn plain() -> Self { 20 | Self::default() 21 | } 22 | 23 | pub(crate) fn key(self, display: D) -> Styled { 24 | Styled::new(display, self.key) 25 | } 26 | 27 | pub(crate) fn value(self, display: D) -> Styled { 28 | Styled::new(display, self.value) 29 | } 30 | } 31 | 32 | #[derive(Debug)] 33 | pub(crate) struct Styled { 34 | display: D, 35 | style: anstyle::Style, 36 | } 37 | 38 | impl Styled { 39 | pub(crate) fn new(display: D, style: anstyle::Style) -> Self { 40 | Self { display, style } 41 | } 42 | } 43 | 44 | impl std::fmt::Display for Styled { 45 | #[inline] 46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 47 | if f.alternate() { 48 | write!(f, "{}", self.style.render())?; 49 | self.display.fmt(f)?; 50 | write!(f, "{}", self.style.render_reset())?; 51 | Ok(()) 52 | } else { 53 | self.display.fmt(f) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/fixture/child.rs: -------------------------------------------------------------------------------- 1 | use std::path; 2 | 3 | /// Access paths within [`TempDir`][crate::TempDir] for testing. 4 | /// 5 | /// See [`ChildPath`] trait implementations. 6 | /// 7 | /// ```rust 8 | /// use assert_fs::prelude::*; 9 | /// 10 | /// let temp = assert_fs::TempDir::new().unwrap(); 11 | /// let input_file = temp.child("foo.txt"); 12 | /// input_file.touch().unwrap(); 13 | /// temp.close().unwrap(); 14 | /// ``` 15 | /// 16 | pub trait PathChild { 17 | /// Access a path within the temp directory. 18 | /// 19 | /// # Examples 20 | /// 21 | /// ```rust 22 | /// use assert_fs::prelude::*; 23 | /// 24 | /// let temp = assert_fs::TempDir::new().unwrap(); 25 | /// println!("{}", temp.path().display()); 26 | /// println!("{}", temp.child("foo/bar.txt").path().display()); 27 | /// temp.close().unwrap(); 28 | /// ``` 29 | fn child

(&self, path: P) -> ChildPath 30 | where 31 | P: AsRef; 32 | } 33 | 34 | impl PathChild for super::TempDir { 35 | fn child

(&self, path: P) -> ChildPath 36 | where 37 | P: AsRef, 38 | { 39 | ChildPath::new(self.path().join(path.as_ref())) 40 | } 41 | } 42 | 43 | impl PathChild for ChildPath { 44 | fn child

(&self, path: P) -> ChildPath 45 | where 46 | P: AsRef, 47 | { 48 | ChildPath::new(self.path().join(path.as_ref())) 49 | } 50 | } 51 | 52 | /// A path within a [`TempDir`][crate::TempDir] 53 | /// 54 | /// See Trait Implementations. 55 | /// 56 | /// # Examples 57 | /// 58 | /// ```rust 59 | /// use assert_fs::prelude::*; 60 | /// 61 | /// let temp = assert_fs::TempDir::new().unwrap(); 62 | /// 63 | /// let input_file = temp.child("foo.txt"); 64 | /// input_file.touch().unwrap(); 65 | /// 66 | /// temp.child("bar.txt").touch().unwrap(); 67 | /// 68 | /// temp.close().unwrap(); 69 | /// ``` 70 | /// 71 | pub struct ChildPath { 72 | path: path::PathBuf, 73 | } 74 | 75 | impl ChildPath { 76 | /// Wrap a path for use with extension traits. 77 | /// 78 | /// See trait implementations or [`PathChild`] for more details. 79 | /// 80 | pub fn new

(path: P) -> Self 81 | where 82 | P: Into, 83 | { 84 | Self { path: path.into() } 85 | } 86 | 87 | /// Access the path. 88 | pub fn path(&self) -> &path::Path { 89 | &self.path 90 | } 91 | } 92 | 93 | impl AsRef for ChildPath { 94 | fn as_ref(&self) -> &path::Path { 95 | &self.path 96 | } 97 | } 98 | 99 | impl std::ops::Deref for ChildPath { 100 | type Target = path::Path; 101 | #[inline] 102 | fn deref(&self) -> &path::Path { 103 | &self.path 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/fixture/dir.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | use std::path; 3 | 4 | use super::errors::FixtureError; 5 | use super::errors::FixtureKind; 6 | use super::errors::ResultChainExt; 7 | 8 | /// A directory in the filesystem that is automatically deleted when 9 | /// it goes out of scope. 10 | /// 11 | /// The [`TempDir`] type creates a directory on the file system that 12 | /// is deleted once it goes out of scope. At construction, the 13 | /// `TempDir` creates a new directory with a randomly generated name. 14 | /// 15 | /// The constructor, [`TempDir::new()`], creates directories in 16 | /// the location returned by [`std::env::temp_dir()`]. 17 | /// 18 | /// After creating a `TempDir`, work with the file system by doing 19 | /// standard [`std::fs`] file system operations on its [`Path`], 20 | /// which can be retrieved with [`TempDir::path()`]. Once the `TempDir` 21 | /// value is dropped, the directory at the path will be deleted, along 22 | /// with any files and directories it contains. It is your responsibility 23 | /// to ensure that no further file system operations are attempted 24 | /// inside the temporary directory once it has been deleted. 25 | /// 26 | /// # Resource Leaking 27 | /// 28 | /// Various platform-specific conditions may cause `TempDir` to fail 29 | /// to delete the underlying directory. It's important to ensure that 30 | /// handles (like [`File`] and [`ReadDir`]) to files inside the 31 | /// directory are dropped before the `TempDir` goes out of scope. The 32 | /// `TempDir` destructor will silently ignore any errors in deleting 33 | /// the directory; to instead handle errors call [`TempDir::close()`]. 34 | /// 35 | /// Note that if the program exits before the `TempDir` destructor is 36 | /// run, such as via [`std::process::exit()`], by segfaulting, or by 37 | /// receiving a signal like `SIGINT`, then the temporary directory 38 | /// will not be deleted. 39 | /// 40 | /// # Examples 41 | /// 42 | /// Create a temporary directory with a generated name: 43 | /// 44 | /// ``` 45 | /// use assert_fs::fixture::TempDir; 46 | /// 47 | /// let tmp_dir = TempDir::new().unwrap(); 48 | /// 49 | /// // Ensure deletion happens. 50 | /// tmp_dir.close().unwrap(); 51 | /// ``` 52 | /// 53 | /// [`File`]: std::fs::File 54 | /// [`Path`]: std::path::Path 55 | /// [`ReadDir`]: std::fs::ReadDir 56 | #[derive(Debug)] 57 | pub struct TempDir { 58 | temp: Inner, 59 | } 60 | 61 | #[derive(Debug)] 62 | enum Inner { 63 | Temp(tempfile::TempDir), 64 | Persisted(path::PathBuf), 65 | } 66 | 67 | impl TempDir { 68 | /// Attempts to make a temporary directory inside of `env::temp_dir()`. 69 | /// 70 | /// The directory and everything inside it will be automatically deleted 71 | /// once the returned `TempDir` is destroyed. 72 | /// 73 | /// # Errors 74 | /// 75 | /// If the directory can not be created, `Err` is returned. 76 | /// 77 | /// # Examples 78 | /// 79 | /// ``` 80 | /// use assert_fs::fixture::TempDir; 81 | /// 82 | /// let tmp_dir = TempDir::new().unwrap(); 83 | /// 84 | /// // Ensure deletion happens. 85 | /// tmp_dir.close().unwrap(); 86 | /// ``` 87 | pub fn new() -> Result { 88 | let temp = tempfile::TempDir::new().chain(FixtureError::new(FixtureKind::CreateDir))?; 89 | let temp = Inner::Temp(temp); 90 | Ok(Self { temp }) 91 | } 92 | 93 | /// Attempts to make a temporary directory inside of `dir`. 94 | /// 95 | /// The directory and everything inside it will be automatically deleted 96 | /// once the returned `TempDir` is destroyed. 97 | /// 98 | /// # Errors 99 | /// 100 | /// If the directory can not be created, `Err` is returned. 101 | /// 102 | /// # Examples 103 | /// 104 | /// ``` 105 | /// use assert_fs::fixture::TempDir; 106 | /// 107 | /// // Create a directory inside of the current directory. 108 | /// let tmp_dir = TempDir::new_in(".").unwrap(); 109 | /// 110 | /// // Ensure deletion happens. 111 | /// tmp_dir.close().unwrap(); 112 | /// ``` 113 | pub fn new_in>(dir: P) -> Result { 114 | let temp = 115 | tempfile::TempDir::new_in(dir).chain(FixtureError::new(FixtureKind::CreateDir))?; 116 | let temp = Inner::Temp(temp); 117 | Ok(Self { temp }) 118 | } 119 | 120 | /// Attempts to make a temporary directory with the specified prefix inside 121 | /// of `env::temp_dir()`. 122 | /// 123 | /// The directory and everything inside it will be automatically deleted 124 | /// once the returned `TempDir` is destroyed. 125 | /// 126 | /// # Errors 127 | /// 128 | /// If the directory can not be created, `Err` is returned. 129 | /// 130 | /// # Examples 131 | /// 132 | /// ``` 133 | /// use assert_fs::fixture::TempDir; 134 | /// 135 | /// let tmp_dir = TempDir::with_prefix("foo-").unwrap(); 136 | /// 137 | /// // Ensure deletion happens. 138 | /// tmp_dir.close().unwrap(); 139 | /// ``` 140 | pub fn with_prefix>(prefix: S) -> Result { 141 | let temp = tempfile::TempDir::with_prefix(prefix) 142 | .chain(FixtureError::new(FixtureKind::CreateDir))?; 143 | let temp = Inner::Temp(temp); 144 | Ok(Self { temp }) 145 | } 146 | 147 | /// Attempts to make a temporary directory with the specified prefix inside 148 | /// the specified directory. 149 | /// 150 | /// The directory and everything inside it will be automatically deleted 151 | /// once the returned `TempDir` is destroyed. 152 | /// 153 | /// # Errors 154 | /// 155 | /// If the directory can not be created, `Err` is returned. 156 | /// 157 | /// # Examples 158 | /// 159 | /// ``` 160 | /// use assert_fs::fixture::TempDir; 161 | /// 162 | /// // Create a directory with prefix "foo-" inside the current directory. 163 | /// let tmp_dir = TempDir::with_prefix_in("foo-", ".").unwrap(); 164 | /// 165 | /// // Ensure deletion happens. 166 | /// tmp_dir.close().unwrap(); 167 | /// ``` 168 | pub fn with_prefix_in, P: AsRef>( 169 | prefix: S, 170 | dir: P, 171 | ) -> Result { 172 | let temp = tempfile::TempDir::with_prefix_in(prefix, dir) 173 | .chain(FixtureError::new(FixtureKind::CreateDir))?; 174 | let temp = Inner::Temp(temp); 175 | Ok(Self { temp }) 176 | } 177 | 178 | /// Conditionally persist the temporary directory for debug purposes. 179 | /// 180 | /// Note: this operation is not reversible, i.e. `into_persistent_if(false)` is a no-op. 181 | /// 182 | /// # Examples 183 | /// 184 | /// ```no_run 185 | /// use assert_fs::fixture::TempDir; 186 | /// 187 | /// let tmp_dir = TempDir::new() 188 | /// .unwrap() 189 | /// .into_persistent_if(std::env::var_os("TEST_PERSIST_FILES").is_some()); 190 | /// 191 | /// // Ensure deletion happens. 192 | /// tmp_dir.close().unwrap(); 193 | /// ``` 194 | pub fn into_persistent_if(self, yes: bool) -> Self { 195 | if !yes { 196 | return self; 197 | } 198 | 199 | self.into_persistent() 200 | } 201 | 202 | /// Persist the temporary directory for debug purposes. 203 | /// 204 | /// Note: this operation is not reversible, i.e. `into_persistent_if(false)` is a no-op. 205 | /// 206 | /// # Examples 207 | /// 208 | /// ```no_run 209 | /// use assert_fs::fixture::TempDir; 210 | /// 211 | /// let tmp_dir = TempDir::new() 212 | /// .unwrap() 213 | /// .into_persistent(); 214 | /// 215 | /// // Ensure deletion happens. 216 | /// tmp_dir.close().unwrap(); 217 | /// ``` 218 | pub fn into_persistent(self) -> Self { 219 | let path = match self.temp { 220 | Inner::Temp(temp) => temp.into_path(), 221 | Inner::Persisted(path) => path, 222 | }; 223 | let temp = Inner::Persisted(path); 224 | Self { temp } 225 | } 226 | 227 | /// Accesses the [`Path`] to the temporary directory. 228 | /// 229 | /// [`Path`]: std::path::Path 230 | /// 231 | /// # Examples 232 | /// 233 | /// ``` 234 | /// use assert_fs::fixture::TempDir; 235 | /// 236 | /// let tmp_dir = TempDir::new().unwrap(); 237 | /// 238 | /// println!("{}", tmp_dir.path().display()); 239 | /// 240 | /// tmp_dir.close().unwrap(); 241 | /// ``` 242 | pub fn path(&self) -> &path::Path { 243 | match self.temp { 244 | Inner::Temp(ref temp) => temp.path(), 245 | Inner::Persisted(ref path) => path.as_path(), 246 | } 247 | } 248 | 249 | /// Closes and removes the temporary directory, returning a `Result`. 250 | /// 251 | /// Although `TempDir` removes the directory on drop, in the destructor 252 | /// any errors are ignored. To detect errors cleaning up the temporary 253 | /// directory, call `close` instead. 254 | /// 255 | /// # Errors 256 | /// 257 | /// This function may return a variety of [`std::io::Error`]s that result from deleting 258 | /// the files and directories contained with the temporary directory, 259 | /// as well as from deleting the temporary directory itself. These errors 260 | /// may be platform specific. 261 | /// 262 | /// 263 | /// # Examples 264 | /// 265 | /// ``` 266 | /// use assert_fs::fixture::TempDir; 267 | /// 268 | /// let tmp_dir = TempDir::new().unwrap(); 269 | /// 270 | /// // Ensure deletion happens. 271 | /// tmp_dir.close().unwrap(); 272 | /// ``` 273 | pub fn close(self) -> Result<(), FixtureError> { 274 | match self.temp { 275 | Inner::Temp(temp) => temp 276 | .close() 277 | .chain(FixtureError::new(FixtureKind::Cleanup))?, 278 | Inner::Persisted(_) => (), 279 | } 280 | Ok(()) 281 | } 282 | } 283 | 284 | impl AsRef for TempDir { 285 | fn as_ref(&self) -> &path::Path { 286 | self.path() 287 | } 288 | } 289 | 290 | impl std::ops::Deref for TempDir { 291 | type Target = path::Path; 292 | #[inline] 293 | fn deref(&self) -> &path::Path { 294 | self.path() 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/fixture/errors.rs: -------------------------------------------------------------------------------- 1 | //! Error types for fixtures. 2 | 3 | use std::error::Error; 4 | use std::fmt; 5 | 6 | pub(crate) trait ChainError { 7 | fn chain(self, cause: F) -> Self 8 | where 9 | F: Error + Send + Sync + 'static; 10 | } 11 | 12 | pub(crate) trait ResultChainExt { 13 | fn chain(self, chainable: C) -> Result 14 | where 15 | C: ChainError; 16 | } 17 | 18 | impl ResultChainExt for Result 19 | where 20 | E: Error + Send + Sync + 'static, 21 | { 22 | fn chain(self, chainable: C) -> Result 23 | where 24 | C: ChainError, 25 | { 26 | self.map_err(|e| chainable.chain(e)) 27 | } 28 | } 29 | 30 | /// Fixture initialization cause. 31 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 32 | #[non_exhaustive] 33 | pub enum FixtureKind { 34 | /// Failed when walking the source tree. 35 | Walk, 36 | /// Failed when copying a file. 37 | CopyFile, 38 | /// Failed when writing to a file. 39 | WriteFile, 40 | /// Failed when creating a directory. 41 | CreateDir, 42 | /// Failed to cleanup fixture. 43 | Cleanup, 44 | /// Failed to create symlink 45 | Symlink, 46 | } 47 | 48 | impl fmt::Display for FixtureKind { 49 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | match *self { 51 | FixtureKind::Walk => write!(f, "Failed when walking the source tree,"), 52 | FixtureKind::CopyFile => write!(f, "Failed when copying a file."), 53 | FixtureKind::WriteFile => write!(f, "Failed when writing to a file."), 54 | FixtureKind::CreateDir => write!(f, "Failed when creating a directory."), 55 | FixtureKind::Cleanup => write!(f, "Failed to cleanup fixture."), 56 | FixtureKind::Symlink => write!(f, "Failed when symlinking to the target."), 57 | } 58 | } 59 | } 60 | 61 | /// Failure when initializing the fixture. 62 | #[derive(Debug)] 63 | pub struct FixtureError { 64 | kind: FixtureKind, 65 | cause: Option>, 66 | } 67 | 68 | impl FixtureError { 69 | /// Create a `FixtureError`. 70 | pub fn new(kind: FixtureKind) -> Self { 71 | Self { kind, cause: None } 72 | } 73 | 74 | /// Fixture initialization cause. 75 | pub fn kind(&self) -> FixtureKind { 76 | self.kind 77 | } 78 | } 79 | 80 | impl Error for FixtureError { 81 | fn description(&self) -> &str { 82 | "Failed to initialize fixture" 83 | } 84 | 85 | fn cause(&self) -> Option<&dyn Error> { 86 | self.cause.as_ref().map(|c| { 87 | let c: &dyn Error = c.as_ref(); 88 | c 89 | }) 90 | } 91 | } 92 | 93 | impl fmt::Display for FixtureError { 94 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 95 | match self.cause { 96 | Some(ref cause) => write!( 97 | f, 98 | "Failed to initialize fixture: {}\nCause: {}", 99 | self.kind, cause 100 | ), 101 | None => write!(f, "Failed to initialize fixture: {}", self.kind), 102 | } 103 | } 104 | } 105 | 106 | impl ChainError for FixtureError { 107 | fn chain(mut self, cause: F) -> Self 108 | where 109 | F: Error + Send + Sync + 'static, 110 | { 111 | self.cause = Some(Box::new(cause)); 112 | self 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/fixture/file.rs: -------------------------------------------------------------------------------- 1 | use std::ffi; 2 | use std::path; 3 | 4 | use super::errors::FixtureError; 5 | use super::errors::FixtureKind; 6 | use super::errors::ResultChainExt; 7 | 8 | /// A potential file in the filesystem that is automatically deleted when 9 | /// it goes out of scope. 10 | /// 11 | /// The [`NamedTempFile`] type creates a directory on the file system that 12 | /// is deleted once it goes out of scope. At construction, the 13 | /// `NamedTempFile` creates a new directory with a randomly generated name. 14 | /// 15 | /// The constructor, [`NamedTempFile::new(name)`], creates directories in 16 | /// the location returned by [`std::env::temp_dir()`]. 17 | /// 18 | /// After creating a `NamedTempFile`, work with the file system by doing 19 | /// standard [`std::fs`] file system operations on its [`Path`], 20 | /// which can be retrieved with [`NamedTempFile::path()`]. Once the `NamedTempFile` 21 | /// value is dropped, the parent directory will be deleted, along with the file. It is your 22 | /// responsibility to ensure that no further file system operations are attempted inside the 23 | /// temporary directory once it has been deleted. 24 | /// 25 | /// # Resource Leaking 26 | /// 27 | /// Various platform-specific conditions may cause `NamedTempFile` to fail 28 | /// to delete the underlying directory. It's important to ensure that 29 | /// handles (like [`File`] and [`ReadDir`]) to the file inside the 30 | /// directory is dropped before the `NamedTempFile` goes out of scope. The 31 | /// `NamedTempFile` destructor will silently ignore any errors in deleting 32 | /// the directory; to instead handle errors call [`NamedTempFile::close()`]. 33 | /// 34 | /// Note that if the program exits before the `NamedTempFile` destructor is 35 | /// run, such as via [`std::process::exit()`], by segfaulting, or by 36 | /// receiving a signal like `SIGINT`, then the temporary directory 37 | /// will not be deleted. 38 | /// 39 | /// # Examples 40 | /// 41 | /// Create a temporary file. 42 | /// 43 | /// ``` 44 | /// use assert_fs::fixture::NamedTempFile; 45 | /// 46 | /// let tmp_file = NamedTempFile::new("foo.rs").unwrap(); 47 | /// 48 | /// // Ensure deletion happens. 49 | /// tmp_file.close().unwrap(); 50 | /// ``` 51 | /// 52 | /// [`File`]: std::fs::File 53 | /// [`Path`]: std::path::Path 54 | /// [`ReadDir`]: std::fs::ReadDir 55 | #[derive(Debug)] 56 | pub struct NamedTempFile { 57 | temp: Inner, 58 | path: path::PathBuf, 59 | } 60 | 61 | #[derive(Debug)] 62 | enum Inner { 63 | Temp(tempfile::TempDir), 64 | Persisted, 65 | } 66 | 67 | impl NamedTempFile { 68 | /// Attempts to make a temporary file inside of `env::temp_dir()`. 69 | /// 70 | /// The file and parent directory will be automatically deleted once the returned 71 | /// `NamedTempFile` is destroyed. 72 | /// 73 | /// # Errors 74 | /// 75 | /// If the parent directory can not be created, `Err` is returned. 76 | /// 77 | /// # Examples 78 | /// 79 | /// ``` 80 | /// use assert_fs::fixture::NamedTempFile; 81 | /// 82 | /// let tmp_file = NamedTempFile::new("foo.rs").unwrap(); 83 | /// 84 | /// // Ensure deletion happens. 85 | /// tmp_file.close().unwrap(); 86 | /// ``` 87 | pub fn new(name: S) -> Result 88 | where 89 | S: AsRef, 90 | { 91 | let temp = tempfile::TempDir::new().chain(FixtureError::new(FixtureKind::CreateDir))?; 92 | let path = temp.path().join(name.as_ref()); 93 | let temp = Inner::Temp(temp); 94 | Ok(Self { temp, path }) 95 | } 96 | 97 | /// Conditionally persist the temporary file for debug purposes. 98 | /// 99 | /// Note: this operation is not reversible, i.e. `into_persistent_if(false)` is a no-op. 100 | /// 101 | /// # Examples 102 | /// 103 | /// ```no_run 104 | /// use assert_fs::fixture::NamedTempFile; 105 | /// 106 | /// let tmp_file = NamedTempFile::new("foo.rs") 107 | /// .unwrap() 108 | /// .into_persistent_if(std::env::var_os("TEST_PERSIST_FILES").is_some()); 109 | /// 110 | /// // Ensure deletion happens. 111 | /// tmp_file.close().unwrap(); 112 | /// ``` 113 | pub fn into_persistent_if(self, yes: bool) -> Self { 114 | if !yes { 115 | return self; 116 | } 117 | 118 | self.into_persistent() 119 | } 120 | 121 | /// Persist the temporary file for debug purposes. 122 | /// 123 | /// Note: this operation is not reversible, i.e. `into_persistent_if(false)` is a no-op. 124 | /// 125 | /// # Examples 126 | /// 127 | /// ```no_run 128 | /// use assert_fs::fixture::NamedTempFile; 129 | /// 130 | /// let tmp_file = NamedTempFile::new("foo.rs") 131 | /// .unwrap() 132 | /// .into_persistent(); 133 | /// 134 | /// // Ensure deletion happens. 135 | /// tmp_file.close().unwrap(); 136 | /// ``` 137 | pub fn into_persistent(mut self) -> Self { 138 | let mut temp = Inner::Persisted; 139 | ::std::mem::swap(&mut self.temp, &mut temp); 140 | if let Inner::Temp(temp) = temp { 141 | _ = temp.into_path(); 142 | } 143 | 144 | self 145 | } 146 | 147 | /// Accesses the [`Path`] to the temporary file. 148 | /// 149 | /// [`Path`]: std::path::Path 150 | /// 151 | /// # Examples 152 | /// 153 | /// ``` 154 | /// use assert_fs::fixture::NamedTempFile; 155 | /// 156 | /// let tmp_file = NamedTempFile::new("foo.rs").unwrap(); 157 | /// 158 | /// println!("{}", tmp_file.path().display()); 159 | /// 160 | /// // Ensure deletion happens. 161 | /// tmp_file.close().unwrap(); 162 | /// ``` 163 | pub fn path(&self) -> &path::Path { 164 | &self.path 165 | } 166 | 167 | /// Closes and removes the temporary file and parent directory, returning a `Result`. 168 | /// 169 | /// Although `NamedTempFile` removes the directory on drop, in the destructor 170 | /// any errors are ignored. To detect errors cleaning up the temporary 171 | /// directory, call `close` instead. 172 | /// 173 | /// # Errors 174 | /// 175 | /// This function may return a variety of [`std::io::Error`]s that result from deleting the 176 | /// temporary file and parent directory, These errors may be platform specific. 177 | /// 178 | /// 179 | /// # Examples 180 | /// 181 | /// ``` 182 | /// use assert_fs::fixture::NamedTempFile; 183 | /// 184 | /// let tmp_file = NamedTempFile::new("foo.rs").unwrap(); 185 | /// 186 | /// // Ensure deletion happens. 187 | /// tmp_file.close().unwrap(); 188 | /// ``` 189 | pub fn close(self) -> Result<(), FixtureError> { 190 | match self.temp { 191 | Inner::Temp(temp) => temp 192 | .close() 193 | .chain(FixtureError::new(FixtureKind::Cleanup))?, 194 | Inner::Persisted => (), 195 | } 196 | Ok(()) 197 | } 198 | } 199 | 200 | impl AsRef for NamedTempFile { 201 | fn as_ref(&self) -> &path::Path { 202 | self.path() 203 | } 204 | } 205 | 206 | impl std::ops::Deref for NamedTempFile { 207 | type Target = path::Path; 208 | #[inline] 209 | fn deref(&self) -> &path::Path { 210 | self.path() 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/fixture/mod.rs: -------------------------------------------------------------------------------- 1 | //! Initialize the filesystem to use as test fixtures. 2 | 3 | mod child; 4 | mod dir; 5 | mod errors; 6 | mod file; 7 | mod tools; 8 | 9 | pub use self::child::*; 10 | pub use self::dir::*; 11 | pub use self::errors::*; 12 | pub use self::file::*; 13 | pub use self::tools::*; 14 | -------------------------------------------------------------------------------- /src/fixture/tools.rs: -------------------------------------------------------------------------------- 1 | //! Initialize the filesystem to use as test fixtures. 2 | 3 | use std::fs; 4 | use std::io::Write; 5 | use std::path; 6 | 7 | use super::errors::FixtureError; 8 | use super::errors::FixtureKind; 9 | use super::errors::ResultChainExt; 10 | use super::ChildPath; 11 | use super::NamedTempFile; 12 | use super::TempDir; 13 | 14 | /// Create empty directories at [`ChildPath`]. 15 | /// 16 | pub trait PathCreateDir { 17 | /// Create an empty directory at [`ChildPath`]. 18 | /// 19 | /// # Examples 20 | /// 21 | /// ```rust 22 | /// use assert_fs::prelude::*; 23 | /// 24 | /// let temp = assert_fs::TempDir::new().unwrap(); 25 | /// temp.child("subdir").create_dir_all().unwrap(); 26 | /// temp.close().unwrap(); 27 | /// ``` 28 | /// 29 | fn create_dir_all(&self) -> Result<(), FixtureError>; 30 | } 31 | 32 | impl PathCreateDir for ChildPath { 33 | fn create_dir_all(&self) -> Result<(), FixtureError> { 34 | create_dir_all(self.path()) 35 | } 36 | } 37 | 38 | /// Create empty files at [`ChildPath`]. 39 | /// 40 | pub trait FileTouch { 41 | /// Create an empty file at [`ChildPath`]. 42 | /// 43 | /// # Examples 44 | /// 45 | /// ```rust 46 | /// use assert_fs::prelude::*; 47 | /// 48 | /// let temp = assert_fs::TempDir::new().unwrap(); 49 | /// temp.child("foo.txt").touch().unwrap(); 50 | /// temp.close().unwrap(); 51 | /// ``` 52 | /// 53 | fn touch(&self) -> Result<(), FixtureError>; 54 | } 55 | 56 | impl FileTouch for ChildPath { 57 | fn touch(&self) -> Result<(), FixtureError> { 58 | touch(self.path()) 59 | } 60 | } 61 | 62 | impl FileTouch for NamedTempFile { 63 | fn touch(&self) -> Result<(), FixtureError> { 64 | touch(self.path()) 65 | } 66 | } 67 | 68 | /// Write a binary file at [`ChildPath`]. 69 | /// 70 | pub trait FileWriteBin { 71 | /// Write a binary file at [`ChildPath`]. 72 | /// 73 | /// # Examples 74 | /// 75 | /// ```rust 76 | /// use assert_fs::prelude::*; 77 | /// 78 | /// let temp = assert_fs::TempDir::new().unwrap(); 79 | /// temp 80 | /// .child("foo.txt") 81 | /// .write_binary(b"To be or not to be...") 82 | /// .unwrap(); 83 | /// temp.close().unwrap(); 84 | /// ``` 85 | /// 86 | fn write_binary(&self, data: &[u8]) -> Result<(), FixtureError>; 87 | } 88 | 89 | impl FileWriteBin for ChildPath { 90 | fn write_binary(&self, data: &[u8]) -> Result<(), FixtureError> { 91 | write_binary(self.path(), data) 92 | } 93 | } 94 | 95 | impl FileWriteBin for NamedTempFile { 96 | fn write_binary(&self, data: &[u8]) -> Result<(), FixtureError> { 97 | write_binary(self.path(), data) 98 | } 99 | } 100 | 101 | /// Write a text file at [`ChildPath`]. 102 | /// 103 | pub trait FileWriteStr { 104 | /// Write a text file at [`ChildPath`]. 105 | /// 106 | /// # Examples 107 | /// 108 | /// ```rust 109 | /// use assert_fs::prelude::*; 110 | /// 111 | /// let temp = assert_fs::TempDir::new().unwrap(); 112 | /// temp 113 | /// .child("foo.txt") 114 | /// .write_str("To be or not to be...") 115 | /// .unwrap(); 116 | /// temp.close().unwrap(); 117 | /// ``` 118 | /// 119 | fn write_str(&self, data: &str) -> Result<(), FixtureError>; 120 | } 121 | 122 | impl FileWriteStr for ChildPath { 123 | fn write_str(&self, data: &str) -> Result<(), FixtureError> { 124 | write_str(self.path(), data) 125 | } 126 | } 127 | 128 | impl FileWriteStr for NamedTempFile { 129 | fn write_str(&self, data: &str) -> Result<(), FixtureError> { 130 | write_str(self.path(), data) 131 | } 132 | } 133 | 134 | /// Write (copy) a file to [`ChildPath`]. 135 | /// 136 | pub trait FileWriteFile { 137 | /// Write (copy) a file to [`ChildPath`]. 138 | /// 139 | /// # Examples 140 | /// 141 | /// ```rust 142 | /// use std::path::Path; 143 | /// use assert_fs::prelude::*; 144 | /// 145 | /// let temp = assert_fs::TempDir::new().unwrap(); 146 | /// temp 147 | /// .child("foo.txt") 148 | /// .write_file(Path::new("Cargo.toml")) 149 | /// .unwrap(); 150 | /// temp.close().unwrap(); 151 | /// ``` 152 | /// 153 | fn write_file(&self, data: &path::Path) -> Result<(), FixtureError>; 154 | } 155 | 156 | impl FileWriteFile for ChildPath { 157 | fn write_file(&self, data: &path::Path) -> Result<(), FixtureError> { 158 | write_file(self.path(), data) 159 | } 160 | } 161 | 162 | impl FileWriteFile for NamedTempFile { 163 | fn write_file(&self, data: &path::Path) -> Result<(), FixtureError> { 164 | write_file(self.path(), data) 165 | } 166 | } 167 | 168 | /// Copy files into [`TempDir`]. 169 | /// 170 | pub trait PathCopy { 171 | /// Copy files and directories into the current path from the `source` according to the glob 172 | /// `patterns`. 173 | /// 174 | /// # Examples 175 | /// 176 | /// ```rust 177 | /// use assert_fs::prelude::*; 178 | /// 179 | /// let temp = assert_fs::TempDir::new().unwrap(); 180 | /// temp.copy_from(".", &["*.rs"]).unwrap(); 181 | /// temp.close().unwrap(); 182 | /// ``` 183 | fn copy_from(&self, source: P, patterns: &[S]) -> Result<(), FixtureError> 184 | where 185 | P: AsRef, 186 | S: AsRef; 187 | } 188 | 189 | impl PathCopy for TempDir { 190 | fn copy_from(&self, source: P, patterns: &[S]) -> Result<(), FixtureError> 191 | where 192 | P: AsRef, 193 | S: AsRef, 194 | { 195 | copy_files(self.path(), source.as_ref(), patterns) 196 | } 197 | } 198 | 199 | impl PathCopy for ChildPath { 200 | fn copy_from(&self, source: P, patterns: &[S]) -> Result<(), FixtureError> 201 | where 202 | P: AsRef, 203 | S: AsRef, 204 | { 205 | copy_files(self.path(), source.as_ref(), patterns) 206 | } 207 | } 208 | 209 | /// Create a symlink to the target 210 | /// 211 | pub trait SymlinkToFile { 212 | /// Create a symlink to the target 213 | /// 214 | /// # Examples 215 | /// 216 | /// ```rust 217 | /// use assert_fs::prelude::*; 218 | /// 219 | /// let temp = assert_fs::TempDir::new().unwrap(); 220 | /// let real_file = temp.child("real_file"); 221 | /// real_file.touch().unwrap(); 222 | /// 223 | /// temp.child("link_file").symlink_to_file(real_file.path()).unwrap(); 224 | /// 225 | /// temp.close().unwrap(); 226 | /// ``` 227 | fn symlink_to_file

(&self, target: P) -> Result<(), FixtureError> 228 | where 229 | P: AsRef; 230 | } 231 | 232 | impl SymlinkToFile for ChildPath { 233 | fn symlink_to_file

(&self, target: P) -> Result<(), FixtureError> 234 | where 235 | P: AsRef, 236 | { 237 | symlink_to_file(self.path(), target.as_ref()) 238 | } 239 | } 240 | 241 | impl SymlinkToFile for NamedTempFile { 242 | fn symlink_to_file

(&self, target: P) -> Result<(), FixtureError> 243 | where 244 | P: AsRef, 245 | { 246 | symlink_to_file(self.path(), target.as_ref()) 247 | } 248 | } 249 | 250 | /// Create a symlink to the target 251 | /// 252 | pub trait SymlinkToDir { 253 | /// Create a symlink to the target 254 | /// 255 | /// # Examples 256 | /// 257 | /// ```rust 258 | /// use assert_fs::prelude::*; 259 | /// 260 | /// let temp = assert_fs::TempDir::new().unwrap(); 261 | /// let real_dir = temp.child("real_dir"); 262 | /// real_dir.create_dir_all().unwrap(); 263 | /// 264 | /// temp.child("link_dir").symlink_to_dir(real_dir.path()).unwrap(); 265 | /// 266 | /// temp.close().unwrap(); 267 | /// ``` 268 | fn symlink_to_dir

(&self, target: P) -> Result<(), FixtureError> 269 | where 270 | P: AsRef; 271 | } 272 | 273 | impl SymlinkToDir for ChildPath { 274 | fn symlink_to_dir

(&self, target: P) -> Result<(), FixtureError> 275 | where 276 | P: AsRef, 277 | { 278 | symlink_to_dir(self.path(), target.as_ref()) 279 | } 280 | } 281 | 282 | impl SymlinkToDir for TempDir { 283 | fn symlink_to_dir

(&self, target: P) -> Result<(), FixtureError> 284 | where 285 | P: AsRef, 286 | { 287 | symlink_to_dir(self.path(), target.as_ref()) 288 | } 289 | } 290 | 291 | fn ensure_parent_dir(path: &path::Path) -> Result<(), FixtureError> { 292 | if let Some(parent) = path.parent() { 293 | fs::create_dir_all(parent).chain(FixtureError::new(FixtureKind::CreateDir))?; 294 | } 295 | Ok(()) 296 | } 297 | 298 | fn create_dir_all(path: &path::Path) -> Result<(), FixtureError> { 299 | fs::create_dir_all(path).chain(FixtureError::new(FixtureKind::CreateDir))?; 300 | Ok(()) 301 | } 302 | 303 | fn touch(path: &path::Path) -> Result<(), FixtureError> { 304 | ensure_parent_dir(path)?; 305 | fs::File::create(path).chain(FixtureError::new(FixtureKind::WriteFile))?; 306 | Ok(()) 307 | } 308 | 309 | fn write_binary(path: &path::Path, data: &[u8]) -> Result<(), FixtureError> { 310 | ensure_parent_dir(path)?; 311 | let mut file = fs::File::create(path).chain(FixtureError::new(FixtureKind::WriteFile))?; 312 | file.write_all(data) 313 | .chain(FixtureError::new(FixtureKind::WriteFile))?; 314 | Ok(()) 315 | } 316 | 317 | fn write_str(path: &path::Path, data: &str) -> Result<(), FixtureError> { 318 | ensure_parent_dir(path)?; 319 | write_binary(path, data.as_bytes()).chain(FixtureError::new(FixtureKind::WriteFile)) 320 | } 321 | 322 | fn write_file(path: &path::Path, data: &path::Path) -> Result<(), FixtureError> { 323 | ensure_parent_dir(path)?; 324 | fs::copy(data, path).chain(FixtureError::new(FixtureKind::CopyFile))?; 325 | Ok(()) 326 | } 327 | 328 | fn copy_files( 329 | target: &path::Path, 330 | source: &path::Path, 331 | patterns: &[S], 332 | ) -> Result<(), FixtureError> 333 | where 334 | S: AsRef, 335 | { 336 | // `walkdir`, on Windows, seems to convert "." into "" which then fails. 337 | let source = source 338 | .canonicalize() 339 | .chain(FixtureError::new(FixtureKind::Walk))?; 340 | for entry in globwalk::GlobWalkerBuilder::from_patterns(&source, patterns) 341 | .follow_links(true) 342 | .build() 343 | .chain(FixtureError::new(FixtureKind::Walk))? 344 | { 345 | let entry = entry.chain(FixtureError::new(FixtureKind::Walk))?; 346 | let rel = entry 347 | .path() 348 | .strip_prefix(&source) 349 | .expect("entries to be under `source`"); 350 | let target_path = target.join(rel); 351 | if entry.file_type().is_dir() { 352 | fs::create_dir_all(target_path).chain(FixtureError::new(FixtureKind::CreateDir))?; 353 | } else if entry.file_type().is_file() { 354 | fs::create_dir_all(target_path.parent().expect("at least `target` exists")) 355 | .chain(FixtureError::new(FixtureKind::CreateDir))?; 356 | fs::copy(entry.path(), target_path).chain(FixtureError::new(FixtureKind::CopyFile))?; 357 | } 358 | } 359 | Ok(()) 360 | } 361 | 362 | #[cfg(windows)] 363 | fn symlink_to_file(link: &path::Path, target: &path::Path) -> Result<(), FixtureError> { 364 | std::os::windows::fs::symlink_file(target, link) 365 | .chain(FixtureError::new(FixtureKind::Symlink))?; 366 | Ok(()) 367 | } 368 | 369 | #[cfg(windows)] 370 | fn symlink_to_dir(link: &path::Path, target: &path::Path) -> Result<(), FixtureError> { 371 | std::os::windows::fs::symlink_dir(target, link) 372 | .chain(FixtureError::new(FixtureKind::Symlink))?; 373 | Ok(()) 374 | } 375 | 376 | #[cfg(not(windows))] 377 | fn symlink_to_file(link: &path::Path, target: &path::Path) -> Result<(), FixtureError> { 378 | std::os::unix::fs::symlink(target, link).chain(FixtureError::new(FixtureKind::Symlink))?; 379 | Ok(()) 380 | } 381 | 382 | #[cfg(not(windows))] 383 | fn symlink_to_dir(link: &path::Path, target: &path::Path) -> Result<(), FixtureError> { 384 | std::os::unix::fs::symlink(target, link).chain(FixtureError::new(FixtureKind::Symlink))?; 385 | Ok(()) 386 | } 387 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Filesystem fixtures and assertions for testing. 2 | //! 3 | //! `assert_fs` aims to simplify 4 | //! - Setting up files for your tests to consume 5 | //! - Asserting on files produced by your tests 6 | //! 7 | //! ## Overview 8 | //! 9 | //! Setting up a fixture 10 | //! - [`TempDir`] or [`NamedTempFile`] for a sandbox to test in. 11 | //! - [`touch`][`FileTouch`] a [`ChildPath`] or [`NamedTempFile`] 12 | //! - [`write_binary`][`FileWriteBin`] a [`ChildPath`] or [`NamedTempFile`] 13 | //! - [`write_str`][`FileWriteStr`] a [`ChildPath`] or [`NamedTempFile`] 14 | //! - [`write_file`][`FileWriteFile`] a [`ChildPath`] or [`NamedTempFile`] 15 | //! - [`copy_from`][`PathCopy`] a pristine folder to a [`ChildPath`] or [`TempDir`] 16 | //! - [`symlink_to_file`][`SymlinkToFile`] a [`ChildPath`] or [`NamedTempFile`] 17 | //! - [`symlink_to_dir`][`SymlinkToDir`] a [`ChildPath`] or [`TempDir`] 18 | //! 19 | //! Validating 20 | //! - [`assert`][`PathAssert`] a [`ChildPath`], [`TempDir`], or [`NamedTempFile`] 21 | //! 22 | //! ## Example 23 | //! 24 | //! Here is a trivial example: 25 | //! 26 | //! ```rust 27 | //! use assert_fs::prelude::*; 28 | //! use predicates::prelude::*; 29 | //! 30 | //! let temp = assert_fs::TempDir::new().unwrap(); 31 | //! let input_file = temp.child("foo.txt"); 32 | //! input_file.touch().unwrap(); 33 | //! 34 | //! // ... do something with input_file ... 35 | //! 36 | //! input_file.assert(""); 37 | //! temp.child("bar.txt").assert(predicate::path::missing()); 38 | //! 39 | //! temp.close().unwrap(); 40 | //! ``` 41 | //! 42 | //! [`ChildPath`]: fixture::ChildPath 43 | //! [`FileTouch`]: fixture::FileTouch 44 | //! [`FileWriteBin`]: fixture::FileWriteBin 45 | //! [`FileWriteStr`]: fixture::FileWriteStr 46 | //! [`FileWriteFile`]: fixture::FileWriteFile 47 | //! [`SymlinkToDir`]: fixture::SymlinkToDir 48 | //! [`SymlinkToFile`]: fixture::SymlinkToFile 49 | //! [`PathCopy`]: fixture::PathCopy 50 | //! [`PathAssert`]: assert::PathAssert 51 | //! [dir-diff]: https://crates.io/crates/dir-diff 52 | 53 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 54 | #![warn(clippy::print_stderr)] 55 | #![warn(clippy::print_stdout)] 56 | 57 | pub mod assert; 58 | pub mod fixture; 59 | 60 | // Pulling this in for convenience-sake 61 | #[doc(inline)] 62 | pub use crate::fixture::TempDir; 63 | 64 | // Pulling this in for convenience-sake 65 | #[doc(inline)] 66 | pub use crate::fixture::NamedTempFile; 67 | 68 | /// Extension traits that are useful to have available. 69 | pub mod prelude { 70 | pub use crate::assert::PathAssert; 71 | pub use crate::fixture::FileTouch; 72 | pub use crate::fixture::FileWriteBin; 73 | pub use crate::fixture::FileWriteFile; 74 | pub use crate::fixture::FileWriteStr; 75 | pub use crate::fixture::PathChild; 76 | pub use crate::fixture::PathCopy; 77 | pub use crate::fixture::PathCreateDir; 78 | pub use crate::fixture::SymlinkToDir; 79 | pub use crate::fixture::SymlinkToFile; 80 | } 81 | 82 | mod color; 83 | use color::Palette; 84 | 85 | #[doc = include_str!("../README.md")] 86 | #[cfg(doctest)] 87 | pub struct ReadmeDoctests; 88 | -------------------------------------------------------------------------------- /tests/assert.rs: -------------------------------------------------------------------------------- 1 | use assert_fs::prelude::*; 2 | use predicates::prelude::*; 3 | 4 | #[test] 5 | fn code_example() { 6 | let temp = assert_fs::TempDir::new().unwrap(); 7 | let input_file = temp.child("foo.txt"); 8 | input_file.touch().unwrap(); 9 | 10 | // ... do something with input_file ... 11 | 12 | input_file.assert(""); 13 | temp.child("bar.txt").assert(predicate::path::missing()); 14 | 15 | temp.close().unwrap(); 16 | } 17 | 18 | #[test] 19 | #[should_panic] 20 | fn verify_failure_output() { 21 | let f = assert_fs::fixture::ChildPath::new("Cargo.toml"); 22 | f.assert("Not real content"); 23 | } 24 | -------------------------------------------------------------------------------- /tests/fixture/.gitattributes: -------------------------------------------------------------------------------- 1 | # Ensure consistent test results regardless of platform 2 | * text eol=lf 3 | -------------------------------------------------------------------------------- /tests/fixture/hello.txt: -------------------------------------------------------------------------------- 1 | hello 2 | --------------------------------------------------------------------------------