Skip to content

Commit

Permalink
Support TAP version 12 plus skipped and TODO tests. (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
tessereth authored and maschwenk committed Oct 12, 2019
1 parent 88a7787 commit c4dc780
Show file tree
Hide file tree
Showing 7 changed files with 1,716 additions and 44 deletions.
6 changes: 6 additions & 0 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ steps:
- label: ":camel: tap"
artifact_path: spec/sample_artifacts/example.tap
type: tap
- label: ":camel: tap v12"
artifact_path: spec/sample_artifacts/version_12.tap
type: tap
- label: ":eslint: eslint"
artifact_path: spec/sample_artifacts/eslint-*.txt
type: oneline
Expand Down Expand Up @@ -96,6 +99,9 @@ steps:
- label: ":camel: tap"
artifact_path: spec/sample_artifacts/example.tap
type: tap
- label: ":camel: tap v12"
artifact_path: spec/sample_artifacts/version_12.tap
type: tap
- label: ":eslint: eslint"
artifact_path: spec/sample_artifacts/eslint-*.txt
type: oneline
Expand Down
1 change: 1 addition & 0 deletions lib/test_summary_buildkite_plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require 'test_summary_buildkite_plugin/haml_render'
require 'test_summary_buildkite_plugin/input'
require 'test_summary_buildkite_plugin/runner'
require 'test_summary_buildkite_plugin/tap'
require 'test_summary_buildkite_plugin/truncater'
require 'test_summary_buildkite_plugin/utils'
require 'test_summary_buildkite_plugin/version'
Expand Down
51 changes: 7 additions & 44 deletions lib/test_summary_buildkite_plugin/input.rb
Original file line number Diff line number Diff line change
Expand Up @@ -173,51 +173,14 @@ def details_regex
end

class Tap < Base
TEST_LINE = /^(?<not>not )?ok(?<test_number> \d+)?(?<description>[^#]*)(#(?<directive>.*))?/
YAML_START = /^\s+---/
YAML_END = /^\s+\.\.\./

# TODO: Factor this out into its own parser class
def file_contents_to_failures(tap) # rubocop:disable Metrics/MethodLength
lines = tap.split("\n")
raise 'Only TAP version 13 supported' unless lines.first.strip == 'TAP version 13'
tests = []
in_failure = false
yaml_lines = nil
lines.each do |line|
if (matchdata = line.match(TEST_LINE))
if matchdata['not']
# start of a failing test
in_failure = true
tests << Failure::Structured.new(
summary: summary(matchdata)
)
else
# we're in a successful test, ignore subsequent lines until we hit a failure
in_failure = false
end
elsif line.match?(YAML_START)
yaml_lines = []
elsif line.match?(YAML_END)
tests.last.details = details(yaml_lines)
yaml_lines = nil
elsif in_failure && yaml_lines
yaml_lines << line
end
def file_contents_to_failures(tap)
suite = ::TestSummaryBuildkitePlugin::Tap::Parser.new(tap).parse
suite.tests.select { |x| !x.passed && !x.todo && !x.skipped }.map do |x|
Failure::Structured.new(
summary: x.description,
details: x.yaml || x.diagnostic
)
end
tests
end

def summary(matchdata)
# There's a convention to put a ' - ' between the test number and the description
# We strip that for better readability
matchdata['description'].strip.gsub(/^- /, '')
end

def details(yaml_lines)
# strip indent
indent = yaml_lines.first.match(/(\s*)/)[1].length
yaml_lines.map { |line| line[indent..-1] }.join("\n")
end
end

Expand Down
129 changes: 129 additions & 0 deletions lib/test_summary_buildkite_plugin/tap.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# frozen_string_literal: true

module TestSummaryBuildkitePlugin
# Parses most of the TAP protocol, assuming the inputs are sane.
#
# The specification is at https://testanything.org. This parses both
# version 12 and version 13.
#
# Notable omissions:
#
# * Test numbering and the planned number of tests are ignored.
# * "Bail out!" is ignored.
#
# Disclaimer:
#
# This works about as well as you'd expect a hand-rolled parser made of
# regular expressions to work. Use at your own risk, pull requests welcome.
#
# TODO: Use a proper grammar and parser rather than regexes.
class Tap
class Suite
attr_accessor :tests, :version

def initialize
self.version = 12
self.tests = []
end
end

Test = Struct.new(:passed, :description, :directive, :todo, :skipped, :diagnostic, :yaml,
keyword_init: true)

class Parser
PATTERNS = {
plan: /^(?<start>\d+)\.\.(?<end>\d+)/,
test:
/^(?<not>not )?ok(?<number> \d+)?(?<description>[^#]*)(#\s*(?<directive>((?<todo>TODO)|(?<skip>SKIP))?.*))?/i,
comment: /^#(?<comment>.*)$/,
yaml_start: /^\s+---/,
yaml_end: /^\s+\.\.\./,
version: /^TAP version (?<version>\d+)/i
}.freeze

attr_reader :text
attr_reader :suite

def initialize(text)
@text = text
@suite = Suite.new
@current_diagnostic = []
@current_yaml = []
@in_yaml = []
end

def parse # rubocop:disable Metrics/MethodLength
text.split("\n").each do |line|
type, match = type(line)
case type
when :test
save_previous_blocks
suite.tests.push(to_test(match))
when :version
suite.version = match['version'].to_i
when :plan
# we currently have no use for the 1..x info
nil
when :comment
@current_diagnostic.push(match['comment'])
when :yaml_start
@in_yaml = true
when :yaml_end
@in_yaml = false
else
@current_yaml.push(line) if @in_yaml
# as per the spec, we just ignore anything else we don't recognise
end
end
save_previous_blocks
suite
end

private

def type(line)
PATTERNS.each do |name, regex|
match = regex.match(line)
return name, match if match
end
[:unknown, nil]
end

def to_test(match)
Test.new(
passed: !match['not'],
description: description(match),
directive: match['directive'],
todo: match['todo'],
skipped: match['skip']
)
end

def description(match)
# There's a convention to put a ' - ' between the test number and the description
# We strip that for better readability
match['description'].strip.gsub(/^- /, '')
end

def save_previous_blocks
last_test = suite.tests.last
if last_test
last_test.diagnostic = normalize_multiline(@current_diagnostic)
last_test.yaml = normalize_multiline(@current_yaml)
end
@current_diagnostic = []
@current_yaml = []
@in_yaml = false
end

def normalize_multiline(lines)
if lines.empty?
nil
else
indent = lines.first.match(/(\s*)/)[1].length
lines.map { |line| line[indent..-1] }.join("\n")
end
end
end
end
end
Loading

0 comments on commit c4dc780

Please sign in to comment.