Combine check-* scripts into super check-tool script. Add check-spelling, check-dev-env, and docs/test-coverage directory.

This commit is contained in:
Daniel Nichter
2011-07-14 17:49:32 -06:00
parent 767f8b3211
commit 48cf39930b
45 changed files with 763 additions and 62 deletions

View File

@@ -1,4 +1,6 @@
config/NaturalDocs/Data
docs/dev/*
docs/user/html
docs/test-coverage/db
docs/test-coverage/html
.DS_Store

View File

@@ -75,7 +75,7 @@ how wide each column should be, and then prints them out.
This is useful for things like aligning the output of vmstat or iostat so it
is easier to read.
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -5536,7 +5536,7 @@ A sample module might look like this:
1;
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -1301,7 +1301,7 @@ Show version and exit.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -298,7 +298,7 @@ Usage: pt-collect [OPTION...]
pt-collect focuses on gathering diagnostic data during a MySQL performance
problem. It is typically executed by C<stalk>.
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -3162,7 +3162,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -2641,7 +2641,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -656,7 +656,7 @@ The file read should look like this:
... et cetera
TS <timestamp> <-- must end with a TS line.
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -4175,7 +4175,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -1448,7 +1448,7 @@ Show version and exit.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -3723,7 +3723,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -2361,7 +2361,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -4206,7 +4206,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -6091,7 +6091,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -4106,12 +4106,6 @@ short form: -P; type: int
Port number to use for connection.
=item --[no]strip-comments
default: yes
Remove SQL comments from queries in the Info column of the PROCESSLIST.
=item --run-time
type: time
@@ -4152,6 +4146,12 @@ Causes pt-kill to create the sentinel file specified by L<"--sentinel"> and
exit. This should have the effect of stopping all running instances which are
watching the same sentinel file.
=item --[no]strip-comments
default: yes
Remove SQL comments from queries in the Info column of the PROCESSLIST.
=item --user
short form: -u; type: string
@@ -4226,23 +4226,6 @@ See also L<"GROUP, MATCH AND KILL">.
=over
=item --match-all
group: Query Matches
Match all queries that are not ignored. If no ignore options are specified,
then every query matches (except replication threads, unless
L<"--replication-threads"> is also specified). This option allows you to
specify negative matches, i.e. "match every query I<except>..." where the
exceptions are defined by specifying various C<--ignore> options.
This option is I<not> the same as L<"--victims"> C<all>. This option matches
all queries within a class, whereas L<"--victims"> C<all> specifies that all
matching queries in a class (however they matched) will be killed. Normally,
however, the two are used together because if, for example, you specify
L<"--victims"> C<oldest>, then although all queries may match, only the oldest
will be killed.
=item --busy-time
type: time; group: Query Matches
@@ -4314,6 +4297,23 @@ Ignore queries whose user matches this Perl regex.
See L<"--match-user">.
=item --match-all
group: Query Matches
Match all queries that are not ignored. If no ignore options are specified,
then every query matches (except replication threads, unless
L<"--replication-threads"> is also specified). This option allows you to
specify negative matches, i.e. "match every query I<except>..." where the
exceptions are defined by specifying various C<--ignore> options.
This option is I<not> the same as L<"--victims"> C<all>. This option matches
all queries within a class, whereas L<"--victims"> C<all> specifies that all
matching queries in a class (however they matched) will be killed. Normally,
however, the two are used together because if, for example, you specify
L<"--victims"> C<oldest>, then although all queries may match, only the oldest
will be killed.
=item --match-command
type: string; group: Query Matches
@@ -4581,7 +4581,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -3517,7 +3517,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -112,7 +112,7 @@ Usage: pt-mext [OPTION...]
pt-mext aggregates and summarizes mysqladmin extended output.
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -1289,7 +1289,7 @@ you would use to connect to MySQL, such as "./mysql-summary --user=foo"
* Parse queries out of processlist and aggregate them.
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -5203,7 +5203,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -205,7 +205,7 @@ Usage: pt-pmp [OPTION...]
pt-pmp aggregates stack traces.
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -1266,7 +1266,7 @@ Show version and exit.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -7191,7 +7191,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -15635,7 +15635,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -2751,7 +2751,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -84,7 +84,7 @@ The output will be
Mutex spin waits 0, rounds 6645565, OS waits 34159
RW-shared spins 14648, OS waits 882; RW-excl spins 13146
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -2129,7 +2129,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -481,7 +481,7 @@ Usage: pt-sift [OPTION...]
pt-sift browses the files created by the collect tool.
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -2613,7 +2613,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -3093,7 +3093,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -3264,7 +3264,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -183,7 +183,7 @@ and restarting this one.
The name 'stalk' is because 'watch' is already taken, and 'stalk' is fun.
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -1203,7 +1203,7 @@ Options:
NETWORK: Don't print out information on network controllers & config.
PROCESS: Don't print out top processes and vmstat information.
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -8645,7 +8645,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -9970,7 +9970,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -2318,7 +2318,7 @@ number.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -1992,7 +1992,7 @@ Show version and exit.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -11732,7 +11732,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -794,7 +794,7 @@ This tool is based on Neil Gunther's book Guerrilla Capacity Planning.
other samples; it will introduce skew into the throughput for that sample,
too.
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -3806,7 +3806,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

View File

@@ -2996,7 +2996,7 @@ User for login if not current user.
=back
=head1 DOWNLOADING
=head1 DOWNLOADING
Visit L<http://www.percona.com/software/> to download the latest release of
Percona Toolkit. Or, to get the latest release from the command line:

90
util/aspell.en.pws Normal file
View File

@@ -0,0 +1,90 @@
personal_ws-1.1 en 86
STDOUT
MKDEBUG
PTDEBUG
timestamp
perlgpl
perlartistic
Ver
Distrib
Nichter
Percona
binmode
config
PID
MySQL
mysql
Maatkit
processlist
PROCESSLIST
STDIN
login
STDERR
wget
DSN
SQL
charset
daemonize
daemonized
POSIX
regex
cron
DBI
DBD
Freenode
IRC
MERCHANTABILITY
whitespace
manpage
binlog
runtime
OLTP
mojibake
archiver
INFILE
OUTFILE
umask
InnoDB
transactional
MyISAM
checksum
checksums
checksumming
checksummed
chunking
chunked
chunkable
hostname
PostgreSQL
Continuent
LLC
sakila
gzip
lossless
mysqldump
mysqlbinlog
mysqlimport
fifo
recurse
wildcard
bool
boolean
subquery
subqueries
memcached
cardinality
Apdex
sparkline
cryptographic
tcpdump
timeline
profiler
canonicalize
prefetch
unresolvable
incrementing
scalability
IP
runnable
online
Aspersa

40
util/check-dev-env Executable file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env perl
# This pseudo-script is for developers to see if their box has all
# the modules necessary for testing Maatkit. Any missing modules
# will cause an error like "Can't locate Foo.pm in @INC ...". Else
# the version for each module used by this script will be printed.
use Data::Dumper;
use DBD::mysql;
use DBI;
use Digest::MD5;
use File::Basename;
use File::Find;
use File::Spec;
use File::Temp;
use Getopt::Long;
use IO::Compress::Gzip;
use IO::File;
use IO::Uncompress::Inflate;
use List::Util;
use POSIX;
use Socket;
use Term::ReadKey;
use Test::More;
use threads;
use Thread::Queue;
use Time::HiRes;
use Time::Local;
#use Digest::Crc32;
my $file = __FILE__;
my $m = `cat $file | grep '^use'`;
my @modules = map { m/use (.+?);/; $1 } split("\n", $m);
foreach my $module ( @modules ) {
my $version = "${module}::VERSION";
print "$module " . ${$version} . "\n";
}
exit;

45
util/check-spelling Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env perl
# You must run the program from util/.
if ( !(-f 'aspell.en.pws' && -f 'parse-aspell-output') ) {
die "This script must be run from the util/ directory of a branch\n"
}
use strict;
use warnings FATAL => 'all';
use English qw(-no_match_vars);
# These programs should be in your PATH.
my $pod2text = 'pod2text';
my $aspell = 'aspell';
my $tool_file = shift @ARGV;
if ( !$tool_file ) {
die "Usage: $PROGRAM_NAME TOOL\n";
}
my ($tool_name) = $tool_file =~ m{([a-z-]+)$};
if ( !$tool_name ) {
die "Cannot parse tool name from $tool_file";
}
# Temp files.
my $pod_file = "/tmp/$tool_name-pod";
my $bad_words_file = "/tmp/$tool_name-misspelled-words";
# Convert tool's POD to text.
`$pod2text $tool_file > $pod_file`;
# Spell check the text file.
`cat $pod_file | $aspell --pipe --lang en_US --personal ./aspell.en.pws > $bad_words_file`;
# Parse and match the aspell output to the text file.
print "$tool_name:\n\n";
print `./parse-aspell-output $bad_words_file $pod_file`;
print "\n";
# Cleanup.
`rm -rf $pod_file`;
`rm -rf $bad_words_file`;
exit;

435
util/check-tool Executable file
View File

@@ -0,0 +1,435 @@
#!/usr/bin/env perl
use strict;
use warnings FATAL => 'all';
use English qw(-no_match_vars);
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
use Data::Dumper;
$Data::Dumper::Indent = 1;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Quotekeys = 0;
my @tool_files = @ARGV;
if ( !@tool_files ) {
die "Usage: $PROGRAM_NAME [TOOL...]\n";
}
my $exit_status = 0; # 0 no problems, 1 any problems
my $tool_file; # bin/pt-archiver
my $tool_name; # pt-archiver (no path)
my @check_subs = (qw(
check_alpha_order
check_module_usage
check_option_types
check_pod_header_order
check_pod_formatting
check_option_usage
));
TOOL:
while ( defined($tool_file = shift @ARGV) ) {
my $fh;
eval {
open $fh, "<", $tool_file;
};
if ( $EVAL_ERROR ) {
$exit_status = 1;
warn "Cannot open $tool_file: $OS_ERROR";
next TOOL;
}
($tool_name) = $tool_file =~ m{/([a-z-]+)$};
if ( !$tool_name ) {
$exit_status = 1;
warn "Cannot parse tool name from $tool_file";
next TOOL;
}
foreach my $check_sub ( @check_subs ) {
seek $fh, 0, 0;
print "# $check_sub ", ('#' x (70 - length $check_sub)), "\n";
my $sub = \&$check_sub;
eval {
&$sub($fh);
};
if ( $EVAL_ERROR ) {
$exit_status = 1;
warn "Error while checking $tool_name: $EVAL_ERROR";
}
}
}
exit $exit_status;
# ############################################################################
# Subroutines
# ############################################################################
# Check that options in the tool's POD are in alphabetical order. Only the
# head1 OPTIONS section and any head2 subsections are checked. Subsections
# are considered independent. The "[no]" part of negatable options is not
# part of the base option name, so it's ignored.
sub check_alpha_order {
my ($fh) = @_;
local $INPUT_RECORD_SEPARATOR = '';
my $para;
my $section;
while ( $para = <$fh> ) {
last if ($section) = $para =~ m/^=head1 (OPTIONS)/;
}
die "Cannot find =head1 OPTIONS" unless $section;
parse_options($fh, $section);
return;
}
sub parse_options {
my ($fh, $section) = @_;
my $para;
my @opts;
while ( $para = <$fh> ) {
last if $para =~ m/^=head1/;
if ( my ($option) = $para =~ m/^=item --(?:\[no\])?(.*)/ ) {
push @opts, $option;
}
elsif ( $para =~ m/^=head2 (.+)/ ) {
parse_options($fh, $1);
}
}
my $fmt = "%-20s %-20s\n";
my @sorted = sort @opts;
for my $i ( 0..$#sorted ) {
if ( $opts[$i] ne $sorted[$i] ) {
$exit_status = 1;
printf "$tool_name has unsorted options in $section\n";
printf $fmt, 'ACTUAL', 'CORRECT';
printf $fmt, '=' x 20, '=' x 20;
map { printf $fmt, $opts[$_], $sorted[$_] }
grep { $opts[$_] ne $sorted[$_] }
($i..$#sorted);
last;
}
}
return;
}
sub check_module_usage {
my ($fh) = @_;
# These modules are not instantiated as objects.
my %not_obj = (
Transformers => 1,
);
# Many tools dyanmically instantiate objs like $plugin="WatchStatus",
# $plugin->new(). So this script can't detect that.
my %dynamic = (
'pt-loadavg' => {
WatchStatus => 1,
WatchProcesslist => 1,
WatchServer => 1,
},
'pt-query-advisor' => {
SlowLogParser => 1,
GeneralLogParser => 1,
},
'pt-query-digest' => {
TcpdumpParser => 1,
MySQLProtocolParser => 1,
PgLogParser => 1,
SlowLogParser => 1,
MemcachedProtocolParser => 1,
MemcachedEvent => 1,
BinaryLogParser => 1,
GeneralLogParser => 1,
ProtocolParser => 1,
HTTPProtocolParser => 1,
},
'pt-table-sync' => {
TableSyncStream => 1,
TableSyncChunk => 1,
TableSyncNibble => 1,
TableSyncGroupBy => 1,
},
'pt-table-usage' => {
SlowLogParser => 1,
},
);
# If these base-class modules are present, they should be accompanied
# by a subclass.
my %base_class = (
'AdvisorRules' => [ qw(QueryAdvisorRules VariableAdvisorRules) ],
);
# Nearly every tool has or needs these modules.
my %ignore = (
OptionParser => 1,
DSNParser => 1,
);
my $contents = do { local $/ = undef; <$fh> };
my %uses = map { $_ => 1 } $contents =~ m/new ([A-Z]\w+)(?:\(|;)/gm;
my @unused = grep {
my $module = $_;
my $unused = 0;
if ( $not_obj{$module} ) {
# MaatkitCommon::_d
chomp(my $g = `grep -c '${_}::' $tool_file`);
# Transformers->import
chomp(my $i = `grep -c '${_}->import' $tool_file`);
$g ||= 0;
$i ||= 0;
$unused = 1 if $g + $i == 0;
}
elsif ( $dynamic{$tool_name}->{$module} ) {
# Can't detect these.
}
elsif ( $base_class{$module} ) {
$unused = 1 unless grep { $uses{$_} } @{$base_class{$module}};
}
else {
$unused = 1 unless $uses{$module};
}
$unused;
}
grep { !$ignore{$_} } $contents =~ m/^# (\w+) package$/gm;
if ( @unused ) {
print "$tool_name has unused modules:\n"
. join('', map { "\t$_\n" } @unused);
$exit_status = 1;
}
return;
}
sub check_option_types {
my ($fh) = @_;
# Standard options: http://code.google.com/p/maatkit/wiki/CommandLineOptions
my $sop = {
'defaults-file' => {type => 's', short => 'F' },
'host' => {type => 's', short => 'h' },
'password' => {type => 's', short => 'p' },
'port' => {type => 'i', short => 'P' },
'socket' => {type => 's', short => 'S' },
'user' => {type => 's', short => 'u' },
'charset' => {type => 's', short => 'A' },
'ask-pass' => {type => '', short => '', },
'database' => {type => 's', short => 'D' },
'set-vars' => {type => 's', short => '', },
'where' => {type => 's', short => '', },
'databases' => {type => 'h', short => 'd' },
'tables' => {type => 'h', short => 't' },
'columns' => {type => 'a', short => 'c' },
'engines' => {type => 'h', short => 'e' },
'ignore-databases'=> {type => 'H', short => '', },
'ignore-tables' => {type => 'H', short => '', },
'ignore-columns' => {type => 'H', short => '', },
'ignore-engines' => {type => 'H', short => '', },
'config' => {type => 'A', short => '', },
'daemonize' => {type => '', short => '', },
'dry-run' => {type => '', short => '', },
'log' => {type => 's', short => '', },
'pid' => {type => 's', short => '', },
# --progress is not standard. Some older tools had their own special
# progress, whereas newer tools use Progress.
# 'progress' => {type => 'a', short => '', },
'quiet' => {type => '', short => 'q' },
'sentinel' => {type => 's', short => '', },
'stop' => {type => '', short => '', },
'run-time' => {type => 'm', short => '', },
'threads' => {type => 'i', short => '', },
'verbose' => {type => '', short => 'v' },
'wait' => {type => 'm', short => 'w' },
'recurse' => {type => 'i', short => '' },
};
# Exceptions are inevitable. E.g., pt-deadlock-logger --columns is not the
# standard filter --columns (e.g. same family as --databases, --tables, etc.)
# These exceptions are good candidates for change, so our standard options
# really are standard across all tools.
my $exception = {
'pt-deadlock-logger' => { # not standard filter
columns => {
type => 'h',
short => '',
},
},
'pt-checksum-filter' => { # not standard filter
'ignore-databases' => {
type => '',
short => '',
},
},
};
my $help = `$tool_file --help`;
# Options are listed after the line "Options:" (default group).
# Each line is like:
# --defaults-file=s -F Only read mysql options from the given file
# The short form (-F) is optional. The list is terminated at
# the line "Option types:". Problem is: there's a second list
# of options. The second list shows each option's value. So
# we stop parsing when we get an option that we already have.
my $opt = {};
while ( $help =~ m/^\s{2,}--(\S+?)(?:=(.))?\s+(?:-([a-zA-Z]))?\s+\w+/mg ) {
my ($long, $type, $short) = ($1, $2, $3);
die "Failed to parse $help" unless $long;
last if $opt->{$long};
$opt->{$long} = 1;
if ( $sop->{$long} ) {
# Check option type.
my $expected_type = $sop->{$long}->{type};
$expected_type = $exception->{$tool_name}->{$long}->{type}
if exists $exception->{$tool_name}->{$long}->{type};
$expected_type = '' unless defined $expected_type;
if ( ($type || '') ne $expected_type ) {
$exit_status = 1;
print "$tool_name --$long "
. ($type ? "is type $type" : "has no type")
. " but should "
. ($expected_type ? "be type $expected_type"
: "have no type")
. "\n";
}
# Check short form.
my $expected_short = $sop->{$long}->{short};
$expected_short = $exception->{$tool_name}->{$long}->{short}
if exists $exception->{$tool_name}->{$long}->{short};
$expected_short = '' unless defined $expected_short;
if ( ($short || '') ne $expected_short ) {
print "$tool_name --$long "
. ($short ? "has short form -$short" : "has no short form")
. " but should have "
. ($expected_short ? "short form -$expected_short"
: "no short form")
. "\n";
}
}
}
return;
}
# Check that the POD headers are in standard order. Only major, required
# headers are checked. For example, there maybe be other headers between
# DESCRIPTION and OPTIONS, but these are ignored.
sub check_pod_header_order {
my @std_hdrs = (
'NAME',
'SYNOPSIS',
'RISKS',
'DESCRIPTION',
'OPTIONS',
'DOWNLOADING',
'ENVIRONMENT',
'SYSTEM REQUIREMENTS',
'BUGS',
'AUTHORS',
'COPYRIGHT, LICENSE, AND WARRANTY',
'VERSION',
);
my @hdrs;
foreach my $hdr ( split(/\n/, `grep '^=head1' $tool_file`) ) {
$hdr =~ s/^=head1 //;
if ( $hdr =~ s/\s+$// ) {
print "Extra space after $hdr\n";
}
push @hdrs, $hdr if grep { $hdr eq $_ } @std_hdrs;
}
my $fmt = "%-32s %-32s\n";
for my $i ( 0..$#std_hdrs ) {
if ( ($hdrs[$i] || '') ne $std_hdrs[$i] ) {
$exit_status = 1;
print "$tool_name has missing or out-of-order standard headers:\n";
printf $fmt, 'ACTUAL', 'CORRECT';
printf $fmt, '=' x 32, '=' x 32;
map { printf $fmt, ($hdrs[$_] || ''), $std_hdrs[$_] }
grep { ($hdrs[$_] || '') ne $std_hdrs[$_] }
($i..$#std_hdrs);
last;
}
}
return;
}
sub check_pod_formatting {
my ($fh) = @_;
my $output = `perldoc -T $tool_file 2>&1`;
# unlike() will print the whole POD if this fails; ok() is more terse.
if ( $output =~ m/can't break/ ) {
$exit_status = 1;
print "$tool_name POD has lines that are too long\n";
}
chomp(my $podchecker = `which podchecker`);
if ( $podchecker ) {
$output = `$podchecker $tool_file 2>&1`;
if ( $output !~ m/pod syntax OK/ ) {
$exit_status = 1;
print "$output\n";
}
}
return;
}
sub check_option_usage {
my ($fh) = @_;
# help and version are special opts, intrinsic to OptionParser.
# The other opts are usually processed all at once by calling
# DSNParser::parse_options().
my %ignore = qw(
version 1
help 1
charset 1
defaults-file 1
host 1
password 1
port 1
socket 1
user 1
);
my $contents = do { local $/ = undef; <$fh> };
my @options = grep { !$ignore{$_} }
map { s/^\[no\]//; $_; }
$contents =~ m/^=item --(.+)$/gm;
my @unused = grep {
chomp(my $get = `grep -c "get('$_')" $tool_file`);
chomp(my $got = `grep -c "got('$_')" $tool_file`);
$get ||= 0;
$got ||= 0;
my $used = $get + $got;
!$used;
} @options;
if ( @unused ) {
$exit_status = 1;
print "$tool_name has unused options:\n"
. join('', map { "\t--$_\n" } @unused);
}
(my $pkg = $tool_name) =~ s/-/_/g;
my $main = `grep -A 1000 $pkg $tool_file`;
if ( $main !~ m/->parse_options\(/ ) {
$exit_status = 1;
print "$tool_name does not call DSNParser::parse_options()\n";
}
return;
}

89
util/parse-aspell-output Executable file
View File

@@ -0,0 +1,89 @@
#!/usr/bin/env perl
# This script parses the output of ispell/aspell --pipe into something
# meaningful. The output is like:
#
# @(#) International Ispell Version 3.1.20 (but really Aspell 0.60.6)
# & mk 50 0: Mk, km, mks, ml, K, M, k, ...
# *
# *
# *
# *
#
# *
# *
# & mk 50 6: Mk, km, mks, ml, K, M, k, ...
#
# Spelling errors are the "& WORD COUNT OFFSET: SUGGESTIONS" lines. We
# don't care about COUNT and OFFSET is per-word (or so it seems), so it's
# not helpful to us either. We also don't care about the suggestions.
#
# What we care about is on which line the bad WORD appears. Lines are
# separated by blank lines in the output; so that output reflects 2 lines
# in the input. The asterisk lines are good/spelled correctly words.
use strict;
use warnings FATAL => 'all';
use English qw(-no_match_vars);
use Data::Dumper;
my ($ispell_output, $pod_text) = @ARGV;
die "No ispell output file given" unless $ispell_output && -f $ispell_output;
die "No POD text file given" unless $pod_text && -f $pod_text;
my $fh;
open $fh, '<', $pod_text or die "Cannot open $pod_text: $OS_ERROR";
my @pod = <$fh>;
close $fh;
open $fh, '<', $ispell_output or die "Cannot open $ispell_output: $OS_ERROR";
my $pod_lineno = 1;
my $i = 0;
my $j = 0;
LINE:
while ( defined(my $line = <$fh>) ) {
if ( $line =~ m/^\s*$/ ) {
$pod_lineno++;
next LINE;
}
my ($word, $correct) = $line =~ m/^& (\w+) \d+ \d+: (.+)/;
next LINE unless $word;
next LINE if $word eq 'mk';
if ( $i < $pod_lineno ) {
for my $pod_line ( $j..$#pod ) {
$i++ if $pod[$j++] ne "\n";
last if $i == $pod_lineno;
}
}
my $pod_line = $pod[$j - 1];
next LINE if $pod_line =~ m/^\s*(?:type|short form): [\w-]+/;
next LINE if $word =~ m/utf/i && $pod_line =~ m/utf8/i;
next LINE if $pod_line =~ m/^\s+--$word$/;
next LINE if $word eq 'maatkit' && $pod_line =~ m/maatkit manpage/;
next LINE if $word eq 'maatkit' && $pod_line =~ m{http://code.google.com/p/maatkit/};
next LINE if $word eq 'dsn' && $pod_line =~ m/dsn: \w+/;
next LINE if $word eq 'tmp' && $pod_line =~ m/tmp table/;
next LINE if $word eq 'toolname' && $pod_line =~ m/Where "toolname"/;
$pod_line =~ s/^\s+//;
my @correct = map { s/^s+//g; s/\s+$//g; $_ } split(',', $correct);
print " Misspelled: $word\n"
. " Suggestions: " . join(', ',
grep { defined $_ } map { $correct[$_] } (0..2)) . "\n"
. " Line: $pod_line\n";
}
close $fh;
exit;