require 'minitest/test'

module Beaker
  module DSL
    # Any custom assertions for Test::Unit or minitest live here. You may
    # include them in your own testing if you wish, override them, or re-open
    # the class to register new ones for use within
    # {Beaker::TestCase}.
    #
    # You may use any test/unit assertion within your assertion. The
    # assertion below assumes access to the method #result which will
    # contain the output (according to the interface defined in
    # {Beaker::Result}). When writing your own, to make them more
    # portable and less brittle it is recommended that you pass the result
    # or direct object for asserting against into your assertion.
    #
    module Assertions
      include Minitest::Assertions

      #Why do we need this accessor?
      # https://github.com/seattlerb/minitest/blob/master/lib/minitest/assertions.rb#L8-L12
      # Protocol: Nearly everything here boils up to +assert+, which
      # expects to be able to increment an instance accessor named
      # +assertions+. This is not provided by Assertions and must be
      # provided by the thing including Assertions. See Minitest::Runnable
      # for an example.
      attr_accessor :assertions
      def assertions
        @assertions || 0
      end

      # Make assertions about the content of console output.
      #
      # By default, each line of +output+ is assumed to come from STDOUT.
      # You may specify the stream explicitly by annotating the line with a
      # stream marker. (If your line literally requires any stream marker at
      # the beginning of a line, you must prefix the line with an explicit
      # stream marker.)  The currently recognized markers are:
      #
      # * "STDOUT> "
      # * "STDERR> "
      # * "OUT> "
      # * "ERR> "
      # * "1> "
      # * "2> "
      #
      # Any leading common indentation is automatically removed from the
      # +output+ parameter.  For cases where this matters (e.g. every line
      # should be indented), you should prefix the line with an explicit
      # stream marker.
      #
      # @example Assert order of interleaved output streams
      #   !!!plain
      #   assert_output <<-CONSOLE
      #     STDOUT> 0123456789
      #     STDERR> ^- This is left aligned
      #     STDOUT>   01234567890
      #     STDERR>   ^- This is indented 2 characters.
      #   CONSOLE
      #
      # @example Assert all content went to STDOUT
      #   !!!plain
      #   assert_output <<-CONSOLE
      #     0123456789
      #     ^- This is left aligned
      #       01234567890
      #       ^- This is indented 2 characters.
      #   CONSOLE
      #
      # @param [String] exp_out The expected console output, optionally
      #                         annotated with stream markers.
      # @param [String] msg     An explanatory message about why the test
      #                         failure is relevant.
      def assert_output(exp_out, msg='Output lines did not match')
        # Remove the minimal consistent indentation from the input;
        # useful for clean HEREDOCs.
        indentation = exp_out.lines.map { |line| line[/^ */].length }.min
        cleaned_exp = exp_out.gsub(/^ {#{indentation}}/, '')

        # Divide output based on expected destination
        out, err = cleaned_exp.lines.partition do |line|
          line !~ /^((STD)?ERR|2)> /
        end
        our_out, our_err, our_output = [
          out.join, err.join, cleaned_exp
        ].map do |str|
          str.gsub(/^((STD)?(ERR|OUT)|[12])> /, '')
        end

        # Exercise assertions about output
        assert_equal our_output, (result.nil? ? '' : result.output), msg
        assert_equal our_out,    (result.nil? ? '' : result.stdout),
          'The contents of STDOUT did not match expectations'
        assert_equal our_err,    (result.nil? ? '' : result.stderr),
          'The contents of STDERR did not match expectations'
      end

      # Assert that the provided string does not match the provided regular expression, can pass optional message
      # @deprecated This is placed her for backwards compatability for tests that used Test::Unit::Assertions,
      #             http://apidock.com/ruby/Test/Unit/Assertions/assert_no_match
      #
      def assert_no_match(regexp, string, msg=nil)
        assert_instance_of(Regexp, regexp, "The first argument to assert_no_match should be a Regexp.")
        msg = message(msg) { "<#{mu_pp(regexp)}> expected to not match\n<#{mu_pp(string)}>" }
        assert(regexp !~ string, msg)
      end

      alias_method :assert_not_match, :assert_no_match

    end
  end
end
