wip: findr.

This commit is contained in:
2026-06-17 08:54:03 -04:00
parent 55ed98659b
commit dd522de02c
7 changed files with 780 additions and 219 deletions

View File

@@ -3,6 +3,10 @@ package findr
import "core:os"
import "core:testing"
// ============================================================================
// .Ignored mode tests (original findr behavior — emit ONLY gitignored files)
// ============================================================================
@(test)
test_basic_gitignored :: proc(t: ^testing.T) {
env := create_test_env()
@@ -14,7 +18,9 @@ test_basic_gitignored :: proc(t: ^testing.T) {
create_file(env, "repo/secrets.env")
create_file(env, "repo/normal.txt")
assert_output(t, env, nil, {"repo/.env", "repo/secrets.env"})
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .Ignored}, {
"repo/.env", "repo/secrets.env",
})
}
@(test)
@@ -26,7 +32,7 @@ test_non_repo_not_scanned :: proc(t: ^testing.T) {
create_file(env, "norepo/.gitignore", "*.env\n")
create_file(env, "norepo/.env")
assert_output_empty(t, env, nil)
assert_output_empty(t, env, nil, {include_hidden = true, ignore_mode = .Ignored})
}
@(test)
@@ -40,7 +46,9 @@ test_negation_pattern :: proc(t: ^testing.T) {
create_file(env, "repo/secrets.env")
create_file(env, "repo/prod.env")
assert_output(t, env, nil, {"repo/.env", "repo/secrets.env"})
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .Ignored}, {
"repo/.env", "repo/secrets.env",
})
}
@(test)
@@ -55,8 +63,7 @@ test_dir_only_pattern :: proc(t: ^testing.T) {
create_dir(env, "repo/ignored_dir")
create_file(env, "repo/.gitignore", "ignored_dir/\n")
// dir-only patterns don't produce file results
assert_output(t, env, nil, {})
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .Ignored}, {})
}
@(test)
@@ -72,7 +79,9 @@ test_multiple_repos :: proc(t: ^testing.T) {
create_file(env, "repo2/.gitignore", "*.key\n")
create_file(env, "repo2/secret.key")
assert_output(t, env, nil, {"repo1/a.env", "repo2/secret.key"})
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .Ignored}, {
"repo1/a.env", "repo2/secret.key",
})
}
@(test)
@@ -88,11 +97,13 @@ test_nested_repos :: proc(t: ^testing.T) {
create_file(env, "parent/child/.gitignore", "*.key\n")
create_file(env, "parent/child/api.key")
assert_output(t, env, nil, {"parent/top.env", "parent/child/api.key"})
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .Ignored}, {
"parent/top.env", "parent/child/api.key",
})
}
@(test)
test_gitignore_in_subdir_ignored :: proc(t: ^testing.T) {
test_nested_gitignore_read :: proc(t: ^testing.T) {
env := create_test_env()
defer destroy_test_env(&env)
@@ -103,10 +114,73 @@ test_gitignore_in_subdir_ignored :: proc(t: ^testing.T) {
create_file(env, "repo/sub/secret.txt")
create_file(env, "repo/sub/.env")
// .gitignore in subdir is not read (flat model).
// secret.txt should NOT appear (subdir .gitignore ignored).
// .env should NOT appear (it's nested, not top-level of repo).
assert_output(t, env, nil, {})
// Both root and nested .gitignore are read.
// secret.txt: ignored by sub/.gitignore (*.txt)
// .env: ignored by root .gitignore (*.env)
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .Ignored}, {
"repo/sub/secret.txt", "repo/sub/.env",
})
}
@(test)
test_nested_gitignore_negation :: proc(t: ^testing.T) {
env := create_test_env()
defer destroy_test_env(&env)
create_git_repo(env, "repo")
create_file(env, "repo/.gitignore", "*.log\n")
create_dir(env, "repo/sub")
create_file(env, "repo/sub/.gitignore", "!important.log\n")
create_file(env, "repo/sub/important.log")
create_file(env, "repo/sub/debug.log")
// Nested negation overrides root pattern.
// important.log: un-ignored by sub/.gitignore → NOT emitted in .Ignored mode
// debug.log: still ignored by root → emitted
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .Ignored}, {
"repo/sub/debug.log",
})
}
@(test)
test_nested_gitignore_respected_mode :: proc(t: ^testing.T) {
env := create_test_env()
defer destroy_test_env(&env)
create_git_repo(env, "repo")
create_file(env, "repo/.gitignore", "*.log\n")
create_dir(env, "repo/sub")
create_file(env, "repo/sub/.gitignore", "!important.log\n")
create_file(env, "repo/sub/important.log")
create_file(env, "repo/sub/debug.log")
// In .Respected mode:
// important.log: un-ignored by nested negation → emitted
// debug.log: ignored by root → skipped
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .Respected}, {
"repo/.gitignore", "repo/sub/.gitignore", "repo/sub/important.log",
})
}
@(test)
test_multisegment_pattern :: proc(t: ^testing.T) {
env := create_test_env()
defer destroy_test_env(&env)
create_git_repo(env, "repo")
create_file(env, "repo/.gitignore", "build/output.txt\n")
create_dir(env, "repo/build")
create_file(env, "repo/build/output.txt")
create_file(env, "repo/build/other.txt")
create_file(env, "repo/output.txt")
// Multi-segment pattern matches relative path, not just basename.
// build/output.txt: matches → ignored
// build/other.txt: doesn't match → not ignored
// output.txt: doesn't match (needs build/ prefix) → not ignored
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .Ignored}, {
"repo/build/output.txt",
})
}
@(test)
@@ -117,7 +191,7 @@ test_no_gitignore_file :: proc(t: ^testing.T) {
create_git_repo(env, "repo")
create_file(env, "repo/.env")
assert_output_empty(t, env, nil)
assert_output_empty(t, env, nil, {include_hidden = true, ignore_mode = .Ignored})
}
@(test)
@@ -129,7 +203,7 @@ test_empty_gitignore :: proc(t: ^testing.T) {
create_file(env, "repo/.gitignore", "\n\n# comment\n\n")
create_file(env, "repo/.env")
assert_output_empty(t, env, nil)
assert_output_empty(t, env, nil, {include_hidden = true, ignore_mode = .Ignored})
}
@(test)
@@ -156,9 +230,135 @@ test_multiple_search_dirs :: proc(t: ^testing.T) {
delete(results)
}
opts := WalkOptions{include_hidden = true, ignore_mode = .Ignored}
thread_count := os.get_processor_core_count()
walk(dir1, &results, thread_count)
walk(dir2, &results, thread_count)
walk(dir1, &results, opts, thread_count)
walk(dir2, &results, opts, thread_count)
testing.expect_value(t, len(results), 2)
}
// ============================================================================
// .All mode tests (fd -HI parity — ignore gitignore entirely)
// ============================================================================
@(test)
test_all_mode_emits_all_files :: proc(t: ^testing.T) {
env := create_test_env()
defer destroy_test_env(&env)
create_git_repo(env, "repo")
create_file(env, "repo/.gitignore", "*.env\n")
create_file(env, "repo/.env")
create_file(env, "repo/secrets.env")
create_file(env, "repo/normal.txt")
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .All}, {
"repo/.env", "repo/.gitignore", "repo/secrets.env", "repo/normal.txt",
})
}
@(test)
test_all_mode_descends_everywhere :: proc(t: ^testing.T) {
env := create_test_env()
defer destroy_test_env(&env)
create_git_repo(env, "repo")
create_file(env, "repo/.gitignore", "build/\n")
create_dir(env, "repo/build")
create_file(env, "repo/build/output.txt")
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .All}, {
"repo/.gitignore", "repo/build/output.txt",
})
}
// ============================================================================
// .Respected mode tests (fd -H parity — skip gitignored, prune ignored dirs)
// ============================================================================
@(test)
test_respected_mode_skips_gitignored :: proc(t: ^testing.T) {
env := create_test_env()
defer destroy_test_env(&env)
create_git_repo(env, "repo")
create_file(env, "repo/.gitignore", "*.env\n")
create_file(env, "repo/.env")
create_file(env, "repo/secrets.env")
create_file(env, "repo/normal.txt")
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .Respected}, {
"repo/.gitignore", "repo/normal.txt",
})
}
@(test)
test_respected_mode_prunes_ignored_dirs :: proc(t: ^testing.T) {
env := create_test_env()
defer destroy_test_env(&env)
create_git_repo(env, "repo")
create_file(env, "repo/.gitignore", "build/\n")
create_dir(env, "repo/build")
create_file(env, "repo/build/output.txt")
create_file(env, "repo/main.txt")
assert_output(t, env, nil, {include_hidden = true, ignore_mode = .Respected}, {
"repo/.gitignore", "repo/main.txt",
})
}
// ============================================================================
// Filter tests (excludes, pattern, hidden)
// ============================================================================
@(test)
test_excludes_prune_dirs :: proc(t: ^testing.T) {
env := create_test_env()
defer destroy_test_env(&env)
create_git_repo(env, "repo")
create_file(env, "repo/.gitignore", "*.env\n")
create_file(env, "repo/.env")
create_dir(env, "repo/vendor")
create_file(env, "repo/vendor/lib.env")
assert_output(t, env, nil,
{include_hidden = true, ignore_mode = .Ignored, excludes = {"vendor"}},
{"repo/.env"},
)
}
@(test)
test_pattern_filters_results :: proc(t: ^testing.T) {
env := create_test_env()
defer destroy_test_env(&env)
create_git_repo(env, "repo")
create_file(env, "repo/.gitignore", "*.env\n*.key\n")
create_file(env, "repo/.env")
create_file(env, "repo/secrets.env")
create_file(env, "repo/master.key")
assert_output(t, env, nil,
{pattern = "\\.env$", include_hidden = true, ignore_mode = .Ignored},
{"repo/.env", "repo/secrets.env"},
)
}
@(test)
test_no_hidden_skips_dotfiles :: proc(t: ^testing.T) {
env := create_test_env()
defer destroy_test_env(&env)
create_git_repo(env, "repo")
create_file(env, "repo/.gitignore", "*.env\n")
create_file(env, "repo/.env")
create_file(env, "repo/secrets.env")
create_file(env, "repo/.hidden.env")
assert_output(t, env, nil,
{include_hidden = false, ignore_mode = .Ignored},
{"repo/secrets.env"},
)
}