module Test::Unit::CoreAssertions

Constants

ABORT_SIGNALS
FailDesc
PERFORMANCE_CLOCK

Public Instance Methods

all_assertions(msg = nil)
all_assertions_foreach(msg = nil, *keys, &block)
assert(test, [failure_message]) click to toggle source

Tests if test is true.

msg may be a String or a Proc. If msg is a String, it will be used as the failure message. Otherwise, the result of calling msg will be used as the message if the assertion fails.

If no msg is given, a default message will be used.

assert(false, "This was expected to be true")
Calls superclass method
# File lib/core_assertions.rb, line 531
def assert(test, *msgs)
  case msg = msgs.first
  when String, Proc
  when nil
    msgs.shift
  else
    bt = caller.reject { |s| s.start_with?(TEST_DIR) }
    raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt
  end unless msgs.empty?
  super
end
assert_all?(obj, m = nil, &blk) click to toggle source
# File lib/core_assertions.rb, line 760
def assert_all?(obj, m = nil, &blk)
  failed = []
  obj.each do |*a, &b|
    unless blk.call(*a, &b)
      failed << (a.size > 1 ? a : a[0])
    end
  end
  assert(failed.empty?, message(m) {failed.pretty_inspect})
end
assert_all_assertions(msg = nil) { |all| ... } click to toggle source
# File lib/core_assertions.rb, line 770
def assert_all_assertions(msg = nil)
  all = AllFailures.new
  yield all
ensure
  assert(all.pass?, message(msg) {all.message.chomp(".")})
end
Also aliased as: all_assertions
assert_all_assertions_foreach(msg = nil, *keys, &block) click to toggle source
# File lib/core_assertions.rb, line 778
def assert_all_assertions_foreach(msg = nil, *keys, &block)
  all = AllFailures.new
  all.foreach(*keys, &block)
ensure
  assert(all.pass?, message(msg) {all.message.chomp(".")})
end
Also aliased as: all_assertions_foreach
assert_deprecated_warn(mesg = /deprecated/) { || ... } click to toggle source
# File lib/core_assertions.rb, line 657
def assert_deprecated_warn(mesg = /deprecated/)
  assert_warn(mesg) do
    Warning[:deprecated] = true if Warning.respond_to?(:[]=)
    yield
  end
end
assert_deprecated_warning(mesg = /deprecated/) { || ... } click to toggle source
# File lib/core_assertions.rb, line 650
def assert_deprecated_warning(mesg = /deprecated/)
  assert_warning(mesg) do
    Warning[:deprecated] = true if Warning.respond_to?(:[]=)
    yield
  end
end
assert_file() click to toggle source
# File lib/core_assertions.rb, line 88
def assert_file
  AssertFile
end
assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, success: nil, **opt) { |lines.map {|l| chomp }, lines.map {|l| chomp }, status| ... } click to toggle source
# File lib/core_assertions.rb, line 99
def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil,
                      success: nil, **opt)
  args = Array(args).dup
  args.insert((Hash === args[0] ? 1 : 0), '--disable=gems')
  stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt)
  desc = FailDesc[status, message, stderr]
  if block_given?
    raise "test_stdout ignored, use block only or without block" if test_stdout != []
    raise "test_stderr ignored, use block only or without block" if test_stderr != []
    yield(stdout.lines.map {|l| l.chomp }, stderr.lines.map {|l| l.chomp }, status)
  else
    all_assertions(desc) do |a|
      [["stdout", test_stdout, stdout], ["stderr", test_stderr, stderr]].each do |key, exp, act|
        a.for(key) do
          if exp.is_a?(Regexp)
            assert_match(exp, act)
          elsif exp.all? {|e| String === e}
            assert_equal(exp, act.lines.map {|l| l.chomp })
          else
            assert_pattern_list(exp, act)
          end
        end
      end
      unless success.nil?
        a.for("success?") do
          if success
            assert_predicate(status, :success?)
          else
            assert_not_predicate(status, :success?)
          end
        end
      end
    end
    status
  end
end
assert_join_threads(threads, message = nil) click to toggle source

threads should respond to shift method. Array can be used.

# File lib/core_assertions.rb, line 730
def assert_join_threads(threads, message = nil)
  errs = []
  values = []
  while th = threads.shift
    begin
      values << th.value
    rescue Exception
      errs << [th, $!]
      th = nil
    end
  end
  values
ensure
  if th&.alive?
    th.raise(Timeout::Error.new)
    th.join rescue errs << [th, $!]
  end
  if !errs.empty?
    msg = "exceptions on #{errs.length} threads:\n" +
      errs.map {|t, err|
      "#{t.inspect}:\n" +
        (err.respond_to?(:full_message) ? err.full_message(highlight: false, order: :top) : err.message)
    }.join("\n---\n")
    if message
      msg = "#{message}\n#{msg}"
    end
    raise Test::Unit::AssertionFailedError, msg
  end
end
assert_linear_performance(seq, rehearsal: nil, pre: ->(n) {n} { |*arg| ... } click to toggle source

Expect seq to respond to first and each methods, e.g., Array, Range, Enumerator::ArithmeticSequence and other Enumerable-s, and each elements should be size factors.

# File lib/core_assertions.rb, line 808
def assert_linear_performance(seq, rehearsal: nil, pre: ->(n) {n})
  pend "No PERFORMANCE_CLOCK found" unless defined?(PERFORMANCE_CLOCK)

  # Timeout testing generally doesn't work when RJIT compilation happens.
  rjit_enabled = defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
  measure = proc do |arg, message|
    st = Process.clock_gettime(PERFORMANCE_CLOCK)
    yield(*arg)
    t = (Process.clock_gettime(PERFORMANCE_CLOCK) - st)
    assert_operator 0, :<=, t, message unless rjit_enabled
    t
  end

  first = seq.first
  *arg = pre.call(first)
  times = (0..(rehearsal || (2 * first))).map do
    measure[arg, "rehearsal"].nonzero?
  end
  times.compact!
  tmin, tmax = times.minmax
  tbase = 10 ** Math.log10(tmax * ([(tmax / tmin), 2].max ** 2)).ceil
  info = "(tmin: #{tmin}, tmax: #{tmax}, tbase: #{tbase})"

  seq.each do |i|
    next if i == first
    t = tbase * i.fdiv(first)
    *arg = pre.call(i)
    message = "[#{i}]: in #{t}s #{info}"
    Timeout.timeout(t, Timeout::Error, message) do
      measure[arg, message]
    end
  end
end
assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) click to toggle source
# File lib/core_assertions.rb, line 155
def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt)
  # TODO: consider choosing some appropriate limit for RJIT and stop skipping this once it does not randomly fail
  pend 'assert_no_memory_leak may consider RJIT memory usage as leak' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
  # For previous versions which implemented MJIT
  pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
  # ASAN has the same problem - its shadow memory greatly increases memory usage
  # (plus asan has better ways to detect memory leaks than this assertion)
  pend 'assert_no_memory_leak may consider ASAN memory usage as leak' if defined?(Test::ASAN) && Test::ASAN.enabled?

  require_relative 'memory_status'
  raise Test::Unit::PendedError, "unsupported platform" unless defined?(Memory::Status)

  token_dump, token_re = new_test_token
  envs = args.shift if Array === args and Hash === args.first
  args = [
    "--disable=gems",
    "-r", File.expand_path("../memory_status", __FILE__),
    *args,
    "-v", "-",
  ]
  if defined? Memory::NO_MEMORY_LEAK_ENVS then
    envs ||= {}
    newenvs = envs.merge(Memory::NO_MEMORY_LEAK_ENVS) { |_, _, _| break }
    envs = newenvs if newenvs
  end
  args.unshift(envs) if envs
  cmd = [
    'END {STDERR.puts '"#{token_dump}"'"FINAL=#{Memory::Status.new}"}',
    prepare,
    'STDERR.puts('"#{token_dump}"'"START=#{$initial_status = Memory::Status.new}")',
    '$initial_size = $initial_status.size',
    code,
    'GC.start',
  ].join("\n")
  _, err, status = EnvUtil.invoke_ruby(args, cmd, true, true, **opt)
  before = err.sub!(/^#{token_re}START=(\{.*\})\n/, '') && Memory::Status.parse($1)
  after = err.sub!(/^#{token_re}FINAL=(\{.*\})\n/, '') && Memory::Status.parse($1)
  assert(status.success?, FailDesc[status, message, err])
  ([:size, (rss && :rss)] & after.members).each do |n|
    b = before[n]
    a = after[n]
    next unless a > 0 and b > 0
    assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"})
  end
rescue LoadError
  pend
end
assert_normal_exit(testsrc, message = '', child_env: nil, **opt) click to toggle source
# File lib/core_assertions.rb, line 271
def assert_normal_exit(testsrc, message = '', child_env: nil, **opt)
  assert_valid_syntax(testsrc, caller_locations(1, 1)[0])
  if child_env
    child_env = [child_env]
  else
    child_env = []
  end
  out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt)
  assert !status.signaled?, FailDesc[status, message, out]
end
assert_not_respond_to( object, method, failure_message = nil ) click to toggle source

Tests if the given Object does not respond to method.

An optional failure message may be provided as the final argument.

assert_not_respond_to("hello", :reverse)  #Fails
assert_not_respond_to("hello", :does_not_exist)  #Succeeds
# File lib/core_assertions.rb, line 575
def assert_not_respond_to(obj, (meth, *priv), msg = nil)
  unless priv.empty?
    msg = message(msg) {
      "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}"
    }
    return assert !obj.respond_to?(meth, *priv), msg
  end
  #get rid of overcounting
  if caller_locations(1, 1)[0].path.start_with?(TEST_DIR)
    return unless obj.respond_to?(meth)
  end
  refute_respond_to(obj, meth, msg)
end
assert_nothing_raised( *args, &block ) click to toggle source

If any exceptions are given as arguments, the assertion will fail if one of those exceptions are raised. Otherwise, the test fails if any exceptions are raised.

The final argument may be a failure message.

assert_nothing_raised RuntimeError do
  raise Exception #Assertion passes, Exception is not a RuntimeError
end

assert_nothing_raised do
  raise Exception #Assertion fails
end
# File lib/core_assertions.rb, line 219
def assert_nothing_raised(*args)
  self._assertions += 1
  if Module === args.last
    msg = nil
  else
    msg = args.pop
  end
  begin
    yield
  rescue Test::Unit::PendedError, *(Test::Unit::AssertionFailedError if args.empty?)
    raise
  rescue *(args.empty? ? Exception : args) => e
    msg = message(msg) {
      "Exception raised:\n<#{mu_pp(e)}>\n""Backtrace:\n" <<
      Test.filter_backtrace(e.backtrace).map{|frame| "  #{frame}"}.join("\n")
    }
    raise Test::Unit::AssertionFailedError, msg.call, e.backtrace
  end
end
assert_pattern_list(pattern_list, actual, message=nil) click to toggle source

pattern_list is an array which contains regexp, string and :*. :* means any sequence.

pattern_list is anchored. Use [:*, regexp/string, :*] for non-anchored match.

# File lib/core_assertions.rb, line 594
def assert_pattern_list(pattern_list, actual, message=nil)
  rest = actual
  anchored = true
  pattern_list.each_with_index {|pattern, i|
    if pattern == :*
      anchored = false
    else
      if anchored
        match = rest.rindex(pattern, 0)
      else
        match = rest.index(pattern)
      end
      if match
        post_match = $~ ? $~.post_match : rest[match+pattern.size..-1]
      else
        msg = message(msg) {
          expect_msg = "Expected #{mu_pp pattern}\n"
          if /\n[^\n]/ =~ rest
            actual_mesg = +"to match\n"
            rest.scan(/.*\n+/) {
              actual_mesg << '  ' << $&.inspect << "+\n"
            }
            actual_mesg.sub!(/\+\n\z/, '')
          else
            actual_mesg = "to match " + mu_pp(rest)
          end
          actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters"
          expect_msg + actual_mesg
        }
        assert false, msg
      end
      rest = post_match
      anchored = true
    end
  }
  if anchored
    assert_equal("", rest)
  end
end
assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt) click to toggle source

Run Ractor-related test without influencing the main test suite

# File lib/core_assertions.rb, line 371
      def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt)
        return unless defined?(Ractor)

        require = "require #{require.inspect}" if require
        if require_relative
          dir = File.dirname(caller_locations[0,1][0].absolute_path)
          full_path = File.expand_path(require_relative, dir)
          require = "#{require}; require #{full_path.inspect}"
        end

        assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt)
          #{require}
          previous_verbose = $VERBOSE
          $VERBOSE = nil
          Ractor.new {} # trigger initial warning
          $VERBOSE = previous_verbose
          #{src}
        RUBY
      end
assert_raise( *args, &block ) click to toggle source

Tests if the given block raises an exception. Acceptable exception types may be given as optional arguments. If the last argument is a String, it will be used as the error message.

assert_raise do #Fails, no Exceptions are raised
end

assert_raise NameError do
  puts x  #Raises NameError, so assertion succeeds
end
# File lib/core_assertions.rb, line 433
def assert_raise(*exp, &b)
  case exp.last
  when String, Proc
    msg = exp.pop
  end

  begin
    yield
  rescue Test::Unit::PendedError => e
    return e if exp.include? Test::Unit::PendedError
    raise e
  rescue Exception => e
    expected = exp.any? { |ex|
      if ex.instance_of? Module then
        e.kind_of? ex
      else
        e.instance_of? ex
      end
    }

    assert expected, proc {
      flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"})
    }

    return e
  ensure
    unless e
      exp = exp.first if exp.size == 1

      flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"})
    end
  end
end
assert_raise_with_message(exception, expected, msg = nil, &block) click to toggle source

Tests if the given block raises an exception with the expected message.

assert_raise_with_message(RuntimeError, "foo") do
  nil #Fails, no Exceptions are raised
end

assert_raise_with_message(RuntimeError, "foo") do
  raise ArgumentError, "foo" #Fails, different Exception is raised
end

assert_raise_with_message(RuntimeError, "foo") do
  raise "bar" #Fails, RuntimeError is raised but the message differs
end

assert_raise_with_message(RuntimeError, "foo") do
  raise "foo" #Raises RuntimeError with the message, so assertion succeeds
end
# File lib/core_assertions.rb, line 488
def assert_raise_with_message(exception, expected, msg = nil, &block)
  case expected
  when String
    assert = :assert_equal
  when Regexp
    assert = :assert_match
  else
    raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}"
  end

  ex = m = nil
  EnvUtil.with_default_internal(expected.encoding) do
    ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do
      yield
    end
    m = ex.message
  end
  msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"}

  if assert == :assert_equal
    assert_equal(expected, m, msg)
  else
    msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" }
    assert expected =~ m, msg
    block.binding.eval("proc{|_|$~=_}").call($~)
  end
  ex
end
assert_respond_to( object, method, failure_message = nil ) click to toggle source

Tests if the given Object responds to method.

An optional failure message may be provided as the final argument.

assert_respond_to("hello", :reverse)  #Succeeds
assert_respond_to("hello", :does_not_exist)  #Fails
Calls superclass method
# File lib/core_assertions.rb, line 552
def assert_respond_to(obj, (meth, *priv), msg = nil)
  unless priv.empty?
    msg = message(msg) {
      "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}"
    }
    return assert obj.respond_to?(meth, *priv), msg
  end
  #get rid of overcounting
  if caller_locations(1, 1)[0].path.start_with?(TEST_DIR)
    return if obj.respond_to?(meth)
  end
  super(obj, meth, msg)
end
assert_ruby_status(args, test_stdin="", message=nil, **opt) click to toggle source
# File lib/core_assertions.rb, line 282
def assert_ruby_status(args, test_stdin="", message=nil, **opt)
  out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt)
  desc = FailDesc[status, message, out]
  assert(!status.signaled?, desc)
  message ||= "ruby exit status is not success:"
  assert(status.success?, desc)
end
assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) click to toggle source
# File lib/core_assertions.rb, line 305
      def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt)
        unless file and line
          loc, = caller_locations(1,1)
          file ||= loc.path
          line ||= loc.lineno
        end
        capture_stdout = true
        unless /mswin|mingw/ =~ RbConfig::CONFIG['host_os']
          capture_stdout = false
          opt[:out] = Test::Unit::Runner.output if defined?(Test::Unit::Runner)
          res_p, res_c = IO.pipe
          opt[:ios] = [res_c]
        end
        token_dump, token_re = new_test_token
        src = <<eom
# -*- coding: #{line += __LINE__; src.encoding}; -*-
BEGIN {
  require "test/unit";include Test::Unit::Assertions;require #{__FILE__.dump};include Test::Unit::CoreAssertions
  separated_runner #{token_dump}, #{res_c&.fileno || 'nil'}
}
#{line -= __LINE__; src}
eom
        args = args.dup
        args.insert((Hash === args.first ? 1 : 0), "-w", "--disable=gems", *$:.map {|l| "-I#{l}"})
        args << "--debug" if RUBY_ENGINE == 'jruby' # warning: tracing (e.g. set_trace_func) will not capture all events without --debug flag
        stdout, stderr, status = EnvUtil.invoke_ruby(args, src, capture_stdout, true, **opt)
      ensure
        if res_c
          res_c.close
          res = res_p.read
          res_p.close
        else
          res = stdout
        end
        raise if $!
        abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig))
        assert(!abort, FailDesc[status, nil, stderr])
        self._assertions += res[/^#{token_re}assertions=(\d+)/, 1].to_i
        begin
          res = Marshal.load(res[/^#{token_re}<error>\n\K.*\n(?=#{token_re}<\/error>$)/m].unpack1("m"))
        rescue => marshal_error
          ignore_stderr = nil
          res = nil
        end
        if res and !(SystemExit === res)
          if bt = res.backtrace
            bt.each do |l|
              l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"}
            end
            bt.concat(caller)
          else
            res.set_backtrace(caller)
          end
          raise res
        end

        # really is it succeed?
        unless ignore_stderr
          # the body of assert_separately must not output anything to detect error
          assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr])
        end
        assert(status.success?, FailDesc[status, "assert_separately failed", stderr])
        raise marshal_error if marshal_error
      end
assert_throw( tag, failure_message = nil, &block ) click to toggle source

Fails unless the given block throws tag, returns the caught value otherwise.

An optional failure message may be provided as the final argument.

tag = Object.new
assert_throw(tag, "#{tag} was not thrown!") do
  throw tag
end
# File lib/core_assertions.rb, line 403
def assert_throw(tag, msg = nil)
  ret = catch(tag) do
    begin
      yield(tag)
    rescue UncaughtThrowError => e
      thrown = e.tag
    end
    msg = message(msg) {
      "Expected #{mu_pp(tag)} to have been thrown"\
      "#{%Q[, not #{thrown}] if thrown}"
    }
    assert(false, msg)
  end
  assert(true)
  ret
end
assert_valid_syntax(code, *args, **opt) { || ... } click to toggle source
# File lib/core_assertions.rb, line 262
def assert_valid_syntax(code, *args, **opt)
  prepare_syntax_check(code, *args, **opt) do |src, fname, line, mesg|
    yield if defined?(yield)
    assert_nothing_raised(SyntaxError, mesg) do
      assert_equal(:ok, syntax_check(src, fname, line), mesg)
    end
  end
end
assert_warn(*args) { || ... } click to toggle source
# File lib/core_assertions.rb, line 646
def assert_warn(*args)
  assert_warning(*args) {$VERBOSE = false; yield}
end
assert_warning(pat, msg = nil) { || ... } click to toggle source
# File lib/core_assertions.rb, line 634
def assert_warning(pat, msg = nil)
  result = nil
  stderr = EnvUtil.with_default_internal(pat.encoding) {
    EnvUtil.verbose_warning {
      result = yield
    }
  }
  msg = message(msg) {diff pat, stderr}
  assert(pat === stderr, msg)
  result
end
diff(exp, act) click to toggle source
# File lib/core_assertions.rb, line 842
def diff(exp, act)
  require 'pp'
  q = PP.new(+"")
  q.guard_inspect_key do
    q.group(2, "expected: ") do
      q.pp exp
    end
    q.text q.newline
    q.group(2, "actual: ") do
      q.pp act
    end
    q.flush
  end
  q.output
end
new_test_token() click to toggle source
# File lib/core_assertions.rb, line 858
def new_test_token
  token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m"
  return token.dump, Regexp.quote(token)
end
prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil) { |code, fname, line, message(mesg {| ... } click to toggle source
# File lib/core_assertions.rb, line 239
def prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil)
  fname ||= caller_locations(2, 1)[0]
  mesg ||= fname.to_s
  verbose, $VERBOSE = $VERBOSE, verbose
  case
  when Array === fname
    fname, line = *fname
  when defined?(fname.path) && defined?(fname.lineno)
    fname, line = fname.path, fname.lineno
  else
    line = 1
  end
  yield(code, fname, line, message(mesg) {
          if code.end_with?("\n")
            "```\n#{code}```\n"
          else
            "```\n#{code}\n```\n""no-newline"
          end
        })
ensure
  $VERBOSE = verbose
end
separated_runner(token, out = nil) click to toggle source
# File lib/core_assertions.rb, line 292
def separated_runner(token, out = nil)
  include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) })
  out = out ? IO.new(out, 'w') : STDOUT
  at_exit {
    out.puts "#{token}<error>", [Marshal.dump($!)].pack('m'), "#{token}</error>", "#{token}assertions=#{self._assertions}"
  }
  if defined?(Test::Unit::Runner)
    Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true)
  elsif defined?(Test::Unit::AutoRunner)
    Test::Unit::AutoRunner.need_auto_run = false
  end
end
syntax_check(code, fname, line) click to toggle source
# File lib/core_assertions.rb, line 137
def syntax_check(code, fname, line)
  code = code.dup.force_encoding(Encoding::UTF_8)
  RubyVM::InstructionSequence.compile(code, fname, fname, line)
  :ok
ensure
  raise if SyntaxError === $!
end