mod common;
use anyhow::Result;
use assert_cmd::assert::OutputAssertExt;
use assert_fs::fixture::{FileWriteStr, PathChild, PathCreateDir};
use indoc::indoc;
use prek_consts::env_vars::EnvVars;
use prek_consts::{PRE_COMMIT_CONFIG_YAML, PRE_COMMIT_HOOKS_YAML};
use crate::common::{TestContext, cmd_snapshot, git_cmd};
#[test]
fn basic_discovery() -> Result<()> {
let context = TestContext::new();
let cwd = context.work_dir();
context.init_project();
let config = indoc! {r"
repos:
- repo: local
hooks:
- id: show-cwd
name: Show CWD
language: python
entry: python -c 'import sys, os; print(os.getcwd()); print(sys.argv[1:])'
verbose: true
"};
context.setup_workspace(
&[
"project2",
"project3",
"nested/project4",
"project3/project5",
],
config,
)?;
context.git_add(".");
cmd_snapshot!(context.filters(), context.run(), @r#"
success: true
exit_code: 0
----- stdout -----
✓ nested/project4
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/nested/project4
['.pre-commit-config.yaml']
✓ project3/project5
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project5
['.pre-commit-config.yaml']
✓ project2
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
✓ project3
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project5/.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'nested/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml']
[TEMP_DIR]/
['project3/project5/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().current_dir(cwd.join("project2")), @r"
success: true
exit_code: 0
----- stdout -----
Show CWD.................................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
----- stderr -----
");
cmd_snapshot!(context.filters(), context.run().current_dir(cwd.join("project2")).arg("--all-files"), @r"
success: true
exit_code: 0
----- stdout -----
Show CWD.................................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
----- stderr -----
");
cmd_snapshot!(context.filters(), context.run().current_dir(cwd.join("project3")), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project5
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project5
['.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project5/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().arg("--cd").arg(cwd.join("project3")), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project5
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project5
['.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project5/.pre-commit-config.yaml']
----- stderr -----
"#);
context
.work_dir()
.child("project3/.prekignore")
.write_str("project5/\n")?;
context.git_add(".");
cmd_snapshot!(context.filters(), context.run().arg("--refresh").arg("--cd").arg(cwd.join("project3")), @r#"
success: true
exit_code: 0
----- stdout -----
Show CWD.................................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', '.prekignore', 'project5/.pre-commit-config.yaml']
----- stderr -----
"#);
context
.work_dir()
.child("project3/.prekignore")
.write_str("*\n")?;
context.git_add(".");
cmd_snapshot!(context.filters(), context.run().arg("--refresh").arg("--cd").arg(cwd.join("project3")), @r#"
success: true
exit_code: 0
----- stdout -----
Show CWD.................................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', '.prekignore', 'project5/.pre-commit-config.yaml']
----- stderr -----
"#);
Ok(())
}
#[test]
fn same_depth_project_concurrency_has_stable_output() -> Result<()> {
let context = TestContext::new();
context.init_project();
context.write_pre_commit_config("repos: []");
context
.work_dir()
.child("concurrent_hook.py")
.write_str(indoc! {r#"
from pathlib import Path
import time
project = Path.cwd().name
workspace = Path.cwd().parent
ready = workspace / f"{project}.ready"
peer = workspace / f"{'b' if project == 'a' else 'a'}.ready"
ready.touch()
deadline = time.monotonic() + 5
while not peer.exists():
if time.monotonic() >= deadline:
raise TimeoutError(f"timed out waiting for {peer.name}")
time.sleep(0.01)
# Make b finish first so output order cannot follow completion order.
if project == "a":
time.sleep(0.5)
"#})?;
let config = indoc! {r"
repos:
- repo: local
hooks:
- id: concurrent-hook
name: Concurrent Hook
language: system
entry: python3 ../concurrent_hook.py
always_run: true
pass_filenames: false
"};
for project in ["a", "b"] {
let project_dir = context.work_dir().child(project);
project_dir.create_dir_all()?;
project_dir
.child(".pre-commit-config.yaml")
.write_str(config)?;
project_dir.child("file.txt").write_str("")?;
}
context.git_add(".");
let mut run = context.run();
run.arg("--all-files")
.env(EnvVars::PREK_CONCURRENT_HOOKS, "2");
cmd_snapshot!(context.filters(), run, @r#"
success: true
exit_code: 0
----- stdout -----
✓ a
Concurrent Hook........................................................Passed
✓ b
Concurrent Hook........................................................Passed
----- stderr -----
"#);
Ok(())
}
#[test]
fn fail_fast_stops_after_current_project_level() -> Result<()> {
let context = TestContext::new();
context.init_project();
context.write_pre_commit_config(indoc! {r#"
repos:
- repo: local
hooks:
- id: root-hook
name: Root Hook
language: system
entry: python3 -c "print('root ran')"
always_run: true
"#});
let failing_config = indoc! {r#"
repos:
- repo: local
hooks:
- id: failing-hook
name: Failing Hook
language: system
entry: python3 -c "import sys; sys.exit(1)"
always_run: true
fail_fast: true
"#};
let passing_config = indoc! {r#"
repos:
- repo: local
hooks:
- id: passing-hook
name: Passing Hook
language: system
entry: python3 -c "print('sibling ran')"
always_run: true
"#};
let project_a = context.work_dir().child("a");
project_a.create_dir_all()?;
project_a
.child(".pre-commit-config.yaml")
.write_str(failing_config)?;
let project_b = context.work_dir().child("b");
project_b.create_dir_all()?;
project_b
.child(".pre-commit-config.yaml")
.write_str(passing_config)?;
context.git_add(".");
let mut run = context.run();
run.arg("--all-files")
.env(EnvVars::PREK_CONCURRENT_HOOKS, "2");
cmd_snapshot!(context.filters(), run, @r#"
success: false
exit_code: 1
----- stdout -----
× a
Failing Hook...........................................................Failed
- hook id: failing-hook
- exit code: 1
✓ b
Passing Hook...........................................................Passed
----- stderr -----
"#);
Ok(())
}
#[test]
fn config_not_staged() -> Result<()> {
let context = TestContext::new();
let cwd = context.work_dir();
context.init_project();
let config = indoc! {r"
repos:
- repo: local
hooks:
- id: show-cwd
name: Show CWD
language: python
entry: python -c 'import sys, os; print(os.getcwd()); print(sys.argv[1:])'
verbose: true
"};
context.setup_workspace(
&[
"project2",
"project3",
"nested/project4",
"project3/project5",
],
config,
)?;
context.git_add(".");
let config = indoc! {r"
repos:
- repo: local
hooks:
- id: show-cwd-modified
name: Show CWD
language: python
entry: python -c 'import sys, os; print(os.getcwd()); print(sys.argv[1:])'
verbose: true
"};
context.setup_workspace(
&[
"project2",
"project3",
"nested/project4",
"project3/project5",
],
config,
)?;
cmd_snapshot!(context.filters(), context.run(), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: The following configuration files are not staged. Stage them with `git add` and try again:
- `.pre-commit-config.yaml`
- `nested/project4/.pre-commit-config.yaml`
- `project2/.pre-commit-config.yaml`
- `project3/.pre-commit-config.yaml`
- `project3/project5/.pre-commit-config.yaml`
");
cmd_snapshot!(context.filters(), context.run().current_dir(cwd.join("project3")), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: The following configuration files are not staged. Stage them with `git add` and try again:
- `.pre-commit-config.yaml`
- `project5/.pre-commit-config.yaml`
");
cmd_snapshot!(context.filters(), context.run().current_dir(cwd.join("project2")), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Configuration file `.pre-commit-config.yaml` is not staged. Stage it with `git add` and try again
");
Ok(())
}
#[test]
fn run_with_selectors() -> Result<()> {
let context = TestContext::new();
context.init_project();
let config = indoc! {r"
repos:
- repo: local
hooks:
- id: show-cwd
name: Show CWD
language: python
entry: python -c 'import sys, os; print(os.getcwd()); print(sys.argv[1:])'
verbose: true
"};
context.setup_workspace(
&[
"project2",
"project3",
"nested/project4",
"project3/project5",
],
config,
)?;
context.git_add(".");
cmd_snapshot!(context.filters(), context.run().arg("project2/"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project2
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().arg("--skip").arg("project2/"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ nested/project4
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/nested/project4
['.pre-commit-config.yaml']
✓ project3/project5
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project5
['.pre-commit-config.yaml']
✓ project3
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project5/.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'nested/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml']
[TEMP_DIR]/
['project3/project5/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().arg("--skip").arg("nested/").arg("--skip").arg("project3/"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project2
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'nested/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml']
[TEMP_DIR]/
['project3/project5/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().arg("show-cwd"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ nested/project4
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/nested/project4
['.pre-commit-config.yaml']
✓ project3/project5
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project5
['.pre-commit-config.yaml']
✓ project2
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
✓ project3
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project5/.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'nested/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml']
[TEMP_DIR]/
['project3/project5/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().arg("project2:show-cwd"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project2
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().arg(".:show-cwd"), @r#"
success: true
exit_code: 0
----- stdout -----
Show CWD.................................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'nested/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml']
[TEMP_DIR]/
['project3/project5/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().arg("--skip").arg("show-cwd"), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
error: No hooks found after filtering with the given selectors
");
cmd_snapshot!(context.filters(), context.run().arg("--skip").arg("project2:show-cwd").arg("--skip").arg("nested:show-cwd"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ nested/project4
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/nested/project4
['.pre-commit-config.yaml']
✓ project3/project5
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project5
['.pre-commit-config.yaml']
✓ project3
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project5/.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'nested/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml']
[TEMP_DIR]/
['project3/project5/.pre-commit-config.yaml']
----- stderr -----
warning: selector `--skip=nested:show-cwd` did not match any hooks
"#);
cmd_snapshot!(context.filters(), context.run().arg("--skip").arg("non-exist"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ nested/project4
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/nested/project4
['.pre-commit-config.yaml']
✓ project3/project5
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project5
['.pre-commit-config.yaml']
✓ project2
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
✓ project3
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project5/.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'nested/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml']
[TEMP_DIR]/
['project3/project5/.pre-commit-config.yaml']
----- stderr -----
warning: selector `--skip=non-exist` did not match any hooks
"#);
cmd_snapshot!(context.filters(), context.run().arg("--skip").arg("../"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Invalid selector: `../`
caused by: Invalid project path: `../`
caused by: path is outside the workspace root
");
cmd_snapshot!(context.filters(), context.run().current_dir(context.work_dir().join("project2")), @r"
success: true
exit_code: 0
----- stdout -----
Show CWD.................................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
----- stderr -----
");
Ok(())
}
#[test]
fn run_with_mixed_project_and_hook_selectors() -> Result<()> {
let context = TestContext::new();
context.init_project();
context.write_pre_commit_config(indoc! {r"
repos:
- repo: local
hooks:
- id: root-hook
name: root hook
entry: echo root
language: system
pass_filenames: false
"});
let sub = context.work_dir().child("sub");
sub.create_dir_all()?;
sub.child(".pre-commit-config.yaml").write_str(indoc! {r"
repos:
- repo: local
hooks:
- id: sub-hook
name: sub hook
entry: echo sub
language: system
pass_filenames: false
"})?;
sub.child("file.txt").write_str("")?;
let empty = context.work_dir().child("empty");
empty.create_dir_all()?;
empty
.child(".pre-commit-config.yaml")
.write_str("repos: []\n")?;
let unselected = context.work_dir().child("unselected");
unselected.create_dir_all()?;
unselected
.child(".pre-commit-config.yaml")
.write_str("invalid: config\n")?;
context.git_add(".");
cmd_snapshot!(context.filters(), context.run().arg("--all-files").arg("sub/").arg(".:root-hook"), @r"
success: true
exit_code: 0
----- stdout -----
✓ sub
sub hook...............................................................Passed
✓ <workspace>
root hook..............................................................Passed
----- stderr -----
");
cmd_snapshot!(context.filters(), context.run().arg("--all-files").arg("sub/").arg("root-hook"), @r"
success: true
exit_code: 0
----- stdout -----
✓ sub
sub hook...............................................................Passed
✓ <workspace>
root hook..............................................................Passed
----- stderr -----
");
cmd_snapshot!(context.filters(), context.run().arg("--all-files").arg("empty/").arg("root-hook"), @r"
success: true
exit_code: 0
----- stdout -----
root hook................................................................Passed
----- stderr -----
");
Ok(())
}
#[test]
fn skips() -> Result<()> {
let context = TestContext::new();
context.init_project();
let config = indoc! {r"
repos:
- repo: local
hooks:
- id: show-cwd
name: Show CWD
language: python
entry: python -c 'import sys, os; print(os.getcwd()); print(sys.argv[1:])'
verbose: true
"};
context.setup_workspace(&["project2", "project3", "project3/project4"], config)?;
context.git_add(".");
cmd_snapshot!(context.filters(), context.run().arg("--skip").arg("project2/"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project3/project4
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project4
['.pre-commit-config.yaml']
✓ project3
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project4/.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml', 'project3/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().env(EnvVars::PREK_SKIP, "project2/"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project3/project4
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project4
['.pre-commit-config.yaml']
✓ project3
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project4/.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml', 'project3/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().env(EnvVars::SKIP, "project2/"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project3/project4
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project4
['.pre-commit-config.yaml']
✓ project3
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project4/.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml', 'project3/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().arg("--skip").arg("project2/").env(EnvVars::PREK_SKIP, "project3/"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project3/project4
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project4
['.pre-commit-config.yaml']
✓ project3
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project4/.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml', 'project3/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().env(EnvVars::PREK_SKIP, "project2/").env(EnvVars::SKIP, "project3/"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project3/project4
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3/project4
['.pre-commit-config.yaml']
✓ project3
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project3
['.pre-commit-config.yaml', 'project4/.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml', 'project3/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().env("PREK_SKIP", "project2/,project3/,non-exist-hook"), @r"
success: true
exit_code: 0
----- stdout -----
Show CWD.................................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml', 'project3/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml']
----- stderr -----
warning: selector `PREK_SKIP=non-exist-hook` did not match any hooks
");
context
.work_dir()
.child("project3/.pre-commit-config.yaml")
.write_str("invalid_yaml: [")?;
context.git_add(".");
cmd_snapshot!(context.filters(), context.run(), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to parse `project3/.pre-commit-config.yaml`
caused by: error: line 1 column 15: unclosed bracket '['
--> <input>:1:15
|
1 | invalid_yaml: [
| ^ unclosed bracket '['
");
cmd_snapshot!(context.filters(), context.run().arg("--skip").arg("project3/"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project2
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml', 'project3/project4/.pre-commit-config.yaml', 'project3/.pre-commit-config.yaml']
----- stderr -----
"#);
Ok(())
}
#[test]
fn workspace_no_projects() {
let context = TestContext::new();
context.init_project();
context.write_pre_commit_config("repos: []");
context.git_add(".");
cmd_snapshot!(context.filters(), context.run().arg("--skip").arg("."), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No `prek.toml` or `.pre-commit-config.yaml` found in the current directory or parent directories.
hint: If you just added one, rerun your command with the `--refresh` flag to rescan the workspace.
");
}
#[test]
fn gitignore_respected() -> Result<()> {
let context = TestContext::new();
context.init_project();
let config = indoc! {r"
repos:
- repo: local
hooks:
- id: show-cwd
name: Show CWD
language: python
entry: python -c 'import sys, os; print(os.getcwd()); print(sorted(sys.argv[1:]))'
verbose: true
"};
context.setup_workspace(
&[
"src",
"node_modules/ignored", "target/ignored", ],
config,
)?;
context
.work_dir()
.child(".gitignore")
.write_str("node_modules/\ntarget/\n")?;
context.git_add(".");
cmd_snapshot!(context.filters(), context.run(), @r#"
success: true
exit_code: 0
----- stdout -----
✓ src
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/src
['.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.gitignore', '.pre-commit-config.yaml', 'src/.pre-commit-config.yaml']
----- stderr -----
"#);
Ok(())
}
#[test]
fn nested_project_exclude_is_relative() -> Result<()> {
let context = TestContext::new();
context.init_project();
let config = indoc! {r#"
exclude: \.pre-commit-config\.yaml$|^excluded_by_project$
repos:
- repo: local
hooks:
- id: show-files
name: Show Files
language: python
entry: python -c 'import sys; print("Processing {} files".format(len(sys.argv[1:]))); [print(" - {}".format(f)) for f in sys.argv[1:]]'
pass_filenames: true
verbose: true
"#};
context.setup_workspace(&["nested"], config)?;
context
.work_dir()
.child("excluded_by_project")
.write_str("")?;
context.work_dir().child("nested/include").write_str("")?;
context
.work_dir()
.child("nested/excluded_by_project")
.write_str("")?;
context.git_add(".");
cmd_snapshot!(context.filters(), context.run().arg("--all-files"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ nested
Show Files.............................................................Passed
- hook id: show-files
- duration: [TIME]
Processing 1 files
- include
✓ <workspace>
Show Files.............................................................Passed
- hook id: show-files
- duration: [TIME]
Processing 2 files
- nested/excluded_by_project
- nested/include
----- stderr -----
"#);
Ok(())
}
#[test]
fn reference_files_across_projects() -> Result<()> {
let context = TestContext::new();
context.init_project();
let config = indoc! {r"
repos:
- repo: local
hooks:
- id: echo
name: echo
language: system
entry: echo
verbose: true
"};
context.setup_workspace(&["frontend", "backend"], config)?;
let cwd = context.work_dir();
cwd.child("backend/app.py")
.write_str("print('Hello from backend')")?;
context.git_add(".");
cmd_snapshot!(context.filters(), context.run().current_dir(cwd.child("frontend")).arg("--files").arg("../backend/app.py").arg("../backend/non-exist.py"), @r"
success: true
exit_code: 0
----- stdout -----
echo.................................................(no files to check)Skipped
----- stderr -----
warning: This file does not exist and will be ignored: `../backend/non-exist.py`
");
Ok(())
}
#[test]
fn submodule_discovery() -> Result<()> {
let context = TestContext::new();
let cwd = context.work_dir();
context.init_project();
let config = indoc! {r"
repos:
- repo: local
hooks:
- id: show-cwd
name: Show CWD
language: python
entry: python -c 'import sys, os; print(os.getcwd()); print(sys.argv[1:])'
verbose: true
"};
context.setup_workspace(&["project2"], config)?;
let submodule_path = cwd.child("submodule");
let submodule_context = TestContext::new_at(submodule_path.to_path_buf());
submodule_context.init_project();
submodule_context.write_pre_commit_config(config);
submodule_context.git_add(".");
submodule_context.git_commit("Initial commit");
git_cmd(cwd)
.args(["submodule", "add", "./submodule"])
.assert()
.success();
context.git_add(".");
cmd_snapshot!(context.filters(), context.run().arg("--all-files"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project2
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.gitmodules', '.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml']
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().current_dir(&submodule_path).arg("--all-files"), @r"
success: true
exit_code: 0
----- stdout -----
Show CWD.................................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/submodule
['.pre-commit-config.yaml']
----- stderr -----
");
fs_err::remove_file(submodule_path.join(".pre-commit-config.yaml"))?;
submodule_context.git_add(".");
submodule_context.git_commit("Remove config");
cmd_snapshot!(context.filters(), context.run().current_dir(&submodule_path), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No `prek.toml` or `.pre-commit-config.yaml` found in the current directory or parent directories.
hint: If you just added one, rerun your command with the `--refresh` flag to rescan the workspace.
");
Ok(())
}
#[test]
fn cookiecutter_template_directories_are_skipped() -> Result<()> {
let context = TestContext::new();
context.init_project();
let config = indoc! {r"
repos:
- repo: local
hooks:
- id: show-cwd
name: Show CWD
language: python
entry: python -c 'import sys, os; print(os.getcwd()); print(sys.argv[1:])'
verbose: true
"};
context.setup_workspace(&["project2", "{{cookiecutter.project_slug}}"], config)?;
context.git_add(".pre-commit-config.yaml");
context.git_add("project2/.pre-commit-config.yaml");
cmd_snapshot!(context.filters(), context.run().arg("--refresh").arg("--all-files"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ project2
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/project2
['.pre-commit-config.yaml']
✓ <workspace>
Show CWD...............................................................Passed
- hook id: show-cwd
- duration: [TIME]
[TEMP_DIR]/
['.pre-commit-config.yaml', 'project2/.pre-commit-config.yaml']
----- stderr -----
"#);
Ok(())
}
#[test]
fn orphan_projects() -> Result<()> {
let context = TestContext::new();
context.init_project();
let config = indoc! {r#"
exclude: \.pre-commit-config\.yaml$
repos:
- repo: local
hooks:
- id: show-files
name: Show Files
language: python
entry: python -c 'import sys; print("Processing {} files".format(len(sys.argv[1:]))); [print(" - {}".format(f)) for f in sys.argv[1:]]'
pass_filenames: true
verbose: true
"#};
context
.work_dir()
.child("src/backend/.pre-commit-config.yaml")
.write_str(config)?;
context
.work_dir()
.child("src/.pre-commit-config.yaml")
.write_str(config)?;
context
.work_dir()
.child(".pre-commit-config.yaml")
.write_str(config)?;
context
.work_dir()
.child("src/backend/test.py")
.write_str("")?;
context.work_dir().child("src/test.py").write_str("")?;
context.work_dir().child("test.py").write_str("")?;
context.git_add(".");
cmd_snapshot!(context.filters(), context.run().arg("--all-files"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ src/backend
Show Files.............................................................Passed
- hook id: show-files
- duration: [TIME]
Processing 1 files
- test.py
✓ src
Show Files.............................................................Passed
- hook id: show-files
- duration: [TIME]
Processing 2 files
- backend/test.py
- test.py
✓ <workspace>
Show Files.............................................................Passed
- hook id: show-files
- duration: [TIME]
Processing 3 files
- src/backend/test.py
- src/test.py
- test.py
----- stderr -----
"#);
context
.work_dir()
.child("src/backend/.pre-commit-config.yaml")
.write_str(indoc! {r#"
orphan: true
exclude: \.pre-commit-config\.yaml$
repos:
- repo: local
hooks:
- id: show-files
name: Show Files
language: python
entry: python -c 'import sys; print("Processing {} files".format(len(sys.argv[1:]))); [print(" - {}".format(f)) for f in sys.argv[1:]]'
pass_filenames: true
verbose: true
"#})?;
context
.work_dir()
.child("src/.pre-commit-config.yaml")
.write_str(indoc! {r#"
orphan: true
files: ^$
exclude: \.pre-commit-config\.yaml$
repos:
- repo: local
hooks:
- id: show-files
name: Show Files
language: python
entry: python -c 'import sys; print("Processing {} files".format(len(sys.argv[1:]))); [print(" - {}".format(f)) for f in sys.argv[1:]]'
pass_filenames: true
verbose: true
"#})?;
context
.work_dir()
.child(".pre-commit-config.yaml")
.write_str(indoc! {r#"
orphan: false
exclude: \.pre-commit-config\.yaml$
repos:
- repo: local
hooks:
- id: show-files
name: Show Files
language: python
entry: python -c 'import sys; print("Processing {} files".format(len(sys.argv[1:]))); [print(" - {}".format(f)) for f in sys.argv[1:]]'
pass_filenames: true
verbose: true
"#})?;
cmd_snapshot!(context.filters(), context.run().arg("--all-files"), @r#"
success: true
exit_code: 0
----- stdout -----
✓ src/backend
Show Files.............................................................Passed
- hook id: show-files
- duration: [TIME]
Processing 1 files
- test.py
✓ src
Show Files.........................................(no files to check)Skipped
✓ <workspace>
Show Files.............................................................Passed
- hook id: show-files
- duration: [TIME]
Processing 1 files
- test.py
----- stderr -----
"#);
cmd_snapshot!(context.filters(), context.run().arg("--all-files").arg("--skip").arg("src/"), @r"
success: true
exit_code: 0
----- stdout -----
Show Files...............................................................Passed
- hook id: show-files
- duration: [TIME]
Processing 1 files
- test.py
----- stderr -----
");
Ok(())
}
fn setup_relative_repo_path_project() -> Result<TestContext> {
let context = TestContext::new();
context.init_project();
let hook_repo = context.work_dir().child("hook-repo");
hook_repo.create_dir_all()?;
git_cmd(&hook_repo).args(["init"]).assert().success();
hook_repo.child(PRE_COMMIT_HOOKS_YAML).write_str(indoc! {r"
- id: test-hook
name: Test Hook
entry: echo test
language: system
always_run: true
"})?;
git_cmd(&hook_repo).args(["add", "."]).assert().success();
git_cmd(&hook_repo)
.args(["commit", "-m", "Initial commit"])
.assert()
.success();
let output = git_cmd(&hook_repo).args(["rev-parse", "HEAD"]).output()?;
let commit_sha = String::from_utf8_lossy(&output.stdout).trim().to_string();
let subproject = context.work_dir().child("subproject");
subproject.create_dir_all()?;
subproject
.child(PRE_COMMIT_CONFIG_YAML)
.write_str(&indoc::formatdoc! {r"
repos:
- repo: ../hook-repo
rev: {commit_sha}
hooks:
- id: test-hook
always_run: true
"})?;
subproject.child("test.txt").write_str("test content")?;
context.write_pre_commit_config(indoc! {r"
repos:
- repo: local
hooks:
- id: noop
name: Noop
entry: echo noop
language: system
always_run: true
"});
context.git_add(".");
Ok(context)
}
#[test]
fn relative_repo_path_resolution() -> Result<()> {
let context = setup_relative_repo_path_project()?;
cmd_snapshot!(context.filters(), context.run(), @r#"
success: true
exit_code: 0
----- stdout -----
✓ subproject
Test Hook..............................................................Passed
✓ <workspace>
Noop...................................................................Passed
----- stderr -----
"#);
Ok(())
}
#[test]
fn relative_repo_path_resolution_with_explicit_relative_config() -> Result<()> {
let context = setup_relative_repo_path_project()?;
cmd_snapshot!(context.filters(), context.run()
.arg("--config")
.arg("subproject/.pre-commit-config.yaml"), @r#"
success: true
exit_code: 0
----- stdout -----
Test Hook................................................................Passed
----- stderr -----
"#);
Ok(())
}