require 'colored' # Layout structure module CLIntegracon module Adapter end end # Define concrete adapter module CLIntegracon::Adapter::Bacon module Context # Get or configure the current subject # # @note On first call this will create a new subject on base of the # shared configuration and store it in the ivar `@subject`. # # @param [Block<(Subject) -> ()>] # This block, if given, will be evaluated on the caller. # It receives as first argument the subject itself. # # @return [Subject] # the subject # def subject &block @subject ||= CLIntegracon::shared_config.subject.dup return @subject if block.nil? instance_exec(@subject, &block) end # Get or configure the current context for FileTreeSpecs # # @note On first call this will create a new context on base of the # shared configuration and store it in the ivar `@file_tree_spec_context`. # # @param [Block<(FileTreeSpecContext) -> ()>] # This block, if given, will be evaluated on the caller. # It receives as first argument the context itself. # # @return [FileTreeSpecContext] # the spec context, will be lazily created if not already present. # def file_tree_spec_context &block @file_tree_spec_context ||= CLIntegracon.shared_config.file_tree_spec_context.dup return @file_tree_spec_context if block.nil? instance_exec(@file_tree_spec_context, &block) end # Works like `behaves_like`, but takes arguments for the shared example # # @param [String] name # name of the shared context. # # @param [...] args # params to pass to the shared context # def behaves_like_a(name, *args) instance_exec(*args, &Bacon::Shared[name]) end # Ad-hoc defines a set of shared expectations to be consumed directly by `behaves_like`. # See the following example for usage: # # behaves_like cli_spec('my_spec_dir', 'install --verbose') # # @note This expects that a method `file_tree_spec_context` is defined, which is # returning an instance of {FileTreeSpecContext}. # # @param [String] spec_dir # the concrete directory of the spec, see {file_spec}. # # @param [String] args # the additional arguments to pass on launch to {CLIntegracon::Subject}. # # @return [String] # name of the set of shared expectations # def cli_spec(spec_dir, *args) file_spec spec_dir do output = subject.launch(*args) status = $? it "$ #{subject.name} #{args.join(' ')}" do status.should.satisfy("Binary failed\n\n#{output}") do status.success? end end end end # Ad-hoc defines a set of shared expectations to be consumed directly by `behaves_like`. # See the following example for usage: # # behaves_like file_spec('my_spec_dir') do # # do some changes to the current dir # end # # @note This expects that a method `file_tree_spec_context` is defined, which is # returning an instance of {FileTreeSpecContext}. # # @param [String] spec_dir # the concrete directory of the spec to be passed to # {FileTreeSpecContext.spec} # # @param [Block<() -> ()>] block # the block which will be executed after the before state is laid out in the # temporary directory, which normally will make modifications to file system, # which will be compare to the state given in the after directory. # # @return [String] # name of the set of shared expectations # def file_spec(spec_dir, &block) raise ArgumentError.new("Spec directory is missing!") if spec_dir.nil? shared_name = spec_dir shared shared_name do file_tree_spec_context.spec(spec_dir).run do |spec| instance_eval &block formatter = spec.formatter.lazy spec.compare do |diff| it diff.relative_path.to_s do diff.produced.should.satisfy(formatter.describe_missing_file(diff.relative_path)) do diff.produced.exist? end diff.produced.should.satisfy(formatter.describe_file_diff(diff)) do diff.is_equal? end end end spec.check_unexpected_files do |files| it "should not produce unexpected files" do files.should.satisfy(formatter.describe_unexpected_files(files)) do files.size == 0 end end end end end shared_name end end # Describe a command line interface # This method basically behaves like {Bacon::Context.describe}, but it provides # automatically the methods #subject, #file_tree_spec_context, #cli_spec and #file_spec. # # @param [String] subject_name # the subject name will be used as first argument to initialize # a new {CLIntegracon::Subject}, which will be accessible in the # spec by #subject. # # @param [Hash] context_options # the options to configure this spec context, could be one or more of: # * :executable: the executable used to initialize {CLIntegracon::Subject} # if not given, will fallback to param {subject_name}. # # @param [Block<() -> ()>] block # the block to provide further sub-specs or requirements, as # known from {Bacon::Context.describe} # def describe_cli(subject_name, context_options = {}, &block) context = describe subject_name do # Make Context methods available # WORKAROUND: Bacon auto-inherits singleton methods to child contexts # by using the runtime and won't include methods in modules included # by the parent context. We have to ensure that the methods will be # accessible by the child contexts by defining them as singleton methods. extended = self.extend Context Context.instance_methods.each do |method| class << self; self end.instance_eval do unbound_method = extended.method(method).unbind send :define_method, method do |*args, &b| unbound_method.bind(self).call(*args, &b) end end end subject do |s| s.name = subject_name s.executable = context_options[:executable] || subject_name end instance_eval &block end Bacon::ErrorLog.gsub! %r{^.*lib/CLIntegracon/.*\n}, '' context end end # Make #describe_cli global available extend CLIntegracon::Adapter::Bacon # Patch Bacon::Context to support #describe_cli module Bacon class Context include CLIntegracon::Adapter::Bacon end end