2018-11-02 19:07:56 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require "forwardable"
|
|
|
|
require "support/the_bundle"
|
|
|
|
module Spec
|
|
|
|
module Matchers
|
|
|
|
extend RSpec::Matchers
|
|
|
|
|
|
|
|
class Precondition
|
|
|
|
include RSpec::Matchers::Composable
|
|
|
|
extend Forwardable
|
|
|
|
def_delegators :failing_matcher,
|
|
|
|
:failure_message,
|
|
|
|
:actual,
|
|
|
|
:description,
|
|
|
|
:diffable?,
|
|
|
|
:expected,
|
|
|
|
:failure_message_when_negated
|
|
|
|
|
|
|
|
def initialize(matcher, preconditions)
|
|
|
|
@matcher = with_matchers_cloned(matcher)
|
|
|
|
@preconditions = with_matchers_cloned(preconditions)
|
|
|
|
@failure_index = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
def matches?(target, &blk)
|
|
|
|
return false if @failure_index = @preconditions.index {|pc| !pc.matches?(target, &blk) }
|
|
|
|
@matcher.matches?(target, &blk)
|
|
|
|
end
|
|
|
|
|
|
|
|
def does_not_match?(target, &blk)
|
|
|
|
return false if @failure_index = @preconditions.index {|pc| !pc.matches?(target, &blk) }
|
|
|
|
if @matcher.respond_to?(:does_not_match?)
|
|
|
|
@matcher.does_not_match?(target, &blk)
|
|
|
|
else
|
|
|
|
!@matcher.matches?(target, &blk)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def expects_call_stack_jump?
|
|
|
|
@matcher.expects_call_stack_jump? || @preconditions.any?(&:expects_call_stack_jump)
|
|
|
|
end
|
|
|
|
|
|
|
|
def supports_block_expectations?
|
|
|
|
@matcher.supports_block_expectations? || @preconditions.any?(&:supports_block_expectations)
|
|
|
|
end
|
|
|
|
|
|
|
|
def failing_matcher
|
|
|
|
@failure_index ? @preconditions[@failure_index] : @matcher
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.define_compound_matcher(matcher, preconditions, &declarations)
|
|
|
|
raise "Must have preconditions to define a compound matcher" if preconditions.empty?
|
|
|
|
define_method(matcher) do |*expected, &block_arg|
|
|
|
|
Precondition.new(
|
|
|
|
RSpec::Matchers::DSL::Matcher.new(matcher, declarations, self, *expected, &block_arg),
|
|
|
|
preconditions
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
RSpec::Matchers.define :have_dep do |*args|
|
|
|
|
dep = Bundler::Dependency.new(*args)
|
|
|
|
|
|
|
|
match do |actual|
|
|
|
|
actual.length == 1 && actual.all? {|d| d == dep }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
RSpec::Matchers.define :have_gem do |*args|
|
|
|
|
match do |actual|
|
|
|
|
actual.length == args.length && actual.all? {|a| args.include?(a.full_name) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
RSpec::Matchers.define :have_rubyopts do |*args|
|
|
|
|
args = args.flatten
|
|
|
|
args = args.first.split(/\s+/) if args.size == 1
|
|
|
|
|
|
|
|
match do |actual|
|
|
|
|
actual = actual.split(/\s+/) if actual.is_a?(String)
|
|
|
|
args.all? {|arg| actual.include?(arg) } && actual.uniq.size == actual.size
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
RSpec::Matchers.define :be_sorted do
|
|
|
|
diffable
|
|
|
|
attr_reader :expected
|
|
|
|
match do |actual|
|
|
|
|
expected = block_arg ? actual.sort_by(&block_arg) : actual.sort
|
|
|
|
actual.==(expected).tap do
|
|
|
|
# HACK: since rspec won't show a diff when everything is a string
|
|
|
|
differ = RSpec::Support::Differ.new
|
|
|
|
@actual = differ.send(:object_to_string, actual)
|
|
|
|
@expected = differ.send(:object_to_string, expected)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-04-14 02:01:35 -04:00
|
|
|
RSpec::Matchers.define :be_well_formed do
|
|
|
|
match(&:empty?)
|
|
|
|
|
|
|
|
failure_message do |actual|
|
|
|
|
actual.join("\n")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-11-02 19:07:56 -04:00
|
|
|
define_compound_matcher :read_as, [exist] do |file_contents|
|
|
|
|
diffable
|
|
|
|
|
|
|
|
match do |actual|
|
|
|
|
@actual = Bundler.read_file(actual)
|
|
|
|
values_match?(file_contents, @actual)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def indent(string, padding = 4, indent_character = " ")
|
|
|
|
string.to_s.gsub(/^/, indent_character * padding).gsub("\t", " ")
|
|
|
|
end
|
|
|
|
|
|
|
|
define_compound_matcher :include_gems, [be_an_instance_of(Spec::TheBundle)] do |*names|
|
|
|
|
match do
|
|
|
|
opts = names.last.is_a?(Hash) ? names.pop : {}
|
|
|
|
source = opts.delete(:source)
|
|
|
|
groups = Array(opts[:groups])
|
|
|
|
groups << opts
|
|
|
|
@errors = names.map do |name|
|
|
|
|
name, version, platform = name.split(/\s+/)
|
|
|
|
version_const = name == "bundler" ? "Bundler::VERSION" : Spec::Builders.constantize(name)
|
|
|
|
begin
|
|
|
|
run! "require '#{name}.rb'; puts #{version_const}", *groups
|
2019-04-14 02:01:35 -04:00
|
|
|
rescue StandardError => e
|
2018-11-02 19:07:56 -04:00
|
|
|
next "#{name} is not installed:\n#{indent(e)}"
|
|
|
|
end
|
2019-06-01 05:49:40 -04:00
|
|
|
actual_version, actual_platform = out.strip.split(/\s+/, 2)
|
2018-11-02 19:07:56 -04:00
|
|
|
unless Gem::Version.new(actual_version) == Gem::Version.new(version)
|
|
|
|
next "#{name} was expected to be at version #{version} but was #{actual_version}"
|
|
|
|
end
|
|
|
|
unless actual_platform == platform
|
|
|
|
next "#{name} was expected to be of platform #{platform} but was #{actual_platform}"
|
|
|
|
end
|
|
|
|
next unless source
|
|
|
|
begin
|
|
|
|
source_const = "#{Spec::Builders.constantize(name)}_SOURCE"
|
|
|
|
run! "require '#{name}/source'; puts #{source_const}", *groups
|
2019-04-14 02:01:35 -04:00
|
|
|
rescue StandardError
|
2018-11-02 19:07:56 -04:00
|
|
|
next "#{name} does not have a source defined:\n#{indent(e)}"
|
|
|
|
end
|
2019-06-01 05:49:40 -04:00
|
|
|
unless out.strip == source
|
2018-11-02 19:07:56 -04:00
|
|
|
next "Expected #{name} (#{version}) to be installed from `#{source}`, was actually from `#{out}`"
|
|
|
|
end
|
|
|
|
end.compact
|
|
|
|
|
|
|
|
@errors.empty?
|
|
|
|
end
|
|
|
|
|
|
|
|
match_when_negated do
|
|
|
|
opts = names.last.is_a?(Hash) ? names.pop : {}
|
|
|
|
groups = Array(opts[:groups]) || []
|
|
|
|
@errors = names.map do |name|
|
|
|
|
name, version = name.split(/\s+/, 2)
|
|
|
|
begin
|
|
|
|
run <<-R, *(groups + [opts])
|
|
|
|
begin
|
|
|
|
require '#{name}'
|
|
|
|
puts #{Spec::Builders.constantize(name)}
|
|
|
|
rescue LoadError, NameError
|
|
|
|
puts "WIN"
|
|
|
|
end
|
|
|
|
R
|
2019-04-14 02:01:35 -04:00
|
|
|
rescue StandardError => e
|
2018-11-02 19:07:56 -04:00
|
|
|
next "checking for #{name} failed:\n#{e}"
|
|
|
|
end
|
2019-06-01 05:49:40 -04:00
|
|
|
next if out == "WIN"
|
2018-11-02 19:07:56 -04:00
|
|
|
next "expected #{name} to not be installed, but it was" if version.nil?
|
2019-06-01 05:49:40 -04:00
|
|
|
if Gem::Version.new(out) == Gem::Version.new(version)
|
2018-11-02 19:07:56 -04:00
|
|
|
next "expected #{name} (#{version}) not to be installed, but it was"
|
|
|
|
end
|
|
|
|
end.compact
|
|
|
|
|
|
|
|
@errors.empty?
|
|
|
|
end
|
|
|
|
|
|
|
|
failure_message do
|
|
|
|
super() + " but:\n" + @errors.map {|e| indent(e) }.join("\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
failure_message_when_negated do
|
|
|
|
super() + " but:\n" + @errors.map {|e| indent(e) }.join("\n")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
RSpec::Matchers.define_negated_matcher :not_include_gems, :include_gems
|
|
|
|
RSpec::Matchers.alias_matcher :include_gem, :include_gems
|
|
|
|
|
|
|
|
def have_lockfile(expected)
|
2019-04-14 02:01:35 -04:00
|
|
|
read_as(normalize_uri_file(strip_whitespace(expected)))
|
2018-11-02 19:07:56 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def plugin_should_be_installed(*names)
|
|
|
|
names.each do |name|
|
|
|
|
expect(Bundler::Plugin).to be_installed(name)
|
|
|
|
path = Pathname.new(Bundler::Plugin.installed?(name))
|
|
|
|
expect(path + "plugins.rb").to exist
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def plugin_should_not_be_installed(*names)
|
|
|
|
names.each do |name|
|
|
|
|
expect(Bundler::Plugin).not_to be_installed(name)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def lockfile_should_be(expected)
|
2019-04-14 02:01:35 -04:00
|
|
|
expect(bundled_app("Gemfile.lock")).to have_lockfile(expected)
|
2018-11-02 19:07:56 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def gemfile_should_be(expected)
|
|
|
|
expect(bundled_app("Gemfile")).to read_as(strip_whitespace(expected))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|