spectracer 1.2.1 → 1.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 +4 -4
- data/CHANGELOG.md +18 -0
- data/lib/spectracer/core/glob_factorizer.rb +44 -0
- data/lib/spectracer/core/paths.rb +2 -2
- data/lib/spectracer/core/spec_selector.rb +7 -3
- data/lib/spectracer/io/config_loader.rb +13 -2
- data/lib/spectracer/orchestrators/dependency_collector.rb +3 -1
- data/lib/spectracer/tasks/spectracer.rake +28 -1
- data/lib/spectracer/version.rb +1 -1
- data/lib/spectracer.default.minitest.yml +16 -0
- data/lib/spectracer.default.yml +1 -0
- data/lib/spectracer.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c01031e82f15d06eeef77717e0d31ec34a5bb73940323deb023c6bf3ec13eb13
|
|
4
|
+
data.tar.gz: 77395beec7e9113f050495f1ea036e8f019814514da08fb2a90654d25f0a6a10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30dd806f8a0428ea5890a4f39b2c315ae198c740b27fa33c629f1be9b6279d28b71227afceb349994bf6dfea58e05e5b65fa574623d63131e5b338e51ba9a13e
|
|
7
|
+
data.tar.gz: 0741cbb623678895406c7a233124654c6b54f8a96743e3bb8ab05de3e4447cafc1b8fa4affc3ea0ded7466f0aa3a2b749048e9ff96a52982f09e7e8472969669
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.0] - 2026-02-04
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Remove `./` prefix from traced paths for bktec/zzglob compatibility
|
|
8
|
+
- zzglob's `path.Clean()` normalizes `./spec/` to `spec/`, causing pattern mismatches
|
|
9
|
+
- Paths are now stored without the `./` prefix (e.g., `spec/models/user_spec.rb`)
|
|
10
|
+
- Output is compatible with both `bktec --test-file-pattern` and `rspec --pattern`
|
|
11
|
+
|
|
12
|
+
## [1.3.0] - 2026-02-02
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- GlobFactorizer to reduce redundant spec patterns before running tests
|
|
17
|
+
- Removes concrete files already matched by broader globs
|
|
18
|
+
- Eliminates narrower globs subsumed by broader ones
|
|
19
|
+
- Example: `{spec/user/**/*_spec.rb, spec/user/**/one_spec.rb}` → `{spec/user/**/*_spec.rb}`
|
|
20
|
+
|
|
3
21
|
## [1.2.0] - 2026-02-02
|
|
4
22
|
|
|
5
23
|
### Added
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spectracer
|
|
4
|
+
module Core
|
|
5
|
+
class GlobFactorizer
|
|
6
|
+
def call(patterns)
|
|
7
|
+
return patterns.to_a if patterns.size <= 1
|
|
8
|
+
|
|
9
|
+
patterns_array = patterns.to_a.uniq
|
|
10
|
+
globs, files = patterns_array.partition { |p| glob_pattern?(p) }
|
|
11
|
+
|
|
12
|
+
# Remove files that are matched by any glob
|
|
13
|
+
remaining_files = files.reject do |file|
|
|
14
|
+
globs.any? { |glob| File.fnmatch?(glob, file, File::FNM_PATHNAME) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Remove globs subsumed by other globs (broader patterns win)
|
|
18
|
+
remaining_globs = globs.reject do |glob|
|
|
19
|
+
globs.any? { |other| other != glob && subsumes?(other, glob) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
remaining_globs + remaining_files
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def glob_pattern?(pattern)
|
|
28
|
+
pattern.include?("*") || pattern.include?("?") || pattern.include?("[")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def subsumes?(broader, narrower)
|
|
32
|
+
return false if broader == narrower
|
|
33
|
+
|
|
34
|
+
# Convert the narrower glob to a concrete-ish path by replacing wildcards
|
|
35
|
+
# then check if the broader glob would match it
|
|
36
|
+
test_path = narrower
|
|
37
|
+
.gsub("**", "any/nested/path")
|
|
38
|
+
.gsub("*", "placeholder")
|
|
39
|
+
|
|
40
|
+
File.fnmatch?(broader, test_path, File::FNM_PATHNAME)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -34,8 +34,8 @@ module Spectracer
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def normalize(file_path, repo_root:)
|
|
37
|
-
relative = file_path.sub(
|
|
38
|
-
relative.
|
|
37
|
+
relative = file_path.sub(%r{\A#{Regexp.escape(repo_root)}/?}, "")
|
|
38
|
+
relative.sub(%r{\A\./}, "")
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def strip_dot_prefix(path)
|
|
@@ -5,6 +5,10 @@ module Spectracer
|
|
|
5
5
|
class SpecSelector
|
|
6
6
|
Result = Struct.new(:specs, :file_to_specs_map, keyword_init: true)
|
|
7
7
|
|
|
8
|
+
def initialize(factorizer: GlobFactorizer.new)
|
|
9
|
+
@factorizer = factorizer
|
|
10
|
+
end
|
|
11
|
+
|
|
8
12
|
def call(changed_files:, inverse_deps:, globs:, on_empty:)
|
|
9
13
|
spec_set = Set.new
|
|
10
14
|
file_to_specs = {}
|
|
@@ -12,12 +16,12 @@ module Spectracer
|
|
|
12
16
|
changed_files.each do |file|
|
|
13
17
|
matched_specs = []
|
|
14
18
|
|
|
15
|
-
if file.end_with?("_spec.rb")
|
|
19
|
+
if file.end_with?("_spec.rb", "_test.rb")
|
|
16
20
|
spec_set.add(file)
|
|
17
21
|
matched_specs << file
|
|
18
22
|
end
|
|
19
23
|
|
|
20
|
-
file_key = file.
|
|
24
|
+
file_key = file.sub(%r{\A\./}, "")
|
|
21
25
|
if (specs = inverse_deps[file_key])
|
|
22
26
|
spec_set.merge(specs)
|
|
23
27
|
matched_specs.concat(specs)
|
|
@@ -34,7 +38,7 @@ module Spectracer
|
|
|
34
38
|
file_to_specs[file] = matched_specs.uniq.sort unless matched_specs.empty?
|
|
35
39
|
end
|
|
36
40
|
|
|
37
|
-
files = spec_set.
|
|
41
|
+
files = @factorizer.call(spec_set).sort
|
|
38
42
|
specs_result = files.empty? ? on_empty : files.join(",")
|
|
39
43
|
|
|
40
44
|
Result.new(specs: specs_result, file_to_specs_map: file_to_specs)
|
|
@@ -6,7 +6,8 @@ module Spectracer
|
|
|
6
6
|
module IO
|
|
7
7
|
class ConfigLoader
|
|
8
8
|
FILE_PATH = ".spectracer.yml"
|
|
9
|
-
|
|
9
|
+
DEFAULT_RSPEC_FILE_PATH = File.expand_path("../../spectracer.default.yml", __dir__)
|
|
10
|
+
DEFAULT_MINITEST_FILE_PATH = File.expand_path("../../spectracer.default.minitest.yml", __dir__)
|
|
10
11
|
|
|
11
12
|
def initialize(logger: nil)
|
|
12
13
|
@logger = logger
|
|
@@ -29,7 +30,17 @@ module Spectracer
|
|
|
29
30
|
YAML.safe_load_file(FILE_PATH)
|
|
30
31
|
else
|
|
31
32
|
@logger&.debug("No configuration file found at #{FILE_PATH.inspect}. Using defaults.")
|
|
32
|
-
YAML.safe_load_file(
|
|
33
|
+
YAML.safe_load_file(default_file_path)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def default_file_path
|
|
38
|
+
if defined?(RSpec)
|
|
39
|
+
DEFAULT_RSPEC_FILE_PATH
|
|
40
|
+
elsif defined?(Minitest)
|
|
41
|
+
DEFAULT_MINITEST_FILE_PATH
|
|
42
|
+
else
|
|
43
|
+
DEFAULT_RSPEC_FILE_PATH
|
|
33
44
|
end
|
|
34
45
|
end
|
|
35
46
|
|
|
@@ -24,7 +24,9 @@ module Spectracer
|
|
|
24
24
|
|
|
25
25
|
inverse_deps.each_value(&:sort!)
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
output_path = @paths.collected_dependencies_file
|
|
28
|
+
@logger&.debug("Writing collected dependencies to: #{output_path}")
|
|
29
|
+
@store.write(inverse_deps, output_path)
|
|
28
30
|
|
|
29
31
|
nil
|
|
30
32
|
end
|
|
@@ -10,12 +10,39 @@ namespace :spectracer do
|
|
|
10
10
|
exit 0
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
framework = detect_test_framework
|
|
14
|
+
default_file_path = case framework
|
|
15
|
+
when :minitest
|
|
16
|
+
Spectracer::IO::ConfigLoader::DEFAULT_MINITEST_FILE_PATH
|
|
17
|
+
else
|
|
18
|
+
Spectracer::IO::ConfigLoader::DEFAULT_RSPEC_FILE_PATH
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
warn "Detected test framework: #{framework}"
|
|
13
22
|
warn "Creating '#{config_path}' file."
|
|
14
23
|
|
|
15
|
-
default_content = File.read(
|
|
24
|
+
default_content = File.read(default_file_path)
|
|
16
25
|
File.write(config_path, default_content)
|
|
17
26
|
end
|
|
18
27
|
|
|
28
|
+
def detect_test_framework
|
|
29
|
+
gemfile_path = File.join(Dir.pwd, "Gemfile")
|
|
30
|
+
return :rspec unless File.exist?(gemfile_path)
|
|
31
|
+
|
|
32
|
+
gemfile_content = File.read(gemfile_path)
|
|
33
|
+
|
|
34
|
+
has_rspec = gemfile_content.match?(/['"]rspec['"]|['"]rspec-rails['"]/)
|
|
35
|
+
has_minitest = gemfile_content.match?(/['"]minitest['"]/) || Dir.exist?("test")
|
|
36
|
+
|
|
37
|
+
if has_rspec
|
|
38
|
+
:rspec
|
|
39
|
+
elsif has_minitest
|
|
40
|
+
:minitest
|
|
41
|
+
else
|
|
42
|
+
:rspec
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
19
46
|
desc "Collect spec dependencies from tracing artifacts"
|
|
20
47
|
task :collect_dependencies do
|
|
21
48
|
logger = Spectracer::Logger.new(
|
data/lib/spectracer/version.rb
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# This file was generated by the 'spectracer:install' Rake task.
|
|
2
|
+
# You can modify this file to customize the behavior of Spectracer.
|
|
3
|
+
# Test framework: Minitest
|
|
4
|
+
defaults:
|
|
5
|
+
all_specs_glob: "test/**/*_test.rb"
|
|
6
|
+
no_specs_glob: "{}"
|
|
7
|
+
|
|
8
|
+
# When no spec files are detected to run, Spectracer will run the specs defined in the 'on_empty_spec_set' key.
|
|
9
|
+
on_empty_spec_set: "{{no_specs_glob}}"
|
|
10
|
+
|
|
11
|
+
# If a file changes and it matches the glob, the corresponding pattern will be added to the list of spec files to run.
|
|
12
|
+
# These will take precedence over the 'on_empty_spec_set' key.
|
|
13
|
+
globs_matcher:
|
|
14
|
+
"Gemfile": "{{all_specs_glob}}"
|
|
15
|
+
"Gemfile.lock": "{{all_specs_glob}}"
|
|
16
|
+
"config/**": "{{all_specs_glob}}"
|
data/lib/spectracer.default.yml
CHANGED
data/lib/spectracer.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "spectracer/logger"
|
|
|
5
5
|
|
|
6
6
|
require_relative "spectracer/core/paths"
|
|
7
7
|
require_relative "spectracer/core/path_filter"
|
|
8
|
+
require_relative "spectracer/core/glob_factorizer"
|
|
8
9
|
require_relative "spectracer/core/spec_selector"
|
|
9
10
|
|
|
10
11
|
require_relative "spectracer/io/command_runner"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spectracer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mitch Smith
|
|
@@ -36,8 +36,10 @@ files:
|
|
|
36
36
|
- CHANGELOG.md
|
|
37
37
|
- LICENSE.txt
|
|
38
38
|
- README.md
|
|
39
|
+
- lib/spectracer.default.minitest.yml
|
|
39
40
|
- lib/spectracer.default.yml
|
|
40
41
|
- lib/spectracer.rb
|
|
42
|
+
- lib/spectracer/core/glob_factorizer.rb
|
|
41
43
|
- lib/spectracer/core/path_filter.rb
|
|
42
44
|
- lib/spectracer/core/paths.rb
|
|
43
45
|
- lib/spectracer/core/spec_selector.rb
|