blob: 23bf7192535429947a975addbfa46396de116e13 [file] [log] [blame]
# 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.
module Buildr #:nodoc:
class TestFramework::Java < TestFramework::Base
class << self
def applies_to?(project) #:nodoc:
project.test.compile.language == :java || project.test.compile.language == :groovy
end
def dependencies
unless @dependencies
super
# Add buildr utility classes (e.g. JavaTestFilter)
@dependencies |= [ File.join(File.dirname(__FILE__)) ]
end
@dependencies
end
end
private
# Add buildr utilities (JavaTestFilter) to classpath
Java.classpath << lambda { dependencies }
# :call-seq:
# filter_classes(dependencies, criteria)
#
# Return a list of classnames that match the given criteria.
# The criteria parameter is a hash that must contain at least one of:
#
# * :class_names -- List of patterns to match against class name
# * :interfaces -- List of java interfaces or java classes
# * :class_annotations -- List of annotations on class level
# * :method_annotations -- List of annotations on method level
# * :fields -- List of java field names
#
def filter_classes(dependencies, criteria = {})
return [] unless task.compile.target
target = task.compile.target.to_s
candidates = Dir["#{target}/**/*.class"].
map { |file| Util.relative_path(file, target).ext('').gsub(File::SEPARATOR, '.') }.
reject { |name| name =~ /\$./ }
result = []
if criteria[:class_names]
result.concat candidates.select { |name| criteria[:class_names].flatten.any? { |pat| pat === name } }
end
begin
Java.load
filter = Java.org.apache.buildr.JavaTestFilter.new(dependencies.to_java(Java.java.lang.String))
if criteria[:interfaces]
filter.add_interfaces(criteria[:interfaces].to_java(Java.java.lang.String))
end
if criteria[:class_annotations]
filter.add_class_annotations(criteria[:class_annotations].to_java(Java.java.lang.String))
end
if criteria[:method_annotations]
filter.add_method_annotations(criteria[:method_annotations].to_java(Java.java.lang.String))
end
if criteria[:fields]
filter.add_fields(criteria[:fields].to_java(Java.java.lang.String))
end
result.concat filter.filter(candidates.to_java(Java.java.lang.String)).map(&:to_s)
rescue =>ex
info "#{ex.class}: #{ex.message}"
raise
end
end
end
# JMock is available when using JUnit and TestNG, JBehave.
module JMock
VERSION = '2.5.1'
class << self
def version
Buildr.settings.build['jmock'] || VERSION
end
def dependencies(versions = {:hamcrest => '1.1'})
two_or_later = version[0,1].to_i >= 2
group = two_or_later ? 'org.jmock' : 'jmock'
@dependencies ||= ["#{group}:jmock:jar:#{version}"]
if two_or_later
@dependencies << "org.jmock:jmock-junit#{Buildr::JUnit.version.to_s[0,1]}:jar:#{version}"
@dependencies << "org.hamcrest:hamcrest-core:jar:#{versions[:hamcrest]}"
@dependencies << "org.hamcrest:hamcrest-library:jar:#{versions[:hamcrest]}"
end
@dependencies
end
end
end
# JUnit test framework, the default test framework for Java tests.
#
# Support the following options:
# * :fork -- If true/:once (default), fork for each test class. If :each, fork for each individual
# test case. If false, run all tests in the same VM (fast, but dangerous).
# * :clonevm -- If true clone the VM each time it is forked.
# * :properties -- Hash of system properties available to the test case.
# * :environment -- Hash of environment variables available to the test case.
# * :java_args -- Arguments passed as is to the JVM.
class JUnit < TestFramework::Java
# Used by the junit:report task. Access through JUnit#report if you want to set various
# options for that task, for example:
# JUnit.report.frames = false
class Report
# Parameters passed to the Ant JUnitReport task.
attr_reader :params
# True (default) to produce a report using frames, false to produce a single-page report.
attr_accessor :frames
# Directory for the report style (defaults to using the internal style).
attr_accessor :style_dir
# Target directory for generated report.
attr_accessor :target
def initialize
@params = {}
@frames = true
@target = 'reports/junit'
end
# :call-seq:
# generate(projects, target?)
#
# Generates a JUnit report for these projects (must run JUnit tests first) into the
# target directory. You can specify a target, or let it pick the default one from the
# target attribute.
def generate(projects, target = @target.to_s)
html_in = File.join(target, 'html')
rm_rf html_in ; mkpath html_in
Buildr.ant('junit-report') do |ant|
ant.taskdef :name=>'junitreport', :classname=>'org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator',
:classpath=>Buildr.artifacts(JUnit.ant_taskdef).each(&:invoke).map(&:to_s).join(File::PATH_SEPARATOR)
ant.junitreport :todir=>target do
projects.select { |project| project.test.framework == :junit }.
map { |project| project.test.report_to.to_s }.select { |path| File.exist?(path) }.
each { |path| ant.fileset(:dir=>path) { ant.include :name=>'TEST-*.xml' } }
options = { :format=>frames ? 'frames' : 'noframes' }
options[:styledir] = style_dir if style_dir
ant.report options.merge(:todir=>html_in) do
params.each { |key, value| ant.param :name=>key, :expression=>value }
end
end
end
end
end
# JUnit version number.
VERSION = '4.11'
class << self
# :call-seq:
# report()
#
# Returns the Report object used by the junit:report task. You can use this object to set
# various options that affect your report, for example:
# JUnit.report.frames = false
# JUnit.report.params['title'] = 'My App'
def report
@report ||= Report.new
end
def version
Buildr.settings.build['junit'] || VERSION
end
def dependencies
four11_or_newer = version >= '4.11'
@dependencies ||= ["junit:junit:jar:#{version}"]+ (four11_or_newer ? JMock.dependencies({:hamcrest => '1.3'}) : JMock.dependencies)
end
def ant_taskdef #:nodoc:
"org.apache.ant:ant-junit:jar:#{Ant.version}"
end
end
def tests(dependencies) #:nodoc:
if (self.class.version.to_s[0,1].to_i < 4)
filter_classes(dependencies, :interfaces => %w{junit.framework.TestCase})
else
filter_classes(dependencies,
:interfaces => %w{junit.framework.TestCase},
:class_annotations => %w{org.junit.runner.RunWith},
:method_annotations => %w{org.junit.Test})
end
end
def run(tests, dependencies) #:nodoc:
# Use Ant to execute the Junit tasks, gives us performance and reporting.
Buildr.ant('junit') do |ant|
case options[:fork]
when false
forking = {}
when :each
forking = { :fork=>true, :forkmode=>'perTest' }
when nil, true, :once
forking = { :fork=>true, :forkmode=>'once' }
else
fail 'Option fork must be :once, :each or false.'
end
mkpath task.report_to.to_s
taskdef = Buildr.artifact(JUnit.ant_taskdef)
taskdef.invoke
ant.taskdef :name=>'junit', :classname=>'org.apache.tools.ant.taskdefs.optional.junit.JUnitTask', :classpath=>taskdef.to_s
ant.junit forking.merge(:clonevm=> !!options[:clonevm], :dir=>task.send(:project).path_to) do
ant.classpath :path=>dependencies.join(File::PATH_SEPARATOR)
(options[:properties] || []).each { |key, value| ant.sysproperty :key=>key, :value=>value }
(options[:environment] || []).each { |key, value| ant.env :key=>key, :value=>value }
Array(options[:java_args]).each { |value| ant.jvmarg :value=>value }
ant.formatter :type=>'plain'
ant.formatter :type=>'plain', :usefile=>false # log test
ant.formatter :type=>'xml'
ant.batchtest :todir=>task.report_to.to_s, :failureproperty=>'failed' do
ant.fileset :dir=>task.compile.target.to_s do
tests.each { |test| ant.include :name=>File.join(*test.split('.')).ext('class') }
end
end
end
return tests unless ant.project.getProperty('failed')
end
# But Ant doesn't tell us what went kaput, so we'll have to parse the test files.
tests.inject([]) do |passed, test|
report_file = File.join(task.report_to.to_s, "TEST-#{test}.txt")
if File.exist?(report_file)
report = File.read(report_file)
# The second line (if exists) is the status line and we scan it for its values.
status = (report.split("\n")[1] || '').scan(/(run|failures|errors):\s*(\d+)/i).
inject(Hash.new(0)) { |hash, pair| hash[pair[0].downcase.to_sym] = pair[1].to_i ; hash }
passed << test if status[:failures] == 0 && status[:errors] == 0
end
passed
end
end
namespace 'junit' do
desc "Generate JUnit tests report in #{report.target}"
task('report') do |task|
report.generate Project.projects
info "Generated JUnit tests report in #{report.target}"
end
end
task('clean') { rm_rf report.target.to_s }
end
# TestNG test framework. To use in your project:
# test.using :testng
#
# Support the following options:
# * :properties -- Hash of properties passed to the test suite.
# * :java_args -- Arguments passed to the JVM.
# * :args -- Arguments passed to the TestNG command line runner.
class TestNG < TestFramework::Java
VERSION = '6.11'
class << self
def version
Buildr.settings.build['testng'] || VERSION
end
def dependencies
return ["org.testng:testng:jar:jdk15:#{version}"] + JMock.dependencies if version < '6.0'
%W(org.testng:testng:jar:#{version} com.beust:jcommander:jar:1.27) + JMock.dependencies
end
end
def tests(dependencies) #:nodoc:
filter_classes(dependencies,
:class_annotations => %w{org.testng.annotations.Test},
:method_annotations => %w{org.testng.annotations.Test})
end
def run(tests, dependencies) #:nodoc:
cmd_args = []
cmd_args << '-suitename' << task.project.id
cmd_args << '-sourcedir' << task.compile.sources.join(';') if TestNG.version < '6.0'
cmd_args << '-log' << '2'
cmd_args << '-d' << task.report_to.to_s
exclude_args = options[:excludegroups] || []
unless exclude_args.empty?
cmd_args << '-excludegroups' << exclude_args.join(',')
end
groups_args = options[:groups] || []
unless groups_args.empty?
cmd_args << '-groups' << groups_args.join(',')
end
# run all tests in the same suite
cmd_args << '-testclass' << (TestNG.version < '6.0' ? test : tests.join(','))
cmd_args += options[:args] if options[:args]
cmd_options = { :properties=>options[:properties], :java_args=>options[:java_args],
:classpath=>dependencies, :name => "TestNG in #{task.send(:project).name}" }
tmp = nil
begin
tmp = Tempfile.open('testNG')
tmp.write cmd_args.join("\n")
tmp.close
Java::Commands.java ['org.testng.TestNG', "@#{tmp.path}"], cmd_options
ensure
tmp.close unless tmp.nil?
end
# testng-failed.xml contains the list of failed tests *only*
failed_tests = File.join(task.report_to.to_s, 'testng-failed.xml')
if File.exist?(failed_tests)
report = File.read(failed_tests)
failed = report.scan(/<class name="(.*?)">/im).flatten
# return the list of passed tests
return tests - failed
else
return tests
end
end
end
# A composite test framework that runs multiple other test frameworks.
#
# e.g.,
# test.using :multitest, :frameworks => [ Buildr::JUnit, Buildr::TestNG ], :options = {
# :junit => { :fork => true },
# :testng => { ... }
# }
#
class MultiTest < Buildr::TestFramework::Java
# TODO: Support multiple test report locations, one per framework
class << self
def applies_to?(project) #:nodoc:
false # no auto-detection, should be set explicitly
end
end
attr_accessor :frameworks
def initialize(task, options) #:nodoc:
super
fail 'Missing :frameworks option' unless options[:frameworks]
@frameworks = options[:frameworks].map do |f|
framework_options = (options[:options] || {})[f.to_sym] || {}
f.new(task, framework_options)
end
end
def dependencies #:nodoc:
unless @dependencies
@dependencies = TestFramework::Java.dependencies
@dependencies += @frameworks.map(&:dependencies).flatten
end
@dependencies
end
def tests(dependencies)
@frameworks.map { |f| f.tests(dependencies) }.flatten
end
def run(tests, dependencies) #:nodoc:
framework_for_test = @frameworks.inject({}) do |hash, f|
f.tests(dependencies).each { |t| hash[t] = f }
hash
end
tests_by_framework = tests.group_by { |t| framework_for_test[t] }
passed = []
tests_by_framework.each do |f, tests|
passed += f.run(tests, dependencies)
end
passed
end
end # MultiTest
end # Buildr
Buildr::TestFramework << Buildr::JUnit
Buildr::TestFramework << Buildr::TestNG
Buildr::TestFramework << Buildr::MultiTest