Author: lacton
Date: Sat Aug 23 03:03:38 2008
New Revision: 688286

URL: http://svn.apache.org/viewvc?rev=688286&view=rev
Log:
BUILDR-128: Emma support (test coverage tool similar to Cobertura)

Added:
    incubator/buildr/trunk/addon/buildr/emma.rb
    incubator/buildr/trunk/spec/emma_spec.rb
Modified:
    incubator/buildr/trunk/CHANGELOG
    incubator/buildr/trunk/doc/pages/more_stuff.textile
    incubator/buildr/trunk/spec/spec_helpers.rb

Modified: incubator/buildr/trunk/CHANGELOG
URL: 
http://svn.apache.org/viewvc/incubator/buildr/trunk/CHANGELOG?rev=688286&r1=688285&r2=688286&view=diff
==============================================================================
--- incubator/buildr/trunk/CHANGELOG (original)
+++ incubator/buildr/trunk/CHANGELOG Sat Aug 23 03:03:38 2008
@@ -1,6 +1,7 @@
 1.3.3 (Pending)
 * Added:  Growl notifications (OS X only).
 * Added:  error, info and trace methods.
+* Added:  BUILDR-128 Emma support
 * Change: Error reporting now shows 'buildr aborted!' (used to say rake),
           more of the stack trace without running --trace, and when running
           with supported terminal, error message is red.

Added: incubator/buildr/trunk/addon/buildr/emma.rb
URL: 
http://svn.apache.org/viewvc/incubator/buildr/trunk/addon/buildr/emma.rb?rev=688286&view=auto
==============================================================================
--- incubator/buildr/trunk/addon/buildr/emma.rb (added)
+++ incubator/buildr/trunk/addon/buildr/emma.rb Sat Aug 23 03:03:38 2008
@@ -0,0 +1,236 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with this
+# work for additional information regarding copyright ownership.  The ASF
+# licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+
+require 'buildr/java'
+
+
+module Buildr
+
+  # Provides the <code>emma:html</code> and <code>emma:xml</code> tasks.
+  # Require explicitly using <code>require "buildr/emma"</code>.
+  #
+  # You can generate emma reports for a single project 
+  # using the project name as prefix:
+  #
+  #   project_name:emma:html
+  #
+  # You can also specify which classes to include/exclude from instrumentation 
by 
+  # passing a class name regexp to the <code>emma.include</code> or 
+  # <code>emma.exclude</code> methods. 
+  # 
+  #   define 'someModule' do 
+  #      emma.include 'some.package.*'
+  #      emma.exclude 'some.foo.util.SimpleUtil'
+  #   end
+  module Emma
+
+    class << self
+
+      REQUIRES = ['emma:emma_ant:jar:2.0.5312', 'emma:emma:jar:2.0.5312'] 
unless const_defined?('REQUIRES')
+
+      def requires()
+        @requires ||= Buildr.artifacts(REQUIRES).each(&:invoke).map(&:to_s)
+      end
+
+      def report_to format=nil
+        File.expand_path('reports/emma')
+      end
+
+      def data_file()
+        File.join(report_to, 'coverage.es')
+      end
+
+      def ant
+          Buildr.ant 'emma' do |ant|
+            ant.taskdef :classpath=>requires.join(File::PATH_SEPARATOR), 
:resource=>'emma_ant.properties'
+            ant.emma :verbosity=>(Buildr.application.options.trace ? 'verbose' 
: 'warning') do
+              yield ant
+            end
+          end
+      end
+    end
+    
+    class EmmaConfig # :nodoc:
+      
+      def initialize(project)
+        @project = project
+      end
+      
+      attr_reader :project
+      private :project
+
+      attr_writer :metadata_file, :coverage_file, :instrumented_dir, 
:report_dir
+      
+      def coverage_file
+        @coverage_file ||= File.join(report_dir, 'coverage.ec')
+      end
+
+      def metadata_file
+        @metadata_file ||= File.join(report_dir, 'coverage.em')
+      end
+
+      def instrumented_dir
+        @instrumented_dir ||= project.path_to(:target, :instrumented, :classes)
+      end
+
+      def report_dir
+        @report_dir ||= project.path_to(:reports, :emma)
+      end
+      
+      def report_to format
+        report_dir
+      end
+
+      # :call-seq:
+      #   project.emma.include(*classPatterns)
+      #
+      def include(*classPatterns)
+        includes.push(*classPatterns)
+        self
+      end
+      
+      def includes
+        @includeClasses ||= []
+      end
+
+      # :call-seq:
+      #   project.emma.exclude(*classPatterns)
+      #
+      def exclude(*classPatterns)
+        excludes.push(*classPatterns)
+        self
+      end
+
+      def excludes
+        @excludeClasses ||= []
+      end
+
+      def sources
+        project.compile.sources
+      end
+    end
+
+    module EmmaExtension # :nodoc:
+      include Buildr::Extension
+
+      def emma
+        @emma_config ||= EmmaConfig.new(self)
+      end
+
+      after_define do |project|
+        emma = project.emma
+        
+        namespace 'emma' do
+          # Instrumented bytecode goes in a different directory. This task 
creates before running the test
+          # cases and monitors for changes in the generate bytecode.
+          instrumented = project.file(emma.instrumented_dir => 
file(project.compile.target)) do |task|
+            unless project.compile.sources.empty?
+              info "Instrumenting classes with emma metadata file 
#{emma.metadata_file}"
+              Emma.ant do |ant|
+                ant.instr :instrpath=>project.compile.target.to_s, 
:destdir=>task.to_s, :metadatafile=>emma.metadata_file do
+                  ant.filter :includes=>emma.includes.join(', ') unless 
emma.includes.empty?
+                  ant.filter :excludes=>emma.excludes.join(', ') unless 
emma.excludes.empty?
+                end
+              end
+              touch task.to_s, :verbose=>false
+            end
+          end
+
+          task 'instrument' => instrumented
+          
+          [:xml, :html].each do |format|
+            task format => ['instrument', 'test'] do
+              missing_required_files = [emma.metadata_file, 
emma.coverage_file].reject { |f| File.exist?(f) }
+              if missing_required_files.empty?
+                info "Creating test coverage reports in #{emma.report_dir}"
+                mkdir_p emma.report_dir, :verbose=>false
+                Emma.ant do |ant|
+                  ant.report do
+                    ant.infileset :file=>emma.metadata_file
+                    ant.infileset :file=>emma.coverage_file
+                    ant.send format, 
:outfile=>File.join(emma.report_to(format),"coverage.#{format}")
+                    ant.sourcepath do
+                      emma.sources.flatten.each do |src|
+                        ant.dirset(:dir=>src.to_s) if File.exist?(src.to_s)
+                      end
+                    end
+                  end
+                end
+              else
+                info "No test coverage report for #{project}. Missing: 
#{missing_required_files.join(', ')}"
+              end
+            end
+          end
+            
+        end
+
+        # We now have two target directories with bytecode.
+        project.test.dependencies.unshift emma.instrumented_dir
+        project.test.with Emma.requires
+        project.test.options[:properties]["emma.coverage.out.file"] = 
emma.coverage_file
+        
+        project.clean do
+          rm_rf [emma.report_dir, emma.coverage_file, emma.metadata_file, 
emma.instrumented_dir], :verbose=>false
+        end
+        
+      end
+      
+    end
+
+    class Buildr::Project
+      include EmmaExtension
+    end
+
+    namespace "emma" do
+
+      Project.local_task('instrument') { |name| "Instrumenting #{name}" }
+      
+      [:xml, :html].each do |format|
+        desc "Run the test cases and produce code coverage reports in 
#{format}"
+        task format => ['instrument', 'test'] do
+          info "Creating test coverage reports in #{format}"
+          mkdir_p report_to(format), :verbose=>false
+          Emma.ant do |ant|
+            ant.merge :outfile=>data_file do
+              Buildr.projects.each do |project|
+                ant.fileset :file=>project.emma.metadata_file
+                ant.fileset :file=>project.emma.coverage_file
+              end
+            end
+            ant.report do
+              ant.infileset :file=>data_file
+              ant.send format, :outfile=>File.join(report_to(format), 
"coverage.#{format}")
+              ant.sourcepath do
+                
Buildr.projects.map(&:emma).map(&:sources).flatten.map(&:to_s).each do |src|
+                  ant.dirset :dir=>src if File.exist?(src)
+                end    
+              end
+            end
+          end
+        end
+      end
+      
+      task :clean do
+        rm_rf [report_to, data_file], :verbose=>false
+      end
+      end
+
+    task :clean do
+      task('emma:clean').invoke if Dir.pwd == Rake.application.original_dir
+    end
+
+  end
+end

Modified: incubator/buildr/trunk/doc/pages/more_stuff.textile
URL: 
http://svn.apache.org/viewvc/incubator/buildr/trunk/doc/pages/more_stuff.textile?rev=688286&r1=688285&r2=688286&view=diff
==============================================================================
--- incubator/buildr/trunk/doc/pages/more_stuff.textile (original)
+++ incubator/buildr/trunk/doc/pages/more_stuff.textile Sat Aug 23 03:03:38 2008
@@ -171,25 +171,25 @@
 Also, check out the "Buildr plugin for 
IDEA":http://www.digitalsanctum.com/buildr-plug-in/ (IDEA 7 and later).  Once 
installed, open your project with IDEA.  If IDEA finds that you have Buildr 
installed and finds a buildfile in the project's directory, it will show all 
the tasks available for that project.  To run a task, double-click it.  When 
the task completes, IDEA will show the results in the Buildr Output window.
 
 
-h2. Cobertura, JDepend
+h2. Cobertura, Emma, JDepend
 
-You can use "Cobertura":http://cobertura.sourceforge.net/ to instrument your 
code, run the tests and create a test coverage report in either HTML or XML 
format.
+You can use "Cobertura":http://cobertura.sourceforge.net/ or 
"Emma":http://emma.sourceforge.net/ to instrument your code, run the tests and 
create a test coverage report in either HTML or XML format.
 
-There are two tasks, both of which generate a test coverage report in the 
@reports/cobertura@ directory.  For example:
+There are two tasks for each tool, both of which generate a test coverage 
report in the @reports/cobertura@ (respectively @reports/emma@) directory.  For 
example:
 
 {{{!sh
 $ buildr test cobertura:html
 }}}
 
-As you can guess, the other task is @cobertura:[EMAIL PROTECTED]
+As you can guess, the other tasks are @cobertura:xml@, @emma:html@ and 
@emma:[EMAIL PROTECTED]
 
-If you want to generate cobertura reports only for a specific project, you can 
do so by using the project name as prefix to cobertura tasks. 
+If you want to generate a test coverage report only for a specific project, 
you can do so by using the project name as prefix to the tasks. 
 
 {{{!sh
 $ buildr subModule:cobertura:html
 }}}
 
-Each project can specify which classes to include or exclude from cobertura 
instrumentation by giving a class-name regexp to the @cobertura.include@ or  
@cobertura.exclude@ methods:
+Each project can specify which classes to include or exclude from cobertura 
instrumentation by giving a class-name regexp to the @cobertura.include@ or 
@cobertura.exclude@ methods:
 
 {{{!ruby
 define 'someModule' do 
@@ -200,6 +200,8 @@
 end
 }}}
 
+Emma has @include@ and @exclude@ methods too, but they take glob patterns 
instead of regexps.
+
 You can use "JDepend":http://clarkware.com/software/JDepend.html on to 
generate design quality metrics.  There are three tasks this time, the eye 
candy one:
 
 {{{!sh
@@ -208,14 +210,15 @@
 
 The other two tasks are @jdepend:text@ and @jdepend:[EMAIL PROTECTED]
 
-We want Buildr to load fast, and not everyone cares for these tasks, so we 
don't include them by default.  If you want to use either one, you need to 
require it explicitly.  The proper way to do it in Ruby:
+We want Buildr to load fast, and not everyone cares for these tasks, so we 
don't include them by default.  If you want to use one of them, you need to 
require it explicitly.  The proper way to do it in Ruby:
 
 {{{!ruby
 require 'buildr/cobertura'
+require 'buildr/emma'
 require 'buildr/jdepend'
 }}}
 
-You may want to add those to the Buildfile.  Alternatively, you can use these 
tasks for all your projects without modifying the Buildfile.  One convenient 
method is to add these two likes to the @buildr.rb@ file in your home directory.
+You may want to add those to the Buildfile.  Alternatively, you can use these 
tasks for all your projects without modifying the Buildfile.  One convenient 
method is to add these lines to the @buildr.rb@ file in your home directory.
 
 Another option is to require it from the command line (@--require@ or @-r@), 
for example:
 

Added: incubator/buildr/trunk/spec/emma_spec.rb
URL: 
http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/emma_spec.rb?rev=688286&view=auto
==============================================================================
--- incubator/buildr/trunk/spec/emma_spec.rb (added)
+++ incubator/buildr/trunk/spec/emma_spec.rb Sat Aug 23 03:03:38 2008
@@ -0,0 +1,120 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with this
+# work for additional information regarding copyright ownership.  The ASF
+# licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+
+require File.join(File.dirname(__FILE__), 'spec_helpers')
+require File.join(File.dirname(__FILE__), 'test_coverage_spec')
+Sandbox.require_addon File.join(File.dirname(__FILE__), '../addon', 
'buildr/emma')
+
+Buildr::Emma::requires
+
+
+describe Buildr::Emma do
+  before do
+    # Reloading the addon because the sandbox removes all its actions
+    load File.expand_path('../addon/buildr/emma.rb')
+    @tool_module = Buildr::Emma
+  end
+
+  it_should_behave_like 'test coverage tool'
+  
+  describe 'project-specific' do
+    describe 'metadata file' do
+      it 'should have a default value' do
+        define('foo').emma.metadata_file.should 
point_to_path('reports/emma/coverage.em')
+      end
+      
+      it 'should be overridable' do
+        define('foo') { emma.metadata_file = path_to('target/metadata.emma') }
+        project('foo').emma.metadata_file.should 
point_to_path('target/metadata.emma')
+      end
+      
+      it 'should be created during instrumentation' do
+        write 'src/main/java/Foo.java', 'public class Foo {}'
+        define('foo')
+        task('foo:emma:instrument').invoke
+        file(project('foo').emma.metadata_file).should exist
+      end
+    end
+    
+    describe 'coverage file' do
+      it 'should have a default value' do
+        define('foo').emma.coverage_file.should 
point_to_path('reports/emma/coverage.ec')
+      end
+      
+      it 'should be overridable' do
+        define('foo') { emma.coverage_file = path_to('target/coverage.emma') }
+        project('foo').emma.coverage_file.should 
point_to_path('target/coverage.emma')
+      end
+      
+      it 'should be created during test' do
+        write 'src/main/java/Foo.java', 'public class Foo {}'
+        write_test :for=>'Foo', :in=>'src/test/java'
+        define('foo')
+        task('foo:test').invoke
+        file(project('foo').emma.coverage_file).should exist
+      end
+    end
+
+    describe 'instrumentation' do
+      before do
+        ['Foo', 'Bar'].each { |cls| write File.join('src/main/java', 
"#{cls}.java"), "public class #{cls} {}" }
+      end
+
+      it 'should instrument only included classes' do
+        define('foo') { emma.include 'Foo' }
+        task("foo:emma:instrument").invoke
+        Dir.chdir('target/instrumented/classes') { Dir.glob('*').sort.should 
== ['Foo.class'] }
+      end
+
+      it 'should not instrument excluded classes' do
+        define('foo') { emma.exclude 'Foo' }
+        task("foo:emma:instrument").invoke
+        Dir.chdir('target/instrumented/classes') { Dir.glob('*').sort.should 
== ['Bar.class'] }
+      end
+      
+      it 'should instrument classes that are included but not excluded' do
+        write 'src/main/java/Baz.java', 'public class Baz {}'
+        define('foo') { emma.include('Ba*').exclude('*ar') }
+        task("foo:emma:instrument").invoke
+        Dir.chdir('target/instrumented/classes') { Dir.glob('*').sort.should 
== ['Baz.class'] }
+      end
+    end
+    
+    describe 'reports' do
+      before do
+        write 'src/main/java/Foo.java', 'public class Foo {}'
+        write_test :for=>'Foo', :in=>'src/test/java'
+      end
+      
+      describe 'in html' do
+        it 'should inform the user if no coverage data' do
+          rm 'src/test/java/FooTest.java'
+          define('foo')
+          lambda { task('foo:emma:html').invoke }.
+            should show_info(/No test coverage report for foo. Missing: 
#{project('foo').emma.coverage_file}/)
+        end
+      end
+      
+      describe 'in xml' do
+        it 'should have an xml file' do
+          define('foo')
+          task('foo:emma:xml').invoke
+          file(File.join(project('foo').emma.report_dir, 
'coverage.xml')).should exist
+        end
+      end      
+    end
+  end
+end

Modified: incubator/buildr/trunk/spec/spec_helpers.rb
URL: 
http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/spec_helpers.rb?rev=688286&r1=688285&r2=688286&view=diff
==============================================================================
--- incubator/buildr/trunk/spec/spec_helpers.rb (original)
+++ incubator/buildr/trunk/spec/spec_helpers.rb Sat Aug 23 03:03:38 2008
@@ -28,13 +28,15 @@
 
     include Checks::Matchers
 
-    class ::Object #:nodoc:
-      def warn(message)
-        $warning ||= []
-        $warning << message
+    [:info, :warn, :error].each do |severity|
+      ::Object.class_eval do
+        define_method severity do |message|
+          $messages ||= {}
+          ($messages[severity] ||= []) << message
+        end
       end
     end
-
+    
     class << Buildr.application
       alias :deprecated_without_capture :deprecated
       def deprecated(message)
@@ -42,67 +44,50 @@
       end
     end
 
-    class WarningMatcher
-      def initialize(message)
+    class MessageWithSeverityMatcher
+      def initialize(severity, message)
+        @severity = severity
         @expect = message
       end
 
       def matches?(target)
-        $warning = []
+        [EMAIL PROTECTED] = []
         target.call
-        return Regexp === @expect ? $warning.join('\n') =~ @expect : 
$warning.include?(@expect.to_s)
+        return Regexp === @expect ? [EMAIL PROTECTED]('\n') =~ @expect : 
[EMAIL PROTECTED](@expect.to_s)
       end
 
       def failure_message
-        $warning ? "Expected warning [EMAIL PROTECTED], found #{$warning}" : 
"Expected warning [EMAIL PROTECTED], no warning issued"
+        $messages ? "Expected [EMAIL PROTECTED] '[EMAIL PROTECTED]', found 
[EMAIL PROTECTED]" : \
+          "Expected [EMAIL PROTECTED] '[EMAIL PROTECTED]', no [EMAIL 
PROTECTED] issued"
       end
 
       def negative_failure_message
-        "Found unexpected #{$warning}"
+        "Found unexpected '[EMAIL PROTECTED]'"
       end
     end
 
+    # Test if an info message was shown.  You can use a string or regular 
expression.
+    #
+    # For example:
+    #   lambda { info 'ze test' }.should show_info(/ze test/)
+    def show_info(message)
+      MessageWithSeverityMatcher.new :info, message
+    end
+
     # Tests if a warning was issued. You can use a string or regular 
expression.
     #
     # For example:
     #   lambda { warn 'ze test' }.should show_warning(/ze test/)
     def show_warning(message)
-      WarningMatcher.new message
-    end
-
-    class ::Object  #:nodoc:
-      def error(message)
-        $error ||= []
-        $error << message
-      end
+      MessageWithSeverityMatcher.new :warn, message
     end
 
-    class ErrorMessageMatcher
-      def initialize(message)
-        @expect = message
-      end
-
-      def matches?(target)
-        $error = []
-        target.call
-        return Regexp === @expect ? $error.join('\n') =~ @expect : 
$error.include?(@expect.to_s)
-      end
-
-      def failure_message
-        $error ? "Expected error [EMAIL PROTECTED], found #{$error}" : 
"Expected error [EMAIL PROTECTED], no error issued"
-      end
-
-      def negative_failure_message
-        "Found unexpected #{$error}"
-      end
-    end
-    
-    # Test if error message was shown.  You can use a string or regular 
expression.
+    # Test if an error message was shown.  You can use a string or regular 
expression.
     #
     # For example:
     #   lambda { error 'ze test' }.should show_error(/ze test/)
     def show_error(message)
-      ErrorMessageMatcher.new message
+      MessageWithSeverityMatcher.new :error, message
     end
 
 


Reply via email to