asciidoctor-doctest 1.5.2.0 → 2.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.adoc +48 -68
- data/features/fixtures/html-slim/Rakefile +5 -11
- data/features/generator_html.feature +6 -6
- data/features/test_html.feature +70 -28
- data/lib/asciidoctor/doctest.rb +11 -14
- data/lib/asciidoctor/doctest/asciidoc_converter.rb +85 -0
- data/lib/asciidoctor/doctest/{base_example.rb → example.rb} +6 -27
- data/lib/asciidoctor/doctest/factory.rb +36 -0
- data/lib/asciidoctor/doctest/generator.rb +30 -23
- data/lib/asciidoctor/doctest/html/converter.rb +64 -0
- data/lib/asciidoctor/doctest/{html/normalizer.rb → html_normalizer.rb} +4 -4
- data/lib/asciidoctor/doctest/io.rb +14 -0
- data/lib/asciidoctor/doctest/{asciidoc/examples_suite.rb → io/asciidoc.rb} +4 -8
- data/lib/asciidoctor/doctest/{base_examples_suite.rb → io/base.rb} +28 -46
- data/lib/asciidoctor/doctest/io/xml.rb +69 -0
- data/lib/asciidoctor/doctest/no_fallback_template_converter.rb +42 -0
- data/lib/asciidoctor/doctest/rake_tasks.rb +229 -0
- data/lib/asciidoctor/doctest/test_reporter.rb +110 -0
- data/lib/asciidoctor/doctest/tester.rb +134 -0
- data/lib/asciidoctor/doctest/version.rb +1 -1
- data/spec/asciidoc_converter_spec.rb +64 -0
- data/spec/{base_example_spec.rb → example_spec.rb} +4 -5
- data/spec/factory_spec.rb +46 -0
- data/spec/html/converter_spec.rb +95 -0
- data/spec/{html/normalizer_spec.rb → html_normalizer_spec.rb} +1 -1
- data/spec/{asciidoc/examples_suite_spec.rb → io/asciidoc_spec.rb} +3 -8
- data/spec/{html/examples_suite_spec.rb → io/xml_spec.rb} +3 -106
- data/spec/no_fallback_template_converter_spec.rb +38 -0
- data/spec/shared_examples/{base_examples_suite.rb → base_examples.rb} +25 -28
- data/spec/spec_helper.rb +4 -0
- data/spec/tester_spec.rb +153 -0
- metadata +52 -59
- data/features/fixtures/html-slim/test/html_test.rb +0 -6
- data/features/fixtures/html-slim/test/test_helper.rb +0 -5
- data/lib/asciidoctor/doctest/asciidoc_renderer.rb +0 -111
- data/lib/asciidoctor/doctest/generator_task.rb +0 -115
- data/lib/asciidoctor/doctest/html/example.rb +0 -21
- data/lib/asciidoctor/doctest/html/examples_suite.rb +0 -118
- data/lib/asciidoctor/doctest/test.rb +0 -125
- data/spec/asciidoc_renderer_spec.rb +0 -103
- data/spec/test_spec.rb +0 -164
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'asciidoctor/doctest/io/base'
|
2
|
+
require 'corefines'
|
3
|
+
|
4
|
+
using Corefines::Object[:blank?, :presence]
|
5
|
+
using Corefines::String::concat!
|
6
|
+
|
7
|
+
module Asciidoctor::DocTest
|
8
|
+
module IO
|
9
|
+
##
|
10
|
+
# Subclass of {IO::Base} for XML-based backends.
|
11
|
+
#
|
12
|
+
# @example Format of the example's header
|
13
|
+
# <!-- .example-name
|
14
|
+
# Any text that is not the example's name or an option is considered
|
15
|
+
# as a description.
|
16
|
+
# :option_1: value 1
|
17
|
+
# :option_2: value 1
|
18
|
+
# :option_2: value 2
|
19
|
+
# :boolean_option:
|
20
|
+
# -->
|
21
|
+
# <p>The example's content in <strong>HTML</strong>.</p>
|
22
|
+
#
|
23
|
+
# <div class="note">The trailing new line (below this) will be removed.</div>
|
24
|
+
#
|
25
|
+
class XML < Base
|
26
|
+
|
27
|
+
def parse(input, group_name)
|
28
|
+
examples = []
|
29
|
+
current = create_example(nil)
|
30
|
+
in_comment = false
|
31
|
+
|
32
|
+
input.each_line do |line|
|
33
|
+
line.chomp!
|
34
|
+
if line =~ /^<!--\s*\.([^ \n]+)/
|
35
|
+
name = $1
|
36
|
+
current.content.chomp!
|
37
|
+
examples << (current = create_example([group_name, name]))
|
38
|
+
in_comment = true
|
39
|
+
elsif in_comment
|
40
|
+
if line =~ /^\s*:([^:]+):(.*)/
|
41
|
+
current[$1.to_sym] = $2.blank? ? true : $2.strip
|
42
|
+
else
|
43
|
+
desc = line.rstrip.chomp('-->').strip
|
44
|
+
(current.desc ||= '').concat!(desc, "\n") unless desc.empty?
|
45
|
+
end
|
46
|
+
else
|
47
|
+
current.content.concat!(line, "\n")
|
48
|
+
end
|
49
|
+
in_comment &= !line.end_with?('-->')
|
50
|
+
end
|
51
|
+
|
52
|
+
examples
|
53
|
+
end
|
54
|
+
|
55
|
+
def serialize(examples)
|
56
|
+
Array(examples).map { |exmpl|
|
57
|
+
header = [
|
58
|
+
".#{exmpl.local_name}",
|
59
|
+
exmpl.desc.presence,
|
60
|
+
*format_options(exmpl.opts)
|
61
|
+
].compact
|
62
|
+
|
63
|
+
header_str = header.one? ? (header.first + ' ') : (header.join("\n") + "\n")
|
64
|
+
[ "<!-- #{header_str}-->", exmpl.content.presence ].compact.join("\n") + "\n"
|
65
|
+
}.join("\n")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'asciidoctor/converter/template'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
module Asciidoctor
|
5
|
+
module DocTest
|
6
|
+
##
|
7
|
+
# @private
|
8
|
+
# TemplateConverter that doesn't fallback to a built-in converter when
|
9
|
+
# no template for a node is found.
|
10
|
+
#
|
11
|
+
class NoFallbackTemplateConverter < SimpleDelegator
|
12
|
+
# NOTE: It didn't work with subclass of TemplateConverter instead of
|
13
|
+
# delegator, I have no idea why.
|
14
|
+
|
15
|
+
# Placeholder to be written in a rendered output in place of the node's
|
16
|
+
# content that cannot be rendered due to missing template.
|
17
|
+
NOT_FOUND_MARKER = '--TEMPLATE NOT FOUND--'
|
18
|
+
|
19
|
+
def initialize(backend, opts = {})
|
20
|
+
super Asciidoctor::Converter::TemplateConverter.new(backend, opts[:template_dirs], opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Delegates to the template converter and returns results, or prints
|
25
|
+
# warning and returns {NOT_FOUND_MARKER} if there is no template to
|
26
|
+
# handle the specified +template_name+.
|
27
|
+
def convert(node, template_name = nil, opts = {})
|
28
|
+
template_name ||= node.node_name
|
29
|
+
|
30
|
+
if handles? template_name
|
31
|
+
super
|
32
|
+
else
|
33
|
+
warn "Could not find a custom template to handle template_name: #{template_name}"
|
34
|
+
NOT_FOUND_MARKER
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Alias for backward compatibility.
|
39
|
+
alias_method :convert_with_options, :convert
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'asciidoctor/doctest/asciidoc_converter'
|
2
|
+
require 'asciidoctor/doctest/generator'
|
3
|
+
require 'asciidoctor/doctest/test_reporter'
|
4
|
+
require 'asciidoctor/doctest/tester'
|
5
|
+
require 'asciidoctor/doctest/io/asciidoc'
|
6
|
+
require 'corefines'
|
7
|
+
require 'rake/tasklib'
|
8
|
+
|
9
|
+
using Corefines::String[:to_b, :unindent]
|
10
|
+
|
11
|
+
module Asciidoctor
|
12
|
+
module DocTest
|
13
|
+
##
|
14
|
+
# Rake tasks for testing and generating output examples.
|
15
|
+
class RakeTasks < Rake::TaskLib
|
16
|
+
|
17
|
+
# Genetates a description of a given task when
|
18
|
+
# {#test_description} is not set.
|
19
|
+
DEFAULT_TEST_DESC = ->(task) do
|
20
|
+
<<-EOS.unindent
|
21
|
+
Run integration tests for the #{task.subject}.
|
22
|
+
|
23
|
+
Options (env. variables):
|
24
|
+
PATTERN glob pattern to select examples to test. [default: #{task.pattern}]
|
25
|
+
E.g. *:*, block_toc:basic, block*:*, *list:with*, ...
|
26
|
+
VERBOSE prints out more details [default: #{task.verbose? ? 'yes' : 'no'}]
|
27
|
+
EOS
|
28
|
+
end
|
29
|
+
|
30
|
+
# Genetates a description of a given task when
|
31
|
+
# {#generate_description} is not set.
|
32
|
+
DEFAULT_GENERATE_DESC = ->(task) do
|
33
|
+
<<-EOS.unindent
|
34
|
+
Generate test examples for the #{task.subject}.
|
35
|
+
|
36
|
+
Options (env. variables):
|
37
|
+
PATTERN glob pattern to select examples to (re)generate. [default: #{task.pattern}]
|
38
|
+
E.g. *:*, block_toc:basic, block*:*, *list:with*, ...
|
39
|
+
FORCE overwrite existing examples (yes/no)? [default: #{task.force? ? 'yes' : 'no'}]
|
40
|
+
EOS
|
41
|
+
end
|
42
|
+
|
43
|
+
private_constant :DEFAULT_TEST_DESC, :DEFAULT_GENERATE_DESC
|
44
|
+
|
45
|
+
# @return [#to_sym] namespace for the +:test+ and +:generate+ tasks. The
|
46
|
+
# +:test+ task will be set as the default task of this namespace.
|
47
|
+
attr_accessor :tasks_namespace
|
48
|
+
|
49
|
+
# @return [#to_s, #call] description of the test task.
|
50
|
+
attr_accessor :test_description
|
51
|
+
|
52
|
+
# @return [#to_s, #call] description of the generator task.
|
53
|
+
attr_accessor :generate_description
|
54
|
+
|
55
|
+
attr_accessor :converter
|
56
|
+
|
57
|
+
# @return [Hash] options for the Asciidoctor converter.
|
58
|
+
# @see AsciidocConverter#initialize
|
59
|
+
attr_accessor :converter_opts
|
60
|
+
|
61
|
+
# @return [String] glob pattern to select examples to test or
|
62
|
+
# (re)generate. May be overriden with +PATTERN+ variable on the command
|
63
|
+
# line (default: +\*:*+).
|
64
|
+
attr_accessor :pattern
|
65
|
+
|
66
|
+
# @return [Minitest::Reporter] an instance of +Reporter+ subclass to
|
67
|
+
# report test results.
|
68
|
+
# @note Used only in the test task.
|
69
|
+
attr_accessor :test_reporter
|
70
|
+
|
71
|
+
# @return [Boolean] whether to print out more details (default: false).
|
72
|
+
# May be overriden with +VERBOSE+ variable on the command line.
|
73
|
+
# @note Used only in the test task and with the default {#test_reporter}.
|
74
|
+
attr_accessor :verbose
|
75
|
+
|
76
|
+
# @return [Boolean] whether to rewrite an already existing testing
|
77
|
+
# example. May be overriden with +FORCE+ variable on the command line
|
78
|
+
# (default: false).
|
79
|
+
# @note Used only in the generator task.
|
80
|
+
attr_accessor :force
|
81
|
+
|
82
|
+
|
83
|
+
##
|
84
|
+
# Defines and configures +:test+ and +:generate+ rake tasks under the
|
85
|
+
# specified namespace.
|
86
|
+
#
|
87
|
+
# @param tasks_namespace [#to_sym] namespace for the +:test+ and
|
88
|
+
# +:generate+ tasks.
|
89
|
+
# @yield [self] Gives self to the block.
|
90
|
+
#
|
91
|
+
def initialize(tasks_namespace = :doctest)
|
92
|
+
@tasks_namespace = tasks_namespace
|
93
|
+
@test_description = DEFAULT_TEST_DESC
|
94
|
+
@generate_description = DEFAULT_GENERATE_DESC
|
95
|
+
@input_examples = IO.create(:asciidoc)
|
96
|
+
@converter_opts = {}
|
97
|
+
@force = false
|
98
|
+
@pattern = '*:*'
|
99
|
+
|
100
|
+
yield self
|
101
|
+
|
102
|
+
fail ArgumentError, 'The output_examples must be provided!' unless @output_examples
|
103
|
+
|
104
|
+
@converter = converter.new(converter_opts) if converter.is_a? Class
|
105
|
+
@test_reporter ||= TestReporter.new($stdout, verbose: verbose?,
|
106
|
+
title: "Running DocTest for the #{subject}.")
|
107
|
+
|
108
|
+
namespace(tasks_namespace) do
|
109
|
+
define_test_task!
|
110
|
+
define_generate_task!
|
111
|
+
end
|
112
|
+
|
113
|
+
desc "Alias for #{tasks_namespace}:test."
|
114
|
+
task tasks_namespace => "#{tasks_namespace}:test"
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Specifies a reader for the input examples. Defaults to +:asciidoc+ with
|
119
|
+
# the built-in reference examples.
|
120
|
+
#
|
121
|
+
# @overload input_examples(format, opts)
|
122
|
+
# @param format [Symbol, String]
|
123
|
+
# @param opts [Hash]
|
124
|
+
# @option opts :path see {#output_examples}
|
125
|
+
# @option opts :file_ext see {#output_examples}
|
126
|
+
#
|
127
|
+
# @overload input_examples(io)
|
128
|
+
# @param io [IO::Base]
|
129
|
+
#
|
130
|
+
def input_examples(*args)
|
131
|
+
@input_examples = create_io(*args)
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Specifies a reader/writer for the output examples (required).
|
136
|
+
#
|
137
|
+
# @overload output_examples(format, opts)
|
138
|
+
# @param format [Symbol, String]
|
139
|
+
# @param opts [Hash]
|
140
|
+
# @option opts :path [String, Array<String>] path of the directory
|
141
|
+
# (or multiple directories) where to look for the examples.
|
142
|
+
# Default is {DocTest.examples_path}.
|
143
|
+
# @option opts :file_ext [String] the filename extension (e.g. +.adoc+)
|
144
|
+
# of the examples files. Default value depends on the
|
145
|
+
# specified format.
|
146
|
+
#
|
147
|
+
# @overload output_examples(io)
|
148
|
+
# @param io [IO::Base]
|
149
|
+
#
|
150
|
+
def output_examples(*args)
|
151
|
+
@output_examples = create_io(*args)
|
152
|
+
end
|
153
|
+
|
154
|
+
def pattern
|
155
|
+
ENV['PATTERN'] || @pattern
|
156
|
+
end
|
157
|
+
|
158
|
+
# (see #force)
|
159
|
+
def force?
|
160
|
+
ENV.fetch('FORCE', @force.to_s).to_b
|
161
|
+
end
|
162
|
+
|
163
|
+
alias_method :force, :force?
|
164
|
+
|
165
|
+
# (see #verbose)
|
166
|
+
def verbose?
|
167
|
+
ENV.fetch('VERBOSE', @verbose.to_s).to_b
|
168
|
+
end
|
169
|
+
|
170
|
+
alias_method :verbose, :verbose?
|
171
|
+
|
172
|
+
# @private
|
173
|
+
def subject
|
174
|
+
{
|
175
|
+
converter: 'converter',
|
176
|
+
template_dirs: 'templates',
|
177
|
+
backend_name: 'backend'
|
178
|
+
}
|
179
|
+
.each do |key, desc|
|
180
|
+
val = converter_opts[key]
|
181
|
+
return "#{desc}: #{val}" if val
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
protected
|
186
|
+
|
187
|
+
def run_tests!
|
188
|
+
tester = Tester.new(@input_examples, @output_examples, @converter, @test_reporter)
|
189
|
+
fail unless tester.run_tests(pattern: pattern)
|
190
|
+
end
|
191
|
+
|
192
|
+
##
|
193
|
+
# @param desc [#to_s, #call] description of the next rake task. When the
|
194
|
+
# given object responds to +#call+, then it's invoked with +self+
|
195
|
+
# as a parameter. The result is expected to be a +String+.
|
196
|
+
def desc(desc)
|
197
|
+
super desc.respond_to?(:call) ? desc.call(self) : desc.to_s
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def define_test_task!
|
203
|
+
desc test_description
|
204
|
+
task :test do
|
205
|
+
run_tests!
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def define_generate_task!
|
210
|
+
desc generate_description
|
211
|
+
task :generate do
|
212
|
+
puts "Generating test examples #{pattern} in #{@output_examples.path.first}"
|
213
|
+
|
214
|
+
Generator.new(@input_examples, @output_examples, @converter)
|
215
|
+
.generate! pattern: pattern, rewrite: force?
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def create_io(*args)
|
220
|
+
case args.first
|
221
|
+
when Symbol, String
|
222
|
+
IO.create(*args)
|
223
|
+
else
|
224
|
+
args.first
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'minitest'
|
3
|
+
|
4
|
+
using Corefines::String[:color, :indent]
|
5
|
+
|
6
|
+
module Asciidoctor
|
7
|
+
module DocTest
|
8
|
+
##
|
9
|
+
# This class is responsible for printing a formatted output of the test run.
|
10
|
+
class TestReporter < Minitest::StatisticsReporter
|
11
|
+
|
12
|
+
RESULT_COLOR = { :'.' => :green, E: :yellow, F: :red, S: :cyan }
|
13
|
+
RESULT_SYMBOL = { :'.' => '✓', E: '⚠', F: '✗', S: '∅' }
|
14
|
+
|
15
|
+
private_constant :RESULT_COLOR, :RESULT_SYMBOL
|
16
|
+
|
17
|
+
##
|
18
|
+
# @note Overrides method from +Minitest::StatisticsReporter+.
|
19
|
+
def start
|
20
|
+
super
|
21
|
+
io.puts "\n" + (options[:title] || 'Running DocTest:') + "\n\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# @param result [Minitest::Test] a single test result.
|
26
|
+
# @note Overrides method from +Minitest::StatisticsReporter+.
|
27
|
+
def record(result)
|
28
|
+
result.extend ResultExt
|
29
|
+
|
30
|
+
if verbose?
|
31
|
+
io.puts [ result.symbol.color(result.color), result.name ].join(' ')
|
32
|
+
else
|
33
|
+
io.print result.result_code.color(result.color)
|
34
|
+
end
|
35
|
+
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Outputs the summary of the run.
|
41
|
+
# @note Overrides method from +Minitest::StatisticsReporter+.
|
42
|
+
def report
|
43
|
+
super
|
44
|
+
io.puts unless verbose? # finish the dots
|
45
|
+
io.puts ['', aggregated_results, summary, ''].compact.join("\n")
|
46
|
+
end
|
47
|
+
|
48
|
+
# @private
|
49
|
+
def aggregated_results
|
50
|
+
filtered_results = verbose? ? results : results.reject(&:skipped?)
|
51
|
+
|
52
|
+
return nil if filtered_results.empty?
|
53
|
+
|
54
|
+
str = "Aggregated results:\n"
|
55
|
+
filtered_results.each do |res|
|
56
|
+
str << "\n#{res.symbol} #{res.failure.result_label}: ".color(res.color)
|
57
|
+
str << "#{res.name}\n#{res.failure.message.indent(3)}\n\n"
|
58
|
+
end
|
59
|
+
|
60
|
+
str
|
61
|
+
end
|
62
|
+
|
63
|
+
# @private
|
64
|
+
def summary
|
65
|
+
str = "#{count} examples ("
|
66
|
+
str << [
|
67
|
+
("#{passes} passed".color(:green) if passes > 0),
|
68
|
+
("#{failures} failed".color(:red) if failures > 0),
|
69
|
+
("#{errors} errored".color(:yellow) if errors > 0),
|
70
|
+
("#{skips} skipped".color(:cyan) if skips > 0)
|
71
|
+
].compact.join(', ') + ")\n\n"
|
72
|
+
|
73
|
+
str << "Finished in %.3f s.\n" % total_time
|
74
|
+
|
75
|
+
if results.any?(&:skipped?) && !verbose?
|
76
|
+
str << "\nYou have skipped tests. Run with VERBOSE=yes for details.\n"
|
77
|
+
end
|
78
|
+
|
79
|
+
str
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# @return [Integer] number of passed tests (examples).
|
84
|
+
def passes
|
85
|
+
count - failures - errors - skips
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# @return [Boolean] whether verbose mode is enabled.
|
90
|
+
def verbose?
|
91
|
+
!!options[:verbose]
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
##
|
96
|
+
# @private
|
97
|
+
# Module to be included into +Minitest::Test+.
|
98
|
+
module ResultExt
|
99
|
+
|
100
|
+
def symbol
|
101
|
+
RESULT_SYMBOL[result_code.to_sym]
|
102
|
+
end
|
103
|
+
|
104
|
+
def color
|
105
|
+
RESULT_COLOR[result_code.to_sym]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|