You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@harmony.apache.org by hi...@apache.org on 2007/08/03 09:51:57 UTC

svn commit: r562374 - in /harmony/standard/tools/check-junit-code: ./ check-junit-code

Author: hindessm
Date: Fri Aug  3 00:51:56 2007
New Revision: 562374

URL: http://svn.apache.org/viewvc?view=rev&rev=562374
Log:
Adding code from "[#HARMONY-1960] [classlib] Tool to help spot potential junit usage issues"

Added:
    harmony/standard/tools/check-junit-code/
    harmony/standard/tools/check-junit-code/check-junit-code   (with props)

Added: harmony/standard/tools/check-junit-code/check-junit-code
URL: http://svn.apache.org/viewvc/harmony/standard/tools/check-junit-code/check-junit-code?view=auto&rev=562374
==============================================================================
--- harmony/standard/tools/check-junit-code/check-junit-code (added)
+++ harmony/standard/tools/check-junit-code/check-junit-code Fri Aug  3 00:51:56 2007
@@ -0,0 +1,355 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+check-junit-code - suggest best practice for Junit test code
+
+=head1 SYNOPSIS
+
+  # print usage
+  check-junit-code -h
+
+  # check Test.java
+  check-junit-code Test.java
+
+  # check all files under the directory src/test but only report on
+  # issues involving assertEquals
+  check-junit-code -p assertEquals src/test
+
+  # check all files under the directory modules and only print the
+  # summary of findings to stderr
+  check-junit-code modules >/dev/null
+
+=head1 DESCRIPTION
+
+This tool attempts to highlight possible areas for improvement in
+Junit usage in existing test code.  It prints the findings to stdout
+and prints a summary of the issues found to stderr.
+
+Typical issues highlighted are:
+
+=over 4
+
+=item actual may be a constant
+
+The order of assertEquals arguments should be the I<expected> value
+followed by the I<actual> value being tested.  This type of issue
+means that the I<actual> value argument looks like it might be a
+constant which means that it is likely that the arguments are in the
+wrong order.  Having arguments in the wrong order leads to very confusing
+Junit failure messages.  For example:
+
+  assertEquals(buf.position(), 0);
+
+should be:
+
+  assertEquals(0, buf.position());
+
+=item should possibly use assertEquals
+
+The failure messages from Junit give more information if assertEquals is
+used in place of, for instance, "assertTrue(A == B)".  For example:
+
+  assertTrue(buf.hashCode() == readonly.hashCode());
+
+could be more helpfully written as:
+
+  assertEquals(buf.hashCode(), readonly.hashCode());
+
+=item consider using separate asserts for each '&&' component
+
+An assert written as:
+
+  assertTrue("A or B failed", A && B);
+
+could be more helpfully written as:
+
+  assertTrue("A failed", A);
+  assertTrue("B failed", B);
+
+=item exception may be left to junit
+
+It is possible that failure messages about 'unexpected exceptions' are
+better removed and left to be caught by Junit's execption handling
+functions which typically give more useful error messages.
+
+=item expected is true - should use assertTrue
+
+Often assertEquals is used to test for true when it would be better to
+use assertTrue.  Similar warnings are issued about the false and null.
+
+=item should be fail (always true/false)
+
+Constant asserts such as:
+
+  assertFalse(true);
+  assertTrue(false);
+
+should probably be written using:
+
+  fail();
+
+=back
+
+=head1 BUGS
+
+Almost certainly. It's not a full parser so it will be inaccurate.
+Patches welcome.
+
+=head1 AUTHOR
+
+Mark Hindess, C<mark dot hindess at gmail dot com>
+
+=cut
+
+use strict;
+use FileHandle;
+use File::Find;
+use Text::Balanced qw/extract_delimited/;
+use Getopt::Std;
+my %o;
+getopts('hp:', \%o);
+
+if (!@ARGV or exists $o{'h'}) {
+  die <<EOM;
+Usage: $0 [options] <list of Junit test files or directories>
+where valid options are:
+  -p pattern  - only report on issues matching pattern - useful if you
+                get too many results to deal with at once
+
+The verbose list of issues is printed to stdout and a summary is printed
+on stderr so you can just see the summary with:
+  $0 src/test >/dev/null
+
+EOM
+}
+
+my $pattern = $o{'p'};
+my %types =
+  (
+   assertEquals => \&check_assertEquals,
+   assertTrue => \&check_assertTrue,
+   assertFalse => \&check_assertFalse,
+   fail => \&check_fail,
+  );
+my %stats = ();
+
+sub check;
+
+foreach (@ARGV) {
+  if (-f $_) {
+    check_junit($_, \%stats);
+  } else {
+    File::Find::find({ wanted => \&find_check }, $_);
+  }
+}
+
+my @types = keys %{$stats{type}};
+unless (@types) {
+  print STDERR "No issues identified\n";
+  exit;
+}
+
+print STDERR "Types of issue identified\n\n";
+foreach (sort { $stats{type}->{$b} <=> $stats{type}->{$a} } @types) {
+  printf STDERR "%8d %s\n", $stats{type}->{$_}, $_;
+}
+print STDERR "\n\n";
+
+my @modules = keys %{$stats{module}};
+if (scalar @modules > 1) {
+  print STDERR "Number of Issues by module\n\n";
+  foreach (sort { $stats{module}->{$b} <=> $stats{module}->{$a} } @modules) {
+    printf STDERR "%8d %s\n", $stats{module}->{$_}, $_;
+  }
+} else {
+  print STDERR "Number of Issues by file\n\n";
+  foreach (sort { $stats{file}->{$a} <=> $stats{file}->{$b}
+                } keys %{$stats{file}}) {
+    printf STDERR "%8d %s\n", $stats{file}->{$_}, $_;
+  }
+}
+
+sub find_check {
+  # ignore .svn directories (and files if there were any)
+  if (/^\.svn$/) {
+    $File::Find::prune = 1;
+    return;
+  }
+
+  # harmony specific - to short cut searching non-test directories
+  if ($File::Find::name =~ m!src/! && $File::Find::name !~ m!src/test!) {
+    $File::Find::prune = 1;
+    return;
+  }
+
+  # ignore if it's not a file
+  return unless (-f $_);
+
+  # ignore if it's not .java
+  return unless (/\.java$/);
+
+  # call the junit checker and pass basename as third argument since
+  # File::Find has changed directory to where the file is
+  check_junit($File::Find::name, \%stats, $_);
+}
+
+sub check_junit {
+  my $file = shift;
+  my $stats = shift;
+  my $local_name = shift || $file;
+  my $fh = FileHandle->new($local_name) or
+    do { warn "Failed to open $file: $!\n"; return };
+
+  # want to read the whole file here, but rather than slurp the whole
+  # file, we read it line by line and create a map so that we can get
+  # the line numbers back after doing the multiline matches below.
+  my $pos = 0;
+  my $c = "";
+  my %pos_to_line_map = ();
+  while (<$fh>) {
+    $c .= $_;
+    $pos_to_line_map{$pos} = $fh->input_line_number();
+    $pos += length;
+  }
+  while ($c =~ /\n(\s*(?:fail|assert\w+)\s*\([^;]+\);)/g) {
+    my $assert = $1;
+    my $pos = pos($c) - length($assert);
+    my $line = $pos_to_line_map{$pos} || "?";
+    compress_white_space($assert);
+    my ($type, $args) = ($assert =~ /^(fail|assert\w+)\s*\(([^;]+)\);$/);
+    my $msg = trim_message_argument($args);
+    my $fn = $types{$type};
+    if ($fn) {
+      my $res = $fn->($type, $msg, $args);
+      if ($res && (!$pattern or $res =~ /$pattern/o)) {
+        print $file,' line ',$line,":\n  ",$assert,"\n  ",$res,"\n\n";
+        $stats->{type}->{$res}++;
+        $stats->{file}->{$file}++;
+        if ($file =~ m!modules/([^/]+)/!) {
+          $stats->{module}->{$1}++;
+        }
+      }
+    }
+  }
+}
+
+sub compress_white_space {
+  $_[0] =~ s/[\r\n\s]+/ /g;
+  $_[0] =~ s/^\s+//g;
+  $_[0] =~ s/\s+$//;
+  $_[0] =~ s/\s+\./\./;
+}
+
+sub trim_message_argument {
+  my $message;
+  while ($_[0]) {
+    my $part;
+    ($part, $_[0]) = extract_delimited($_[0], q{"});
+    last unless ($part);
+    $message .= $part;
+    $_[0] =~ s/^\s*,\s*// and last;
+    $_[0] =~ s/^(\s*\+\s*[^\,\+]+\s*\+?\s*)// and $message .= $1;
+    $_[0] =~ s/^\s*,\s*//;
+  }
+  return $message;
+}
+
+sub check_fail {
+  my $type = shift;
+  my $message = shift;
+  my $args = shift;
+  if ($message and
+      ( $message =~ /(got|unexpected|wrong).*exception/i or
+        $message =~ /^exception during/i ) ) {
+    return "exception may be left to junit";
+  }
+}
+
+sub check_assertEquals {
+  my $type = shift;
+  my $message = shift;
+  my $args = shift;
+  my @args = split(/,/, $args); # takes no account of nested commas in arguments
+  my $num_args = scalar @args;
+  my $simple = $num_args == 2;
+  foreach (@args) {
+    s/^\s+//;
+    s/\s+$//;
+  }
+  my $first = $args[0];
+  my $last = $args[$#args];
+  if ($last eq 'null') {
+    return "actual is null which is constant but should use assertNull";
+  } elsif ($last eq 'false') {
+    return "actual is false which is constant but should use assertFalse";
+  } elsif ($last eq 'true') {
+    return "actual is true which is constant but should use assertTrue";
+  } elsif ($first eq 'null') {
+    return "expected is null - should use assertNull";
+  } elsif ($first eq 'false') {
+    return "expected is false - should use assertFalse";
+  } elsif ($first eq 'true') {
+    return "expected is true - should use assertTrue";
+  } elsif ($simple and
+           ( $last =~ /^"[^"]*"$/ or
+             $last =~ /^'[^']+'$/ or
+             $last =~ /^[-0-9]+L?$/ )
+          ) {
+    return "actual may be a constant";
+  } elsif (( $last =~ /^"[^"]*"$/ or
+             $last =~ /^'[^']+'$/ or
+             $last =~ /^[-0-9]+L?$/ ) and
+           not ( $first =~ /^"[^"]*"$/ or
+                 $first =~ /^'[^']+'$/ or
+                 $first =~ /^[-0-9\.]+f?$/ ) and
+           $args !~ /(float|double)/i
+          ) {
+    return "actual *may* be a constant";
+  }
+}
+
+sub check_assertTrue {
+  my $type = shift;
+  my $message = shift;
+  my $args = shift;
+  if ($args eq "false") {
+    return "should be fail (always true)";
+  } elsif ($args =~ /\&\&/) {
+    return "consider using separate asserts for each '&&' component";
+  } elsif ($args =~ /^[^|&]+==\s*null$/ or
+           $args =~ /^\s*null\s*==[^|&]+$/) {
+    return "should use assertNull";
+  } elsif ($args =~ /^[^|&]+==\s*true$/ or
+           $args =~ /^\s*true\s*==[^|&]+$/) {
+    return "should use assertTrue";
+  } elsif ($args =~ /^[^|&]+==\s*false$/ or
+           $args =~ /^\s*false\s*==[^|&]+$/) {
+    return "should use assertFalse";
+  } elsif ($args =~ /Arrays\.equals/) {
+    return;
+  } elsif ($args =~ /^[^!|&]+\.equals[^|&]+$/ or
+           $args =~ /^[^|&]+==[^|&]+$/) {
+    return "should possibly use assertEquals";
+  }
+}
+
+sub check_assertFalse {
+  my $type = shift;
+  my $message = shift;
+  my $args = shift;
+  if ($args eq "true") {
+    return "should be fail (always false)";
+  } elsif ($args =~ /^[^|&]+!=\s*null$/ or
+           $args =~ /^\s*null\s*!=[^|&]+$/) {
+    return "should use assertNotNull";
+  } elsif ($args =~ /^[^|&]+!=\s*true$/ or
+           $args =~ /^\s*true\s*!=[^|&]+$/) {
+    return "should use assertFalse";
+  } elsif ($args =~ /^[^|&]+!=\s*false$/ or
+           $args =~ /^\s*false\s*!=[^|&]+$/) {
+    return "should use assertTrue";
+  } elsif ($args =~ /\|\|/) {
+    return "consider using separate asserts for each '||' component";
+  }
+}

Propchange: harmony/standard/tools/check-junit-code/check-junit-code
------------------------------------------------------------------------------
    svn:executable = *