My favorites | Sign in
Project Logo
                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# todo:
# the 'inline for rubydoc' option
# low priority:
# make it even more robust to errors--if it errs, look for internal hashes, convert
# Fix so raising errors validates -- currently ignores
# multi line strings
# option to run each in its own directory
# tell them how to change dirs on their own [add an end guy?]
# #doctest_end
# WAY more line logic
# some way of reusing tests, like classes [?] or functions :) -- this doctest gets this replaced with this :P
# normal code inline [maybe that can raise if it errs, maybe more]
# check python's --what they got?
# use rspec, how can it improve?
# todo better params

if ARGV[0] == '--help'
print "
use: doctest file_name.rb (or directory_name): default '.'
-- note it recurses and scans all .rb in that directory and its subdirectories
ex: doctest .
doctest file.rb
doctest dir #also scans its subdirs
"
exit
end

BINDING = binding()

class DocTest
CODE_REGEX = Regexp.new(/^(>>|irb.*?>) (.*)/)
RESULT_REGEX = Regexp.new(/^=> (.*)/)
EXCEPTION_REGEX = Regexp.new(/^([A-Z][A-Za-z0-9]*):/)

def get_ruby_files(dir_name)
ruby_file_names = []

Dir.foreach(dir_name) do |file_name|
unless file_name == '.' || file_name == '..'
full_name = File.join(dir_name, file_name)
if /.*\.rb$/ =~ full_name
ruby_file_names << full_name
elsif File.directory? full_name
sub_files = get_ruby_files(full_name)
ruby_file_names.concat(sub_files) unless sub_files.empty?
end
end
end

ruby_file_names
end
=begin
#doctest normalize substring
>> a = DocTest.new
=> #<DocTest:0x37012c>
>> a.normalize_result('0xtion:0x1876bc0 @@p')
=> "0xtion:0xXXXXXXXX @@p"
=end
def normalize_result(input)
input.gsub(/:0x([a-f0-9]){5,8}/, ':0xXXXXXXXX') # makes them all 8 digits long
end

def failure_report(statement, expected_result, result)
report = "\n FAILED" #add line number logic here
report << " Code: " << statement << "\n"
report << " Expected: " << expected_result << "\n"
report << " But got: " << result
end

=begin
#doctest should match with hashes
>> {1=>1, 2=>2, 3=> 3, 4=>4,5=>5}
=> {5=>5, 1=>1, 2=>2, 3=>3, 4=>4}
now test with different ordered hashes
>> {1=>1, 2=>2, 3=> 3, 4=>4,5=>5}
=> {4=>4, 1=>1, 2=>2, 3=>3, 5=>5}
>> {1=>":0x123456", 2=>2, 3=> 3, 4=>4,5=>5}
=> {4=>4, 1=>":0x123456", 2=>2, 3=>3, 5=>5}
=end
def dbg
require 'rubygems'; require 'ruby-debug'; debugger
end
def run_doc_tests(doc_test)
statement, report = '', ''
wrong, passed = 0, 0
doc_test.split("\n").each do |line|
case line
when CODE_REGEX
statement << CODE_REGEX.match(line)[2]
when RESULT_REGEX, EXCEPTION_REGEX
if line =~ RESULT_REGEX
expected_result_string = normalize_result(RESULT_REGEX.match(line)[1])
else
raise unless line =~ EXCEPTION_REGEX
expected_result_string = $1
end

begin
result_we_got = eval(statement, BINDING)
rescue Exception => e
result_we_got = e.class
end

they_match = false
if result_we_got.class.ancestors.include? Hash
# change them to 'kind of real' hashes, so that we cancompare them and have comparison work--hashes sometimes display in different orders when printed
expected_result = eval(expected_result_string, BINDING)
if eval(normalize_result(result_we_got.inspect), BINDING) == expected_result # todo some tests for this with whack-o stuff thrown in :)
# the Hashes matched, string-wise
they_match = true
end
end

they_match = true if expected_result_string =~ /#doctest_fail_ok/
result_string = normalize_result(result_we_got.inspect)
they_match = true if result_string == expected_result_string
unless they_match
report << failure_report(statement, expected_result_string, result_string)
wrong += 1
else
passed += 1
end
statement = '' # reset it for the next round
end
end
return passed, wrong, report
end

def process_ruby_file(file_name)
tests, succeeded, failed = 0, 0, 0
file_report = ''
code = File.read file_name

startup_code_for_this_file = code.scan(/begin\s#setup_doctest once_per_file(.*?)=end/m)

if startup_code_for_this_file.length > 0
raise 'can only do one_time_file_setup declaration once' if startup_code_for_this_file.length > 1 or startup_code_for_this_file[0].length > 1
startup_code_for_this_file = startup_code_for_this_file[0][0]
begin
eval startup_code_for_this_file, BINDING
rescue Exception => e
print "Uh oh unable to execute startup code for #{file_name}...continuing #{e}\n"
end
end

# todo would be nice to have multiple tests in the same comment block
# so a scan + sub scan for doctests
code.scan(/=begin\s#doctest([^\n]*)\n(.*?)=end/m) do |doc_test| # could do--replace default named ones with their line number :)
require file_name # might as well have its functions available to itself :P
# todo could tear out anything loaded after each file, I suppose, as active support does
file_report << "\n Testing '#{doc_test[0]}'..."
passed, wrong, report = run_doc_tests(doc_test[1])
file_report += (wrong == 0 ? "OK" : report)
tests += 1
succeeded += passed
failed += wrong
end
file_report = "Processing '#{file_name}' from current directory " + file_report unless file_report.empty?
return tests, succeeded, failed, file_report
end
def DocTest.run
# parse command line--currently just 'filename' or 'directory name'
runner = DocTest.new
if File.directory? ARGV[0] || ''
ruby_file_names = runner.get_ruby_files(ARGV[0])
elsif File.exist? ARGV[0] || ''
ruby_file_names = [ARGV[0]]
else
ruby_file_names = runner.get_ruby_files('.')
end

total_report = "Looking for doctests in a total of #{ruby_file_names.length} possible files\n"
total_files, total_tests, total_succeeded, total_failed = 0, 0, 0, 0
ruby_file_names.each do |ruby_file_name|
tests, succeeded, failed, report = runner.process_ruby_file(ruby_file_name)
total_files += 1 if tests > 0
total_tests += tests
total_succeeded += succeeded
total_failed += failed
total_report << report << "\n" unless report.empty?
end
total_report << "Total files: #{total_files}, total tests: #{total_tests}, assertions succeeded: #{total_succeeded}, assertions failed: #{total_failed}"
puts total_report
end

end

if $0 == __FILE__
DocTest.run
end
Show details Hide details

Change log

r48 by rogerpack2005 on Apr 12, 2008   Diff
allow binary to work from gem
Go to: 
Project members, sign in to write a code review

Older revisions

r46 by rogerpack2005 on Apr 12, 2008   Diff
[No log message]
r42 by rogerpack2005 on Apr 12, 2008   Diff
yes
r41 by rogerpack2005 on Apr 12, 2008   Diff
now works real with exceptions
All revisions of this file

File info

Size: 6638 bytes, 190 lines
Hosted by Google Code