raider_reporter 0.5.1 → 0.6.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: 43cb5da1df209da0c5607c26891e58a868982311569e8f844d80b87a46943f14
4
- data.tar.gz: 82099d1f6c53736ef7f9d075207887581e6280af14a4d0335008ef54712ee48b
3
+ metadata.gz: e5bb1cd462fbac0bd6a44635be2b0e14f6da29667e6cb4cf0a5f358aed7581be
4
+ data.tar.gz: c814a3009d7e7e6ac0857bc34636ad1fdc913d3e3489ff64c3e5b62cd7f50dbb
5
5
  SHA512:
6
- metadata.gz: 276522de77789af81f1534ca8980cd3e168915963c2dd52b1863d2aa7ca4f5bfb54d3fecd018498931dcc45d58905adc331388ba26d152797e0800e421ab9688
7
- data.tar.gz: b980514dc7c2a51d08cb89de5caf549a648406cbce998159d706d4397735c1eb1af37221eaf354d4e383104facf1025917b4ce12d21fc1937b68c38964960b61
6
+ metadata.gz: a1064490905e372f3392d056343cc3b8304d1de127e785d122e5ef6e3c50622873583f7ceedece6f87bbf633d8fbe4e2b94a0111f3d325ff98ba6280ba84e75c
7
+ data.tar.gz: a91849038df0a28f54023e411d4115636ce97e1aa6a4ad83ec03a298f7f87e438e0b94583a6248e568c8c14af9c9176e813ddb0b287962e5ebd2021737a8748d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 — 2026-10-14
4
+
5
+ - Schema v1, additive: optional `failure.app_frame`, the first line of the raw exception backtrace that lies inside the project root (`Dir.pwd` when the reporter starts), outside `vendor/`, `.bundle/`, `Gem.path` and Ruby's library directory, and not `<internal:...>`. The path is made project-relative and the rest of the line is unchanged, e.g. `spec/pages/login_page.rb:42:in 'LoginPage#submit'`; at most 500 characters. The key is omitted when no line qualifies or computing it fails, so payloads without it are unchanged. Raider uses it to group failures by error
6
+ - Reported by both the RSpec and Cucumber reporters
7
+ - No breaking changes
8
+
3
9
  ## 0.5.1 — 2026-10-13
4
10
 
5
11
  - Fix: a failed upload now reports one short line. An HTML error page (for example a server 500) is summarised as `the server returned an HTML error page` instead of being printed, other bodies are collapsed to one line of at most 200 characters, and the token is redacted from upload errors, including exception messages
data/README.md CHANGED
@@ -305,7 +305,7 @@ Tip: run `rm -rf raider-results` before a parallel run. Each process only cleans
305
305
 
306
306
  ## Schema
307
307
 
308
- `run.json` follows schema v1, documented in [docs/schema-v1.md](docs/schema-v1.md). Statuses are exactly `passed`, `failed`, `pending` and `skipped`.
308
+ `run.json` follows schema v1, documented in [docs/schema-v1.md](docs/schema-v1.md). Statuses are exactly `passed`, `failed`, `pending` and `skipped`. In both RSpec and Cucumber, a failure may carry `app_frame` (when a project frame is found): the first backtrace line inside your project, for example `spec/pages/login_page.rb:42:in 'LoginPage#submit'`, which Raider uses to group failures with the same error.
309
309
 
310
310
  ## Contributing
311
311
 
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
5
+ module RaiderReporter
6
+ # Finds the first project frame of a raw backtrace, so the server can group
7
+ # failures by where the project's own code raised them.
8
+ module AppFrame
9
+ MAX_LENGTH = 500
10
+
11
+ # Returns "relative/path.rb:42:in 'Class#method'" or nil when no line
12
+ # of the backtrace lies in project code.
13
+ def self.from(backtrace, root:)
14
+ return nil if backtrace.nil? || backtrace.empty? || root.nil?
15
+
16
+ base = "#{File.expand_path(root.to_s).chomp("/")}/"
17
+ excluded = excluded_prefixes
18
+ Array(backtrace).each do |line|
19
+ line = line.to_s
20
+ path = line[/\A(.+?):\d+/, 1]
21
+ next if path.nil? || path.start_with?("<internal:", "(", "-")
22
+
23
+ absolute = File.expand_path(path, base)
24
+ next unless File.file?(absolute)
25
+
26
+ absolute = inside_root(absolute, base)
27
+ next unless absolute
28
+ next if excluded.any? { |prefix| absolute.start_with?(prefix) }
29
+
30
+ relative = absolute.delete_prefix(base)
31
+ next if relative.start_with?("vendor/", ".bundle/") || relative.include?("/.bundle/")
32
+
33
+ return Text.utf8("#{relative}#{line[path.length..]}")[0, MAX_LENGTH]
34
+ end
35
+ nil
36
+ end
37
+
38
+ # Returns the path (rewritten under base when only the real paths match,
39
+ # e.g. a symlinked tmpdir) or nil when it lies outside the root.
40
+ def self.inside_root(absolute, base)
41
+ return absolute if absolute.start_with?(base)
42
+
43
+ real_base = "#{File.realpath(base)}/"
44
+ real = File.realpath(absolute)
45
+ real.start_with?(real_base) ? "#{base}#{real.delete_prefix(real_base)}" : nil
46
+ rescue SystemCallError
47
+ nil
48
+ end
49
+ private_class_method :inside_root
50
+
51
+ def self.excluded_prefixes
52
+ dirs = Array(Gem.path) + [RbConfig::CONFIG["rubylibdir"]]
53
+ dirs.compact.map(&:to_s).reject(&:empty?).map { |dir| "#{File.expand_path(dir).chomp("/")}/" }
54
+ end
55
+ private_class_method :excluded_prefixes
56
+ end
57
+ end
@@ -199,10 +199,17 @@ module RaiderReporter
199
199
  Failure.new(
200
200
  message: Text.utf8("#{step_label(step_info, test_case)}\n\n#{exception.message}")[0, Text::MAX_MESSAGE],
201
201
  exception_class: Text.utf8(exception.class.name),
202
- backtrace: (exception.backtrace || []).first(Text::MAX_BACKTRACE).map { |line| Text.utf8(line) }
202
+ backtrace: (exception.backtrace || []).first(Text::MAX_BACKTRACE).map { |line| Text.utf8(line) },
203
+ app_frame: app_frame_for(exception)
203
204
  )
204
205
  end
205
206
 
207
+ def app_frame_for(exception)
208
+ AppFrame.from(exception.backtrace, root: RaiderReporter.project_root || Dir.pwd)
209
+ rescue StandardError
210
+ nil
211
+ end
212
+
206
213
  def step_label(step_info, test_case)
207
214
  return "Step" unless step_info
208
215
 
@@ -4,7 +4,11 @@ module RaiderReporter
4
4
  SCHEMA_VERSION = 1
5
5
  STATUSES = %w[passed failed pending skipped].freeze
6
6
 
7
- Failure = Data.define(:message, :exception_class, :backtrace)
7
+ Failure = Data.define(:message, :exception_class, :backtrace, :app_frame) do
8
+ def initialize(message:, exception_class:, backtrace:, app_frame: nil)
9
+ super
10
+ end
11
+ end
8
12
  Attachment = Data.define(:name, :source_path, :content_type)
9
13
  TestResult = Data.define(
10
14
  :identifier, :full_name, :file_path, :line_number, :status,
@@ -73,11 +73,13 @@ module RaiderReporter
73
73
  end
74
74
 
75
75
  def failure_hash(failure)
76
- {
76
+ hash = {
77
77
  "message" => failure.message,
78
78
  "exception_class" => failure.exception_class,
79
79
  "backtrace" => failure.backtrace
80
80
  }
81
+ hash["app_frame"] = failure.app_frame if failure.app_frame
82
+ hash
81
83
  end
82
84
 
83
85
  def attachment_hash(entry, mode)
@@ -74,10 +74,26 @@ module RaiderReporter
74
74
  Failure.new(
75
75
  message: utf8(exception.message)[0, MAX_MESSAGE],
76
76
  exception_class: utf8(exception.class.name),
77
- backtrace: backtrace.first(MAX_BACKTRACE).map { |line| utf8(line) }
77
+ backtrace: backtrace.first(MAX_BACKTRACE).map { |line| utf8(line) },
78
+ app_frame: app_frame_for(exception)
78
79
  )
79
80
  end
80
81
 
82
+ # From the raw backtrace: RSpec's formatter strips gem and project-support frames.
83
+ # MultipleExceptionError (aggregate_failures) has no backtrace of its own.
84
+ def raw_backtrace(exception)
85
+ return exception.backtrace if exception.backtrace
86
+ return nil unless exception.respond_to?(:all_exceptions)
87
+
88
+ exception.all_exceptions.first&.backtrace
89
+ end
90
+
91
+ def app_frame_for(exception)
92
+ AppFrame.from(raw_backtrace(exception), root: RaiderReporter.project_root || Dir.pwd)
93
+ rescue StandardError
94
+ nil
95
+ end
96
+
81
97
  def utf8(value)
82
98
  Text.utf8(value)
83
99
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RaiderReporter
4
- VERSION = "0.5.1"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -5,6 +5,7 @@ require "rbconfig"
5
5
  require_relative "raider_reporter/version"
6
6
  require_relative "raider_reporter/text"
7
7
  require_relative "raider_reporter/models"
8
+ require_relative "raider_reporter/app_frame"
8
9
  require_relative "raider_reporter/payload"
9
10
  require_relative "raider_reporter/configuration"
10
11
  require_relative "raider_reporter/content_types"
@@ -19,7 +20,7 @@ require_relative "raider_reporter/comment/azure_client"
19
20
 
20
21
  module RaiderReporter
21
22
  class << self
22
- attr_reader :collector
23
+ attr_reader :collector, :project_root
23
24
 
24
25
  def configuration
25
26
  @configuration ||= Configuration.new
@@ -37,10 +38,12 @@ module RaiderReporter
37
38
  @collector&.cleanup
38
39
  @configuration = nil
39
40
  @collector = nil
41
+ @project_root = nil
40
42
  end
41
43
 
42
44
  def start_collector!
43
45
  @dry_run = false
46
+ @project_root = Dir.pwd
44
47
  @collector = Collector.new(
45
48
  max_attachment_bytes: configuration.max_attachment_bytes,
46
49
  warn: ->(message) { warn(message) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raider_reporter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Augustin Gottlieb
@@ -24,6 +24,7 @@ files:
24
24
  - README.md
25
25
  - exe/raider-reporter
26
26
  - lib/raider_reporter.rb
27
+ - lib/raider_reporter/app_frame.rb
27
28
  - lib/raider_reporter/azure_env.rb
28
29
  - lib/raider_reporter/ci_detector.rb
29
30
  - lib/raider_reporter/cli.rb