fit 1.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.
- data/README.txt +203 -0
- data/Rakefile +111 -0
- data/bin/FitServer.rb +6 -0
- data/bin/fit +10 -0
- data/bin/fit.cgi +36 -0
- data/doc/examples/AllCombinations.html +55 -0
- data/doc/examples/AllFiles.html +60 -0
- data/doc/examples/AllPairs/function/cosine.html +57 -0
- data/doc/examples/AllPairs/function/sine.html +57 -0
- data/doc/examples/AllPairs/magnitude/180+30.html +45 -0
- data/doc/examples/AllPairs/magnitude/30.html +33 -0
- data/doc/examples/AllPairs/magnitude/360+30.html +45 -0
- data/doc/examples/AllPairs/magnitude/90-30.html +45 -0
- data/doc/examples/AllPairs/sign/change-sign.html +27 -0
- data/doc/examples/AllPairs/sign/multiply.html +31 -0
- data/doc/examples/AllPairs/sign/no-change.html +23 -0
- data/doc/examples/AllPairs.html +51 -0
- data/doc/examples/BinaryChop.html +89 -0
- data/doc/examples/CalculatorExample.html +108 -0
- data/doc/examples/ColumnIndex.html +43 -0
- data/doc/examples/ExampleTests.html +43 -0
- data/doc/examples/FitAcceptanceTests.html +53 -0
- data/doc/examples/GeoCoordinate.html +87 -0
- data/doc/examples/MusicExample.html +143 -0
- data/doc/examples/MusicExampleWithErrors.html +128 -0
- data/doc/examples/NetworkExample.html +47 -0
- data/doc/examples/WebPageExample.html +92 -0
- data/doc/examples/arithmetic.html +211 -0
- data/doc/examples/files/hp35bk.jpg +0 -0
- data/doc/examples/logo.gif +0 -0
- data/doc/fitnesse/FitNesse.RubY.AcceptanceTests.FixtureParameters.html +81 -0
- data/doc/fitnesse/FitNesse.RubY.AcceptanceTests.GracefulFixtureNames.html +87 -0
- data/doc/fitnesse/FitNesse.RubY.AcceptanceTests.GracefulMemberNames.html +73 -0
- data/doc/fitnesse/FitNesse.RubY.AcceptanceTests.ImportFixture.html +61 -0
- data/doc/fitnesse/FitNesse.RubY.AcceptanceTests.WaysToSpecifyaFixtureNamespace.html +81 -0
- data/doc/spec/annotation.html +3833 -0
- data/doc/spec/extensions.html +302 -0
- data/doc/spec/fixtures.html +5181 -0
- data/doc/spec/index.html +947 -0
- data/doc/spec/parse.html +3094 -0
- data/lib/eg/all_combinations.rb +44 -0
- data/lib/eg/all_files.rb +94 -0
- data/lib/eg/all_pairs.rb +172 -0
- data/lib/eg/arithmetic_column_fixture.rb +35 -0
- data/lib/eg/arithmetic_fixture.rb +29 -0
- data/lib/eg/binary_chop.rb +100 -0
- data/lib/eg/calculator.rb +69 -0
- data/lib/eg/column_index.rb +85 -0
- data/lib/eg/division.rb +13 -0
- data/lib/eg/echo_args_fixture.rb +9 -0
- data/lib/eg/example_tests.rb +84 -0
- data/lib/eg/music/Music.txt +38 -0
- data/lib/eg/music/browser.rb +60 -0
- data/lib/eg/music/display.rb +24 -0
- data/lib/eg/music/music.rb +67 -0
- data/lib/eg/music/music_library.rb +70 -0
- data/lib/eg/music/music_player.rb +77 -0
- data/lib/eg/music/realtime.rb +39 -0
- data/lib/eg/music/simulator.rb +81 -0
- data/lib/eg/nested/bob.rb +12 -0
- data/lib/eg/nested/bob_the_builder_fixture.rb +11 -0
- data/lib/eg/net/simulator.rb +69 -0
- data/lib/eg/page.rb +91 -0
- data/lib/eg/sqrt.rb +19 -0
- data/lib/fat/annotation_fixture.rb +83 -0
- data/lib/fat/color.rb +45 -0
- data/lib/fat/divide.rb +13 -0
- data/lib/fat/document_parse_fixture.rb +67 -0
- data/lib/fat/equals.rb +59 -0
- data/lib/fat/fixture_name_fixture.rb +67 -0
- data/lib/fat/html_to_text_fixture.rb +20 -0
- data/lib/fat/money.rb +18 -0
- data/lib/fat/output_fixture.rb +32 -0
- data/lib/fat/parse_fixture.rb +92 -0
- data/lib/fat/reference_fixture.rb +31 -0
- data/lib/fat/standard_annotation_fixture.rb +58 -0
- data/lib/fat/string_writer.rb +12 -0
- data/lib/fat/table.rb +23 -0
- data/lib/fat/table_parse_fixture.rb +33 -0
- data/lib/fat/text_to_html_fixture.rb +21 -0
- data/lib/fit/action_fixture.rb +65 -0
- data/lib/fit/column_fixture.rb +104 -0
- data/lib/fit/file_runner.rb +80 -0
- data/lib/fit/fit_protocol.rb +62 -0
- data/lib/fit/fit_server.rb +173 -0
- data/lib/fit/fixture.rb +309 -0
- data/lib/fit/fixture_loader.rb +75 -0
- data/lib/fit/import_fixture.rb +9 -0
- data/lib/fit/parse.rb +206 -0
- data/lib/fit/primitive_fixture.rb +52 -0
- data/lib/fit/row_fixture.rb +188 -0
- data/lib/fit/scientific_double.rb +70 -0
- data/lib/fit/summary.rb +46 -0
- data/lib/fit/timed_action_fixture.rb +35 -0
- data/lib/fit/type_adapter.rb +95 -0
- data/lib/fit/wiki_runner.rb +15 -0
- data/lib/fittask.rb +184 -0
- data/test/all_tests.rb +13 -0
- data/test/file_runner_test.rb +52 -0
- data/test/fit_server_test.rb +214 -0
- data/test/fixture_loader_test.rb +71 -0
- data/test/fixture_test.rb +41 -0
- data/test/fixtures/fail_fixture.rb +9 -0
- data/test/fixtures/pass_fixture.rb +9 -0
- data/test/framework_test.rb +51 -0
- data/test/parse_test.rb +101 -0
- data/test/row_fixture_test.rb +44 -0
- data/test/scientific_double_test.rb +35 -0
- data/test/type_adapter_test.rb +120 -0
- metadata +165 -0
data/lib/fit/fixture.rb
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# Copyright (c) 2002 Cunningham & Cunningham, Inc.
|
|
2
|
+
# Released under the terms of the GNU General Public License version 2 or later.
|
|
3
|
+
|
|
4
|
+
require 'fit/scientific_double'
|
|
5
|
+
require 'parsedate'
|
|
6
|
+
require 'fit/fixture_loader'
|
|
7
|
+
|
|
8
|
+
module Fit
|
|
9
|
+
|
|
10
|
+
class RunTime
|
|
11
|
+
def initialize
|
|
12
|
+
@elapsed = 0
|
|
13
|
+
@start = Time.now
|
|
14
|
+
end
|
|
15
|
+
def to_s
|
|
16
|
+
@elapsed = Time.now.to_f - @start.to_f
|
|
17
|
+
if @elapsed > 1
|
|
18
|
+
@elapsed = @elapsed.round
|
|
19
|
+
return d(3600) + ':' + d(600) + d(60) + ':' + d(10) + d(1)
|
|
20
|
+
else
|
|
21
|
+
@elapsed = (@elapsed * 100).round
|
|
22
|
+
return d(6000) + ':' + d(1000) + d(100) + '.' + d(10) + d(1)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
def d scale
|
|
26
|
+
report = @elapsed / scale
|
|
27
|
+
@elapsed -= report * scale
|
|
28
|
+
report.to_s
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Counts
|
|
33
|
+
attr_accessor :right, :wrong, :ignores, :exceptions
|
|
34
|
+
def initialize
|
|
35
|
+
@right, @wrong, @ignores, @exceptions = 0, 0, 0, 0
|
|
36
|
+
end
|
|
37
|
+
def to_s
|
|
38
|
+
"#@right right, #@wrong wrong, #@ignores ignored, #@exceptions exceptions"
|
|
39
|
+
end
|
|
40
|
+
def tally c
|
|
41
|
+
@right += c.right
|
|
42
|
+
@wrong += c.wrong
|
|
43
|
+
@ignores += c.ignores
|
|
44
|
+
@exceptions += c.exceptions
|
|
45
|
+
end
|
|
46
|
+
def total_errors
|
|
47
|
+
@wrong + @exceptions
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class FixtureListener
|
|
52
|
+
def tables_finished(finalCounts)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def table_finished(tableParse)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class Fixture
|
|
60
|
+
@@metadata = {}
|
|
61
|
+
def Fixture.metadata; @@metadata; end
|
|
62
|
+
|
|
63
|
+
attr_accessor :summary, :counts, :listener, :args
|
|
64
|
+
|
|
65
|
+
def initialize
|
|
66
|
+
@summary = {}
|
|
67
|
+
@counts = Counts.new
|
|
68
|
+
@listener = FixtureListener.new
|
|
69
|
+
@loader = FixtureLoader.new
|
|
70
|
+
@args = []
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def total_errors
|
|
74
|
+
@counts.total_errors
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Traversal
|
|
78
|
+
|
|
79
|
+
def find_class class_name
|
|
80
|
+
@loader.find_fixture_class class_name
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def do_tables tables
|
|
84
|
+
@summary['run date'] = Time.now.to_s
|
|
85
|
+
@summary['run elapsed time'] = RunTime.new
|
|
86
|
+
unless tables.nil?
|
|
87
|
+
fixture_name = fixture_name tables
|
|
88
|
+
unless fixture_name.nil?
|
|
89
|
+
begin
|
|
90
|
+
fixture = get_linked_fixture_with_args tables
|
|
91
|
+
fixture.interpret_tables tables
|
|
92
|
+
rescue Exception => e
|
|
93
|
+
exception fixture_name, e
|
|
94
|
+
interpret_following_tables tables
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
@listener.tables_finished(@counts)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def interpret_tables tables
|
|
102
|
+
# don't create the first fixture again, because creation may do something important
|
|
103
|
+
begin
|
|
104
|
+
get_args_for_table(tables) # get them again for the new fixture object
|
|
105
|
+
do_table tables
|
|
106
|
+
rescue Exception => e
|
|
107
|
+
exception fixture_name(tables), e
|
|
108
|
+
return
|
|
109
|
+
end
|
|
110
|
+
@listener.table_finished(tables)
|
|
111
|
+
interpret_following_tables tables
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def interpret_following_tables tables
|
|
115
|
+
tables = tables.more
|
|
116
|
+
until tables.nil?
|
|
117
|
+
fixture_name = fixture_name(tables)
|
|
118
|
+
unless fixture_name.nil?
|
|
119
|
+
begin
|
|
120
|
+
fixture = get_linked_fixture_with_args tables
|
|
121
|
+
fixture.do_table tables
|
|
122
|
+
rescue Exception => e
|
|
123
|
+
exception fixture_name, e
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
@listener.table_finished(tables)
|
|
127
|
+
tables = tables.more
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def get_linked_fixture_with_args tables
|
|
132
|
+
header = fixture_name tables
|
|
133
|
+
unless header.nil?
|
|
134
|
+
begin
|
|
135
|
+
fixture = (find_class header.text).new # using reflection
|
|
136
|
+
fixture.summary = @summary
|
|
137
|
+
fixture.counts = @counts
|
|
138
|
+
fixture.listener = @listener
|
|
139
|
+
fixture.get_args_for_table tables
|
|
140
|
+
return fixture
|
|
141
|
+
rescue Exception => e
|
|
142
|
+
exception header, e
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def get_args_for_table table
|
|
148
|
+
arg = table.parts.parts.more
|
|
149
|
+
args = []
|
|
150
|
+
while arg
|
|
151
|
+
args << arg.text
|
|
152
|
+
arg = arg.more
|
|
153
|
+
end
|
|
154
|
+
@args = args
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def fixture_name tables
|
|
158
|
+
tables.at 0, 0, 0
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def do_table table
|
|
162
|
+
do_rows table.parts.more
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def do_rows rows
|
|
166
|
+
until rows.nil?
|
|
167
|
+
more = rows.more
|
|
168
|
+
do_row rows
|
|
169
|
+
rows = more
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def do_row row
|
|
174
|
+
do_cells row.parts
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def do_cells cells
|
|
178
|
+
i = 0
|
|
179
|
+
until cells.nil?
|
|
180
|
+
begin
|
|
181
|
+
do_cell cells, i
|
|
182
|
+
rescue Exception => e
|
|
183
|
+
exception cells, e
|
|
184
|
+
end
|
|
185
|
+
cells = cells.more
|
|
186
|
+
i += 1
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def do_cell cell, column_number
|
|
191
|
+
ignore cell
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Annotation methods
|
|
195
|
+
GREEN = '#cfffcf'
|
|
196
|
+
RED = '#ffcfcf'
|
|
197
|
+
GRAY = '#efefef'
|
|
198
|
+
YELLOW = '#ffffcf'
|
|
199
|
+
|
|
200
|
+
def right cell
|
|
201
|
+
cell.add_to_tag(' bgcolor="' + GREEN + '"')
|
|
202
|
+
@counts.right += 1
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def wrong cell, actual = nil
|
|
206
|
+
cell.add_to_tag(' bgcolor="' + RED + '"')
|
|
207
|
+
cell.body = Fixture.escape(cell.text)
|
|
208
|
+
@counts.wrong += 1
|
|
209
|
+
unless actual.nil?
|
|
210
|
+
cell.add_to_body(Fixture.label('expected') + '<hr>' + Fixture.escape(actual) + Fixture.label('actual'))
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def ignore cell
|
|
215
|
+
cell.add_to_tag(' bgcolor="' + GRAY + '"')
|
|
216
|
+
@counts.ignores += 1
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def exception cell, e
|
|
220
|
+
stacktrace = e.backtrace.join "\n"
|
|
221
|
+
message = e.message.gsub(/>/, '>').gsub(/</, '<')
|
|
222
|
+
cell.add_to_body "<hr><font size=-2><pre>#{message}\n#{stacktrace}</pre></font>"
|
|
223
|
+
cell.add_to_tag(' bgcolor="' + YELLOW + '"')
|
|
224
|
+
@counts.exceptions += 1
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def info cell, message
|
|
228
|
+
string = ' <font color="#808080">' + Fixture.escape(message) + '</font>'
|
|
229
|
+
cell.add_to_body string
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def error cell, message
|
|
233
|
+
cell.body = Fixture.escape(cell.text)
|
|
234
|
+
cell.add_to_body('<hr><pre>' + Fixture.escape(message) + '</pre>')
|
|
235
|
+
cell.add_to_tag(' bgcolor="' + YELLOW + '"')
|
|
236
|
+
@counts.exceptions += 1
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Utility methods
|
|
240
|
+
|
|
241
|
+
# This method was originally called counts, but the name has been
|
|
242
|
+
# changed to avoid shadowing the counts accessor attribute.
|
|
243
|
+
def totals
|
|
244
|
+
@counts.to_s
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def Fixture.label s
|
|
248
|
+
' <font size=-1 color="#c08080"><i>' + s + '</i></font>'
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def Fixture.gray s
|
|
252
|
+
' <font color="#808080">' + s + '</font>'
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def Fixture.escape s
|
|
256
|
+
str = s.gsub(/&/, '&').gsub(/</, '<').gsub(/ /, ' ')
|
|
257
|
+
# str.gsub(/\r\n/, '<br />').gsub(/\n\r/, '<br />').gsub(/\r/, '<br />').gsub(/\n/, '<br />')
|
|
258
|
+
str.gsub(/\r\n/, '<br />').gsub(/\r/, '<br />').gsub(/\n/, '<br />')
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Originally, this method built the name of a Java method from multiple,
|
|
262
|
+
# space-separated, downcased words, morphing them into a single camelcased
|
|
263
|
+
# word. Ruby just needs those words to be joined with an underscore. For
|
|
264
|
+
# historical reasons, this method still maintains its original name.
|
|
265
|
+
def Fixture.camel name
|
|
266
|
+
name.gsub(/ /, '_')
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def parse string, klass
|
|
270
|
+
return ParseDate.parsedate(string) if klass == ParseDate
|
|
271
|
+
return ScientificDouble.value_of(string) if klass == ScientificDouble
|
|
272
|
+
return string if klass == String
|
|
273
|
+
nil
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def check cell, adapter
|
|
277
|
+
text = cell.text
|
|
278
|
+
if text.empty?
|
|
279
|
+
begin
|
|
280
|
+
info cell, adapter.to_s(adapter.get)
|
|
281
|
+
rescue Exception
|
|
282
|
+
info cell, 'error'
|
|
283
|
+
end
|
|
284
|
+
elsif adapter.nil?
|
|
285
|
+
ignore cell
|
|
286
|
+
elsif text == 'error'
|
|
287
|
+
begin
|
|
288
|
+
result = adapter.invoke # TypeAdapter.invoke does not exist...
|
|
289
|
+
wrong cell, adapter.to_s(result)
|
|
290
|
+
rescue Exception => e
|
|
291
|
+
right cell # no IllegalAccessException?
|
|
292
|
+
end
|
|
293
|
+
else
|
|
294
|
+
begin
|
|
295
|
+
result = adapter.get
|
|
296
|
+
if adapter.equals(adapter.parse(text), result)
|
|
297
|
+
right cell
|
|
298
|
+
else
|
|
299
|
+
wrong cell, adapter.to_s(result)
|
|
300
|
+
end
|
|
301
|
+
rescue Exception => e
|
|
302
|
+
exception cell, e
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module Fit
|
|
2
|
+
|
|
3
|
+
class FixtureLoader
|
|
4
|
+
|
|
5
|
+
def load name
|
|
6
|
+
klass = find_fixture_class name
|
|
7
|
+
klass.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def find_fixture_class name
|
|
11
|
+
camelizedName = (name.split(/[^a-zA-Z0-9.:$]/).collect { |word| first = word.slice!(0,1).upcase; first + word }).join.chomp('.')
|
|
12
|
+
klass = ([camelizedName, camelizedName + 'Fixture'].collect { |n| find_class n }).find { |fixtureKlass| fixtureKlass }
|
|
13
|
+
raise "Fixture #{name} not found." unless klass
|
|
14
|
+
raise "#{name} is not a fixture." unless klass < Fixture
|
|
15
|
+
klass
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Try to load the named class. We first see if it's already loaded by looking
|
|
19
|
+
# for the class name. If not, we convert the class name to a file name (by
|
|
20
|
+
# changing '::' to '/', then try to require that file. We then look for the
|
|
21
|
+
# constant again. This means that the class Example::Sqrt must be in the file
|
|
22
|
+
# Example/sqrt.rb or the file example/sqrt.rb.
|
|
23
|
+
def find_class name
|
|
24
|
+
klass = find_constant name
|
|
25
|
+
unless klass
|
|
26
|
+
([''] + @@fixture_packages).detect do |prefix|
|
|
27
|
+
file_path = (prefix + name).gsub(/::/, '/').gsub(/\./, '/')
|
|
28
|
+
classname = basename = File::basename(file_path)
|
|
29
|
+
if basename.index('$')
|
|
30
|
+
basename_parts = basename.split(/\$/)
|
|
31
|
+
basename = basename_parts[0]
|
|
32
|
+
classname = basename_parts.join('::')
|
|
33
|
+
end
|
|
34
|
+
file_basename = basename.split(/([A-Z][^A-Z]+)/).delete_if {|e| e.empty?}.collect {|e| e.downcase!}.join('_')
|
|
35
|
+
file_dirname = File::dirname(file_path)
|
|
36
|
+
file_name = (file_dirname == '.' ? '' : file_dirname + '/') + file_basename
|
|
37
|
+
begin
|
|
38
|
+
begin
|
|
39
|
+
require file_name
|
|
40
|
+
rescue LoadError
|
|
41
|
+
require file_name.downcase
|
|
42
|
+
end
|
|
43
|
+
rescue LoadError
|
|
44
|
+
#raise "Couldn't load file #{file_name} or file #{file_name.downcase}"
|
|
45
|
+
end
|
|
46
|
+
if file_dirname == '.'
|
|
47
|
+
klass_name = classname
|
|
48
|
+
else
|
|
49
|
+
klass_name = File::dirname(file_path).split(%r{/}).collect { |e|
|
|
50
|
+
e.index(/[A-Z]/).nil? ? e.capitalize : e
|
|
51
|
+
}.join('::') + "::#{classname}"
|
|
52
|
+
end
|
|
53
|
+
klass = find_constant klass_name
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
klass
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def find_constant name
|
|
60
|
+
class_name = name.gsub '.', '::'
|
|
61
|
+
classes = []
|
|
62
|
+
ObjectSpace.each_object(Class) { |klass| classes << klass }
|
|
63
|
+
classes.find { |klass| klass.name == class_name }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@@fixture_packages=['Fit::']
|
|
67
|
+
# This method adds the name of a module as a 'package' we should search for fixtures.
|
|
68
|
+
# Supports import_fixture
|
|
69
|
+
def FixtureLoader.add_fixture_package module_name
|
|
70
|
+
@@fixture_packages << module_name + '::'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
data/lib/fit/parse.rb
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Copyright (c) 2002 Cunningham & Cunningham, Inc.
|
|
2
|
+
# Released under the terms of the GNU General Public License version 2 or later.
|
|
3
|
+
|
|
4
|
+
require 'fileutils' # for footnotes generation
|
|
5
|
+
|
|
6
|
+
module Fit
|
|
7
|
+
|
|
8
|
+
class ParseException < Exception
|
|
9
|
+
attr_reader :error_offset
|
|
10
|
+
def initialize message, offset
|
|
11
|
+
super message
|
|
12
|
+
@error_offset = offset
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# This ParseHolder class is necessary to cope with the different constructors
|
|
17
|
+
# in the Java version of FIT. One of them is the constructor for the Parse Ruby
|
|
18
|
+
# class, the other is a static method in the ParseHolder class.
|
|
19
|
+
#
|
|
20
|
+
# ParseHolder also maintain all the methods belonging to Parse which are instead
|
|
21
|
+
# called whenever in FIT a ParseHolder is created instead of a Parse.
|
|
22
|
+
class ParseHolder
|
|
23
|
+
|
|
24
|
+
attr_accessor :leader, :tag, :body, :end, :trailer
|
|
25
|
+
attr_accessor :more, :parts
|
|
26
|
+
|
|
27
|
+
def ParseHolder.create tag, body, parts, more
|
|
28
|
+
p = new
|
|
29
|
+
p.leader = "\n"
|
|
30
|
+
p.tag = "<#{tag}>"
|
|
31
|
+
p.body = body
|
|
32
|
+
p.end = "</#{tag}>"
|
|
33
|
+
p.trailer = ""
|
|
34
|
+
p.parts = parts
|
|
35
|
+
p.more = more
|
|
36
|
+
p
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def leaf
|
|
40
|
+
@parts.nil? ? self : @parts.leaf
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def add_to_tag text
|
|
44
|
+
@tag = @tag[0..-2] + "#{text}>"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def add_to_body text
|
|
48
|
+
@body += text
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def at i, *rest
|
|
52
|
+
node = (i == 0 || @more.nil?) ? self : @more.at(i - 1)
|
|
53
|
+
rest.each do |j|
|
|
54
|
+
node = node.parts.at(j)
|
|
55
|
+
end
|
|
56
|
+
node
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def text
|
|
60
|
+
Parse.html_to_text @body
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def last
|
|
64
|
+
@more.nil? ? self : @more.last
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def size
|
|
68
|
+
@more.nil? ? 1 : @more.size + 1
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def print out
|
|
72
|
+
out.print @leader
|
|
73
|
+
out.print @tag
|
|
74
|
+
if @parts
|
|
75
|
+
@parts.print out
|
|
76
|
+
else
|
|
77
|
+
out.print @body
|
|
78
|
+
end
|
|
79
|
+
out.print @end
|
|
80
|
+
if @more
|
|
81
|
+
@more.print out
|
|
82
|
+
else
|
|
83
|
+
out.print @trailer
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class Parse < ParseHolder
|
|
90
|
+
|
|
91
|
+
MAX_INT = 2147483647 # hardcoded java.lang.Integer.MAX_VALUE
|
|
92
|
+
DEFAULT_TAGS = ['table', 'tr', 'td']
|
|
93
|
+
|
|
94
|
+
@@footnote_files = 0
|
|
95
|
+
|
|
96
|
+
def initialize text, tags = DEFAULT_TAGS, level = 0, offset = 0
|
|
97
|
+
tag = tags[level]
|
|
98
|
+
lc = text.downcase
|
|
99
|
+
start_tag = lc.index "<#{tag}"
|
|
100
|
+
raise ParseException.new("Can't find tag: #{tag}", offset) if start_tag.nil?
|
|
101
|
+
end_tag = lc.index('>', start_tag)
|
|
102
|
+
raise ParseException.new("Can't find tag: #{tag}", offset) if end_tag.nil?
|
|
103
|
+
end_tag += 1
|
|
104
|
+
start_end = find_matching_end_tag lc, end_tag, tag, offset
|
|
105
|
+
raise ParseException.new("Can't find tag: #{tag}", offset) if start_end.nil?
|
|
106
|
+
end_end = lc.index('>', start_end)
|
|
107
|
+
raise ParseException.new("Can't find tag: #{tag}", offset) if end_end.nil?
|
|
108
|
+
end_end += 1
|
|
109
|
+
start_more = lc.index "<#{tag}", end_end
|
|
110
|
+
|
|
111
|
+
@leader = text[0...start_tag]
|
|
112
|
+
@tag = text[start_tag...end_tag]
|
|
113
|
+
@body = text[end_tag...start_end]
|
|
114
|
+
@end = text[start_end...end_end]
|
|
115
|
+
@trailer = text[end_end..-1]
|
|
116
|
+
|
|
117
|
+
if level + 1 < tags.size
|
|
118
|
+
@parts = Parse.new @body, tags, level + 1, offset + end_tag
|
|
119
|
+
@body = nil
|
|
120
|
+
else # check for nested table
|
|
121
|
+
index = @body.index "<#{tags[0]}"
|
|
122
|
+
unless index.nil?
|
|
123
|
+
@parts = Parse.new @body, tags, 0, offset + end_tag
|
|
124
|
+
@body = ''
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
unless start_more.nil?
|
|
129
|
+
@more = Parse.new @trailer, tags, level, offset + end_end
|
|
130
|
+
@trailer = nil
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def find_matching_end_tag lc, match_from_here, tag, offset
|
|
135
|
+
from_here = match_from_here
|
|
136
|
+
count = 1
|
|
137
|
+
start_end = 0
|
|
138
|
+
while count > 0
|
|
139
|
+
embedded_tag = lc.index "<#{tag}", from_here
|
|
140
|
+
embedded_tag_end = lc.index "</#{tag}", from_here
|
|
141
|
+
# which one is closer?
|
|
142
|
+
raise ParseException.new("Can't find tag: #{tag}", offset) if embedded_tag.nil? and embedded_tag_end.nil?
|
|
143
|
+
embedded_tag = MAX_INT if embedded_tag.nil?
|
|
144
|
+
embedded_tag_end = MAX_INT if embedded_tag_end.nil?
|
|
145
|
+
if embedded_tag < embedded_tag_end
|
|
146
|
+
count += 1
|
|
147
|
+
start_end = embedded_tag
|
|
148
|
+
from_here = lc.index('>', embedded_tag) + 1
|
|
149
|
+
elsif embedded_tag_end < embedded_tag
|
|
150
|
+
count -= 1
|
|
151
|
+
start_end = embedded_tag_end
|
|
152
|
+
from_here = lc.index('>', embedded_tag_end) + 1
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
start_end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def Parse.html_to_text s
|
|
159
|
+
str = s.gsub(%r{<\s*br\s*/?\s*>}, '<br />').gsub(%r{<\s*/\s*p\s*>\s*<\s*p( .*?)?\s*>}, '<br />')
|
|
160
|
+
unescape(condense_whitespace(remove_tags(str)))
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def Parse.remove_tags s
|
|
164
|
+
s.gsub(/<.*?>/m) { $& == '<br />' ? $& : '' }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def Parse.unescape s
|
|
168
|
+
str = s.gsub %r{<br />}, "\n"
|
|
169
|
+
# unescape HTML entities
|
|
170
|
+
str = str.gsub(%r{<}, '<').gsub(%r{>}, '>').gsub(%r{ }, ' ').gsub(%r{"}, '"').gsub(%r{&}, '&')
|
|
171
|
+
# unescape smart quotes
|
|
172
|
+
str.gsub(/\223/, '"').gsub(/\224/, '"').gsub(/\221/, "'").gsub(/\222/, "'")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def Parse.condense_whitespace s
|
|
176
|
+
# NOT_BREAKING_SPACE is decimal character 160, hex a0, oct 240
|
|
177
|
+
s.gsub(%r{\s+}, ' ').gsub(/\240/, ' ').gsub(%r{ }, ' ').strip
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# The original implementation of footnote hardcodes the creation path,
|
|
181
|
+
# hence is somewhat broken. The use of a class variable lets external
|
|
182
|
+
# clients (like Rake and FitTask) decide where to generate footnotes.
|
|
183
|
+
@@footnote_path = 'Reports/'
|
|
184
|
+
def Parse.footnote_path=(path); @@footnote_path = path; end
|
|
185
|
+
def Parse.footnote_path; @@footnote_path; end
|
|
186
|
+
def footnote
|
|
187
|
+
return '[-]' if @@footnote_files >= 25
|
|
188
|
+
begin
|
|
189
|
+
this_footnote = (@@footnote_files += 1)
|
|
190
|
+
html = "footnotes/#{this_footnote}.html"
|
|
191
|
+
path = "#{@@footnote_path}#{html}"
|
|
192
|
+
FileUtils.mkpath File.dirname(path)
|
|
193
|
+
f = File.new(path, 'w')
|
|
194
|
+
print f
|
|
195
|
+
f.close
|
|
196
|
+
return "<a href=#{@@footnote_path}/#{html}>[#{this_footnote}]</a>"
|
|
197
|
+
rescue Exception => e
|
|
198
|
+
puts e.message
|
|
199
|
+
puts e.backtrace
|
|
200
|
+
return '[!]'
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Copyright (c) 2002 Cunningham & Cunningham, Inc.
|
|
2
|
+
# Released under the terms of the GNU General Public License version 2 or later.
|
|
3
|
+
|
|
4
|
+
require 'fit/fixture'
|
|
5
|
+
|
|
6
|
+
module Fit
|
|
7
|
+
|
|
8
|
+
class PrimitiveFixture < Fixture
|
|
9
|
+
|
|
10
|
+
# Format converters
|
|
11
|
+
|
|
12
|
+
def parse_integer cell
|
|
13
|
+
Integer cell.text
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def parse_double cell
|
|
17
|
+
Float cell.text
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parse_boolean cell
|
|
21
|
+
cell.text.downcase == 'true' ? true : false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Answer comparison
|
|
25
|
+
|
|
26
|
+
def check_value cell, value
|
|
27
|
+
if value.to_s == cell.text
|
|
28
|
+
right cell
|
|
29
|
+
else
|
|
30
|
+
wrong cell, value.to_s
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def check_boolean cell, value
|
|
35
|
+
if value == parse_boolean(cell)
|
|
36
|
+
right cell
|
|
37
|
+
else
|
|
38
|
+
wrong cell, value.to_s
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def check cell, expected, value
|
|
43
|
+
if expected == value
|
|
44
|
+
right cell
|
|
45
|
+
else
|
|
46
|
+
wrong cell, value
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|