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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.adoc +48 -68
  4. data/features/fixtures/html-slim/Rakefile +5 -11
  5. data/features/generator_html.feature +6 -6
  6. data/features/test_html.feature +70 -28
  7. data/lib/asciidoctor/doctest.rb +11 -14
  8. data/lib/asciidoctor/doctest/asciidoc_converter.rb +85 -0
  9. data/lib/asciidoctor/doctest/{base_example.rb → example.rb} +6 -27
  10. data/lib/asciidoctor/doctest/factory.rb +36 -0
  11. data/lib/asciidoctor/doctest/generator.rb +30 -23
  12. data/lib/asciidoctor/doctest/html/converter.rb +64 -0
  13. data/lib/asciidoctor/doctest/{html/normalizer.rb → html_normalizer.rb} +4 -4
  14. data/lib/asciidoctor/doctest/io.rb +14 -0
  15. data/lib/asciidoctor/doctest/{asciidoc/examples_suite.rb → io/asciidoc.rb} +4 -8
  16. data/lib/asciidoctor/doctest/{base_examples_suite.rb → io/base.rb} +28 -46
  17. data/lib/asciidoctor/doctest/io/xml.rb +69 -0
  18. data/lib/asciidoctor/doctest/no_fallback_template_converter.rb +42 -0
  19. data/lib/asciidoctor/doctest/rake_tasks.rb +229 -0
  20. data/lib/asciidoctor/doctest/test_reporter.rb +110 -0
  21. data/lib/asciidoctor/doctest/tester.rb +134 -0
  22. data/lib/asciidoctor/doctest/version.rb +1 -1
  23. data/spec/asciidoc_converter_spec.rb +64 -0
  24. data/spec/{base_example_spec.rb → example_spec.rb} +4 -5
  25. data/spec/factory_spec.rb +46 -0
  26. data/spec/html/converter_spec.rb +95 -0
  27. data/spec/{html/normalizer_spec.rb → html_normalizer_spec.rb} +1 -1
  28. data/spec/{asciidoc/examples_suite_spec.rb → io/asciidoc_spec.rb} +3 -8
  29. data/spec/{html/examples_suite_spec.rb → io/xml_spec.rb} +3 -106
  30. data/spec/no_fallback_template_converter_spec.rb +38 -0
  31. data/spec/shared_examples/{base_examples_suite.rb → base_examples.rb} +25 -28
  32. data/spec/spec_helper.rb +4 -0
  33. data/spec/tester_spec.rb +153 -0
  34. metadata +52 -59
  35. data/features/fixtures/html-slim/test/html_test.rb +0 -6
  36. data/features/fixtures/html-slim/test/test_helper.rb +0 -5
  37. data/lib/asciidoctor/doctest/asciidoc_renderer.rb +0 -111
  38. data/lib/asciidoctor/doctest/generator_task.rb +0 -115
  39. data/lib/asciidoctor/doctest/html/example.rb +0 -21
  40. data/lib/asciidoctor/doctest/html/examples_suite.rb +0 -118
  41. data/lib/asciidoctor/doctest/test.rb +0 -125
  42. data/spec/asciidoc_renderer_spec.rb +0 -103
  43. 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