| |
| # 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 |