#!/usr/bin/perl BEGIN { die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; }; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; use OptionParser; use DSNParser; use PerconaTest; use Test::More tests => 162; my $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME ", ); isa_ok($o, 'OptionParser'); my @opt_specs; my %opts; my $output; # Prevent print_usage() from breaking lines in the first paragraph # at 80 chars width. This paragraph contains $PROGRAM_NAME and that # becomes too long when this test is ran from trunk, causing a break. # To make this test path-independent, we don't break lines. This only # affects testing. $ENV{DONT_BREAK_LINES} = 1; # Some tests need a DSNParser but we don't provide a POD with a # DSN OPTIONS section that would cause the OptionParser to create # a DSNParser automatically. So we create the DSNParser manually # and hack it into the OptionParser. my $dsn_opts = [ { key => 'A', desc => 'Default character set', dsn => 'charset', copy => 1, }, { key => 'D', desc => 'Database to use', dsn => 'database', copy => 1, }, { key => 'F', desc => 'Only read default options from the given file', dsn => 'mysql_read_default_file', copy => 1, }, { key => 'h', desc => 'Connect to host', dsn => 'host', copy => 1, }, { key => 'p', desc => 'Password to use when connecting', dsn => 'password', copy => 1, }, { key => 'P', desc => 'Port number to use for connection', dsn => 'port', copy => 1, }, { key => 'S', desc => 'Socket file to use for connection', dsn => 'mysql_socket', copy => 1, }, { key => 'u', desc => 'User for login if not current user', dsn => 'user', copy => 1, }, ]; my $dp = new DSNParser(opts => $dsn_opts); # ############################################################################# # Test basic usage. # ############################################################################# # Quick test of standard interface. $o->get_specs("$trunk/t/lib/samples/pod/pod_sample_01.txt"); %opts = $o->opts(); ok( exists $opts{help}, 'get_specs() basic interface' ); # More exhaustive test of how the standard interface works internally. $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME ", ); ok(!$o->has('time'), 'There is no --time yet'); @opt_specs = $o->_pod_to_specs("$trunk/t/lib/samples/pod/pod_sample_01.txt"); # hacky workaround for the next test: # since now opt_specs includes the attributes hash, but it's a pain to # check for all of those, we simply remove it. @opt_specs = map {delete $_->{'attributes'}; $_ } @opt_specs; is_deeply( \@opt_specs, [ { spec=>'database|D=s', group=>'default', desc=>'database string', }, { spec=>'port|p=i', group=>'default', desc=>'port (default 3306)', }, { spec=>'price=f', group=>'default', desc=>'price float (default 1.23)' }, { spec=>'hash-req=H', group=>'default', desc=>'hash that requires a value' }, { spec=>'hash-opt=h', group=>'default', desc=>'hash with an optional value'}, { spec=>'array-req=A',group=>'default', desc=>'array that requires a value'}, { spec=>'array-opt=a',group=>'default',desc=>'array with an optional value'}, { spec=>'host=d', group=>'default', desc=>'host DSN' }, { spec=>'chunk-size=z', group=>'default', desc=>'chunk size' }, { spec=>'time=m', group=>'default', desc=>'time' }, { spec=>'help+', group=>'default', desc=>'help cumulative' }, { spec=>'other!', group=>'default', desc=>'other negatable' }, ], 'Convert POD OPTIONS to opt specs (pod_sample_01.txt)', ); $o->_parse_specs(@opt_specs); ok($o->has('time'), 'There is a --time now'); %opts = $o->opts(); is_deeply( \%opts, { 'database' => { spec => 'database|D=s', desc => 'database string', group => 'default', long => 'database', short => 'D', is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 's', got => 0, value => undef, attributes => {}, }, 'port' => { spec => 'port|p=i', desc => 'port (default 3306)', group => 'default', long => 'port', short => 'p', is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 'i', got => 0, value => undef, attributes => {}, }, 'price' => { spec => 'price=f', desc => 'price float (default 1.23)', group => 'default', long => 'price', short => undef, is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 'f', got => 0, value => undef, attributes => {}, }, 'hash-req' => { spec => 'hash-req=s', desc => 'hash that requires a value', group => 'default', long => 'hash-req', short => undef, is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 'H', got => 0, value => undef, attributes => {}, }, 'hash-opt' => { spec => 'hash-opt=s', desc => 'hash with an optional value', group => 'default', long => 'hash-opt', short => undef, is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 'h', got => 0, value => undef, attributes => {}, }, 'array-req' => { spec => 'array-req=s', desc => 'array that requires a value', group => 'default', long => 'array-req', short => undef, is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 'A', got => 0, value => undef, attributes => {}, }, 'array-opt' => { spec => 'array-opt=s', desc => 'array with an optional value', group => 'default', long => 'array-opt', short => undef, is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 'a', got => 0, value => undef, attributes => {}, }, 'host' => { spec => 'host=s', desc => 'host DSN', group => 'default', long => 'host', short => undef, is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 'd', got => 0, value => undef, attributes => {}, }, 'chunk-size' => { spec => 'chunk-size=s', desc => 'chunk size', group => 'default', long => 'chunk-size', short => undef, is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 'z', got => 0, value => undef, attributes => {}, }, 'time' => { spec => 'time=s', desc => 'time', group => 'default', long => 'time', short => undef, is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 'm', got => 0, value => undef, attributes => {}, }, 'help' => { spec => 'help+', desc => 'help cumulative', group => 'default', long => 'help', short => undef, is_cumulative => 1, is_negatable => 0, is_required => 0, is_repeatable => 0, type => undef, got => 0, value => undef, attributes => {}, }, 'other' => { spec => 'other!', desc => 'other negatable', group => 'default', long => 'other', short => undef, is_cumulative => 0, is_negatable => 1, is_required => 0, is_repeatable => 0, type => undef, got => 0, value => undef, attributes => {}, } }, 'Parse opt specs' ); %opts = $o->short_opts(); is_deeply( \%opts, { 'D' => 'database', 'p' => 'port', }, 'Short opts => log opts' ); # get() single option is( $o->get('database'), undef, 'Get valueless long opt' ); is( $o->get('p'), undef, 'Get valuless short opt' ); eval { $o->get('foo'); }; like( $EVAL_ERROR, qr/Option foo does not exist/, 'Die trying to get() nonexistent long opt' ); eval { $o->get('x'); }; like( $EVAL_ERROR, qr/Option x does not exist/, 'Die trying to get() nonexistent short opt' ); # set() $o->set('database', 'foodb'); is( $o->get('database'), 'foodb', 'Set long opt' ); $o->set('p', 12345); is( $o->get('p'), 12345, 'Set short opt' ); eval { $o->set('foo', 123); }; like( $EVAL_ERROR, qr/Option foo does not exist/, 'Die trying to set() nonexistent long opt' ); eval { $o->set('x', 123); }; like( $EVAL_ERROR, qr/Option x does not exist/, 'Die trying to set() nonexistent short opt' ); # got() @ARGV = qw(--port 12345); $o->get_opts(); is( $o->got('port'), 1, 'Got long opt' ); is( $o->got('p'), 1, 'Got short opt' ); is( $o->got('database'), 0, 'Did not "got" long opt' ); is( $o->got('D'), 0, 'Did not "got" short opt' ); eval { $o->got('foo'); }; like( $EVAL_ERROR, qr/Option foo does not exist/, 'Die trying to got() nonexistent long opt', ); eval { $o->got('x'); }; like( $EVAL_ERROR, qr/Option x does not exist/, 'Die trying to got() nonexistent short opt', ); @ARGV = qw(--bar); eval { local *STDERR; open STDERR, '>', '/dev/null'; $o->get_opts(); $o->get('bar'); }; like( $EVAL_ERROR, qr/Option bar does not exist/, 'Ignore nonexistent opt given on cmd line' ); @ARGV = qw(--port 12345); $o->get_opts(); is_deeply( $o->errors(), [], 'get_opts() resets errors' ); ok( $o->has('D'), 'Has short opt' ); ok( !$o->has('X'), 'Does not "has" nonexistent short opt' ); # ############################################################################# # Test hostile, broken usage. # ############################################################################# eval { $o->_pod_to_specs("$trunk/t/lib/samples/pod/pod_sample_02.txt"); }; like( $EVAL_ERROR, qr/POD has no OPTIONS section/, 'Dies on POD without an OPTIONS section' ); eval { $o->_pod_to_specs("$trunk/t/lib/samples/pod/pod_sample_03.txt"); }; like( $EVAL_ERROR, qr/No valid specs in OPTIONS/, 'Dies on POD with an OPTIONS section but no option items' ); eval { $o->_pod_to_specs("$trunk/t/lib/samples/pod/pod_sample_04.txt"); }; like( $EVAL_ERROR, qr/No description after option spec foo/, 'Dies on option with no description' ); # TODO: more hostile tests: duplicate opts, can't parse long opt from spec, # unrecognized rules, ... # ############################################################################# # Test option defaults. # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME [OPTIONS]", ); # These are dog opt specs. They're used by other tests below. $o->_parse_specs( { spec => 'defaultset!', desc => 'alignment test with a very long thing ' . 'that is longer than 80 characters wide ' . 'and must be wrapped' }, { spec => 'defaults-file|F=s', desc => 'alignment test' }, { spec => 'dog|D=s', desc => 'Dogs are fun' }, { spec => 'foo!', desc => 'Foo' }, { spec => 'love|l+', desc => 'And peace' }, ); is_deeply( $o->get_defaults(), {}, 'No default defaults', ); $o->set_defaults(foo => 1); is_deeply( $o->get_defaults(), { foo => 1, }, 'set_defaults() with values' ); $o->set_defaults(); is_deeply( $o->get_defaults(), {}, 'set_defaults() without values unsets defaults' ); # We've already tested opt spec parsing, # but we do it again for thoroughness. %opts = $o->opts(); is_deeply( \%opts, { 'foo' => { spec => 'foo!', desc => 'Foo', group => 'default', long => 'foo', short => undef, is_cumulative => 0, is_negatable => 1, is_required => 0, is_repeatable => 0, type => undef, got => 0, value => undef, attributes => {}, }, 'defaultset' => { spec => 'defaultset!', desc => 'alignment test with a very long thing ' . 'that is longer than 80 characters wide ' . 'and must be wrapped', group => 'default', long => 'defaultset', short => undef, is_cumulative => 0, is_negatable => 1, is_required => 0, is_repeatable => 0, type => undef, got => 0, value => undef, attributes => {}, }, 'defaults-file' => { spec => 'defaults-file|F=s', desc => 'alignment test', group => 'default', long => 'defaults-file', short => 'F', is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 's', got => 0, value => undef, attributes => {}, }, 'dog' => { spec => 'dog|D=s', desc => 'Dogs are fun', group => 'default', long => 'dog', short => 'D', is_cumulative => 0, is_negatable => 0, is_required => 0, is_repeatable => 0, type => 's', got => 0, value => undef, attributes => {}, }, 'love' => { spec => 'love|l+', desc => 'And peace', group => 'default', long => 'love', short => 'l', is_cumulative => 1, is_negatable => 0, is_required => 0, is_repeatable => 0, type => undef, got => 0, value => undef, attributes => {}, }, }, 'Parse dog specs' ); $o->set_defaults('dog' => 'fido'); @ARGV = (); $o->get_opts(); is( $o->get('dog'), 'fido', 'Opt gets default value' ); is( $o->got('dog'), 0, 'Did not "got" opt with default value' ); @ARGV = qw(--dog rover); $o->get_opts(); is( $o->get('dog'), 'rover', 'Value given on cmd line overrides default value' ); eval { $o->set_defaults('bone' => 1) }; like( $EVAL_ERROR, qr/Cannot set default for nonexistent option bone/, 'Cannot set default for nonexistent option' ); # ############################################################################# # Test option attributes negatable and cumulative. # ############################################################################# # These tests use the dog opt specs from above. @ARGV = qw(--nofoo); $o->get_opts(); is( $o->get('foo'), 0, 'Can negate negatable opt like --nofoo' ); @ARGV = qw(--no-foo); $o->get_opts(); is( $o->get('foo'), 0, 'Can negate negatable opt like --no-foo' ); @ARGV = qw(--nodog); { local *STDERR; open STDERR, '>', '/dev/null'; $o->get_opts(); } is_deeply( $o->get('dog'), undef, 'Cannot negate non-negatable opt' ); is_deeply( $o->errors(), ['Error parsing options'], 'Trying to negate non-negatable opt sets an error' ); @ARGV = (); $o->get_opts(); is( $o->get('love'), 0, 'Cumulative defaults to 0 when not given' ); @ARGV = qw(--love -l -l); $o->get_opts(); is( $o->get('love'), 3, 'Cumulative opt val increases (--love -l -l)' ); is( $o->got('love'), 1, "got('love') when given multiple times short and long" ); @ARGV = qw(--love); $o->get_opts(); is( $o->got('love'), 1, "got('love') long once" ); @ARGV = qw(-l); $o->get_opts(); is( $o->got('l'), 1, "got('l') short once" ); # ############################################################################# # Test usage output. # ############################################################################# # The following one test uses the dog opt specs from above. my $prog_name = $PROGRAM_NAME; $prog_name =~ s/([\.\/\\])/\\$1/g; my $home_esc = $ENV{HOME}; $home_esc =~ s/([\.\/\\])/\\$1/g; my $trunk_esc = $trunk; $trunk_esc =~ s/([\.\/\\])/\\$1/g; sub check_usage { my ( $o, $file ) = @_; die "I need an OptionParser object" unless $o; die "I need a file name" unless $file; no_diff( $o->print_usage(), "t/lib/samples/OptionParser/$file", cmd_output => 1, sed => [ "'s/ $prog_name/ \$PROGRAM_NAME/g'", "'s/$trunk_esc/\$trunk/g'", "'s/$home_esc/\$ENV\{HOME\}/g'", ], ), } # Clear values from previous tests. $o->set_defaults(); @ARGV = (); $o->get_opts(); ok( check_usage($o, "help001.txt"), 'Options aligned and custom prompt included' ); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME ", ); $o->_parse_specs( { spec => 'database|D=s', desc => 'Specify the database for all tables' }, { spec => 'nouniquechecks!', desc => 'Set UNIQUE_CHECKS=0 before LOAD DATA INFILE' }, ); $o->get_opts(); ok( check_usage($o, "help002.txt"), 'Really long option aligns with shorts, and prompt defaults to ' ); # ############################################################################# # Test _get_participants() # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME ", ); $o->_parse_specs( { spec => 'foo', desc => 'opt' }, { spec => 'bar-bar!', desc => 'opt' }, { spec => 'baz', desc => 'opt' }, ); is_deeply( [ $o->_get_participants('L<"--foo"> disables --bar-bar and C<--baz>') ], [qw(foo bar-bar baz)], 'Extract option names from a string', ); is_deeply( [ $o->_get_participants('L<"--foo"> disables L<"--[no]bar-bar">.') ], [qw(foo bar-bar)], 'Extract [no]-negatable option names from a string', ); # TODO: test w/ opts that don't exist, or short opts # ############################################################################# # Test required options. # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME ", ); $o->_parse_specs( { spec => 'cat|C=s', desc => 'How to catch the cat; required' } ); @ARGV = (); $o->get_opts(); is_deeply( $o->errors(), ['Required option --cat must be specified'], 'Missing required option sets an error', ); is( $o->print_errors(), "Usage: $PROGRAM_NAME Errors in command-line arguments: * Required option --cat must be specified OptionParser.t parses command line options. For more details, please use the --help option, or try 'perldoc $PROGRAM_NAME' for complete documentation.", 'Error output includes note about missing required option' ); @ARGV = qw(--cat net); $o->get_opts(); is( $o->get('cat'), 'net', 'Required option OK', ); # ############################################################################# # Test option rules. # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME ", ); $o->_parse_specs( { spec => 'ignore|i', desc => 'Use IGNORE for INSERT statements' }, { spec => 'replace|r', desc => 'Use REPLACE instead of INSERT statements' }, '--ignore and --replace are mutually exclusive.', ); $o->get_opts(); ok( check_usage($o, "help003.txt"), 'Usage with rules' ); @ARGV = qw(--replace); $o->get_opts(); is_deeply( $o->errors(), [], '--replace does not trigger an error', ); @ARGV = qw(--ignore --replace); $o->get_opts(); is_deeply( $o->errors(), ['--ignore and --replace are mutually exclusive.'], 'Error set when rule violated', ); # These are used several times in the follow tests. my @ird_specs = ( { spec => 'ignore|i', desc => 'Use IGNORE for INSERT statements' }, { spec => 'replace|r', desc => 'Use REPLACE instead of INSERT statements' }, { spec => 'delete|d', desc => 'Delete' }, ); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME ", ); $o->_parse_specs( @ird_specs, '--ignore, --replace and --delete are mutually exclusive.', ); @ARGV = qw(--ignore --replace); $o->get_opts(); is_deeply( $o->errors(), ['--ignore, --replace and --delete are mutually exclusive.'], 'Error set with long opt name and nice commas when rule violated', ); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME ", ); eval { $o->_parse_specs( @ird_specs, 'Use one and only one of --insert, --replace, or --delete.', ); }; like( $EVAL_ERROR, qr/Option --insert does not exist/, 'Die on using nonexistent option in one-and-only-one rule' ); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME ", ); $o->_parse_specs( @ird_specs, 'Use one and only one of --ignore, --replace, or --delete.', ); @ARGV = qw(--ignore --replace); $o->get_opts(); is_deeply( $o->errors(), ['--ignore, --replace and --delete are mutually exclusive.'], 'Error set with one-and-only-one rule violated', ); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( @ird_specs, 'Use one and only one of --ignore, --replace, or --delete.', ); @ARGV = (); $o->get_opts(); is_deeply( $o->errors(), ['Specify at least one of --ignore, --replace or --delete'], 'Error set with one-and-only-one when none specified', ); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( @ird_specs, 'Use at least one of --ignore, --replace, or --delete.', ); @ARGV = (); $o->get_opts(); is_deeply( $o->errors(), ['Specify at least one of --ignore, --replace or --delete'], 'Error set with at-least-one when none specified', ); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( @ird_specs, 'Use at least one of --ignore, --replace, or --delete.', ); @ARGV = qw(-ir); $o->get_opts(); ok( $o->get('ignore') == 1 && $o->get('replace') == 1, 'Multiple options OK for at-least-one', ); use Data::Dumper; $Data::Dumper::Indent=1; $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'foo=i', desc => 'Foo disables --bar' }, { spec => 'bar', desc => 'Bar (default 1)' }, ); @ARGV = qw(--foo 5); $o->get_opts(); is_deeply( [ $o->get('foo'), $o->get('bar') ], [ 5, undef ], '--foo disables --bar', ); %opts = $o->opts(); is_deeply( $opts{'bar'}, { spec => 'bar', is_required => 0, value => undef, is_cumulative => 0, short => undef, group => 'default', got => 0, is_negatable => 0, is_repeatable => 0, desc => 'Bar (default 1)', long => 'bar', type => undef, parsed => 1, attributes => {}, }, 'Disabled opt is not destroyed' ); # Option can't disable a nonexistent option. $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); eval { $o->_parse_specs( { spec => 'foo=i', desc => 'Foo disables --fox' }, { spec => 'bar', desc => 'Bar (default 1)' }, ); }; like( $EVAL_ERROR, qr/Option --fox does not exist/, 'Invalid option name in disable rule', ); # Option can't 'allowed with' a nonexistent option. $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); eval { $o->_parse_specs( { spec => 'foo=i', desc => 'Foo disables --bar' }, { spec => 'bar', desc => 'Bar (default 0)' }, 'allowed with --foo: --fox', ); }; like( $EVAL_ERROR, qr/Option --fox does not exist/, 'Invalid option name in \'allowed with\' rule', ); # ############################################################################# # Test default values encoded in description. # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); # Hack DSNParser into OptionParser. This is just for testing. $o->{DSNParser} = $dp; $o->_parse_specs( { spec => 'foo=i', desc => 'Foo (default 5)' }, { spec => 'bar', desc => 'Bar (default)' }, { spec => 'price=f', desc => 'Price (default 12345.123456)' }, { spec => 'size=z', desc => 'Size (default 128M)' }, { spec => 'time=m', desc => 'Time (default 24h)' }, { spec => 'host=d', desc => 'Host (default h=127.1,P=12345)' }, ); @ARGV = (); $o->get_opts(); is( $o->get('foo'), 5, 'Default integer value encoded in description' ); is( $o->get('bar'), 1, 'Default option enabled encoded in description' ); is( $o->get('price'), 12345.123456, 'Default float value encoded in description' ); is( $o->get('size'), 134217728, 'Default size value encoded in description' ); is( $o->get('time'), 86400, 'Default time value encoded in description' ); is_deeply( $o->get('host'), { S => undef, F => undef, A => undef, p => undef, u => undef, h => '127.1', D => undef, P => '12345' }, 'Default host value encoded in description' ); is( $o->got('foo'), 0, 'Did not "got" --foo with encoded default' ); is( $o->got('bar'), 0, 'Did not "got" --bar with encoded default' ); is( $o->got('price'), 0, 'Did not "got" --price with encoded default' ); is( $o->got('size'), 0, 'Did not "got" --size with encoded default' ); is( $o->got('time'), 0, 'Did not "got" --time with encoded default' ); is( $o->got('host'), 0, 'Did not "got" --host with encoded default' ); # ############################################################################# # Test size option type. # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'size=z', desc => 'size' } ); @ARGV = qw(--size 5k); $o->get_opts(); is_deeply( $o->get('size'), 1024*5, '5K expanded', ); @ARGV = qw(--size -5k); $o->get_opts(); is_deeply( $o->get('size'), -1024*5, '-5K expanded', ); @ARGV = qw(--size +5k); $o->get_opts(); is_deeply( $o->get('size'), '+' . (1024*5), '+5K expanded', ); @ARGV = qw(--size 5); $o->get_opts(); is_deeply( $o->get('size'), 5, '5 expanded', ); @ARGV = qw(--size 5z); $o->get_opts(); is_deeply( $o->errors(), ['Invalid size for --size: 5z'], 'Bad size argument sets an error', ); # ############################################################################# # Test time option type. # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 't=m', desc => 'Time' }, { spec => 's=m', desc => 'Time (suffix s)' }, { spec => 'm=m', desc => 'Time (suffix m)' }, { spec => 'h=m', desc => 'Time (suffix h)' }, { spec => 'd=m', desc => 'Time (suffix d)' }, ); @ARGV = qw(-t 10 -s 20 -m 30 -h 40 -d 50); $o->get_opts(); is_deeply( $o->get('t'), 10, 'Time value with default suffix decoded', ); is_deeply( $o->get('s'), 20, 'Time value with s suffix decoded', ); is_deeply( $o->get('m'), 30*60, 'Time value with m suffix decoded', ); is_deeply( $o->get('h'), 40*3600, 'Time value with h suffix decoded', ); is_deeply( $o->get('d'), 50*86400, 'Time value with d suffix decoded', ); @ARGV = qw(-d 5m); $o->get_opts(); is_deeply( $o->get('d'), 5*60, 'Explicit suffix overrides default suffix' ); # Use shorter, simpler specs to test usage for time blurb. $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME ", ); $o->_parse_specs( { spec => 'foo=m', desc => 'Time' }, { spec => 'bar=m', desc => 'Time (suffix m)' }, ); $o->get_opts(); ok( check_usage($o, "help004.txt"), 'Usage for time value' ); @ARGV = qw(--foo 5z); $o->get_opts(); is_deeply( $o->errors(), ['Invalid time suffix for --foo'], 'Bad time argument sets an error', ); # ############################################################################# # Test DSN option type. # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); # Hack DSNParser into OptionParser. This is just for testing. $o->{DSNParser} = $dp; $o->_parse_specs( { spec => 'foo=d', desc => 'DSN foo' }, { spec => 'bar=d', desc => 'DSN bar' }, 'DSN values in --foo default to values in --bar if COPY is yes.', ); $o->get_opts(); ok( check_usage($o, "help005.txt"), 'DSN is integrated into help output' ); @ARGV = ('--bar', 'D=DB,u=USER,h=localhost', '--foo', 'h=otherhost'); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); # Hack DSNParser into OptionParser. This is just for testing. $o->{DSNParser} = $dp; $o->_parse_specs( { spec => 'foo=d', desc => 'DSN foo' }, { spec => 'bar=d', desc => 'DSN bar' }, 'DSN values in --foo default to values in --bar if COPY is yes.', ); $o->get_opts(); is_deeply( $o->get('bar'), { D => 'DB', u => 'USER', S => undef, F => undef, P => undef, h => 'localhost', p => undef, A => undef, }, 'DSN parsing on type=d', ); is_deeply( $o->get('foo'), { D => 'DB', u => 'USER', S => undef, F => undef, P => undef, h => 'otherhost', p => undef, A => undef, }, 'DSN parsing on type=d inheriting from --bar', ); ok( check_usage($o, "help006.txt"), 'DSN stringified with inheritance into post-processed args' ); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); # Hack DSNParser into OptionParser. This is just for testing. $o->{DSNParser} = $dp; $o->_parse_specs( { spec => 'foo|f=d', desc => 'DSN foo' }, { spec => 'bar|b=d', desc => 'DSN bar' }, 'DSN values in --foo default to values in --bar if COPY is yes.', ); @ARGV = ('-b', 'D=DB,u=USER,h=localhost', '-f', 'h=otherhost'); $o->get_opts(); is_deeply( $o->get('f'), { D => 'DB', u => 'USER', S => undef, F => undef, P => undef, h => 'otherhost', p => undef, A => undef, }, 'DSN parsing on type=d inheriting from --bar with short options', ); # ############################################################################# # Test [Hh]ash and [Aa]rray option types. # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'columns|C=H', desc => 'cols required' }, { spec => 'tables|t=h', desc => 'tables optional' }, { spec => 'databases|d=A', desc => 'databases required' }, { spec => 'books|b=a', desc => 'books optional' }, { spec => 'foo=A', desc => 'foo (default a,b,c)' }, ); @ARGV = (); $o->get_opts(); is_deeply( $o->get('C'), {}, 'required Hash' ); is_deeply( $o->get('t'), undef, 'optional hash' ); is_deeply( $o->get('d'), [], 'required Array' ); is_deeply( $o->get('b'), undef, 'optional array' ); is_deeply($o->get('foo'), [qw(a b c)], 'Array got a default'); @ARGV = ('-C', 'a,b', '-t', 'd, e', '-d', 'f,g', '-b', 'o,p' ); $o->get_opts(); %opts = ( C => $o->get('C'), t => $o->get('t'), d => $o->get('d'), b => $o->get('b'), ); is_deeply( \%opts, { C => { a => 1, b => 1 }, t => { d => 1, e => 1 }, d => [qw(f g)], b => [qw(o p)], }, 'Comma-separated lists: all processed when given', ); ok( check_usage($o, "help007.txt"), 'Lists properly expanded into usage information', ); # ############################################################################# # Test groups. # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->get_specs("$trunk/t/lib/samples/pod/pod_sample_05.txt"); is_deeply( $o->get_groups(), { 'Help' => { 'explain-hosts' => 1, 'help' => 1, 'version' => 1, }, 'Filter' => { 'databases' => 1, }, 'Output' => { 'tab' => 1, }, 'Connection' => { 'defaults-file' => 1, }, 'default' => { 'algorithm' => 1, 'schema' => 1, } }, 'get_groups()' ); @ARGV = (); $o->get_opts(); ok( check_usage($o, "help008.txt"), 'Option groupings usage', ); @ARGV = qw(--schema --tab); $o->get_opts(); ok( $o->get('schema') && $o->get('tab'), 'Opt allowed with opt from allowed group' ); @ARGV = qw(--schema --algorithm ACCUM); $o->get_opts(); is_deeply( $o->errors(), ['--schema is not allowed with --algorithm'], 'Opt is not allowed with opt from restricted group' ); # ############################################################################# # Test clone(). # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'user=s', desc => 'User', }, { spec => 'dog|d', desc => 'dog option', group => 'Dogs', }, { spec => 'cat|c', desc => 'cat option', group => 'Cats', }, ); @ARGV = qw(--user foo --dog); $o->get_opts(); my $o_clone = $o->clone(); isa_ok( $o_clone, 'OptionParser' ); ok( $o_clone->has('user') && $o_clone->has('dog') && $o_clone->has('cat'), 'Clone has same opts' ); $o_clone->set('user', 'Bob'); is( $o->get('user'), 'foo', 'Change to clone does not change original' ); # ############################################################################# # Test issues. Any other tests should find their proper place above. # ############################################################################# # ############################################################################# # Issue 140: Check that new style =item --[no]foo works like old style: # =item --foo # negatable: yes # ############################################################################# @opt_specs = $o->_pod_to_specs("$trunk/t/lib/samples/pod/pod_sample_issue_140.txt"); is_deeply( \@opt_specs, [ { spec => 'foo', desc => 'Basic foo', group => 'default', attributes => {} }, { spec => 'bar!', desc => 'New negatable bar', group => 'default', attributes => { negatable => 1 } }, ], 'New =item --[no]foo style for negatables' ); # ############################################################################# # Issue 92: extract a paragraph from POD. # ############################################################################# is( $o->read_para_after("$trunk/t/lib/samples/pod/pod_sample_issue_92.txt", qr/magic/), 'This is the paragraph, hooray', 'read_para_after' ); # The first time I wrote this, I used the /o flag to the regex, which means you # always get the same thing on each subsequent call no matter what regex you # pass in. This is to test and make sure I don't do that again. is( $o->read_para_after("$trunk/t/lib/samples/pod/pod_sample_issue_92.txt", qr/abracadabra/), 'This is the next paragraph, hooray', 'read_para_after again' ); # ############################################################################# # Issue 231: read configuration files # ############################################################################# is_deeply( [$o->_read_config_file("$trunk/t/lib/samples/config_file_1.conf")], ['--foo', 'bar', '--verbose', '/path/to/file', 'h=127.1,P=12346'], 'Reads a config file', ); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'config=A', desc => 'Read this comma-separated list of config ' . 'files (must be the first option on the command line).', }, { spec => 'cat=A', desc => 'cat option (default a,b)', }, ); is_deeply( [$o->get_defaults_files()], ["/etc/percona-toolkit/percona-toolkit.conf", "/etc/percona-toolkit/OptionParser.t.conf", "$ENV{HOME}/.percona-toolkit.conf", "$ENV{HOME}/.OptionParser.t.conf"], "default options files", ); ok(!$o->got('config'), 'Did not got --config'); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'config=A', desc => 'Read this comma-separated list of config ' . 'files (must be the first option on the command line).', }, { spec => 'cat=A', desc => 'cat option (default a,b)', }, ); $o->get_opts(); ok( check_usage($o, "help009.txt"), 'Sets special config file default value', ); @ARGV=qw(--config /path/to/config --cat); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'config=A', desc => 'Read this comma-separated list of config ' . 'files (must be the first option on the command line).', }, { spec => 'cat', desc => 'cat option', }, ); eval { $o->get_opts(); }; like($EVAL_ERROR, qr/Cannot open/, 'No config file found'); @ARGV = ('--config',"$trunk/t/lib/samples/empty",'--cat'); $o->get_opts(); ok($o->got('config'), 'Got --config'); ok( check_usage($o, "help010.txt"), 'Parses special --config option first', ); @ARGV = ("--config=$trunk/t/lib/samples/empty",'--cat'); $o->get_opts(); ok($o->got('config'), 'Got --config='); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'config=A', desc => 'Read this comma-separated list of config ' . 'files (must be the first option on the command line).', }, { spec => 'cat', desc => 'cat option', }, ); @ARGV=qw(--cat --config /path/to/config); { local *STDERR; open STDERR, '>', '/dev/null'; $o->get_opts(); } is_deeply( $o->errors(), ['Error parsing options', 'Unrecognized command-line options /path/to/config'], 'special --config option not given first', ); # And now we can actually get it to read a config file into the options! $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME ", ); is($o->{strict}, 1, "Strict mode enabled by default"); $o->_parse_specs( "This tool accepts additional command-line arguments. Refer to the synopsis and usage information for details.", { spec => 'config=A', desc => 'Read this comma-separated list of config ' . 'files (must be the first option on the command line).', }, { spec => 'foo=s', desc => 'foo option', }, { spec => 'verbose+', desc => 'increase verbosity', }, ); is($o->{strict}, 0, "Special rule disables strict mode"); @ARGV = ('--config', "$trunk/t/lib/samples/config_file_1.conf"); $o->get_opts(); is_deeply( [@ARGV], ['/path/to/file', 'h=127.1,P=12346'], 'Config file influences @ARGV', ); ok($o->got('foo'), 'Got --foo'); is($o->get('foo'), 'bar', 'Got --foo value'); ok($o->got('verbose'), 'Got --verbose'); is($o->get('verbose'), 1, 'Got --verbose value'); @ARGV = ('--config', "$trunk/t/lib/samples/config_file_1.conf,$trunk/t/lib/samples/config_file_2.conf"); $o->get_opts(); is_deeply( [@ARGV], ['/path/to/file', 'h=127.1,P=12346', '/path/to/file'], 'Second config file influences @ARGV', ); ok($o->got('foo'), 'Got --foo again'); is($o->get('foo'), 'baz', 'Got overridden --foo value'); ok($o->got('verbose'), 'Got --verbose twice'); is($o->get('verbose'), 2, 'Got --verbose value twice'); # ############################################################################# # Issue 409: OptionParser modifies second value of # ' -- .*','(\w+): ([^,]+)' for array type opt # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'foo=a', desc => 'foo' }, ); @ARGV = ('--foo', ' -- .*,(\w+): ([^\,]+)'); $o->get_opts(); is_deeply( $o->get('foo'), [ ' -- .*', '(\w+): ([^\,]+)', ], 'Array of vals with internal commas (issue 409)' ); # ############################################################################# # Issue 349: Make OptionParser die on unrecognized attributes # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); eval { $o->get_specs("$trunk/t/lib/samples/pod/pod_sample_06.txt"); }; like( $EVAL_ERROR, qr/Unrecognized attribute for --verbose: culumative/, 'Die on unrecognized attribute' ); # ############################################################################# # Issue 460: mk-archiver does not inherit DSN as documented # ############################################################################# # The problem is actually in how OptionParser handles copying DSN vals. $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); # Hack DSNParser into OptionParser. This is just for testing. $o->{DSNParser} = $dp; $o->_parse_specs( { spec => 'source=d', desc => 'source', }, { spec => 'dest=d', desc => 'dest', }, 'DSN values in --dest default to values from --source if COPY is yes.', ); @ARGV = ( '--source', 'h=127.1,P=12345,D=test,u=bob,p=foo', '--dest', 'P=12346', ); $o->get_opts(); my $dest_dsn = $o->get('dest'); is_deeply( $dest_dsn, { A => undef, D => 'test', F =>undef, P => '12346', S => undef, h => '127.1', p => 'foo', u => 'bob', }, 'Copies DSN values correctly (issue 460)' ); # Test DSN can be repeatable $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); # Hack DSNParser into OptionParser. This is just for testing. $o->{DSNParser} = $dp; $o->_parse_specs( { spec => 'rep=d', desc => 'source', attributes => { repeatable => 1}, }, ); @ARGV = ( '--rep', 'h=127.1,P=12345,D=test,u=bob,p=foo', '--rep', 'h=127.1,P=12346', ); $o->get_opts(); my $ddest_dsn = $o->get('rep'); is_deeply( $ddest_dsn, [ { A => undef, u => 'bob', D => 'test', h => '127.1', P => '12345', S => undef, p => 'foo', F => undef }, { p => undef, F => undef, u => undef, D => undef, A => undef, P => '12346', S => undef, h => '127.1' } ], 'DSN type can be repeatable' ); # ############################################################################# # Issue 248: Add --user, --pass, --host, etc to all tools # ############################################################################# # See the 5 cases (i.-v.) at http://groups.google.com/group/maatkit-discuss/browse_thread/thread/f4bf1e659c60f03e # case ii. $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); # Hack DSNParser into OptionParser. This is just for testing. $o->{DSNParser} = $dp; $o->get_specs("$trunk/bin/pt-archiver"); @ARGV = ( '--source', 'h=127.1,S=/tmp/mysql.socket', '--port', '12345', '--user', 'bob', '--password', 'foo', '--socket', '/tmp/bad.socket', # should *not* override DSN '--where', '1=1', # required ); $o->get_opts(); my $src_dsn = $o->get('source'); is_deeply( $src_dsn, { a => undef, A => undef, b => undef, D => undef, F => undef, i => undef, m => undef, P => '12345', S => '/tmp/mysql.socket', h => '127.1', p => 'foo', t => undef, u => 'bob', L => undef, }, 'DSN opt gets missing vals from --host, --port, etc. (issue 248)', ); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); # Hack DSNParser into OptionParser. This is just for testing. $o->{DSNParser} = $dp; $o->get_specs("$trunk/bin/pt-archiver"); # Like case ii. but make sure --dest copies u from --source, not --user. @ARGV = ( '--source', 'h=127.1,u=bob', '--dest', 'h=127.1', '--user', 'wrong_user', '--where', '1=1', # required ); $o->get_opts(); $dest_dsn = $o->get('dest'); is_deeply( $dest_dsn, { a => undef, A => undef, b => undef, D => undef, F => undef, i => undef, m => undef, P => undef, S => undef, h => '127.1', p => undef, t => undef, u => 'bob', L => undef, }, 'Vals from "defaults to" DSN take precedence over defaults (issue 248)' ); # ############################################################################# # Issue 617: Command line options do no override config file options # ############################################################################# diag(`echo "iterations=4" > ~/.OptionParser.t.conf`); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->get_specs("$trunk/bin/pt-query-digest"); @ARGV = (qw(--iterations 9)); $o->get_opts(); is( $o->get('iterations'), 9, 'Cmd line opt overrides conf (issue 617)' ); diag(`rm -rf ~/.OptionParser.t.conf`); # ############################################################################# # Issue 623: --since +N does not work in mk-parallel-dump # ############################################################################# # time type opts need to allow leading +/- $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->get_specs("$trunk/bin/pt-query-digest"); @ARGV = (qw(--run-time +9)); $o->get_opts(); is( $o->get('run-time'), '+9', '+N time value' ); @ARGV = (qw(--run-time -7)); $o->get_opts(); is( $o->get('run-time'), '-7', '-N time value' ); @ARGV = (qw(--run-time +1m)); $o->get_opts(); is( $o->get('run-time'), '+60', '+N time value with suffix' ); # ############################################################################# # Issue 829: maatkit: mk-query-digest.1p : provokes warnings from man # ############################################################################# # This happens because --ignore-attributes has a really long default # value like val,val,val. man can't break this line unless that list # has spaces like val, val, val. $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'foo=a', desc => 'foo (default arg, cmd, ip, port)' }, ); @ARGV = (); $o->get_opts(); is_deeply( $o->get('foo'), [qw(arg cmd ip port)], 'List vals separated by spaces' ); # ############################################################################# # Issue 940: OptionParser cannot resolve option dependencies # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); # Hack DSNParser into OptionParser. This is just for testing. $o->{DSNParser} = $dp; $o->_parse_specs( { spec => 'foo=d', desc => 'DSN foo' }, { spec => 'bar=d', desc => 'DSN bar' }, 'DSN values in --foo default to values in --bar if COPY is yes.', ); # This simulates what get_opts() does but allows us to call # _check_opts() manually with foo first. $o->{opts}->{bar} = { long => 'bar', value => 'D=DB,u=USER,h=localhost', got => 1, type => 'd', }; $o->{opts}->{foo} = { long => 'foo', value => 'h=otherhost', got => 1, type => 'd', }; $o->_check_opts(qw(foo bar)); is_deeply( $o->get('foo'), { D => 'DB', u => 'USER', S => undef, F => undef, P => undef, h => 'otherhost', p => undef, A => undef, }, 'Resolves dependency' ); # Should die on circular dependency, avoid infinite loop. $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'foo=d', desc => 'DSN foo' }, { spec => 'bar=d', desc => 'DSN bar' }, 'DSN values in --foo default to values in --bar if COPY is yes.', 'DSN values in --bar default to values in --foo if COPY is yes.', ); $o->{opts}->{bar} = { long => 'bar', value => 'D=DB,u=USER,h=localhost', got => 1, type => 'd', }; $o->{opts}->{foo} = { long => 'foo', value => 'h=otherhost', got => 1, type => 'd', }; throws_ok( sub { $o->_check_opts(qw(foo bar)) }, qr/circular dependencies/, 'Dies on circular dependency' ); # ############################################################################# # Issue 344 # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->_parse_specs( { spec => 'foo=z', desc => 'foo' }, ); @ARGV = qw(--foo null); $o->get_opts(); is( $o->get('foo'), 'null', 'NULL size' ); # ############################################################################# # Issue 55: Integrate DSN specifications into POD # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->get_specs("$trunk/t/lib/samples/pod/pod_sample_dsn.txt"); ok( $o->DSNParser(), 'Auto-created DNSParser obj' ); @ARGV = (); $o->get_opts(); like( $o->print_usage(), qr/z\s+no\s+/, 'copy: no' ); # ############################################################################# # Issue 1123: OptionParser type: string; default: 0 doesn't work # ############################################################################# $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->get_specs("$trunk/bin/pt-fifo-split"); @ARGV = (); $o->get_opts(); is( $o->get('offset'), 0, "type: string; default: 0 (issue 1123)" ); # ############################################################################# # Issue 338: Make all of tool's --help output come from POD # ############################################################################# $o = new OptionParser(file => "$trunk/bin/pt-archiver"); my %synop = $o->_parse_synopsis(); is_deeply( \%synop, { usage => "pt-archiver [OPTIONS] --source DSN --where WHERE", description => "pt-archiver nibbles records from a MySQL table. The --source and --dest arguments use DSN syntax; if COPY is yes, --dest defaults to the key's value from --source.", }, "_parse_synopsis() gets usage and description" ); @ARGV = qw(--help); $o->get_specs(); $o->get_opts(); $output = output( sub { $o->usage_or_errors(undef, 1); }, ); $synop{usage} =~ s/([\[\]])/\\$1/g; like( $output, qr/^$synop{description} For more details.+\nUsage: $synop{usage}$/m, "Uses desc and usage from SYNOPSIS for help" ); # ############################################################################# # Bug 1039074: Tools exit 0 on error parsing options, should exit non-zero # ############################################################################# # pt-archiver requires at least one of --dest, --file or --purge, as well as # --where and --source. So specifying no options should cause errors. @ARGV = qw(); $o = new OptionParser(file => "$trunk/bin/pt-archiver"); $o->get_specs(); $o->get_opts(); my $exit_status = 0; ($output, $exit_status) = full_output( sub { $o->usage_or_errors("$trunk/bin/pt-archiver"); }, ); is( $exit_status, 1, "Non-zero exit status on error parsing options (bug 1039074)" ); # ############################################################################# # --set-vars/set_vars() # ############################################################################# @ARGV = qw(); $o = new OptionParser(file => "$trunk/bin/pt-archiver"); $o->get_specs(); $o->get_opts(); my $vars = $o->set_vars("$trunk/bin/pt-archiver"); is_deeply( $vars, { wait_timeout => { default => 1, val => '10000', }, }, "set_vars(): 1 default from docs" ) or diag(Dumper($vars)); # Bug 1182856: Zero values causes "Invalid --set-vars value: var=0" @ARGV = qw(--set-vars SQL_LOG_BIN=0); $o->get_opts(); $vars = $o->set_vars("$trunk/bin/pt-archiver"); is_deeply( $vars, { wait_timeout => { default => 1, val => '10000', }, SQL_LOG_BIN => { default => 0, val => '0', }, }, "set_vars(): var=0 (bug 1182856)" ) or diag(Dumper($vars)); # ############################################################################# # https://bugs.launchpad.net/percona-toolkit/+bug/1199589 # pt-archiver deletes data despite --dry-run # ############################################################################# # From the issue: "One problem is that --optimize is not being used correctly: # the option takes an argument: d, s, or ds (see --analyze). The real problem # is that --optimize is consuming the next option, which is --dry-run in this # case. This shouldn't happen; it means the option parser is failing to notice # that --dry-run is not the string val to --optimize but rather an option; # it should catch this and the tool should fail to start with an error like # "--optimize requires a value". @ARGV = qw(--optimize --dry-run --ascend-first --where 1=1 --purge --source localhost); $o = new OptionParser(file => "$trunk/bin/pt-archiver"); $o->get_specs(); $o->get_opts(); $output = output( sub { $o->usage_or_errors(undef, 1); }, ); like( $output, qr/--optimize requires a string value/, "String opts don't consume the next opt (bug 1199589)" ); is( $o->get('optimize'), undef, "--optimize didn't consume --dry-run (bug 1199589)" ); @ARGV = qw(--optimize ds --dry-run --ascend-first --where 1=1 --purge --source localhost); $o->get_opts(); $output = output( sub { $o->usage_or_errors(undef, 1); }, ); is( $output, '', "String opts still work (bug 1199589)" ); is( $o->get('optimize'), 'ds', "--optimize got its value (bug 1199589)" ); # ############################################################################# # Issue 1290911 : prompt_noecho should send prompt to STDERR so user can # direct STDOUT to a file and still see the prompt # ############################################################################# SKIP: { eval {require Term::ReadKey}; skip ('\'prompt_no_echo outputs to STDERR\' because Term::ReadKey not available', 1) if $EVAL_ERROR; $o = new OptionParser(); $output = output( sub { my $input = "thepassword"; local *STDIN; open STDIN, '<', \$input; $o->prompt_noecho('Test Prompt:'); }, stderr=>1, ); is ( $output, "Test Prompt:\n", 'prompt_no_echo outputs prompt to STDERR' ); } # end skip # ############################################################################# # Issue 1361293: Global config file with no-version-check option makes tools # that don't recognize the option fail. # ############################################################################# diag(`echo "no-version-check" > ~/.OptionParser.t.conf`); $o = new OptionParser( description => 'OptionParser.t parses command line options.', usage => "$PROGRAM_NAME " ); $o->get_specs("$trunk/bin/pt-slave-find"), # doesn't have version-check option $output = output( sub { $o->get_opts(); }, stderr=>1 ); unlike( $output, qr/Unknown option: no-version-check/, 'no-version-check ignored if unsupported and in config file. issue 1361293' ); diag(`rm -rf ~/.OptionParser.t.conf`); # ############################################################################# # Done. # ############################################################################# { local *STDERR; open STDERR, '>', \$output; $o->_d('Complete test coverage'); } like( $output, qr/Complete test coverage/, '_d() works' ); done_testing; exit;