You've already forked uutils-args
mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
derive: split 'compile_error' toy into many test cases, demonstrate new issues
This commit is contained in:
@@ -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 = []
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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_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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 {
|
||||
|
|
||||
@@ -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
Reference in New Issue
Block a user