Author: lacton
Date: Fri Aug 22 15:54:59 2008
New Revision: 688213
URL: http://svn.apache.org/viewvc?rev=688213&view=rev
Log:
BUILDR-127: Tests for Cobertura addon
The tests are divided between those that are specific to Cobertura
(cobertura_spec.rb) and those that should be true for most test coverage tool
(test_coverage_spec.rb).
Changed 'sandbox.rb' so that loading the Cobertura addon does not affect other
tests (i.e., the sandbox removes the Cobertura callbacks). Otherwise, some
tests fail for wrong reasons.
Added:
incubator/buildr/trunk/spec/cobertura_spec.rb
incubator/buildr/trunk/spec/test_coverage_spec.rb
Modified:
incubator/buildr/trunk/addon/buildr/cobertura.rb
incubator/buildr/trunk/spec/sandbox.rb
Modified: incubator/buildr/trunk/addon/buildr/cobertura.rb
URL:
http://svn.apache.org/viewvc/incubator/buildr/trunk/addon/buildr/cobertura.rb?rev=688213&r1=688212&r2=688213&view=diff
==============================================================================
--- incubator/buildr/trunk/addon/buildr/cobertura.rb (original)
+++ incubator/buildr/trunk/addon/buildr/cobertura.rb Fri Aug 22 15:54:59 2008
@@ -42,7 +42,7 @@
class << self
REQUIRES = ["net.sourceforge.cobertura:cobertura:jar:1.9",
"log4j:log4j:jar:1.2.9",
- "asm:asm:jar:2.2.1", "asm:asm-tree:jar:2.2.1", "oro:oro:jar:2.0.8"]
+ "asm:asm:jar:2.2.1", "asm:asm-tree:jar:2.2.1", "oro:oro:jar:2.0.8"]
unless const_defined?('REQUIRES')
def requires()
@requires ||= Buildr.artifacts(REQUIRES).each(&:invoke).map(&:to_s)
Added: incubator/buildr/trunk/spec/cobertura_spec.rb
URL:
http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/cobertura_spec.rb?rev=688213&view=auto
==============================================================================
--- incubator/buildr/trunk/spec/cobertura_spec.rb (added)
+++ incubator/buildr/trunk/spec/cobertura_spec.rb Fri Aug 22 15:54:59 2008
@@ -0,0 +1,78 @@
+# 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/cobertura')
+
+Buildr::Cobertura::requires
+
+
+describe Buildr::Cobertura do
+ before do
+ # Reloading the addon because the sandbox removes all its actions
+ load File.expand_path('../addon/buildr/cobertura.rb')
+ @tool_module = Buildr::Cobertura
+ end
+
+ it_should_behave_like 'test coverage tool'
+
+ describe 'project-specific' do
+
+ describe 'data file' do
+ it 'should have a default value' do
+ define('foo').cobertura.data_file.should
point_to_path('reports/cobertura.ser')
+ end
+
+ it 'should be overridable' do
+ define('foo') { cobertura.data_file = path_to('target/data.cobertura')
}
+ project('foo').cobertura.data_file.should
point_to_path('target/data.cobertura')
+ end
+
+ it 'should be created during instrumentation' do
+ write 'src/main/java/Foo.java', 'public class Foo {}'
+ define('foo')
+ task('foo:cobertura:instrument').invoke
+ file(project('foo').cobertura.data_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') { cobertura.include 'Foo' }
+ task("foo:cobertura:instrument").invoke
+ Dir.chdir('target/instrumented/classes') { Dir.glob('*').sort.should
== ['Foo.class'] }
+ end
+
+ it 'should not instrument excluded classes' do
+ define('foo') { cobertura.exclude 'Foo' }
+ task("foo:cobertura: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') { cobertura.include('Ba').exclude('ar') }
+ task("foo:cobertura:instrument").invoke
+ Dir.chdir('target/instrumented/classes') { Dir.glob('*').sort.should
== ['Baz.class'] }
+ end
+ end
+ end
+end
\ No newline at end of file
Modified: incubator/buildr/trunk/spec/sandbox.rb
URL:
http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/sandbox.rb?rev=688213&r1=688212&r2=688213&view=diff
==============================================================================
--- incubator/buildr/trunk/spec/sandbox.rb (original)
+++ incubator/buildr/trunk/spec/sandbox.rb Fri Aug 22 15:54:59 2008
@@ -40,6 +40,16 @@
spec.before(:each) { sandbox }
spec.after(:each) { reset }
end
+
+ # Require an addon without letting its callbacks pollute the Project class.
+ def require_addon addon_require_path
+ project_callbacks_without_addon = Project.class_eval { @callbacks }.dup
+ begin
+ require addon_require_path
+ ensure
+ Project.class_eval { @callbacks = project_callbacks_without_addon }
+ end
+ end
end
@tasks = Buildr.application.tasks.collect do |original|
@@ -75,6 +85,7 @@
# Later on we'll want to lose all the on_define created during the test.
@_sandbox[:on_define] = Project.class_eval { (@on_define || []).dup }
+ @_sandbox[:callbacks] = Project.class_eval { (@callbacks || []).dup }
@_sandbox[:layout] = Layout.default.clone
# Create a local repository we can play with. However, our local
repository will be void
@@ -105,6 +116,8 @@
Project.clear
on_define = @_sandbox[:on_define]
Project.class_eval { @on_define = on_define }
+ callbacks = @_sandbox[:callbacks]
+ Project.class_eval { @callbacks = callbacks }
Layout.default = @_sandbox[:layout].clone
$LOAD_PATH.replace @_sandbox[:load_path]
Added: incubator/buildr/trunk/spec/test_coverage_spec.rb
URL:
http://svn.apache.org/viewvc/incubator/buildr/trunk/spec/test_coverage_spec.rb?rev=688213&view=auto
==============================================================================
--- incubator/buildr/trunk/spec/test_coverage_spec.rb (added)
+++ incubator/buildr/trunk/spec/test_coverage_spec.rb Fri Aug 22 15:54:59 2008
@@ -0,0 +1,221 @@
+# 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')
+
+module TestCoverageHelper
+ def write_test options
+ write File.join(options[:in], "#{options[:for]}Test.java"),
+ "public class #{options[:for]}Test extends junit.framework.TestCase {
public void test#{options[:for]}() { new #{options[:for]}(); } }"
+ end
+
+ # Rspec matcher using file glob patterns.
+ class FileNamePatternMatcher
+ def initialize(pattern)
+ @expected_pattern = pattern
+ @pattern_matcher = lambda { |filename| File.fnmatch? pattern, filename }
+ end
+
+ def matches?(directory)
+ @actual_filenames = Dir[File.join(directory,'*')]
+ @actual_filenames.any? &@pattern_matcher
+ end
+
+ def failure_message
+ "Expected to find at least one element matching '[EMAIL PROTECTED]'
among [EMAIL PROTECTED], but found none"
+ end
+
+ def negative_failure_message
+ "Expected to find no element matching '[EMAIL PROTECTED]' among [EMAIL
PROTECTED], but found matching element(s) [EMAIL
PROTECTED](&@pattern_matcher).inspect}"
+ end
+ end
+
+ def have_files_matching pattern
+ FileNamePatternMatcher.new pattern
+ end
+end
+
+describe 'test coverage tool', :shared=>true do
+ include TestCoverageHelper
+
+ def toolname
+ @tool_module.name.split('::').last.downcase
+ end
+
+ describe 'project-specific' do
+
+ before do
+ write 'src/main/java/Foo.java', 'public class Foo {}'
+ write_test :for=>'Foo', :in=>'src/test/java'
+ end
+
+ def test_coverage_config
+ project('foo').send(toolname)
+ end
+
+ describe 'clean' do
+ before { define('foo') }
+
+ it 'should remove the instrumented directory' do
+ mkdir_p test_coverage_config.instrumented_dir.to_s
+ task('foo:clean').invoke
+ file(test_coverage_config.instrumented_dir).should_not exist
+ end
+
+ it 'should remove the reporting directory' do
+ mkdir_p test_coverage_config.report_dir
+ task('foo:clean').invoke
+ file(test_coverage_config.report_dir).should_not exist
+ end
+ end
+
+ describe 'instrumented directory' do
+ it 'should have a default value' do
+ define('foo')
+ test_coverage_config.instrumented_dir.should
point_to_path('target/instrumented/classes')
+ end
+
+ it 'should be overridable' do
+ toolname = toolname()
+ define('foo') { send(toolname).instrumented_dir =
path_to('target/coverage/classes') }
+ test_coverage_config.instrumented_dir.should
point_to_path('target/coverage/classes')
+ end
+
+ it 'should be created during instrumentation' do
+ define('foo')
+ task("foo:#{toolname}:instrument").invoke
+ file(test_coverage_config.instrumented_dir).should exist
+ end
+ end
+
+ describe 'instrumentation' do
+ def instrumented_dir
+ file(test_coverage_config.instrumented_dir)
+ end
+
+ it 'should happen after compile' do
+ define('foo')
+ lambda { task("foo:#{toolname}:instrument").invoke }.should
run_task('foo:compile')
+ end
+
+ it 'should put classes from compile.target in the instrumented
directory' do
+ define('foo')
+ task("foo:#{toolname}:instrument").invoke
+ Dir.entries(instrumented_dir.to_s).should ==
Dir.entries(project('foo').compile.target.to_s)
+ end
+
+ it 'should touch instrumented directory if anything instrumented' do
+ a_long_time_ago = Time.now - 10
+ define('foo')
+ mkpath instrumented_dir.to_s
+ File.utime(a_long_time_ago, a_long_time_ago, instrumented_dir.to_s)
+ task("foo:#{toolname}:instrument").invoke
+ instrumented_dir.timestamp.should be_close(Time.now, 2)
+ end
+
+ it 'should not touch instrumented directory if nothing instrumented' do
+ a_long_time_ago = Time.now - 10
+ define('foo').compile.invoke
+ mkpath instrumented_dir.to_s
+ [project('foo').compile.target, instrumented_dir].map(&:to_s).each {
|dir| File.utime(a_long_time_ago, a_long_time_ago, dir) }
+ task("foo:#{toolname}:instrument").invoke
+ instrumented_dir.timestamp.should be_close(a_long_time_ago, 2)
+ end
+ end
+
+ describe 'testing classpath' do
+ it 'should give priority to instrumented classes over non-instrumented
ones' do
+ define('foo')
+ depends = project('foo').test.dependencies
+ depends.index(test_coverage_config.instrumented_dir).should <
depends.index(project('foo').compile.target)
+ end
+
+ it 'should have the test coverage tools artifacts' do
+ define('foo')
+ @tool_module.requires.each { |artifact|
project('foo').test.dependencies.should include(artifact) }
+ end
+ end
+
+ describe 'html report' do
+ it 'should have html files' do
+ define('foo')
+ task("foo:#{toolname}:html").invoke
+ test_coverage_config.report_to(:html).should
have_files_matching('*.html')
+ end
+
+ it 'should contain full source code, including comments' do
+ write 'src/main/java/Foo.java',
+ 'public class Foo { /* This comment is a TOKEN to check that test
coverage reports include the source code */ }'
+ define('foo')
+ task("foo:#{toolname}:html").invoke
+ htlm_report_contents = Dir[File.join(test_coverage_config.report_dir,
'**/*.html')].map{|path|File.open(path).read}.join
+ htlm_report_contents.should =~ /TOKEN/
+ end
+ end
+ end
+
+ describe 'cross-project' do
+ describe 'reporting' do
+ before do
+ write 'src/main/java/Foo.java', 'public class Foo {}'
+ write 'bar/src/main/java/Bar.java', 'public class Bar {}'
+ write_test :for=>'Bar', :in=>'bar/src/test/java'
+ define('foo') { define('bar')}
+ end
+
+ it 'should have a default target' do
+ @tool_module.report_to.should point_to_path(File.join('reports',
toolname))
+ end
+
+ describe 'in html' do
+ it 'should be a defined task' do
+ Rake::Task.task_defined?("#{toolname}:html").should be(true)
+ end
+
+ it 'should happen after project instrumentation and testing' do
+ lambda { task("#{toolname}:html").invoke }.should
run_tasks(["foo:#{toolname}:instrument", 'foo:bar:test'])
+ end
+
+ it 'should have html files' do
+ task("#{toolname}:html").invoke
+ @tool_module.report_to(:html).should have_files_matching('*.html')
+ end
+
+ it 'should contain full source code, including comments' do
+ write 'bar/src/main/java/Bar.java',
+ 'public class Bar { /* This comment is a TOKEN to check that test
coverage reports include the source code */ }'
+ task("#{toolname}:html").invoke
+ htlm_report_contents = Dir[File.join(@tool_module.report_to(:html),
'**/*.html')].map{|path|File.open(path).read}.join
+ htlm_report_contents.should =~ /TOKEN/
+ end
+ end
+ end
+
+ describe 'clean' do
+ it 'should remove the report directory' do
+ define('foo')
+ mkdir_p @tool_module.report_to
+ task("#{toolname}:clean").invoke
+ file(@tool_module.report_to).should_not exist
+ end
+
+ it 'should be called when calling global clean' do
+ define('foo')
+ lambda { task('clean').invoke }.should run_task("#{toolname}:clean")
+ end
+ end
+ end
+end
\ No newline at end of file