Module: Polyrun::Reporting::Junit
- Defined in:
- lib/polyrun/reporting/junit.rb,
lib/polyrun/reporting/junit_emit.rb
Overview
JUnit XML for CI (replaces rspec_junit_formatter) — stdlib only.
Input JSON may be:
-
**RSpec JSON** — output of rspec –format json –out rspec.json (
examplesarray). -
**Polyrun canonical** — { “name” => “…”, “testcases” => [ … ] } (see
emit_xml).
Each testcase hash supports: classname, name, time, status (passed, failed, pending/skipped), and optional failure => { “message” => “…”, “body” => “…” }.
Class Method Summary collapse
-
.emit_xml(doc) ⇒ Object
docis { “name”, “hostname”, “testcases” => [ … ] }. - .esc(s) ⇒ Object
- .format_float(x) ⇒ Object
- .from_polyrun_hash(data) ⇒ Object
- .from_rspec_json(data) ⇒ Object
- .hostname ⇒ Object
- .junit_rspec_example_to_case(ex) ⇒ Object
- .junit_rspec_failure_hash(ex) ⇒ Object
- .junit_xml_failure_body(c) ⇒ Object
- .junit_xml_header(doc, cases) ⇒ Object
- .junit_xml_testcase_line(c) ⇒ Object
-
.merge_rspec_json_files(paths, output_path:) ⇒ Object
Merge several RSpec JSON outputs (parallel shards) by concatenating
examples. - .parse_input(data) ⇒ Object
- .status_of(c) ⇒ Object
- .write_from_hash(data, output_path:) ⇒ Object
- .write_from_json_file(json_path, output_path:) ⇒ Object
Class Method Details
.emit_xml(doc) ⇒ Object
doc is { “name”, “hostname”, “testcases” => [ … ] }
7 8 9 10 11 12 13 14 15 16 17 |
# File 'lib/polyrun/reporting/junit_emit.rb', line 7 def emit_xml(doc) cases = doc["testcases"] || [] lines = [] lines << junit_xml_header(doc, cases) cases.each do |c| lines << junit_xml_testcase_line(c) end lines << %(</testsuite>) lines << %(</testsuites>) lines.join("\n") + "\n" end |
.esc(s) ⇒ Object
118 119 120 |
# File 'lib/polyrun/reporting/junit.rb', line 118 def esc(s) CGI.escapeHTML(s.to_s) end |
.format_float(x) ⇒ Object
114 115 116 |
# File 'lib/polyrun/reporting/junit.rb', line 114 def format_float(x) format("%.6f", x.to_f) end |
.from_polyrun_hash(data) ⇒ Object
92 93 94 95 96 97 98 |
# File 'lib/polyrun/reporting/junit.rb', line 92 def from_polyrun_hash(data) { "name" => (data["name"] || data["testsuite_name"] || "tests").to_s, "hostname" => (data["hostname"] || hostname).to_s, "testcases" => Array(data["testcases"]) } end |
.from_rspec_json(data) ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/polyrun/reporting/junit.rb', line 53 def from_rspec_json(data) cases = [] data["examples"].each do |ex| next unless ex.is_a?(Hash) cases << junit_rspec_example_to_case(ex) end name = (data.dig("summary", "summary_line") || data["name"] || "RSpec").to_s from_polyrun_hash("name" => name, "hostname" => hostname, "testcases" => cases) end |
.hostname ⇒ Object
100 101 102 103 104 105 |
# File 'lib/polyrun/reporting/junit.rb', line 100 def hostname require "socket" Socket.gethostname rescue "localhost" end |
.junit_rspec_example_to_case(ex) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/polyrun/reporting/junit.rb', line 65 def junit_rspec_example_to_case(ex) status = (ex["status"] || "unknown").to_s file = ex["file_path"].to_s.sub(%r{\A\./}, "") tc = { "classname" => file.empty? ? "rspec" : file, "name" => (ex["full_description"] || ex["description"] || ex["id"]).to_s, "time" => (ex["run_time"] || ex["time"] || 0).to_f, "status" => status } if status == "failed" tc["failure"] = junit_rspec_failure_hash(ex) end tc end |
.junit_rspec_failure_hash(ex) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/polyrun/reporting/junit.rb', line 80 def junit_rspec_failure_hash(ex) e = ex["exception"] if e.is_a?(Hash) { "message" => e["message"].to_s, "body" => Array(e["backtrace"]).join("\n") } else {"message" => "failed", "body" => ex.inspect} end end |
.junit_xml_failure_body(c) ⇒ Object
49 50 51 52 53 54 55 |
# File 'lib/polyrun/reporting/junit_emit.rb', line 49 def junit_xml_failure_body(c) f = c["failure"] || {} fm = f["message"] || f[:message] || status_of(c) fb = f["body"] || f[:body] || "" tag = (status_of(c) == "error") ? "error" : "failure" %(<#{tag} message="#{esc(fm)}">#{esc(fb)}</#{tag}>) end |
.junit_xml_header(doc, cases) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/polyrun/reporting/junit_emit.rb', line 19 def junit_xml_header(doc, cases) total_time = cases.sum { |c| (c["time"] || c[:time] || 0).to_f } failures = cases.count { |c| status_of(c) == "failed" } errors = cases.count { |c| status_of(c) == "error" } skipped = cases.count { |c| %w[pending skipped].include?(status_of(c)) } tests = cases.size lines = [] lines << %(<?xml version="1.0" encoding="UTF-8"?>) lines << %(<testsuites name="#{esc(doc["name"])}">) lines << %(<testsuite name="#{esc(doc["name"])}" tests="#{tests}" failures="#{failures}" errors="#{errors}" skipped="#{skipped}" time="#{format_float(total_time)}" hostname="#{esc(doc["hostname"])}">) lines.join("\n") end |
.junit_xml_testcase_line(c) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/polyrun/reporting/junit_emit.rb', line 32 def junit_xml_testcase_line(c) c = c.transform_keys(&:to_s) classname = c["classname"].to_s name = c["name"].to_s time = (c["time"] || 0).to_f lines = [] lines << %(<testcase classname="#{esc(classname)}" name="#{esc(name)}" file="#{esc(c["file"] || classname)}" line="#{esc(c["line"] || "")}" time="#{format_float(time)}">) case status_of(c) when "failed", "error" lines << junit_xml_failure_body(c) when "pending", "skipped" lines << %(<skipped/>) end lines << %(</testcase>) lines.join("\n") end |
.merge_rspec_json_files(paths, output_path:) ⇒ Object
Merge several RSpec JSON outputs (parallel shards) by concatenating examples.
23 24 25 26 27 28 29 30 31 |
# File 'lib/polyrun/reporting/junit.rb', line 23 def merge_rspec_json_files(paths, output_path:) merged = {"examples" => []} paths.each do |p| data = JSON.parse(File.read(p)) merged["examples"].concat(data["examples"] || []) end merged["summary"] = {"summary_line" => "merged #{paths.size} RSpec JSON file(s)"} write_from_hash(merged, output_path: output_path) end |
.parse_input(data) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/polyrun/reporting/junit.rb', line 40 def parse_input(data) raise Polyrun::Error, "JUnit input must be a Hash" unless data.is_a?(Hash) if data["examples"].is_a?(Array) from_rspec_json(data) elsif data["testcases"].is_a?(Array) from_polyrun_hash(data) else raise Polyrun::Error, 'JUnit input: expected top-level "examples" (RSpec JSON) or "testcases" (Polyrun schema)' end end |
.status_of(c) ⇒ Object
107 108 109 110 111 112 |
# File 'lib/polyrun/reporting/junit.rb', line 107 def status_of(c) s = (c["status"] || c[:status] || "passed").to_s return "skipped" if s == "pending" s end |
.write_from_hash(data, output_path:) ⇒ Object
33 34 35 36 37 38 |
# File 'lib/polyrun/reporting/junit.rb', line 33 def write_from_hash(data, output_path:) doc = parse_input(data) xml = emit_xml(doc) File.write(output_path, xml) output_path end |
.write_from_json_file(json_path, output_path:) ⇒ Object
17 18 19 20 |
# File 'lib/polyrun/reporting/junit.rb', line 17 def write_from_json_file(json_path, output_path:) data = JSON.parse(File.read(json_path)) write_from_hash(data, output_path: output_path) end |