diff --git a/src/dir_walker.rs b/src/dir_walker.rs index 10557482..351c11a0 100644 --- a/src/dir_walker.rs +++ b/src/dir_walker.rs @@ -142,44 +142,49 @@ fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option { .into_iter() .par_bridge() .filter_map(|entry| { - if let Ok(ref entry) = entry { - // uncommenting the below line gives simpler code but - // rayon doesn't parallelize as well giving a 3X performance drop - // hence we unravel the recursion a bit + match entry { + Ok(ref entry) => { + // uncommenting the below line gives simpler code but + // rayon doesn't parallelize as well giving a 3X performance drop + // hence we unravel the recursion a bit - // return walk(entry.path(), walk_data, depth) + // return walk(entry.path(), walk_data, depth) - if !ignore_file(entry, walk_data) { - if let Ok(data) = entry.file_type() { - if data.is_dir() - || (walk_data.follow_links && data.is_symlink()) - { - return walk(entry.path(), walk_data, depth + 1); - } + if !ignore_file(entry, walk_data) { + if let Ok(data) = entry.file_type() { + if data.is_dir() + || (walk_data.follow_links && data.is_symlink()) + { + return walk(entry.path(), walk_data, depth + 1); + } - let node = build_node( - entry.path(), - vec![], - walk_data.filter_regex, - walk_data.invert_filter_regex, - walk_data.use_apparent_size, - data.is_symlink(), - data.is_file(), - walk_data.by_filecount, - depth, - ); + let node = build_node( + entry.path(), + vec![], + walk_data.filter_regex, + walk_data.invert_filter_regex, + walk_data.use_apparent_size, + data.is_symlink(), + data.is_file(), + walk_data.by_filecount, + depth, + ); - prog_data.num_files.fetch_add(1, ORDERING); - if let Some(ref file) = node { - prog_data.total_file_size.fetch_add(file.size, ORDERING); - } + prog_data.num_files.fetch_add(1, ORDERING); + if let Some(ref file) = node { + prog_data + .total_file_size + .fetch_add(file.size, ORDERING); + } - return node; + return node; + } } } - } else { - let mut editable_error = errors.lock().unwrap(); - editable_error.no_permissions = true + Err(ref failed) => { + let mut editable_error = errors.lock().unwrap(); + editable_error.no_permissions.insert(failed.to_string()); + } } None }) @@ -189,7 +194,9 @@ fn walk(dir: PathBuf, walk_data: &WalkData, depth: usize) -> Option { let mut editable_error = errors.lock().unwrap(); match failed.kind() { std::io::ErrorKind::PermissionDenied => { - editable_error.no_permissions = true; + editable_error + .no_permissions + .insert(dir.to_string_lossy().into()); } std::io::ErrorKind::NotFound => { editable_error.file_not_found.insert(failed.to_string()); diff --git a/src/main.rs b/src/main.rs index 99e8e35e..3f2f0c32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -269,7 +269,6 @@ fn main() { } let final_errors = walk_data.errors.lock().unwrap(); - let failed_permissions = final_errors.no_permissions; if !final_errors.file_not_found.is_empty() { let err = final_errors .file_not_found @@ -279,8 +278,14 @@ fn main() { .join(", "); eprintln!("No such file or directory: {}", err); } - if failed_permissions { - eprintln!("Did not have permissions for all directories"); + if !final_errors.no_permissions.is_empty() { + let err = final_errors + .no_permissions + .iter() + .map(|a| a.as_ref()) + .collect::>() + .join(", "); + eprintln!("Did not have permissions for directories: {}", err); } if !final_errors.unknown_error.is_empty() { let err = final_errors diff --git a/src/progress.rs b/src/progress.rs index e1bfc620..138ab013 100644 --- a/src/progress.rs +++ b/src/progress.rs @@ -70,7 +70,7 @@ impl PAtomicInfo { #[derive(Default)] pub struct RuntimeErrors { - pub no_permissions: bool, + pub no_permissions: HashSet, pub file_not_found: HashSet, pub unknown_error: HashSet, pub abort: bool, diff --git a/tests/test_exact_output.rs b/tests/test_exact_output.rs index c2e7f88e..094efbc8 100644 --- a/tests/test_exact_output.rs +++ b/tests/test_exact_output.rs @@ -41,7 +41,11 @@ fn initialize() { }); } -fn exact_output_test>(valid_outputs: Vec, command_args: Vec) { +fn exact_output_test>( + command_args: Vec, + valid_outputs: Vec, + error_outputs: Vec, +) { initialize(); let mut a = &mut Command::cargo_bin("dust").unwrap(); @@ -50,17 +54,32 @@ fn exact_output_test>(valid_outputs: Vec, command_args: a = a.arg(p); } - let output = str::from_utf8(&a.unwrap().stdout).unwrap().to_owned(); + if !valid_outputs.is_empty() { + let stdout_output = str::from_utf8(&a.unwrap().stdout).unwrap().to_owned(); + let will_fail = valid_outputs.iter().any(|i| stdout_output.contains(i)); + if !will_fail { + eprintln!( + "output(stdout):\n{}\ndoes not contain any of:\n{}", + stdout_output, + valid_outputs.join("\n\n") + ); + } + assert!(will_fail); + } - let will_fail = valid_outputs.iter().any(|i| output.contains(i)); - if !will_fail { - eprintln!( - "output:\n{}\ndoes not contain any of:\n{}", - output, - valid_outputs.join("\n\n") - ); + // check stderr + if !error_outputs.is_empty() { + let stderr_output = str::from_utf8(&a.unwrap().stderr).unwrap().to_owned(); + let will_fail = error_outputs.iter().any(|i| stderr_output.contains(i)); + if !will_fail { + eprintln!( + "output(stderr):\n{}\ndoes not contain any of:\n{}", + stderr_output, + valid_outputs.join("\n\n") + ); + } + assert!(will_fail); } - assert!(will_fail) } // "windows" result data can vary by host (size seems to be variable by one byte); fix code vs test and re-enable @@ -68,7 +87,7 @@ fn exact_output_test>(valid_outputs: Vec, command_args: #[test] pub fn test_main_basic() { // -c is no color mode - This makes testing much simpler - exact_output_test(main_output(), vec!["-c", "-B", "/tmp/test_dir/"]) + exact_output_test(vec!["-c", "-B", "/tmp/test_dir/"], main_output(), vec![]); } #[cfg_attr(target_os = "windows", ignore)] @@ -81,7 +100,7 @@ pub fn test_main_multi_arg() { "/tmp/test_dir", "/tmp/test_dir", ]; - exact_output_test(main_output(), command_args); + exact_output_test(command_args, main_output(), vec![]); } fn main_output() -> Vec { @@ -112,7 +131,7 @@ fn main_output() -> Vec { #[test] pub fn test_main_long_paths() { let command_args = vec!["-c", "-p", "-B", "/tmp/test_dir/"]; - exact_output_test(main_output_long_paths(), command_args); + exact_output_test(command_args, main_output_long_paths(), vec![]); } fn main_output_long_paths() -> Vec { @@ -140,7 +159,7 @@ fn main_output_long_paths() -> Vec { #[test] pub fn test_substring_of_names_and_long_names() { let command_args = vec!["-c", "-B", "/tmp/test_dir2"]; - exact_output_test(no_substring_of_names_output(), command_args); + exact_output_test(command_args, no_substring_of_names_output(), vec![]); } fn no_substring_of_names_output() -> Vec { @@ -174,7 +193,7 @@ fn no_substring_of_names_output() -> Vec { #[test] pub fn test_unicode_directories() { let command_args = vec!["-c", "-B", "/tmp/test_dir_unicode"]; - exact_output_test(unicode_dir(), command_args); + exact_output_test(command_args, unicode_dir(), vec![]); } fn unicode_dir() -> Vec { @@ -201,7 +220,7 @@ fn unicode_dir() -> Vec { #[test] pub fn test_apparent_size() { let command_args = vec!["-c", "-s", "-b", "/tmp/test_dir"]; - exact_output_test(apparent_size_output(), command_args); + exact_output_test(command_args, apparent_size_output(), vec![]); } fn apparent_size_output() -> Vec { @@ -222,3 +241,22 @@ fn apparent_size_output() -> Vec { vec![one_space_before, two_space_before] } + +#[cfg_attr(target_os = "windows", ignore)] +#[test] +pub fn test_permission() { + Command::new("sh") + .arg("-c") + .arg("mkdir -p /tmp/unreadable_folder && chmod 000 /tmp/unreadable_folder") + .output() + .unwrap(); + let command_args = vec!["/tmp/unreadable_folder"]; + let permission_msg = r#"Did not have permissions"#.trim().to_string(); + exact_output_test(command_args, vec![], vec![permission_msg]); + + Command::new("sh") + .arg("-c") + .arg("chmod 555 /tmp/unreadable_folder") + .output() + .unwrap(); +}