diff --git a/.github/workflows/GnuComment.yml b/.github/workflows/GnuComment.yml
new file mode 100644
index 00000000000..f22abdf4223
--- /dev/null
+++ b/.github/workflows/GnuComment.yml
@@ -0,0 +1,52 @@
+name: GnuComment
+
+on:
+ workflow_run:
+ workflows: ["GnuTests"]
+ types:
+ - completed
+
+jobs:
+ post-comment:
+ runs-on: ubuntu-latest
+ if: >
+ github.event.workflow_run.event == 'pull_request'
+ steps:
+ - name: 'Download artifact'
+ uses: actions/github-script@v3.1.0
+ with:
+ script: |
+ // List all artifacts from GnuTests
+ var artifacts = await github.actions.listWorkflowRunArtifacts({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: ${{ github.event.workflow_run.id }},
+ });
+
+ // Download the "comment" artifact, which contains a PR number (NR) and result.txt
+ var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
+ return artifact.name == "comment"
+ })[0];
+ var download = await github.actions.downloadArtifact({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ artifact_id: matchArtifact.id,
+ archive_format: 'zip',
+ });
+ var fs = require('fs');
+ fs.writeFileSync('${{ github.workspace }}/comment.zip', Buffer.from(download.data));
+ - run: unzip comment.zip
+
+ - name: 'Comment on PR'
+ uses: actions/github-script@v3
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ var fs = require('fs');
+ var issue_number = Number(fs.readFileSync('./NR'));
+ await github.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issue_number,
+ body: String(fs.readFileSync('./result.txt')),
+ });
diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml
index b36a97cacfe..0861746548e 100644
--- a/.github/workflows/GnuTests.yml
+++ b/.github/workflows/GnuTests.yml
@@ -14,7 +14,8 @@ jobs:
permissions:
actions: read # for dawidd6/action-download-artifact to query and download artifacts
contents: read # for actions/checkout to fetch code
- pull-requests: read # for dawidd6/action-download-artifact to query commit hash
+ issues: write # to publish comment
+ pull-requests: write # for dawidd6/action-download-artifact to query commit hash & publish comment
name: Run GNU tests
runs-on: ubuntu-latest
steps:
@@ -186,6 +187,15 @@ jobs:
REF_LOG_FILE='${{ steps.vars.outputs.path_reference }}/test-logs/test-suite.log'
REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json'
REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}'
+
+ mkdir -p ${{ steps.vars.outputs.path_reference }}
+
+ COMMENT_DIR="${{ steps.vars.outputs.path_reference }}/comment"
+ mkdir -p ${COMMENT_DIR}
+ echo ${{ github.event.number }} > ${COMMENT_DIR}/NR
+ COMMENT_LOG="${COMMENT_DIR}/result.txt"
+ touch ${COMMENT_LOG}
+
if test -f "${REF_LOG_FILE}"; then
echo "Reference SHA1/ID: $(sha1sum -- "${REF_SUMMARY_FILE}")"
REF_ERROR=$(sed -n "s/^ERROR: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort)
@@ -195,28 +205,36 @@ jobs:
for LINE in ${REF_FAILING}
do
if ! grep -Fxq ${LINE}<<<"${NEW_FAILING}"; then
- echo "::warning ::Congrats! The gnu test ${LINE} is no longer failing!"
+ MSG="Congrats! The gnu test ${LINE} is no longer failing!"
+ echo "::warning ::$MSG"
+ echo $MSG >> ${COMMENT_LOG}
fi
done
for LINE in ${NEW_FAILING}
do
if ! grep -Fxq ${LINE}<<<"${REF_FAILING}"
then
- echo "::error ::GNU test failed: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
+ MSG="GNU test failed: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
+ echo "::error ::$MSG"
+ echo $MSG >> ${COMMENT_LOG}
have_new_failures="true"
fi
done
for LINE in ${REF_ERROR}
do
if ! grep -Fxq ${LINE}<<<"${NEW_ERROR}"; then
- echo "::warning ::Congrats! The gnu test ${LINE} is no longer ERROR!"
+ MSG="Congrats! The gnu test ${LINE} is no longer ERROR!"
+ echo "::warning ::$MSG"
+ echo $MSG >> ${COMMENT_LOG}
fi
done
for LINE in ${NEW_ERROR}
do
if ! grep -Fxq ${LINE}<<<"${REF_ERROR}"
then
- echo "::error ::GNU test error: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
+ MSG="GNU test error: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
+ echo "::error ::$MSG"
+ echo $MSG >> ${COMMENT_LOG}
have_new_failures="true"
fi
done
@@ -225,6 +243,12 @@ jobs:
echo "::warning ::Skipping test failure comparison; no prior reference test logs are available."
fi
if test -n "${have_new_failures}" ; then exit -1 ; fi
+ - name: Upload comparison log (for GnuComment workflow)
+ if: success() || failure() # run regardless of prior step success/failure
+ uses: actions/upload-artifact@v3
+ with:
+ name: comment
+ path: ${{ steps.vars.outputs.path_reference }}/comment/
- name: Compare test summary VS reference
if: success() || failure() # run regardless of prior step success/failure
shell: bash
diff --git a/Cargo.lock b/Cargo.lock
index cf806cd7877..1134a1c5988 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2834,6 +2834,7 @@ version = "0.0.15"
dependencies = [
"clap",
"libc",
+ "nix",
"uucore",
"winapi",
]
diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs
index 96164827244..79743385a93 100644
--- a/src/uu/cp/src/cp.rs
+++ b/src/uu/cp/src/cp.rs
@@ -22,15 +22,11 @@ use std::env;
#[cfg(not(windows))]
use std::ffi::CString;
use std::fs::{self, File, OpenOptions};
-#[cfg(any(target_os = "linux", target_os = "android"))]
-use std::io::Read;
use std::io::{self, stderr, stdin, Write};
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
#[cfg(unix)]
use std::os::unix::fs::{FileTypeExt, PermissionsExt};
-#[cfg(any(target_os = "linux", target_os = "android"))]
-use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr;
use std::string::ToString;
@@ -49,6 +45,9 @@ use uucore::fs::{
};
use walkdir::WalkDir;
+mod platform;
+use platform::copy_on_write;
+
quick_error! {
#[derive(Debug)]
pub enum Error {
@@ -224,16 +223,6 @@ pub struct Options {
verbose: bool,
}
-// From /usr/include/linux/fs.h:
-// #define FICLONE _IOW(0x94, 9, int)
-#[cfg(any(target_os = "linux", target_os = "android"))]
-// Use a macro as libc::ioctl expects u32 or u64 depending on the arch
-macro_rules! FICLONE {
- () => {
- 0x40049409
- };
-}
-
static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
static LONG_HELP: &str = "";
static EXIT_ERR: i32 = 1;
@@ -1598,26 +1587,7 @@ fn copy_helper(
} else if source_is_symlink {
copy_link(source, dest, symlinked_files)?;
} else {
- #[cfg(target_os = "macos")]
- copy_on_write_macos(
- source,
- dest,
- options.reflink_mode,
- options.sparse_mode,
- context,
- )?;
-
- #[cfg(any(target_os = "linux", target_os = "android"))]
- copy_on_write_linux(
- source,
- dest,
- options.reflink_mode,
- options.sparse_mode,
- context,
- )?;
-
- #[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
- copy_no_cow_fallback(
+ copy_on_write(
source,
dest,
options.reflink_mode,
@@ -1673,180 +1643,6 @@ fn copy_link(
symlink_file(&link, &dest, &context_for(&link, &dest), symlinked_files)
}
-/// Copies `source` to `dest` for systems without copy-on-write
-#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
-fn copy_no_cow_fallback(
- source: &Path,
- dest: &Path,
- reflink_mode: ReflinkMode,
- sparse_mode: SparseMode,
- context: &str,
-) -> CopyResult<()> {
- if reflink_mode != ReflinkMode::Never {
- return Err("--reflink is only supported on linux and macOS"
- .to_string()
- .into());
- }
- if sparse_mode != SparseMode::Auto {
- return Err("--sparse is only supported on linux".to_string().into());
- }
-
- fs::copy(source, dest).context(context)?;
-
- Ok(())
-}
-
-/// Use the Linux `ioctl_ficlone` API to do a copy-on-write clone.
-///
-/// If `fallback` is true and there is a failure performing the clone,
-/// then this function performs a standard [`std::fs::copy`]. Otherwise,
-/// this function returns an error.
-#[cfg(any(target_os = "linux", target_os = "android"))]
-fn clone
(source: P, dest: P, fallback: bool) -> std::io::Result<()>
-where
- P: AsRef,
-{
- let src_file = File::open(&source)?;
- let dst_file = File::create(&dest)?;
- let src_fd = src_file.as_raw_fd();
- let dst_fd = dst_file.as_raw_fd();
- let result = unsafe { libc::ioctl(dst_fd, FICLONE!(), src_fd) };
- if result != 0 {
- if fallback {
- std::fs::copy(source, dest).map(|_| ())
- } else {
- Err(std::io::Error::last_os_error())
- }
- } else {
- Ok(())
- }
-}
-
-/// Perform a sparse copy from one file to another.
-#[cfg(any(target_os = "linux", target_os = "android"))]
-fn sparse_copy(source: P, dest: P) -> std::io::Result<()>
-where
- P: AsRef,
-{
- use std::os::unix::prelude::MetadataExt;
-
- let mut src_file = File::open(source)?;
- let dst_file = File::create(dest)?;
- let dst_fd = dst_file.as_raw_fd();
-
- let size: usize = src_file.metadata()?.size().try_into().unwrap();
- if unsafe { libc::ftruncate(dst_fd, size.try_into().unwrap()) } < 0 {
- return Err(std::io::Error::last_os_error());
- }
-
- let blksize = dst_file.metadata()?.blksize();
- let mut buf: Vec = vec![0; blksize.try_into().unwrap()];
- let mut current_offset: usize = 0;
-
- // TODO Perhaps we can employ the "fiemap ioctl" API to get the
- // file extent mappings:
- // https://www.kernel.org/doc/html/latest/filesystems/fiemap.html
- while current_offset < size {
- let this_read = src_file.read(&mut buf)?;
- if buf.iter().any(|&x| x != 0) {
- unsafe {
- libc::pwrite(
- dst_fd,
- buf.as_ptr() as *const libc::c_void,
- this_read,
- current_offset.try_into().unwrap(),
- )
- };
- }
- current_offset += this_read;
- }
- Ok(())
-}
-
-/// Copies `source` to `dest` using copy-on-write if possible.
-#[cfg(any(target_os = "linux", target_os = "android"))]
-fn copy_on_write_linux(
- source: &Path,
- dest: &Path,
- reflink_mode: ReflinkMode,
- sparse_mode: SparseMode,
- context: &str,
-) -> CopyResult<()> {
- let result = match (reflink_mode, sparse_mode) {
- (ReflinkMode::Never, _) => std::fs::copy(source, dest).map(|_| ()),
- (ReflinkMode::Auto, SparseMode::Always) => sparse_copy(source, dest),
- (ReflinkMode::Auto, _) => clone(source, dest, true),
- (ReflinkMode::Always, SparseMode::Auto) => clone(source, dest, false),
- (ReflinkMode::Always, _) => {
- return Err("`--reflink=always` can be used only with --sparse=auto".into())
- }
- };
- result.context(context)?;
- Ok(())
-}
-
-/// Copies `source` to `dest` using copy-on-write if possible.
-#[cfg(target_os = "macos")]
-fn copy_on_write_macos(
- source: &Path,
- dest: &Path,
- reflink_mode: ReflinkMode,
- sparse_mode: SparseMode,
- context: &str,
-) -> CopyResult<()> {
- if sparse_mode != SparseMode::Auto {
- return Err("--sparse is only supported on linux".to_string().into());
- }
-
- // Extract paths in a form suitable to be passed to a syscall.
- // The unwrap() is safe because they come from the command-line and so contain non nul
- // character.
- let src = CString::new(source.as_os_str().as_bytes()).unwrap();
- let dst = CString::new(dest.as_os_str().as_bytes()).unwrap();
-
- // clonefile(2) was introduced in macOS 10.12 so we cannot statically link against it
- // for backward compatibility.
- let clonefile = CString::new("clonefile").unwrap();
- let raw_pfn = unsafe { libc::dlsym(libc::RTLD_NEXT, clonefile.as_ptr()) };
-
- let mut error = 0;
- if !raw_pfn.is_null() {
- // Call clonefile(2).
- // Safety: Casting a C function pointer to a rust function value is one of the few
- // blessed uses of `transmute()`.
- unsafe {
- let pfn: extern "C" fn(
- src: *const libc::c_char,
- dst: *const libc::c_char,
- flags: u32,
- ) -> libc::c_int = std::mem::transmute(raw_pfn);
- error = pfn(src.as_ptr(), dst.as_ptr(), 0);
- if std::io::Error::last_os_error().kind() == std::io::ErrorKind::AlreadyExists {
- // clonefile(2) fails if the destination exists. Remove it and try again. Do not
- // bother to check if removal worked because we're going to try to clone again.
- let _ = fs::remove_file(dest);
- error = pfn(src.as_ptr(), dst.as_ptr(), 0);
- }
- }
- }
-
- if raw_pfn.is_null() || error != 0 {
- // clonefile(2) is either not supported or it errored out (possibly because the FS does not
- // support COW).
- match reflink_mode {
- ReflinkMode::Always => {
- return Err(
- format!("failed to clone {:?} from {:?}: {}", source, dest, error).into(),
- )
- }
- ReflinkMode::Auto => fs::copy(source, dest).context(context)?,
- ReflinkMode::Never => fs::copy(source, dest).context(context)?,
- };
- }
-
- Ok(())
-}
-
/// Generate an error message if `target` is not the correct `target_type`
pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {
match (target_type, target.is_dir()) {
diff --git a/src/uu/cp/src/platform/linux.rs b/src/uu/cp/src/platform/linux.rs
new file mode 100644
index 00000000000..7f47b884a64
--- /dev/null
+++ b/src/uu/cp/src/platform/linux.rs
@@ -0,0 +1,110 @@
+// * This file is part of the uutils coreutils package.
+// *
+// * For the full copyright and license information, please view the LICENSE
+// * file that was distributed with this source code.
+// spell-checker:ignore ficlone reflink ftruncate pwrite fiemap
+use std::fs::File;
+use std::io::Read;
+use std::os::unix::io::AsRawFd;
+use std::path::Path;
+
+use quick_error::ResultExt;
+
+use crate::{CopyResult, ReflinkMode, SparseMode};
+
+// From /usr/include/linux/fs.h:
+// #define FICLONE _IOW(0x94, 9, int)
+// Use a macro as libc::ioctl expects u32 or u64 depending on the arch
+macro_rules! FICLONE {
+ () => {
+ 0x40049409
+ };
+}
+
+/// Use the Linux `ioctl_ficlone` API to do a copy-on-write clone.
+///
+/// If `fallback` is true and there is a failure performing the clone,
+/// then this function performs a standard [`std::fs::copy`]. Otherwise,
+/// this function returns an error.
+#[cfg(any(target_os = "linux", target_os = "android"))]
+fn clone(source: P, dest: P, fallback: bool) -> std::io::Result<()>
+where
+ P: AsRef,
+{
+ let src_file = File::open(&source)?;
+ let dst_file = File::create(&dest)?;
+ let src_fd = src_file.as_raw_fd();
+ let dst_fd = dst_file.as_raw_fd();
+ let result = unsafe { libc::ioctl(dst_fd, FICLONE!(), src_fd) };
+ if result != 0 {
+ if fallback {
+ std::fs::copy(source, dest).map(|_| ())
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ } else {
+ Ok(())
+ }
+}
+
+/// Perform a sparse copy from one file to another.
+#[cfg(any(target_os = "linux", target_os = "android"))]
+fn sparse_copy(source: P, dest: P) -> std::io::Result<()>
+where
+ P: AsRef,
+{
+ use std::os::unix::prelude::MetadataExt;
+
+ let mut src_file = File::open(source)?;
+ let dst_file = File::create(dest)?;
+ let dst_fd = dst_file.as_raw_fd();
+
+ let size: usize = src_file.metadata()?.size().try_into().unwrap();
+ if unsafe { libc::ftruncate(dst_fd, size.try_into().unwrap()) } < 0 {
+ return Err(std::io::Error::last_os_error());
+ }
+
+ let blksize = dst_file.metadata()?.blksize();
+ let mut buf: Vec = vec![0; blksize.try_into().unwrap()];
+ let mut current_offset: usize = 0;
+
+ // TODO Perhaps we can employ the "fiemap ioctl" API to get the
+ // file extent mappings:
+ // https://www.kernel.org/doc/html/latest/filesystems/fiemap.html
+ while current_offset < size {
+ let this_read = src_file.read(&mut buf)?;
+ if buf.iter().any(|&x| x != 0) {
+ unsafe {
+ libc::pwrite(
+ dst_fd,
+ buf.as_ptr() as *const libc::c_void,
+ this_read,
+ current_offset.try_into().unwrap(),
+ )
+ };
+ }
+ current_offset += this_read;
+ }
+ Ok(())
+}
+
+/// Copies `source` to `dest` using copy-on-write if possible.
+pub(crate) fn copy_on_write(
+ source: &Path,
+ dest: &Path,
+ reflink_mode: ReflinkMode,
+ sparse_mode: SparseMode,
+ context: &str,
+) -> CopyResult<()> {
+ let result = match (reflink_mode, sparse_mode) {
+ (ReflinkMode::Never, _) => std::fs::copy(source, dest).map(|_| ()),
+ (ReflinkMode::Auto, SparseMode::Always) => sparse_copy(source, dest),
+ (ReflinkMode::Auto, _) => clone(source, dest, true),
+ (ReflinkMode::Always, SparseMode::Auto) => clone(source, dest, false),
+ (ReflinkMode::Always, _) => {
+ return Err("`--reflink=always` can be used only with --sparse=auto".into())
+ }
+ };
+ result.context(context)?;
+ Ok(())
+}
diff --git a/src/uu/cp/src/platform/macos.rs b/src/uu/cp/src/platform/macos.rs
new file mode 100644
index 00000000000..7f152415412
--- /dev/null
+++ b/src/uu/cp/src/platform/macos.rs
@@ -0,0 +1,74 @@
+// * This file is part of the uutils coreutils package.
+// *
+// * For the full copyright and license information, please view the LICENSE
+// * file that was distributed with this source code.
+// spell-checker:ignore reflink
+use std::ffi::CString;
+use std::fs;
+use std::os::unix::ffi::OsStrExt;
+use std::path::Path;
+
+use quick_error::ResultExt;
+
+use crate::{CopyResult, ReflinkMode, SparseMode};
+
+/// Copies `source` to `dest` using copy-on-write if possible.
+pub(crate) fn copy_on_write(
+ source: &Path,
+ dest: &Path,
+ reflink_mode: ReflinkMode,
+ sparse_mode: SparseMode,
+ context: &str,
+) -> CopyResult<()> {
+ if sparse_mode != SparseMode::Auto {
+ return Err("--sparse is only supported on linux".to_string().into());
+ }
+
+ // Extract paths in a form suitable to be passed to a syscall.
+ // The unwrap() is safe because they come from the command-line and so contain non nul
+ // character.
+ let src = CString::new(source.as_os_str().as_bytes()).unwrap();
+ let dst = CString::new(dest.as_os_str().as_bytes()).unwrap();
+
+ // clonefile(2) was introduced in macOS 10.12 so we cannot statically link against it
+ // for backward compatibility.
+ let clonefile = CString::new("clonefile").unwrap();
+ let raw_pfn = unsafe { libc::dlsym(libc::RTLD_NEXT, clonefile.as_ptr()) };
+
+ let mut error = 0;
+ if !raw_pfn.is_null() {
+ // Call clonefile(2).
+ // Safety: Casting a C function pointer to a rust function value is one of the few
+ // blessed uses of `transmute()`.
+ unsafe {
+ let pfn: extern "C" fn(
+ src: *const libc::c_char,
+ dst: *const libc::c_char,
+ flags: u32,
+ ) -> libc::c_int = std::mem::transmute(raw_pfn);
+ error = pfn(src.as_ptr(), dst.as_ptr(), 0);
+ if std::io::Error::last_os_error().kind() == std::io::ErrorKind::AlreadyExists {
+ // clonefile(2) fails if the destination exists. Remove it and try again. Do not
+ // bother to check if removal worked because we're going to try to clone again.
+ let _ = fs::remove_file(dest);
+ error = pfn(src.as_ptr(), dst.as_ptr(), 0);
+ }
+ }
+ }
+
+ if raw_pfn.is_null() || error != 0 {
+ // clonefile(2) is either not supported or it errored out (possibly because the FS does not
+ // support COW).
+ match reflink_mode {
+ ReflinkMode::Always => {
+ return Err(
+ format!("failed to clone {:?} from {:?}: {}", source, dest, error).into(),
+ )
+ }
+ ReflinkMode::Auto => fs::copy(source, dest).context(context)?,
+ ReflinkMode::Never => fs::copy(source, dest).context(context)?,
+ };
+ }
+
+ Ok(())
+}
diff --git a/src/uu/cp/src/platform/mod.rs b/src/uu/cp/src/platform/mod.rs
new file mode 100644
index 00000000000..9dbcefa806f
--- /dev/null
+++ b/src/uu/cp/src/platform/mod.rs
@@ -0,0 +1,18 @@
+// * This file is part of the uutils coreutils package.
+// *
+// * For the full copyright and license information, please view the LICENSE
+// * file that was distributed with this source code.
+#[cfg(target_os = "macos")]
+mod macos;
+#[cfg(target_os = "macos")]
+pub(crate) use self::macos::copy_on_write;
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+mod linux;
+#[cfg(any(target_os = "linux", target_os = "android"))]
+pub(crate) use self::linux::copy_on_write;
+
+#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
+mod other;
+#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
+pub(crate) use self::other::copy_on_write;
diff --git a/src/uu/cp/src/platform/other.rs b/src/uu/cp/src/platform/other.rs
new file mode 100644
index 00000000000..b70da2f2341
--- /dev/null
+++ b/src/uu/cp/src/platform/other.rs
@@ -0,0 +1,33 @@
+// * This file is part of the uutils coreutils package.
+// *
+// * For the full copyright and license information, please view the LICENSE
+// * file that was distributed with this source code.
+// spell-checker:ignore reflink
+use std::fs;
+use std::path::Path;
+
+use quick_error::ResultExt;
+
+use crate::{CopyResult, ReflinkMode, SparseMode};
+
+/// Copies `source` to `dest` for systems without copy-on-write
+pub(crate) fn copy_on_write(
+ source: &Path,
+ dest: &Path,
+ reflink_mode: ReflinkMode,
+ sparse_mode: SparseMode,
+ context: &str,
+) -> CopyResult<()> {
+ if reflink_mode != ReflinkMode::Never {
+ return Err("--reflink is only supported on linux and macOS"
+ .to_string()
+ .into());
+ }
+ if sparse_mode != SparseMode::Auto {
+ return Err("--sparse is only supported on linux".to_string().into());
+ }
+
+ fs::copy(source, dest).context(context)?;
+
+ Ok(())
+}
diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs
index 1be54adcea9..962ce164bbe 100644
--- a/src/uu/ls/src/ls.rs
+++ b/src/uu/ls/src/ls.rs
@@ -14,7 +14,7 @@ use clap::{
builder::{NonEmptyStringValueParser, ValueParser},
crate_version, Arg, Command,
};
-use glob::Pattern;
+use glob::{MatchOptions, Pattern};
use lscolors::LsColors;
use number_prefix::NumberPrefix;
use once_cell::unsync::OnceCell;
@@ -41,6 +41,7 @@ use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use unicode_width::UnicodeWidthStr;
#[cfg(unix)]
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
+use uucore::parse_glob;
use uucore::quoting_style::{escape_name, QuotingStyle};
use uucore::{
display::Quotable,
@@ -765,7 +766,7 @@ impl Config {
.into_iter()
.flatten()
{
- match Pattern::new(pattern) {
+ match parse_glob::from_str(pattern) {
Ok(p) => {
ignore_patterns.push(p);
}
@@ -779,7 +780,7 @@ impl Config {
.into_iter()
.flatten()
{
- match Pattern::new(pattern) {
+ match parse_glob::from_str(pattern) {
Ok(p) => {
ignore_patterns.push(p);
}
@@ -1877,16 +1878,18 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool {
return false;
}
- // check if explicitly ignored
- for pattern in &config.ignore_patterns {
- if pattern.matches(entry.file_name().to_str().unwrap()) {
- return false;
- };
- continue;
- }
-
- // else default to display
- true
+ // check if it is among ignore_patterns
+ let options = MatchOptions {
+ // setting require_literal_leading_dot to match behavior in GNU ls
+ require_literal_leading_dot: true,
+ require_literal_separator: false,
+ case_sensitive: true,
+ };
+ let file_name = entry.file_name().into_string().unwrap();
+ !config
+ .ignore_patterns
+ .iter()
+ .any(|p| p.matches_with(&file_name, options))
}
fn enter_directory(
diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs
index 6f68caeb41f..95ae5d40f33 100644
--- a/src/uu/split/src/filenames.rs
+++ b/src/uu/split/src/filenames.rs
@@ -28,6 +28,7 @@
use crate::number::DynamicWidthNumber;
use crate::number::FixedWidthNumber;
use crate::number::Number;
+use uucore::error::{UResult, USimpleError};
/// The format to use for suffixes in the filename for each output chunk.
#[derive(Clone, Copy)]
@@ -119,19 +120,28 @@ impl<'a> FilenameIterator<'a> {
additional_suffix: &'a str,
suffix_length: usize,
suffix_type: SuffixType,
- ) -> FilenameIterator<'a> {
+ suffix_start: usize,
+ ) -> UResult> {
let radix = suffix_type.radix();
let number = if suffix_length == 0 {
- Number::DynamicWidth(DynamicWidthNumber::new(radix))
+ Number::DynamicWidth(DynamicWidthNumber::new(radix, suffix_start))
} else {
- Number::FixedWidth(FixedWidthNumber::new(radix, suffix_length))
+ Number::FixedWidth(
+ FixedWidthNumber::new(radix, suffix_length, suffix_start).map_err(|_| {
+ USimpleError::new(
+ 1,
+ "numerical suffix start value is too large for the suffix length",
+ )
+ })?,
+ )
};
- FilenameIterator {
+
+ Ok(FilenameIterator {
prefix,
additional_suffix,
number,
first_iteration: true,
- }
+ })
}
}
@@ -161,36 +171,36 @@ mod tests {
#[test]
fn test_filename_iterator_alphabetic_fixed_width() {
- let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic);
+ let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0).unwrap();
assert_eq!(it.next().unwrap(), "chunk_aa.txt");
assert_eq!(it.next().unwrap(), "chunk_ab.txt");
assert_eq!(it.next().unwrap(), "chunk_ac.txt");
- let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic);
+ let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0).unwrap();
assert_eq!(it.nth(26 * 26 - 1).unwrap(), "chunk_zz.txt");
assert_eq!(it.next(), None);
}
#[test]
fn test_filename_iterator_numeric_fixed_width() {
- let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal);
+ let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0).unwrap();
assert_eq!(it.next().unwrap(), "chunk_00.txt");
assert_eq!(it.next().unwrap(), "chunk_01.txt");
assert_eq!(it.next().unwrap(), "chunk_02.txt");
- let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal);
+ let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0).unwrap();
assert_eq!(it.nth(10 * 10 - 1).unwrap(), "chunk_99.txt");
assert_eq!(it.next(), None);
}
#[test]
fn test_filename_iterator_alphabetic_dynamic_width() {
- let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic);
+ let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic, 0).unwrap();
assert_eq!(it.next().unwrap(), "chunk_aa.txt");
assert_eq!(it.next().unwrap(), "chunk_ab.txt");
assert_eq!(it.next().unwrap(), "chunk_ac.txt");
- let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic);
+ let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic, 0).unwrap();
assert_eq!(it.nth(26 * 25 - 1).unwrap(), "chunk_yz.txt");
assert_eq!(it.next().unwrap(), "chunk_zaaa.txt");
assert_eq!(it.next().unwrap(), "chunk_zaab.txt");
@@ -198,14 +208,49 @@ mod tests {
#[test]
fn test_filename_iterator_numeric_dynamic_width() {
- let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal);
+ let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 0).unwrap();
assert_eq!(it.next().unwrap(), "chunk_00.txt");
assert_eq!(it.next().unwrap(), "chunk_01.txt");
assert_eq!(it.next().unwrap(), "chunk_02.txt");
- let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal);
+ let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 0).unwrap();
assert_eq!(it.nth(10 * 9 - 1).unwrap(), "chunk_89.txt");
assert_eq!(it.next().unwrap(), "chunk_9000.txt");
assert_eq!(it.next().unwrap(), "chunk_9001.txt");
}
+
+ #[test]
+ fn test_filename_iterator_numeric_suffix_decimal() {
+ let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 5).unwrap();
+ assert_eq!(it.next().unwrap(), "chunk_05.txt");
+ assert_eq!(it.next().unwrap(), "chunk_06.txt");
+ assert_eq!(it.next().unwrap(), "chunk_07.txt");
+ }
+
+ #[test]
+ fn test_filename_iterator_numeric_suffix_hex() {
+ let mut it =
+ FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Hexadecimal, 9).unwrap();
+ assert_eq!(it.next().unwrap(), "chunk_09.txt");
+ assert_eq!(it.next().unwrap(), "chunk_0a.txt");
+ assert_eq!(it.next().unwrap(), "chunk_0b.txt");
+ }
+
+ #[test]
+ fn test_filename_iterator_numeric_suffix_err() {
+ let mut it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 999).unwrap();
+ assert_eq!(it.next().unwrap(), "chunk_999.txt");
+ assert!(it.next().is_none());
+
+ let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 1000);
+ assert!(it.is_err());
+
+ let mut it =
+ FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0xfff).unwrap();
+ assert_eq!(it.next().unwrap(), "chunk_fff.txt");
+ assert!(it.next().is_none());
+
+ let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0x1000);
+ assert!(it.is_err());
+ }
}
diff --git a/src/uu/split/src/number.rs b/src/uu/split/src/number.rs
index c7557271db6..4605d6fb0d7 100644
--- a/src/uu/split/src/number.rs
+++ b/src/uu/split/src/number.rs
@@ -100,10 +100,10 @@ impl Number {
/// differently and we only intend to use these numbers for display
/// purposes and not for mathematical purposes.
#[allow(dead_code)]
- fn digits(&self) -> &Vec {
+ fn digits(&self) -> Vec {
match self {
- Self::FixedWidth(number) => &number.digits,
- Self::DynamicWidth(number) => &number.digits,
+ Self::FixedWidth(number) => number.digits.clone(),
+ Self::DynamicWidth(number) => number.digits(),
}
}
@@ -175,7 +175,7 @@ impl Display for Number {
///
/// # Displaying
///
-/// This number is only displayable if `radix` is 10, 26, or 26. If
+/// This number is only displayable if `radix` is 10, 16, or 26. If
/// `radix` is 10 or 16, then the digits are concatenated and
/// displayed as a fixed-width decimal or hexadecimal number,
/// respectively. If `radix` is 26, then each digit is translated to
@@ -189,10 +189,21 @@ pub struct FixedWidthNumber {
impl FixedWidthNumber {
/// Instantiate a number of the given radix and width.
- pub fn new(radix: u8, width: usize) -> Self {
- Self {
- radix,
- digits: vec![0; width],
+ pub fn new(radix: u8, width: usize, mut suffix_start: usize) -> Result {
+ let mut digits = vec![0_u8; width];
+
+ for i in (0..digits.len()).rev() {
+ let remainder = (suffix_start % (radix as usize)) as u8;
+ suffix_start /= radix as usize;
+ digits[i] = remainder;
+ if suffix_start == 0 {
+ break;
+ }
+ }
+ if suffix_start != 0 {
+ Err(Overflow)
+ } else {
+ Ok(Self { radix, digits })
}
}
@@ -229,25 +240,12 @@ impl FixedWidthNumber {
impl Display for FixedWidthNumber {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self.radix {
- 10 => {
- let digits: String = self.digits.iter().map(|d| (b'0' + d) as char).collect();
- write!(f, "{}", digits)
- }
- 16 => {
- let digits: String = self
- .digits
- .iter()
- .map(|d| (if *d < 10 { b'0' + d } else { b'a' + (d - 10) }) as char)
- .collect();
- write!(f, "{}", digits)
- }
- 26 => {
- let digits: String = self.digits.iter().map(|d| (b'a' + d) as char).collect();
- write!(f, "{}", digits)
- }
- _ => Err(fmt::Error),
- }
+ let digits: String = self
+ .digits
+ .iter()
+ .map(|d| map_digit(self.radix, *d))
+ .collect();
+ write!(f, "{}", digits)
}
}
@@ -293,105 +291,74 @@ impl Display for FixedWidthNumber {
#[derive(Clone)]
pub struct DynamicWidthNumber {
radix: u8,
- digits: Vec,
+ current: usize,
}
impl DynamicWidthNumber {
- /// Instantiate a number of the given radix, starting with width 2.
- ///
- /// This associated function returns a new instance of the struct
- /// with the given radix and a width of two digits, both 0.
- pub fn new(radix: u8) -> Self {
+ pub fn new(radix: u8, suffix_start: usize) -> Self {
Self {
radix,
- digits: vec![0, 0],
- }
- }
-
- /// Set all digits to zero.
- fn reset(&mut self) {
- for i in 0..self.digits.len() {
- self.digits[i] = 0;
+ current: suffix_start,
}
}
- /// Increment this number.
- ///
- /// This method adds one to this number. The first time that the
- /// most significant digit would achieve its highest possible
- /// value (that is, `radix - 1`), then all the digits get reset to
- /// 0 and the number of digits increases by one.
- ///
- /// This method never returns an error.
fn increment(&mut self) -> Result<(), Overflow> {
- for i in (0..self.digits.len()).rev() {
- // Increment the current digit.
- self.digits[i] += 1;
+ self.current += 1;
+ Ok(())
+ }
- // If the digit overflows, then set it to 0 and continue
- // to the next iteration to increment the next most
- // significant digit. Otherwise, terminate the loop, since
- // there will be no further changes to any higher order
- // digits.
- if self.digits[i] == self.radix {
- self.digits[i] = 0;
- } else {
- break;
- }
+ fn digits(&self) -> Vec {
+ let radix = self.radix as usize;
+ let mut remaining = self.current;
+ let mut sub_value = (radix - 1) * radix;
+ let mut num_fill_chars = 2;
+
+ // Convert the number into "num_fill_chars" and "remaining"
+ while remaining >= sub_value {
+ remaining -= sub_value;
+ sub_value *= radix;
+ num_fill_chars += 1;
}
- // If the most significant digit is at its maximum value, then
- // add another digit and reset all digits zero.
- if self.digits[0] == self.radix - 1 {
- self.digits.push(0);
- self.reset();
+ // Convert the "remainder" to digits
+ let mut digits = Vec::new();
+ while remaining > 0 {
+ digits.push((remaining % radix) as u8);
+ remaining /= radix;
}
- Ok(())
+ // Left pad the vec
+ digits.resize(num_fill_chars, 0);
+ digits.reverse();
+ digits
}
}
-impl Display for DynamicWidthNumber {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self.radix {
- 10 => {
- let num_fill_chars = self.digits.len() - 2;
- let digits: String = self.digits.iter().map(|d| (b'0' + d) as char).collect();
- write!(
- f,
- "{empty:9 {
- let num_fill_chars = self.digits.len() - 2;
- let digits: String = self
- .digits
- .iter()
- .map(|d| (if *d < 10 { b'0' + d } else { b'a' + (d - 10) }) as char)
- .collect();
- write!(
- f,
- "{empty:f {
- let num_fill_chars = self.digits.len() - 2;
- let digits: String = self.digits.iter().map(|d| (b'a' + d) as char).collect();
- write!(
- f,
- "{empty:z char {
+ (match radix {
+ 10 => b'0' + d,
+ 16 => {
+ if d < 10 {
+ b'0' + d
+ } else {
+ b'a' + (d - 10)
}
- _ => Err(fmt::Error),
}
+ 26 => b'a' + d,
+ _ => 0,
+ }) as char
+}
+
+impl Display for DynamicWidthNumber {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ let digits: String = self
+ .digits()
+ .iter()
+ .map(|d| map_digit(self.radix, *d))
+ .collect();
+ let fill: String = (0..digits.len() - 2)
+ .map(|_| map_digit(self.radix, self.radix - 1))
+ .collect();
+ write!(f, "{fill}{digits}")
}
}
@@ -404,35 +371,36 @@ mod tests {
#[test]
fn test_dynamic_width_number_increment() {
- let mut n = Number::DynamicWidth(DynamicWidthNumber::new(3));
- assert_eq!(n.digits(), &vec![0, 0]);
+ println!("Here");
+ let mut n = Number::DynamicWidth(DynamicWidthNumber::new(3, 0));
+ assert_eq!(n.digits(), vec![0, 0]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![0, 1]);
+ assert_eq!(n.digits(), vec![0, 1]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![0, 2]);
+ assert_eq!(n.digits(), vec![0, 2]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![1, 0]);
+ assert_eq!(n.digits(), vec![1, 0]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![1, 1]);
+ assert_eq!(n.digits(), vec![1, 1]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![1, 2]);
+ assert_eq!(n.digits(), vec![1, 2]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![0, 0, 0]);
+ assert_eq!(n.digits(), vec![0, 0, 0]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![0, 0, 1]);
+ assert_eq!(n.digits(), vec![0, 0, 1]);
}
#[test]
fn test_dynamic_width_number_display_alphabetic() {
fn num(n: usize) -> Number {
- let mut number = Number::DynamicWidth(DynamicWidthNumber::new(26));
+ let mut number = Number::DynamicWidth(DynamicWidthNumber::new(26, 0));
for _ in 0..n {
number.increment().unwrap();
}
@@ -456,7 +424,7 @@ mod tests {
#[test]
fn test_dynamic_width_number_display_numeric_decimal() {
fn num(n: usize) -> Number {
- let mut number = Number::DynamicWidth(DynamicWidthNumber::new(10));
+ let mut number = Number::DynamicWidth(DynamicWidthNumber::new(10, 0));
for _ in 0..n {
number.increment().unwrap();
}
@@ -477,7 +445,7 @@ mod tests {
#[test]
fn test_dynamic_width_number_display_numeric_hexadecimal() {
fn num(n: usize) -> Number {
- let mut number = Number::DynamicWidth(DynamicWidthNumber::new(16));
+ let mut number = Number::DynamicWidth(DynamicWidthNumber::new(16, 0));
for _ in 0..n {
number.increment().unwrap();
}
@@ -500,31 +468,31 @@ mod tests {
#[test]
fn test_fixed_width_number_increment() {
- let mut n = Number::FixedWidth(FixedWidthNumber::new(3, 2));
- assert_eq!(n.digits(), &vec![0, 0]);
+ let mut n = Number::FixedWidth(FixedWidthNumber::new(3, 2, 0).unwrap());
+ assert_eq!(n.digits(), vec![0, 0]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![0, 1]);
+ assert_eq!(n.digits(), vec![0, 1]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![0, 2]);
+ assert_eq!(n.digits(), vec![0, 2]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![1, 0]);
+ assert_eq!(n.digits(), vec![1, 0]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![1, 1]);
+ assert_eq!(n.digits(), vec![1, 1]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![1, 2]);
+ assert_eq!(n.digits(), vec![1, 2]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![2, 0]);
+ assert_eq!(n.digits(), vec![2, 0]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![2, 1]);
+ assert_eq!(n.digits(), vec![2, 1]);
n.increment().unwrap();
- assert_eq!(n.digits(), &vec![2, 2]);
+ assert_eq!(n.digits(), vec![2, 2]);
assert!(n.increment().is_err());
}
#[test]
fn test_fixed_width_number_display_alphabetic() {
fn num(n: usize) -> Result {
- let mut number = Number::FixedWidth(FixedWidthNumber::new(26, 2));
+ let mut number = Number::FixedWidth(FixedWidthNumber::new(26, 2, 0).unwrap());
for _ in 0..n {
number.increment()?;
}
@@ -549,7 +517,7 @@ mod tests {
#[test]
fn test_fixed_width_number_display_numeric_decimal() {
fn num(n: usize) -> Result {
- let mut number = Number::FixedWidth(FixedWidthNumber::new(10, 2));
+ let mut number = Number::FixedWidth(FixedWidthNumber::new(10, 2, 0).unwrap());
for _ in 0..n {
number.increment()?;
}
@@ -568,7 +536,7 @@ mod tests {
#[test]
fn test_fixed_width_number_display_numeric_hexadecimal() {
fn num(n: usize) -> Result {
- let mut number = Number::FixedWidth(FixedWidthNumber::new(16, 2));
+ let mut number = Number::FixedWidth(FixedWidthNumber::new(16, 2, 0).unwrap());
for _ in 0..n {
number.increment()?;
}
@@ -583,4 +551,32 @@ mod tests {
assert_eq!(format!("{}", num(16 * 16 - 1).unwrap()), "ff");
assert!(num(16 * 16).is_err());
}
+
+ #[test]
+ fn test_fixed_width_number_start_suffix() {
+ fn num(n: usize) -> Result {
+ let mut number = Number::FixedWidth(FixedWidthNumber::new(16, 2, 0x14)?);
+ for _ in 0..n {
+ number.increment()?;
+ }
+ Ok(number)
+ }
+
+ assert_eq!(format!("{}", num(0).unwrap()), "14");
+ assert_eq!(format!("{}", num(0xf).unwrap()), "23");
+ }
+
+ #[test]
+ fn test_dynamic_width_number_start_suffix() {
+ fn num(n: usize) -> Result {
+ let mut number = Number::DynamicWidth(DynamicWidthNumber::new(10, 8));
+ for _ in 0..n {
+ number.increment()?;
+ }
+ Ok(number)
+ }
+
+ assert_eq!(format!("{}", num(0).unwrap()), "08");
+ assert_eq!(format!("{}", num(8).unwrap()), "16");
+ }
}
diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs
index 1224c83ec9d..2406ba8b718 100644
--- a/src/uu/split/src/split.rs
+++ b/src/uu/split/src/split.rs
@@ -144,7 +144,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.takes_value(true)
.value_name("N")
.default_value(OPT_DEFAULT_SUFFIX_LENGTH)
- .help("use suffixes of length N (default 2)"),
+ .help("use suffixes of fixed length N. 0 implies dynamic length."),
)
.arg(
Arg::new(OPT_HEX_SUFFIXES)
@@ -400,13 +400,23 @@ impl Strategy {
}
/// Parse the suffix type from the command-line arguments.
-fn suffix_type_from(matches: &ArgMatches) -> SuffixType {
+fn suffix_type_from(matches: &ArgMatches) -> Result<(SuffixType, usize), SettingsError> {
if matches.value_source(OPT_NUMERIC_SUFFIXES) == Some(ValueSource::CommandLine) {
- SuffixType::Decimal
+ let suffix_start = matches.value_of(OPT_NUMERIC_SUFFIXES);
+ let suffix_start = suffix_start.ok_or(SettingsError::SuffixNotParsable(String::new()))?;
+ let suffix_start = suffix_start
+ .parse()
+ .map_err(|_| SettingsError::SuffixNotParsable(suffix_start.to_string()))?;
+ Ok((SuffixType::Decimal, suffix_start))
} else if matches.value_source(OPT_HEX_SUFFIXES) == Some(ValueSource::CommandLine) {
- SuffixType::Hexadecimal
+ let suffix_start = matches.value_of(OPT_HEX_SUFFIXES);
+ let suffix_start = suffix_start.ok_or(SettingsError::SuffixNotParsable(String::new()))?;
+ let suffix_start = usize::from_str_radix(suffix_start, 16)
+ .map_err(|_| SettingsError::SuffixNotParsable(suffix_start.to_string()))?;
+ Ok((SuffixType::Hexadecimal, suffix_start))
} else {
- SuffixType::Alphabetic
+ // no numeric/hex suffix
+ Ok((SuffixType::Alphabetic, 0))
}
}
@@ -418,6 +428,7 @@ struct Settings {
prefix: String,
suffix_type: SuffixType,
suffix_length: usize,
+ suffix_start: usize,
additional_suffix: String,
input: String,
/// When supplied, a shell command to output to instead of xaa, xab …
@@ -497,7 +508,7 @@ impl Settings {
return Err(SettingsError::SuffixContainsSeparator(additional_suffix));
}
let strategy = Strategy::from(matches).map_err(SettingsError::Strategy)?;
- let suffix_type = suffix_type_from(matches);
+ let (suffix_type, suffix_start) = suffix_type_from(matches)?;
let suffix_length_str = matches.get_one::(OPT_SUFFIX_LENGTH).unwrap();
let suffix_length: usize = suffix_length_str
.parse()
@@ -517,6 +528,7 @@ impl Settings {
.parse()
.map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?,
suffix_type,
+ suffix_start,
additional_suffix,
verbose: matches.value_source("verbose") == Some(ValueSource::CommandLine),
strategy,
@@ -589,7 +601,8 @@ impl<'a> ByteChunkWriter<'a> {
&settings.additional_suffix,
settings.suffix_length,
settings.suffix_type,
- );
+ settings.suffix_start,
+ )?;
let filename = filename_iterator
.next()
.ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?;
@@ -717,7 +730,8 @@ impl<'a> LineChunkWriter<'a> {
&settings.additional_suffix,
settings.suffix_length,
settings.suffix_type,
- );
+ settings.suffix_start,
+ )?;
let filename = filename_iterator
.next()
.ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?;
@@ -825,7 +839,8 @@ impl<'a> LineBytesChunkWriter<'a> {
&settings.additional_suffix,
settings.suffix_length,
settings.suffix_type,
- );
+ settings.suffix_start,
+ )?;
let filename = filename_iterator
.next()
.ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?;
@@ -1022,7 +1037,8 @@ where
&settings.additional_suffix,
settings.suffix_length,
settings.suffix_type,
- );
+ settings.suffix_start,
+ )?;
// Create one writer for each chunk. This will create each
// of the underlying files (if not in `--filter` mode).
@@ -1098,7 +1114,8 @@ where
&settings.additional_suffix,
settings.suffix_length,
settings.suffix_type,
- );
+ settings.suffix_start,
+ )?;
// Create one writer for each chunk. This will create each
// of the underlying files (if not in `--filter` mode).
diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml
index b5a9e14be5a..0d804e85bc1 100644
--- a/src/uu/sync/Cargo.toml
+++ b/src/uu/sync/Cargo.toml
@@ -19,6 +19,9 @@ clap = { version = "3.2", features = ["wrap_help", "cargo"] }
libc = "0.2.132"
uucore = { version=">=0.0.15", package="uucore", path="../../uucore", features=["wide"] }
+[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
+nix = "0.25"
+
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "std", "winbase", "winerror"] }
diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs
index 5bd21bb1b2c..5b08f45cbab 100644
--- a/src/uu/sync/src/sync.rs
+++ b/src/uu/sync/src/sync.rs
@@ -10,6 +10,12 @@
extern crate libc;
use clap::{crate_version, Arg, Command};
+#[cfg(any(target_os = "linux", target_os = "android"))]
+use nix::errno::Errno;
+#[cfg(any(target_os = "linux", target_os = "android"))]
+use nix::fcntl::{open, OFlag};
+#[cfg(any(target_os = "linux", target_os = "android"))]
+use nix::sys::stat::Mode;
use std::path::Path;
use uucore::display::Quotable;
use uucore::error::{UResult, USimpleError};
@@ -169,12 +175,45 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
+ if matches.is_present(options::DATA) && files.is_empty() {
+ return Err(USimpleError::new(1, "--data needs at least one argument"));
+ }
+
for f in &files {
- if !Path::new(&f).exists() {
- return Err(USimpleError::new(
- 1,
- format!("cannot stat {}: No such file or directory", f.quote()),
- ));
+ // Use the Nix open to be able to set the NONBLOCK flags for fifo files
+ #[cfg(any(target_os = "linux", target_os = "android"))]
+ {
+ match open(Path::new(&f), OFlag::O_NONBLOCK, Mode::empty()) {
+ Ok(_) => {}
+ Err(e) => {
+ if e == Errno::ENOENT {
+ return Err(USimpleError::new(
+ 1,
+ format!("cannot stat {}: No such file or directory", f.quote()),
+ ));
+ }
+ if e == Errno::EACCES {
+ if Path::new(&f).is_dir() {
+ return Err(USimpleError::new(
+ 1,
+ format!("error opening {}: Permission denied", f.quote()),
+ ));
+ } else {
+ // ignore the issue
+ // ./target/debug/coreutils sync --data file
+ }
+ }
+ }
+ }
+ }
+ #[cfg(not(any(target_os = "linux", target_os = "android")))]
+ {
+ if !Path::new(&f).exists() {
+ return Err(USimpleError::new(
+ 1,
+ format!("cannot stat {}: No such file or directory", f.quote()),
+ ));
+ }
}
}
diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs
index e083464d64e..a2a88a20ac3 100644
--- a/tests/by-util/test_ls.rs
+++ b/tests/by-util/test_ls.rs
@@ -2681,6 +2681,73 @@ fn test_ls_ignore_backups() {
.stdout_does_not_contain(".somehiddenbackup~");
}
+// This test fails on windows, see details at #3985
+#[cfg(not(windows))]
+#[test]
+fn test_ls_ignore_explicit_period() {
+ // In ls ignore patterns, leading periods must be explicitly specified
+ let scene = TestScenario::new(util_name!());
+
+ let at = &scene.fixtures;
+ at.touch(".hidden.yml");
+ at.touch("regular.yml");
+
+ scene
+ .ucmd()
+ .arg("-a")
+ .arg("--ignore")
+ .arg("?hidden.yml")
+ .succeeds()
+ .stdout_contains(".hidden.yml")
+ .stdout_contains("regular.yml");
+
+ scene
+ .ucmd()
+ .arg("-a")
+ .arg("--ignore")
+ .arg("*.yml")
+ .succeeds()
+ .stdout_contains(".hidden.yml")
+ .stdout_does_not_contain("regular.yml");
+
+ // Leading period is explicitly specified
+ scene
+ .ucmd()
+ .arg("-a")
+ .arg("--ignore")
+ .arg(".*.yml")
+ .succeeds()
+ .stdout_does_not_contain(".hidden.yml")
+ .stdout_contains("regular.yml");
+}
+
+// This test fails on windows, see details at #3985
+#[cfg(not(windows))]
+#[test]
+fn test_ls_ignore_negation() {
+ let scene = TestScenario::new(util_name!());
+
+ let at = &scene.fixtures;
+ at.touch("apple");
+ at.touch("boy");
+
+ scene
+ .ucmd()
+ .arg("--ignore")
+ .arg("[!a]*")
+ .succeeds()
+ .stdout_contains("apple")
+ .stdout_does_not_contain("boy");
+
+ scene
+ .ucmd()
+ .arg("--ignore")
+ .arg("[^a]*")
+ .succeeds()
+ .stdout_contains("apple")
+ .stdout_does_not_contain("boy");
+}
+
#[test]
fn test_ls_directory() {
let scene = TestScenario::new(util_name!());
diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs
index c57e6bd6ec7..2527f4562db 100644
--- a/tests/by-util/test_split.rs
+++ b/tests/by-util/test_split.rs
@@ -701,3 +701,29 @@ fn test_multiple_of_input_chunk() {
}
assert_eq!(glob.collate(), at.read_bytes(name));
}
+
+#[test]
+fn test_numeric_suffix() {
+ let (at, mut ucmd) = at_and_ucmd!();
+ ucmd.args(&["-n", "4", "--numeric-suffixes", "9", "threebytes.txt"])
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
+ assert_eq!(at.read("x09"), "a");
+ assert_eq!(at.read("x10"), "b");
+ assert_eq!(at.read("x11"), "c");
+ assert_eq!(at.read("x12"), "");
+}
+
+#[test]
+fn test_hex_suffix() {
+ let (at, mut ucmd) = at_and_ucmd!();
+ ucmd.args(&["-n", "4", "--hex-suffixes", "9", "threebytes.txt"])
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
+ assert_eq!(at.read("x09"), "a");
+ assert_eq!(at.read("x0a"), "b");
+ assert_eq!(at.read("x0b"), "c");
+ assert_eq!(at.read("x0c"), "");
+}
diff --git a/tests/by-util/test_sync.rs b/tests/by-util/test_sync.rs
index 97259cddaef..7f2cd4b661e 100644
--- a/tests/by-util/test_sync.rs
+++ b/tests/by-util/test_sync.rs
@@ -44,3 +44,40 @@ fn test_sync_no_existing_files() {
.fails()
.stderr_contains("cannot stat");
}
+
+#[test]
+fn test_sync_data_but_not_file() {
+ new_ucmd!()
+ .arg("--data")
+ .fails()
+ .stderr_contains("sync: --data needs at least one argument");
+}
+
+#[cfg(any(target_os = "linux", target_os = "android"))]
+#[cfg(feature = "chmod")]
+#[test]
+fn test_sync_no_permission_dir() {
+ let ts = TestScenario::new(util_name!());
+ let at = &ts.fixtures;
+ let dir = "foo";
+ at.mkdir_all(dir);
+
+ ts.ccmd("chmod").arg("0").arg(dir).succeeds();
+ let result = ts.ucmd().arg("--data").arg(dir).fails();
+ result.stderr_contains("sync: error opening 'foo': Permission denied");
+ let result = ts.ucmd().arg(dir).fails();
+ result.stderr_contains("sync: error opening 'foo': Permission denied");
+}
+
+#[cfg(not(target_os = "windows"))]
+#[cfg(feature = "chmod")]
+#[test]
+fn test_sync_no_permission_file() {
+ let ts = TestScenario::new(util_name!());
+ let at = &ts.fixtures;
+ let f = "file";
+ at.touch(f);
+
+ ts.ccmd("chmod").arg("0200").arg(f).succeeds();
+ ts.ucmd().arg(f).succeeds();
+}
diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs
index 5460df554a1..98089f906fa 100644
--- a/tests/by-util/test_tail.rs
+++ b/tests/by-util/test_tail.rs
@@ -11,24 +11,23 @@ extern crate tail;
use crate::common::random::*;
use crate::common::util::*;
+use rand::distributions::Alphanumeric;
use std::char::from_digit;
-#[cfg(unix)]
use std::io::Read;
use std::io::Write;
use std::process::Stdio;
-#[cfg(unix)]
use std::thread::sleep;
-#[cfg(unix)]
use std::time::Duration;
+use tail::chunks::BUFFER_SIZE as CHUNK_BUFFER_SIZE;
static FOOBAR_TXT: &str = "foobar.txt";
static FOOBAR_2_TXT: &str = "foobar2.txt";
static FOOBAR_WITH_NULL_TXT: &str = "foobar_with_null.txt";
-#[cfg(unix)]
+#[allow(dead_code)]
static FOLLOW_NAME_TXT: &str = "follow_name.txt";
-#[cfg(unix)]
+#[allow(dead_code)]
static FOLLOW_NAME_SHORT_EXP: &str = "follow_name_short.expected";
-#[cfg(target_os = "linux")]
+#[allow(dead_code)]
static FOLLOW_NAME_EXP: &str = "follow_name.expected";
#[test]
@@ -37,7 +36,6 @@ fn test_invalid_arg() {
}
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_stdin_default() {
new_ucmd!()
.pipe_in_fixture(FOOBAR_TXT)
@@ -47,7 +45,6 @@ fn test_stdin_default() {
}
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_stdin_explicit() {
new_ucmd!()
.pipe_in_fixture(FOOBAR_TXT)
@@ -58,7 +55,7 @@ fn test_stdin_explicit() {
}
#[test]
-#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
+#[cfg(not(target_vendor = "apple"))] // FIXME: for currently not working platforms
fn test_stdin_redirect_file() {
// $ echo foo > f
@@ -93,7 +90,12 @@ fn test_stdin_redirect_file() {
.run_no_wait();
sleep(Duration::from_millis(500));
- p.kill().unwrap();
+
+ // Cleanup the process if it is still running. The result isn't important
+ // for the test, so it is ignored.
+ // NOTE: The result may be Error on windows with an Os error `Permission
+ // Denied` if the process already terminated:
+ let _ = p.kill();
let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p);
assert!(buf_stdout.eq("foo"));
@@ -101,7 +103,7 @@ fn test_stdin_redirect_file() {
}
#[test]
-#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
+#[cfg(not(target_vendor = "apple"))] // FIXME: for currently not working platforms
fn test_stdin_redirect_offset() {
// inspired by: "gnu/tests/tail-2/start-middle.sh"
use std::io::{Seek, SeekFrom};
@@ -122,8 +124,12 @@ fn test_stdin_redirect_offset() {
}
#[test]
-#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
+#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] // FIXME: for currently not working platforms
fn test_stdin_redirect_offset2() {
+ // FIXME: windows: Failed because of difference in printed header. See below.
+ // actual : ==> - <==
+ // expected: ==> standard input <==
+
// like test_stdin_redirect_offset but with multiple files
use std::io::{Seek, SeekFrom};
@@ -197,8 +203,9 @@ fn test_nc_0_wo_follow2() {
.succeeded();
}
+// TODO: Add similar test for windows
#[test]
-#[cfg(all(unix, not(target_os = "freebsd")))]
+#[cfg(unix)]
fn test_permission_denied() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
@@ -217,8 +224,9 @@ fn test_permission_denied() {
.code_is(1);
}
+// TODO: Add similar test for windows
#[test]
-#[cfg(all(unix, not(target_os = "freebsd")))]
+#[cfg(unix)]
fn test_permission_denied_multiple() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
@@ -241,7 +249,6 @@ fn test_permission_denied_multiple() {
}
#[test]
-#[cfg(target_os = "linux")]
fn test_follow_redirect_stdin_name_retry() {
// $ touch f && tail -F - < f
// tail: cannot follow '-' by name
@@ -265,8 +272,12 @@ fn test_follow_redirect_stdin_name_retry() {
}
#[test]
-#[cfg(not(target_os = "macos"))] // See test_stdin_redirect_dir_when_target_os_is_macos
-#[cfg(all(unix, not(any(target_os = "android", target_os = "freebsd"))))] // FIXME: fix this test for Android/FreeBSD
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_stdin_redirect_dir() {
// $ mkdir dir
// $ tail < dir, $ tail - < dir
@@ -299,7 +310,7 @@ fn test_stdin_redirect_dir() {
// redirected directories like linux show the correct message like in
// `test_stdin_redirect_dir`
#[test]
-#[cfg(target_os = "macos")]
+#[cfg(target_vendor = "apple")]
fn test_stdin_redirect_dir_when_target_os_is_macos() {
// $ mkdir dir
// $ tail < dir, $ tail - < dir
@@ -325,7 +336,6 @@ fn test_stdin_redirect_dir_when_target_os_is_macos() {
}
#[test]
-#[cfg(target_os = "linux")]
fn test_follow_stdin_descriptor() {
let ts = TestScenario::new(util_name!());
@@ -345,7 +355,6 @@ fn test_follow_stdin_descriptor() {
}
#[test]
-#[cfg(target_os = "linux")]
fn test_follow_stdin_name_retry() {
// $ tail -F -
// tail: cannot follow '-' by name
@@ -417,8 +426,6 @@ fn test_follow_stdin_explicit_indefinitely() {
}
#[test]
-#[cfg(target_os = "linux")]
-#[cfg(disable_until_fixed)]
fn test_follow_bad_fd() {
// Provoke a "bad file descriptor" error by closing the fd
// inspired by: "gnu/tests/tail-2/follow-stdin.sh"
@@ -465,7 +472,7 @@ fn test_null_default() {
}
#[test]
-#[cfg(unix)]
+#[cfg(not(target_os = "windows"))] // FIXME: test times out
fn test_follow_single() {
let (at, mut ucmd) = at_and_ucmd!();
@@ -489,7 +496,7 @@ fn test_follow_single() {
/// Test for following when bytes are written that are not valid UTF-8.
#[test]
-#[cfg(unix)]
+#[cfg(not(target_os = "windows"))] // FIXME: test times out
fn test_follow_non_utf8_bytes() {
// Tail the test file and start following it.
let (at, mut ucmd) = at_and_ucmd!();
@@ -521,7 +528,7 @@ fn test_follow_non_utf8_bytes() {
}
#[test]
-#[cfg(unix)]
+#[cfg(not(target_os = "windows"))] // FIXME: test times out
fn test_follow_multiple() {
let (at, mut ucmd) = at_and_ucmd!();
let mut child = ucmd
@@ -547,7 +554,7 @@ fn test_follow_multiple() {
}
#[test]
-#[cfg(unix)]
+#[cfg(not(target_os = "windows"))] // FIXME: test times out
fn test_follow_name_multiple() {
let (at, mut ucmd) = at_and_ucmd!();
let mut child = ucmd
@@ -573,7 +580,6 @@ fn test_follow_name_multiple() {
}
#[test]
-#[cfg(unix)]
fn test_follow_multiple_untailable() {
// $ tail -f DIR1 DIR2
// ==> DIR1 <==
@@ -606,7 +612,6 @@ fn test_follow_multiple_untailable() {
}
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_follow_stdin_pipe() {
new_ucmd!()
.arg("-f")
@@ -617,7 +622,7 @@ fn test_follow_stdin_pipe() {
}
#[test]
-#[cfg(unix)]
+#[cfg(not(target_os = "windows"))] // FIXME: for currently not working platforms
fn test_follow_invalid_pid() {
new_ucmd!()
.args(&["-f", "--pid=-1234"])
@@ -641,8 +646,12 @@ fn test_follow_invalid_pid() {
}
// FixME: test PASSES for usual windows builds, but fails for coverage testing builds (likely related to the specific RUSTFLAGS '-Zpanic_abort_tests -Cpanic=abort') This test also breaks tty settings under bash requiring a 'stty sane' or reset. // spell-checker:disable-line
-#[cfg(disable_until_fixed)]
#[test]
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android")
+))] // FIXME: for currently not working platforms
fn test_follow_with_pid() {
use std::process::{Command, Stdio};
use std::thread::sleep;
@@ -652,6 +661,7 @@ fn test_follow_with_pid() {
#[cfg(unix)]
let dummy_cmd = "sh";
+
#[cfg(windows)]
let dummy_cmd = "cmd";
@@ -687,13 +697,18 @@ fn test_follow_with_pid() {
let third_append = "should\nbe\nignored\n";
at.append(FOOBAR_TXT, third_append);
- assert_eq!(read_size(&mut child, 1), "\u{0}");
+ let mut buffer: [u8; 1] = [0; 1];
+ let result = child.stdout.as_mut().unwrap().read(&mut buffer);
+ assert!(result.is_ok());
+ assert_eq!(result.unwrap(), 0);
// On Unix, trying to kill a process that's already dead is fine; on Windows it's an error.
- #[cfg(unix)]
- child.kill().unwrap();
- #[cfg(windows)]
- assert_eq!(child.kill().is_err(), true);
+ let result = child.kill();
+ if cfg!(windows) {
+ assert!(result.is_err());
+ } else {
+ assert!(result.is_ok());
+ }
}
#[test]
@@ -735,7 +750,6 @@ fn test_bytes_single() {
}
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_bytes_stdin() {
new_ucmd!()
.pipe_in_fixture(FOOBAR_TXT)
@@ -984,7 +998,6 @@ fn test_sleep_interval() {
/// Test for reading all but the first NUM bytes: `tail -c +3`.
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_positive_bytes() {
new_ucmd!()
.args(&["-c", "+3"])
@@ -995,7 +1008,6 @@ fn test_positive_bytes() {
/// Test for reading all bytes, specified by `tail -c +0`.
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_positive_zero_bytes() {
let ts = TestScenario::new(util_name!());
ts.ucmd()
@@ -1013,7 +1025,6 @@ fn test_positive_zero_bytes() {
/// Test for reading all but the first NUM lines: `tail -n +3`.
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_positive_lines() {
new_ucmd!()
.args(&["-n", "+3"])
@@ -1055,7 +1066,6 @@ once
/// Test for reading all but the first NUM lines: `tail -3`.
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_obsolete_syntax_positive_lines() {
new_ucmd!()
.args(&["-3"])
@@ -1066,7 +1076,6 @@ fn test_obsolete_syntax_positive_lines() {
/// Test for reading all but the first NUM lines: `tail -n -10`.
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_small_file() {
new_ucmd!()
.args(&["-n -10"])
@@ -1077,7 +1086,6 @@ fn test_small_file() {
/// Test for reading all but the first NUM lines: `tail -10`.
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_obsolete_syntax_small_file() {
new_ucmd!()
.args(&["-10"])
@@ -1088,7 +1096,6 @@ fn test_obsolete_syntax_small_file() {
/// Test for reading all lines, specified by `tail -n +0`.
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_positive_zero_lines() {
let ts = TestScenario::new(util_name!());
ts.ucmd()
@@ -1106,7 +1113,6 @@ fn test_positive_zero_lines() {
}
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_invalid_num() {
new_ucmd!()
.args(&["-c", "1024R", "emptyfile.txt"])
@@ -1138,7 +1144,6 @@ fn test_invalid_num() {
}
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_num_with_undocumented_sign_bytes() {
// tail: '-' is not documented (8.32 man pages)
// head: '+' is not documented (8.32 man pages)
@@ -1162,7 +1167,7 @@ fn test_num_with_undocumented_sign_bytes() {
#[test]
#[cfg(unix)]
-fn test_bytes_for_funny_files() {
+fn test_bytes_for_funny_unix_files() {
// inspired by: gnu/tests/tail-2/tail-c.sh
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
@@ -1181,7 +1186,6 @@ fn test_bytes_for_funny_files() {
}
#[test]
-#[cfg(unix)]
fn test_retry1() {
// inspired by: gnu/tests/tail-2/retry.sh
// Ensure --retry without --follow results in a warning.
@@ -1198,7 +1202,6 @@ fn test_retry1() {
}
#[test]
-#[cfg(unix)]
fn test_retry2() {
// inspired by: gnu/tests/tail-2/retry.sh
// The same as test_retry2 with a missing file: expect error message and exit 1.
@@ -1221,7 +1224,12 @@ fn test_retry2() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_retry3() {
// inspired by: gnu/tests/tail-2/retry.sh
// Ensure that `tail --retry --follow=name` waits for the file to appear.
@@ -1259,7 +1267,12 @@ fn test_retry3() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_retry4() {
// inspired by: gnu/tests/tail-2/retry.sh
// Ensure that `tail --retry --follow=descriptor` waits for the file to appear.
@@ -1309,7 +1322,12 @@ fn test_retry4() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_retry5() {
// inspired by: gnu/tests/tail-2/retry.sh
// Ensure that `tail --follow=descriptor --retry` exits when the file appears untailable.
@@ -1345,7 +1363,7 @@ fn test_retry5() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
+#[cfg(not(target_os = "windows"))] // FIXME: for currently not working platforms
fn test_retry6() {
// inspired by: gnu/tests/tail-2/retry.sh
// Ensure that --follow=descriptor (without --retry) does *not* try
@@ -1383,7 +1401,12 @@ fn test_retry6() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_retry7() {
// inspired by: gnu/tests/tail-2/retry.sh
// Ensure that `tail -F` retries when the file is initially untailable.
@@ -1450,7 +1473,12 @@ fn test_retry7() {
}
#[test]
-#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_retry8() {
// Ensure that inotify will switch to polling mode if directory
// of the watched file was initially missing and later created.
@@ -1512,7 +1540,11 @@ fn test_retry8() {
}
#[test]
-#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_retry9() {
// inspired by: gnu/tests/tail-2/inotify-dir-recreate.sh
// Ensure that inotify will switch to polling mode if directory
@@ -1589,7 +1621,11 @@ fn test_retry9() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_follow_descriptor_vs_rename1() {
// inspired by: gnu/tests/tail-2/descriptor-vs-rename.sh
// $ ((rm -f A && touch A && sleep 1 && echo -n "A\n" >> A && sleep 1 && \
@@ -1646,7 +1682,11 @@ fn test_follow_descriptor_vs_rename1() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_follow_descriptor_vs_rename2() {
// Ensure the headers are correct for --verbose.
// NOTE: GNU's tail does not update the header from FILE_A to FILE_C after `mv FILE_A FILE_C`
@@ -1695,7 +1735,12 @@ fn test_follow_descriptor_vs_rename2() {
}
#[test]
-#[cfg(target_os = "linux")]
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_follow_name_retry_headers() {
// inspired by: "gnu/tests/tail-2/F-headers.sh"
// Ensure tail -F distinguishes output with the
@@ -1756,7 +1801,7 @@ fn test_follow_name_retry_headers() {
}
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: make this work not just on Linux
+#[cfg(all(not(target_os = "windows"), not(target_os = "android")))] // FIXME: for currently not working platforms
fn test_follow_name_remove() {
// This test triggers a remove event while `tail --follow=name file` is running.
// ((sleep 2 && rm file &)>/dev/null 2>&1 &) ; tail --follow=name file
@@ -1805,7 +1850,11 @@ fn test_follow_name_remove() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
+#[cfg(all(
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_follow_name_truncate1() {
// This test triggers a truncate event while `tail --follow=name file` is running.
// $ cp file backup && head file > file && sleep 1 && cp backup file
@@ -1839,7 +1888,11 @@ fn test_follow_name_truncate1() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
+#[cfg(all(
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_follow_name_truncate2() {
// This test triggers a truncate event while `tail --follow=name file` is running.
// $ ((sleep 1 && echo -n "x\nx\nx\n" >> file && sleep 1 && \
@@ -1876,7 +1929,7 @@ fn test_follow_name_truncate2() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
+#[cfg(not(target_os = "windows"))] // FIXME: for currently not working platforms
fn test_follow_name_truncate3() {
// Opening an empty file in truncate mode should not trigger a truncate event while
// `tail --follow=name file` is running.
@@ -1907,7 +1960,7 @@ fn test_follow_name_truncate3() {
}
#[test]
-#[cfg(unix)]
+#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] // FIXME: for currently not working platforms
fn test_follow_name_truncate4() {
// Truncating a file with the same content it already has should not trigger a truncate event
@@ -1939,7 +1992,7 @@ fn test_follow_name_truncate4() {
}
#[test]
-#[cfg(all(unix, not(target_os = "android")))]
+#[cfg(not(target_os = "windows"))] // FIXME: for currently not working platforms
fn test_follow_truncate_fast() {
// inspired by: "gnu/tests/tail-2/truncate.sh"
// Ensure all logs are output upon file truncation
@@ -1989,7 +2042,12 @@ fn test_follow_truncate_fast() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: make this work not just on Linux
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_follow_name_move_create1() {
// This test triggers a move/create event while `tail --follow=name file` is running.
// ((sleep 2 && mv file backup && sleep 2 && cp backup file &)>/dev/null 2>&1 &) ; tail --follow=name file
@@ -2002,6 +2060,7 @@ fn test_follow_name_move_create1() {
#[cfg(target_os = "linux")]
let expected_stdout = at.read(FOLLOW_NAME_EXP);
+
#[cfg(target_os = "linux")]
let expected_stderr = format!(
"{}: {}: No such file or directory\n{0}: '{1}' has appeared; following new file\n",
@@ -2009,8 +2068,10 @@ fn test_follow_name_move_create1() {
);
// NOTE: We are less strict if not on Linux (inotify backend).
+
#[cfg(not(target_os = "linux"))]
let expected_stdout = at.read(FOLLOW_NAME_SHORT_EXP);
+
#[cfg(not(target_os = "linux"))]
let expected_stderr = format!("{}: {}: No such file or directory\n", ts.util_name, source);
@@ -2034,7 +2095,11 @@ fn test_follow_name_move_create1() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: make this work not just on Linux
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_follow_name_move_create2() {
// inspired by: "gnu/tests/tail-2/inotify-hash-abuse.sh"
// Exercise an abort-inducing flaw in inotify-enabled tail -F
@@ -2105,7 +2170,12 @@ fn test_follow_name_move_create2() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: make this work not just on Linux
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_follow_name_move1() {
// This test triggers a move event while `tail --follow=name file` is running.
// ((sleep 2 && mv file backup &)>/dev/null 2>&1 &) ; tail --follow=name file
@@ -2150,7 +2220,12 @@ fn test_follow_name_move1() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: make this work not just on Linux
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_follow_name_move2() {
// Like test_follow_name_move1, but move to a name that's already monitored.
@@ -2231,7 +2306,12 @@ fn test_follow_name_move2() {
}
#[test]
-#[cfg(target_os = "linux")] // FIXME: make this work not just on Linux
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_follow_name_move_retry1() {
// Similar to test_follow_name_move1 but with `--retry` (`-F`)
// This test triggers two move/rename events while `tail --follow=name --retry file` is running.
@@ -2281,8 +2361,14 @@ fn test_follow_name_move_retry1() {
delay /= 3;
}
}
+
#[test]
-#[cfg(target_os = "linux")] // FIXME: make this work not just on Linux
+#[cfg(all(
+ not(target_vendor = "apple"),
+ not(target_os = "windows"),
+ not(target_os = "android"),
+ not(target_os = "freebsd")
+))] // FIXME: for currently not working platforms
fn test_follow_name_move_retry2() {
// inspired by: "gnu/tests/tail-2/F-vs-rename.sh"
// Similar to test_follow_name_move2 (move to a name that's already monitored)
@@ -2380,7 +2466,7 @@ fn test_follow_name_move_retry2() {
}
#[test]
-#[cfg(all(unix, not(target_os = "freebsd")))] // FIXME: fix this test for FreeBSD
+#[cfg(not(target_os = "windows"))] // FIXME: for currently not working platforms
fn test_follow_inotify_only_regular() {
// The GNU test inotify-only-regular.sh uses strace to ensure that `tail -f`
// doesn't make inotify syscalls and only uses inotify for regular files or fifos.
@@ -2403,7 +2489,6 @@ fn test_follow_inotify_only_regular() {
assert_eq!(buf_stderr, "".to_string());
}
-#[cfg(unix)]
fn take_stdout_stderr(p: &mut std::process::Child) -> (String, String) {
let mut buf_stdout = String::new();
let mut p_stdout = p.stdout.take().unwrap();
@@ -2426,13 +2511,11 @@ fn test_no_such_file() {
}
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_no_trailing_newline() {
new_ucmd!().pipe_in("x").succeeds().stdout_only("x");
}
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_lines_zero_terminated() {
new_ucmd!()
.args(&["-z", "-n", "2"])
@@ -2447,7 +2530,6 @@ fn test_lines_zero_terminated() {
}
#[test]
-#[cfg(all(unix, not(target_os = "android")))] // FIXME: fix this test for Android
fn test_presume_input_pipe_default() {
new_ucmd!()
.arg("---presume-input-pipe")
@@ -2458,7 +2540,7 @@ fn test_presume_input_pipe_default() {
}
#[test]
-#[cfg(unix)]
+#[cfg(not(windows))]
fn test_fifo() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
@@ -2512,722 +2594,721 @@ fn test_illegal_seek() {
assert_eq!(p.wait().unwrap().code().unwrap(), 1);
}
-#[cfg(all(not(target_os = "android"), not(target_os = "windows")))] // FIXME: See https://github.com/uutils/coreutils/issues/3881
-mod pipe_tests {
- use super::*;
- use rand::distributions::Alphanumeric;
- use tail::chunks::BUFFER_SIZE as CHUNK_BUFFER_SIZE;
-
- #[test]
- fn test_pipe_when_lines_option_value_is_higher_than_contained_lines() {
- let test_string = "a\nb\n";
- new_ucmd!()
- .args(&["-n", "3"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(test_string);
-
- new_ucmd!()
- .args(&["-n", "4"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(test_string);
+#[test]
+fn test_pipe_when_lines_option_value_is_higher_than_contained_lines() {
+ let test_string = "a\nb\n";
+ new_ucmd!()
+ .args(&["-n", "3"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(test_string);
- new_ucmd!()
- .args(&["-n", "999"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(test_string);
+ new_ucmd!()
+ .args(&["-n", "4"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(test_string);
- new_ucmd!()
- .args(&["-n", "+3"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+ new_ucmd!()
+ .args(&["-n", "999"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(test_string);
- new_ucmd!()
- .args(&["-n", "+4"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+ new_ucmd!()
+ .args(&["-n", "+3"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- new_ucmd!()
- .args(&["-n", "+999"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
- }
+ new_ucmd!()
+ .args(&["-n", "+4"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- #[test]
- fn test_pipe_when_negative_lines_option_given_no_newline_at_eof() {
- let test_string = "a\nb";
+ new_ucmd!()
+ .args(&["-n", "+999"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
+}
- new_ucmd!()
- .args(&["-n", "0"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+#[test]
+fn test_pipe_when_negative_lines_option_given_no_newline_at_eof() {
+ let test_string = "a\nb";
- new_ucmd!()
- .args(&["-n", "1"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("b");
+ new_ucmd!()
+ .args(&["-n", "0"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- new_ucmd!()
- .args(&["-n", "2"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("a\nb");
- }
+ new_ucmd!()
+ .args(&["-n", "1"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("b");
- #[test]
- fn test_pipe_when_positive_lines_option_given_no_newline_at_eof() {
- let test_string = "a\nb";
+ new_ucmd!()
+ .args(&["-n", "2"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("a\nb");
+}
- new_ucmd!()
- .args(&["-n", "+0"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("a\nb");
+#[test]
+fn test_pipe_when_positive_lines_option_given_no_newline_at_eof() {
+ let test_string = "a\nb";
- new_ucmd!()
- .args(&["-n", "+1"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("a\nb");
+ new_ucmd!()
+ .args(&["-n", "+0"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("a\nb");
- new_ucmd!()
- .args(&["-n", "+2"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("b");
- }
+ new_ucmd!()
+ .args(&["-n", "+1"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("a\nb");
- #[test]
- fn test_pipe_when_lines_option_given_multibyte_utf8_characters() {
- // the test string consists of from left to right a 4-byte,3-byte,2-byte,1-byte utf-8 character
- let test_string = "𝅘𝅥𝅮\n⏻\nƒ\na";
+ new_ucmd!()
+ .args(&["-n", "+2"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("b");
+}
- new_ucmd!()
- .args(&["-n", "+0"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(test_string);
+#[test]
+fn test_pipe_when_lines_option_given_multibyte_utf8_characters() {
+ // the test string consists of from left to right a 4-byte,3-byte,2-byte,1-byte utf-8 character
+ let test_string = "𝅘𝅥𝅮\n⏻\nƒ\na";
- new_ucmd!()
- .args(&["-n", "+2"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("⏻\nƒ\na");
+ new_ucmd!()
+ .args(&["-n", "+0"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(test_string);
- new_ucmd!()
- .args(&["-n", "+3"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("ƒ\na");
+ new_ucmd!()
+ .args(&["-n", "+2"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("⏻\nƒ\na");
- new_ucmd!()
- .args(&["-n", "+4"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("a");
+ new_ucmd!()
+ .args(&["-n", "+3"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("ƒ\na");
- new_ucmd!()
- .args(&["-n", "+5"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+ new_ucmd!()
+ .args(&["-n", "+4"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("a");
- new_ucmd!()
- .args(&["-n", "-4"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(test_string);
+ new_ucmd!()
+ .args(&["-n", "+5"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- new_ucmd!()
- .args(&["-n", "-3"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("⏻\nƒ\na");
+ new_ucmd!()
+ .args(&["-n", "-4"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(test_string);
- new_ucmd!()
- .args(&["-n", "-2"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("ƒ\na");
+ new_ucmd!()
+ .args(&["-n", "-3"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("⏻\nƒ\na");
- new_ucmd!()
- .args(&["-n", "-1"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("a");
+ new_ucmd!()
+ .args(&["-n", "-2"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("ƒ\na");
- new_ucmd!()
- .args(&["-n", "-0"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
- }
+ new_ucmd!()
+ .args(&["-n", "-1"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("a");
- #[test]
- fn test_pipe_when_lines_option_given_input_size_is_equal_to_buffer_size_no_newline_at_eof() {
- let total_lines = 1;
- let random_string = RandomString::generate_with_delimiter(
- Alphanumeric,
- b'\n',
- total_lines,
- false,
- CHUNK_BUFFER_SIZE,
- );
- let random_string = random_string.as_str();
- let lines = random_string.split_inclusive('\n');
+ new_ucmd!()
+ .args(&["-n", "-0"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
+}
- let expected = lines.clone().skip(1).collect::();
- new_ucmd!()
- .args(&["-n", "+2"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(expected);
+#[test]
+fn test_pipe_when_lines_option_given_input_size_is_equal_to_buffer_size_no_newline_at_eof() {
+ let total_lines = 1;
+ let random_string = RandomString::generate_with_delimiter(
+ Alphanumeric,
+ b'\n',
+ total_lines,
+ false,
+ CHUNK_BUFFER_SIZE,
+ );
+ let random_string = random_string.as_str();
+ let lines = random_string.split_inclusive('\n');
- let expected = lines.clone().skip(1).collect::();
- new_ucmd!()
- .args(&["-n", "-1"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(expected);
- }
+ let expected = lines.clone().skip(1).collect::();
+ new_ucmd!()
+ .args(&["-n", "+2"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(expected);
- #[test]
- fn test_pipe_when_lines_option_given_input_size_is_equal_to_buffer_size() {
- let total_lines = 100;
- let random_string = RandomString::generate_with_delimiter(
- Alphanumeric,
- b'\n',
- total_lines,
- true,
- CHUNK_BUFFER_SIZE,
- );
- let random_string = random_string.as_str();
- let lines = random_string.split_inclusive('\n');
+ let expected = lines.clone().skip(1).collect::();
+ new_ucmd!()
+ .args(&["-n", "-1"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(expected);
+}
- new_ucmd!()
- .args(&["-n", "+0"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(random_string);
+#[test]
+fn test_pipe_when_lines_option_given_input_size_is_equal_to_buffer_size() {
+ let total_lines = 100;
+ let random_string = RandomString::generate_with_delimiter(
+ Alphanumeric,
+ b'\n',
+ total_lines,
+ true,
+ CHUNK_BUFFER_SIZE,
+ );
+ let random_string = random_string.as_str();
+ let lines = random_string.split_inclusive('\n');
- let expected = lines.clone().skip(1).collect::();
- new_ucmd!()
- .args(&["-n", "+2"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(expected);
+ new_ucmd!()
+ .args(&["-n", "+0"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(random_string);
- new_ucmd!()
- .args(&["-n", "-0"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+ let expected = lines.clone().skip(1).collect::();
+ new_ucmd!()
+ .args(&["-n", "+2"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(expected);
- let expected = lines.clone().skip(total_lines - 1).collect::();
- new_ucmd!()
- .args(&["-n", "-1"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(expected);
+ new_ucmd!()
+ .args(&["-n", "-0"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- let expected = lines.clone().skip(1).collect::();
- new_ucmd!()
- .args(&["-n", "-99"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(expected);
+ let expected = lines.clone().skip(total_lines - 1).collect::();
+ new_ucmd!()
+ .args(&["-n", "-1"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(expected);
- new_ucmd!()
- .args(&["-n", "-100"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(random_string);
- }
+ let expected = lines.clone().skip(1).collect::();
+ new_ucmd!()
+ .args(&["-n", "-99"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(expected);
- #[test]
- fn test_pipe_when_lines_option_given_input_size_is_one_byte_greater_than_buffer_size() {
- let total_lines = 100;
- let random_string = RandomString::generate_with_delimiter(
- Alphanumeric,
- b'\n',
- total_lines,
- true,
- CHUNK_BUFFER_SIZE + 1,
- );
- let random_string = random_string.as_str();
- let lines = random_string.split_inclusive('\n');
+ new_ucmd!()
+ .args(&["-n", "-100"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(random_string);
+}
- new_ucmd!()
- .args(&["-n", "+0"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(random_string);
+#[test]
+fn test_pipe_when_lines_option_given_input_size_is_one_byte_greater_than_buffer_size() {
+ let total_lines = 100;
+ let random_string = RandomString::generate_with_delimiter(
+ Alphanumeric,
+ b'\n',
+ total_lines,
+ true,
+ CHUNK_BUFFER_SIZE + 1,
+ );
+ let random_string = random_string.as_str();
+ let lines = random_string.split_inclusive('\n');
- let expected = lines.clone().skip(total_lines - 1).collect::();
- new_ucmd!()
- .args(&["-n", "-1"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(expected);
+ new_ucmd!()
+ .args(&["-n", "+0"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(random_string);
- let expected = lines.clone().skip(1).collect::();
- new_ucmd!()
- .args(&["-n", "+2"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(expected);
+ let expected = lines.clone().skip(total_lines - 1).collect::();
+ new_ucmd!()
+ .args(&["-n", "-1"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(expected);
- let expected = lines.clone().skip(1).collect::();
- new_ucmd!()
- .args(&["-n", "-99"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(expected);
- }
+ let expected = lines.clone().skip(1).collect::();
+ new_ucmd!()
+ .args(&["-n", "+2"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(expected);
- #[test]
- fn test_pipe_when_lines_option_given_input_size_has_multiple_size_of_buffer_size() {
- let total_lines = 100;
- let random_string = RandomString::generate_with_delimiter(
- Alphanumeric,
- b'\n',
- total_lines,
- true,
- CHUNK_BUFFER_SIZE * 3 + 1,
- );
- let random_string = random_string.as_str();
- let lines = random_string.split_inclusive('\n');
+ let expected = lines.clone().skip(1).collect::();
+ new_ucmd!()
+ .args(&["-n", "-99"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(expected);
+}
- new_ucmd!()
- .args(&["-n", "+0"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(random_string);
+// FIXME: windows: this test failed with timeout in the CI. Running this test in
+// a Windows VirtualBox image produces no errors.
+#[test]
+#[cfg(not(target_os = "windows"))]
+fn test_pipe_when_lines_option_given_input_size_has_multiple_size_of_buffer_size() {
+ let total_lines = 100;
+ let random_string = RandomString::generate_with_delimiter(
+ Alphanumeric,
+ b'\n',
+ total_lines,
+ true,
+ CHUNK_BUFFER_SIZE * 3 + 1,
+ );
+ let random_string = random_string.as_str();
+ let lines = random_string.split_inclusive('\n');
- let expected = lines.clone().skip(1).collect::();
- new_ucmd!()
- .args(&["-n", "+2"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(expected);
+ new_ucmd!()
+ .args(&["-n", "+0"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(random_string);
- new_ucmd!()
- .args(&["-n", "-0"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+ let expected = lines.clone().skip(1).collect::();
+ new_ucmd!()
+ .args(&["-n", "+2"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(expected);
- let expected = lines.clone().skip(total_lines - 1).collect::();
- new_ucmd!()
- .args(&["-n", "-1"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(expected);
+ new_ucmd!()
+ .args(&["-n", "-0"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- let expected = lines.clone().skip(1).collect::();
- new_ucmd!()
- .args(&["-n", "-99"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(expected);
+ let expected = lines.clone().skip(total_lines - 1).collect::();
+ new_ucmd!()
+ .args(&["-n", "-1"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(expected);
- new_ucmd!()
- .args(&["-n", "-100"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(random_string);
- }
+ let expected = lines.clone().skip(1).collect::();
+ new_ucmd!()
+ .args(&["-n", "-99"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(expected);
- #[test]
- fn test_pipe_when_bytes_option_value_is_higher_than_contained_bytes() {
- let test_string = "a\nb";
- new_ucmd!()
- .args(&["-c", "4"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(test_string);
+ new_ucmd!()
+ .args(&["-n", "-100"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(random_string);
+}
- new_ucmd!()
- .args(&["-c", "5"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(test_string);
+#[test]
+fn test_pipe_when_bytes_option_value_is_higher_than_contained_bytes() {
+ let test_string = "a\nb";
+ new_ucmd!()
+ .args(&["-c", "4"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(test_string);
- new_ucmd!()
- .args(&["-c", "999"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(test_string);
+ new_ucmd!()
+ .args(&["-c", "5"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(test_string);
- new_ucmd!()
- .args(&["-c", "+4"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+ new_ucmd!()
+ .args(&["-c", "999"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(test_string);
- new_ucmd!()
- .args(&["-c", "+5"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+ new_ucmd!()
+ .args(&["-c", "+4"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- new_ucmd!()
- .args(&["-c", "+999"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
- }
+ new_ucmd!()
+ .args(&["-c", "+5"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- #[test]
- fn test_pipe_when_bytes_option_given_multibyte_utf8_characters() {
- // the test string consists of from left to right a 4-byte,3-byte,2-byte,1-byte utf-8 character
- let test_string = "𝅘𝅥𝅮⏻ƒa";
+ new_ucmd!()
+ .args(&["-c", "+999"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
+}
- new_ucmd!()
- .args(&["-c", "+0"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(test_string);
+#[test]
+fn test_pipe_when_bytes_option_given_multibyte_utf8_characters() {
+ // the test string consists of from left to right a 4-byte,3-byte,2-byte,1-byte utf-8 character
+ let test_string = "𝅘𝅥𝅮⏻ƒa";
- new_ucmd!()
- .args(&["-c", "+2"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(&test_string.as_bytes()[1..]);
+ new_ucmd!()
+ .args(&["-c", "+0"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(test_string);
- new_ucmd!()
- .args(&["-c", "+5"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("⏻ƒa");
+ new_ucmd!()
+ .args(&["-c", "+2"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(&test_string.as_bytes()[1..]);
- new_ucmd!()
- .args(&["-c", "+8"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("ƒa");
+ new_ucmd!()
+ .args(&["-c", "+5"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("⏻ƒa");
- new_ucmd!()
- .args(&["-c", "+10"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("a");
+ new_ucmd!()
+ .args(&["-c", "+8"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("ƒa");
- new_ucmd!()
- .args(&["-c", "+11"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+ new_ucmd!()
+ .args(&["-c", "+10"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("a");
- new_ucmd!()
- .args(&["-c", "-1"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("a");
+ new_ucmd!()
+ .args(&["-c", "+11"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- new_ucmd!()
- .args(&["-c", "-2"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(&"ƒa".as_bytes()[1..]);
+ new_ucmd!()
+ .args(&["-c", "-1"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("a");
- new_ucmd!()
- .args(&["-c", "-3"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("ƒa");
+ new_ucmd!()
+ .args(&["-c", "-2"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(&"ƒa".as_bytes()[1..]);
- new_ucmd!()
- .args(&["-c", "-6"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only("⏻ƒa");
+ new_ucmd!()
+ .args(&["-c", "-3"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("ƒa");
- new_ucmd!()
- .args(&["-c", "-10"])
- .pipe_in(test_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(test_string);
- }
+ new_ucmd!()
+ .args(&["-c", "-6"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only("⏻ƒa");
- #[test]
- fn test_pipe_when_bytes_option_given_input_size_is_equal_to_buffer_size() {
- let random_string = RandomString::generate(AlphanumericNewline, CHUNK_BUFFER_SIZE);
- let random_string = random_string.as_str();
+ new_ucmd!()
+ .args(&["-c", "-10"])
+ .pipe_in(test_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(test_string);
+}
- new_ucmd!()
- .args(&["-c", "+0"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(random_string);
+#[test]
+fn test_pipe_when_bytes_option_given_input_size_is_equal_to_buffer_size() {
+ let random_string = RandomString::generate(AlphanumericNewline, CHUNK_BUFFER_SIZE);
+ let random_string = random_string.as_str();
- let expected = &random_string.as_bytes()[1..];
- new_ucmd!()
- .args(&["-c", "+2"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ new_ucmd!()
+ .args(&["-c", "+0"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(random_string);
- new_ucmd!()
- .args(&["-c", "-0"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+ let expected = &random_string.as_bytes()[1..];
+ new_ucmd!()
+ .args(&["-c", "+2"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- let expected = &random_string.as_bytes()[1..];
- new_ucmd!()
- .args(&["-c", "-8191"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ new_ucmd!()
+ .args(&["-c", "-0"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- new_ucmd!()
- .args(&["-c", "-8192"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(random_string);
+ let expected = &random_string.as_bytes()[1..];
+ new_ucmd!()
+ .args(&["-c", "-8191"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- new_ucmd!()
- .args(&["-c", "-8193"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(random_string);
+ new_ucmd!()
+ .args(&["-c", "-8192"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(random_string);
- let expected = &random_string.as_bytes()[CHUNK_BUFFER_SIZE - 1..];
- new_ucmd!()
- .args(&["-c", "-1"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
- }
+ new_ucmd!()
+ .args(&["-c", "-8193"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(random_string);
- #[test]
- fn test_pipe_when_bytes_option_given_input_size_is_one_byte_greater_than_buffer_size() {
- let random_string = RandomString::generate(AlphanumericNewline, CHUNK_BUFFER_SIZE + 1);
- let random_string = random_string.as_str();
+ let expected = &random_string.as_bytes()[CHUNK_BUFFER_SIZE - 1..];
+ new_ucmd!()
+ .args(&["-c", "-1"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
+}
- new_ucmd!()
- .args(&["-c", "+0"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(random_string);
+#[test]
+fn test_pipe_when_bytes_option_given_input_size_is_one_byte_greater_than_buffer_size() {
+ let random_string = RandomString::generate(AlphanumericNewline, CHUNK_BUFFER_SIZE + 1);
+ let random_string = random_string.as_str();
- let expected = &random_string.as_bytes()[1..];
- new_ucmd!()
- .args(&["-c", "+2"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ new_ucmd!()
+ .args(&["-c", "+0"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(random_string);
- new_ucmd!()
- .args(&["-c", "-0"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+ let expected = &random_string.as_bytes()[1..];
+ new_ucmd!()
+ .args(&["-c", "+2"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- let expected = &random_string.as_bytes()[CHUNK_BUFFER_SIZE..];
- new_ucmd!()
- .args(&["-c", "-1"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ new_ucmd!()
+ .args(&["-c", "-0"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- let expected = &random_string.as_bytes()[1..];
- new_ucmd!()
- .args(&["-c", "-8192"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ let expected = &random_string.as_bytes()[CHUNK_BUFFER_SIZE..];
+ new_ucmd!()
+ .args(&["-c", "-1"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- new_ucmd!()
- .args(&["-c", "-8193"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(random_string);
- }
+ let expected = &random_string.as_bytes()[1..];
+ new_ucmd!()
+ .args(&["-c", "-8192"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
+
+ new_ucmd!()
+ .args(&["-c", "-8193"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(random_string);
+}
- #[test]
- fn test_pipe_when_bytes_option_given_input_size_has_multiple_size_of_buffer_size() {
- let random_string = RandomString::generate(AlphanumericNewline, CHUNK_BUFFER_SIZE * 3);
- let random_string = random_string.as_str();
+// FIXME: windows: this test failed with timeout in the CI. Running this test in
+// a Windows VirtualBox image produces no errors.
+#[test]
+#[cfg(not(target_os = "windows"))]
+fn test_pipe_when_bytes_option_given_input_size_has_multiple_size_of_buffer_size() {
+ let random_string = RandomString::generate(AlphanumericNewline, CHUNK_BUFFER_SIZE * 3);
+ let random_string = random_string.as_str();
- new_ucmd!()
- .args(&["-c", "+0"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(random_string);
+ new_ucmd!()
+ .args(&["-c", "+0"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(random_string);
- new_ucmd!()
- .args(&["-c", "-0"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .no_stdout()
- .no_stderr();
+ new_ucmd!()
+ .args(&["-c", "-0"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .no_stdout()
+ .no_stderr();
- let expected = &random_string.as_bytes()[8192..];
- new_ucmd!()
- .args(&["-c", "+8193"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ let expected = &random_string.as_bytes()[8192..];
+ new_ucmd!()
+ .args(&["-c", "+8193"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- let expected = &random_string.as_bytes()[8193..];
- new_ucmd!()
- .args(&["-c", "+8194"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ let expected = &random_string.as_bytes()[8193..];
+ new_ucmd!()
+ .args(&["-c", "+8194"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- let expected = &random_string.as_bytes()[16384..];
- new_ucmd!()
- .args(&["-c", "+16385"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ let expected = &random_string.as_bytes()[16384..];
+ new_ucmd!()
+ .args(&["-c", "+16385"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- let expected = &random_string.as_bytes()[16385..];
- new_ucmd!()
- .args(&["-c", "+16386"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ let expected = &random_string.as_bytes()[16385..];
+ new_ucmd!()
+ .args(&["-c", "+16386"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- let expected = &random_string.as_bytes()[16384..];
- new_ucmd!()
- .args(&["-c", "-8192"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ let expected = &random_string.as_bytes()[16384..];
+ new_ucmd!()
+ .args(&["-c", "-8192"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- let expected = &random_string.as_bytes()[16383..];
- new_ucmd!()
- .args(&["-c", "-8193"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ let expected = &random_string.as_bytes()[16383..];
+ new_ucmd!()
+ .args(&["-c", "-8193"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- let expected = &random_string.as_bytes()[8192..];
- new_ucmd!()
- .args(&["-c", "-16384"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ let expected = &random_string.as_bytes()[8192..];
+ new_ucmd!()
+ .args(&["-c", "-16384"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- let expected = &random_string.as_bytes()[8191..];
- new_ucmd!()
- .args(&["-c", "-16385"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only_bytes(expected);
+ let expected = &random_string.as_bytes()[8191..];
+ new_ucmd!()
+ .args(&["-c", "-16385"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only_bytes(expected);
- new_ucmd!()
- .args(&["-c", "-24576"])
- .pipe_in(random_string)
- .ignore_stdin_write_error()
- .succeeds()
- .stdout_only(random_string);
- }
+ new_ucmd!()
+ .args(&["-c", "-24576"])
+ .pipe_in(random_string)
+ .ignore_stdin_write_error()
+ .succeeds()
+ .stdout_only(random_string);
}
#[test]
diff --git a/tests/common/random.rs b/tests/common/random.rs
index 338aeab5070..36ca3ab4f3a 100644
--- a/tests/common/random.rs
+++ b/tests/common/random.rs
@@ -311,4 +311,30 @@ mod tests {
);
assert!(random_string.as_bytes().ends_with(&[0]));
}
+
+ /// Originally used to exclude an error within the `random` module. The two
+ /// affected tests timed out on windows, but only in the ci. These tests are
+ /// also the source for the concrete numbers. The timed out tests are
+ /// `test_tail.rs::test_pipe_when_lines_option_given_input_size_has_multiple_size_of_buffer_size`
+ /// `test_tail.rs::test_pipe_when_bytes_option_given_input_size_has_multiple_size_of_buffer_size`.
+ #[test]
+ fn test_generate_random_strings_when_length_is_around_critical_buffer_sizes() {
+ let length = 8192 * 3;
+ let random_string = RandomString::generate(AlphanumericNewline, length);
+ assert_eq!(length, random_string.len());
+
+ let length = 8192 * 3 + 1;
+ let random_string =
+ RandomString::generate_with_delimiter(&Alphanumeric, b'\n', 100, true, length);
+ assert_eq!(length, random_string.len());
+ assert_eq!(
+ 100,
+ random_string
+ .as_bytes()
+ .iter()
+ .filter(|p| **p == b'\n')
+ .count()
+ );
+ assert!(!random_string.as_bytes().ends_with(&[0]));
+ }
}