Module: Verity::Fingerprint
- Defined in:
- lib/verity/fingerprint.rb
Overview
Public: Content-addressed fingerprinting for test bodies. Parses source files with Prism to produce stable identifiers that survive line-number shifts when unrelated code changes, while disambiguating tests with identical bodies by appending the line number.
Constant Summary collapse
- HEX_LENGTH =
16- THREAD_KEY =
:__verity_fp_plan__
Class Method Summary collapse
-
.clear_plan! ⇒ Object
Internal: Remove the current thread’s fingerprint plan.
- .derive_method_suffix(fingerprint) ⇒ Object
-
.fallback_fingerprint(file, line) ⇒ Object
Public: Generate a location-based fingerprint when the Prism plan does not cover a given line (e.g. dynamically generated tests).
-
.install_plan!(absolute_path) ⇒ Object
Internal: Parse the given source file and install its line-to-fingerprint mapping on the current thread.
-
.lookup(line) ⇒ Object
Internal: Look up the fingerprint for a source line from the current thread’s installed plan.
-
.plan_file(absolute_path) ⇒ Object
Internal: Parse a source file and build a Hash mapping each ‘test` call’s line number to a content-addressed fingerprint string.
Class Method Details
.clear_plan! ⇒ Object
Internal: Remove the current thread’s fingerprint plan. Called after a test file finishes loading.
Returns nil.
32 33 34 |
# File 'lib/verity/fingerprint.rb', line 32 def clear_plan! Thread.current[THREAD_KEY] = nil end |
.derive_method_suffix(fingerprint) ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/verity/fingerprint.rb', line 98 def derive_method_suffix(fingerprint) parts = fingerprint.split(":") hex = if parts.size >= 3 && parts.last.match?(/\A\d+\z/) parts[-2] else parts[-1] end raise ArgumentError, "invalid fingerprint (expected ...:#{HEX_LENGTH} hex chars): #{fingerprint}" unless /\A[a-f0-9]{#{HEX_LENGTH}}\z/.match?(hex) hex end |
.fallback_fingerprint(file, line) ⇒ Object
Public: Generate a location-based fingerprint when the Prism plan does not cover a given line (e.g. dynamically generated tests).
file - String file path. line - Integer line number.
Returns a String in the form “relative/path:hex”.
54 55 56 57 58 |
# File 'lib/verity/fingerprint.rb', line 54 def fallback_fingerprint(file, line) rel = relative_source_path(file) sha = Digest::SHA1.hexdigest("#{file}:#{line}")[0, HEX_LENGTH] "#{rel}:#{sha}" end |
.install_plan!(absolute_path) ⇒ Object
Internal: Parse the given source file and install its line-to-fingerprint mapping on the current thread. Must be called before loading a test file.
absolute_path - String absolute filesystem path to the source file.
Returns the plan Hash (line => fingerprint).
24 25 26 |
# File 'lib/verity/fingerprint.rb', line 24 def install_plan!(absolute_path) Thread.current[THREAD_KEY] = plan_file(absolute_path) end |
.lookup(line) ⇒ Object
Internal: Look up the fingerprint for a source line from the current thread’s installed plan.
line - Integer source line number.
Returns a String fingerprint, or nil if no plan is active or the line has no entry.
43 44 45 |
# File 'lib/verity/fingerprint.rb', line 43 def lookup(line) Thread.current[THREAD_KEY]&.[](line) end |
.plan_file(absolute_path) ⇒ Object
Internal: Parse a source file and build a Hash mapping each ‘test` call’s line number to a content-addressed fingerprint string. Duplicate body hashes within the same file are disambiguated by appending the line number.
absolute_path - String absolute path to the Ruby source file.
Returns a Hash { Integer => String }.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/verity/fingerprint.rb', line 68 def plan_file(absolute_path) source = File.read(absolute_path, encoding: "UTF-8") result = Prism.parse(source, filepath: File.(absolute_path)) return {} unless result.success? program = result.value rows = [] each_load_time_test(program) do |call| body = call.block.body canon = canonical(body) body_hex = Digest::SHA1.hexdigest(canon)[0, HEX_LENGTH] rows << { line: call.location.start_line, body_hex: body_hex } end relative = relative_source_path(absolute_path) by_hex = rows.group_by { _1[:body_hex] } plan = {} rows.each do |row| line = row[:line] body_hex = row[:body_hex] plan[line] = if by_hex[body_hex].length > 1 "#{relative}:#{body_hex}:#{line}" else "#{relative}:#{body_hex}" end end plan end |