class Judges::Test

License
MIT
Copyright
Copyright © 2024-2025 Yegor Bugayenko
Author

Yegor Bugayenko (yegor256@gmail.com)
are not supposed to instantiate it yourself.
This class is instantiated by the bin/judge command line interface. You
The test command.

def assert(judge, tname, fb, yaml)

def assert(judge, tname, fb, yaml)
  xpaths = yaml['expected']
  return if xpaths.nil?
  xml = Nokogiri::XML.parse(Factbase::ToXML.new(fb).xml)
  xpaths.each do |xp|
    raise "#{judge.name}/#{tname} doesn't match '#{xp}':\n#{xml}" if xml.xpath(xp).empty?
  end
end

def include?(opts, name, tname = nil)

def include?(opts, name, tname = nil)
  judges = opts['judge'] || []
  return true if judges.empty?
  re = tname.nil? ? '.+' : tname
  judges.any? { |n| n.match?(%r{^#{name}(/#{re})?$}) }
end

def initialize(loog)

Parameters:
  • loog (Loog) -- Logging facility
def initialize(loog)
  @loog = loog
end

def prepare(fb, yaml)

def prepare(fb, yaml)
  inputs = yaml['input']
  (yaml['repeat']&.to_i || 1).times do
    inputs&.each do |i|
      f = fb.insert
      i.each do |k, vv|
        if vv.is_a?(Array)
          vv.each do |v|
            f.send(:"#{k}=", v)
          end
        else
          f.send(:"#{k}=", vv)
        end
      end
    end
  end
end

def run(opts, args)

Raises:
  • (RuntimeError) - If not exactly one argument provided

Parameters:
  • args (Array) -- List of command line arguments
  • opts (Hash) -- Command line options (start with '--')
def run(opts, args)
  raise 'Exactly one argument required' unless args.size == 1
  dir = args[0]
  @loog.info("Testing judges in #{dir.to_rel}...")
  errors = []
  tested = 0
  tests = 0
  visible = []
  times = {}
  judges = Judges::Judges.new(dir, opts['lib'], @loog)
  elapsed(@loog, level: Logger::INFO) do
    judges.each_with_index do |judge, i|
      visible << judge.name
      next unless include?(opts, judge.name)
      @loog.info("👉 Testing #{judge.script} (##{i}) in #{judge.dir.to_rel}...")
      buf = Loog::Buffer.new
      judge = judge.with_loog(buf)
      judge.tests.each do |f|
        tname = File.basename(f).gsub(/\.yml$/, '')
        visible << "  #{judge.name}/#{tname}"
        next unless include?(opts, judge.name, tname)
        yaml = YAML.load_file(f, permitted_classes: [Time])
        if yaml['skip']
          buf.info("Skipped #{f.to_rel}")
          next
        end
        unless Judges::Categories.new(opts['enable'], opts['disable']).ok?(yaml['category'])
          buf.info("Skipped #{f.to_rel} because of its category")
          next
        end
        buf.info("🛠️ Testing #{f.to_rel}:")
        start = Time.now
        badge = "#{judge.name}/#{tname}"
        begin
          fb = Factbase.new
          prepare(fb, yaml)
          yaml['before']&.each do |n|
            j = judges.get(n).with_loog(buf)
            buf.info("Running #{j.script} judge as a pre-condition...")
            test_one(fb, opts, j, n, yaml, assert: false)
          end
          test_one(fb, opts, judge, tname, yaml)
          yaml['after']&.each do |rb|
            buf.info("Running #{rb} assertion script...")
            $fb = fb
            $loog = buf
            load(File.join(judge.dir, rb), true)
          end
          tests += 1
        rescue StandardError => e
          @loog.info(buf.to_s)
          @loog.warn(Backtrace.new(e))
          errors << badge
        end
        times[badge] = Time.now - start
      end
      tested += 1
    end
    unless times.empty?
      fmt = "%-60s\t%9s\t%-9s"
      @loog.info(
        [
          'Test summary:',
          format(fmt, 'Script', 'Seconds', 'Result'),
          format(fmt, '---', '---', '---'),
          times.sort_by { |_, v| v }.reverse.map do |script, sec|
            format(fmt, script, format('%.3f', sec), errors.include?(script) ? 'ERROR' : 'OK')
          end.join("\n  ")
        ].join("\n  ")
      )
    end
    throw :'👍 No judges tested' if tested.zero?
    throw :"👍 All #{tested} judge(s) but no tests passed" if tests.zero?
    throw :"👍 All #{tested} judge(s) and #{tests} tests passed" if errors.empty?
    throw :"❌ #{tested} judge(s) tested, #{errors.size} of them failed"
  end
  unless errors.empty?
    raise "#{errors.size} tests failed" unless opts['quiet']
    @loog.debug('Not failing the build with tests failures, due to the --quiet option')
  end
  return unless tested.zero? || tests.zero?
  if opts['judge'].nil?
    raise 'There are seems to be no judges' unless opts['quiet']
    @loog.debug('Not failing the build with no judges tested, due to the --quiet option')
  else
    raise 'There are seems to be no judges' if visible.empty?
    @loog.info("The following judges are available to use with the --judge option:\n  #{visible.join("\n  ")}")
  end
end

def test_one(fb, opts, judge, tname, yaml, assert: true)

Returns:
  • (nil) - Always NIL

Parameters:
  • assert (Boolean) -- Should we assert (TRUE) or simply skip (FALSE)?
  • yaml (Hash) -- The YAML to be tested
  • tname (String) -- The name of the test (without .rb suffix)
  • judge (Judges::Judge) -- The judge to run
  • opts (Hash) -- The command line options
  • fb (Factbase) -- The factbase to use
def test_one(fb, opts, judge, tname, yaml, assert: true)
  options = Judges::Options.new(opts['option']) + Judges::Options.new(yaml['options'])
  runs = opts['runs'] || yaml['runs'] || 1
  (1..runs).each do |r|
    fbx = fb
    if opts['log']
      require 'factbase/logged'
      fbx = Factbase::Logged.new(fb, @loog)
    end
    expected_failure = yaml['expected_failure']
    begin
      judge.run(fbx, {}, {}, options)
      raise 'Exception expected but not raised' if expected_failure
    # rubocop:disable Lint/RescueException
    rescue Exception => e
      # rubocop:enable Lint/RescueException
      raise e unless expected_failure
      if expected_failure.is_a?(Array) && expected_failure.none? { |s| e.message.include?(s) }
        raise "Exception #{e.class} raised with #{e.message.inspect}, but this is not what was expected"
      end
    end
    next unless assert
    assert(judge, tname, fb, yaml) if r == runs || opts['assert_once'].is_a?(FalseClass)
  end
end