derive: split 'compile_error' toy into many test cases, demonstrate new issues

This commit is contained in:
Ben Wiederhake
2025-04-15 01:59:32 +02:00
parent 985af4aa33
commit f47e84c714
27 changed files with 628 additions and 84 deletions
+3
View File
@@ -15,6 +15,9 @@ roff = "0.2.1"
strsim = "0.11.1"
uutils-args-derive = { version = "0.1.0", path = "derive" }
[dev-dependencies]
trybuild = "1.0.104"
[features]
parse-is-complete = []
-84
View File
@@ -1,84 +0,0 @@
use uutils_args::{Arguments, Options, Value};
// Using a fully-fledged compile-error testsuite is a bit overkill, but we still
// want to make sure that the `derive` crate generates reasonable error messages.
// That's what this "example" is for. In the following, there are blocks of
// lines, one marked as POSITIVE and multiple lines marked as NEGATIVE. The
// committed version of this file should only contain POSITIVE. In order to run a
// test, comment out the POSITIVE line, and use a NEGATIVE line instead, and
// manually check whether you see a reasonable error message ideally the error
// message indicated by the comment. One way to do this is:
// $ cargo build --example test_compile_errors_manually
#[derive(Value, Debug, Default)]
enum Flavor {
#[default]
#[value("kind", "nice")]
Kind,
#[value("condescending")] // POSITIVE
// #[value(condescending)] // NEGATIVE: "expected comma-separated list of string literals"
Condescending,
}
#[derive(Arguments)]
#[arguments(file = "examples/hello_world_help.md")] // POSITIVE
// #[arguments(file = "examples/nonexistent.md")] // NEGATIVE: "cannot open help-string file"
// #[arguments(file = "/dev/full")] // NEGATIVE: Causes OOM, FIXME
// #[arguments(file = "/")] // NEGATIVE: "cannot read from help-string file"
// #[arguments(file = "path/to/some/WRITE-ONLY/file")] // NEGATIVE: "cannot open help-string file"
enum Arg {
/// The name to greet
#[arg("-n NAME", "--name[=NAME]", "name=NAME")] // POSITIVE
// #[arg("-")] // NEGATIVE: flag name must be non-empty (cannot be just '-')
// #[arg("-n NAME", "--name[NAME]", "name=NAME")] // NEGATIVE: "expected '=' after '[' in flag pattern"
// #[arg("-n NAME", "--name[=NAME", "name=NAME")] // NEGATIVE: "expected final ']' in flag pattern"
// #[arg(key="name")] // NEGATIVE: "can't parse arg attributes, expected one or more strings"
Name(String),
/// The number of times to greet
#[arg("-c N", "--count=N")]
Count(u8),
#[arg("--flavor=FLAVOR")]
Flavor(Flavor),
}
struct Settings {
name: String,
count: u8,
flavor: Flavor,
}
impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) -> Result<(), uutils_args::Error> {
match arg {
Arg::Name(n) => self.name = n,
Arg::Count(c) => self.count = c,
Arg::Flavor(flavor) => self.flavor = flavor,
}
Ok(())
}
}
fn main() -> Result<(), uutils_args::Error> {
let (settings, _operands) = Settings {
name: String::new(),
count: 1,
flavor: Flavor::Kind,
}
.parse(std::env::args_os())
.unwrap();
for _ in 0..settings.count {
match settings.flavor {
Flavor::Kind => {
println!("Hello, {}!", settings.name);
}
Flavor::Condescending => {
println!("Ugh, {}.", settings.name);
}
}
}
Ok(())
}
+41
View File
@@ -0,0 +1,41 @@
#[test]
fn derive_error_messages_common() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/derive/arg_bare_keyword.rs");
t.compile_fail("tests/derive/arg_just_minus.rs");
t.compile_fail("tests/derive/arg_key_value.rs");
t.compile_fail("tests/derive/arg_missing_closing_bracket.rs");
t.compile_fail("tests/derive/arg_missing_equals.rs");
t.compile_fail("tests/derive/arg_missing_field.rs");
t.compile_fail("tests/derive/arg_missing_metavar.rs");
t.compile_fail("tests/derive/arguments_file_nonexistent.rs");
t.compile_fail("tests/derive/value_bare_keyword.rs");
t.pass("tests/derive/arg_duplicate_other.rs"); // FIXME: Should fail!
t.pass("tests/derive/arg_duplicate_within.rs"); // FIXME: Should fail!
}
#[cfg(unix)]
#[test]
fn derive_error_messages_unix() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/derive/arguments_file_isdir.rs"); // Needs the directory "/"
}
#[cfg(target_os = "linux")]
#[test]
fn derive_error_messages_linux_writeonly_file() {
use std::fs::metadata;
use std::os::unix::fs::PermissionsExt;
// First, verify that /proc/self/clear_refs exists and is write-only:
// https://man.archlinux.org/man/proc_pid_clear_refs.5.en
let metadata = metadata("/proc/self/clear_refs").expect("should be in Linux 2.6.22");
eprintln!("is_file={}", metadata.is_file());
eprintln!("permissions={:?}", metadata.permissions());
assert_eq!(0o100200, metadata.permissions().mode());
// The file exists, as it should. Now we can run the test, using this
// special write-only file, without having to worry about clean-up:
let t = trybuild::TestCases::new();
t.compile_fail("tests/derive/arguments_file_writeonly.rs");
}
+19
View File
@@ -0,0 +1,19 @@
use uutils_args::{Arguments, Options};
#[derive(Arguments)]
enum Arg {
#[arg(banana)] // Oops!
Something,
}
struct Settings {}
impl Options<Arg> for Settings {
fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> {
Ok(())
}
}
fn main() {
Settings {}.parse(std::env::args_os()).unwrap();
}
+16
View File
@@ -0,0 +1,16 @@
error[E0425]: cannot find function `banana` in this scope
--> tests/derive/arg_bare_keyword.rs:5:11
|
5 | #[arg(banana)] // Oops!
| ^^^^^^ not found in this scope
error[E0618]: expected function, found `Arg`
--> tests/derive/arg_bare_keyword.rs:3:10
|
3 | #[derive(Arguments)]
| ^^^^^^^^^ call expression requires function
...
6 | Something,
| --------- `Arg::Something` defined here
|
= note: this error originates in the derive macro `Arguments` (in Nightly builds, run with -Z macro-backtrace for more info)
+21
View File
@@ -0,0 +1,21 @@
use uutils_args::{Arguments, Options};
#[derive(Arguments)]
enum Arg {
#[arg("--foo")]
Something,
#[arg("--foo")] // Oops!
Banana,
}
struct Settings {}
impl Options<Arg> for Settings {
fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> {
Ok(())
}
}
fn main() {
Settings {}.parse(std::env::args_os()).unwrap();
}
+19
View File
@@ -0,0 +1,19 @@
use uutils_args::{Arguments, Options};
#[derive(Arguments)]
enum Arg {
#[arg("--foo", "--foo")] // Oops!
Something,
}
struct Settings {}
impl Options<Arg> for Settings {
fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> {
Ok(())
}
}
fn main() {
Settings {}.parse(std::env::args_os()).unwrap();
}
+19
View File
@@ -0,0 +1,19 @@
use uutils_args::{Arguments, Options};
#[derive(Arguments)]
enum Arg {
#[arg("-")] // Oops!
Something,
}
struct Settings {}
impl Options<Arg> for Settings {
fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> {
Ok(())
}
}
fn main() {
Settings {}.parse(std::env::args_os()).unwrap();
}
+34
View File
@@ -0,0 +1,34 @@
error: proc-macro derive panicked
--> tests/derive/arg_just_minus.rs:3:10
|
3 | #[derive(Arguments)]
| ^^^^^^^^^
|
= help: message: flag name must be non-empty (cannot be just '-')
error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied
--> tests/derive/arg_just_minus.rs:11:6
|
11 | impl Options<Arg> for Settings {
| ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg`
|
note: required by a bound in `Options`
--> src/lib.rs
|
| pub trait Options<Arg: Arguments>: Sized {
| ^^^^^^^^^ required by this bound in `Options`
error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied
--> tests/derive/arg_just_minus.rs:18:17
|
18 | Settings {}.parse(std::env::args_os()).unwrap();
| ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg`
|
note: required by a bound in `uutils_args::Options::parse`
--> src/lib.rs
|
| pub trait Options<Arg: Arguments>: Sized {
| ^^^^^^^^^ required by this bound in `Options::parse`
...
| fn parse<I>(mut self, args: I) -> Result<(Self, Vec<OsString>), Error>
| ----- required by a bound in this associated function
+19
View File
@@ -0,0 +1,19 @@
use uutils_args::{Arguments, Options};
#[derive(Arguments)]
enum Arg {
#[arg(key = "name")] // Oops!
Something,
}
struct Settings {}
impl Options<Arg> for Settings {
fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> {
Ok(())
}
}
fn main() {
Settings {}.parse(std::env::args_os()).unwrap();
}
+34
View File
@@ -0,0 +1,34 @@
error: proc-macro derive panicked
--> tests/derive/arg_key_value.rs:3:10
|
3 | #[derive(Arguments)]
| ^^^^^^^^^
|
= help: message: can't parse arg attributes, expected one or more strings: Error("expected `,`")
error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied
--> tests/derive/arg_key_value.rs:11:6
|
11 | impl Options<Arg> for Settings {
| ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg`
|
note: required by a bound in `Options`
--> src/lib.rs
|
| pub trait Options<Arg: Arguments>: Sized {
| ^^^^^^^^^ required by this bound in `Options`
error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied
--> tests/derive/arg_key_value.rs:18:17
|
18 | Settings {}.parse(std::env::args_os()).unwrap();
| ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg`
|
note: required by a bound in `uutils_args::Options::parse`
--> src/lib.rs
|
| pub trait Options<Arg: Arguments>: Sized {
| ^^^^^^^^^ required by this bound in `Options::parse`
...
| fn parse<I>(mut self, args: I) -> Result<(Self, Vec<OsString>), Error>
| ----- required by a bound in this associated function
@@ -0,0 +1,19 @@
use uutils_args::{Arguments, Options};
#[derive(Arguments)]
enum Arg {
#[arg("--foo[=FOO")] // Oops!
Something(String),
}
struct Settings {}
impl Options<Arg> for Settings {
fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> {
Ok(())
}
}
fn main() {
Settings {}.parse(std::env::args_os()).unwrap();
}
@@ -0,0 +1,34 @@
error: proc-macro derive panicked
--> tests/derive/arg_missing_closing_bracket.rs:3:10
|
3 | #[derive(Arguments)]
| ^^^^^^^^^
|
= help: message: expected final ']' in flag pattern
error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied
--> tests/derive/arg_missing_closing_bracket.rs:11:6
|
11 | impl Options<Arg> for Settings {
| ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg`
|
note: required by a bound in `Options`
--> src/lib.rs
|
| pub trait Options<Arg: Arguments>: Sized {
| ^^^^^^^^^ required by this bound in `Options`
error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied
--> tests/derive/arg_missing_closing_bracket.rs:18:17
|
18 | Settings {}.parse(std::env::args_os()).unwrap();
| ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg`
|
note: required by a bound in `uutils_args::Options::parse`
--> src/lib.rs
|
| pub trait Options<Arg: Arguments>: Sized {
| ^^^^^^^^^ required by this bound in `Options::parse`
...
| fn parse<I>(mut self, args: I) -> Result<(Self, Vec<OsString>), Error>
| ----- required by a bound in this associated function
+19
View File
@@ -0,0 +1,19 @@
use uutils_args::{Arguments, Options};
#[derive(Arguments)]
enum Arg {
#[arg("--foo[FOO]")] // Oops!
Something(String),
}
struct Settings {}
impl Options<Arg> for Settings {
fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> {
Ok(())
}
}
fn main() {
Settings {}.parse(std::env::args_os()).unwrap();
}
+34
View File
@@ -0,0 +1,34 @@
error: proc-macro derive panicked
--> tests/derive/arg_missing_equals.rs:3:10
|
3 | #[derive(Arguments)]
| ^^^^^^^^^
|
= help: message: expected '=' after '[' in flag pattern
error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied
--> tests/derive/arg_missing_equals.rs:11:6
|
11 | impl Options<Arg> for Settings {
| ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg`
|
note: required by a bound in `Options`
--> src/lib.rs
|
| pub trait Options<Arg: Arguments>: Sized {
| ^^^^^^^^^ required by this bound in `Options`
error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied
--> tests/derive/arg_missing_equals.rs:18:17
|
18 | Settings {}.parse(std::env::args_os()).unwrap();
| ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg`
|
note: required by a bound in `uutils_args::Options::parse`
--> src/lib.rs
|
| pub trait Options<Arg: Arguments>: Sized {
| ^^^^^^^^^ required by this bound in `Options::parse`
...
| fn parse<I>(mut self, args: I) -> Result<(Self, Vec<OsString>), Error>
| ----- required by a bound in this associated function
+19
View File
@@ -0,0 +1,19 @@
use uutils_args::{Arguments, Options};
#[derive(Arguments)]
enum Arg {
#[arg("--foo[=FOO]")]
Something, // Oops!
}
struct Settings {}
impl Options<Arg> for Settings {
fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> {
Ok(())
}
}
fn main() {
Settings {}.parse(std::env::args_os()).unwrap();
}
+34
View File
@@ -0,0 +1,34 @@
error: proc-macro derive panicked
--> tests/derive/arg_missing_field.rs:3:10
|
3 | #[derive(Arguments)]
| ^^^^^^^^^
|
= help: message: Option cannot take a value if the variant doesn't have a field
error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied
--> tests/derive/arg_missing_field.rs:11:6
|
11 | impl Options<Arg> for Settings {
| ^^^^^^^^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg`
|
note: required by a bound in `Options`
--> src/lib.rs
|
| pub trait Options<Arg: Arguments>: Sized {
| ^^^^^^^^^ required by this bound in `Options`
error[E0277]: the trait bound `Arg: uutils_args::Arguments` is not satisfied
--> tests/derive/arg_missing_field.rs:18:17
|
18 | Settings {}.parse(std::env::args_os()).unwrap();
| ^^^^^ the trait `uutils_args::Arguments` is not implemented for `Arg`
|
note: required by a bound in `uutils_args::Options::parse`
--> src/lib.rs
|
| pub trait Options<Arg: Arguments>: Sized {
| ^^^^^^^^^ required by this bound in `Options::parse`
...
| fn parse<I>(mut self, args: I) -> Result<(Self, Vec<OsString>), Error>
| ----- required by a bound in this associated function
+23
View File
@@ -0,0 +1,23 @@
use uutils_args::{Arguments, Options};
struct Complicated {
// Doesn't "impl Default". Oops!
}
#[derive(Arguments)]
enum Arg {
#[arg("--foo")]
Something(Complicated),
}
struct Settings {}
impl Options<Arg> for Settings {
fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> {
Ok(())
}
}
fn main() {
Settings {}.parse(std::env::args_os()).unwrap();
}
+12
View File
@@ -0,0 +1,12 @@
error[E0277]: the trait bound `Complicated: Default` is not satisfied
--> tests/derive/arg_missing_metavar.rs:7:10
|
7 | #[derive(Arguments)]
| ^^^^^^^^^ the trait `Default` is not implemented for `Complicated`
|
= note: this error originates in the derive macro `Arguments` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider annotating `Complicated` with `#[derive(Default)]`
|
3 + #[derive(Default)]
4 | struct Complicated {
|
+17
View File
@@ -0,0 +1,17 @@
use uutils_args::{Arguments, Options};
#[derive(Arguments)]
#[arguments(file = "/")] // Oops!
enum Arg {}
struct Settings {}
impl Options<Arg> for Settings {
fn apply(&mut self, _arg: Arg) -> Result<(), uutils_args::Error> {
Ok(())
}
}
fn main() {
Settings {}.parse(std::env::args_os()).unwrap();
}

Some files were not shown because too many files have changed in this diff Show More