← Back to index

lib/BDD/Behave/Formatter/JsonEvents.rakumod

86 / 88 lines covered (97.7%)

1use BDD::Behave::Formatter;2use BDD::Behave::Failures;3use BDD::Behave::Failure;45unit class BDD::Behave::Formatter::JsonEvents does BDD::Behave::Formatter;67has Int $!failure-watermark = 0;89method name(--> Str) { 'json-events' }1011sub emit(%event --> Nil) {12  $*OUT.put(to-json-line(%event));13}1415sub to-json-line(%h --> Str) {16  json-value(%h);17}1819sub json-value($v --> Str) {20  given $v {21    when Nil      { 'null' }22    when Bool     { $v ?? 'true' !! 'false' }23    when Int      { $v.Str }24    when Real     { my $s = $v.Str; $s eq 'NaN' || $s eq 'Inf' || $s eq '-Inf' ?? 'null' !! $s }25    when Str      { json-string($v) }26    when Positional { '[' ~ $v.list.map(&json-value).join(',') ~ ']' }27    when Associative {28      # Sorting the keys rather than the rendered pairs keeps the order an29      # event is emitted in stable without comparing whole payloads.30      '{' ~ $v.keys.sort.map({ json-string(.Str) ~ ':' ~ json-value($v{$_}) }).join(',') ~ '}';31    }32    default { json-string($v.gist) }33  }34}3536sub json-string(Str $s --> Str) {37  my $out = $s;38  $out = $out.subst('\\', '\\\\', :g);39  $out = $out.subst('"',  '\\"',  :g);40  $out = $out.subst("\n", '\\n',  :g);41  $out = $out.subst("\r", '\\r',  :g);42  $out = $out.subst("\t", '\\t',  :g);43  # A control character other than the three with their own escape is rare, so44  # the pass that rewrites one runs only when the string holds one.45  $out = $out.subst(/<:Cc>/, { sprintf('\\u%04x', $/.Str.ord) }, :g) if $out ~~ /<:Cc>/;46  '"' ~ $out ~ '"';47}4849method node-id($node --> Str) {50  $node.file.absolute ~ ':' ~ $node.line;51}5253method suite-loading(Str :$file) {54  emit %( :type<suite-loading>, :$file );55}5657method suite-start($suite, Bool :$multi-file = False) {58  emit %(59    :type<suite-start>,60    :id(self.node-id($suite)),61    :description($suite.description),62    :file($suite.file.absolute),63    :line($suite.line),64    :$multi-file,65  );66}6768method suite-end($suite) {69  emit %(70    :type<suite-end>,71    :id(self.node-id($suite)),72  );73}7475method group-start($group) {76  emit %(77    :type<group-start>,78    :id(self.node-id($group)),79    :description($group.description),80    :file($group.file.absolute),81    :line($group.line),82  );83}8485method group-end($group) {86  emit %(87    :type<group-end>,88    :id(self.node-id($group)),89  );90}9192method group-around-skipped($group) {93  emit %(94    :type<group-around-skipped>,95    :id(self.node-id($group)),96  );97}9899method example-start($example, Bool :$auto = False) {100  emit %(101    :type<example-start>,102    :id(self.node-id($example)),103    :description($example.description),104    :file($example.file.absolute),105    :line($example.line),106    :$auto,107  );108}109110method example-auto-description($example, Str :$description) {111  emit %(112    :type<example-auto-description>,113    :id(self.node-id($example)),114    :$description,115  );116}117118method example-pass($example) {119  emit %(120    :type<example-pass>,121    :id(self.node-id($example)),122    :description($example.description),123    :file($example.file.absolute),124    :line($example.line),125    :duration($example.duration.defined ?? $example.duration.Real !! 0.0),126  );127}128129method example-fail($example, :$failure-info) {130  my %payload = (131    :type<example-fail>,132    :id(self.node-id($example)),133    :description($example.description),134    :file($example.file.absolute),135    :line($example.line),136    :duration($example.duration.defined ?? $example.duration.Real !! 0.0),137  );138  with $failure-info {139    %payload<failure-description> = ($failure-info<description> // '').Str;140    %payload<failure-file>        = ($failure-info<file> // '').Str;141    %payload<failure-line>        = ($failure-info<line> // 0).Int;142    with $failure-info<exception> {143      %payload<exception-message>   = .message;144      %payload<exception-backtrace> = .backtrace.full.Str;145    }146  }147  my $total = Failures.list.elems;148  my @new   = $total > $!failure-watermark149    ?? Failures.list[$!failure-watermark ..^ $total].grep(!*.from-runner-exception).List150    !! ().List;151  $!failure-watermark = $total;152  %payload<failures> = @new.map({153    my $given    = ($_.given // '').gist;154    my $expected = ($_.expected // '').gist;155    my $negated  = ?$_.negated;156157    %(158      :file($_.file // ''),159      :line($_.line // 0),160      :$given,161      :$expected,162      :message(($_.?message // Str).defined163        ?? $_.message164        !! compose-failure-message($given, $expected, $negated)),165      :aggregation-label(($_.?aggregation-label // Str).defined ?? $_.aggregation-label !! ''),166      :$negated,167    )168  }).List;169  emit %payload;170}171172method example-pending($example) {173  emit %(174    :type<example-pending>,175    :id(self.node-id($example)),176    :description($example.description),177    :file($example.file.absolute),178    :line($example.line),179  );180}181182method example-skipped($example) {183  emit %(184    :type<example-skipped>,185    :id(self.node-id($example)),186    :description($example.description),187    :file($example.file.absolute),188    :line($example.line),189  );190}191192method example-around-skipped($example) {193  emit %(194    :type<example-around-skipped>,195    :id(self.node-id($example)),196    :description($example.description),197    :file($example.file.absolute),198    :line($example.line),199  );200}201202method example-retry($example, Int :$attempt, Int :$max-attempts) {203  emit %(204    :type<example-retry>,205    :id(self.node-id($example)),206    :description($example.description),207    :file($example.file.absolute),208    :line($example.line),209    :attempt($attempt.Int),210    :max-attempts($max-attempts.Int),211  );212}213214method example-slow($example, Real :$threshold) {215  emit %(216    :type<example-slow>,217    :id(self.node-id($example)),218    :threshold($threshold.Real),219    :duration($example.duration.defined ?? $example.duration.Real !! 0.0),220  );221}222223method retry-summary(@records) {224  for @records -> $rec {225    emit %(226      :type<retry-record>,227      :description($rec.description.Str),228      :location($rec.location.Str),229      :attempts($rec.attempts.Int),230      :max-attempts($rec.max-attempts.Int),231      :outcome($rec.outcome.Str),232    );233  }234}235236method profile-summary(@records, Int :$limit) {237  return unless $limit > 0;238  for @records -> $rec {239    my $ex = $rec<example>;240    emit %(241      :type<profile-record>,242      :id($ex.defined ?? self.node-id($ex) !! ''),243      :description(($rec<description> // '').Str),244      :duration(($rec<duration> // 0).Real),245      :file($ex.defined ?? $ex.file.absolute !! ''),246      :line($ex.defined ?? $ex.line.Int !! 0),247    );248  }249}250251method benchmark-summary-section(252  @summaries, @regressions,253  Real     :$threshold,254  Str      :$format,255  IO::Path :$output,256  :$runner,257) {258  for @summaries -> %s {259    my $ex = %s<example>;260    emit %(261      :type<benchmark-record>,262      :id($ex.defined ?? self.node-id($ex) !! ''),263      :description((%s<description> // '').Str),264      :key((%s<key> // '').Str),265      :label(%s<label>.defined ?? %s<label>.Str !! ''),266      :position((%s<position> // 0).Int),267      :runs((%s<runs> // 0).Int),268      :iterations((%s<iterations> // 0).Int),269      :timings(%s<timings>.list.map(*.Real).List),270      :min((%s<min> // 0).Real),271      :max((%s<max> // 0).Real),272      :mean((%s<mean> // 0).Real),273      :median((%s<median> // 0).Real),274      :total((%s<total> // 0).Real),275      :file($ex.defined ?? $ex.file.absolute !! ''),276      :line($ex.defined ?? $ex.line.Int !! 0),277    );278  }279}280281method run-summary(282  $result,283  Bool :$aborted   = False,284  Int  :$fail-fast = 0,285  Str  :$order     = 'defined',286  Int  :$seed,287) {288  emit %(289    :type<run-summary>,290    :total($result.total.Int),291    :passed($result.passed.Int),292    :failed($result.failed.Int),293    :pending($result.pending.Int),294    :skipped($result.skipped.Int),295    :aborted(?$aborted),296    :fail-fast($fail-fast.Int),297    :$order,298    :seed($seed.defined ?? $seed.Int !! 0),299  );300}301302method load-errors(@errors) {303  for @errors -> $err {304    emit %(305      :type<load-error>,306      :file(($err<file> // '').Str),307      :message(($err<message> // '').Str),308    );309  }310}