Files
percona-toolkit/t/lib/OptionParser.t
Sveta Smirnova 5c999ca3e0 PT-2340 - Support MySQL 8.4
- Removed runtime.txt after discussion with Anastasia Alexandrova
- Added "use VersionParser" into tests in t/lib when needed
- Removed word master from tests for pt-archiver, pt-config-diff, pt-deadlock-logger, pt-duplicate-key-checker, pt-find, pt-fk-error-logger, pt-heartbeat, pt-index-usage, pt-ioprofile, pt-kill, pt-mysql-summary
- Removed word slave from tests for pt-archiver, pt-config-diff, pt-deadlock-logger, pt-duplicate-key-checker, pt-find, pt-fk-error-logger, pt-heartbeat, pt-index-usage, pt-ioprofile, pt-kill, pt-mysql-summary
- Updated modules for pt-archiver, pt-config-diff, pt-deadlock-logger, pt-duplicate-key-checker, pt-find, pt-fk-error-logger, pt-heartbeat, pt-index-usage, pt-ioprofile, pt-kill, pt-mysql-summary
- Changed mysql_ssl patch, so it is now short option s
- Added a check for existing zombies in t/pt-kill/execute_command.t
- Added bin/pt-galera-log-explainer to .gitignore
2024-07-27 01:59:52 +03:00

2303 lines
60 KiB
Perl

#!/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 <options>",
);
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 <options>",
);
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 <options>",
);
$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 <options>'
);
# #############################################################################
# Test _get_participants()
# #############################################################################
$o = new OptionParser(
description => 'OptionParser.t parses command line options.',
usage => "$PROGRAM_NAME <options>",
);
$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 <options>",
);
$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 <options>
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 <options>",
);
$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 <options>",
);
$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 <options>",
);
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 <options>",
);
$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 <options>"
);
$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 <options>"
);
$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 <options>"
);
$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 <options>"
);
$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 <options>"
);
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 <options>"
);
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 <options>"
);
# 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 <options>"
);
$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 <options>"
);
$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 <options>",
);
$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 <options>"
);
# 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 <options>"
);
# 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 <options>"
);
# 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 <options>"
);
$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 <options>"
);
$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 <options>"
);
$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 <options>"
);
$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 <options>"
);
$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 <options>"
);
$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 <options>"
);
$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 <options>",
);
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 <options>"
);
$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 <options>"
);
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 <options>"
);
# 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 <options>"
);
# 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 <options>"
);
# 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,
s => 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 <options>"
);
# 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,
s => 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 <options>"
);
$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 <options>"
);
$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 <options>"
);
$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 <options>"
);
# 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 <options>"
);
$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 <options>"
);
$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 <options>"
);
$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 <options>"
);
$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 <options>"
);
$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;