# 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