101 / 101 lines covered (100.0%)
1use BDD::Behave::Failures;2use BDD::Behave::Formatter;34unit class BDD::Behave::Formatter::TAP does BDD::Behave::Formatter;56class Entry {7 has Str $.status is required; # 'ok' or 'not ok'8 has Str $.directive is rw; # 'TODO' or 'SKIP' (optional)9 has Str $.directive-reason is rw;10 has Str $.description is rw = '';11 has @.diagnostics; # yaml-ish key/value pairs (Array of Pair)12}1314has @!description-stack;15has @!entries;16has Bool $!multi-file = False;17has Bool $!emitted = False;18has Int $!failure-watermark = 0;19has Str $!pending-auto-description;2021method name(--> Str) { 'tap' }2223method suite-loading(Str :$file) { }2425method suite-start($suite, Bool :$multi-file = False) {26 $!multi-file = $multi-file;27}2829method suite-end($suite) { }3031method group-start($group) {32 @!description-stack.push: $group.description;33}3435method group-end($group) {36 @!description-stack.pop;37}3839method group-around-skipped($group) { }4041method !full-description(Str $description --> Str) {42 my @parts = @!description-stack.clone;43 @parts.push: $description;44 @parts.grep(*.defined).join(' ');45}4647method example-start($example, Bool :$auto = False) {48 $!failure-watermark = Failures.list.elems;49 $!pending-auto-description = Str;50}5152method example-auto-description($example, Str :$description) {53 $!pending-auto-description = $description;54}5556method example-pass($example) {57 my $description = $!pending-auto-description // $example.description;58 @!entries.push: Entry.new(59 :status('ok'),60 :description(self!full-description($description)),61 );62 $!pending-auto-description = Str;63}6465method example-fail($example, :$failure-info) {66 my $description = $!pending-auto-description // $example.description;67 my $entry = Entry.new(68 :status('not ok'),69 :description(self!full-description($description)),70 );71 my @diag;72 if $failure-info.defined && $failure-info<exception>.defined {73 @diag.push: 'severity' => 'error';74 @diag.push: 'message' => $failure-info<exception>.message;75 @diag.push: 'file' => $example.file.Str;76 @diag.push: 'line' => ($failure-info<line> // $example.line);77 } else {78 @diag.push: 'severity' => 'fail';79 @diag.push: 'file' => $example.file.Str;80 @diag.push: 'line' => ($failure-info<line> // $example.line);81 my $count = Failures.list.elems;82 if $count > $!failure-watermark {83 my $first = Failures.list[$!failure-watermark];84 if $first.message.defined {85 @diag.push: 'message' => $first.message.lines[0];86 } else {87 my $op = $first.negated ?? 'not to be' !! 'to be';88 @diag.push: 'message' => "Expected " ~ $first.given.gist ~ " $op " ~ $first.expected.gist;89 @diag.push: 'got' => $first.given.gist;90 @diag.push: 'expected' => $first.expected.gist;91 }92 }93 }94 $entry.diagnostics = @diag;95 @!entries.push: $entry;96 $!pending-auto-description = Str;97}9899method example-pending($example) {100 my $reason = $example.get-metadata('pending-reason') // 'pending';101 my $description = $!pending-auto-description // $example.description;102 @!entries.push: Entry.new(103 :status('ok'),104 :description(self!full-description($description)),105 :directive('TODO'),106 :directive-reason($reason),107 );108}109110method example-skipped($example) {111 my $description = $!pending-auto-description // $example.description;112 @!entries.push: Entry.new(113 :status('ok'),114 :description(self!full-description($description)),115 :directive('SKIP'),116 :directive-reason('skipped'),117 );118}119120method example-around-skipped($example) {121 my $description = $!pending-auto-description // $example.description;122 @!entries.push: Entry.new(123 :status('ok'),124 :description(self!full-description($description)),125 :directive('SKIP'),126 :directive-reason('around-each did not invoke continuation'),127 );128}129130method example-slow($example, Real :$threshold) { }131132method run-summary(133 $result,134 Bool :$aborted = False,135 Int :$fail-fast = 0,136 Str :$order = 'defined',137 Int :$seed,138) {139 return if $!multi-file;140 self!emit($aborted);141}142143method profile-summary(@records, Int :$limit) { }144145method benchmark-summary-section(146 @summaries, @regressions,147 Real :$threshold,148 Str :$format,149 IO::Path :$output,150 :$runner,151) { }152153method multi-file-overall($result, Str :$order = 'defined', Int :$seed) {154 self!emit;155}156157method multi-file-profile($runner, @records, Int :$limit) { }158method multi-file-benchmark(159 $runner,160 @summaries, @regressions,161 Real :$threshold,162 Str :$format,163 IO::Path :$output,164) { }165166method load-errors(@errors) { }167168method !escape-description(Str $s --> Str) {169 return '' unless $s.defined;170 $s.subst('#', '\\#', :g);171}172173method !yaml-string(Str $s --> Str) {174 return "''" unless $s.defined;175 my $body = $s.subst('\\', '\\\\', :g).subst("'", "''", :g);176 "'" ~ $body ~ "'";177}178179method !render-diag(@diagnostics --> Str) {180 return '' unless @diagnostics.elems;181 my @lines;182 @lines.push: ' ---';183 for @diagnostics -> $pair {184 my $key = $pair.key;185 my $val = $pair.value;186 my $rendered = do given $val {187 when Int { $val.Str }188 when Real { $val.Str }189 default { self!yaml-string($val.Str) }190 };191 @lines.push: " $key: $rendered";192 }193 @lines.push: ' ...';194 @lines.join("\n");195}196197method !render-entry(Int $index, Entry $entry --> Str) {198 my $desc = self!escape-description($entry.description);199 my $line = "{$entry.status} $index";200 $line ~= " - $desc" if $desc.chars;201 if $entry.directive.defined {202 my $reason = $entry.directive-reason // '';203 $line ~= " # {$entry.directive} {$reason}";204 }205 if $entry.diagnostics.elems {206 $line ~= "\n" ~ self!render-diag($entry.diagnostics);207 }208 $line;209}210211method !emit(Bool $aborted = False) {212 return if $!emitted;213 $!emitted = True;214215 say 'TAP version 13';216 say "1..{@!entries.elems}";217 for @!entries.kv -> $i, $entry {218 say self!render-entry($i + 1, $entry);219 }220 say "Bail out! Aborted after fail-fast" if $aborted;221}