use std::fs;
use std::fs::DirEntry;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use std::thread::JoinHandle;
use std::time::Duration;
use crossbeam_deque::{Injector, Steal, Stealer, Worker};
use crate::{EngineConfig, FilesPresent, GitignoreStack, ObeyedIgnoreFiles, ParsableFile, ScanProgress,
SharedModuleLookups, TraversedDir, UnreadableDirDetails};
use crate::engine::identity::ModuleLookups;
use crate::engine::modules::{ModuleId, Modules};
pub(crate) fn start_producer_thread(id: usize, files_injector: Arc<Injector<ParsableFile>>, dirs_injector: Arc<Injector<TraversedDir>>, worker: Worker<TraversedDir>,
stealers: Arc<Vec<Stealer<TraversedDir>>>, idle_producers: Arc<AtomicUsize>, language_lookups: SharedModuleLookups,
exclude_matcher: Arc<globset::GlobSet>,
config: Arc<EngineConfig>, files_stats: Arc<Mutex<FilesPresent>>, modules: Arc<Modules>,
unreadable_dirs: Arc<Mutex<Vec<UnreadableDirDetails>>>, producers_total: Arc<AtomicUsize>,
worker_panics: Arc<Mutex<Vec<String>>>, progress: Arc<ScanProgress>)
-> std::io::Result<JoinHandle<()>>
{
thread::Builder::new().name(format!("producer-{id}")).spawn(move || {
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(||
search_for_files(id, files_injector, dirs_injector, worker, &stealers, idle_producers.clone(),
language_lookups, exclude_matcher, config, modules, &producers_total, &progress)));
match outcome {
Ok((found, unreadable)) => {
if !unreadable.is_empty() {
unreadable_dirs.lock().unwrap().extend(unreadable);
}
let mut file_stats_guard = files_stats.lock().unwrap();
file_stats_guard.total_files += found.total_files;
file_stats_guard.relevant_files += found.relevant_files;
file_stats_guard.excluded_files += found.excluded_files;
},
Err(payload) => {
worker_panics.lock().unwrap().push(crate::panic_message(payload.as_ref()));
idle_producers.fetch_add(1, Ordering::SeqCst);
}
}
})
}
fn search_for_files(id: usize, files_injector: Arc<Injector<ParsableFile>>, dirs_injector: Arc<Injector<TraversedDir>>, worker: Worker<TraversedDir>,
stealers: &[Stealer<TraversedDir>], idle_producers: Arc<AtomicUsize>,
language_lookups: SharedModuleLookups, exclude_matcher: Arc<globset::GlobSet>, config: Arc<EngineConfig>, modules: Arc<Modules>,
producers_total: &AtomicUsize, progress: &ScanProgress)
-> (FilesPresent, Vec<UnreadableDirDetails>)
{
let mut files_present = FilesPresent::default();
let mut unreadable_dirs = Vec::new();
let mut should_terminate = false;
loop {
let next_dir = worker.pop().or_else(|| steal_a_dir(id, &dirs_injector, stealers, &worker));
if let Some(dir) = &next_dir {
if should_terminate {
should_terminate = false;
idle_producers.fetch_sub(1, Ordering::SeqCst);
}
#[cfg(test)]
if dir.path.to_string_lossy().contains("mezura-dead-producer") {
panic!("test-induced producer panic");
}
match fs::read_dir(&dir.path) {
Ok(entries) => {
let entries = entries.flatten().collect::<Vec<_>>();
let present = find_ignore_files_among(&entries, ObeyedIgnoreFiles::of(&config));
let gitignore_stack = GitignoreStack::extend_with_ignore_files(&dir.path,
dir.gitignore_stack.clone(), &present);
traverse_dir(&files_injector, entries, &worker, &language_lookups, &exclude_matcher, &gitignore_stack,
&config, &modules, dir.module, &dir.path, &mut files_present, progress)
},
Err(error) => unreadable_dirs.push(UnreadableDirDetails {
path: crate::engine::targets::normalise_separators(&dir.path.to_string_lossy()).into_owned(),
error_msg: error.to_string()
})
}
} else {
if !should_terminate {
should_terminate = true;
idle_producers.fetch_add(1, Ordering::SeqCst);
}
if idle_producers.load(Ordering::SeqCst) == producers_total.load(Ordering::SeqCst) {
break;
}
thread::sleep(Duration::from_micros(50));
}
}
(files_present, unreadable_dirs)
}
fn steal_a_dir(id: usize, dirs_injector: &Injector<TraversedDir>, stealers: &[Stealer<TraversedDir>],
worker: &Worker<TraversedDir>)
-> Option<TraversedDir>
{
loop {
match dirs_injector.steal_batch_and_pop(worker) {
Steal::Success(dir) => return Some(dir),
Steal::Retry => thread::yield_now(),
Steal::Empty => break
}
}
for (at, stealer) in stealers.iter().enumerate() {
if at == id { continue; }
if let Steal::Success(dir) = stealer.steal_batch_and_pop(worker) {
return Some(dir);
}
}
None
}
fn find_ignore_files_among(entries: &[DirEntry], obeyed: ObeyedIgnoreFiles) -> Vec<&'static str> {
let mut present = Vec::new();
for entry in entries {
let file_name = entry.file_name();
let Some(name) = obeyed.get_file_names().find(|name| match cfg!(any(windows, target_os = "macos")) {
true => file_name.as_encoded_bytes().eq_ignore_ascii_case(name.as_bytes()),
false => file_name == **name
}) else { continue };
let is_a_file = entry.file_type().is_ok_and(|file_type| match file_type.is_symlink() {
true => entry.path().is_file(),
false => file_type.is_file()
});
if is_a_file {
present.push(name);
}
}
obeyed.get_file_names().filter(|name| present.contains(name)).collect()
}
fn traverse_dir(files_injector: &Injector<ParsableFile>, entries: Vec<DirEntry>, dirs_worker: &Worker<TraversedDir>,
language_lookups: &ModuleLookups, exclude_matcher: &globset::GlobSet, gitignore_stack: &Option<Arc<GitignoreStack>>,
config: &EngineConfig, modules: &Modules, module: ModuleId, dir_path: &Path,
files_present: &mut FilesPresent, progress: &ScanProgress)
{
let mut local_total_files = 0;
let mut local_relevant_files = 0;
let mut local_excluded_files = 0;
let (dir_boundaries, file_boundaries) = (modules.has_dir_boundaries(), modules.has_file_boundaries());
for e in entries {
if let Ok(ft) = e.file_type() {
if ft.is_symlink() {
continue;
}
if ft.is_file() {
local_total_files += 1;
let file_name = e.file_name();
let name = Path::new(&file_name);
let of_module = file_boundaries.then(|| dir_path.join(&file_name));
let module = match &of_module {
Some(path) => modules.at_file(path, module),
None => module
};
let language_lookup = language_lookups.get_of_module(module);
let claimed = language_lookup.of_name(name);
if claimed.is_none() && !language_lookup.needs_a_shebang_probe(name) {
continue;
}
let path_buf = of_module.unwrap_or_else(|| dir_path.join(&file_name));
if (!exclude_matcher.is_empty() && exclude_matcher.is_match(&path_buf))
|| gitignore_stack.as_ref().is_some_and(|stack| stack.is_ignored(&path_buf, false)) {
if claimed.is_some() {
local_excluded_files += 1;
}
continue;
}
let Some(lang_name) = claimed.or_else(|| language_lookup.of_shebang(&path_buf)) else {
continue;
};
local_relevant_files += 1;
let size = e.metadata().map_or(0, |m| m.len());
files_injector.push(ParsableFile::new(path_buf, lang_name, module, size)
.with_extension_rules(language_lookup.find_extension_rules(name)));
progress.record_file_found();
} else {
let file_name = e.file_name();
let dir_name = file_name.to_string_lossy();
if dir_name == ".git" { continue; }
if !config.should_search_in_dotted && dir_name.starts_with('.') { continue; }
let pathbuf = dir_path.join(&file_name);
if !exclude_matcher.is_empty() && exclude_matcher.is_match(&pathbuf) {
continue;
}
if let Some(stack) = gitignore_stack && stack.is_ignored(&pathbuf, true) {
continue;
}
let module = if dir_boundaries {modules.at_dir(&pathbuf, module)} else {module};
dirs_worker.push(TraversedDir::new(pathbuf, gitignore_stack.clone(), module));
}
}
}
files_present.total_files += local_total_files;
files_present.relevant_files += local_relevant_files;
files_present.excluded_files += local_excluded_files;
}
#[cfg(test)]
mod tests {
use std::path::{Path, PathBuf};
use super::*;
use crate::engine::config::Target;
use crate::engine::targets::build_exclude_matcher;
use crate::queue_the_targets;
use crate::test_paths::LANGUAGES_DIR;
use crate::engine::identity::{ClaimKind, LanguageLookup, ModuleLookups, build_extension_language_map,
build_language_map_by};
fn count_files_of(target: &str, extra_args: &str) -> (usize, usize, usize, Vec<String>) {
let (total, relevant, excluded, found) = walk(target, extra_args);
(total, relevant, excluded, found.into_iter().map(|(name, _)| name).collect())
}
fn walk(target: &str, extra_args: &str) -> (usize, usize, usize, Vec<(String, Option<String>)>) {
let declares_a_module = target.contains('=');
let pieces = if declares_a_module {target.split_whitespace().collect::<Vec<_>>()}
else {target.split(',').collect::<Vec<_>>()};
let declared = pieces.into_iter().map(str::trim).filter(|x| !x.is_empty())
.map(|piece| match piece.split_once('=').filter(|_| declares_a_module) {
Some((name, path)) => Target::named(name.trim(), path.trim()),
None => Target::of(piece)
}).collect::<Vec<_>>();
let config = EngineConfig {
targets: declared,
threads: crate::Threads::new(1, 1),
no_gitignore: extra_args.contains("--no-gitignore"),
no_ignore_files: extra_args.contains("--no-ignore-files"),
should_search_in_dotted: extra_args.contains("--search-in-dotted"),
..Default::default()
};
let targets = crate::engine::targets::resolve(&config.targets, crate::ObeyedIgnoreFiles::of(&config),
config.should_search_in_dotted).unwrap();
let config = Arc::new(config);
let language_map = Arc::new(crate::languages::keyed_by_name(
crate::language_file::parse_languages_in_dir(LANGUAGES_DIR).unwrap().0));
let files_injector = Arc::new(Injector::new());
let dirs_injector = Arc::new(Injector::new());
let idle_producers = Arc::new(AtomicUsize::new(0));
let language_lookups: SharedModuleLookups = Arc::new(ModuleLookups::OfTheWholeRun(LanguageLookup {
by_extension: build_extension_language_map(&language_map, &Default::default(), &Default::default()).0,
by_shebang: build_language_map_by(ClaimKind::Shebang, &language_map, &Default::default(), &Default::default()).0,
..Default::default() }));
let modules = Arc::new(Modules::of(&targets));
let mut files_present = FilesPresent::default();
queue_the_targets(&config, &targets, &dirs_injector, &files_injector, &mut files_present, &language_lookups, &modules,
&ScanProgress::default());
let exclude_matcher = Arc::new(build_exclude_matcher(&config.exclude_dirs).unwrap());
let (found, _) = search_for_files(0, files_injector.clone(), dirs_injector,
Worker::new_lifo(), &[], idle_producers, language_lookups, exclude_matcher, config, modules.clone(),
&AtomicUsize::new(1), &ScanProgress::default());
let mut found_files = Vec::new();
while let Steal::Success(f) = files_injector.steal() {
found_files.push((f.path.file_name().unwrap().to_str().unwrap().to_owned(),
modules.name_of(f.module).map(str::to_owned)));
}
found_files.sort();
(found.total_files, found.relevant_files, found.excluded_files, found_files)
}
#[test]
fn a_directory_that_cannot_be_read_is_reported_and_not_silently_dropped() {
let root = std::env::temp_dir().join("mezura_unreadable_dir_test");
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(&root).unwrap();
fs::write(root.join("a.rs"), "fn main() {}\n").unwrap();
let root_str = root.to_str().unwrap().replace('\\', "/");
let vanished = format!("{root_str}/gone");
let config = EngineConfig { threads: crate::Threads::new(1, 1), ..EngineConfig::new([&root_str]) };
let targets = crate::engine::targets::resolve(&config.targets, crate::ObeyedIgnoreFiles::of(&config),
config.should_search_in_dotted).unwrap();
let config = Arc::new(config);
let language_map = Arc::new(crate::languages::keyed_by_name(
crate::language_file::parse_languages_in_dir(LANGUAGES_DIR).unwrap().0));
let language_lookups: SharedModuleLookups = Arc::new(ModuleLookups::OfTheWholeRun(
LanguageLookup { by_extension: build_extension_language_map(&language_map, &Default::default(), &Default::default()).0,
..Default::default() }));
let modules = Arc::new(Modules::of(&targets));
let (files_injector, dirs_injector) = (Arc::new(Injector::new()), Arc::new(Injector::new()));
let mut files_present = FilesPresent::default();
queue_the_targets(&config, &targets, &dirs_injector, &files_injector,
&mut files_present, &language_lookups, &modules, &ScanProgress::default());
dirs_injector.push(TraversedDir::new(std::path::PathBuf::from(&vanished), None, 0));
let exclude_matcher = Arc::new(build_exclude_matcher(&config.exclude_dirs).unwrap());
let (found, unreadable) = search_for_files(0, files_injector, dirs_injector,
Worker::new_lifo(), &[], Arc::new(AtomicUsize::new(0)), language_lookups, exclude_matcher,
config, modules, &AtomicUsize::new(1), &ScanProgress::default());
fs::remove_dir_all(&root).unwrap();
assert_eq!((1, 1), (found.total_files, found.relevant_files));
assert_eq!(vec![vanished], unreadable.iter().map(|x| x.path.clone()).collect::<Vec<_>>(),
"the directory that could not be read went unreported");
assert!(!unreadable[0].error_msg.is_empty(), "the reason it could not be read was dropped");
}
#[test]
fn the_git_directory_is_never_walked_even_when_dotted_ones_are() {
let root = std::env::temp_dir().join("mezura_git_skip_test");
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(root.join(".git").join("hooks")).unwrap();
fs::create_dir_all(root.join("sub").join(".git")).unwrap();
fs::create_dir_all(root.join(".github")).unwrap();
fs::write(root.join("a.rs"), "fn main() {}
").unwrap();
fs::write(root.join(".git").join("hooks").join("pre.rs"), "fn main() {}
").unwrap();
fs::write(root.join("sub").join(".git").join("nested.rs"), "fn main() {}
").unwrap();
fs::write(root.join(".github").join("deploy.rs"), "fn main() {}
").unwrap();
let root_str = root.to_str().unwrap().replace('\\', "/");
let (_, _, _, found) = count_files_of(&root_str, "");
assert_eq!(vec!["a.rs"], found, "the dotted rule alone should have kept all three out");
let (_, _, _, found) = count_files_of(&root_str, "--search-in-dotted");
assert_eq!(vec!["a.rs", "deploy.rs"], found, "the object database was walked");
fs::remove_dir_all(&root).unwrap();
}
#[test]
fn overlapping_and_globbed_targets_count_every_file_once() {
let root = std::env::temp_dir().join("mezura_overlap_test");
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(root.join("sub").join("deep")).unwrap();
fs::write(root.join("a.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("sub").join("b.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("sub").join("deep").join("c.rs"), "fn main() {}\n").unwrap();
let root = root.to_str().unwrap().replace('\\', "/");
let (_, _, _, found_files) = count_files_of(&root, "");
assert_eq!(vec!["a.rs", "b.rs", "c.rs"], found_files);
let (_, _, _, found_files) = count_files_of(&format!("{root},{root}/sub,{root}/sub/deep"), "");
assert_eq!(vec!["a.rs", "b.rs", "c.rs"], found_files);
let (_, _, _, found_files) = count_files_of(&format!("{root}/sub/deep,{root}/sub"), "");
assert_eq!(vec!["b.rs", "c.rs"], found_files);
let (_, _, _, found_files) = count_files_of(&format!("{root}/*,{root}/**/*.rs"), "");
assert_eq!(vec!["a.rs", "b.rs", "c.rs"], found_files);
fs::remove_dir_all(&root).unwrap();
}
#[test]
fn every_file_is_attributed_to_exactly_one_module() {
let root = std::env::temp_dir().join("mezura_modules_test");
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(root.join("api").join("tests").join("deep")).unwrap();
fs::create_dir_all(root.join("web")).unwrap();
fs::create_dir_all(root.join("loose")).unwrap();
fs::write(root.join("api").join("server.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("api").join("tests").join("api_test.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("api").join("tests").join("deep").join("nested_test.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("web").join("app.js"), "let x = 1;\n").unwrap();
fs::write(root.join("loose").join("script.py"), "x = 1\n").unwrap();
let root = root.to_str().unwrap().replace('\\', "/");
let of = |name: &str| Some(name.to_owned());
let (_, _, _, found) = walk(&format!("backend={root}/api frontend={root}/web"), "");
assert_eq!(vec![("api_test.rs".to_owned(), of("backend")), ("app.js".to_owned(), of("frontend")),
("nested_test.rs".to_owned(), of("backend")), ("server.rs".to_owned(), of("backend"))], found);
let (_, _, _, found) = walk(&format!("backend={root}/api tests={root}/api/tests"), "");
assert_eq!(vec![("api_test.rs".to_owned(), of("tests")), ("nested_test.rs".to_owned(), of("tests")),
("server.rs".to_owned(), of("backend"))], found);
let (_, _, _, found) = walk(&format!("{root} tests={root}/api/tests"), "");
assert_eq!(vec![("api_test.rs".to_owned(), of("tests")), ("app.js".to_owned(), None),
("nested_test.rs".to_owned(), of("tests")), ("script.py".to_owned(), None),
("server.rs".to_owned(), None)], found);
let (_, _, _, found) = walk(&format!("{root}/api entry={root}/api/server.rs"), "");
assert_eq!(vec![("api_test.rs".to_owned(), None), ("nested_test.rs".to_owned(), None),
("server.rs".to_owned(), of("entry"))], found);
let (_, _, _, found) = walk(&format!("tests={root}/api/tests backend={root}/api"), "");
assert_eq!(vec![("api_test.rs".to_owned(), of("tests")), ("nested_test.rs".to_owned(), of("tests")),
("server.rs".to_owned(), of("backend"))], found);
fs::remove_dir_all(&root).unwrap();
}
#[cfg(windows)]
fn link_dir(original: &Path, link: &Path) {
let output = std::process::Command::new("cmd")
.args(["/C", "mklink", "/J", &link.to_string_lossy(), &original.to_string_lossy()])
.output().expect("mklink is part of the shell on every Windows");
assert!(link.exists(), "could not create a junction: {}", String::from_utf8_lossy(&output.stderr));
}
#[cfg(unix)]
fn link_dir(original: &Path, link: &Path) {
std::os::unix::fs::symlink(original, link).unwrap();
}
#[test]
fn a_link_found_during_the_walk_is_not_followed_but_one_that_was_asked_for_is() {
let root = std::env::temp_dir().join("mezura_symlink_test");
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(root.join("real")).unwrap();
fs::write(root.join("real").join("a.rs"), "fn main() {}\nlet x = 1;\n").unwrap();
link_dir(&root.join("real"), &root.join("linked"));
let root_str = root.to_str().unwrap().replace('\\', "/");
let (_, relevant, _, found) = count_files_of(&root_str, "");
assert_eq!(1, relevant, "the file under the link was counted a second time");
assert_eq!(vec!["a.rs"], found);
let (_, relevant, _, found) = count_files_of(&format!("{root_str}/linked"), "");
assert_eq!(1, relevant);
assert_eq!(vec!["a.rs"], found);
let (_, relevant, _, found) = count_files_of(&format!("{root_str}/*"), "");
assert_eq!(1, relevant, "the link was reached through a pattern and counted a second time");
assert_eq!(vec!["a.rs"], found);
#[cfg(unix)]
{
std::os::unix::fs::symlink(root.join("real").join("a.rs"), root.join("real").join("b.rs")).unwrap();
let (_, relevant, _, found) = count_files_of(&root_str, "");
assert_eq!(1, relevant);
assert_eq!(vec!["a.rs"], found);
}
fs::remove_dir_all(&root).unwrap();
}
#[test]
fn a_target_whose_path_contains_a_space_is_one_target() {
let root = std::env::temp_dir().join("mezura space test");
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(root.join("sub")).unwrap();
fs::write(root.join("a.rs"), "fn main() {}
").unwrap();
fs::write(root.join("sub").join("b.rs"), "fn main() {}
").unwrap();
let root = root.to_str().unwrap().replace('\\', "/");
let (_, relevant, _, found) = count_files_of(&root, "");
assert_eq!(2, relevant, "a path with a space in it was read as two targets");
assert_eq!(vec!["a.rs", "b.rs"], found);
fs::remove_dir_all(&root).unwrap();
}
#[test]
fn an_extensionless_script_is_claimed_through_its_first_line() {
let root = std::env::temp_dir().join("mezura_shebang_walk_test");
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(root.join(".git")).unwrap();
fs::write(root.join(".gitignore"), "ignored-deploy\n").unwrap();
fs::write(root.join("deploy"), "#!/usr/bin/env bash\necho hi\n").unwrap();
fs::write(root.join("ignored-deploy"), "#!/bin/sh\necho hi\n").unwrap();
fs::write(root.join("LICENSE"), "MIT License\n").unwrap();
fs::write(root.join("script.xyz"), "#!/bin/sh\necho hi\n").unwrap();
fs::write(root.join("a.rs"), "fn main() {}\n").unwrap();
let root_str = root.to_str().unwrap().replace('\\', "/");
let (total, relevant, excluded, found) = count_files_of(&root_str, "");
assert_eq!(vec!["a.rs", "deploy"], found, "the shebang did not claim the script");
assert_eq!((6, 2, 0), (total, relevant, excluded));
fs::remove_dir_all(&root).unwrap();
}
#[test]
fn what_a_gitignore_names_is_left_out_of_the_walk() {
let root = std::env::temp_dir().join("mezura_gitignore_test");
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(root.join(".git")).unwrap();
fs::create_dir_all(root.join("ignored_dir")).unwrap();
fs::create_dir_all(root.join("sub")).unwrap();
fs::write(root.join(".gitignore"), "*.py\nignored_dir/\n!keep.py\n").unwrap();
fs::write(root.join("a.py"), "x = 1\n").unwrap();
fs::write(root.join("keep.py"), "x = 1\n").unwrap();
fs::write(root.join("b.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("ignored_dir").join("c.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("sub").join(".gitignore"), "*.rs\n").unwrap();
fs::write(root.join("sub").join("d.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("sub").join("e.py"), "x = 1\n").unwrap();
let root_str = root.to_str().unwrap().replace('\\', "/");
let (total, relevant, excluded, found_files) = count_files_of(&root_str, "");
assert_eq!((7, 2, 3), (total, relevant, excluded));
assert_eq!(vec!["b.rs", "keep.py"], found_files);
let (total, relevant, excluded, found_files) = count_files_of(&root_str, "--no-gitignore");
assert_eq!((8, 6, 0), (total, relevant, excluded));
assert_eq!(vec!["a.py", "b.rs", "c.rs", "d.rs", "e.py", "keep.py"], found_files);
let (_, relevant, _, found_files) = count_files_of(&format!("{root_str}/ignored_dir"), "");
assert_eq!(1, relevant);
assert_eq!(vec!["c.rs"], found_files);
fs::remove_dir_all(&root).unwrap();
}
#[test]
fn the_ignore_files_git_does_not_read_are_obeyed_and_are_turned_off_on_their_own() {
let root = std::env::temp_dir().join("mezura_ignore_files_test");
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(root.join(".git")).unwrap();
fs::create_dir_all(root.join("vendor")).unwrap();
fs::write(root.join(".gitignore"), "built.rs\n").unwrap();
fs::write(root.join(".ignore"), "vendor/\nbundle.rs\n").unwrap();
fs::write(root.join(".rgignore"), "!bundle.rs\n").unwrap();
fs::write(root.join("mine.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("built.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("bundle.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("vendor").join("dep.rs"), "fn main() {}\n").unwrap();
let root_str = root.to_str().unwrap().replace('\\', "/");
let (_, _, _, found_files) = count_files_of(&root_str, "");
assert_eq!(vec!["bundle.rs", "mine.rs"], found_files);
let (_, _, _, found_files) = count_files_of(&root_str, "--no-ignore-files");
assert_eq!(vec!["bundle.rs", "dep.rs", "mine.rs"], found_files);
let (_, _, _, found_files) = count_files_of(&root_str, "--no-gitignore");
assert_eq!(vec!["built.rs", "bundle.rs", "mine.rs"], found_files);
let (_, _, _, found_files) = count_files_of(&root_str, "--no-gitignore --no-ignore-files");
assert_eq!(vec!["built.rs", "bundle.rs", "dep.rs", "mine.rs"], found_files);
fs::remove_dir_all(&root).unwrap();
}
#[test]
fn a_producer_out_of_work_takes_a_directory_from_a_peer_and_never_from_its_own_queue() {
let (mine, peer) = (Worker::new_lifo(), Worker::new_lifo());
let stealers = [mine.stealer(), peer.stealer()];
let peers_dir = PathBuf::from("mezura-a-peers-directory");
peer.push(TraversedDir::new(peers_dir.clone(), None, 0));
assert_eq!(Some(peers_dir), steal_a_dir(0, &Injector::new(), &stealers, &mine).map(|dir| dir.path));
mine.push(TraversedDir::new(PathBuf::from("mezura-my-own-directory"), None, 0));
assert!(steal_a_dir(0, &Injector::new(), &stealers, &mine).is_none(),
"a producer stole from its own queue");
}
#[test]
#[cfg(any(windows, target_os = "macos"))]
fn an_ignore_file_is_obeyed_however_its_name_is_spelled() {
let root = std::env::temp_dir().join("mezura_ignore_file_case_test");
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(&root).unwrap();
fs::write(root.join(".GITIGNORE"), "hide.rs\n").unwrap();
fs::write(root.join("hide.rs"), "fn main() {}\n").unwrap();
fs::write(root.join("keep.rs"), "fn main() {}\n").unwrap();
let root_str = root.to_str().unwrap().replace('\\', "/");
let (_, _, _, found_files) = count_files_of(&root_str, "");
assert_eq!(vec!["keep.rs"], found_files);
fs::remove_dir_all(&root).unwrap();
}
}