# 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. # This file gets loaded twice when running 'spec spec/*' and not with pleasent results, # so ignore the second attempt to load it. unless defined?(SpecHelpers) require 'rubygems' $LOAD_PATH.unshift File.expand_path('../lib', File.dirname(__FILE__)), File.expand_path('../addon', File.dirname(__FILE__)) require 'buildr' require File.expand_path('sandbox', File.dirname(__FILE__)) module SpecHelpers include Checks::Matchers [: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) verbose(true) { deprecated_without_capture message } end end class MessageWithSeverityMatcher def initialize(severity, message) @severity = severity @expect = message end def matches?(target) $messages = {@severity => []} target.call return Regexp === @expect ? $messages[@severity].join('\n') =~ @expect : $messages[@severity].include?(@expect.to_s) end def failure_message "Expected #{@severity} #{@expect.inspect}, " + ($messages[@severity].empty? ? "no #{@severity} issued" : "found #{$messages[@severity].inspect}") end def negative_failure_message "Found unexpected #{$messages[@severity].inspect}" 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) MessageWithSeverityMatcher.new :warn, message end # 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) MessageWithSeverityMatcher.new :error, message end class ::Rake::Task alias :execute_without_a_record :execute def execute(args) $executed ||= [] $executed << name execute_without_a_record args end end class InvokeMatcher def initialize(*tasks) @expecting = tasks.map { |task| [task].flatten.map(&:to_s) } end def matches?(target) $executed = [] target.call return false unless all_ran? return !@but_not.any_ran? if @but_not return true end def failure_message return @but_not.negative_failure_message if all_ran? && @but_not "Expected the tasks #{expected} to run, but #{remaining} did not run, or not in the order we expected them to." + " Tasks that ran: #{$executed.inspect}" end def negative_failure_message if all_ran? "Expected the tasks #{expected} to not run, but they all ran." else "Expected the tasks #{expected} to not run, and all but #{remaining} ran." end end def but_not(*tasks) @but_not = InvokeMatcher.new(*tasks) self end protected def expected @expecting.map { |tests| tests.join('=>') }.join(', ') end def remaining @remaining.map { |tests| tests.join('=>') }.join(', ') end def all_ran? @remaining ||= $executed.inject(@expecting) do |expecting, executed| expecting.map { |tasks| tasks.first == executed ? tasks[1..-1] : tasks }.reject(&:empty?) end @remaining.empty? end def any_ran? all_ran? @remaining.size < @expecting.size end end # Tests that all the tasks ran, in the order specified. Can also be used to test that some # tasks and not others ran. # # Takes a list of arguments. Each argument can be a task name, matching only if that task ran. # Each argument can be an array of task names, matching only if all these tasks ran in that order. # So run_tasks('foo', 'bar') expects foo and bar to run in any order, but run_task(['foo', 'bar']) # expects foo to run before bar. # # You can call but_not on the matchers to specify that certain tasks must not execute. # # For example: # # Either task # lambda { task('compile').invoke }.should run_tasks('compile', 'resources') # # In that order # lambda { task('build').invoke }.should run_tasks(['compile', 'test']) # # With exclusion # lambda { task('build').invoke }.should run_tasks('compile').but_not('install') def run_tasks(*tasks) InvokeMatcher.new *tasks end # Tests that a task ran. Similar to run_tasks, but accepts a single task name. # # For example: # lambda { task('build').invoke }.should run_task('test') def run_task(task) InvokeMatcher.new [task] end class UriPathMatcher def initialize(re) @expression = re end def matches?(uri) @uri = uri uri.path =~ @expression end def description "URI with path matching #{@expression}" end end # Matches a parsed URI's path against the given regular expression def uri(re) UriPathMatcher.new(re) end class AbsolutePathMatcher def initialize(path) @expected = File.expand_path(path.to_s) end def matches?(path) @provided = File.expand_path(path.to_s) @provided == @expected end def failure_message "Expected path #{@expected}, but found path #{@provided}" end def negative_failure_message "Expected a path other than #{@expected}" end end def point_to_path(path) AbsolutePathMatcher.new(path) end def suppress_stdout stdout = $stdout $stdout = StringIO.new begin yield ensure $stdout = stdout end end def dryrun Buildr.application.options.dryrun = true begin suppress_stdout { yield } ensure Buildr.application.options.dryrun = false end end # We run tests with tracing off. Then things break. And we need to figure out what went wrong. # So just use trace() as you would use verbose() to find and squash the bug. def trace(value = nil) old_value = Buildr.application.options.trace Buildr.application.options.trace = value unless value.nil? if block_given? begin yield ensure Buildr.application.options.trace = old_value end end Buildr.application.options.trace end # Change the Buildr original directory, faking invocation from a different directory. def in_original_dir(dir) begin original_dir = Buildr.application.original_dir Buildr.application.instance_eval { @original_dir = File.expand_path(dir) } yield ensure Buildr.application.instance_eval { @original_dir = original_dir } end end # Buildr's define method creates a project definition but does not evaluate it # (that happens once the buildfile is loaded), and we include Buildr's define in # the test context so we can use it without prefixing with Buildr. This just patches # define to evaluate the project definition before returning it. def define(name, properties = nil, &block) #:yields:project Project.define(name, properties, &block).tap { |project| project.invoke } end end # Allow using matchers within the project definition. class Buildr::Project include ::Spec::Matchers, SpecHelpers end Spec::Runner.configure do |config| # Make all Buildr methods accessible from test cases, and add various helper methods. config.include Buildr, SpecHelpers # Sanbdox Buildr for each test. config.include Sandbox end end