mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Merge pull request #4347 from bbara/tail-gnu
tail: fix GNU test 'misc/tail'
This commit is contained in:
commit
3bc220610d
4 changed files with 642 additions and 191 deletions
|
@ -59,12 +59,30 @@ pub enum FilterMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilterMode {
|
impl FilterMode {
|
||||||
|
fn from_obsolete_args(args: &parse::ObsoleteArgs) -> Self {
|
||||||
|
let signum = if args.plus {
|
||||||
|
Signum::Positive(args.num)
|
||||||
|
} else {
|
||||||
|
Signum::Negative(args.num)
|
||||||
|
};
|
||||||
|
if args.lines {
|
||||||
|
Self::Lines(signum, b'\n')
|
||||||
|
} else {
|
||||||
|
Self::Bytes(signum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn from(matches: &ArgMatches) -> UResult<Self> {
|
fn from(matches: &ArgMatches) -> UResult<Self> {
|
||||||
let zero_term = matches.get_flag(options::ZERO_TERM);
|
let zero_term = matches.get_flag(options::ZERO_TERM);
|
||||||
let mode = if let Some(arg) = matches.get_one::<String>(options::BYTES) {
|
let mode = if let Some(arg) = matches.get_one::<String>(options::BYTES) {
|
||||||
match parse_num(arg) {
|
match parse_num(arg) {
|
||||||
Ok(signum) => Self::Bytes(signum),
|
Ok(signum) => Self::Bytes(signum),
|
||||||
Err(e) => return Err(UUsageError::new(1, format!("invalid number of bytes: {e}"))),
|
Err(e) => {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!("invalid number of bytes: {e}"),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(arg) = matches.get_one::<String>(options::LINES) {
|
} else if let Some(arg) = matches.get_one::<String>(options::LINES) {
|
||||||
match parse_num(arg) {
|
match parse_num(arg) {
|
||||||
|
@ -72,7 +90,12 @@ impl FilterMode {
|
||||||
let delimiter = if zero_term { 0 } else { b'\n' };
|
let delimiter = if zero_term { 0 } else { b'\n' };
|
||||||
Self::Lines(signum, delimiter)
|
Self::Lines(signum, delimiter)
|
||||||
}
|
}
|
||||||
Err(e) => return Err(UUsageError::new(1, format!("invalid number of lines: {e}"))),
|
Err(e) => {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!("invalid number of lines: {e}"),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if zero_term {
|
} else if zero_term {
|
||||||
Self::default_zero()
|
Self::default_zero()
|
||||||
|
@ -107,7 +130,7 @@ pub enum VerificationResult {
|
||||||
NoOutput,
|
NoOutput,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub follow: Option<FollowMode>,
|
pub follow: Option<FollowMode>,
|
||||||
pub max_unchanged_stats: u32,
|
pub max_unchanged_stats: u32,
|
||||||
|
@ -121,28 +144,64 @@ pub struct Settings {
|
||||||
pub inputs: VecDeque<Input>,
|
pub inputs: VecDeque<Input>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
max_unchanged_stats: 5,
|
||||||
|
sleep_sec: Duration::from_secs_f32(1.0),
|
||||||
|
follow: Default::default(),
|
||||||
|
mode: Default::default(),
|
||||||
|
pid: Default::default(),
|
||||||
|
retry: Default::default(),
|
||||||
|
use_polling: Default::default(),
|
||||||
|
verbose: Default::default(),
|
||||||
|
presume_input_pipe: Default::default(),
|
||||||
|
inputs: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
pub fn from_obsolete_args(args: &parse::ObsoleteArgs, name: Option<&OsString>) -> Self {
|
||||||
|
let mut settings: Self = Default::default();
|
||||||
|
if args.follow {
|
||||||
|
settings.follow = if name.is_some() {
|
||||||
|
Some(FollowMode::Name)
|
||||||
|
} else {
|
||||||
|
Some(FollowMode::Descriptor)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
settings.mode = FilterMode::from_obsolete_args(args);
|
||||||
|
let input = if let Some(name) = name {
|
||||||
|
Input::from(&name)
|
||||||
|
} else {
|
||||||
|
Input::default()
|
||||||
|
};
|
||||||
|
settings.inputs.push_back(input);
|
||||||
|
settings
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from(matches: &clap::ArgMatches) -> UResult<Self> {
|
pub fn from(matches: &clap::ArgMatches) -> UResult<Self> {
|
||||||
let mut settings: Self = Self {
|
let mut settings: Self = Self {
|
||||||
sleep_sec: Duration::from_secs_f32(1.0),
|
follow: if matches.get_flag(options::FOLLOW_RETRY) {
|
||||||
max_unchanged_stats: 5,
|
Some(FollowMode::Name)
|
||||||
|
} else if matches.value_source(options::FOLLOW) != Some(ValueSource::CommandLine) {
|
||||||
|
None
|
||||||
|
} else if matches.get_one::<String>(options::FOLLOW)
|
||||||
|
== Some(String::from("name")).as_ref()
|
||||||
|
{
|
||||||
|
Some(FollowMode::Name)
|
||||||
|
} else {
|
||||||
|
Some(FollowMode::Descriptor)
|
||||||
|
},
|
||||||
|
retry: matches.get_flag(options::RETRY) || matches.get_flag(options::FOLLOW_RETRY),
|
||||||
|
use_polling: matches.get_flag(options::USE_POLLING),
|
||||||
|
mode: FilterMode::from(matches)?,
|
||||||
|
verbose: matches.get_flag(options::verbosity::VERBOSE),
|
||||||
|
presume_input_pipe: matches.get_flag(options::PRESUME_INPUT_PIPE),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
settings.follow = if matches.get_flag(options::FOLLOW_RETRY) {
|
|
||||||
Some(FollowMode::Name)
|
|
||||||
} else if matches.value_source(options::FOLLOW) != Some(ValueSource::CommandLine) {
|
|
||||||
None
|
|
||||||
} else if matches.get_one::<String>(options::FOLLOW) == Some(String::from("name")).as_ref()
|
|
||||||
{
|
|
||||||
Some(FollowMode::Name)
|
|
||||||
} else {
|
|
||||||
Some(FollowMode::Descriptor)
|
|
||||||
};
|
|
||||||
|
|
||||||
settings.retry =
|
|
||||||
matches.get_flag(options::RETRY) || matches.get_flag(options::FOLLOW_RETRY);
|
|
||||||
|
|
||||||
if let Some(source) = matches.get_one::<String>(options::SLEEP_INT) {
|
if let Some(source) = matches.get_one::<String>(options::SLEEP_INT) {
|
||||||
// Advantage of `fundu` over `Duration::(try_)from_secs_f64(source.parse().unwrap())`:
|
// Advantage of `fundu` over `Duration::(try_)from_secs_f64(source.parse().unwrap())`:
|
||||||
// * doesn't panic on errors like `Duration::from_secs_f64` would.
|
// * doesn't panic on errors like `Duration::from_secs_f64` would.
|
||||||
|
@ -159,8 +218,6 @@ impl Settings {
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.use_polling = matches.get_flag(options::USE_POLLING);
|
|
||||||
|
|
||||||
if let Some(s) = matches.get_one::<String>(options::MAX_UNCHANGED_STATS) {
|
if let Some(s) = matches.get_one::<String>(options::MAX_UNCHANGED_STATS) {
|
||||||
settings.max_unchanged_stats = match s.parse::<u32>() {
|
settings.max_unchanged_stats = match s.parse::<u32>() {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
|
@ -200,11 +257,9 @@ impl Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.mode = FilterMode::from(matches)?;
|
|
||||||
|
|
||||||
let mut inputs: VecDeque<Input> = matches
|
let mut inputs: VecDeque<Input> = matches
|
||||||
.get_many::<String>(options::ARG_FILES)
|
.get_many::<String>(options::ARG_FILES)
|
||||||
.map(|v| v.map(|string| Input::from(string.clone())).collect())
|
.map(|v| v.map(|string| Input::from(&string)).collect())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
// apply default and add '-' to inputs if none is present
|
// apply default and add '-' to inputs if none is present
|
||||||
|
@ -212,13 +267,10 @@ impl Settings {
|
||||||
inputs.push_front(Input::default());
|
inputs.push_front(Input::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.verbose = (matches.get_flag(options::verbosity::VERBOSE) || inputs.len() > 1)
|
settings.verbose = inputs.len() > 1 && !matches.get_flag(options::verbosity::QUIET);
|
||||||
&& !matches.get_flag(options::verbosity::QUIET);
|
|
||||||
|
|
||||||
settings.inputs = inputs;
|
settings.inputs = inputs;
|
||||||
|
|
||||||
settings.presume_input_pipe = matches.get_flag(options::PRESUME_INPUT_PIPE);
|
|
||||||
|
|
||||||
Ok(settings)
|
Ok(settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,32 +350,31 @@ impl Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arg_iterate<'a>(
|
pub fn parse_obsolete(arg: &OsString, input: Option<&OsString>) -> UResult<Option<Settings>> {
|
||||||
mut args: impl uucore::Args + 'a,
|
match parse::parse_obsolete(arg) {
|
||||||
) -> UResult<Box<dyn Iterator<Item = OsString> + 'a>> {
|
Some(Ok(args)) => Ok(Some(Settings::from_obsolete_args(&args, input))),
|
||||||
// argv[0] is always present
|
None => Ok(None),
|
||||||
let first = args.next().unwrap();
|
Some(Err(e)) => {
|
||||||
if let Some(second) = args.next() {
|
let arg_str = arg.to_string_lossy();
|
||||||
if let Some(s) = second.to_str() {
|
Err(USimpleError::new(
|
||||||
match parse::parse_obsolete(s) {
|
1,
|
||||||
Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
|
match e {
|
||||||
Some(Err(e)) => Err(UUsageError::new(
|
parse::ParseError::OutOfRange => format!(
|
||||||
1,
|
"invalid number: {}: Numerical result out of range",
|
||||||
match e {
|
arg_str.quote()
|
||||||
parse::ParseError::Syntax => format!("bad argument format: {}", s.quote()),
|
),
|
||||||
parse::ParseError::Overflow => format!(
|
parse::ParseError::Overflow => format!("invalid number: {}", arg_str.quote()),
|
||||||
"invalid argument: {} Value too large for defined datatype",
|
// this ensures compatibility to GNU's error message (as tested in misc/tail)
|
||||||
s.quote()
|
parse::ParseError::Context => format!(
|
||||||
),
|
"option used in invalid context -- {}",
|
||||||
},
|
arg_str.chars().nth(1).unwrap_or_default()
|
||||||
)),
|
),
|
||||||
None => Ok(Box::new(vec![first, second].into_iter().chain(args))),
|
parse::ParseError::InvalidEncoding => {
|
||||||
}
|
format!("bad argument encoding: '{arg_str}'")
|
||||||
} else {
|
}
|
||||||
Err(UUsageError::new(1, "bad argument encoding".to_owned()))
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Ok(Box::new(vec![first].into_iter()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,8 +403,44 @@ fn parse_num(src: &str) -> Result<Signum, ParseSizeError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_args(args: impl uucore::Args) -> UResult<Settings> {
|
pub fn parse_args(args: impl uucore::Args) -> UResult<Settings> {
|
||||||
let matches = uu_app().try_get_matches_from(arg_iterate(args)?)?;
|
let args_vec: Vec<OsString> = args.collect();
|
||||||
Settings::from(&matches)
|
let clap_args = uu_app().try_get_matches_from(args_vec.clone());
|
||||||
|
let clap_result = match clap_args {
|
||||||
|
Ok(matches) => Ok(Settings::from(&matches)?),
|
||||||
|
Err(err) => Err(err.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// clap isn't able to handle obsolete syntax.
|
||||||
|
// therefore, we want to check further for obsolete arguments.
|
||||||
|
// argv[0] is always present, argv[1] might be obsolete arguments
|
||||||
|
// argv[2] might contain an input file, argv[3] isn't allowed in obsolete mode
|
||||||
|
if args_vec.len() != 2 && args_vec.len() != 3 {
|
||||||
|
return clap_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, there are a few possible cases:
|
||||||
|
//
|
||||||
|
// 1. clap has succeeded and the arguments would be invalid for the obsolete syntax.
|
||||||
|
// 2. The case of `tail -c 5` is ambiguous. clap parses this as `tail -c5`,
|
||||||
|
// but it could also be interpreted as valid obsolete syntax (tail -c on file '5').
|
||||||
|
// GNU chooses to interpret this as `tail -c5`, like clap.
|
||||||
|
// 3. `tail -f foo` is also ambiguous, but has the same effect in both cases. We can safely
|
||||||
|
// use the clap result here.
|
||||||
|
// 4. clap succeeded for obsolete arguments starting with '+', but misinterprets them as
|
||||||
|
// input files (e.g. 'tail +f').
|
||||||
|
// 5. clap failed because of unknown flags, but possibly valid obsolete arguments
|
||||||
|
// (e.g. tail -l; tail -10c).
|
||||||
|
//
|
||||||
|
// In cases 4 & 5, we want to try parsing the obsolete arguments, which corresponds to
|
||||||
|
// checking whether clap succeeded or the first argument starts with '+'.
|
||||||
|
let possible_obsolete_args = &args_vec[1];
|
||||||
|
if clap_result.is_ok() && !possible_obsolete_args.to_string_lossy().starts_with('+') {
|
||||||
|
return clap_result;
|
||||||
|
}
|
||||||
|
match parse_obsolete(possible_obsolete_args, args_vec.get(2))? {
|
||||||
|
Some(settings) => Ok(settings),
|
||||||
|
None => clap_result,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> Command {
|
pub fn uu_app() -> Command {
|
||||||
|
@ -386,6 +473,7 @@ pub fn uu_app() -> Command {
|
||||||
.num_args(0..=1)
|
.num_args(0..=1)
|
||||||
.require_equals(true)
|
.require_equals(true)
|
||||||
.value_parser(["descriptor", "name"])
|
.value_parser(["descriptor", "name"])
|
||||||
|
.overrides_with(options::FOLLOW)
|
||||||
.help("Print the file as it grows"),
|
.help("Print the file as it grows"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -482,6 +570,8 @@ pub fn uu_app() -> Command {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::parse::ObsoleteArgs;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -513,4 +603,17 @@ mod tests {
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(result.unwrap(), Signum::Negative(1));
|
assert_eq!(result.unwrap(), Signum::Negative(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_obsolete_settings_f() {
|
||||||
|
let args = ObsoleteArgs {
|
||||||
|
follow: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let result = Settings::from_obsolete_args(&args, None);
|
||||||
|
assert_eq!(result.follow, Some(FollowMode::Descriptor));
|
||||||
|
|
||||||
|
let result = Settings::from_obsolete_args(&args, Some(&"file".into()));
|
||||||
|
assert_eq!(result.follow, Some(FollowMode::Name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,157 +5,185 @@
|
||||||
|
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||||
|
pub struct ObsoleteArgs {
|
||||||
|
pub num: u64,
|
||||||
|
pub plus: bool,
|
||||||
|
pub lines: bool,
|
||||||
|
pub follow: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ObsoleteArgs {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
num: 10,
|
||||||
|
plus: false,
|
||||||
|
lines: true,
|
||||||
|
follow: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
Syntax,
|
OutOfRange,
|
||||||
Overflow,
|
Overflow,
|
||||||
|
Context,
|
||||||
|
InvalidEncoding,
|
||||||
}
|
}
|
||||||
/// Parses obsolete syntax
|
/// Parses obsolete syntax
|
||||||
/// tail -NUM\[kmzv\] // spell-checker:disable-line
|
/// tail -\[NUM\]\[bcl\]\[f\] and tail +\[NUM\]\[bcl\]\[f\] // spell-checker:disable-line
|
||||||
pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>, ParseError>> {
|
pub fn parse_obsolete(src: &OsString) -> Option<Result<ObsoleteArgs, ParseError>> {
|
||||||
let mut chars = src.char_indices();
|
let mut rest = match src.to_str() {
|
||||||
if let Some((_, '-')) = chars.next() {
|
Some(src) => src,
|
||||||
let mut num_end = 0usize;
|
None => return Some(Err(ParseError::InvalidEncoding)),
|
||||||
let mut has_num = false;
|
};
|
||||||
let mut last_char = 0 as char;
|
let sign = if let Some(r) = rest.strip_prefix('-') {
|
||||||
for (n, c) in &mut chars {
|
rest = r;
|
||||||
if c.is_ascii_digit() {
|
'-'
|
||||||
has_num = true;
|
} else if let Some(r) = rest.strip_prefix('+') {
|
||||||
num_end = n;
|
rest = r;
|
||||||
} else {
|
'+'
|
||||||
last_char = c;
|
} else {
|
||||||
break;
|
return None;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
if has_num {
|
let end_num = rest
|
||||||
match src[1..=num_end].parse::<usize>() {
|
.find(|c: char| !c.is_ascii_digit())
|
||||||
Ok(num) => {
|
.unwrap_or(rest.len());
|
||||||
let mut quiet = false;
|
let has_num = !rest[..end_num].is_empty();
|
||||||
let mut verbose = false;
|
let num: u64 = if has_num {
|
||||||
let mut zero_terminated = false;
|
if let Ok(num) = rest[..end_num].parse() {
|
||||||
let mut multiplier = None;
|
num
|
||||||
let mut c = last_char;
|
|
||||||
loop {
|
|
||||||
// not that here, we only match lower case 'k', 'c', and 'm'
|
|
||||||
match c {
|
|
||||||
// we want to preserve order
|
|
||||||
// this also saves us 1 heap allocation
|
|
||||||
'q' => {
|
|
||||||
quiet = true;
|
|
||||||
verbose = false;
|
|
||||||
}
|
|
||||||
'v' => {
|
|
||||||
verbose = true;
|
|
||||||
quiet = false;
|
|
||||||
}
|
|
||||||
'z' => zero_terminated = true,
|
|
||||||
'c' => multiplier = Some(1),
|
|
||||||
'b' => multiplier = Some(512),
|
|
||||||
'k' => multiplier = Some(1024),
|
|
||||||
'm' => multiplier = Some(1024 * 1024),
|
|
||||||
'\0' => {}
|
|
||||||
_ => return Some(Err(ParseError::Syntax)),
|
|
||||||
}
|
|
||||||
if let Some((_, next)) = chars.next() {
|
|
||||||
c = next;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut options = Vec::new();
|
|
||||||
if quiet {
|
|
||||||
options.push(OsString::from("-q"));
|
|
||||||
}
|
|
||||||
if verbose {
|
|
||||||
options.push(OsString::from("-v"));
|
|
||||||
}
|
|
||||||
if zero_terminated {
|
|
||||||
options.push(OsString::from("-z"));
|
|
||||||
}
|
|
||||||
if let Some(n) = multiplier {
|
|
||||||
options.push(OsString::from("-c"));
|
|
||||||
let num = match num.checked_mul(n) {
|
|
||||||
Some(n) => n,
|
|
||||||
None => return Some(Err(ParseError::Overflow)),
|
|
||||||
};
|
|
||||||
options.push(OsString::from(format!("{num}")));
|
|
||||||
} else {
|
|
||||||
options.push(OsString::from("-n"));
|
|
||||||
options.push(OsString::from(format!("{num}")));
|
|
||||||
}
|
|
||||||
Some(Ok(options.into_iter()))
|
|
||||||
}
|
|
||||||
Err(_) => Some(Err(ParseError::Overflow)),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
return Some(Err(ParseError::OutOfRange));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
10
|
||||||
|
};
|
||||||
|
rest = &rest[end_num..];
|
||||||
|
|
||||||
|
let mode = if let Some(r) = rest.strip_prefix('l') {
|
||||||
|
rest = r;
|
||||||
|
'l'
|
||||||
|
} else if let Some(r) = rest.strip_prefix('c') {
|
||||||
|
rest = r;
|
||||||
|
'c'
|
||||||
|
} else if let Some(r) = rest.strip_prefix('b') {
|
||||||
|
rest = r;
|
||||||
|
'b'
|
||||||
|
} else {
|
||||||
|
'l'
|
||||||
|
};
|
||||||
|
|
||||||
|
let follow = rest.contains('f');
|
||||||
|
if !rest.chars().all(|f| f == 'f') {
|
||||||
|
// GNU allows an arbitrary amount of following fs, but nothing else
|
||||||
|
if sign == '-' && has_num {
|
||||||
|
return Some(Err(ParseError::Context));
|
||||||
|
}
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let multiplier = if mode == 'b' { 512 } else { 1 };
|
||||||
|
let num = match num.checked_mul(multiplier) {
|
||||||
|
Some(n) => n,
|
||||||
|
None => return Some(Err(ParseError::Overflow)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Ok(ObsoleteArgs {
|
||||||
|
num,
|
||||||
|
plus: sign == '+',
|
||||||
|
lines: mode == 'l',
|
||||||
|
follow,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
fn obsolete(src: &str) -> Option<Result<Vec<String>, ParseError>> {
|
|
||||||
let r = parse_obsolete(src);
|
|
||||||
match r {
|
|
||||||
Some(s) => match s {
|
|
||||||
Ok(v) => Some(Ok(v.map(|s| s.to_str().unwrap().to_owned()).collect())),
|
|
||||||
Err(e) => Some(Err(e)),
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn obsolete_result(src: &[&str]) -> Option<Result<Vec<String>, ParseError>> {
|
|
||||||
Some(Ok(src.iter().map(|s| s.to_string()).collect()))
|
|
||||||
}
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_numbers_obsolete() {
|
fn test_parse_numbers_obsolete() {
|
||||||
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"]));
|
|
||||||
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"]));
|
|
||||||
assert_eq!(obsolete("-5m"), obsolete_result(&["-c", "5242880"]));
|
|
||||||
assert_eq!(obsolete("-1k"), obsolete_result(&["-c", "1024"]));
|
|
||||||
assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "1024"]));
|
|
||||||
assert_eq!(obsolete("-1mmk"), obsolete_result(&["-c", "1024"]));
|
|
||||||
assert_eq!(obsolete("-1vz"), obsolete_result(&["-v", "-z", "-n", "1"]));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
obsolete("-1vzqvq"), // spell-checker:disable-line
|
parse_obsolete(&OsString::from("+2c")),
|
||||||
obsolete_result(&["-q", "-z", "-n", "1"])
|
Some(Ok(ObsoleteArgs {
|
||||||
|
num: 2,
|
||||||
|
plus: true,
|
||||||
|
lines: false,
|
||||||
|
follow: false,
|
||||||
|
}))
|
||||||
);
|
);
|
||||||
assert_eq!(obsolete("-1vzc"), obsolete_result(&["-v", "-z", "-c", "1"]));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
obsolete("-105kzm"),
|
parse_obsolete(&OsString::from("-5")),
|
||||||
obsolete_result(&["-z", "-c", "110100480"])
|
Some(Ok(ObsoleteArgs {
|
||||||
|
num: 5,
|
||||||
|
plus: false,
|
||||||
|
lines: true,
|
||||||
|
follow: false,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_obsolete(&OsString::from("+100f")),
|
||||||
|
Some(Ok(ObsoleteArgs {
|
||||||
|
num: 100,
|
||||||
|
plus: true,
|
||||||
|
lines: true,
|
||||||
|
follow: true,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_obsolete(&OsString::from("-2b")),
|
||||||
|
Some(Ok(ObsoleteArgs {
|
||||||
|
num: 1024,
|
||||||
|
plus: false,
|
||||||
|
lines: false,
|
||||||
|
follow: false,
|
||||||
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_errors_obsolete() {
|
fn test_parse_errors_obsolete() {
|
||||||
assert_eq!(obsolete("-5n"), Some(Err(ParseError::Syntax)));
|
assert_eq!(
|
||||||
assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Syntax)));
|
parse_obsolete(&OsString::from("-5n")),
|
||||||
|
Some(Err(ParseError::Context))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_obsolete(&OsString::from("-5c5")),
|
||||||
|
Some(Err(ParseError::Context))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_obsolete(&OsString::from("-1vzc")),
|
||||||
|
Some(Err(ParseError::Context))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_obsolete(&OsString::from("-5m")),
|
||||||
|
Some(Err(ParseError::Context))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_obsolete(&OsString::from("-1k")),
|
||||||
|
Some(Err(ParseError::Context))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_obsolete(&OsString::from("-1mmk")),
|
||||||
|
Some(Err(ParseError::Context))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_obsolete(&OsString::from("-105kzm")),
|
||||||
|
Some(Err(ParseError::Context))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_obsolete(&OsString::from("-1vz")),
|
||||||
|
Some(Err(ParseError::Context))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_obsolete(&OsString::from("-1vzqvq")), // spell-checker:disable-line
|
||||||
|
Some(Err(ParseError::Context))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_obsolete_no_match() {
|
fn test_parse_obsolete_no_match() {
|
||||||
assert_eq!(obsolete("-k"), None);
|
assert_eq!(parse_obsolete(&OsString::from("-k")), None);
|
||||||
assert_eq!(obsolete("asd"), None);
|
assert_eq!(parse_obsolete(&OsString::from("asd")), None);
|
||||||
}
|
assert_eq!(parse_obsolete(&OsString::from("-cc")), None);
|
||||||
#[test]
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
|
||||||
fn test_parse_obsolete_overflow_x64() {
|
|
||||||
assert_eq!(
|
|
||||||
obsolete("-1000000000000000m"),
|
|
||||||
Some(Err(ParseError::Overflow))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
obsolete("-10000000000000000000000"),
|
|
||||||
Some(Err(ParseError::Overflow))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
|
||||||
fn test_parse_obsolete_overflow_x32() {
|
|
||||||
assert_eq!(obsolete("-42949672960"), Some(Err(ParseError::Overflow)));
|
|
||||||
assert_eq!(obsolete("-42949672k"), Some(Err(ParseError::Overflow)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
// spell-checker:ignore tailable seekable stdlib (stdlib)
|
// spell-checker:ignore tailable seekable stdlib (stdlib)
|
||||||
|
|
||||||
use crate::text;
|
use crate::text;
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::fs::{File, Metadata};
|
use std::fs::{File, Metadata};
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -26,21 +27,20 @@ pub struct Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
// TODO: from &str may be the better choice
|
pub fn from<T: AsRef<OsStr>>(string: &T) -> Self {
|
||||||
pub fn from(string: String) -> Self {
|
let kind = if string.as_ref() == Path::new(text::DASH) {
|
||||||
let kind = if string == text::DASH {
|
|
||||||
InputKind::Stdin
|
InputKind::Stdin
|
||||||
} else {
|
} else {
|
||||||
InputKind::File(PathBuf::from(&string))
|
InputKind::File(PathBuf::from(string.as_ref()))
|
||||||
};
|
};
|
||||||
|
|
||||||
let display_name = match kind {
|
let display_name = match kind {
|
||||||
InputKind::File(_) => string,
|
InputKind::File(_) => string.as_ref().to_string_lossy().to_string(),
|
||||||
InputKind::Stdin => {
|
InputKind::Stdin => {
|
||||||
if cfg!(unix) {
|
if cfg!(unix) {
|
||||||
text::STDIN_HEADER.to_string()
|
text::STDIN_HEADER.to_string()
|
||||||
} else {
|
} else {
|
||||||
string
|
string.as_ref().to_string_lossy().to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,6 +47,13 @@ static FOLLOW_NAME_EXP: &str = "follow_name.expected";
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
const DEFAULT_SLEEP_INTERVAL_MILLIS: u64 = 1000;
|
const DEFAULT_SLEEP_INTERVAL_MILLIS: u64 = 1000;
|
||||||
|
|
||||||
|
// The binary integer "10000000" is *not* a valid UTF-8 encoding
|
||||||
|
// of a character: https://en.wikipedia.org/wiki/UTF-8#Encoding
|
||||||
|
#[cfg(unix)]
|
||||||
|
const INVALID_UTF8: u8 = 0x80;
|
||||||
|
#[cfg(windows)]
|
||||||
|
const INVALID_UTF16: u16 = 0xD800;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_arg() {
|
fn test_invalid_arg() {
|
||||||
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
|
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
|
||||||
|
@ -469,16 +476,13 @@ fn test_follow_non_utf8_bytes() {
|
||||||
|
|
||||||
// Now append some bytes that are not valid UTF-8.
|
// Now append some bytes that are not valid UTF-8.
|
||||||
//
|
//
|
||||||
// The binary integer "10000000" is *not* a valid UTF-8 encoding
|
|
||||||
// of a character: https://en.wikipedia.org/wiki/UTF-8#Encoding
|
|
||||||
//
|
|
||||||
// We also write the newline character because our implementation
|
// We also write the newline character because our implementation
|
||||||
// of `tail` is attempting to read a line of input, so the
|
// of `tail` is attempting to read a line of input, so the
|
||||||
// presence of a newline character will force the `follow()`
|
// presence of a newline character will force the `follow()`
|
||||||
// function to conclude reading input bytes and start writing them
|
// function to conclude reading input bytes and start writing them
|
||||||
// to output. The newline character is not fundamental to this
|
// to output. The newline character is not fundamental to this
|
||||||
// test, it is just a requirement of the current implementation.
|
// test, it is just a requirement of the current implementation.
|
||||||
let expected = [0b10000000, b'\n'];
|
let expected = [INVALID_UTF8, b'\n'];
|
||||||
at.append_bytes(FOOBAR_TXT, &expected);
|
at.append_bytes(FOOBAR_TXT, &expected);
|
||||||
|
|
||||||
child
|
child
|
||||||
|
@ -4475,3 +4479,319 @@ fn test_args_sleep_interval_when_illegal_argument_then_usage_error(#[case] sleep
|
||||||
.usage_error(format!("invalid number of seconds: '{sleep_interval}'"))
|
.usage_error(format!("invalid number of seconds: '{sleep_interval}'"))
|
||||||
.code_is(1);
|
.code_is(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_args_plus_c() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
// obs-plus-c1
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("+2c")
|
||||||
|
.pipe_in("abcd")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("bcd");
|
||||||
|
// obs-plus-c2
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("+8c")
|
||||||
|
.pipe_in("abcd")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("");
|
||||||
|
// obs-plus-x1: same as +10c
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("+c")
|
||||||
|
.pipe_in(format!("x{}z", "y".repeat(10)))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("yyz");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_args_c() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
// obs-c3
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-1c")
|
||||||
|
.pipe_in("abcd")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("d");
|
||||||
|
// obs-c4
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-9c")
|
||||||
|
.pipe_in("abcd")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("abcd");
|
||||||
|
// obs-c5
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-12c")
|
||||||
|
.pipe_in(format!("x{}z", "y".repeat(12)))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only(&format!("{}z", "y".repeat(11)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_args_l() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
// obs-l1
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-1l")
|
||||||
|
.pipe_in("x")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("x");
|
||||||
|
// obs-l2
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-1l")
|
||||||
|
.pipe_in("x\ny\n")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("y\n");
|
||||||
|
// obs-l3
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-1l")
|
||||||
|
.pipe_in("x\ny")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("y");
|
||||||
|
// obs-l: same as -10l
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-l")
|
||||||
|
.pipe_in(format!("x{}z", "y\n".repeat(10)))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only(&format!("{}z", "y\n".repeat(9)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_args_plus_l() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
// obs-plus-l4
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("+1l")
|
||||||
|
.pipe_in("x\ny\n")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("x\ny\n");
|
||||||
|
// ops-plus-l5
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("+2l")
|
||||||
|
.pipe_in("x\ny\n")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("y\n");
|
||||||
|
// obs-plus-x2: same as +10l
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("+l")
|
||||||
|
.pipe_in(format!("x\n{}z", "y\n".repeat(10)))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("y\ny\nz");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_args_number() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
// obs-1
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-1")
|
||||||
|
.pipe_in("x")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("x");
|
||||||
|
// obs-2
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-1")
|
||||||
|
.pipe_in("x\ny\n")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("y\n");
|
||||||
|
// obs-3
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-1")
|
||||||
|
.pipe_in("x\ny")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("y");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_args_plus_number() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
// obs-plus-4
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("+1")
|
||||||
|
.pipe_in("x\ny\n")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("x\ny\n");
|
||||||
|
// ops-plus-5
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("+2")
|
||||||
|
.pipe_in("x\ny\n")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("y\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_args_b() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
// obs-b
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-b")
|
||||||
|
.pipe_in("x\n".repeat(512 * 10 / 2 + 1))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only(&"x\n".repeat(512 * 10 / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_args_err() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
// err-1
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("+cl")
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_is("tail: cannot open '+cl' for reading: No such file or directory\n")
|
||||||
|
.code_is(1);
|
||||||
|
// err-2
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-cl")
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_is("tail: invalid number of bytes: 'l'\n")
|
||||||
|
.code_is(1);
|
||||||
|
// err-3
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("+2cz")
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_is("tail: cannot open '+2cz' for reading: No such file or directory\n")
|
||||||
|
.code_is(1);
|
||||||
|
// err-4
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-2cX")
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_is("tail: option used in invalid context -- 2\n")
|
||||||
|
.code_is(1);
|
||||||
|
// err-5
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-c99999999999999999999")
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_is("tail: invalid number of bytes: '99999999999999999999'\n")
|
||||||
|
.code_is(1);
|
||||||
|
// err-6
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-c --")
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_is("tail: invalid number of bytes: '-'\n")
|
||||||
|
.code_is(1);
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-5cz")
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_is("tail: option used in invalid context -- 5\n")
|
||||||
|
.code_is(1);
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-9999999999999999999b")
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_is("tail: invalid number: '-9999999999999999999b'\n")
|
||||||
|
.code_is(1);
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-999999999999999999999b")
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_is(
|
||||||
|
"tail: invalid number: '-999999999999999999999b': Numerical result out of range\n",
|
||||||
|
)
|
||||||
|
.code_is(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_args_f() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
let source = "file";
|
||||||
|
at.touch(source);
|
||||||
|
let mut p = scene.ucmd().args(&["+f", source]).run_no_wait();
|
||||||
|
p.make_assertion_with_delay(500).is_alive();
|
||||||
|
p.kill()
|
||||||
|
.make_assertion()
|
||||||
|
.with_all_output()
|
||||||
|
.no_stderr()
|
||||||
|
.no_stdout();
|
||||||
|
|
||||||
|
let mut p = scene
|
||||||
|
.ucmd()
|
||||||
|
.set_stdin(Stdio::piped())
|
||||||
|
.arg("+f")
|
||||||
|
.run_no_wait();
|
||||||
|
p.make_assertion_with_delay(500).is_alive();
|
||||||
|
p.kill()
|
||||||
|
.make_assertion()
|
||||||
|
.with_all_output()
|
||||||
|
.no_stderr()
|
||||||
|
.no_stdout();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn test_obsolete_encoding_unix() {
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let invalid_utf8_arg = OsStr::from_bytes(&[b'-', INVALID_UTF8, b'b']);
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg(invalid_utf8_arg)
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_is("tail: bad argument encoding: '-<2D>b'\n")
|
||||||
|
.code_is(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn test_obsolete_encoding_windows() {
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::os::windows::ffi::OsStringExt;
|
||||||
|
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let invalid_utf16_arg = OsString::from_wide(&['-' as u16, INVALID_UTF16, 'b' as u16]);
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg(&invalid_utf16_arg)
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_is("tail: bad argument encoding: '-<2D>b'\n")
|
||||||
|
.code_is(1);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue