// Copyright 2017 Lyndon Brown // // This file is part of the `gong` command-line argument processing library. // // Licensed under the MIT license or the Apache license (version 2.0), at your option. You may not // copy, modify, or distribute this file except in compliance with said license. You can find copies // of these licenses either in the LICENSE-MIT and LICENSE-APACHE files, or alternatively at // and // respectively. //! Testing “available” option set construction/modification //! //! Note, construction with macros is tested separately #[macro_use] extern crate gong; #[allow(unused_macros)] #[allow(dead_code)] //Mod shared across test crates #[macro_use] mod common; use gong::analysis::*; use gong::options::*; use common::{Actual, Expected, check_result, MODE_DEFAULT, ABBR_SUP_DEFAULT}; /// Check basic valid construction methods #[test] fn basic() { let mut opts = OptionSetEx::new(3, 3); opts.add_short('h') .add_short_data('o') .add_existing_short(gong_shortopt!('a', false)) .add_long("foo") .add_long_data("bar") .add_existing_long(gong_longopt!("foobar", false)); let expected = OptionSetEx { long: vec![ gong_longopt!("foo", false), gong_longopt!("bar", true), gong_longopt!("foobar", false), ], short: vec![ gong_shortopt!('h', false), gong_shortopt!('o', true), gong_shortopt!('a', false), ], mode: MODE_DEFAULT, allow_abbreviations: ABBR_SUP_DEFAULT, }; assert_eq!(opts, expected); assert!(opts.validate().is_ok()); } /// Check `is_empty` #[test] fn is_empty() { // Here, let's double-check that the derive of `Default` for fixed option sets is really an // empty set let opt_set = OptionSet::default(); assert!(opt_set.is_empty()); let opt_set = gong_option_set_fixed!(); assert!(opt_set.is_empty()); let opt_set = gong_option_set_fixed!([], []); assert!(opt_set.is_empty()); let opt_set = gong_option_set_fixed!([ gong_longopt!("foo", false) ], []); assert!(!opt_set.is_empty()); let opt_set = gong_option_set_fixed!([], [ gong_shortopt!('h', false) ]); assert!(!opt_set.is_empty()); let opt_set = gong_option_set_fixed!( [ gong_longopt!("foo", false) ], [ gong_shortopt!('h', false) ] ); assert!(!opt_set.is_empty()); } /// Check set type (`OptionSet`/`OptionSetEx`) conversion and comparison #[test] fn set_types() { let opts_fixed = OptionSet { long: &[ gong_longopt!("foo", false), gong_longopt!("bar", true), gong_longopt!("foobar", false), ], short: &[ gong_shortopt!('h', false), gong_shortopt!('o', true), gong_shortopt!('a', false), ], mode: MODE_DEFAULT, allow_abbreviations: ABBR_SUP_DEFAULT, }; let opts_extendible = OptionSetEx { long: vec![ gong_longopt!("foo", false), gong_longopt!("bar", true), gong_longopt!("foobar", false), ], short: vec![ gong_shortopt!('h', false), gong_shortopt!('o', true), gong_shortopt!('a', false), ], mode: MODE_DEFAULT, allow_abbreviations: ABBR_SUP_DEFAULT, }; // Check the two types can be compared assert_eq!(true, opts_fixed.eq(&opts_extendible)); assert_eq!(true, opts_extendible.eq(&opts_fixed)); // Check conversions let fixed_from_extendible: OptionSet = opts_extendible.as_fixed(); assert_eq!(true, opts_fixed.eq(&fixed_from_extendible)); assert_eq!(true, opts_extendible.eq(&fixed_from_extendible)); assert_eq!(true, fixed_from_extendible.eq(&opts_fixed)); assert_eq!(true, fixed_from_extendible.eq(&opts_extendible)); let extendible_from_fixed: OptionSetEx = opts_fixed.to_extendible(); assert_eq!(true, opts_fixed.eq(&extendible_from_fixed)); assert_eq!(true, opts_extendible.eq(&extendible_from_fixed)); assert_eq!(true, extendible_from_fixed.eq(&opts_fixed)); assert_eq!(true, extendible_from_fixed.eq(&opts_extendible)); let opts_fixed_2 = OptionSet { long: &[ gong_longopt!("blah", false), ], short: &[], mode: MODE_DEFAULT, allow_abbreviations: ABBR_SUP_DEFAULT, }; let opts_extendible_2 = OptionSetEx { long: vec![ gong_longopt!("blah", false), ], short: vec![], mode: MODE_DEFAULT, allow_abbreviations: ABBR_SUP_DEFAULT, }; // Verify not equal assert!(opts_fixed != opts_fixed_2); assert!(opts_fixed != opts_extendible_2); assert!(opts_extendible != opts_fixed_2); assert!(opts_extendible != opts_extendible_2); } /// Dash (`-`) is an invalid short option (clashes with early terminator if it were given on its own /// (`--`), and would be misinterpreted as a long option if given as the first in a short option set /// (`--abc`)). mod short_dash { use super::*; #[test] #[cfg_attr(debug_assertions, should_panic)] fn add_short() { let mut opts = OptionSetEx::new(0, 1); opts.add_short('-'); // Should panic here in debug mode! } #[test] #[cfg_attr(debug_assertions, should_panic)] fn add_short_data() { let mut opts = OptionSetEx::new(0, 1); opts.add_short_data('-'); // Should panic here in debug mode! } #[test] fn add_short_existing() { let mut opts = OptionSetEx::new(0, 1); opts.add_existing_short(gong_shortopt!('-', false)); // Should work, no validation done } /// Bypassing add methods, check validation fails #[test] fn invalid_set() { let opts = gong_option_set_fixed!( [], [ gong_shortopt!('a'), gong_shortopt!('-'), gong_shortopt!('b'), ] ); assert_eq!(false, opts.is_valid()); assert_eq!(opts.validate(), Err(vec![ OptionFlaw::ShortDash ])); } /// Check behaviour when validity check bypassed. /// /// This situation is an invalid use case, the user should always validate their option set; /// this test verifies that things behave though as we expect if the set is invalid due to a /// short that is a dash (`-`). /// /// The expected behaviour is this: If the first char in an argument is a dash, then as long as /// the second char is not also a dash, then it will succeed in matching as a short option. If /// an attempt is made to use a dash in a short-opt set as the first one in the set, thus the /// argument starts with two dashes, it will then be taken to be either a long option or early /// terminator, as appropriate, giving no consideration to the possibility of it being a short /// option. #[test] fn bypassed_processing() { let args = arg_list!( "--abc", // Can’t use as a shortopt like this, will be interpreted as long opt "-a-bc", // Can use like this "--", // Can’t use as a shortopt like this, will be interpreted as early terminator ); let expected = expected!( error: false, warn: true, [ expected_item!(0, UnknownLong, "abc"), expected_item!(1, UnknownShort, 'a'), expected_item!(1, Short, '-'), expected_item!(1, UnknownShort, 'b'), expected_item!(1, UnknownShort, 'c'), expected_item!(2, EarlyTerminator), ] ); // Using a custom **invalid** option set (short is '-') let opts = gong_option_set_fixed!([], [ gong_shortopt!('-') ]); //assert!(opts.validate().is_ok()); DISABLED! WHAT HAPPENS NEXT? LET’S SEE... check_result(&Actual(opts.process(&args)), &expected); } } /// An empty string is not a valid long-option name property mod long_no_name { use super::*; #[test] #[cfg_attr(debug_assertions, should_panic)] fn add_long() { let mut opts = OptionSetEx::new(1, 0); opts.add_long(""); // Should panic here in debug mode! } #[test] #[cfg_attr(debug_assertions, should_panic)] fn add_long_data() { let mut opts = OptionSetEx::new(1, 0); opts.add_long_data(""); // Should panic here in debug mode! } #[test] fn add_long_existing() { let mut opts = OptionSetEx::new(1, 0); opts.add_existing_long(gong_longopt!("", false)); // Should work, no validation done } /// Bypassing add methods, check validation fails #[test] fn invalid_set() { let opts = gong_option_set_fixed!( [ gong_longopt!("foo"), gong_longopt!(""), gong_longopt!("bar"), ], [] ); assert_eq!(false, opts.is_valid()); assert_eq!(opts.validate(), Err(vec![ OptionFlaw::LongEmpty ])); } } /// Long option names cannot contain an `=` (used for declaring a data sub-argument in the same /// argument; if names could contain an `=`, as data can, we would not know where to do the split, /// complicating matching. mod long_equals { use super::*; #[test] #[cfg_attr(debug_assertions, should_panic)] fn add_long() { let mut opts = OptionSetEx::new(1, 0); opts.add_long("a=b"); // Should panic here in debug mode! } #[test] #[cfg_attr(debug_assertions, should_panic)] fn add_long_data() { let mut opts = OptionSetEx::new(1, 0); opts.add_long_data("a=b"); // Should panic here in debug mode! } #[test] fn add_long_existing() { let mut opts = OptionSetEx::new(1, 0); opts.add_existing_long(gong_longopt!("=", false)); // Should work, no validation done } /// Bypassing add methods, check validation fails #[test] fn invalid_set() { let opts = gong_option_set_fixed!( [ gong_longopt!("foo"), gong_longopt!("a=b"), gong_longopt!("bar"), ], [] ); assert_eq!(false, opts.is_valid()); assert_eq!(opts.validate(), Err(vec![ OptionFlaw::LongIncludesEquals("a=b") ])); } /// Check behaviour when validity check bypassed. /// /// This situation is an invalid use case, the user should always validate their option set; /// this test verifies that things behave though as we expect if the set is invalid due to a /// long with an equals (`=`). #[test] fn bypassed_processing() { let args = arg_list!( "--a", // This should match against the “a=b” invalid option as an abbreviation "--a=b", // Here, this is a long option with “in-arg” data, thus the name is “a”, // which again therefore matches the invalid “a=b” option, as an // abbreviation, but carrying “b” as data. ); let expected = expected!( error: false, warn: true, [ expected_item!(0, Long, "a=b"), expected_item!(1, LongWithUnexpectedData, "a=b", "b"), ] ); // Using a custom **invalid** option set (long name contains `=`) let opts = gong_option_set_fixed!([ gong_longopt!("a=b") ], []); //assert!(opts.validate().is_ok()); DISABLED! WHAT HAPPENS NEXT? LET’S SEE... check_result(&Actual(opts.process(&args)), &expected); } } /// Duplicates pose a potential problem due to potential for confusion over differing `expects_data` /// attributes. They also can result from option name-clashing bugs with programs that dynamically /// generate (large) option sets (rare? VLC media player is one example, which dynamically builds an /// option set including options from plugins). An option set containing duplicates is thus /// considered invalid. mod duplicates { use super::*; #[test] fn short() { let mut opts = OptionSetEx::new(0, 8); opts.add_short('a') .add_short('b') .add_short('c') .add_short('c') // dup .add_short_data('d') .add_short_data('b') // dup (ignore data indicator) .add_short('e') .add_short('b'); // dup assert_eq!(false, opts.is_valid()); assert_eq!(opts.validate(), Err(vec![ OptionFlaw::ShortDup('c'), OptionFlaw::ShortDup('b'), ])); } #[test] fn long() { let mut opts = OptionSetEx::new(8, 0); opts.add_long("aaa") .add_long("bbb") .add_long("ccc") .add_long("ccc") // dup .add_long_data("ddd") .add_long_data("bbb") // dup (ignore data indicator) .add_long("eee") .add_long("bbb"); // dup assert_eq!(false, opts.is_valid()); assert_eq!(opts.validate(), Err(vec![ OptionFlaw::LongDup("ccc"), OptionFlaw::LongDup("bbb"), ])); } }