diff options
| author | Stefan Boberg <[email protected]> | 2021-11-04 14:18:54 +0100 |
|---|---|---|
| committer | Stefan Boberg <[email protected]> | 2021-11-04 14:19:05 +0100 |
| commit | 472569d4a5f2daaef7e234d93b417ccb650be624 (patch) | |
| tree | 085c33f178855ad5ffe48b01bf99d41516ffa011 /thirdparty/BLAKE3/b3sum/tests/cli_tests.rs | |
| parent | Merge branch 'main' of https://github.com/EpicGames/zen (diff) | |
| download | zen-472569d4a5f2daaef7e234d93b417ccb650be624.tar.xz zen-472569d4a5f2daaef7e234d93b417ccb650be624.zip | |
Renamed 3rdparty -> thirdparty for legal compliance
Diffstat (limited to 'thirdparty/BLAKE3/b3sum/tests/cli_tests.rs')
| -rw-r--r-- | thirdparty/BLAKE3/b3sum/tests/cli_tests.rs | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/thirdparty/BLAKE3/b3sum/tests/cli_tests.rs b/thirdparty/BLAKE3/b3sum/tests/cli_tests.rs new file mode 100644 index 000000000..51fbbba98 --- /dev/null +++ b/thirdparty/BLAKE3/b3sum/tests/cli_tests.rs @@ -0,0 +1,552 @@ +use duct::cmd; +use std::ffi::OsString; +use std::fs; +use std::io::prelude::*; +use std::path::PathBuf; + +pub fn b3sum_exe() -> PathBuf { + env!("CARGO_BIN_EXE_b3sum").into() +} + +#[test] +fn test_hash_one() { + let expected = format!("{} -", blake3::hash(b"foo").to_hex()); + let output = cmd!(b3sum_exe()).stdin_bytes("foo").read().unwrap(); + assert_eq!(&*expected, output); +} + +#[test] +fn test_hash_one_raw() { + let expected = blake3::hash(b"foo").as_bytes().to_owned(); + let output = cmd!(b3sum_exe(), "--raw") + .stdin_bytes("foo") + .stdout_capture() + .run() + .unwrap() + .stdout; + assert_eq!(expected, output.as_slice()); +} + +#[test] +fn test_hash_many() { + let dir = tempfile::tempdir().unwrap(); + let file1 = dir.path().join("file1"); + fs::write(&file1, b"foo").unwrap(); + let file2 = dir.path().join("file2"); + fs::write(&file2, b"bar").unwrap(); + + let output = cmd!(b3sum_exe(), &file1, &file2).read().unwrap(); + let foo_hash = blake3::hash(b"foo"); + let bar_hash = blake3::hash(b"bar"); + let expected = format!( + "{} {}\n{} {}", + foo_hash.to_hex(), + // account for slash normalization on Windows + file1.to_string_lossy().replace("\\", "/"), + bar_hash.to_hex(), + file2.to_string_lossy().replace("\\", "/"), + ); + assert_eq!(expected, output); + + let output_no_names = cmd!(b3sum_exe(), "--no-names", &file1, &file2) + .read() + .unwrap(); + let expected_no_names = format!("{}\n{}", foo_hash.to_hex(), bar_hash.to_hex(),); + assert_eq!(expected_no_names, output_no_names); +} + +#[test] +fn test_missing_files() { + let dir = tempfile::tempdir().unwrap(); + let file1 = dir.path().join("file1"); + fs::write(&file1, b"foo").unwrap(); + let file2 = dir.path().join("file2"); + fs::write(&file2, b"bar").unwrap(); + + let output = cmd!(b3sum_exe(), "file1", "missing_file", "file2") + .dir(dir.path()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run() + .unwrap(); + assert!(!output.status.success()); + + let foo_hash = blake3::hash(b"foo"); + let bar_hash = blake3::hash(b"bar"); + let expected_stdout = format!( + "{} file1\n{} file2\n", + foo_hash.to_hex(), + bar_hash.to_hex(), + ); + assert_eq!(expected_stdout.as_bytes(), &output.stdout[..]); + + let bing_error = fs::File::open(dir.path().join("missing_file")).unwrap_err(); + let expected_stderr = format!("b3sum: missing_file: {}\n", bing_error.to_string()); + assert_eq!(expected_stderr.as_bytes(), &output.stderr[..]); +} + +#[test] +fn test_hash_length() { + let mut buf = [0; 100]; + blake3::Hasher::new() + .update(b"foo") + .finalize_xof() + .fill(&mut buf); + let expected = format!("{} -", hex::encode(&buf[..])); + let output = cmd!(b3sum_exe(), "--length=100") + .stdin_bytes("foo") + .read() + .unwrap(); + assert_eq!(&*expected, &*output); +} + +#[test] +fn test_keyed() { + let key = [42; blake3::KEY_LEN]; + let f = tempfile::NamedTempFile::new().unwrap(); + f.as_file().write_all(b"foo").unwrap(); + f.as_file().flush().unwrap(); + let expected = blake3::keyed_hash(&key, b"foo").to_hex(); + let output = cmd!(b3sum_exe(), "--keyed", "--no-names", f.path()) + .stdin_bytes(&key[..]) + .read() + .unwrap(); + assert_eq!(&*expected, &*output); +} + +#[test] +fn test_derive_key() { + let context = "BLAKE3 2019-12-28 10:28:41 example context"; + let f = tempfile::NamedTempFile::new().unwrap(); + f.as_file().write_all(b"key material").unwrap(); + f.as_file().flush().unwrap(); + let mut derive_key_out = [0; blake3::OUT_LEN]; + blake3::derive_key(context, b"key material", &mut derive_key_out); + let expected = hex::encode(&derive_key_out); + let output = cmd!(b3sum_exe(), "--derive-key", context, "--no-names", f.path()) + .read() + .unwrap(); + assert_eq!(&*expected, &*output); +} + +#[test] +fn test_no_mmap() { + let f = tempfile::NamedTempFile::new().unwrap(); + f.as_file().write_all(b"foo").unwrap(); + f.as_file().flush().unwrap(); + + let expected = blake3::hash(b"foo").to_hex(); + let output = cmd!(b3sum_exe(), "--no-mmap", "--no-names", f.path()) + .read() + .unwrap(); + assert_eq!(&*expected, &*output); +} + +#[test] +fn test_length_without_value_is_an_error() { + let result = cmd!(b3sum_exe(), "--length") + .stdin_bytes("foo") + .stderr_capture() + .run(); + assert!(result.is_err()); +} + +#[test] +fn test_raw_with_multi_files_is_an_error() { + let f1 = tempfile::NamedTempFile::new().unwrap(); + let f2 = tempfile::NamedTempFile::new().unwrap(); + + // Make sure it doesn't error with just one file + let result = cmd!(b3sum_exe(), "--raw", f1.path()).stdout_capture().run(); + assert!(result.is_ok()); + + // Make sure it errors when both file are passed + let result = cmd!(b3sum_exe(), "--raw", f1.path(), f2.path()) + .stderr_capture() + .run(); + assert!(result.is_err()); +} + +#[test] +#[cfg(unix)] +fn test_newline_and_backslash_escaping_on_unix() { + let empty_hash = blake3::hash(b"").to_hex(); + let dir = tempfile::tempdir().unwrap(); + fs::create_dir(dir.path().join("subdir")).unwrap(); + let names = [ + "abcdef", + "abc\ndef", + "abc\\def", + "abc\rdef", + "abc\r\ndef", + "subdir/foo", + ]; + let mut paths = Vec::new(); + for name in &names { + let path = dir.path().join(name); + println!("creating file at {:?}", path); + fs::write(&path, b"").unwrap(); + paths.push(path); + } + let output = cmd(b3sum_exe(), &names).dir(dir.path()).read().unwrap(); + let expected = format!( + "\ +{0} abcdef +\\{0} abc\\ndef +\\{0} abc\\\\def +{0} abc\rdef +\\{0} abc\r\\ndef +{0} subdir/foo", + empty_hash, + ); + println!("output"); + println!("======"); + println!("{}", output); + println!(); + println!("expected"); + println!("========"); + println!("{}", expected); + println!(); + assert_eq!(expected, output); +} + +#[test] +#[cfg(windows)] +fn test_slash_normalization_on_windows() { + let empty_hash = blake3::hash(b"").to_hex(); + let dir = tempfile::tempdir().unwrap(); + fs::create_dir(dir.path().join("subdir")).unwrap(); + // Note that filenames can't contain newlines or backslashes on Windows, so + // we don't test escaping here. We only test forward slash and backslash as + // directory separators. + let names = ["abcdef", "subdir/foo", "subdir\\bar"]; + let mut paths = Vec::new(); + for name in &names { + let path = dir.path().join(name); + println!("creating file at {:?}", path); + fs::write(&path, b"").unwrap(); + paths.push(path); + } + let output = cmd(b3sum_exe(), &names).dir(dir.path()).read().unwrap(); + let expected = format!( + "\ +{0} abcdef +{0} subdir/foo +{0} subdir/bar", + empty_hash, + ); + println!("output"); + println!("======"); + println!("{}", output); + println!(); + println!("expected"); + println!("========"); + println!("{}", expected); + println!(); + assert_eq!(expected, output); +} + +#[test] +#[cfg(unix)] +fn test_invalid_unicode_on_unix() { + use std::os::unix::ffi::OsStringExt; + + let empty_hash = blake3::hash(b"").to_hex(); + let dir = tempfile::tempdir().unwrap(); + let names = ["abcdef".into(), OsString::from_vec(b"abc\xffdef".to_vec())]; + let mut paths = Vec::new(); + for name in &names { + let path = dir.path().join(name); + println!("creating file at {:?}", path); + // Note: Some operating systems, macOS in particular, simply don't + // allow invalid Unicode in filenames. On those systems, this write + // will fail. That's fine, we'll just short-circuit this test in that + // case. But assert that at least Linux allows this. + let write_result = fs::write(&path, b""); + if cfg!(target_os = "linux") { + write_result.expect("Linux should allow invalid Unicode"); + } else if write_result.is_err() { + return; + } + paths.push(path); + } + let output = cmd(b3sum_exe(), &names).dir(dir.path()).read().unwrap(); + let expected = format!( + "\ +{0} abcdef +{0} abc�def", + empty_hash, + ); + println!("output"); + println!("======"); + println!("{}", output); + println!(); + println!("expected"); + println!("========"); + println!("{}", expected); + println!(); + assert_eq!(expected, output); +} + +#[test] +#[cfg(windows)] +fn test_invalid_unicode_on_windows() { + use std::os::windows::ffi::OsStringExt; + + let empty_hash = blake3::hash(b"").to_hex(); + let dir = tempfile::tempdir().unwrap(); + let surrogate_char = 0xDC00; + let bad_unicode_wchars = [ + 'a' as u16, + 'b' as u16, + 'c' as u16, + surrogate_char, + 'd' as u16, + 'e' as u16, + 'f' as u16, + ]; + let bad_osstring = OsString::from_wide(&bad_unicode_wchars); + let names = ["abcdef".into(), bad_osstring]; + let mut paths = Vec::new(); + for name in &names { + let path = dir.path().join(name); + println!("creating file at {:?}", path); + fs::write(&path, b"").unwrap(); + paths.push(path); + } + let output = cmd(b3sum_exe(), &names).dir(dir.path()).read().unwrap(); + let expected = format!( + "\ +{0} abcdef +{0} abc�def", + empty_hash, + ); + println!("output"); + println!("======"); + println!("{}", output); + println!(); + println!("expected"); + println!("========"); + println!("{}", expected); + println!(); + assert_eq!(expected, output); +} + +#[test] +fn test_check() { + // Make a directory full of files, and make sure the b3sum output in that + // directory is what we expect. + let a_hash = blake3::hash(b"a").to_hex(); + let b_hash = blake3::hash(b"b").to_hex(); + let cd_hash = blake3::hash(b"cd").to_hex(); + let dir = tempfile::tempdir().unwrap(); + fs::write(dir.path().join("a"), b"a").unwrap(); + fs::write(dir.path().join("b"), b"b").unwrap(); + fs::create_dir(dir.path().join("c")).unwrap(); + fs::write(dir.path().join("c/d"), b"cd").unwrap(); + let output = cmd!(b3sum_exe(), "a", "b", "c/d") + .dir(dir.path()) + .stdout_capture() + .stderr_capture() + .run() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + let expected_checkfile = format!( + "{} a\n\ + {} b\n\ + {} c/d\n", + a_hash, b_hash, cd_hash, + ); + assert_eq!(expected_checkfile, stdout); + assert_eq!("", stderr); + + // Now use the output we just validated as a checkfile, passed to stdin. + let output = cmd!(b3sum_exe(), "--check") + .stdin_bytes(expected_checkfile.as_bytes()) + .dir(dir.path()) + .stdout_capture() + .stderr_capture() + .run() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + let expected_check_output = "\ + a: OK\n\ + b: OK\n\ + c/d: OK\n"; + assert_eq!(expected_check_output, stdout); + assert_eq!("", stderr); + + // Now pass the same checkfile twice on the command line just for fun. + let checkfile_path = dir.path().join("checkfile"); + fs::write(&checkfile_path, &expected_checkfile).unwrap(); + let output = cmd!(b3sum_exe(), "--check", &checkfile_path, &checkfile_path) + .dir(dir.path()) + .stdout_capture() + .stderr_capture() + .run() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + let mut double_check_output = String::new(); + double_check_output.push_str(&expected_check_output); + double_check_output.push_str(&expected_check_output); + assert_eq!(double_check_output, stdout); + assert_eq!("", stderr); + + // Corrupt one of the files and check again. + fs::write(dir.path().join("b"), b"CORRUPTION").unwrap(); + let output = cmd!(b3sum_exe(), "--check", &checkfile_path) + .dir(dir.path()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + let expected_check_failure = "\ + a: OK\n\ + b: FAILED\n\ + c/d: OK\n"; + assert!(!output.status.success()); + assert_eq!(expected_check_failure, stdout); + assert_eq!("", stderr); + + // Delete one of the files and check again. + fs::remove_file(dir.path().join("b")).unwrap(); + let open_file_error = fs::File::open(dir.path().join("b")).unwrap_err(); + let output = cmd!(b3sum_exe(), "--check", &checkfile_path) + .dir(dir.path()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + let expected_check_failure = format!( + "a: OK\n\ + b: FAILED ({})\n\ + c/d: OK\n", + open_file_error, + ); + assert!(!output.status.success()); + assert_eq!(expected_check_failure, stdout); + assert_eq!("", stderr); + + // Confirm that --quiet suppresses the OKs but not the FAILEDs. + let output = cmd!(b3sum_exe(), "--check", "--quiet", &checkfile_path) + .dir(dir.path()) + .stdout_capture() + .stderr_capture() + .unchecked() + .run() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + let expected_check_failure = format!("b: FAILED ({})\n", open_file_error); + assert!(!output.status.success()); + assert_eq!(expected_check_failure, stdout); + assert_eq!("", stderr); +} + +#[test] +fn test_check_invalid_characters() { + // Check that a null character in the path fails. + let output = cmd!(b3sum_exe(), "--check") + .stdin_bytes("0000000000000000000000000000000000000000000000000000000000000000 \0") + .stdout_capture() + .stderr_capture() + .unchecked() + .run() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + assert!(!output.status.success()); + assert_eq!("", stdout); + assert_eq!("b3sum: Null character in path\n", stderr); + + // Check that a Unicode replacement character in the path fails. + let output = cmd!(b3sum_exe(), "--check") + .stdin_bytes("0000000000000000000000000000000000000000000000000000000000000000 �") + .stdout_capture() + .stderr_capture() + .unchecked() + .run() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + assert!(!output.status.success()); + assert_eq!("", stdout); + assert_eq!("b3sum: Unicode replacement character in path\n", stderr); + + // Check that an invalid escape sequence in the path fails. + let output = cmd!(b3sum_exe(), "--check") + .stdin_bytes("\\0000000000000000000000000000000000000000000000000000000000000000 \\a") + .stdout_capture() + .stderr_capture() + .unchecked() + .run() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + assert!(!output.status.success()); + assert_eq!("", stdout); + assert_eq!("b3sum: Invalid backslash escape\n", stderr); + + // Windows also forbids literal backslashes. Check for that if and only if + // we're on Windows. + if cfg!(windows) { + let output = cmd!(b3sum_exe(), "--check") + .stdin_bytes("0000000000000000000000000000000000000000000000000000000000000000 \\") + .stdout_capture() + .stderr_capture() + .unchecked() + .run() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + assert!(!output.status.success()); + assert_eq!("", stdout); + assert_eq!("b3sum: Backslash in path\n", stderr); + } +} + +#[test] +fn test_globbing() { + // On Unix, globbing is provided by the shell. On Windows, globbing is + // provided by us, using the `wild` crate. + let dir = tempfile::tempdir().unwrap(); + let file1 = dir.path().join("file1"); + fs::write(&file1, b"foo").unwrap(); + let file2 = dir.path().join("file2"); + fs::write(&file2, b"bar").unwrap(); + + let foo_hash = blake3::hash(b"foo"); + let bar_hash = blake3::hash(b"bar"); + // NOTE: This assumes that the glob will be expanded in alphabetical order, + // to "file1 file2" rather than "file2 file1". So far, this seems to + // be true (guaranteed?) of Unix shell behavior, and true in practice + // with the `wild` crate on Windows. It's possible that this could + // start failing in the future, though, or on some unknown platform. + // If that ever happens, we'll need to relax this test somehow, + // probably by just testing for both possible outputs. I'm not + // handling that case in advance, though, because I'd prefer to hear + // about it if it comes up. + let expected = format!("{} file1\n{} file2", foo_hash.to_hex(), bar_hash.to_hex()); + + let star_command = format!("{} *", b3sum_exe().to_str().unwrap()); + let (exe, c_flag) = if cfg!(windows) { + ("cmd.exe", "/C") + } else { + ("/bin/sh", "-c") + }; + let output = cmd!(exe, c_flag, star_command) + .dir(dir.path()) + .read() + .unwrap(); + assert_eq!(expected, output); +} |