bryanlarsen-rubydoctest 1.0.2
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.
- data/History.txt +24 -0
- data/License.txt +19 -0
- data/Manifest.txt +38 -0
- data/PostInstall.txt +7 -0
- data/README.txt +100 -0
- data/Rakefile +4 -0
- data/bin/rubydoctest +82 -0
- data/config/hoe.rb +72 -0
- data/config/requirements.rb +15 -0
- data/lib/code_block.rb +68 -0
- data/lib/doctest_require.rb +3 -0
- data/lib/lines.rb +143 -0
- data/lib/result.rb +63 -0
- data/lib/rubydoctest.rb +29 -0
- data/lib/rubydoctest/version.rb +9 -0
- data/lib/runner.rb +414 -0
- data/lib/special_directive.rb +54 -0
- data/lib/statement.rb +81 -0
- data/lib/test.rb +29 -0
- data/rubydoctest.gemspec +72 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/rstakeout +92 -0
- data/script/txt2html +82 -0
- data/setup.rb +1585 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/doctests.rake +30 -0
- data/tasks/environment.rake +7 -0
- data/tasks/website.rake +17 -0
- data/textmate/DocTest (Markdown).textmate +7 -0
- data/textmate/DocTest (Ruby).textmate +65 -0
- data/textmate/DocTest (Text).textmate +66 -0
- data/website/index.html +94 -0
- data/website/index.txt +37 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +138 -0
- data/website/template.html.erb +48 -0
- metadata +106 -0
data/lib/lines.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
module RubyDocTest
|
2
|
+
# === Description
|
3
|
+
# Keeps track of which lines within a document belong to a group. Line groups are
|
4
|
+
# determined by their indentation level, as in the Python programming language.
|
5
|
+
#
|
6
|
+
# === Example
|
7
|
+
# This line and the next one
|
8
|
+
# are part of the same group
|
9
|
+
#
|
10
|
+
# But this line is separate from
|
11
|
+
# this line.
|
12
|
+
#
|
13
|
+
# === Note
|
14
|
+
# This class also considers one '#' character (comment) as an indentation character,
|
15
|
+
# i.e. similar to how whitespace is treated.
|
16
|
+
class Lines
|
17
|
+
def initialize(doc_lines, line_index = 0)
|
18
|
+
@doc_lines, @line_index = doc_lines, line_index
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def line_number
|
23
|
+
@line_index + 1
|
24
|
+
end
|
25
|
+
|
26
|
+
# === Tests
|
27
|
+
# doctest: Return an array of 1 line if there is only one line
|
28
|
+
# >> l = RubyDocTest::Lines.new(["line 1"])
|
29
|
+
# >> l.lines
|
30
|
+
# => ["line 1"]
|
31
|
+
#
|
32
|
+
# doctest: Remove indentation from lines 2 to the end of this Lines group.
|
33
|
+
# >> l = RubyDocTest::Lines.new(["line 1", " line 2", " line 3", " line 4"])
|
34
|
+
# >> l.lines
|
35
|
+
# => ["line 1", "line 2", "line 3", " line 4"]
|
36
|
+
def lines
|
37
|
+
r = range
|
38
|
+
size = r.last - r.first + 1
|
39
|
+
if size > 1
|
40
|
+
# Remove indentation from all lines after the first,
|
41
|
+
# as measured from the 2nd line's indentation level
|
42
|
+
idt2 = indentation(@doc_lines, @line_index + 1)
|
43
|
+
[@doc_lines[range.first]] +
|
44
|
+
@doc_lines[(range.first + 1)..(range.last)].
|
45
|
+
map{ |l| $1 if l =~ /^#{Regexp.escape(idt2)}(.*)/ }
|
46
|
+
else
|
47
|
+
@doc_lines[range]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# === Description
|
53
|
+
# Calculate the range of python-like indentation within this Lines group
|
54
|
+
#
|
55
|
+
# === Tests
|
56
|
+
# >> l = RubyDocTest::Lines.new([])
|
57
|
+
#
|
58
|
+
# doctest: Return a range of one line when there is only one line to begin with
|
59
|
+
# >> l.range %w(a), 0
|
60
|
+
# => 0..0
|
61
|
+
#
|
62
|
+
# doctest: Return a range of one line when there are two lines, side by side
|
63
|
+
# >> l.range %w(a b), 0
|
64
|
+
# => 0..0
|
65
|
+
# >> l.range %w(a b), 1
|
66
|
+
# => 1..1
|
67
|
+
#
|
68
|
+
# doctest: Return a range of two lines when there are two lines, the second blank
|
69
|
+
# >> l.range ["a", ""], 0
|
70
|
+
# => 0..1
|
71
|
+
#
|
72
|
+
# doctest: Return a range of two lines when the second is indented
|
73
|
+
# >> l.range ["a", " b"], 0
|
74
|
+
# => 0..1
|
75
|
+
#
|
76
|
+
# doctest: Indentation can also include the ?> marker
|
77
|
+
# >> l.range [">> 1 +", "?> 2"], 0
|
78
|
+
# => 0..1
|
79
|
+
def range(doc_lines = @doc_lines, start_index = @line_index)
|
80
|
+
end_index = start_index
|
81
|
+
idt = indentation(doc_lines, start_index)
|
82
|
+
# Find next lines that are blank, or have indentation more than the first line
|
83
|
+
remaining_lines(doc_lines, start_index + 1).each do |current_line|
|
84
|
+
if current_line =~ /^(#{Regexp.escape(idt)}(\s+|\?>\s)|\s*$)/
|
85
|
+
end_index += 1
|
86
|
+
else
|
87
|
+
break
|
88
|
+
end
|
89
|
+
end
|
90
|
+
# Compute the range from what we found
|
91
|
+
start_index..end_index
|
92
|
+
end
|
93
|
+
|
94
|
+
def inspect
|
95
|
+
"#<#{self.class} lines=#{lines.inspect}>"
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
# === Tests
|
101
|
+
# >> l = RubyDocTest::Lines.new([])
|
102
|
+
#
|
103
|
+
# doctest: Get a whitespace indent from a line with whitespace
|
104
|
+
# >> l.send :indentation, [" a"], 0
|
105
|
+
# => " "
|
106
|
+
#
|
107
|
+
# doctest: Get a whitespace and '#' indent from a comment line
|
108
|
+
# >> l.send :indentation, [" # a"], 0
|
109
|
+
# => " # "
|
110
|
+
def indentation(doc_lines = @doc_lines, line_index = @line_index)
|
111
|
+
if doc_lines[line_index]
|
112
|
+
doc_lines[line_index][/^(\s*#\s*|\s*)(\?>\s?)?/]
|
113
|
+
else
|
114
|
+
""
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
# === Description
|
120
|
+
# Get lines from +start_index+ up to the end of the document.
|
121
|
+
#
|
122
|
+
# === Tests
|
123
|
+
# >> l = RubyDocTest::Lines.new([])
|
124
|
+
#
|
125
|
+
# doctest: Return an empty array if start_index is out of bounds
|
126
|
+
# >> l.send :remaining_lines, [], 1
|
127
|
+
# => []
|
128
|
+
# >> l.send :remaining_lines, [], -1
|
129
|
+
# => []
|
130
|
+
#
|
131
|
+
# doctest: Return the specified line at start_index, up to and including the
|
132
|
+
# last line of +doc_lines+.
|
133
|
+
# >> l.send :remaining_lines, %w(a b c), 1
|
134
|
+
# => %w(b c)
|
135
|
+
# >> l.send :remaining_lines, %w(a b c), 2
|
136
|
+
# => %w(c)
|
137
|
+
#
|
138
|
+
def remaining_lines(doc_lines = @doc_lines, start_index = @line_index)
|
139
|
+
return [] if start_index < 0 or start_index >= doc_lines.size
|
140
|
+
doc_lines[start_index..-1]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/lib/result.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'lines'
|
5
|
+
|
6
|
+
module RubyDocTest
|
7
|
+
class Result < Lines
|
8
|
+
|
9
|
+
def normalize_result(s)
|
10
|
+
s.gsub(/:0x[a-f0-9]{8}>/, ':0xXXXXXXXX>').strip
|
11
|
+
end
|
12
|
+
|
13
|
+
def expected_result
|
14
|
+
@expected_result ||=
|
15
|
+
begin
|
16
|
+
lines.first =~ /^#{Regexp.escape(indentation)}=>\s(.*)$/
|
17
|
+
([$1] + (lines[1..-1] || [])).join("\n")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# === Tests
|
22
|
+
# doctest: Strings should match
|
23
|
+
# >> r = RubyDocTest::Result.new(["=> 'hi'"])
|
24
|
+
# >> r.matches? 'hi'
|
25
|
+
# => true
|
26
|
+
#
|
27
|
+
# >> r = RubyDocTest::Result.new(["=> \"hi\""])
|
28
|
+
# >> r.matches? "hi"
|
29
|
+
# => true
|
30
|
+
#
|
31
|
+
# doctest: Regexps should match
|
32
|
+
# >> r = RubyDocTest::Result.new(["=> /^reg.../"])
|
33
|
+
# >> r.matches? /^reg.../
|
34
|
+
# => true
|
35
|
+
#
|
36
|
+
# >> r = RubyDocTest::Result.new(["=> /^reg.../"])
|
37
|
+
# >> r.matches? /^regexp/
|
38
|
+
# => false
|
39
|
+
#
|
40
|
+
# doctest: Arrays should match
|
41
|
+
# >> r = RubyDocTest::Result.new(["=> [1, 2, 3]"])
|
42
|
+
# >> r.matches? [1, 2, 3]
|
43
|
+
# => true
|
44
|
+
#
|
45
|
+
# doctest: Arrays of arrays should match
|
46
|
+
# >> r = RubyDocTest::Result.new(["=> [[1, 2], [3, 4]]"])
|
47
|
+
# >> r.matches? [[1, 2], [3, 4]]
|
48
|
+
# => true
|
49
|
+
#
|
50
|
+
# doctest: Hashes should match
|
51
|
+
# >> r = RubyDocTest::Result.new(["=> {:one => 1, :two => 2}"])
|
52
|
+
# >> r.matches?({:two => 2, :one => 1})
|
53
|
+
# => true
|
54
|
+
def matches?(actual_result)
|
55
|
+
normalize_result(actual_result.inspect) ==
|
56
|
+
normalize_result(expected_result) \
|
57
|
+
or
|
58
|
+
actual_result == eval(expected_result, TOPLEVEL_BINDING)
|
59
|
+
rescue Exception
|
60
|
+
false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/rubydoctest.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'irb'
|
5
|
+
require "runner"
|
6
|
+
|
7
|
+
module RubyDocTest
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :trace, :ignore_interactive, :tests, :verbose
|
11
|
+
attr_writer :output_format
|
12
|
+
|
13
|
+
def output_format
|
14
|
+
if @output_format == :ansi or (@output_format.nil? and STDOUT.tty?)
|
15
|
+
:ansi
|
16
|
+
elsif @output_format == :html
|
17
|
+
:html
|
18
|
+
else
|
19
|
+
:plain
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def indent(s, level=4)
|
24
|
+
spaces = " " * level
|
25
|
+
spaces + s.split("\n").join("\n#{spaces}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/runner.rb
ADDED
@@ -0,0 +1,414 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'rubydoctest'
|
5
|
+
require 'statement'
|
6
|
+
require 'result'
|
7
|
+
require 'special_directive'
|
8
|
+
require 'code_block'
|
9
|
+
require 'test'
|
10
|
+
|
11
|
+
module RubyDocTest
|
12
|
+
class Runner
|
13
|
+
attr_reader :groups, :blocks, :tests
|
14
|
+
|
15
|
+
@@color = {
|
16
|
+
:html => {
|
17
|
+
:red => %{<font color="red">%s</font>},
|
18
|
+
:yellow => %{<font color="#C0C000">%s</font>},
|
19
|
+
:green => %{<font color="green">%s</font>}
|
20
|
+
},
|
21
|
+
:ansi => {
|
22
|
+
:red => %{\e[31m%s\e[0m},
|
23
|
+
:yellow => %{\e[33m%s\e[0m},
|
24
|
+
:green => %{\e[32m%s\e[0m}
|
25
|
+
},
|
26
|
+
:plain => {
|
27
|
+
:red => "%s",
|
28
|
+
:yellow => "%s",
|
29
|
+
:green => "%s"
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
# The evaluation mode, either :doctest or :ruby.
|
34
|
+
#
|
35
|
+
# Modes:
|
36
|
+
# :doctest
|
37
|
+
# - The the Runner expects the file to contain text (e.g. a markdown file).
|
38
|
+
# In addition, it assumes that the text will occasionally be interspersed
|
39
|
+
# with irb lines which it should eval, e.g. '>>' and '=>'.
|
40
|
+
#
|
41
|
+
# :ruby
|
42
|
+
# - The Runner expects the file to be a Ruby source file. The source may contain
|
43
|
+
# comments that are interspersed with irb lines to eval, e.g. '>>' and '=>'.
|
44
|
+
attr_accessor :mode
|
45
|
+
|
46
|
+
# === Tests
|
47
|
+
#
|
48
|
+
# doctest: Runner mode should default to :doctest and :ruby from the filename
|
49
|
+
# >> r = RubyDocTest::Runner.new("", "test.doctest")
|
50
|
+
# >> r.mode
|
51
|
+
# => :doctest
|
52
|
+
#
|
53
|
+
# >> r = RubyDocTest::Runner.new("", "test.rb")
|
54
|
+
# >> r.mode
|
55
|
+
# => :ruby
|
56
|
+
#
|
57
|
+
# doctest: The src_lines should be separated into an array
|
58
|
+
# >> r = RubyDocTest::Runner.new("a\nb\n", "test.doctest")
|
59
|
+
# >> r.instance_variable_get("@src_lines")
|
60
|
+
# => ["a", "b"]
|
61
|
+
def initialize(src, file_name = "test.doctest", initial_mode = nil)
|
62
|
+
@src, @file_name = src, file_name
|
63
|
+
@mode = initial_mode || (File.extname(file_name) == ".rb" ? :ruby : :doctest)
|
64
|
+
|
65
|
+
@src_lines = src.split("\n")
|
66
|
+
@groups, @blocks = [], []
|
67
|
+
$rubydoctest = self
|
68
|
+
end
|
69
|
+
|
70
|
+
# doctest: Using the doctest_require: SpecialDirective should require a file relative to the current one.
|
71
|
+
# >> r = RubyDocTest::Runner.new("# doctest_require: 'doctest_require.rb'", __FILE__)
|
72
|
+
# >> r.prepare_tests
|
73
|
+
# >> is_doctest_require_successful?
|
74
|
+
# => true
|
75
|
+
def prepare_tests
|
76
|
+
@groups = read_groups
|
77
|
+
@blocks = organize_blocks
|
78
|
+
@tests = organize_tests
|
79
|
+
eval(@src, TOPLEVEL_BINDING, @file_name) if @mode == :ruby
|
80
|
+
end
|
81
|
+
|
82
|
+
# === Tests
|
83
|
+
# doctest: Run through a simple inline doctest (rb) file and see if it passes
|
84
|
+
# >> file = File.join(File.dirname(__FILE__), "..", "test", "inline.rb")
|
85
|
+
# >> r = RubyDocTest::Runner.new(IO.read(file), "inline.rb")
|
86
|
+
# >> r.pass?
|
87
|
+
# => true
|
88
|
+
def pass?
|
89
|
+
prepare_tests
|
90
|
+
@tests.all?{ |t| t.pass? }
|
91
|
+
end
|
92
|
+
|
93
|
+
# === Description
|
94
|
+
# Starts an IRB prompt when the "!!!" SpecialDirective is given.
|
95
|
+
def start_irb
|
96
|
+
IRB.init_config(nil)
|
97
|
+
IRB.conf[:PROMPT_MODE] = :SIMPLE
|
98
|
+
irb = IRB::Irb.new(IRB::WorkSpace.new(TOPLEVEL_BINDING))
|
99
|
+
IRB.conf[:MAIN_CONTEXT] = irb.context
|
100
|
+
catch(:IRB_EXIT) do
|
101
|
+
irb.eval_input
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def format_color(text, color)
|
106
|
+
@@color[RubyDocTest.output_format][color] % text.to_s
|
107
|
+
end
|
108
|
+
|
109
|
+
def escape(text)
|
110
|
+
case RubyDocTest.output_format
|
111
|
+
when :html
|
112
|
+
text.gsub("<", "<").gsub(">", ">")
|
113
|
+
else
|
114
|
+
text
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def run
|
119
|
+
prepare_tests
|
120
|
+
newline = "\n "
|
121
|
+
everything_passed = true
|
122
|
+
puts "=== Testing '#{@file_name}'..."
|
123
|
+
ok, fail, err = 0, 0, 0
|
124
|
+
@tests.each_with_index do |t, index|
|
125
|
+
if SpecialDirective === t and t.name == "!!!"
|
126
|
+
start_irb unless RubyDocTest.ignore_interactive
|
127
|
+
elsif RubyDocTest.tests.nil? or RubyDocTest.tests.include?(index + 1)
|
128
|
+
begin
|
129
|
+
if t.pass?
|
130
|
+
ok += 1
|
131
|
+
status = ["OK".center(4), :green]
|
132
|
+
detail = nil
|
133
|
+
else
|
134
|
+
fail += 1
|
135
|
+
everything_passed = false
|
136
|
+
status = ["FAIL".center(4), :red]
|
137
|
+
|
138
|
+
result_raw = t.first_failed.actual_result
|
139
|
+
got = if result_raw =~ /\n$/ && result_raw.count("\n") > 1
|
140
|
+
"Got: <<-__END__\n#{result_raw}__END__\n "
|
141
|
+
else
|
142
|
+
"Got: #{t.actual_result}#{newline}"
|
143
|
+
end
|
144
|
+
detail = format_color(
|
145
|
+
"#{got}Expected: #{t.expected_result}" + newline +
|
146
|
+
" from #{@file_name}:#{t.first_failed.result.line_number}",
|
147
|
+
:red)
|
148
|
+
|
149
|
+
end
|
150
|
+
rescue EvaluationError => e
|
151
|
+
err += 1
|
152
|
+
status = ["ERR".center(4), :yellow]
|
153
|
+
exception_text = e.original_exception.to_s.split("\n").join(newline)
|
154
|
+
detail = format_color(
|
155
|
+
"#{escape e.original_exception.class.to_s}: #{escape exception_text}" + newline +
|
156
|
+
" from #{@file_name}:#{e.statement.line_number}" + newline +
|
157
|
+
e.statement.source_code,
|
158
|
+
:yellow)
|
159
|
+
if RubyDocTest.verbose
|
160
|
+
detail += format_color(newline + e.original_exception.backtrace.join("\n"), :red)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
puts \
|
164
|
+
"#{((index + 1).to_s + ".").ljust(3)} " +
|
165
|
+
"#{format_color(*status)} | " +
|
166
|
+
"#{t.description.split("\n").join(newline)}" +
|
167
|
+
(detail ? newline + detail : "")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
puts \
|
171
|
+
"#{@blocks.select{ |b| b.is_a? CodeBlock }.size} comparisons, " +
|
172
|
+
"#{@tests.size} doctests, " +
|
173
|
+
"#{fail} failures, " +
|
174
|
+
"#{err} errors"
|
175
|
+
everything_passed
|
176
|
+
end
|
177
|
+
|
178
|
+
# === Tests
|
179
|
+
#
|
180
|
+
# doctest: Non-statement lines get ignored while statement / result lines are included
|
181
|
+
# Default mode is :doctest, so non-irb prompts should be ignored.
|
182
|
+
# >> r = RubyDocTest::Runner.new("a\nb\n >> c = 1\n => 1")
|
183
|
+
# >> groups = r.read_groups
|
184
|
+
# >> groups.size
|
185
|
+
# => 2
|
186
|
+
#
|
187
|
+
# doctest: Group types are correctly created
|
188
|
+
# >> groups.map{ |g| g.class }
|
189
|
+
# => [RubyDocTest::Statement, RubyDocTest::Result]
|
190
|
+
#
|
191
|
+
# doctest: A ruby document can have =begin and =end blocks in it
|
192
|
+
# >> r = RubyDocTest::Runner.new(<<-RUBY, "test.rb")
|
193
|
+
# some_ruby_code = 1
|
194
|
+
# =begin
|
195
|
+
# this is a normal ruby comment
|
196
|
+
# >> z = 10
|
197
|
+
# => 10
|
198
|
+
# =end
|
199
|
+
# more_ruby_code = 2
|
200
|
+
# RUBY
|
201
|
+
# >> groups = r.read_groups
|
202
|
+
# >> groups.size
|
203
|
+
# => 2
|
204
|
+
# >> groups.map{ |g| g.lines.first }
|
205
|
+
# => [" >> z = 10", " => 10"]
|
206
|
+
def read_groups(src_lines = @src_lines, mode = @mode, start_index = 0)
|
207
|
+
groups = []
|
208
|
+
(start_index).upto(src_lines.size) do |index|
|
209
|
+
line = src_lines[index]
|
210
|
+
case mode
|
211
|
+
when :ruby
|
212
|
+
case line
|
213
|
+
|
214
|
+
# Beginning of a multi-line comment section
|
215
|
+
when /^=begin/
|
216
|
+
groups +=
|
217
|
+
# Get statements, results, and directives as if inside a doctest
|
218
|
+
read_groups(src_lines, :doctest_with_end, index)
|
219
|
+
|
220
|
+
else
|
221
|
+
if g = match_group("\\s*#\\s*", src_lines, index)
|
222
|
+
groups << g
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
when :doctest
|
227
|
+
if g = match_group("\\s*", src_lines, index)
|
228
|
+
groups << g
|
229
|
+
end
|
230
|
+
|
231
|
+
when :doctest_with_end
|
232
|
+
break if line =~ /^=end/
|
233
|
+
if g = match_group("\\s*", src_lines, index)
|
234
|
+
groups << g
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|
239
|
+
groups
|
240
|
+
end
|
241
|
+
|
242
|
+
def match_group(prefix, src_lines, index)
|
243
|
+
case src_lines[index]
|
244
|
+
|
245
|
+
# An irb '>>' marker after a '#' indicates an embedded doctest
|
246
|
+
when /^(#{prefix})>>(\s|\s*$)/
|
247
|
+
Statement.new(src_lines, index, @file_name)
|
248
|
+
|
249
|
+
# An irb '=>' marker after a '#' indicates an embedded result
|
250
|
+
when /^(#{prefix})=>\s/
|
251
|
+
Result.new(src_lines, index)
|
252
|
+
|
253
|
+
# Whenever we match a directive (e.g. 'doctest'), add that in as well
|
254
|
+
when /^(#{prefix})(#{SpecialDirective::NAMES_FOR_RX})(.*)$/
|
255
|
+
SpecialDirective.new(src_lines, index)
|
256
|
+
|
257
|
+
else
|
258
|
+
nil
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# === Tests
|
263
|
+
#
|
264
|
+
# doctest: The organize_blocks method should separate Statement, Result and SpecialDirective
|
265
|
+
# objects into CodeBlocks.
|
266
|
+
# >> r = RubyDocTest::Runner.new(">> t = 1\n>> t + 2\n=> 3\n>> u = 1", "test.doctest")
|
267
|
+
# >> r.prepare_tests
|
268
|
+
#
|
269
|
+
# >> r.blocks.first.statements.map{|s| s.lines}
|
270
|
+
# => [[">> t = 1"], [">> t + 2"]]
|
271
|
+
#
|
272
|
+
# >> r.blocks.first.result.lines
|
273
|
+
# => ["=> 3"]
|
274
|
+
#
|
275
|
+
# >> r.blocks.last.statements.map{|s| s.lines}
|
276
|
+
# => [[">> u = 1"]]
|
277
|
+
#
|
278
|
+
# >> r.blocks.last.result
|
279
|
+
# => nil
|
280
|
+
#
|
281
|
+
# doctest: Two doctest directives--each having its own statement--should be separated properly
|
282
|
+
# by organize_blocks.
|
283
|
+
# >> r = RubyDocTest::Runner.new("doctest: one\n>> t = 1\ndoctest: two\n>> t + 2", "test.doctest")
|
284
|
+
# >> r.prepare_tests
|
285
|
+
# >> r.blocks.map{|b| b.class}
|
286
|
+
# => [RubyDocTest::SpecialDirective, RubyDocTest::CodeBlock,
|
287
|
+
# RubyDocTest::SpecialDirective, RubyDocTest::CodeBlock]
|
288
|
+
#
|
289
|
+
# >> r.blocks[0].value
|
290
|
+
# => "one"
|
291
|
+
#
|
292
|
+
# >> r.blocks[1].statements.map{|s| s.lines}
|
293
|
+
# => [[">> t = 1"]]
|
294
|
+
#
|
295
|
+
# >> r.blocks[2].value
|
296
|
+
# => "two"
|
297
|
+
#
|
298
|
+
# >> r.blocks[3].statements.map{|s| s.lines}
|
299
|
+
# => [[">> t + 2"]]
|
300
|
+
def organize_blocks(groups = @groups)
|
301
|
+
blocks = []
|
302
|
+
current_statements = []
|
303
|
+
groups.each do |g|
|
304
|
+
case g
|
305
|
+
when Statement
|
306
|
+
current_statements << g
|
307
|
+
when Result
|
308
|
+
blocks << CodeBlock.new(current_statements, g)
|
309
|
+
current_statements = []
|
310
|
+
when SpecialDirective
|
311
|
+
case g.name
|
312
|
+
when "doctest:", "it:"
|
313
|
+
blocks << CodeBlock.new(current_statements) unless current_statements.empty?
|
314
|
+
current_statements = []
|
315
|
+
blocks << g
|
316
|
+
when "doctest_require:"
|
317
|
+
doctest_require = eval(g.value, TOPLEVEL_BINDING, @file_name, g.line_number)
|
318
|
+
if doctest_require.is_a? String
|
319
|
+
require_relative_to_file_name(doctest_require, @file_name)
|
320
|
+
end
|
321
|
+
blocks << g
|
322
|
+
when "!!!"
|
323
|
+
# ignore
|
324
|
+
unless RubyDocTest.ignore_interactive
|
325
|
+
fake_statement = Object.new
|
326
|
+
runner = self
|
327
|
+
(class << fake_statement; self; end).send(:define_method, :evaluate) do
|
328
|
+
runner.start_irb
|
329
|
+
end
|
330
|
+
current_statements << fake_statement
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
blocks << CodeBlock.new(current_statements) unless current_statements.empty?
|
336
|
+
blocks
|
337
|
+
end
|
338
|
+
|
339
|
+
def require_relative_to_file_name(file_name, relative_to)
|
340
|
+
load_path = $:.dup
|
341
|
+
$:.unshift File.expand_path(File.join(File.dirname(relative_to), File.dirname(file_name)))
|
342
|
+
if RubyDocTest.verbose
|
343
|
+
puts "doctest_require: [#{File.expand_path(File.join(File.dirname(relative_to), File.dirname(file_name)))}] #{File.basename(file_name)}"
|
344
|
+
end
|
345
|
+
require File.basename(file_name)
|
346
|
+
ensure
|
347
|
+
$:.shift
|
348
|
+
end
|
349
|
+
|
350
|
+
# === Tests
|
351
|
+
#
|
352
|
+
# doctest: Tests should be organized into groups based on the 'doctest' SpecialDirective
|
353
|
+
# >> r = RubyDocTest::Runner.new("doctest: one\n>> t = 1\ndoctest: two\n>> t + 2", "test.doctest")
|
354
|
+
# >> r.prepare_tests
|
355
|
+
# >> r.tests.size
|
356
|
+
# => 2
|
357
|
+
# >> r.tests[0].code_blocks.map{|c| c.statements}.flatten.map{|s| s.lines}
|
358
|
+
# => [[">> t = 1"]]
|
359
|
+
# >> r.tests[1].code_blocks.map{|c| c.statements}.flatten.map{|s| s.lines}
|
360
|
+
# => [[">> t + 2"]]
|
361
|
+
# >> r.tests[0].description
|
362
|
+
# => "one"
|
363
|
+
# >> r.tests[1].description
|
364
|
+
# => "two"
|
365
|
+
#
|
366
|
+
# doctest: Without a 'doctest' SpecialDirective, there is one Test called "Default Test".
|
367
|
+
# >> r = RubyDocTest::Runner.new(">> t = 1\n>> t + 2\n=> 3\n>> u = 1", "test.doctest")
|
368
|
+
# >> r.prepare_tests
|
369
|
+
# >> r.tests.size
|
370
|
+
# => 1
|
371
|
+
#
|
372
|
+
# >> r.tests.first.description
|
373
|
+
# => "Default Test"
|
374
|
+
#
|
375
|
+
# >> r.tests.first.code_blocks.size
|
376
|
+
# => 2
|
377
|
+
#
|
378
|
+
# doctest: When using the "it:" directive, it should re-append "it" to the description;
|
379
|
+
# >> r = RubyDocTest::Runner.new("it: should behave\n>> t = 1\n>> t + 2\n=> 3\n>> u = 1", "test.doctest")
|
380
|
+
# >> r.prepare_tests
|
381
|
+
# >> r.tests.size
|
382
|
+
# => 1
|
383
|
+
#
|
384
|
+
# >> r.tests.first.description
|
385
|
+
# => "it should behave"
|
386
|
+
#
|
387
|
+
# >> r.tests.first.code_blocks.size
|
388
|
+
# => 2
|
389
|
+
def organize_tests(blocks = @blocks)
|
390
|
+
tests = []
|
391
|
+
assigned_blocks = nil
|
392
|
+
unassigned_blocks = []
|
393
|
+
blocks.each do |g|
|
394
|
+
case g
|
395
|
+
when CodeBlock
|
396
|
+
(assigned_blocks || unassigned_blocks) << g
|
397
|
+
when SpecialDirective
|
398
|
+
case g.name
|
399
|
+
when "doctest:"
|
400
|
+
assigned_blocks = []
|
401
|
+
tests << Test.new(g.value, assigned_blocks)
|
402
|
+
when "it:"
|
403
|
+
assigned_blocks = []
|
404
|
+
tests << Test.new("it #{g.value}", assigned_blocks)
|
405
|
+
when "!!!"
|
406
|
+
tests << g
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
tests << Test.new("Default Test", unassigned_blocks) unless unassigned_blocks.empty?
|
411
|
+
tests
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|