spectracer 1.0.1 → 1.1.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 +16 -0
- data/lib/spectracer/core/path_filter.rb +34 -0
- data/lib/spectracer/io/git_adapter.rb +61 -7
- data/lib/spectracer/orchestrators/dependency_collector.rb +4 -0
- data/lib/spectracer/orchestrators/dependency_tracer.rb +3 -0
- data/lib/spectracer/providers/git_changed_files.rb +11 -3
- data/lib/spectracer/version.rb +1 -1
- data/lib/spectracer.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f3390f1fff13f710bc6da01e4ada62a534a13ec3e6132f4ac0a90152acd3941
|
|
4
|
+
data.tar.gz: b51108f46238ba68383dcff7488150d0665b737208ea0b177850dcd8da3986c8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a70a1963b275b76fc5a6f135f3d51810990fbdba6a4e0d212f4cd9284156423d14730669b7ee8e9fc8574fbd365efd768785ccc1af561a73e85caf06880147f0
|
|
7
|
+
data.tar.gz: 071dbe59306dd5b29e4af182697ca5ed42e1d7ff66f999acde6f6258813401eb247b3968570dad5349bc471f424768f6beed38fd99a1623561b01c94df801402
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.0] - 2026-02-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- PathFilter class to exclude gem paths from dependency tracing
|
|
8
|
+
- Local pre-commit hook support: detect affected specs from uncommitted changes
|
|
9
|
+
- Automatic default branch detection from `origin/HEAD` with fallback to `main`/`master`
|
|
10
|
+
- E2E tests for full tracing flow, gem path filtering, and local usage scenarios
|
|
11
|
+
- CI pipeline annotation step to display collected dependency tree as pretty JSON
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- GitAdapter now supports both CI and local modes for changed file detection
|
|
16
|
+
- Changed files detection includes uncommitted changes (staged + unstaged) plus branch diff
|
|
17
|
+
- Improved gem path filtering using both `Gem.path` and `Bundler.bundle_path`
|
|
18
|
+
|
|
3
19
|
## [1.0.1] - 2026-01-31
|
|
4
20
|
|
|
5
21
|
### Fixed
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spectracer
|
|
4
|
+
module Core
|
|
5
|
+
class PathFilter
|
|
6
|
+
def initialize(
|
|
7
|
+
gem_paths: Gem.path,
|
|
8
|
+
bundler_path: defined?(Bundler) ? Bundler.bundle_path.to_s : nil
|
|
9
|
+
)
|
|
10
|
+
@excluded_prefixes = build_excluded_prefixes(gem_paths, bundler_path)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def gem_path?(path)
|
|
14
|
+
@excluded_prefixes.any? { |prefix| path.start_with?(prefix) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def app_path?(path)
|
|
18
|
+
!gem_path?(path)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def build_excluded_prefixes(gem_paths, bundler_path)
|
|
24
|
+
prefixes = gem_paths.map { |p| ensure_trailing_slash(p) }
|
|
25
|
+
prefixes << ensure_trailing_slash(bundler_path) if bundler_path
|
|
26
|
+
prefixes.uniq
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ensure_trailing_slash(path)
|
|
30
|
+
path.end_with?("/") ? path : "#{path}/"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -33,21 +33,75 @@ module Spectracer
|
|
|
33
33
|
[]
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def changed_files_against(target_branch,
|
|
37
|
-
target_ref =
|
|
36
|
+
def changed_files_against(target_branch, include_uncommitted: true)
|
|
37
|
+
target_ref = resolve_target_ref(target_branch)
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
committed_files = git.diff(target_ref, "HEAD").stats[:files].keys
|
|
40
|
+
|
|
41
|
+
if include_uncommitted
|
|
42
|
+
uncommitted_files = uncommitted_changed_files
|
|
43
|
+
(committed_files + uncommitted_files).uniq
|
|
41
44
|
else
|
|
42
|
-
|
|
45
|
+
committed_files
|
|
43
46
|
end
|
|
44
|
-
|
|
45
|
-
diff.stats[:files].keys
|
|
46
47
|
rescue Git::Error => e
|
|
47
48
|
@logger&.warn("Failed to diff against #{target_branch}: #{e.message}")
|
|
48
49
|
[]
|
|
49
50
|
end
|
|
50
51
|
|
|
52
|
+
def resolve_target_ref(branch)
|
|
53
|
+
remote_ref = "origin/#{branch}"
|
|
54
|
+
return remote_ref if ref_exists?(remote_ref)
|
|
55
|
+
|
|
56
|
+
return branch if ref_exists?(branch)
|
|
57
|
+
|
|
58
|
+
@logger&.warn("Neither origin/#{branch} nor #{branch} found, using HEAD~1")
|
|
59
|
+
"HEAD~1"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def ref_exists?(ref)
|
|
63
|
+
git.object(ref)
|
|
64
|
+
true
|
|
65
|
+
rescue Git::Error
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def uncommitted_changed_files
|
|
70
|
+
staged = git.diff("HEAD").stats[:files].keys
|
|
71
|
+
unstaged = git.status.changed.keys + git.status.added.keys + git.status.deleted.keys
|
|
72
|
+
(staged + unstaged).uniq
|
|
73
|
+
rescue Git::Error => e
|
|
74
|
+
@logger&.warn("Failed to get uncommitted changes: #{e.message}")
|
|
75
|
+
[]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def local_changed_files
|
|
79
|
+
uncommitted = uncommitted_changed_files
|
|
80
|
+
|
|
81
|
+
branch_diff = local_branch_diff_files
|
|
82
|
+
(uncommitted + branch_diff).uniq
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def local_branch_diff_files
|
|
86
|
+
current = current_branch
|
|
87
|
+
default_branch = detect_default_branch
|
|
88
|
+
|
|
89
|
+
return [] unless default_branch
|
|
90
|
+
return [] if current == default_branch
|
|
91
|
+
|
|
92
|
+
git.diff(default_branch, "HEAD").stats[:files].keys
|
|
93
|
+
rescue Git::Error => e
|
|
94
|
+
@logger&.warn("Failed to get branch diff: #{e.message}")
|
|
95
|
+
[]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def detect_default_branch
|
|
99
|
+
remote_head = git.lib.send(:command, "symbolic-ref", "refs/remotes/origin/HEAD", "--short").strip
|
|
100
|
+
remote_head.sub("origin/", "")
|
|
101
|
+
rescue
|
|
102
|
+
%w[main master].find { |b| ref_exists?(b) }
|
|
103
|
+
end
|
|
104
|
+
|
|
51
105
|
private
|
|
52
106
|
|
|
53
107
|
def git
|
|
@@ -6,10 +6,12 @@ module Spectracer
|
|
|
6
6
|
def initialize(
|
|
7
7
|
paths: Spectracer::Core::Paths.new,
|
|
8
8
|
store: Spectracer::IO::DependencyStore.new,
|
|
9
|
+
path_filter: Spectracer::Core::PathFilter.new,
|
|
9
10
|
logger: nil
|
|
10
11
|
)
|
|
11
12
|
@paths = paths
|
|
12
13
|
@store = store
|
|
14
|
+
@path_filter = path_filter
|
|
13
15
|
@logger = logger
|
|
14
16
|
end
|
|
15
17
|
|
|
@@ -38,6 +40,8 @@ module Spectracer
|
|
|
38
40
|
|
|
39
41
|
data.each do |spec_file, dependencies|
|
|
40
42
|
dependencies.each do |dep|
|
|
43
|
+
next if @path_filter.gem_path?(dep)
|
|
44
|
+
|
|
41
45
|
inverse[dep] << spec_file unless inverse[dep].include?(spec_file)
|
|
42
46
|
end
|
|
43
47
|
end
|
|
@@ -7,11 +7,13 @@ module Spectracer
|
|
|
7
7
|
paths: Spectracer::Core::Paths.new,
|
|
8
8
|
store: Spectracer::IO::DependencyStore.new,
|
|
9
9
|
repository: Spectracer::Providers::Repository.new,
|
|
10
|
+
path_filter: Spectracer::Core::PathFilter.new,
|
|
10
11
|
logger: nil
|
|
11
12
|
)
|
|
12
13
|
@paths = paths
|
|
13
14
|
@store = store
|
|
14
15
|
@repository = repository
|
|
16
|
+
@path_filter = path_filter
|
|
15
17
|
@logger = logger
|
|
16
18
|
@current_spec_file = nil
|
|
17
19
|
@spec_file_dependencies = Hash.new { |h, k| h[k] = Set.new }
|
|
@@ -42,6 +44,7 @@ module Spectracer
|
|
|
42
44
|
def tracepoint
|
|
43
45
|
@tracepoint ||= TracePoint.new(:call) do |tp|
|
|
44
46
|
path = tp.path
|
|
47
|
+
next if @path_filter.gem_path?(path)
|
|
45
48
|
next unless path.start_with?(repository_root)
|
|
46
49
|
next if @current_spec_file.nil?
|
|
47
50
|
|
|
@@ -12,11 +12,14 @@ module Spectracer
|
|
|
12
12
|
def call
|
|
13
13
|
default_branch = @env.fetch("BUILDKITE_PIPELINE_DEFAULT_BRANCH", "main")
|
|
14
14
|
current_branch = @env.fetch("BUILDKITE_BRANCH") { @git_adapter.current_branch }
|
|
15
|
+
is_ci = @env.key?("BUILDKITE_BUILD_ID")
|
|
15
16
|
|
|
16
|
-
files = if current_branch == default_branch
|
|
17
|
+
files = if is_ci && current_branch == default_branch
|
|
17
18
|
changed_files_for_latest_commit(current_branch)
|
|
18
|
-
|
|
19
|
+
elsif is_ci
|
|
19
20
|
changed_files_against_default_branch(default_branch)
|
|
21
|
+
else
|
|
22
|
+
changed_files_for_local
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
@logger&.debug("Changed files: #{files.inspect}")
|
|
@@ -33,7 +36,12 @@ module Spectracer
|
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
def changed_files_against_default_branch(default_branch)
|
|
36
|
-
@git_adapter.changed_files_against(default_branch,
|
|
39
|
+
@git_adapter.changed_files_against(default_branch, include_uncommitted: true)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def changed_files_for_local
|
|
43
|
+
@logger&.debug("Running locally, checking uncommitted changes + branch diff")
|
|
44
|
+
@git_adapter.local_changed_files
|
|
37
45
|
end
|
|
38
46
|
end
|
|
39
47
|
end
|
data/lib/spectracer/version.rb
CHANGED
data/lib/spectracer.rb
CHANGED
|
@@ -4,6 +4,7 @@ require_relative "spectracer/version"
|
|
|
4
4
|
require_relative "spectracer/logger"
|
|
5
5
|
|
|
6
6
|
require_relative "spectracer/core/paths"
|
|
7
|
+
require_relative "spectracer/core/path_filter"
|
|
7
8
|
require_relative "spectracer/core/spec_selector"
|
|
8
9
|
|
|
9
10
|
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.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mitch Smith
|
|
@@ -38,6 +38,7 @@ files:
|
|
|
38
38
|
- README.md
|
|
39
39
|
- lib/spectracer.default.yml
|
|
40
40
|
- lib/spectracer.rb
|
|
41
|
+
- lib/spectracer/core/path_filter.rb
|
|
41
42
|
- lib/spectracer/core/paths.rb
|
|
42
43
|
- lib/spectracer/core/spec_selector.rb
|
|
43
44
|
- lib/spectracer/integrations/minitest.rb
|
|
@@ -80,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
80
81
|
- !ruby/object:Gem::Version
|
|
81
82
|
version: '0'
|
|
82
83
|
requirements: []
|
|
83
|
-
rubygems_version:
|
|
84
|
+
rubygems_version: 4.0.5
|
|
84
85
|
specification_version: 4
|
|
85
86
|
summary: Intelligent test selection based on code changes
|
|
86
87
|
test_files: []
|