forked from bonsai/harakit
229 lines
7.2 KiB
Rust
229 lines
7.2 KiB
Rust
|
/*
|
||
|
* Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
|
||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify it under
|
||
|
* the terms of the GNU Affero General Public License as published by the Free
|
||
|
* Software Foundation, either version 3 of the License, or (at your option) any
|
||
|
* later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||
|
* details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Affero General Public License
|
||
|
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||
|
*
|
||
|
* This file incorporates work covered by the following copyright and permission
|
||
|
* notice:
|
||
|
* The Clear BSD License
|
||
|
*
|
||
|
* Copyright © 2017-2023 David Wildasin
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted (subject to the limitations in the disclaimer
|
||
|
* below) provided that the following conditions are met:
|
||
|
*
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions, and the following disclaimer.
|
||
|
*
|
||
|
* * Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions, and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
*
|
||
|
* * Neither the name of the copyright holder nor the names of its
|
||
|
* contributors may be used to endorse or promote products derived from
|
||
|
* this software without specific prior written permission.
|
||
|
*
|
||
|
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED
|
||
|
* BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
|
||
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*/
|
||
|
|
||
|
use crate::{Opt, Parser};
|
||
|
|
||
|
macro_rules! basic_test {
|
||
|
($name:ident, $expect:expr, $next:expr, [$($arg:expr),+], $optstr:expr) => (
|
||
|
#[test]
|
||
|
fn $name() -> Result<(), String> {
|
||
|
let expect: Option<Opt> = $expect;
|
||
|
let args: Vec<String> = vec![$($arg),+]
|
||
|
.into_iter()
|
||
|
.map(String::from)
|
||
|
.collect();
|
||
|
let next: Option<String> = $next;
|
||
|
let mut opts = Parser::new(&args, $optstr);
|
||
|
|
||
|
match opts.next().transpose() {
|
||
|
Err(error) => {
|
||
|
return Err(format!("next() returned {:?}", error))
|
||
|
},
|
||
|
Ok(actual) => if actual != expect {
|
||
|
return Err(
|
||
|
format!("expected {:?}; got {:?}", expect, actual)
|
||
|
)
|
||
|
},
|
||
|
};
|
||
|
|
||
|
match next {
|
||
|
None => if opts.index() < args.len() {
|
||
|
return Err(format!(
|
||
|
"expected end of args; got {:?}", args[opts.index()]
|
||
|
))
|
||
|
},
|
||
|
Some(n) => if args[opts.index()] != n {
|
||
|
return Err(format!(
|
||
|
"next arg: expected {:?}; got {:?}",
|
||
|
n,
|
||
|
args[opts.index()]
|
||
|
))
|
||
|
},
|
||
|
};
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
)
|
||
|
}
|
||
|
|
||
|
#[rustfmt::skip] basic_test!(
|
||
|
blank_arg, None, Some(String::new()), ["x", ""], "a"
|
||
|
);
|
||
|
#[rustfmt::skip] basic_test!(
|
||
|
double_dash, None, Some("-a".to_string()), ["x", "--", "-a", "foo"], "a"
|
||
|
);
|
||
|
#[rustfmt::skip] basic_test!(no_opts_1, None, None, ["x"], "a");
|
||
|
#[rustfmt::skip] basic_test!(
|
||
|
no_opts_2, None, Some("foo".to_string()), ["x", "foo"], "a"
|
||
|
);
|
||
|
#[rustfmt::skip] basic_test!(
|
||
|
no_opts_3, None, Some("foo".to_string()), ["x", "foo", "-a"], "a"
|
||
|
);
|
||
|
#[rustfmt::skip] basic_test!(
|
||
|
single_dash, None, Some("-".to_string()), ["x", "-", "-a", "foo"], "a"
|
||
|
);
|
||
|
#[rustfmt::skip] basic_test!(
|
||
|
single_opt,
|
||
|
Some(Opt('a', None)),
|
||
|
Some("foo".to_string()),
|
||
|
["x", "-a", "foo"],
|
||
|
"a"
|
||
|
);
|
||
|
#[rustfmt::skip] basic_test!(
|
||
|
single_optarg,
|
||
|
Some(Opt('a', Some("foo".to_string()))),
|
||
|
None,
|
||
|
["x", "-a", "foo"],
|
||
|
"a:"
|
||
|
);
|
||
|
|
||
|
macro_rules! error_test {
|
||
|
($name:ident, $expect:expr, [$($arg:expr),+], $optstr:expr) => (
|
||
|
#[test]
|
||
|
fn $name() -> Result<(), String> {
|
||
|
let expect: String = $expect.to_string();
|
||
|
let args: Vec<String> = vec![$($arg),+]
|
||
|
.into_iter()
|
||
|
.map(String::from)
|
||
|
.collect();
|
||
|
let mut opts = Parser::new(&args, $optstr);
|
||
|
|
||
|
match opts.next() {
|
||
|
None => {
|
||
|
return Err(format!(
|
||
|
"unexpected successful response: end of options"
|
||
|
))
|
||
|
},
|
||
|
Some(Err(actual)) => {
|
||
|
let actual = actual.to_string();
|
||
|
|
||
|
if actual != expect {
|
||
|
return Err(
|
||
|
format!("expected {:?}; got {:?}", expect, actual)
|
||
|
);
|
||
|
}
|
||
|
},
|
||
|
Some(Ok(opt)) => {
|
||
|
return Err(
|
||
|
format!("unexpected successful response: {:?}", opt)
|
||
|
)
|
||
|
},
|
||
|
};
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
)
|
||
|
}
|
||
|
|
||
|
#[rustfmt::skip] error_test!(
|
||
|
bad_opt,
|
||
|
"unknown option -- 'b'",
|
||
|
["x", "-b"],
|
||
|
"a"
|
||
|
);
|
||
|
|
||
|
#[rustfmt::skip] error_test!(
|
||
|
missing_optarg,
|
||
|
"option requires an argument -- 'a'",
|
||
|
["x", "-a"],
|
||
|
"a:"
|
||
|
);
|
||
|
|
||
|
#[test]
|
||
|
fn multiple() -> Result<(), String> {
|
||
|
let args: Vec<String> = vec!["x", "-abc", "-d", "foo", "-e", "bar"]
|
||
|
.into_iter()
|
||
|
.map(String::from)
|
||
|
.collect();
|
||
|
let optstring = "ab:d:e".to_string();
|
||
|
let mut opts = Parser::new(&args, &optstring);
|
||
|
|
||
|
macro_rules! check_result {
|
||
|
($expect:expr) => {
|
||
|
let expect: Option<Opt> = $expect;
|
||
|
match opts.next().transpose() {
|
||
|
Err(error) => {
|
||
|
return Err(format!("next() returned {:?}", error));
|
||
|
},
|
||
|
Ok(actual) => {
|
||
|
if actual != expect {
|
||
|
return Err(
|
||
|
format!("expected {:?}; got {:?}", expect, actual)
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
}
|
||
|
|
||
|
check_result!(Some(Opt('a', None)));
|
||
|
check_result!(Some(Opt('b', Some("c".to_string()))));
|
||
|
check_result!(Some(Opt('d', Some("foo".to_string()))));
|
||
|
check_result!(Some(Opt('e', None)));
|
||
|
check_result!(None);
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn continue_after_error() {
|
||
|
let args: Vec<String> = vec!["x", "-z", "-abc"]
|
||
|
.into_iter()
|
||
|
.map(String::from)
|
||
|
.collect();
|
||
|
let optstring = "ab:d:e".to_string();
|
||
|
for _opt in Parser::new(&args, &optstring) {
|
||
|
// do nothing, should not panic
|
||
|
}
|
||
|
}
|