testml 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemspec +3 -1
- data/CHANGELOG.yaml +4 -1
- data/LICENSE +1 -1
- data/README.rdoc +30 -5
- data/Rakefile +10 -1
- data/ToDo +56 -0
- data/lib/rake/testml.rb +14 -0
- data/lib/testml.rb +46 -2
- data/lib/testml/bridge.rb +5 -0
- data/lib/testml/compiler.rb +106 -0
- data/lib/testml/compiler/lite.rb +194 -0
- data/lib/testml/compiler/pegex.rb +50 -0
- data/lib/testml/compiler/pegex/ast.rb +145 -0
- data/lib/testml/compiler/pegex/grammar.rb +173 -0
- data/lib/testml/library.rb +7 -0
- data/lib/testml/library/debug.rb +18 -0
- data/lib/testml/library/standard.rb +86 -0
- data/lib/testml/runtime.rb +501 -0
- data/lib/testml/runtime/unit.rb +94 -0
- data/lib/testml/setup.rb +65 -0
- data/lib/testml/util.rb +22 -0
- data/test/ast/arguments.tml +87 -0
- data/test/ast/basic.tml +83 -0
- data/test/ast/dataless.tml +36 -0
- data/test/ast/exceptions.tml +59 -0
- data/test/ast/external.tml +42 -0
- data/test/ast/function.tml +276 -0
- data/test/ast/label.tml +58 -0
- data/test/ast/markers.tml +36 -0
- data/test/ast/semicolons.tml +30 -0
- data/test/ast/truth.tml +85 -0
- data/test/ast/types.tml +163 -0
- data/test/compile-lite.rb +38 -0
- data/test/compile-testml-document.rb +59 -0
- data/test/compile.rb +57 -0
- data/test/inline-bridge.rb +30 -0
- data/test/inline.rb +28 -0
- data/test/strings.rb +24 -0
- data/test/testml.rb +38 -0
- data/test/testml.yaml +10 -0
- data/test/testml/arguments.tml +18 -0
- data/test/testml/assertions.tml +15 -0
- data/test/testml/basic.tml +37 -0
- data/test/testml/dataless.tml +9 -0
- data/test/testml/exceptions.tml +16 -0
- data/test/testml/external.tml +8 -0
- data/test/testml/external1.tml +10 -0
- data/test/testml/external2.tml +3 -0
- data/test/testml/function.tml +82 -0
- data/test/testml/label.tml +24 -0
- data/test/testml/markers.tml +19 -0
- data/test/testml/semicolons.tml +10 -0
- data/test/testml/standard.tml +50 -0
- data/test/testml/truth.tml +22 -0
- data/test/testml/types.tml +24 -0
- data/test/testml_bridge.rb +28 -0
- metadata +69 -4
- data/test/fail.rb +0 -12
@@ -0,0 +1,501 @@
|
|
1
|
+
class TestML;end
|
2
|
+
|
3
|
+
class TestML::Runtime
|
4
|
+
attr_accessor :testml
|
5
|
+
attr_accessor :bridge
|
6
|
+
attr_accessor :library
|
7
|
+
attr_accessor :compiler
|
8
|
+
attr_accessor :skip
|
9
|
+
|
10
|
+
attr_accessor :function
|
11
|
+
attr_accessor :error
|
12
|
+
attr_accessor :global
|
13
|
+
attr_accessor :base
|
14
|
+
|
15
|
+
def initialize(attributes={})
|
16
|
+
attributes.each { |k,v| self.send "#{k}=", v }
|
17
|
+
$TestMLRuntimeSingleton = self
|
18
|
+
@base ||= 'test'
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
compile_testml
|
23
|
+
initialize_runtime
|
24
|
+
run_function(@function, [])
|
25
|
+
end
|
26
|
+
|
27
|
+
def run_function(function, args)
|
28
|
+
signature = apply_signature(function, args)
|
29
|
+
|
30
|
+
parent = @function
|
31
|
+
@function = function
|
32
|
+
|
33
|
+
function.statements.each do |statement|
|
34
|
+
if statement.kind_of? TestML::Assignment
|
35
|
+
run_assignment(statement)
|
36
|
+
else
|
37
|
+
run_statement(statement)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
@function = parent
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
def apply_signature(function, args)
|
46
|
+
signature = function.signature || []
|
47
|
+
|
48
|
+
fail "Function received #{args.size} args but expected #{signature.size}" \
|
49
|
+
if ! signature.empty? and args.size != signature.size
|
50
|
+
|
51
|
+
@function.setvar('Self', function)
|
52
|
+
signature.each_with_index do |sig_elem, i|
|
53
|
+
arg = args[i]
|
54
|
+
arg = run_expression(arg) \
|
55
|
+
if arg.kind_of? TestML::Expression
|
56
|
+
function.setvar(sig_elem, arg)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def run_statement(statement)
|
61
|
+
blocks = select_blocks(statement.points || [])
|
62
|
+
blocks.each do |block|
|
63
|
+
@function.setvar('Block', block) if block != 1
|
64
|
+
|
65
|
+
result = run_expression(statement.expr)
|
66
|
+
if assertion = statement.assert
|
67
|
+
run_assertion(result, assertion)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def run_assignment(assignment)
|
73
|
+
@function.setvar(
|
74
|
+
assignment.name,
|
75
|
+
run_expression(assignment.expr)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def run_assertion left, assert
|
80
|
+
method_ = method(('assert_' + assert.name).to_sym)
|
81
|
+
|
82
|
+
@function.getvar('TestNumber').value += 1
|
83
|
+
|
84
|
+
if assert.expr
|
85
|
+
method_.call(left, run_expression(assert.expr))
|
86
|
+
else
|
87
|
+
method_.call(left)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def run_expression(expr)
|
92
|
+
context = nil
|
93
|
+
@error = nil
|
94
|
+
if expr.kind_of? TestML::Expression
|
95
|
+
calls = expr.calls.clone
|
96
|
+
fail if calls.size <= 1
|
97
|
+
context = run_call(calls.shift)
|
98
|
+
calls.each do |call|
|
99
|
+
if @error
|
100
|
+
next unless
|
101
|
+
call.kind_of?(TestML::Call) and
|
102
|
+
call.name == 'Catch'
|
103
|
+
end
|
104
|
+
context = run_call(call, context)
|
105
|
+
end
|
106
|
+
else
|
107
|
+
context = run_call(expr)
|
108
|
+
end
|
109
|
+
if @error
|
110
|
+
fail @error
|
111
|
+
end
|
112
|
+
return context
|
113
|
+
end
|
114
|
+
|
115
|
+
def run_call call, context=nil
|
116
|
+
if call.kind_of? TestML::Object
|
117
|
+
return call
|
118
|
+
end
|
119
|
+
if call.kind_of? TestML::Function
|
120
|
+
return call
|
121
|
+
end
|
122
|
+
if call.kind_of? TestML::Point
|
123
|
+
return get_point(call.name)
|
124
|
+
end
|
125
|
+
if call.kind_of? TestML::Call
|
126
|
+
name = call.name
|
127
|
+
callable =
|
128
|
+
@function.getvar(name) ||
|
129
|
+
get_point(name) ||
|
130
|
+
lookup_callable(name) ||
|
131
|
+
fail("Can't locate '#{name}' callable")
|
132
|
+
if callable.kind_of? TestML::Object
|
133
|
+
return callable
|
134
|
+
end
|
135
|
+
return callable unless call.args or !context.nil?
|
136
|
+
call.args ||= []
|
137
|
+
args = call.args.map{|arg| run_expression(arg)}
|
138
|
+
args.unshift context if !context.nil?
|
139
|
+
if callable.kind_of? TestML::Callable
|
140
|
+
begin
|
141
|
+
value = callable.value.call(*args)
|
142
|
+
rescue
|
143
|
+
@error = $!.message
|
144
|
+
# @error = "#{$!.class}: #{$!.message}\n at #{$!.backtrace[0]}"
|
145
|
+
return TestML::Error.new(@error)
|
146
|
+
end
|
147
|
+
fail "'#{name}' did not return a TestML::Object object" \
|
148
|
+
unless value.kind_of? TestML::Object
|
149
|
+
return value
|
150
|
+
end
|
151
|
+
if callable.kind_of? TestML::Function
|
152
|
+
return run_function(callable, args)
|
153
|
+
end
|
154
|
+
fail
|
155
|
+
end
|
156
|
+
fail
|
157
|
+
end
|
158
|
+
|
159
|
+
def lookup_callable(name)
|
160
|
+
@function.getvar('Library').value.each do |library|
|
161
|
+
if library.respond_to?(name)
|
162
|
+
function = lambda do |*args|
|
163
|
+
library.method(name).call(*args)
|
164
|
+
end
|
165
|
+
callable = TestML::Callable.new(function)
|
166
|
+
@function.setvar(name, callable)
|
167
|
+
return callable
|
168
|
+
end
|
169
|
+
end
|
170
|
+
return nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def get_point(name)
|
174
|
+
value = @function.getvar('Block').points[name] or return
|
175
|
+
if value.sub!(/\n+\z/, "\n") and value == "\n"
|
176
|
+
value = ''
|
177
|
+
end
|
178
|
+
return TestML::Str.new(value)
|
179
|
+
end
|
180
|
+
|
181
|
+
def select_blocks(wanted)
|
182
|
+
return [1] if wanted.empty?
|
183
|
+
selected = []
|
184
|
+
@function.data.each do |block|
|
185
|
+
points = block.points
|
186
|
+
next if points.key?('SKIP')
|
187
|
+
next unless wanted.all?{|point| points.key?(point)}
|
188
|
+
if points.key?('ONLY')
|
189
|
+
selected = [block]
|
190
|
+
break
|
191
|
+
end
|
192
|
+
selected << block
|
193
|
+
break if points.key?('LAST')
|
194
|
+
end
|
195
|
+
return selected
|
196
|
+
end
|
197
|
+
|
198
|
+
def compile_testml
|
199
|
+
fail "'testml' document required but not found" \
|
200
|
+
unless @testml
|
201
|
+
if @testml !~ /\n/
|
202
|
+
@testml =~ /(.*)\/(.*)/ or fail
|
203
|
+
testml = $2
|
204
|
+
@base = @base + '/' + $1
|
205
|
+
@testml = read_testml_file testml
|
206
|
+
end
|
207
|
+
@function = @compiler.new.compile(@testml)
|
208
|
+
end
|
209
|
+
|
210
|
+
def initialize_runtime
|
211
|
+
@global = @function.outer
|
212
|
+
|
213
|
+
@global.setvar('Block', TestML::Block.new)
|
214
|
+
@global.setvar('Label', TestML::Str.new('$BlockLabel'))
|
215
|
+
@global.setvar('True', TestML::Constant::True)
|
216
|
+
@global.setvar('False', TestML::Constant::False)
|
217
|
+
@global.setvar('None', TestML::Constant::None)
|
218
|
+
@global.setvar('TestNumber', TestML::Num.new(0))
|
219
|
+
@global.setvar('Library', TestML::List.new)
|
220
|
+
|
221
|
+
library = @function.getvar('Library')
|
222
|
+
[@bridge, @library].each do |lib|
|
223
|
+
if lib.kind_of? Array
|
224
|
+
lib.each {|l| library.push(l.new)}
|
225
|
+
else
|
226
|
+
library.push(lib.new)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def get_label
|
232
|
+
label = @function.getvar('Label') or return
|
233
|
+
label = label.value
|
234
|
+
label.gsub(/\$(\w+)/) {|m| replace_label($1)}
|
235
|
+
end
|
236
|
+
|
237
|
+
def replace_label(var)
|
238
|
+
block = @function.getvar('Block')
|
239
|
+
if var == 'BlockLabel'
|
240
|
+
block.label
|
241
|
+
elsif v = block.points[var]
|
242
|
+
v.sub!(/\n.*/m, '')
|
243
|
+
v.strip
|
244
|
+
elsif v = function.getvar(var)
|
245
|
+
v.value
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def read_testml_file file
|
250
|
+
path = @base + '/' + file
|
251
|
+
File.read(path)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
#-----------------------------------------------------------------------------
|
256
|
+
class TestML::Function
|
257
|
+
attr_accessor :signature
|
258
|
+
attr_accessor :statements
|
259
|
+
attr_accessor :namespace
|
260
|
+
attr_accessor :data
|
261
|
+
|
262
|
+
@@outer = {}
|
263
|
+
def outer; @@outer[self.object_id] end
|
264
|
+
def outer=(value); @@outer[self.object_id] = value end
|
265
|
+
def type; 'Func' end
|
266
|
+
def namespace; @namespace ||= {} end
|
267
|
+
|
268
|
+
def initialize
|
269
|
+
@statements = []
|
270
|
+
end
|
271
|
+
|
272
|
+
def getvar name
|
273
|
+
s = self
|
274
|
+
while s
|
275
|
+
if s.namespace.key? name
|
276
|
+
return s.namespace[name]
|
277
|
+
end
|
278
|
+
s = s.outer
|
279
|
+
end
|
280
|
+
nil
|
281
|
+
end
|
282
|
+
|
283
|
+
def setvar name, value
|
284
|
+
namespace[name] = value
|
285
|
+
end
|
286
|
+
|
287
|
+
def forgetvar name
|
288
|
+
namespace.delete name
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
#-----------------------------------------------------------------------------
|
293
|
+
class TestML::Assignment
|
294
|
+
attr_accessor :name
|
295
|
+
attr_accessor :expr
|
296
|
+
|
297
|
+
def initialize(name, expr)
|
298
|
+
@name = name
|
299
|
+
@expr = expr
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
#-----------------------------------------------------------------------------
|
304
|
+
class TestML::Statement
|
305
|
+
attr_accessor :expr
|
306
|
+
attr_accessor :assert
|
307
|
+
attr_accessor :points
|
308
|
+
|
309
|
+
def initialize(expr, assert=nil, points=nil)
|
310
|
+
@expr = expr
|
311
|
+
@assert = assert if assert
|
312
|
+
@points = points if points
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
#-----------------------------------------------------------------------------
|
317
|
+
class TestML::Expression
|
318
|
+
attr_accessor :calls
|
319
|
+
|
320
|
+
def initialize(calls=[])
|
321
|
+
@calls = calls
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
#-----------------------------------------------------------------------------
|
326
|
+
class TestML::Assertion
|
327
|
+
attr_accessor :name
|
328
|
+
attr_accessor :expr
|
329
|
+
|
330
|
+
def initialize(name, expr=nil)
|
331
|
+
@name = name
|
332
|
+
@expr = expr if expr
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
#-----------------------------------------------------------------------------
|
337
|
+
class TestML::Call
|
338
|
+
attr_accessor :name
|
339
|
+
attr_accessor :args
|
340
|
+
|
341
|
+
def initialize(name, args=[])
|
342
|
+
@name = name
|
343
|
+
@args = args if !args.empty?
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
#-----------------------------------------------------------------------------
|
348
|
+
class TestML::Callable
|
349
|
+
attr_accessor :value
|
350
|
+
|
351
|
+
def initialize(value)
|
352
|
+
@value = value
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
#-----------------------------------------------------------------------------
|
357
|
+
class TestML::Block
|
358
|
+
attr_accessor :label
|
359
|
+
attr_accessor :points
|
360
|
+
|
361
|
+
def initialize(label='', points={})
|
362
|
+
@label = label
|
363
|
+
@points = points
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
#-----------------------------------------------------------------------------
|
368
|
+
class TestML::Point
|
369
|
+
attr_accessor :name
|
370
|
+
|
371
|
+
def initialize(name)
|
372
|
+
@name = name
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
#-----------------------------------------------------------------------------
|
377
|
+
class TestML::Object
|
378
|
+
attr_accessor :value
|
379
|
+
|
380
|
+
def initialize(value)
|
381
|
+
@value = value
|
382
|
+
end
|
383
|
+
|
384
|
+
def type
|
385
|
+
type = self.class.to_s
|
386
|
+
type.sub! /^TestML::/, '' or fail "Can't find type of '#{type}'"
|
387
|
+
return type
|
388
|
+
end
|
389
|
+
|
390
|
+
def str
|
391
|
+
fail "Cast from #{type} to Str is not supported"
|
392
|
+
end
|
393
|
+
def num
|
394
|
+
fail "Cast from #{type} to Num is not supported"
|
395
|
+
end
|
396
|
+
def bool
|
397
|
+
fail "Cast from #{type} to Bool is not supported"
|
398
|
+
end
|
399
|
+
def list
|
400
|
+
fail "Cast from #{type} to List is not supported"
|
401
|
+
end
|
402
|
+
def none
|
403
|
+
TestML::Constant::None
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
#-----------------------------------------------------------------------------
|
408
|
+
class TestML::Str < TestML::Object
|
409
|
+
def initialize(str)
|
410
|
+
@value = str.to_s
|
411
|
+
end
|
412
|
+
def str
|
413
|
+
self
|
414
|
+
end
|
415
|
+
def num
|
416
|
+
TestML::Num.new(@value =~ /^-?\d+(?:\.\d+)$/ ? $1.to_i : 0)
|
417
|
+
end
|
418
|
+
def bool
|
419
|
+
!@value.empty? ? TestML::Constant::True : TestML::Constant::False
|
420
|
+
end
|
421
|
+
def list
|
422
|
+
TestML::List.new(@value.split //)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
#-----------------------------------------------------------------------------
|
427
|
+
class TestML::Num < TestML::Object
|
428
|
+
def str
|
429
|
+
TestML::Str.new(@value.to_s)
|
430
|
+
end
|
431
|
+
def num
|
432
|
+
self
|
433
|
+
end
|
434
|
+
def bool
|
435
|
+
@value != 0 ? TestML::Constant::True : TestML::Constant::False
|
436
|
+
end
|
437
|
+
def list
|
438
|
+
list = []
|
439
|
+
[1..(@value-1)].each { |i| list[i - 1] = nil }
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
#-----------------------------------------------------------------------------
|
444
|
+
class TestML::Bool < TestML::Object
|
445
|
+
def str
|
446
|
+
TestML::Str.new(@value ? "1" : "")
|
447
|
+
end
|
448
|
+
def num
|
449
|
+
TestML::Num.new(@value ? 1 : 0)
|
450
|
+
end
|
451
|
+
def bool
|
452
|
+
self
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
#-----------------------------------------------------------------------------
|
457
|
+
class TestML::List < TestML::Object
|
458
|
+
def initialize(value=[])
|
459
|
+
super(value)
|
460
|
+
end
|
461
|
+
def push elem
|
462
|
+
@value.push elem
|
463
|
+
end
|
464
|
+
def list
|
465
|
+
return self
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
#-----------------------------------------------------------------------------
|
470
|
+
class TestML::None < TestML::Object
|
471
|
+
def initialize
|
472
|
+
super(nil)
|
473
|
+
end
|
474
|
+
def str
|
475
|
+
TestML::Str.new('')
|
476
|
+
end
|
477
|
+
def num
|
478
|
+
TestML::Num.new(0)
|
479
|
+
end
|
480
|
+
def bool
|
481
|
+
TestML::Constant::False
|
482
|
+
end
|
483
|
+
def list
|
484
|
+
TestML::List.new []
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
#-----------------------------------------------------------------------------
|
489
|
+
class TestML::Native < TestML::Object
|
490
|
+
end
|
491
|
+
|
492
|
+
#-----------------------------------------------------------------------------
|
493
|
+
class TestML::Error < TestML::Object
|
494
|
+
end
|
495
|
+
|
496
|
+
#-----------------------------------------------------------------------------
|
497
|
+
module TestML::Constant
|
498
|
+
True = TestML::Bool.new(true)
|
499
|
+
False = TestML::Bool.new(false)
|
500
|
+
None = TestML::None.new
|
501
|
+
end
|