Class: Reviewer::Runner::Result
- Inherits:
-
Struct
- Object
- Struct
- Reviewer::Runner::Result
- Defined in:
- lib/reviewer/runner/result.rb
Overview
Immutable value object representing the result of running a single tool
Constant Summary collapse
- STATES =
i[passed failed skipped missing not_run].freeze
Instance Attribute Summary collapse
-
#command_string ⇒ String?
readonly
The full command string that was executed.
-
#command_type ⇒ Symbol
readonly
The type of command run (:review, :format, etc.).
-
#duration ⇒ Float?
readonly
The execution time in seconds.
-
#exit_status ⇒ Integer?
readonly
The exit status code from the command.
-
#missing ⇒ Boolean?
readonly
Compatibility value that is true only for missing results.
-
#skipped ⇒ Boolean?
readonly
Compatibility value that is true only for skipped results.
-
#state ⇒ Symbol
readonly
The canonical result state (:passed, :failed, :skipped, :missing, :not_run).
-
#stderr ⇒ String?
readonly
The standard error from the command.
-
#stdout ⇒ String?
readonly
The standard output from the command.
-
#success ⇒ Boolean
readonly
Compatibility value derived from state; true only for passed results.
-
#summary_label ⇒ Object
Returns the value of attribute summary_label.
-
#summary_pattern ⇒ Object
Returns the value of attribute summary_pattern.
-
#tool_key ⇒ Symbol
readonly
The unique identifier for the tool.
-
#tool_name ⇒ String
readonly
The human-readable name of the tool.
Class Method Summary collapse
-
.from_runner(runner) ⇒ Result
Builds an immutable Result from a runner's current state.
-
.not_run(tool:, command_type:) ⇒ Result
Builds a result for a tool that fail-fast prevented from running.
Instance Method Summary collapse
-
#detail_summary ⇒ String?
Extracts a short summary detail from stdout for display purposes.
-
#executed? ⇒ Boolean
Whether this result represents a tool that actually ran.
- #failed? ⇒ Boolean
-
#initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) ⇒ Result
constructor
Freeze on initialization to maintain immutability like Data.define.
- #missing? ⇒ Boolean
- #not_run? ⇒ Boolean
- #passed? ⇒ Boolean
- #skipped? ⇒ Boolean
- #success? ⇒ Boolean
-
#to_h ⇒ Hash
Converts the result to a hash suitable for serialization.
Constructor Details
#initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) ⇒ Result
Freeze on initialization to maintain immutability like Data.define
49 50 51 52 53 54 55 56 57 58 |
# File 'lib/reviewer/runner/result.rb', line 49 def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end |
Instance Attribute Details
#command_string ⇒ String? (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
#command_type ⇒ Symbol (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
#duration ⇒ Float? (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
#exit_status ⇒ Integer? (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
#missing ⇒ Boolean? (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
#skipped ⇒ Boolean? (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
#state ⇒ Symbol (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
#stderr ⇒ String? (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
#stdout ⇒ String? (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
#success ⇒ Boolean (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
#summary_label ⇒ Object
Returns the value of attribute summary_label
31 32 33 |
# File 'lib/reviewer/runner/result.rb', line 31 def summary_label @summary_label end |
#summary_pattern ⇒ Object
Returns the value of attribute summary_pattern
31 32 33 |
# File 'lib/reviewer/runner/result.rb', line 31 def summary_pattern @summary_pattern end |
#tool_key ⇒ Symbol (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
#tool_name ⇒ String (readonly)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/reviewer/runner/result.rb', line 31 Result = Struct.new( :tool_key, :tool_name, :command_type, :command_string, :success, :exit_status, :duration, :stdout, :stderr, :skipped, :missing, :summary_pattern, :summary_label, :state, keyword_init: true ) do # Freeze on initialization to maintain immutability like Data.define def initialize(state: nil, success: nil, skipped: nil, missing: nil, **attributes) state ||= [[:skipped, skipped], [:missing, missing], [:passed, success == true], [:failed, success == false]] .find(&:last)&.first raise ArgumentError, "Unknown result state: #{state.inspect}" unless Result::STATES.include?(state) super(**attributes, state: state, success: success, skipped: skipped, missing: missing) validate_legacy_values normalize_legacy_values freeze end # Builds an immutable Result from a runner's current state. # @param runner [Runner] the runner after command execution # # @return [Result] an immutable result for reporting def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end # Builds a result for a tool that fail-fast prevented from running. # @param tool [Tool] the selected tool that did not run # @param command_type [Symbol] the requested command type # # @return [Result] an immutable not-run result def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.base_attributes(runner) tool = runner.tool { tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: runner.command.string } end def self.build_skipped(runner) tool = runner.tool new( tool_key: tool.key, tool_name: tool.name, command_type: runner.command.type, command_string: nil, state: :skipped, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end def self.build_missing(runner) new( **base_attributes(runner), state: :missing, exit_status: runner.shell.result.exit_status, duration: 0, stdout: nil, stderr: nil ) end def self.build_executed(runner) shell = runner.shell shell_result = shell.result settings = runner.tool.settings new( **base_attributes(runner), state: runner.success? ? :passed : :failed, exit_status: shell_result.exit_status, duration: shell.timer.total_seconds, stdout: shell_result.stdout, stderr: shell_result.stderr, summary_pattern: settings.summary_pattern, summary_label: settings.summary_label ) end private_class_method :base_attributes, :build_skipped, :build_missing, :build_executed def passed? = state?(:passed) def failed? = state?(:failed) def not_run? = state?(:not_run) def success? = state?(:passed) def skipped? = state?(:skipped) def missing? = state?(:missing) def state?(value) = state == value private :state? # Whether this result represents a tool that actually ran # # @return [Boolean] true if the tool was executed def executed? = passed? || failed? # Extracts a short summary detail from stdout for display purposes. # Each tool type may have its own summary format (test count, offense count, etc.) # # @return [String, nil] a brief summary or nil if no detail can be extracted def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end # Converts the result to a hash suitable for serialization # # @return [Hash] serialized result; skipped and not-run execution fields remain explicit nils def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end private def validate_legacy_values conflicting_flags = [[self[:skipped], :skipped], [self[:missing], :missing]] .select(&:first).map(&:last).any? { |value| value != state } conflicting_success = { true => :failed, false => :passed }.fetch(self[:success]) { nil } == state return unless conflicting_flags || conflicting_success raise ArgumentError, 'Result state conflicts with legacy values' end # Assigns unconditionally so the stored members are a pure function of # state. A partial assignment left the caller's argument in place, so two # results with identical states compared unequal through Struct#==. def normalize_legacy_values self[:success] = passed? self[:skipped] = (true if skipped?) self[:missing] = (true if missing?) end def serialized_attributes { tool: tool_key, name: tool_name, command_type: command_type, command: command_string, state: state, success: success, exit_status: exit_status, duration: duration, stdout: stdout, stderr: stderr, skipped: skipped, missing: missing, detail_summary: detail_summary } end end |
Class Method Details
.from_runner(runner) ⇒ Result
Builds an immutable Result from a runner's current state.
64 65 66 67 68 69 70 71 72 |
# File 'lib/reviewer/runner/result.rb', line 64 def self.from_runner(runner) if runner.skipped? build_skipped(runner) elsif runner.missing? build_missing(runner) else build_executed(runner) end end |
.not_run(tool:, command_type:) ⇒ Result
Builds a result for a tool that fail-fast prevented from running.
79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/reviewer/runner/result.rb', line 79 def self.not_run(tool:, command_type:) new( tool_key: tool.key, tool_name: tool.name, command_type: command_type, command_string: nil, state: :not_run, exit_status: nil, duration: nil, stdout: nil, stderr: nil ) end |
Instance Method Details
#detail_summary ⇒ String?
Extracts a short summary detail from stdout for display purposes. Each tool type may have its own summary format (test count, offense count, etc.)
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/reviewer/runner/result.rb', line 160 def detail_summary return nil unless summary_pattern && summary_label match = stdout&.match(/#{summary_pattern}/i) return nil unless match summary_label.gsub(/\\(\d+)/) do capture = match[Regexp.last_match(1).to_i] return nil unless capture capture end rescue RegexpError nil end |
#executed? ⇒ Boolean
Whether this result represents a tool that actually ran
154 |
# File 'lib/reviewer/runner/result.rb', line 154 def executed? = passed? || failed? |
#failed? ⇒ Boolean
142 |
# File 'lib/reviewer/runner/result.rb', line 142 def failed? = state?(:failed) |
#missing? ⇒ Boolean
146 |
# File 'lib/reviewer/runner/result.rb', line 146 def missing? = state?(:missing) |
#not_run? ⇒ Boolean
143 |
# File 'lib/reviewer/runner/result.rb', line 143 def not_run? = state?(:not_run) |
#passed? ⇒ Boolean
141 |
# File 'lib/reviewer/runner/result.rb', line 141 def passed? = state?(:passed) |
#skipped? ⇒ Boolean
145 |
# File 'lib/reviewer/runner/result.rb', line 145 def skipped? = state?(:skipped) |
#success? ⇒ Boolean
144 |
# File 'lib/reviewer/runner/result.rb', line 144 def success? = state?(:passed) |
#to_h ⇒ Hash
Converts the result to a hash suitable for serialization
179 180 181 182 183 184 185 |
# File 'lib/reviewer/runner/result.rb', line 179 def to_h attributes = serialized_attributes return attributes.compact unless skipped? || not_run? attributes.compact.merge(attributes.slice(:command, :exit_status, :duration, :stdout, :stderr)) end |