/* * Copyright (c) 2023 Emma Tebibyte * 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 = $expect; let args: Vec = vec![$($arg),+] .into_iter() .map(String::from) .collect(); let next: Option = $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 = 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 = 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 = $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 = 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 } }