class Rgot::M

Public Class Methods

new(tests:, benchmarks:, examples:, fuzz_targets: nil, test_module: nil, opts: Options.new) click to toggle source
# File lib/rgot/m.rb, line 19
def initialize(tests:, benchmarks:, examples:, fuzz_targets: nil, test_module: nil, opts: Options.new)
  unless fuzz_targets
    raise "Require `fuzz_targets` keyword" if Gem::Version.new("2.0") <= Gem::Version.new(Rgot::VERSION)
    warn "`Rgot::M#initialize` will require the `fuzz_targets` keyword in the next major version."
  end
  unless test_module
    raise "Require `test_module` keyword" if Gem::Version.new("2.0") <= Gem::Version.new(Rgot::VERSION)
    warn "`Rgot::M#initialize` will require the `test_module` keyword in the next major version."
  end

  @tests = tests
  @benchmarks = benchmarks
  @examples = examples
  @fuzz_targets = fuzz_targets || []
  @test_module = test_module
  @opts = opts

  @cpu_list = []
  @thread_list = []
  @fs = @fuzz_targets.map do |fuzz_target|
    F.new(
      fuzz_target: fuzz_target,
      opts: F::Options.new(
        fuzz: opts.fuzz,
        fuzztime: opts.fuzztime,
      )
    )
  end
end

Public Instance Methods

run() click to toggle source
# File lib/rgot/m.rb, line 49
def run
  duration = Rgot.now
  test_ok = false
  fuzz_targets_ok = false
  example_ok = false

  if @tests.empty? && @benchmarks.empty? && @examples.empty? && @fuzz_targets.empty?
    warn "rgot: warning: no tests to run"
  end

  begin
    parse_option
  rescue Rgot::OptionError
    puts sprintf("%s\t%s\t%.3fs", "FAIL", @test_module, Rgot.now - duration)
    raise
  end

  Timeout.timeout(@opts.timeout.to_f) do
    test_ok = run_tests
    fuzz_targets_ok = run_fuzz_tests
    example_ok = run_examples
  end

  if !test_ok || !example_ok || !fuzz_targets_ok
    puts "FAIL"
    puts "exit status 1"
    puts sprintf("%s\t%s\t%.3fs", "FAIL", @test_module, Rgot.now - duration)
    return 1
  end

  if !run_fuzzing()
    puts "FAIL"
    puts "exit status 1"
    puts sprintf("%s\t%s\t%.3fs", "FAIL", @test_module, Rgot.now - duration)
    return 1
  end

  puts "PASS"
  run_benchmarks
  puts sprintf("%s\t%s\t%.3fs", "ok  ", @test_module, Rgot.now - duration)

  0
end

Private Instance Methods

capture() { || ... } click to toggle source
# File lib/rgot/m.rb, line 233
def capture
  raise LocalJumpError, "no block given" unless block_given?

  orig_out, orig_err = $stdout, $stderr
  out, err = StringIO.new, StringIO.new
  $stdout, $stderr = out, err
  yield
  [out.string, err.string]
ensure
  $stdout, $stderr = orig_out, orig_err
end
parse_option() click to toggle source
# File lib/rgot/m.rb, line 95
def parse_option
  cpu = @opts.cpu || (Etc.respond_to?(:nprocessors) ? Etc.nprocessors : '1').to_s
  @cpu_list = cpu.split(',').map { |i|
    j = i.to_i
    raise Rgot::OptionError, "invalid value #{i.inspect} for --cpu" unless 0 < j
    j
  }

  @thread_list = (@opts.thread || "1").split(',').map { |i|
    j = i.to_i
    raise Rgot::OptionError, "invalid value #{i.inspect} for --thread" unless 0 < j
    j
  }
end
run_benchmarks() click to toggle source
# File lib/rgot/m.rb, line 123
def run_benchmarks
  ok = true
  return ok unless @opts.bench
  @benchmarks.each do |bench|
    next unless /#{@opts.bench}/ =~ bench.name

    @cpu_list.each do |procs|
      @thread_list.each do |threads|
        opts = B::Options.new
        opts.benchtime = @opts.benchtime
        opts.procs = procs
        opts.threads = threads
        b = B.new(bench.module, bench.name.to_sym, opts)

        benchname = bench.name.to_s
        benchname << "-#{procs}" if 1 < procs
        benchname << "(#{threads})" if 1 < threads
        print "#{benchname}\t"
        result = b.run
        if b.failed?
          ok = false
          next
        end
        puts result
        if 0 < b.output.length
          printf("--- BENCH: %s\n%s", benchname, b.output)
        end
      end
    end
  end
  ok
end
run_examples() click to toggle source
# File lib/rgot/m.rb, line 200
def run_examples
  ok = true
  @examples.each do |example|
    if Rgot.verbose?
      puts "=== RUN   #{example.name}"
    end

    start = Rgot.now
    example.module.extend(example.module)
    method = example.module.instance_method(example.name).bind(example.module)
    out, _ = capture do
      method.call
    end
    file = method.source_location&.[](0) or raise("bug")
    example_parser = ExampleParser.new(File.read(file))
    example_parser.parse
    e = example_parser.examples.find { |er| er.name == example.name } or raise("bug")

    duration = Rgot.now - start
    if e.output.strip != out.strip
      printf("--- FAIL: %s (%.2fs)\n", e.name, duration)
      ok = false
      puts "got:"
      puts out.strip
      puts "want:"
      puts e.output.strip
    elsif Rgot.verbose?
      printf("--- PASS: %s (%.2fs)\n", e.name, duration)
    end
  end
  ok
end
run_fuzz_tests() click to toggle source
# File lib/rgot/m.rb, line 156
def run_fuzz_tests
  ok = true
  @fs.each do |f|
    if Rgot.verbose?
      if f.fuzz?
        puts "=== FUZZ  #{f.name}"
      else
        puts "=== RUN   #{f.name}"
      end
    end
    f.run_testing
    ok = ok && !f.failed?
  end
  ok
end
run_fuzzing() click to toggle source
# File lib/rgot/m.rb, line 172
def run_fuzzing
  if @fuzz_targets.empty? || @opts.fuzz.nil?
    return true
  end

  fuzzing_fs = @fs.select(&:fuzz?)

  if fuzzing_fs.empty?
    puts "rgot: warning: no fuzz tests to fuzz"
    return true
  end

  if fuzzing_fs.length > 1
    names = fuzzing_fs.map(&:name)
    puts "rgot: will not fuzz, --fuzz matches more than one fuzz test: #{names.inspect}"
    return false
  end

  ok = true

  fuzzing_fs.each do |f|
    f.run_fuzzing
    ok = ok && !f.failed?
  end

  ok
end
run_tests() click to toggle source
# File lib/rgot/m.rb, line 110
def run_tests
  ok = true
  @tests.each do |test|
    t = T.new(test.module, test.name.to_sym)
    if Rgot.verbose?
      puts "=== RUN   #{test.name}"
    end
    t.run
    ok = ok && !t.failed?
  end
  ok
end