asciidoctor-doctest 1.5.2.0 → 2.0.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|