rdoctest 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +155 -0
- data/Rakefile +5 -0
- data/bin/rdoctest +31 -0
- data/lib/rdoctest/runner.rb +152 -0
- data/lib/rdoctest/task.rb +79 -0
- data/lib/rdoctest/version.rb +9 -0
- data/lib/rdoctest.rb +2 -0
- metadata +71 -0
data/README.rdoc
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
= Rdoctest
|
2
|
+
|
3
|
+
http://github.com/stephencelis/rdoctest
|
4
|
+
|
5
|
+
A {doctest}[http://docs.python.org/py3k/library/doctest.html] for Ruby.
|
6
|
+
|
7
|
+
|
8
|
+
== Why?
|
9
|
+
|
10
|
+
You'll improve your test coverage and documentation in one fell swoop.
|
11
|
+
|
12
|
+
|
13
|
+
== How?
|
14
|
+
|
15
|
+
Install the gem:
|
16
|
+
|
17
|
+
% [sudo] gem install rdoctest
|
18
|
+
|
19
|
+
|
20
|
+
Use the CLI:
|
21
|
+
|
22
|
+
% rdoctest lib/**/*.rb
|
23
|
+
Loaded suite /.../bin/rdoctest
|
24
|
+
Started
|
25
|
+
.
|
26
|
+
Finished in ... seconds.
|
27
|
+
|
28
|
+
1 tests, 7 assertions, 0 failures, 0 errors, 0 skips
|
29
|
+
|
30
|
+
|
31
|
+
== Examples
|
32
|
+
|
33
|
+
=== Formatting
|
34
|
+
|
35
|
+
Rdoctest expects well-formatted RDoc. This means:
|
36
|
+
|
37
|
+
* Indent examples by 2 spaces.
|
38
|
+
|
39
|
+
# For example:
|
40
|
+
#
|
41
|
+
# 1 + 1 # This code is indented by 2 spaces.
|
42
|
+
|
43
|
+
|
44
|
+
* If your example is a block of code, use <tt># =></tt> to designate the
|
45
|
+
expected result.
|
46
|
+
|
47
|
+
# Short blocks can have the result on the same line:
|
48
|
+
#
|
49
|
+
# 1 + 1 # => 2
|
50
|
+
#
|
51
|
+
# Longer blocks can have the result on the line following:
|
52
|
+
#
|
53
|
+
# def hello_world
|
54
|
+
# puts "Hello, world!"
|
55
|
+
# end
|
56
|
+
# # => nil
|
57
|
+
|
58
|
+
|
59
|
+
* If your example is an IRB session, use <tt>>></tt> and <tt>=></tt>
|
60
|
+
accordingly.
|
61
|
+
|
62
|
+
# Simple:
|
63
|
+
#
|
64
|
+
# >> 1 + 1
|
65
|
+
# => 2
|
66
|
+
#
|
67
|
+
# More complex:
|
68
|
+
#
|
69
|
+
# >> def hello_world
|
70
|
+
# >> puts "Hello, world!"
|
71
|
+
# >> end
|
72
|
+
# => nil
|
73
|
+
# >> hello_world
|
74
|
+
# Hello, world!
|
75
|
+
# => nil
|
76
|
+
#
|
77
|
+
# Use ellipses for partial matches:
|
78
|
+
#
|
79
|
+
# >> goodbye_world
|
80
|
+
# NameError: undefined local variable or method `goodbye_world'...
|
81
|
+
|
82
|
+
|
83
|
+
=== CLI
|
84
|
+
|
85
|
+
Rdoctest's CLI works a lot like Ruby's.
|
86
|
+
|
87
|
+
% rdoctest -h
|
88
|
+
Usage: rdoctest [options] [file ...]
|
89
|
+
-Idirectory
|
90
|
+
-rlibrary
|
91
|
+
...
|
92
|
+
|
93
|
+
If you're testing a complex project, make sure you prepare necessary libraries
|
94
|
+
and load paths:
|
95
|
+
|
96
|
+
% rdoctest -Ilib -rmylibrary lib/**/*.rb
|
97
|
+
|
98
|
+
|
99
|
+
=== Rake
|
100
|
+
|
101
|
+
Rdoctest comes with a Rake task that you can load in your Rakefile.
|
102
|
+
|
103
|
+
require 'rdoctest/task'
|
104
|
+
Rdoctest::Task.new
|
105
|
+
|
106
|
+
|
107
|
+
And run in your project:
|
108
|
+
|
109
|
+
% rake doctest
|
110
|
+
|
111
|
+
|
112
|
+
It comes with a similar configuration to Rake's TestTask.
|
113
|
+
|
114
|
+
Rdoctest::Task.new :test do |t|
|
115
|
+
t.libs << 'lib' # The 'lib' directory is loaded by default,
|
116
|
+
t.ruby_opts << '-rrdoctest' # but require needed files with +ruby_opts+.
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
== Roadmap
|
121
|
+
|
122
|
+
* Better detection of code blocks (the 3-space indent is too restrictive).
|
123
|
+
* Test plain text files (READMEs, for example).
|
124
|
+
* Autotest integration?
|
125
|
+
* Test shell snippets (beginning with <tt>$</tt> and <tt>%</tt>)?
|
126
|
+
|
127
|
+
|
128
|
+
== Prior Art
|
129
|
+
|
130
|
+
{rubydoctest}[http://github.com/tablatom/rubydoctest].
|
131
|
+
|
132
|
+
|
133
|
+
== License
|
134
|
+
|
135
|
+
(The MIT License)
|
136
|
+
|
137
|
+
(c) 2010 Stephen Celis <stephen@stephencelis.com>
|
138
|
+
|
139
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
140
|
+
of this software and associated documentation files (the "Software"), to deal
|
141
|
+
in the Software without restriction, including without limitation the rights
|
142
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
143
|
+
copies of the Software, and to permit persons to whom the Software is
|
144
|
+
furnished to do so, subject to the following conditions:
|
145
|
+
|
146
|
+
The above copyright notice and this permission notice shall be included in all
|
147
|
+
copies or substantial portions of the Software.
|
148
|
+
|
149
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
150
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
151
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
152
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
153
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
154
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
155
|
+
SOFTWARE.
|
data/Rakefile
ADDED
data/bin/rdoctest
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
$LOAD_PATH.unshift lib
|
5
|
+
|
6
|
+
options = OptionParser.new do |option|
|
7
|
+
option.banner = "Usage: #{basename = File.basename $0} [options] [file ...]"
|
8
|
+
option.on('-Idirectory') { |v|
|
9
|
+
$LOAD_PATH.concat v.split File::PATH_SEPARATOR
|
10
|
+
}
|
11
|
+
option.on('-rlibrary') { |v|
|
12
|
+
require v
|
13
|
+
}
|
14
|
+
option.on('--version') {
|
15
|
+
require 'rdoctest/version'
|
16
|
+
puts "#{basename} #{Rdoctest::Version::VERSION}"
|
17
|
+
exit
|
18
|
+
}
|
19
|
+
option.on('-h', '--help') {
|
20
|
+
puts option
|
21
|
+
exit
|
22
|
+
}
|
23
|
+
end
|
24
|
+
options.parse!
|
25
|
+
|
26
|
+
if ARGF.argv.empty?
|
27
|
+
puts options
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
|
31
|
+
load 'rdoctest/runner.rb'
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'strscan'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
module Rdoctest
|
6
|
+
# The Runner takes RDoc example text and creates tests for them.
|
7
|
+
#
|
8
|
+
# ==== Example
|
9
|
+
#
|
10
|
+
# Writing inline code that ends with <tt># =></tt> and the result will create
|
11
|
+
# an assertion that the code evaluation is equal to that result.
|
12
|
+
#
|
13
|
+
# 1 + 1
|
14
|
+
# # => 2
|
15
|
+
#
|
16
|
+
# You can also write an assertion on a single line.
|
17
|
+
#
|
18
|
+
# 2 + 2 # => 4
|
19
|
+
#
|
20
|
+
# If you want to assert errors or output, mimic an IRB session.
|
21
|
+
#
|
22
|
+
# >> puts 'hello world'
|
23
|
+
# hello world
|
24
|
+
# => nil
|
25
|
+
# >> def ok
|
26
|
+
# >> a
|
27
|
+
# >> end
|
28
|
+
# => nil
|
29
|
+
#
|
30
|
+
# Use ellipses for partial matches.
|
31
|
+
#
|
32
|
+
# >> a
|
33
|
+
# NameError: undefined local variable or method `a'...
|
34
|
+
class Runner
|
35
|
+
@@ruby = /(.+?)# =>([^\n]+\n)/m
|
36
|
+
@@irb = /((?:^>> [^\n]+\n)+)((?:(?!^(?:>>|=>))[^\n]+\n)*)(^=> [^\n]+)?/m
|
37
|
+
|
38
|
+
attr_reader :files
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@files = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def run
|
45
|
+
parse
|
46
|
+
execute
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def parse
|
52
|
+
filename = lineno = in_comment = in_test = nil
|
53
|
+
|
54
|
+
ARGF.each do |line|
|
55
|
+
lineno, filename = 0, ARGF.filename if filename != ARGF.filename
|
56
|
+
lineno += 1
|
57
|
+
|
58
|
+
next in_comment = nil unless line.sub! /^ *# ?/, ''
|
59
|
+
in_comment = lineno if line =~ /^={1,6} \S/
|
60
|
+
in_comment ||= lineno
|
61
|
+
|
62
|
+
if in_test
|
63
|
+
in_test = false if line !~ /^(?: {2,}|$)/
|
64
|
+
else
|
65
|
+
in_test = true if line =~ /^ {2}\S/
|
66
|
+
end
|
67
|
+
|
68
|
+
line = "\n" unless in_test
|
69
|
+
((files[filename] ||= {})[in_comment] ||= '') << line if in_comment
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def execute
|
74
|
+
files.each_pair do |filename, lineno_and_lines|
|
75
|
+
class_name = File.basename(filename, '.rb')
|
76
|
+
class_name.gsub!(/(?:^|_)(\w+:{,2})/) { |r| r.capitalize }
|
77
|
+
class_name.gsub! /\W+/, ''
|
78
|
+
class_name = 'Rdoctest' if class_name.empty?
|
79
|
+
require_filename filename
|
80
|
+
|
81
|
+
test_class = Class.new Test::Unit::TestCase do
|
82
|
+
lineno_and_lines.each_pair do |lineno, lines|
|
83
|
+
next unless lines =~ /\S/ # /(?:^|# )=>/
|
84
|
+
lines.gsub! /^ /, ''
|
85
|
+
|
86
|
+
define_method "assert_eval" do |expected, result, filename, lineno|
|
87
|
+
if expected.gsub!(/\.{3,}/, '.*')
|
88
|
+
assertion, expected = 'match', /#{expected}/
|
89
|
+
else
|
90
|
+
assertion = 'equal'
|
91
|
+
end
|
92
|
+
|
93
|
+
instance_eval <<ASSERTION, filename, lineno
|
94
|
+
assert_#{assertion} #{expected.inspect}, #{result.inspect}
|
95
|
+
ASSERTION
|
96
|
+
end
|
97
|
+
|
98
|
+
define_method "test_line_#{lineno}" do
|
99
|
+
assertions = []
|
100
|
+
scanner = StringScanner.new lines
|
101
|
+
|
102
|
+
binding = send :binding
|
103
|
+
while scanner.skip_until(@@ruby)
|
104
|
+
code_lineno = lineno + scanner.pre_match.count("\n")
|
105
|
+
expected_lineno = code_lineno + scanner[1].count("\n")
|
106
|
+
|
107
|
+
result = eval scanner[1], binding #, filename, code_lineno
|
108
|
+
assert_eval scanner[2].strip, result.inspect, filename,
|
109
|
+
expected_lineno
|
110
|
+
end
|
111
|
+
|
112
|
+
scanner.pos = 0
|
113
|
+
binding = send :binding
|
114
|
+
while scanner.skip_until(@@irb)
|
115
|
+
code_lineno = lineno + scanner.pre_match.count("\n")
|
116
|
+
output_lineno = code_lineno + scanner[2].to_s.count("\n")
|
117
|
+
expected_lineno = output_lineno + scanner[3].to_s.count("\n")
|
118
|
+
|
119
|
+
begin
|
120
|
+
stdout, $stdout = $stdout, StringIO.new
|
121
|
+
result = eval scanner[1].gsub(/^>> /, ''), binding
|
122
|
+
rescue => e
|
123
|
+
puts "#{e.class}: #{e}"
|
124
|
+
ensure
|
125
|
+
$stdout, stdout = stdout, $stdout
|
126
|
+
end
|
127
|
+
|
128
|
+
stdout.rewind
|
129
|
+
output = stdout.read
|
130
|
+
assert_eval scanner[2], output, filename, output_lineno
|
131
|
+
|
132
|
+
if scanner[3]
|
133
|
+
expected = scanner[3].sub(/^=> /, '').strip
|
134
|
+
assert_eval expected, result.inspect, filename,
|
135
|
+
expected_lineno
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
Object.const_set "#{class_name}Test", test_class
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def require_filename filename
|
147
|
+
require filename.gsub(%r{^(?:lib)/|.rb$}, '') unless filename == '-'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
Rdoctest::Runner.new.run
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
|
4
|
+
module Rdoctest
|
5
|
+
# See the README for more information.
|
6
|
+
class Task < Rake::TaskLib
|
7
|
+
# :enddoc:
|
8
|
+
attr_accessor :libs
|
9
|
+
attr_accessor :name
|
10
|
+
attr_accessor :options
|
11
|
+
attr_accessor :pattern
|
12
|
+
attr_accessor :ruby_opts
|
13
|
+
attr_accessor :test_files
|
14
|
+
attr_accessor :verbose
|
15
|
+
attr_accessor :warning
|
16
|
+
|
17
|
+
def initialize name = :doctest
|
18
|
+
@libs = %w(lib)
|
19
|
+
@name = name
|
20
|
+
@options = nil
|
21
|
+
@pattern = nil
|
22
|
+
@ruby_opts = []
|
23
|
+
@test_files = nil
|
24
|
+
@verbose = false
|
25
|
+
@warning = false
|
26
|
+
yield self if block_given?
|
27
|
+
@pattern = 'lib/**/*.rb' if @pattern.nil? && @test_files.nil?
|
28
|
+
define
|
29
|
+
end
|
30
|
+
|
31
|
+
def define
|
32
|
+
desc name ? "Run doctests for #{name}" : 'Run doctests'
|
33
|
+
task name do
|
34
|
+
ruby "#{run_code} #{ruby_opts_string} #{file_list_string}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def ruby_opts_string
|
41
|
+
opts = []
|
42
|
+
opts << %(-I"#{lib_path}") unless libs.empty?
|
43
|
+
opts << %(-w) if warning
|
44
|
+
opts.concat ruby_opts
|
45
|
+
opts.join ' '
|
46
|
+
end
|
47
|
+
|
48
|
+
def lib_path
|
49
|
+
libs.join File::PATH_SEPARATOR
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_code
|
53
|
+
$LOAD_PATH.each do |path|
|
54
|
+
file = File.join path, 'rdoctest'
|
55
|
+
return file if File.executable?(file)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def file_list_string
|
60
|
+
file_list.join ' '
|
61
|
+
# Dir[*file_list].join ' '
|
62
|
+
end
|
63
|
+
|
64
|
+
def file_list
|
65
|
+
if ENV['TEST']
|
66
|
+
Dir[ENV['TEST']]
|
67
|
+
else
|
68
|
+
result = []
|
69
|
+
result += test_files.to_a if test_files
|
70
|
+
result << pattern if pattern
|
71
|
+
result
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def options_list
|
76
|
+
ENV['TESTOPTS'] || @options
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/rdoctest.rb
ADDED
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rdoctest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Stephen Celis
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-12-05 00:00:00 -06:00
|
18
|
+
default_executable: rdoctest
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Scans RDoc text for examples and tests them.
|
22
|
+
email: stephen@stephencelis.com
|
23
|
+
executables:
|
24
|
+
- rdoctest
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.rdoc
|
29
|
+
files:
|
30
|
+
- README.rdoc
|
31
|
+
- Rakefile
|
32
|
+
- lib/rdoctest/runner.rb
|
33
|
+
- lib/rdoctest/task.rb
|
34
|
+
- lib/rdoctest/version.rb
|
35
|
+
- lib/rdoctest.rb
|
36
|
+
- bin/rdoctest
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://github.com/stephencelis/rdoctest
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options:
|
43
|
+
- --main
|
44
|
+
- README.rdoc
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
version: "0"
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.7
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: A doctest for Ruby.
|
70
|
+
test_files: []
|
71
|
+
|