Class: FastCov::TestMap

Inherits:
Object
  • Object
show all
Defined in:
lib/fast_cov/test_map.rb,
lib/fast_cov/test_map/reader.rb,
lib/fast_cov/test_map/aggregator.rb

Overview

In-memory test mapping that records which files each test depends on. Can be dumped to a gzipped TSV fragment file for later aggregation.

Usage:

# Accumulate mappings (e.g., in an RSpec formatter)
test_map = FastCov::TestMap.new
test_map.add("spec/models/user_spec.rb" => coverage_result)
test_map.dump("tmp/test_mapping.node_0.gz")

# Query mappings
test_map.dependencies("app/models/user.rb")
# => ["spec/models/user_spec.rb"]

# Aggregate fragments from multiple nodes
aggregator = FastCov::TestMap.aggregate(Dir["tmp/test_mapping.*.gz"])
aggregator.on(:sorted) { |elapsed| puts "Sorted in #{elapsed.round(2)}s" }
aggregator.on(:merged) { |files, elapsed| puts "Merged #{files} files in #{elapsed.round(2)}s" }
aggregator.each(10_000) { |batch| database.bulk_write(batch) }

Defined Under Namespace

Classes: Aggregator, Reader

Constant Summary collapse

DEFAULT_MAX_READERS =
[100, Process.getrlimit(Process::RLIMIT_NOFILE).first / 2].min

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTestMap

Returns a new instance of TestMap.



31
32
33
# File 'lib/fast_cov/test_map.rb', line 31

def initialize
  @mapping = {}
end

Class Method Details

.aggregate(*patterns, readers: DEFAULT_MAX_READERS) ⇒ Object

Create an Aggregator for merging fragment files. Accepts file paths or glob patterns.



67
68
69
70
# File 'lib/fast_cov/test_map.rb', line 67

def self.aggregate(*patterns, readers: DEFAULT_MAX_READERS)
  fragment_paths = patterns.flatten.flat_map { |p| p.include?("*") ? Dir.glob(p).sort : p }
  Aggregator.new(fragment_paths, readers)
end

Instance Method Details

#add(mappings) ⇒ Object

Record test -> dependency mappings. Accepts a Hash of { test_path => dependencies }.



37
38
39
40
41
42
43
44
45
# File 'lib/fast_cov/test_map.rb', line 37

def add(mappings)
  mappings.each do |test_path, deps|
    deps.each do |dep|
      next if dep == test_path

      (@mapping[dep] ||= Set.new) << test_path
    end
  end
end

#dependencies(file) ⇒ Object

Returns the test paths that depend on the given file.



48
49
50
# File 'lib/fast_cov/test_map.rb', line 48

def dependencies(file)
  @mapping[file]&.to_a
end

#dump(path) ⇒ Object

Write the accumulated mappings as a gzipped TSV fragment.



53
54
55
56
57
58
# File 'lib/fast_cov/test_map.rb', line 53

def dump(path)
  FileUtils.mkdir_p(File.dirname(path))

  lines = @mapping.map { |file, deps| "#{file}\t#{deps.to_a.join("\t")}\n" }
  Zlib::GzipWriter.open(path) { |gz| gz.write(lines.join) }
end

#sizeObject

Number of unique source files mapped.



61
62
63
# File 'lib/fast_cov/test_map.rb', line 61

def size
  @mapping.size
end