Class: Henitai::CoverageBootstrapper

Inherits:
Object
  • Object
show all
Defined in:
lib/henitai/coverage_bootstrapper.rb,
sig/henitai.rbs

Overview

Ensures coverage data exists before the mutation pipeline starts.

Constant Summary collapse

DEPENDENCY_MANIFEST_FILE =

Sidecar recording which dependency files existed when the coverage artifacts were produced. Deletions drop a path from the current set but leave every surviving file's mtime untouched, so freshness must compare the recorded path set, not just watch existing files.

"henitai_dependency_manifest.json"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(static_filter: StaticFilter.new) ⇒ CoverageBootstrapper

Returns a new instance of CoverageBootstrapper.

Parameters:

  • static_filter: (StaticFilter) (defaults to: StaticFilter.new)


17
18
19
# File 'lib/henitai/coverage_bootstrapper.rb', line 17

def initialize(static_filter: StaticFilter.new)
  @static_filter = static_filter
end

Instance Attribute Details

#static_filterStaticFilter (readonly)

Returns the value of attribute static_filter.

Returns:



60
61
62
# File 'lib/henitai/coverage_bootstrapper.rb', line 60

def static_filter
  @static_filter
end

Instance Method Details

#bootstrap_coverage(integration, config, test_files = nil) ⇒ void

This method returns an undefined value.

Parameters:



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/henitai/coverage_bootstrapper.rb', line 127

def bootstrap_coverage(integration, config, test_files = nil)
  test_files ||= integration.test_files

  with_reports_dir(config) do
    with_coverage_dir(config) do
      result = integration.run_suite(test_files)
      if result == :survived
        record_dependency_manifest(config)
        return
      end

      raise CoverageError, build_bootstrap_error(result)
    end
  end
end

#configured_test_excludes(config) ⇒ Array[String]?

Specs pass bare config doubles exposing only what the example needs.

Parameters:

Returns:

  • (Array[String], nil)


246
247
248
# File 'lib/henitai/coverage_bootstrapper.rb', line 246

def configured_test_excludes(config)
  config.respond_to?(:test_excludes) ? config.test_excludes : nil
end

#coverage_available?(source_files, config) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


62
63
64
65
66
67
# File 'lib/henitai/coverage_bootstrapper.rb', line 62

def coverage_available?(source_files, config)
  coverage_lines = static_filter.coverage_lines_for(config)
  covered_sources = covered_source_files(source_files, coverage_lines)

  covered_sources.any?
end

#coverage_fresh?(source_files, config, test_files) ⇒ Boolean

Returns true when a coverage report already exists, is newer than every watched source and test file, and the dependency path set is unchanged since the report was produced. Stale or absent reports return false.

Parameters:

Returns:

  • (Boolean)


88
89
90
91
92
93
94
# File 'lib/henitai/coverage_bootstrapper.rb', line 88

def coverage_fresh?(source_files, config, test_files)
  watched_files_fresh?(
    coverage_report_path(config),
    source_files,
    test_files
  ) && dependency_manifest_current?(config)
end

#coverage_report_path(config) ⇒ String

Parameters:

Returns:

  • (String)


119
120
121
# File 'lib/henitai/coverage_bootstrapper.rb', line 119

def coverage_report_path(config)
  File.join(coverage_dir(config), ".resultset.json")
end

#ensure!(source_files:, config:, integration:, test_files: nil) ⇒ void

This method returns an undefined value.

Runs the test suite to collect coverage, unless a fresh report already exists.

Parameters:

  • source_files (Array<String>)

    lib files whose coverage must be present

  • config (Configuration)
  • integration (Integration::Base)
  • test_files (Array<String>, nil) (defaults to: nil)

    test files to run; defaults to all files reported by the integration when nil. Either way, config.test_excludes matches are dropped.

  • source_files: (Array[String])
  • config: (Configuration)
  • integration: (Integration::Base)
  • test_files: (Array[String], nil) (defaults to: nil)

Raises:



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/henitai/coverage_bootstrapper.rb', line 38

def ensure!(source_files:, config:, integration:, test_files: nil)
  return if source_files.empty?

  resolved_test_files = resolve_test_files(integration, test_files, config)

  # Skip the bootstrap only when the coverage artifacts are both newer than
  # all watched files and actually cover the configured sources. A fresh
  # but irrelevant report (e.g. from a different working directory) must
  # still trigger a re-bootstrap rather than silently proceeding with no
  # usable coverage.
  unless coverage_ready?(source_files, config, integration, resolved_test_files)
    bootstrap_coverage(integration, config, resolved_test_files)
  end

  return if coverage_available?(source_files, config)

  raise CoverageError,
        "Coverage data is unavailable for the configured source files"
end

#record_dependency_manifest(config) ⇒ Object

Writes the current dependency path set next to the coverage artifacts. Called after every bootstrap; exposed so tests can seed a manifest.



23
24
25
26
# File 'lib/henitai/coverage_bootstrapper.rb', line 23

def record_dependency_manifest(config)
  FileUtils.mkdir_p(reports_dir(config))
  File.write(dependency_manifest_path(config), JSON.generate(current_dependency_paths))
end

#resolve_test_files(integration, test_files, config) ⇒ Array[String]

Excluded tests are dropped here rather than in bootstrap_coverage, so the same filtered list feeds both the suite run and the freshness watch list: a test that no longer contributes to the baseline must not invalidate it either.

Parameters:

Returns:

  • (Array[String])

Raises:



234
235
236
237
238
239
240
241
242
243
# File 'lib/henitai/coverage_bootstrapper.rb', line 234

def resolve_test_files(integration, test_files, config)
  candidates = test_files.nil? ? integration.test_files : test_files
  patterns = configured_test_excludes(config)
  retained = ExcludedTestFilter.new(patterns:).reject(candidates)
  return retained unless retained.empty? && candidates.any?

  raise ConfigurationError,
        "test_excludes #{Array(patterns).inspect} excludes every test file; " \
        "coverage cannot be generated"
end

#source_file_paths(source_files) ⇒ Array[String]

Parameters:

  • (Array[String])

Returns:

  • (Array[String])


81
82
83
# File 'lib/henitai/coverage_bootstrapper.rb', line 81

def source_file_paths(source_files)
  Array(source_files).map { |path| File.expand_path(path) }
end