test-map 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd8d77a4693229a3bdc64882a2771830090f7b04ae977df9eec5386e3ffc16e3
4
- data.tar.gz: dc04407d47b77d1b5a6418072c8e380a327417a82fe27a21f31e1020d7b6b2aa
3
+ metadata.gz: 502e7e183acdd6b0f2576ad014d920a1c00b4233f08cdfaf57243ed4814b1c1b
4
+ data.tar.gz: 4d50c652f8ffa46c485ab2da15c5ddd5da022cd856aac394375679f07ea083b8
5
5
  SHA512:
6
- metadata.gz: d80247e944cecd9b3c06b48b63bae9052589f97713496f674dc9ad23524ebfe6e8bb0720d38a90d0ba9b0874cb33c6a9422d6cd9bf3c49c2f8db252c26dde011
7
- data.tar.gz: 1690f86016ea54148386896023ba40e22210e2559c8d7dd175c7b728c458a9e2abdfdaa7f3a73997cbb4da446186e51033313a35df8c3e7e158a1a8cd8359f5a
6
+ metadata.gz: 60480addcb32f9528c65895488190f4a7c01623b437e9818ff81e85f081b4e884e8fc3afb5684b4105b2eb52010ae1f1e182e2fbae0574a690c2404b86af3314
7
+ data.tar.gz: 61ce5a7bb1bd602f018e6e64b141bdfddbf52bbd61c72794796823d04b8b0a225f1748a6d66afe1d0540bfd85e31d34ed3cc3dd9c368ad543a2ecacfe0f898a5
data/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@ All notable changes to this project will be documented in this file.
6
6
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
7
7
  and this project adheres to [Semantic Versioning](http://semver.org/).
8
8
 
9
+ ## 0.4.0 - 2026-03-07
10
+
11
+ Extend reporting and cache handling for minitest and rspec. Add caches at
12
+ processing and profiling events. Optimize execution by reducing excessive
13
+ calls and calculations.
14
+
15
+ ## 0.3.0 - 2026-03-01
16
+
17
+ Introduce test cache and remove changes runner. Removed `test:changes` Rake
18
+ task as it is no longer needed.
19
+
9
20
  ## 0.2.0 - 2024-10-25
10
21
 
11
22
  Provide explicit Test Task via Rake for Minitest, Rspec, and Rails. Extend
data/README.md CHANGED
@@ -4,12 +4,10 @@
4
4
  Track associated files of executed tests to optimize test execution on file
5
5
  changes.
6
6
 
7
- Test-Map results in a file that maps test files to the files they depend on.
8
- You can use this file to run only the tests that are affected by a file change.
9
- This is useful when you have a large test suite and want to optimize the time
10
- spent running tests. Submit a change request and only run tests that depend on
11
- what you changed. Optimizing in such way, the time spent waiting for CI to
12
- verify can be reduced to seconds.
7
+ Test-Map records which source files each test touches and caches their
8
+ checksums. On subsequent runs, tests whose dependencies haven't changed are
9
+ automatically skipped. This is useful when you have a large test suite and want
10
+ to optimize the time spent running tests locally or in CI.
13
11
 
14
12
  ## Usage
15
13
 
@@ -19,59 +17,89 @@ Add test-map to your Gemfile.
19
17
  $ bundle add test-map
20
18
  ```
21
19
 
22
- ### Minitest
23
-
24
- Include test-map in your test helper. Typically you want to include it
25
- conditionally so it only generates the test map when needed.
20
+ Require test-map in your test helper or spec helper.
26
21
 
27
22
  ```ruby
28
23
  # filename: test/test_helper.rb
29
-
30
- # Include test-map after minitest has been required
31
- require 'test_map' if ENV['TEST_MAP']
24
+ require 'test_map'
32
25
  ```
33
26
 
34
- Run your tests with the `TEST_MAP` environment variable set.
27
+ ## Example Run
28
+
29
+ Running the testsuite, test-map creates a mapping of tests to their code files,
30
+ as well as a test-file result cache. Running the testsuite again, all
31
+ successfully run tests are cached and skipped. Chaning a file, **only tests
32
+ that need to be run are executed**.
35
33
 
36
34
  ```sh
37
- $ TEST_MAP=1 bundle exec ruby -Itest test/models/user_test.rb
38
- # or
39
- $ TEST_MAP=1 bundle exec rake test
35
+ # Running the testsuite for the first time, all tests are executed and mapped
36
+ > be rake
37
+ Run options: --seed 10112
38
+
39
+ # Running:
40
+
41
+ .......................................................
42
+
43
+ Finished in 0.042190s, 1303.6402 runs/s, 1730.2861 assertions/s.
44
+ 55 runs, 73 assertions, 0 failures, 0 errors, 0 skips, 0 cached
45
+
46
+ # Running again without changes, all tests are cached and skipped
47
+ > be rake
48
+ Run options: --seed 40581
49
+
50
+ # Running:
51
+
52
+ CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
53
+
54
+ Finished in 0.007902s, 6960.1126 runs/s, 0.0000 assertions/s.
55
+ 55 runs, 0 assertions, 0 failures, 0 errors, 0 skips, 55 cached
56
+
57
+ # Change a file and rerun the testsuite
58
+ > be rake
59
+ Run options: --seed 47682
60
+
61
+ # Running:
62
+
63
+ CCCCCCCCCC...CCCCCCCCCCCC.....CCCCCCCCCCCCCCCCCCCCCCCCC
64
+
65
+ Finished in 0.014029s, 3920.3764 runs/s, 570.2366 assertions/s.
66
+ 55 runs, 8 assertions, 0 failures, 0 errors, 0 skips, 47 cached
40
67
  ```
41
68
 
42
- Using the a dedicated rake task you can connect a file watcher and trigger
43
- tests on file changes.
69
+ ### Minitest
70
+
71
+ Include test-map in your test helper.
44
72
 
45
73
  ```ruby
46
- # filename: Rakefile
47
- require 'test_map/test_task'
74
+ # filename: test/test_helper.rb
48
75
 
49
- TestMap::TestTask.create
76
+ # Include test-map after minitest has been required
77
+ require 'test_map'
50
78
  ```
51
79
 
52
- Using [entr](https://eradman.com/entrproject/) as example file watcher.
80
+ Run your tests. On the first run test-map records file dependencies into
81
+ `.test-map.yml` and checksums into `.test-cache.yml`. On subsequent runs,
82
+ tests whose source files haven't changed are automatically skipped.
53
83
 
54
84
  ```sh
55
- # find all ruby files | watch them, postpone first execution, clear screen
56
- # with every run and on file change run test suite for the changed file
57
- # (placeholder /_).
58
- $ find . -name "*.rb" | entr -cp bundle exec rake test:changes /_
85
+ $ bundle exec ruby -Itest test/models/user_test.rb
86
+ # or
87
+ $ bundle exec rake test
59
88
  ```
60
89
 
61
90
  ### Rspec
62
91
 
63
- Include test-map in your test helper. Typically you want to include it
64
- conditionally so it only generates the test map when needed.
92
+ Include test-map in your spec helper.
65
93
 
66
94
  ```ruby
67
95
  # filename: spec/spec_helper.rb
68
- require 'test_map' if ENV['TEST_MAP']
96
+ require 'test_map'
69
97
  ```
70
98
 
71
- Run your tests with the `TEST_MAP` environment variable set.
99
+ Run your tests. Caching works the same as with Minitest.
72
100
 
73
101
  ```sh
74
- $ TEST_MAP=1 bundle exec rspec
102
+ $ bundle exec rspec
75
103
  ```
76
104
 
77
105
  ## Configuration
@@ -83,6 +111,7 @@ TestMap::Config.configure do |config|
83
111
  config[:logger] = Logger.new($stdout) # default logs to dev/null
84
112
  config[:merge] = false # merge results (e.g. with multiple testsuites)
85
113
  config[:out_file] = 'my-test-map.yml' # default is .test-map.yml
114
+ config[:cache_file] = 'my-test-cache.yml' # default is .test-cache.yml
86
115
  # defaults to [%r{^(vendor)/}] }
87
116
  config[:exclude_patterns] = [%r{^(vendor|other_libraries)/}]
88
117
  # register a custom rule to match new files; must implement `call(file)`;
@@ -93,14 +122,6 @@ end
93
122
 
94
123
  ## Development
95
124
 
96
- Open list of features:
97
-
98
- - [x] Configure file exclude list (e.g. test files are not needed).
99
- - [ ] Auto-handle packs, packs with subdirectories.
100
- - [x] Demonstrate usage with file watchers.
101
- - [ ] Demonstrate CI pipelines with GitHub actions and GitLab CI.
102
- - [x] Merge results.
103
-
104
125
  ```sh
105
126
  $ bundle install # install dependencies
106
127
  $ bundle exec rake # run testsuite
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'yaml'
5
+
6
+ module TestMap
7
+ # Cache tracks file checksums to skip unchanged tests.
8
+ class Cache
9
+ GLOBAL_FILES = %w[Gemfile.lock .ruby-version].freeze
10
+
11
+ def initialize(cache_file, map_file, root: Dir.pwd)
12
+ @cache_file = cache_file
13
+ @map_file = map_file
14
+ @root = root
15
+ @global_files_changed = nil
16
+ @current_checksums = {}
17
+ @file_exists_cache = {}
18
+ end
19
+
20
+ def fresh?(test_file)
21
+ return false unless cached_checksums
22
+ return false if global_files_changed?
23
+
24
+ files_to_check = [test_file].concat(source_files_for(test_file))
25
+ files_to_check.all? { |f| file_exist?(f) && current_checksum(f) == cached_checksums[f] }
26
+ end
27
+
28
+ def write(results)
29
+ all_files = collect_tracked_files(results)
30
+ checksums = all_files.each_with_object({}) do |file, hash|
31
+ hash[file] = current_checksum(file) if file_exist?(file)
32
+ end
33
+ File.write(@cache_file, checksums.sort.to_h.to_yaml)
34
+ end
35
+
36
+ private
37
+
38
+ def cached_checksums
39
+ @cached_checksums ||= File.exist?(@cache_file) && YAML.safe_load_file(@cache_file)
40
+ end
41
+
42
+ def global_files_changed?
43
+ return @global_files_changed unless @global_files_changed.nil?
44
+
45
+ @global_files_changed = GLOBAL_FILES.any? do |f|
46
+ file_exist?(f) && current_checksum(f) != cached_checksums[f]
47
+ end
48
+ end
49
+
50
+ def inverted_map = @inverted_map ||= build_inverted_map
51
+
52
+ def build_inverted_map
53
+ return {} unless File.exist?(@map_file)
54
+
55
+ map = YAML.safe_load_file(@map_file)
56
+ inverted = Hash.new { |h, k| h[k] = [] }
57
+ map.each do |source, tests|
58
+ tests.each { |t| inverted[t] << source }
59
+ end
60
+ inverted
61
+ end
62
+
63
+ def source_files_for(test_file)
64
+ inverted_map[test_file] || []
65
+ end
66
+
67
+ def current_checksum(file)
68
+ @current_checksums[file] ||= Digest::SHA256.file(File.join(@root, file)).hexdigest
69
+ end
70
+
71
+ def file_exist?(file)
72
+ return @file_exists_cache[file] if @file_exists_cache.key?(file)
73
+
74
+ @file_exists_cache[file] = File.exist?(File.join(@root, file))
75
+ end
76
+
77
+ def collect_tracked_files(results)
78
+ sources = results.keys
79
+ tests = results.values.flatten.uniq
80
+ all = (sources + tests + GLOBAL_FILES).uniq
81
+ all.select { |f| file_exist?(f) }
82
+ end
83
+ end
84
+ end
@@ -11,6 +11,7 @@ module TestMap
11
11
 
12
12
  def self.default_config
13
13
  { logger: Logger.new('/dev/null'), out_file: '.test-map.yml',
14
+ cache_file: '.test-cache.yml',
14
15
  exclude_patterns: [%r{^(vendor)/}], natural_mapping: nil,
15
16
  skip_files: [%r{^(test/)}], merge: false }
16
17
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestMap
4
+ # Publish/subscribe event bus. No overhead when no subscribers.
5
+ #
6
+ # Topics use `type.action` format, e.g. `cache.create`, `map.create`.
7
+ #
8
+ # Block form measures elapsed time and passes it to subscribers:
9
+ # Event.publish('cache.create') { write_cache }
10
+ #
11
+ # Fire-and-forget form for simple notifications:
12
+ # Event.publish('cache.found', test_file:)
13
+ module Event
14
+ @subscribers = {}
15
+
16
+ class << self
17
+ def subscribe(topic, &block)
18
+ (@subscribers[topic] ||= []) << block
19
+ end
20
+
21
+ def publish(topic, **payload, &block)
22
+ listeners = @subscribers[topic]
23
+ return notify(listeners, topic, **payload) unless block
24
+ return yield unless listeners
25
+
26
+ publish_with_timing(listeners, topic, **payload, &block)
27
+ end
28
+
29
+ def reset
30
+ @subscribers = {}
31
+ end
32
+
33
+ private
34
+
35
+ def notify(listeners, topic, **payload)
36
+ listeners&.each { |cb| cb.call(topic, **payload) }
37
+ end
38
+
39
+ def publish_with_timing(listeners, topic, **payload)
40
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
41
+ result = yield
42
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
43
+ listeners.each { |cb| cb.call(topic, elapsed:, **payload) }
44
+ result
45
+ end
46
+ end
47
+ end
48
+ end
@@ -26,8 +26,9 @@ module TestMap
26
26
  def results
27
27
  raise NotTracedError.default unless @trace
28
28
 
29
- @files.filter { _1.start_with? Dir.pwd }
30
- .map { _1.sub("#{Dir.pwd}/", '') }
29
+ cwd = "#{Dir.pwd}/"
30
+ @files.filter { _1.start_with? cwd }
31
+ .map { _1.sub(cwd, '') }
31
32
  .then { Filter.call _1 }
32
33
  end
33
34
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestMap
4
+ module Plugins
5
+ module Minitest
6
+ # Reporter that tracks cached test count and appends it to the summary.
7
+ class CacheReporter < ::Minitest::StatisticsReporter
8
+ attr_accessor :cached, :composite
9
+
10
+ def initialize(io = $stdout, options = {}, composite: nil)
11
+ super(io, options)
12
+ self.cached = 0
13
+ self.composite = composite
14
+ end
15
+
16
+ def record(result)
17
+ super
18
+ self.cached += 1 if result.failure.is_a?(TestMap::CachedSkip)
19
+ end
20
+
21
+ def report
22
+ super
23
+ return unless composite
24
+
25
+ summary_reporter = composite.reporters.find { |r| r.is_a?(::Minitest::SummaryReporter) }
26
+ return unless summary_reporter
27
+
28
+ summary_reporter.results.reject! { |r| r.failure.is_a?(TestMap::CachedSkip) }
29
+ summary_reporter.skips -= cached if summary_reporter.skips
30
+ patch_summary(summary_reporter)
31
+ end
32
+
33
+ private
34
+
35
+ def patch_summary(summary_reporter)
36
+ cached_count = cached
37
+ original_summary = summary_reporter.method(:summary)
38
+
39
+ summary_reporter.define_singleton_method(:summary) do
40
+ "#{original_summary.call}, #{cached_count} cached"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ module Minitest # :nodoc:
49
+ def self.plugin_test_map_cache_init(options)
50
+ cache_reporter = TestMap::Plugins::Minitest::CacheReporter.new(options[:io], options, composite: reporter)
51
+ reporter.reporters.unshift(cache_reporter)
52
+ end
53
+ end
@@ -1,27 +1,85 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module TestMap
4
+ # CachedSkip is raised to skip tests whose files haven't changed.
5
+ # Subclasses Minitest::Skip so Minitest treats it as a skip, but the
6
+ # custom reporter can distinguish it and show `C` instead of `S`.
7
+ class CachedSkip < Minitest::Skip
8
+ def initialize(msg = 'test-map: cached')
9
+ super
10
+ end
11
+
12
+ def result_label
13
+ 'Cached'
14
+ end
15
+ end
16
+ end
17
+
18
+ require_relative 'minitest/cache_reporter'
19
+
3
20
  module TestMap
4
21
  module Plugins
5
22
  # Minitest plugin for TestMap.
6
23
  module Minitest
7
24
  def self.included(_base)
8
25
  TestMap.logger.info 'Registering hooks for Minitest'
9
- ::Minitest.after_run do
10
- TestMap.reporter.write "#{Dir.pwd}/#{Config.config[:out_file]}"
26
+ TestMap.suite_passed = true
27
+
28
+ ::Minitest.after_run { write_results }
29
+ install_cache_reporter
30
+ end
31
+
32
+ def self.install_cache_reporter
33
+ ::Minitest.extensions << 'test_map_cache'
34
+ end
35
+
36
+ def self.write_results
37
+ out_file = "#{Dir.pwd}/#{Config.config[:out_file]}"
38
+ reporter_results = TestMap.reporter.results
39
+
40
+ # All tests were cache-skipped, existing files are still valid
41
+ return if reporter_results.empty?
42
+
43
+ full_results = merge_results(out_file, reporter_results)
44
+ File.write(out_file, full_results.to_yaml)
45
+ TestMap.cache.write(full_results) if TestMap.suite_passed
46
+ end
47
+
48
+ # Merge with existing map to preserve mappings for cache-skipped tests
49
+ def self.merge_results(out_file, reporter_results)
50
+ if File.exist?(out_file)
51
+ TestMap.reporter.merge(reporter_results, YAML.safe_load_file(out_file))
52
+ else
53
+ reporter_results
11
54
  end
12
55
  end
13
56
 
14
- def after_setup
15
- @recorder = FileRecorder.new.tap(&:trace)
57
+ def before_setup
58
+ test_file = resolve_test_file
59
+ raise TestMap::CachedSkip if test_file && TestMap.cache.fresh?(test_file)
16
60
 
61
+ @recorder = FileRecorder.new.tap(&:trace)
17
62
  super
18
63
  end
19
64
 
20
65
  def before_teardown
21
66
  super
22
67
 
68
+ return unless @recorder
69
+
23
70
  @recorder.stop
24
71
  TestMap.reporter.add @recorder.results
72
+
73
+ TestMap.suite_passed = false if !passed? && !skipped?
74
+ end
75
+
76
+ private
77
+
78
+ def resolve_test_file
79
+ file = method(name).source_location&.first
80
+ return unless file
81
+
82
+ file.sub("#{Dir.pwd}/", '')
25
83
  end
26
84
  end
27
85
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/formatters/progress_formatter'
4
+
5
+ module TestMap
6
+ module Plugins
7
+ module RSpec
8
+ # Formatter that shows `C` for cached tests instead of `*` (pending).
9
+ # Extends ProgressFormatter so it can serve as the default formatter.
10
+ # Adjusts the summary to show cached count and filters cached tests
11
+ # from the pending output.
12
+ class CacheFormatter < ::RSpec::Core::Formatters::ProgressFormatter
13
+ CACHED_MESSAGE = 'test-map: cached'
14
+
15
+ ::RSpec::Core::Formatters.register self,
16
+ :example_passed, :example_pending, :example_failed,
17
+ :start_dump, :dump_summary, :dump_pending
18
+
19
+ def initialize(output)
20
+ super
21
+ @cached_count = 0
22
+ end
23
+
24
+ def example_pending(notification)
25
+ if cached?(notification.example)
26
+ @cached_count += 1
27
+ output.print ::RSpec::Core::Formatters::ConsoleCodes.wrap('c', :cyan)
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ def dump_pending(notification)
34
+ examples = notification.pending_examples
35
+ cached = examples.select { |ex| cached?(ex) }
36
+ return if examples.size == cached.size
37
+
38
+ cached.each { |ex| examples.delete(ex) }
39
+ super
40
+ cached.each { |ex| examples.push(ex) }
41
+ end
42
+
43
+ def dump_summary(summary)
44
+ if @cached_count.positive?
45
+ cached_count = @cached_count
46
+ original_totals_line = summary.method(:totals_line)
47
+ summary.define_singleton_method(:totals_line) do
48
+ "#{original_totals_line.call}, #{cached_count} cached"
49
+ end
50
+ end
51
+ super
52
+ end
53
+
54
+ private
55
+
56
+ def cached?(example)
57
+ example.execution_result.pending_message == CACHED_MESSAGE
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,16 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'rspec/cache_formatter'
4
+
3
5
  TestMap.logger.info 'Loading RSpec plugin'
6
+ TestMap.suite_passed = true
7
+
8
+ module TestMap
9
+ module Plugins
10
+ # RSpec integration for TestMap.
11
+ module RSpec
12
+ def self.write_results
13
+ out_file = "#{Dir.pwd}/#{Config.config[:out_file]}"
14
+ reporter_results = TestMap.reporter.results
15
+
16
+ # All tests were cache-skipped, existing files are still valid
17
+ return if reporter_results.empty?
18
+
19
+ full_results = merge_results(out_file, reporter_results)
20
+ File.write(out_file, full_results.to_yaml)
21
+ TestMap.cache.write(full_results) if TestMap.suite_passed
22
+ end
23
+
24
+ # Merge with existing map to preserve mappings for cache-skipped tests
25
+ def self.merge_results(out_file, reporter_results)
26
+ if File.exist?(out_file)
27
+ TestMap.reporter.merge(reporter_results, YAML.safe_load_file(out_file))
28
+ else
29
+ reporter_results
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
4
35
 
5
36
  RSpec.configure do |config|
6
- config.around(:example) do |example|
7
- # path = example.metadata[:example_group][:file_path]
8
- recorder = TestMap::FileRecorder.new
9
- recorder.trace { example.run }
10
- TestMap.reporter.add recorder.results
37
+ config.default_formatter = TestMap::Plugins::RSpec::CacheFormatter
38
+
39
+ config.before(:context) do
40
+ test_file = self.class.metadata[:file_path].sub("#{Dir.pwd}/", '').sub(%r{^\./}, '')
41
+ skip TestMap::Plugins::RSpec::CacheFormatter::CACHED_MESSAGE if TestMap.cache.fresh?(test_file)
11
42
  end
12
43
 
13
- config.after(:suite) do
14
- TestMap.reporter.write "#{Dir.pwd}/#{TestMap::Config.config[:out_file]}"
44
+ config.around(:example) do |example|
45
+ if example.metadata[:skip]
46
+ example.run
47
+ else
48
+ recorder = TestMap::FileRecorder.new
49
+ recorder.trace { example.run }
50
+ TestMap.reporter.add recorder.results
51
+
52
+ TestMap.suite_passed = false if example.exception && !example.skipped?
53
+ end
15
54
  end
55
+
56
+ config.after(:suite) { TestMap::Plugins::RSpec.write_results }
16
57
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestMap
4
- VERSION = '0.2.1'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/test_map.rb CHANGED
@@ -8,11 +8,24 @@ require_relative 'test_map/report'
8
8
  require_relative 'test_map/file_recorder'
9
9
  require_relative 'test_map/natural_mapping'
10
10
  require_relative 'test_map/mapping'
11
+ require_relative 'test_map/cache'
12
+ require_relative 'test_map/event'
11
13
 
12
14
  # TestMap records associated files to test execution.
13
15
  module TestMap
14
16
  def self.reporter = @reporter ||= Report.new
15
17
  def self.logger = Config.config[:logger]
18
+
19
+ def self.cache
20
+ @cache ||= Cache.new(
21
+ "#{Dir.pwd}/#{Config[:cache_file]}",
22
+ "#{Dir.pwd}/#{Config[:out_file]}"
23
+ )
24
+ end
25
+
26
+ class << self
27
+ attr_accessor :suite_passed
28
+ end
16
29
  end
17
30
 
18
31
  # Load plugins for supported test frameworks.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-map
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christoph Lipautz
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-28 00:00:00.000000000 Z
11
+ date: 2026-03-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Track files that are covered by test files to execute only the necessary
@@ -25,16 +25,19 @@ files:
25
25
  - LICENSE.txt
26
26
  - README.md
27
27
  - lib/test_map.rb
28
+ - lib/test_map/cache.rb
28
29
  - lib/test_map/config.rb
29
30
  - lib/test_map/errors.rb
31
+ - lib/test_map/event.rb
30
32
  - lib/test_map/file_recorder.rb
31
33
  - lib/test_map/filter.rb
32
34
  - lib/test_map/mapping.rb
33
35
  - lib/test_map/natural_mapping.rb
34
36
  - lib/test_map/plugins/minitest.rb
37
+ - lib/test_map/plugins/minitest/cache_reporter.rb
35
38
  - lib/test_map/plugins/rspec.rb
39
+ - lib/test_map/plugins/rspec/cache_formatter.rb
36
40
  - lib/test_map/report.rb
37
- - lib/test_map/test_task.rb
38
41
  - lib/test_map/version.rb
39
42
  homepage: https://github.com/unused/test-map
40
43
  licenses: []
@@ -43,7 +46,7 @@ metadata:
43
46
  source_code_uri: https://github.com/unused/test-map
44
47
  changelog_uri: https://github.com/unused/test-map/main/blob/main/CHANGELOG.md
45
48
  rubygems_mfa_required: 'true'
46
- post_install_message:
49
+ post_install_message:
47
50
  rdoc_options: []
48
51
  require_paths:
49
52
  - lib
@@ -51,15 +54,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
51
54
  requirements:
52
55
  - - ">="
53
56
  - !ruby/object:Gem::Version
54
- version: 3.0.0
57
+ version: 3.2.0
55
58
  required_rubygems_version: !ruby/object:Gem::Requirement
56
59
  requirements:
57
60
  - - ">="
58
61
  - !ruby/object:Gem::Version
59
62
  version: '0'
60
63
  requirements: []
61
- rubygems_version: 3.5.16
62
- signing_key:
64
+ rubygems_version: 3.4.10
65
+ signing_key:
63
66
  specification_version: 4
64
67
  summary: Track associated files of tests.
65
68
  test_files: []
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'mapping'
4
- require 'rake/testtask'
5
- require 'minitest'
6
- require 'minitest/unit'
7
-
8
- module TestMap
9
- # TestTask is a rake helper class.
10
- class TestTask < Rake::TaskLib
11
- # Error for unknown test task adapter.
12
- class UnknownAdapterError < StandardError; end
13
-
14
- def initialize(name) # rubocop:disable Lint/MissingSuper
15
- @name = name
16
- end
17
-
18
- # Adapter for rspec test task
19
- class RailsTestTask
20
- attr_accessor :files
21
-
22
- def call = Rails::TestUnit::Runner.run_from_rake('test', files)
23
- end
24
-
25
- # Adapter for minitest test task.
26
- class MinitestTask < Minitest::TestTask
27
- def call = ruby(make_test_cmd, verbose: false)
28
-
29
- def files=(test_files)
30
- self.test_globs = test_files
31
- end
32
- end
33
-
34
- # Adapter for rspec test task
35
- class RSpecTask
36
- attr_accessor :files
37
-
38
- def call = `rspec #{files.join(' ')}`
39
- end
40
-
41
- def self.create(name = :test) = new(name).define
42
-
43
- def define
44
- namespace @name do
45
- desc 'Run tests for changed files'
46
- task :changes do
47
- out_file = "#{Dir.pwd}/.test-map.yml"
48
- args = defined?(Rails) ? ENV['TEST']&.split : ARGV[1..]
49
- test_files = Mapping.new(out_file).lookup(*args)
50
-
51
- # puts "Running tests #{test_files.join(' ')}"
52
- test_task.files = test_files
53
- test_task.call
54
- end
55
- end
56
- end
57
-
58
- def test_task = @test_task ||= build_test_task
59
-
60
- def build_test_task
61
- if defined?(Rails)
62
- return RailsTestTask.new
63
- elsif defined?(Minitest)
64
- require 'minitest/test_task'
65
- return MinitestTask.new
66
- elsif defined?(RSpec)
67
- return RSpecTask.new
68
- end
69
-
70
- raise UnknownAdapterError, 'No test task adapter found'
71
- end
72
- end
73
- end