Class: Canopus::TestRunner::Discovery

Inherits:
Object
  • Object
show all
Defined in:
lib/canopus/test_runner/discovery.rb

Constant Summary collapse

MAX_FILES =
50_000
MAX_TEST_FILES =
2_000
MAX_FILE_BYTES =
1 << 20
MAX_TESTS =
10_000
MAX_DEPTH =
64
MAX_PATH_BYTES =
TestRunner::MAX_PATH_BYTES
MAX_ADAPTERS =
16
MAX_NAME_BYTES =
4_096
MAX_GROUPS =
32

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root:, ignore: nil, adapters: [Minitest.new, RSpec.new]) ⇒ Discovery

Returns a new instance of Discovery.

Raises:

  • (ArgumentError)


18
19
20
21
22
23
24
25
26
27
28
# File 'lib/canopus/test_runner/discovery.rb', line 18

def initialize(root:, ignore: nil, adapters: [Minitest.new, RSpec.new])
  @root = File.realpath(root)
  raise ArgumentError, "test root must be a directory" unless File.directory?(@root)
  raise ArgumentError, "ignore must respond to ignored?" if ignore && !ignore.respond_to?(:ignored?)
  unless adapters.is_a?(Array) && !adapters.empty? && adapters.length <= MAX_ADAPTERS &&
      adapters.all? { |adapter| adapter.respond_to?(:candidate?) && adapter.respond_to?(:discover) }
    raise ArgumentError, "test adapters must support candidate? and discover"
  end

  @ignore, @adapters = ignore, adapters.dup.freeze
end

Instance Attribute Details

#root ⇒ Object (readonly)

Returns the value of attribute root.



16
17
18
# File 'lib/canopus/test_runner/discovery.rb', line 16

def root
  @root
end

Instance Method Details

#discover(cancelled: -> { false }) ⇒ Object

Raises:

  • (ArgumentError)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/canopus/test_runner/discovery.rb', line 30

def discover(cancelled: -> { false })
  raise ArgumentError, "cancelled must respond to call" unless cancelled.respond_to?(:call)

  require "prism"
  tests, files, candidates = [], 0, 0
  ignore = BoundedIgnore.new(root, delegate: @ignore, cancelled: cancelled)
  walker = SafeWalker.new(root, ignore: ignore, hidden: true,
    follow_symlinks: false, max_depth: MAX_DEPTH, cancelled: cancelled)
  walker.each do |relative|
    break if cancelled.call || (files += 1) > MAX_FILES || tests.length >= MAX_TESTS
    relative = TestRunner.normalize_path(relative)
    next unless relative

    adapters = @adapters.select { |adapter| adapter.candidate?(relative) }
    next if adapters.empty?
    break if (candidates += 1) > MAX_TEST_FILES

    source = test_source(relative)
    next unless source
    parsed = Prism.parse(source)
    next unless parsed.success?

    adapters.each do |adapter|
      remaining = MAX_TESTS - tests.length
      break if remaining <= 0 || cancelled.call
      discovered = adapter.discover(parsed.value, path: relative, limit: remaining, cancelled: cancelled)
      next unless discovered.is_a?(Array)
      discovered.first(remaining).each { |test| tests << test if valid_test?(test, relative) }
    end
  rescue Errno::ENOENT, Errno::EACCES, Errno::ELOOP, Errno::ENOTDIR, EncodingError, ArgumentError
    next
  end
  tests.sort_by! { |test| [test.path, test.line, test.column, test.framework.to_s, test.name] }
  tests.freeze
end