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