audition 0.3.0 → 0.4.1
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/README.md +125 -49
- data/lib/audition/bundle_sweep.rb +25 -15
- data/lib/audition/cli.rb +128 -140
- data/lib/audition/config.rb +30 -4
- data/lib/audition/dynamic/harness.rb +415 -41
- data/lib/audition/dynamic/prober.rb +271 -54
- data/lib/audition/finding.rb +14 -2
- data/lib/audition/progress.rb +418 -0
- data/lib/audition/reconciliation.rb +86 -0
- data/lib/audition/report/github.rb +4 -3
- data/lib/audition/report/json.rb +5 -1
- data/lib/audition/report/sweep.rb +182 -0
- data/lib/audition/report/text.rb +13 -2
- data/lib/audition/report.rb +8 -1
- data/lib/audition/rewriters.rb +29 -2
- data/lib/audition/static/analyzer.rb +86 -17
- data/lib/audition/static/checks/dependency_class_state.rb +160 -0
- data/lib/audition/static/checks/instance_memoization.rb +226 -0
- data/lib/audition/static/checks/mutable_constants.rb +172 -9
- data/lib/audition/static/checks/ractor_isolation.rb +214 -15
- data/lib/audition/static/checks/unsafe_calls.rb +7 -4
- data/lib/audition/static/checks/unshareable_reads.rb +259 -0
- data/lib/audition/static/checks.rb +6 -2
- data/lib/audition/static/gem_calls.rb +1770 -0
- data/lib/audition/static/graph_audit.rb +1113 -15
- data/lib/audition/static/literal_classifier.rb +385 -20
- data/lib/audition/static/native_extensions.rb +28 -16
- data/lib/audition/static/work_split.rb +54 -0
- data/lib/audition/target.rb +82 -13
- data/lib/audition/version.rb +1 -1
- data/lib/audition.rb +3 -0
- metadata +12 -4
data/lib/audition/target.rb
CHANGED
|
@@ -12,6 +12,12 @@ module Audition
|
|
|
12
12
|
vendor node_modules tmp log coverage pkg .git .bundle
|
|
13
13
|
].freeze
|
|
14
14
|
|
|
15
|
+
# Directories holding the target's tests rather than the code
|
|
16
|
+
# a production boot loads. The default; `test_dirs` in
|
|
17
|
+
# .audition.yml replaces it for a project that files them
|
|
18
|
+
# elsewhere.
|
|
19
|
+
TEST_DIRS = %w[test spec features].freeze
|
|
20
|
+
|
|
15
21
|
# Cargo and Zig build output: full of .so/.dylib artifacts that
|
|
16
22
|
# are not the extension `require` loads.
|
|
17
23
|
BUILD_DIRS = %w[target zig-out].freeze
|
|
@@ -20,6 +26,11 @@ module Audition
|
|
|
20
26
|
# "bundle" on macOS, "so" everywhere else, Windows included).
|
|
21
27
|
COMPILED = "*.{bundle,so}"
|
|
22
28
|
|
|
29
|
+
# Generated type stubs. The extension is the convention; where
|
|
30
|
+
# the generator writes them is not, so they are found by
|
|
31
|
+
# extension anywhere in the tree.
|
|
32
|
+
STUBS = "*.rbi"
|
|
33
|
+
|
|
23
34
|
# @return [Symbol] one of `:script`, `:gem`, `:rack`, `:rails`,
|
|
24
35
|
# `:directory`, `:bundle`
|
|
25
36
|
attr_reader :type
|
|
@@ -34,6 +45,10 @@ module Audition
|
|
|
34
45
|
# shipped with the target; empty for scripts and file lists
|
|
35
46
|
attr_reader :compiled_files
|
|
36
47
|
|
|
48
|
+
# @return [Array<String>] generated type stubs (.rbi) found
|
|
49
|
+
# anywhere in the target's tree
|
|
50
|
+
attr_reader :stub_files
|
|
51
|
+
|
|
37
52
|
# @return [Hash, nil] dynamic probe entry (`:mode` plus
|
|
38
53
|
# mode-specific keys), nil for static-only targets
|
|
39
54
|
attr_reader :entry
|
|
@@ -75,7 +90,8 @@ module Audition
|
|
|
75
90
|
type: :files,
|
|
76
91
|
root: Dir.pwd,
|
|
77
92
|
ruby_files: paths,
|
|
78
|
-
entry: nil
|
|
93
|
+
entry: nil,
|
|
94
|
+
stub_files: stubs(Dir.pwd)
|
|
79
95
|
)
|
|
80
96
|
end
|
|
81
97
|
|
|
@@ -96,10 +112,26 @@ module Audition
|
|
|
96
112
|
type: :script,
|
|
97
113
|
root: File.dirname(path),
|
|
98
114
|
ruby_files: [path],
|
|
99
|
-
|
|
115
|
+
# A file inside a Rails root cannot run standalone; the
|
|
116
|
+
# script probe would report a misleading boot failure, so
|
|
117
|
+
# such files stay static-only.
|
|
118
|
+
entry: rails_member?(path) ? nil : {mode: :script, path: path},
|
|
119
|
+
stub_files: stubs(File.dirname(path))
|
|
100
120
|
)
|
|
101
121
|
end
|
|
102
122
|
|
|
123
|
+
def self.rails_member?(path)
|
|
124
|
+
dir = File.dirname(File.expand_path(path))
|
|
125
|
+
until dir == (parent = File.dirname(dir))
|
|
126
|
+
return true if File.file?(
|
|
127
|
+
File.join(dir, "config", "application.rb")
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
dir = parent
|
|
131
|
+
end
|
|
132
|
+
false
|
|
133
|
+
end
|
|
134
|
+
|
|
103
135
|
def self.from_directory(dir)
|
|
104
136
|
application_rb = File.join(dir, "config", "application.rb")
|
|
105
137
|
config_ru = File.join(dir, "config.ru")
|
|
@@ -117,7 +149,8 @@ module Audition
|
|
|
117
149
|
root: dir,
|
|
118
150
|
ruby_files: glob(dir),
|
|
119
151
|
entry: nil,
|
|
120
|
-
compiled_files: compiled(dir)
|
|
152
|
+
compiled_files: compiled(dir),
|
|
153
|
+
stub_files: stubs(dir)
|
|
121
154
|
)
|
|
122
155
|
end
|
|
123
156
|
end
|
|
@@ -131,16 +164,21 @@ module Audition
|
|
|
131
164
|
glob(File.join(spec.full_gem_path, rp))
|
|
132
165
|
end,
|
|
133
166
|
entry: {mode: :require, feature: name,
|
|
167
|
+
load_paths: spec.full_require_paths,
|
|
134
168
|
root: spec.full_gem_path},
|
|
135
|
-
compiled_files: compiled_for(spec)
|
|
169
|
+
compiled_files: compiled_for(spec),
|
|
170
|
+
stub_files: stubs(spec.full_gem_path)
|
|
136
171
|
)
|
|
137
172
|
rescue Gem::MissingSpecError
|
|
138
173
|
raise Error,
|
|
139
174
|
"#{name} is not a file, directory, or installed gem"
|
|
140
175
|
end
|
|
141
176
|
|
|
177
|
+
# An app's own Ruby is not confined to app/lib/config: local
|
|
178
|
+
# gems, engines and tests boot into the same process, so the
|
|
179
|
+
# whole root is scanned.
|
|
142
180
|
def self.rails_target(dir)
|
|
143
|
-
files =
|
|
181
|
+
files = glob(dir)
|
|
144
182
|
files << File.join(dir, "config.ru")
|
|
145
183
|
new(
|
|
146
184
|
type: :rails,
|
|
@@ -151,7 +189,8 @@ module Audition
|
|
|
151
189
|
environment: File.join(dir, "config", "environment.rb"),
|
|
152
190
|
root: dir
|
|
153
191
|
},
|
|
154
|
-
compiled_files: compiled(dir)
|
|
192
|
+
compiled_files: compiled(dir),
|
|
193
|
+
stub_files: stubs(dir)
|
|
155
194
|
)
|
|
156
195
|
end
|
|
157
196
|
|
|
@@ -160,24 +199,31 @@ module Audition
|
|
|
160
199
|
type: :rack,
|
|
161
200
|
root: dir,
|
|
162
201
|
ruby_files: [config_ru] + glob(dir),
|
|
163
|
-
entry: {mode: :rack, config_ru: config_ru},
|
|
164
|
-
compiled_files: compiled(dir)
|
|
202
|
+
entry: {mode: :rack, config_ru: config_ru, root: dir},
|
|
203
|
+
compiled_files: compiled(dir),
|
|
204
|
+
stub_files: stubs(dir)
|
|
165
205
|
)
|
|
166
206
|
end
|
|
167
207
|
|
|
208
|
+
# Reading require_paths off the gemspec would mean evaluating
|
|
209
|
+
# it, which the static pass never does; `lib` is the packaging
|
|
210
|
+
# default, and a gem that keeps its code elsewhere falls back to
|
|
211
|
+
# the whole checkout rather than scanning nothing.
|
|
168
212
|
def self.gem_dir_target(dir, gemspec)
|
|
169
213
|
lib = File.join(dir, "lib")
|
|
214
|
+
paths = File.directory?(lib) ? [lib] : [dir]
|
|
170
215
|
new(
|
|
171
216
|
type: :gem,
|
|
172
217
|
root: dir,
|
|
173
|
-
ruby_files: glob(
|
|
218
|
+
ruby_files: paths.flat_map { |path| glob(path) },
|
|
174
219
|
entry: {
|
|
175
220
|
mode: :require,
|
|
176
221
|
feature: File.basename(gemspec, ".gemspec"),
|
|
177
|
-
load_paths:
|
|
222
|
+
load_paths: paths,
|
|
178
223
|
root: dir
|
|
179
224
|
},
|
|
180
|
-
compiled_files: compiled(dir)
|
|
225
|
+
compiled_files: compiled(dir),
|
|
226
|
+
stub_files: stubs(dir)
|
|
181
227
|
)
|
|
182
228
|
end
|
|
183
229
|
|
|
@@ -199,6 +245,14 @@ module Audition
|
|
|
199
245
|
end.sort
|
|
200
246
|
end
|
|
201
247
|
|
|
248
|
+
# Generated type stubs anywhere under a directory.
|
|
249
|
+
#
|
|
250
|
+
# @param dir [String]
|
|
251
|
+
# @return [Array<String>]
|
|
252
|
+
def self.stubs(dir)
|
|
253
|
+
glob(dir, STUBS)
|
|
254
|
+
end
|
|
255
|
+
|
|
202
256
|
# macOS debug-symbol bundles (x.bundle.dSYM/...) carry a file
|
|
203
257
|
# with the extension's name that nothing ever loads.
|
|
204
258
|
def self.compiled(dir)
|
|
@@ -222,15 +276,30 @@ module Audition
|
|
|
222
276
|
|
|
223
277
|
private_class_method :from_file, :from_directory, :from_gem_name,
|
|
224
278
|
:rails_target, :rack_target, :gem_dir_target,
|
|
225
|
-
:glob, :compiled, :normalize
|
|
279
|
+
:rails_member?, :glob, :compiled, :normalize
|
|
226
280
|
|
|
227
281
|
def initialize(type:, root:, ruby_files:, entry:,
|
|
228
|
-
compiled_files: [])
|
|
282
|
+
compiled_files: [], stub_files: [])
|
|
229
283
|
@type = type
|
|
230
284
|
@root = root
|
|
231
285
|
@ruby_files = ruby_files
|
|
232
286
|
@entry = entry
|
|
233
287
|
@compiled_files = compiled_files
|
|
288
|
+
@stub_files = stub_files
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Whether a path is test/spec code rather than code the
|
|
292
|
+
# production boot loads.
|
|
293
|
+
#
|
|
294
|
+
# @param path [String] absolute or root-relative
|
|
295
|
+
# @param dirs [Array<String>] directory names that hold tests
|
|
296
|
+
# @return [Boolean]
|
|
297
|
+
def test_file?(path, dirs: TEST_DIRS)
|
|
298
|
+
relative = File.expand_path(path, root)
|
|
299
|
+
.delete_prefix("#{root}/")
|
|
300
|
+
parts = relative.split("/")
|
|
301
|
+
parts[0..-2].any? { |p| dirs.include?(p) } ||
|
|
302
|
+
parts.last.to_s.end_with?("_test.rb", "_spec.rb")
|
|
234
303
|
end
|
|
235
304
|
end
|
|
236
305
|
end
|
data/lib/audition/version.rb
CHANGED
data/lib/audition.rb
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "audition/version"
|
|
4
|
+
require_relative "audition/progress"
|
|
4
5
|
require_relative "audition/finding"
|
|
5
6
|
require_relative "audition/target"
|
|
6
7
|
require_relative "audition/static/source_file"
|
|
7
8
|
require_relative "audition/static/literal_classifier"
|
|
8
9
|
require_relative "audition/static/checks"
|
|
9
10
|
require_relative "audition/static/graph_audit"
|
|
11
|
+
require_relative "audition/static/gem_calls"
|
|
10
12
|
require_relative "audition/static/native_extensions"
|
|
11
13
|
require_relative "audition/static/analyzer"
|
|
12
14
|
require_relative "audition/dynamic/prober"
|
|
15
|
+
require_relative "audition/reconciliation"
|
|
13
16
|
require_relative "audition/report"
|
|
14
17
|
require_relative "audition/config"
|
|
15
18
|
require_relative "audition/baseline"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: audition
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Markin
|
|
@@ -43,14 +43,14 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0.
|
|
46
|
+
version: '0.4'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0.
|
|
53
|
+
version: '0.4'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: table_tennis
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -102,24 +102,32 @@ files:
|
|
|
102
102
|
- lib/audition/dynamic/prober.rb
|
|
103
103
|
- lib/audition/finding.rb
|
|
104
104
|
- lib/audition/fixer.rb
|
|
105
|
+
- lib/audition/progress.rb
|
|
106
|
+
- lib/audition/reconciliation.rb
|
|
105
107
|
- lib/audition/report.rb
|
|
106
108
|
- lib/audition/report/github.rb
|
|
107
109
|
- lib/audition/report/json.rb
|
|
108
110
|
- lib/audition/report/style.rb
|
|
111
|
+
- lib/audition/report/sweep.rb
|
|
109
112
|
- lib/audition/report/text.rb
|
|
110
113
|
- lib/audition/rewriters.rb
|
|
111
114
|
- lib/audition/static/analyzer.rb
|
|
112
115
|
- lib/audition/static/checks.rb
|
|
113
116
|
- lib/audition/static/checks/base.rb
|
|
117
|
+
- lib/audition/static/checks/dependency_class_state.rb
|
|
114
118
|
- lib/audition/static/checks/global_variables.rb
|
|
119
|
+
- lib/audition/static/checks/instance_memoization.rb
|
|
115
120
|
- lib/audition/static/checks/mutable_constants.rb
|
|
116
121
|
- lib/audition/static/checks/ractor_isolation.rb
|
|
117
122
|
- lib/audition/static/checks/runtime_require.rb
|
|
118
123
|
- lib/audition/static/checks/unsafe_calls.rb
|
|
124
|
+
- lib/audition/static/checks/unshareable_reads.rb
|
|
125
|
+
- lib/audition/static/gem_calls.rb
|
|
119
126
|
- lib/audition/static/graph_audit.rb
|
|
120
127
|
- lib/audition/static/literal_classifier.rb
|
|
121
128
|
- lib/audition/static/native_extensions.rb
|
|
122
129
|
- lib/audition/static/source_file.rb
|
|
130
|
+
- lib/audition/static/work_split.rb
|
|
123
131
|
- lib/audition/target.rb
|
|
124
132
|
- lib/audition/version.rb
|
|
125
133
|
homepage: https://github.com/yaroslav/audition
|
|
@@ -142,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
142
150
|
- !ruby/object:Gem::Version
|
|
143
151
|
version: '0'
|
|
144
152
|
requirements: []
|
|
145
|
-
rubygems_version: 4.0.
|
|
153
|
+
rubygems_version: 4.0.20
|
|
146
154
|
specification_version: 4
|
|
147
155
|
summary: Probe Ruby code for Ractor-readiness
|
|
148
156
|
test_files: []
|