Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions src/uu/test/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,11 @@ impl Parser {
///
/// EXPR → TERM | EXPR BOOLOP EXPR
fn expr(&mut self) -> ParseResult<()> {
if !self.peek_is_boolop() {
let has_term = !self.peek_is_boolop();
if has_term {
self.term()?;
}
self.maybe_boolop()?;
self.maybe_boolop(has_term)?;
Ok(())
}

Expand Down Expand Up @@ -344,7 +345,7 @@ impl Parser {
_ => {
// bang is literal; parsing continues with op
self.literal(Symbol::Bang.into_literal())?;
self.maybe_boolop()?;
self.maybe_boolop(true)?;
}
}
}
Expand Down Expand Up @@ -379,16 +380,28 @@ impl Parser {

/// Peek at the next token and parse it as a BOOLOP or string literal,
/// as appropriate.
fn maybe_boolop(&mut self) -> ParseResult<()> {
///
/// `has_left_operand` tells whether an expression was already parsed for
/// the BOOLOP to apply to, which decides what a BOOLOP at the end of the
/// stream means.
fn maybe_boolop(&mut self, has_left_operand: bool) -> ParseResult<()> {
if self.peek_is_boolop() {
let symbol = self.next_token();

// BoolOp by itself interpreted as Literal
if let Symbol::None = self.peek() {
if has_left_operand {
// The BOOLOP joins the expression so far to nothing.
return Err(ParseError::at_token(
ParseErrorKind::MissingArgument(format!("{symbol}")),
self.last_pos(),
));
}
// With no operand on either side it is an ordinary string:
// `test -a` is the length test of the string "-a".
self.literal(symbol.into_literal())?;
} else {
self.boolop(symbol)?;
self.maybe_boolop()?;
self.maybe_boolop(true)?;
}
}
Ok(())
Expand Down
31 changes: 30 additions & 1 deletion tests/by-util/test_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1140,11 +1140,40 @@ fn test_filename_or_with_equal() {
}

#[test]
#[ignore = "GNU considers this an error"]
fn test_string_length_and_nothing() {
new_ucmd!().args(&["-n", "a", "-a"]).fails_with_code(2);
}

#[test]
fn test_boolop_without_right_operand() {
for op in ["-a", "-o"] {
new_ucmd!()
.args(&["x", op])
.fails_with_code(2)
.stderr_is(format!("test: missing argument after '{op}'\n"));

// An empty left operand is still an operand.
new_ucmd!().args(&["", op]).fails_with_code(2);

// Whatever precedes it, the trailing BOOLOP has nothing to join to.
new_ucmd!().args(&["x", "-a", "y", op]).fails_with_code(2);
new_ucmd!().args(&["(", "x", ")", op]).fails_with_code(2);
new_ucmd!().args(&["!", "x", op]).fails_with_code(2);
}
}

#[test]
fn test_lone_boolop_is_a_string() {
// With no operand on either side, -a and -o are ordinary strings.
for op in ["-a", "-o"] {
new_ucmd!().arg(op).succeeds();
new_ucmd!().args(&["!", op]).fails_with_code(1);
}

// A unary operator still takes the BOOLOP as its operand.
new_ucmd!().args(&["-n", "-a"]).succeeds();
}

#[test]
fn test_bracket_syntax_success() {
let scenario = TestScenario::new("[");
Expand Down
Loading