# 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 cobertura:html
, cobertura:xml
and cobertura:check
tasks.
# Require explicitly using require "buildr/cobertura"
.
#
# You can generate cobertura reports for a single project
# using the project name as prefix:
#
# project_name:cobertura:html
#
# You can also specify which classes to include/exclude from instrumentation by
# passing a class name regexp to the cobertura.include
or
# cobertura.exclude
methods.
#
# define 'someModule' do
# cobertura.include 'some.package.*'
# cobertura.include /some.(foo|bar).*/
# cobertura.exclude 'some.foo.util.SimpleUtil'
# cobertura.exclude /*.Const(ants)?/i
# end
#
module Cobertura
VERSION = '1.9.4.1'
class << self
def version
Buildr.settings.build['cobertura'] || VERSION
end
end
REQUIRES = ArtifactNamespace.for(self).tap do |ns|
ns.cobertura! "net.sourceforge.cobertura:cobertura:jar:#{version}", '>=1.9'
ns.log4j! 'log4j:log4j:jar:1.2.9', ">=1.2.9"
ns.asm! 'asm:asm:jar:2.2.1', '>=2.2.1'
ns.asm_tree! 'asm:asm-tree:jar:2.2.1', '>=2.2.1'
ns.oro! 'oro:oro:jar:2.0.8', '>=2.0.8'
end
class << self
def dependencies
if (VersionRequirement.create('>=1.9.1').satisfied_by?(REQUIRES.cobertura.version))
[:asm, :asm_tree].each { |s| REQUIRES[s] = '3.0' unless REQUIRES[s].selected? }
end
REQUIRES.artifacts
end
def report_to(file = nil)
File.expand_path(File.join(*["reports/cobertura", file.to_s].compact))
end
def data_file()
File.expand_path("reports/cobertura.ser")
end
end
class CoberturaConfig # :nodoc:
def initialize(project)
@project = project
end
attr_reader :project
private :project
attr_writer :data_file, :instrumented_dir, :report_dir
def data_file
@data_file ||= project.path_to(:reports, 'cobertura.ser')
end
def instrumented_dir
@instrumented_dir ||= project.path_to(:target, :instrumented, :classes)
end
def report_dir
@report_dir ||= project.path_to(:reports, :cobertura)
end
def report_to(file = nil)
File.expand_path(File.join(*[report_dir, file.to_s].compact))
end
# :call-seq:
# project.cobertura.include(*classPatterns)
#
def include(*classPatterns)
includes.push(*classPatterns.map { |p| String === p ? Regexp.new(p) : p })
self
end
def includes
@includeClasses ||= []
end
# :call-seq:
# project.cobertura.exclude(*classPatterns)
#
def exclude(*classPatterns)
excludes.push(*classPatterns.map { |p| String === p ? Regexp.new(p) : p })
self
end
def excludes
@excludeClasses ||= []
end
def ignore(*regexps)
ignores.push(*regexps)
end
def ignores
@ignores ||= []
end
def sources
project.compile.sources
end
def check
@check ||= CoberturaCheck.new
end
end
class CoberturaCheck
attr_writer :branch_rate, :line_rate, :total_branch_rate, :total_line_rate, :package_line_rate, :package_branch_rate
attr_reader :branch_rate, :line_rate, :total_branch_rate, :total_line_rate, :package_line_rate, :package_branch_rate
end
module CoberturaExtension # :nodoc:
include Buildr::Extension
def cobertura
@cobertura_config ||= CoberturaConfig.new(self)
end
after_define do |project|
cobertura = project.cobertura
namespace 'cobertura' do
unless project.compile.target.nil?
# 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(cobertura.instrumented_dir => project.compile.target) do |task|
mkdir_p task.to_s
unless project.compile.sources.empty?
info "Instrumenting classes with cobertura data file #{cobertura.data_file}"
Buildr.ant "cobertura" do |ant|
ant.taskdef :resource=>"tasks.properties",
:classpath=>Buildr.artifacts(Cobertura.dependencies).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR)
ant.send "cobertura-instrument", :todir=>task.to_s, :datafile=>cobertura.data_file do
includes, excludes = cobertura.includes, cobertura.excludes
classes_dir = project.compile.target.to_s
if includes.empty? && excludes.empty?
ant.fileset :dir => classes_dir do
ant.include :name => "**/*.class"
end
else
includes = [//] if includes.empty?
Dir.glob(File.join(classes_dir, "**/*.class")) do |cls|
cls_name = cls.gsub(/#{classes_dir}\/?|\.class$/, '').gsub('/', '.')
if includes.any? { |p| p === cls_name } && !excludes.any? { |p| p === cls_name }
ant.fileset :file => cls
end
end
end
cobertura.ignores.each { |r| ant.ignore :regex => r }
end
end
end
touch task.to_s
end
task 'instrument' => instrumented
# We now have two target directories with bytecode. It would make sense to remove compile.target
# and add instrumented instead, but apparently Cobertura only creates some of the classes, so
# we need both directories and instrumented must come first.
project.test.dependencies.unshift cobertura.instrumented_dir
project.test.with Cobertura.dependencies
project.test.options[:properties]["net.sourceforge.cobertura.datafile"] = cobertura.data_file
unless project.compile.sources.empty?
[:xml, :html].each do |format|
task format => ['instrument', 'test'] do
info "Creating test coverage reports in #{cobertura.report_to(format)}"
Buildr.ant "cobertura" do |ant|
ant.taskdef :resource=>"tasks.properties",
:classpath=>Buildr.artifacts(Cobertura.dependencies).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR)
ant.send "cobertura-report", :format=>format,
:destdir=>cobertura.report_to(format), :datafile=>cobertura.data_file do
cobertura.sources.flatten.each do |src|
ant.fileset(:dir=>src.to_s) if File.exist?(src.to_s)
end
end
end
end
end
end
task :check => [:instrument, :test] do
Buildr.ant "cobertura" do |ant|
ant.taskdef :classpath=>Cobertura.dependencies.join(File::PATH_SEPARATOR), :resource=>"tasks.properties"
params = { :datafile => Cobertura.data_file }
# oh so ugly...
params[:branchrate] = cobertura.check.branch_rate if cobertura.check.branch_rate
params[:linerate] = cobertura.check.line_rate if cobertura.check.line_rate
params[:totalbranchrate] = cobertura.check.total_branch_rate if cobertura.check.total_branch_rate
params[:totallinerate] = cobertura.check.total_line_rate if cobertura.check.total_line_rate
params[:packagebranchrate] = cobertura.check.package_branch_rate if cobertura.check.package_branch_rate
params[:packagelinerate] = cobertura.check.package_line_rate if cobertura.check.package_line_rate
ant.send("cobertura-check", params) do
end
end
end
end
end
project.clean do
rm_rf [cobertura.report_to, cobertura.data_file, cobertura.instrumented_dir]
end
end
end
class Buildr::Project
include CoberturaExtension
end
namespace "cobertura" do
task "instrument" do
Buildr.projects.each do |project|
project.cobertura.data_file = data_file
project.test.options[:properties]["net.sourceforge.cobertura.datafile"] = data_file
instrument_task ="#{project.name}:cobertura:instrument"
task(instrument_task).invoke if Rake::Task.task_defined?(instrument_task)
end
end
[:xml, :html].each do |format|
report_target = report_to(format)
desc "Run the test cases and produce code coverage reports in #{report_target}"
task format => ["instrument", "test"] do
if Buildr.projects.detect { |project| !project.compile.sources.empty? }
info "Creating test coverage reports in #{report_target}"
Buildr.ant "cobertura" do |ant|
ant.taskdef :resource=>"tasks.properties",
:classpath=>Buildr.artifacts(Cobertura.dependencies).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR)
ant.send "cobertura-report", :destdir=>report_target, :format=>format, :datafile=>data_file do
Buildr.projects.map(&:cobertura).map(&:sources).flatten.each do |src|
ant.fileset :dir=>src.to_s if File.exist?(src.to_s)
end
end
end
end
end
end
task "clean" do
rm_rf [report_to, data_file]
end
end
task "clean" do
task("cobertura:clean").invoke if Dir.pwd == Rake.application.original_dir
end
end
end