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.
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