testml 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.gemspec +3 -1
  2. data/CHANGELOG.yaml +4 -1
  3. data/LICENSE +1 -1
  4. data/README.rdoc +30 -5
  5. data/Rakefile +10 -1
  6. data/ToDo +56 -0
  7. data/lib/rake/testml.rb +14 -0
  8. data/lib/testml.rb +46 -2
  9. data/lib/testml/bridge.rb +5 -0
  10. data/lib/testml/compiler.rb +106 -0
  11. data/lib/testml/compiler/lite.rb +194 -0
  12. data/lib/testml/compiler/pegex.rb +50 -0
  13. data/lib/testml/compiler/pegex/ast.rb +145 -0
  14. data/lib/testml/compiler/pegex/grammar.rb +173 -0
  15. data/lib/testml/library.rb +7 -0
  16. data/lib/testml/library/debug.rb +18 -0
  17. data/lib/testml/library/standard.rb +86 -0
  18. data/lib/testml/runtime.rb +501 -0
  19. data/lib/testml/runtime/unit.rb +94 -0
  20. data/lib/testml/setup.rb +65 -0
  21. data/lib/testml/util.rb +22 -0
  22. data/test/ast/arguments.tml +87 -0
  23. data/test/ast/basic.tml +83 -0
  24. data/test/ast/dataless.tml +36 -0
  25. data/test/ast/exceptions.tml +59 -0
  26. data/test/ast/external.tml +42 -0
  27. data/test/ast/function.tml +276 -0
  28. data/test/ast/label.tml +58 -0
  29. data/test/ast/markers.tml +36 -0
  30. data/test/ast/semicolons.tml +30 -0
  31. data/test/ast/truth.tml +85 -0
  32. data/test/ast/types.tml +163 -0
  33. data/test/compile-lite.rb +38 -0
  34. data/test/compile-testml-document.rb +59 -0
  35. data/test/compile.rb +57 -0
  36. data/test/inline-bridge.rb +30 -0
  37. data/test/inline.rb +28 -0
  38. data/test/strings.rb +24 -0
  39. data/test/testml.rb +38 -0
  40. data/test/testml.yaml +10 -0
  41. data/test/testml/arguments.tml +18 -0
  42. data/test/testml/assertions.tml +15 -0
  43. data/test/testml/basic.tml +37 -0
  44. data/test/testml/dataless.tml +9 -0
  45. data/test/testml/exceptions.tml +16 -0
  46. data/test/testml/external.tml +8 -0
  47. data/test/testml/external1.tml +10 -0
  48. data/test/testml/external2.tml +3 -0
  49. data/test/testml/function.tml +82 -0
  50. data/test/testml/label.tml +24 -0
  51. data/test/testml/markers.tml +19 -0
  52. data/test/testml/semicolons.tml +10 -0
  53. data/test/testml/standard.tml +50 -0
  54. data/test/testml/truth.tml +22 -0
  55. data/test/testml/types.tml +24 -0
  56. data/test/testml_bridge.rb +28 -0
  57. metadata +69 -4
  58. data/test/fail.rb +0 -12
data/.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  GemSpec = Gem::Specification.new do |gem|
4
4
  gem.name = 'testml'
5
- gem.version = '0.0.1'
5
+ gem.version = '0.0.2'
6
6
  gem.license = 'MIT'
7
7
  gem.required_ruby_version = '>= 1.9.1'
8
8
 
@@ -15,4 +15,6 @@ TestML is an Acmeist testing framework.
15
15
  gem.homepage = 'http://testml.org'
16
16
 
17
17
  gem.files = `git ls-files`.lines.map{|l|l.chomp}
18
+
19
+ gem.add_dependency 'pegex', '>= 0.0.3'
18
20
  end
data/CHANGELOG.yaml CHANGED
@@ -1,3 +1,6 @@
1
+ - version: 0.0.2
2
+ date: Sat Apr 20 07:04:55 CST 2013
3
+ changes: Major refactoring release
1
4
  - version: 0.0.1
2
- date: Wed Dec 19 17:30:18 PST 2012
5
+ date: Tue Dec 18 19:54:09 PST 2012
3
6
  changes: First release
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  (The MIT License)
2
2
 
3
- Copyright © 2012 Ingy döt Net
3
+ Copyright © 2013 Ingy döt Net
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the ‘Software’), to deal in
data/README.rdoc CHANGED
@@ -1,16 +1,41 @@
1
1
  = testml - TestML for Ruby
2
2
 
3
- TestML is an Acmeist Unit Test language/framework.
3
+ TestML is an Acmeist unit test language/framework.
4
4
 
5
5
  = Synopsis
6
6
 
7
+ In a file called +test/mytest.rb+:
8
+
7
9
  require 'testml'
8
10
 
9
- = Status
11
+ TestML.new.testml = <<'.'
12
+ Plan = 4;
13
+
14
+ *x.add(*y) == *sum;
15
+ *x.mult(*y) == *pruduct;
16
+
17
+ === Test one
18
+ --- x: 42
19
+ --- y: 43
20
+ --- sum: 85
21
+ --- product: 1806
22
+ .
23
+
24
+ = Description
25
+
26
+ Coming soon...
27
+
28
+ = TestML::Test API
29
+
30
+ Coming soon...
31
+
32
+ = About TestML
33
+
34
+ TestML subclasses Test::Unit, so it Just Works with your other test code/files
35
+ and with +rake test+.
10
36
 
11
- TestML has not yet been ported to Ruby. It will be soon. Use 'testml-lite' for
12
- now.
37
+ All you need to do is create a new TestML object in a test file.
13
38
 
14
39
  = Copyright
15
40
 
16
- Copyright (c) 2012 Ingy döt Net. See LICENSE for further details.
41
+ Copyright (c) 2012, 2013 Ingy döt Net. See LICENSE for further details.
data/Rakefile CHANGED
@@ -11,6 +11,12 @@ DevNull = '2>/dev/null'
11
11
  require 'rake'
12
12
  require 'rake/testtask'
13
13
  require 'rake/clean'
14
+ if File.exists? 'test/testml.yaml'
15
+ if File.exists? 'lib/rake/testml.rb'
16
+ $:.unshift "#{Dir.getwd}/lib"
17
+ end
18
+ require 'rake/testml'
19
+ end
14
20
 
15
21
  task :default => 'help'
16
22
 
@@ -18,9 +24,12 @@ CLEAN.include GemDir, GemFile, 'data.tar.gz', 'metadata.gz'
18
24
 
19
25
  desc 'Run the tests'
20
26
  task :test do
27
+ load '.env' if File.exists? '.env'
21
28
  Rake::TestTask.new do |t|
22
29
  t.verbose = true
23
- t.test_files = FileList['test/*.rb']
30
+ t.test_files = ENV['DEV_TEST_FILES'] &&
31
+ FileList[ENV['DEV_TEST_FILES'].split] ||
32
+ FileList['test/**/*.rb'].sort
24
33
  end
25
34
  end
26
35
 
data/ToDo ADDED
@@ -0,0 +1,56 @@
1
+ - Constants inside modules
2
+ - Pretty print ruby data structure
3
+ - Find current test file
4
+
5
+
6
+ - Code review
7
+ + rake testml takes yaml file argument
8
+
9
+ + Check irc
10
+ + Turn testml-lite into testml
11
+ + Eliminate explicit.rb and plan.rb
12
+ + Combine testml.tml and testml-lite.tml
13
+ + Port TestML::Runtime(.pm) to ruby
14
+ - Make test pass using real testml runtime
15
+ - Make all tests pass
16
+
17
+
18
+
19
+ + fix 1 == 1
20
+ + move TestML::Compiler to a class
21
+ + move TestML::Runtime to a class
22
+ + testml.plan = 5
23
+ + ~~ Match should use index, not regex
24
+ + testml.blocks = [...]
25
+ + testml.require_or_skip 'x', 'y', 'z'
26
+ + make require_or_skip not exit
27
+ + TestML::Test constructor can take hash or block or nothing
28
+ + rake/testml/lite
29
+ + use test/testml.yaml
30
+ + copy tml files from source repo
31
+ + generate .rb files from templates
32
+ + Eliminate global $TestMLError
33
+
34
+ - Support Point as first-class function
35
+ - Support Set, Get and Del
36
+ x Support surrogate TestML Function as Ruby function
37
+ - Add Label support
38
+
39
+
40
+
41
+ - Make a headless TestML::Test object
42
+
43
+ - make work with pegex-rb
44
+ - make a pegex-tml
45
+
46
+ - write doc
47
+ - look at rdoc and yard
48
+ - release to rubygems
49
+
50
+ - port to perl
51
+ - port to python
52
+ - port to perl6
53
+
54
+
55
+ == Ideas
56
+ - test-converage-rb
@@ -0,0 +1,14 @@
1
+ ##
2
+ # Usage:
3
+ # rake testml
4
+ # rake testml ./test/mytestmlconf.yaml
5
+ #
6
+ # This rake task syncs your local testml setup with a testml repository and
7
+ # creates any needed shim test files.
8
+
9
+ require 'testml/setup'
10
+
11
+ desc 'Update TestML files.'
12
+ task :testml do
13
+ TestML::Setup.new.setup(ARGV[1])
14
+ end
data/lib/testml.rb CHANGED
@@ -1,3 +1,47 @@
1
- module TestML
2
- fail "testml has not been ported to Ruby yet. Try 'testml-lite'"
1
+ class TestML
2
+ VERSION = '0.0.2'
3
+
4
+ attr_accessor :runtime
5
+ attr_accessor :compiler
6
+ attr_accessor :bridge
7
+ attr_accessor :library
8
+ attr_accessor :testml
9
+
10
+ def initialize attributes={}
11
+ attributes.each { |k,v| self.send "#{k}=", v }
12
+ yield self if block_given?
13
+ end
14
+
15
+ def run(*args)
16
+ set_default_classes
17
+ @runtime.new(
18
+ compiler: @compiler,
19
+ bridge: @bridge,
20
+ library: @library,
21
+ testml: @testml,
22
+ ).run(*args)
23
+ end
24
+
25
+ def set_default_classes
26
+ if not @runtime
27
+ require 'testml/runtime/unit'
28
+ @runtime = TestML::Runtime::Unit
29
+ end
30
+ if not @compiler
31
+ require 'testml/compiler/pegex'
32
+ @compiler = TestML::Compiler::Pegex
33
+ end
34
+ if not @bridge
35
+ require 'testml/bridge'
36
+ @bridge = TestML::Bridge
37
+ end
38
+ if not @library
39
+ require 'testml/library/standard'
40
+ require 'testml/library/debug'
41
+ @library = [
42
+ TestML::Library::Standard,
43
+ TestML::Library::Debug,
44
+ ]
45
+ end
46
+ end
3
47
  end
@@ -0,0 +1,5 @@
1
+ class TestML::Bridge
2
+ def runtime
3
+ $TestMLRuntimeSingleton
4
+ end
5
+ end
@@ -0,0 +1,106 @@
1
+ require 'testml/runtime'
2
+
3
+ class TestML::Compiler
4
+
5
+ attr_accessor :code
6
+ attr_accessor :data
7
+ attr_accessor :text
8
+ attr_accessor :directives
9
+ attr_accessor :function
10
+
11
+ def compile(input)
12
+ preprocess(input, 'top')
13
+ compile_code
14
+ compile_data
15
+
16
+ if @directives['DumpAST']
17
+ XXX @function
18
+ end
19
+
20
+ @function.namespace ||= {}
21
+ @function.namespace['TestML'] = @directives['TestML']
22
+
23
+ @function.outer = TestML::Function.new
24
+ return @function
25
+ end
26
+
27
+ def preprocess(input, top=nil)
28
+ parts = input.split /^((?:\%\w+.*|\#.*|\ *)\n)/
29
+ input = ''
30
+
31
+ @directives = {
32
+ 'TestML' => nil,
33
+ 'DataMarker' => nil,
34
+ 'BlockMarker' => '===',
35
+ 'PointMarker' => '---',
36
+ }
37
+
38
+ order_error = false
39
+ parts.each do |part|
40
+ next if part.empty?
41
+ if part =~ /^(\#.*|\ *)\n/
42
+ input << "\n"
43
+ next
44
+ end
45
+ if part =~ /^%(\w+)\s*(.*?)\s*\n/
46
+ directive, value = $1, $2
47
+ input << "\n"
48
+ if directive == 'TestML'
49
+ fail "Invalid TestML directive" \
50
+ unless value =~ /^\d+\.\d+\.\d+$/
51
+ fail "More than one TestML directive found" \
52
+ if @directives['TestML']
53
+ @directives['TestML'] = TestML::Str.new(value)
54
+ next
55
+ end
56
+ order_error = true unless @directives['TestML']
57
+ if directive == 'Include'
58
+ runtime = $TestMLRuntimeSingleton \
59
+ or fail "Can't process Include. No runtime available"
60
+ include_ = self.class.new
61
+ include_.preprocess(runtime.read_testml_file(value))
62
+ input << include_.text
63
+ @directives['DataMarker'] =
64
+ include_.directives['DataMarker']
65
+ @directives['BlockMarker'] =
66
+ include_.directives['BlockMarker']
67
+ @directives['PointMarker'] =
68
+ include_.directives['PointMarker']
69
+ fail "Can't define %TestML in an Included file" \
70
+ if include_.directives['TestML']
71
+ elsif directive =~ /^(DataMarker|BlockMarker|PointMarker)$/
72
+ @directives[directive] = value
73
+ elsif directive =~ /^(DebugPegex|DumpAST)$/
74
+ value = true if value.empty?
75
+ @directives[directive] = value
76
+ else
77
+ fail "Unknown TestML directive '$#{directive}'"
78
+ end
79
+ else
80
+ order_error = true if !input.empty? and !@directives['TestML']
81
+ input << part
82
+ end
83
+ end
84
+
85
+ if top
86
+ fail "No TestML directive found" \
87
+ unless @directives['TestML']
88
+ fail "%TestML directive must be the first (non-comment) statement" \
89
+ if order_error
90
+
91
+ @directives['DataMarker'] ||= @directives['BlockMarker']
92
+ if split = input.index("\n#{@directives['DataMarker']}")
93
+ @code = input[0..(split)]
94
+ @data = input[(split + 1)..-1]
95
+ else
96
+ @code = input
97
+ @data = ''
98
+ end
99
+
100
+ @code.gsub! /^\\(\\*[\%\#])/, '\1'
101
+ @data.gsub! /^\\(\\*[\%\#])/, '\1'
102
+ else
103
+ @text = input
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,194 @@
1
+ require 'testml/compiler'
2
+
3
+ class TestML::Compiler::Lite < TestML::Compiler
4
+ require 'testml/runtime'
5
+
6
+ attr_accessor :input
7
+ attr_accessor :points
8
+ attr_accessor :tokens
9
+ attr_accessor :function
10
+
11
+ WS = %r!\s+!
12
+ ANY = %r!.!
13
+ STAR = %r!\*!
14
+ NUM = %r!-?[0-9]+!
15
+ WORD = %r!\w+!
16
+ HASH = %r!#!
17
+ EQ = %r!=!
18
+ TILDE = %r!~!
19
+ LP = %r!\(!
20
+ RP = %r!\)!
21
+ DOT = %r!\.!
22
+ COMMA = %r!,!
23
+ SEMI = %r!;!
24
+ SSTR = %r!'(?:[^']*)'!
25
+ DSTR = %r!"(?:[^"]*)"!
26
+ ENDING = %r!(?:#{RP}|#{COMMA}|#{SEMI})!
27
+
28
+ POINT = %r!#{STAR}#{WORD}!
29
+ QSTR = %r!(?:#{SSTR}|#{DSTR})!
30
+ COMP = %r!(?:#{EQ}#{EQ}|#{TILDE}#{TILDE})!
31
+ OPER = %r!(?:#{COMP}|#{EQ})!
32
+ PUNCT = %r!(?:#{LP}|#{RP}|#{DOT}|#{COMMA}|#{SEMI})!
33
+
34
+ TOKENS = %r!(?:#{POINT}|#{NUM}|#{WORD}|#{QSTR}|#{PUNCT}|#{OPER})!
35
+
36
+ def compile_code
37
+ @function = TestML::Function.new
38
+ while not @code.empty? do
39
+ @code.sub! /^(.*)(\r\n|\n|)/, ''
40
+ @line = $1
41
+ tokenize
42
+ next if done
43
+ parse_assignment ||
44
+ parse_assertion ||
45
+ fail_()
46
+ end
47
+ end
48
+
49
+ def tokenize
50
+ @tokens = []
51
+ while not @line.empty? do
52
+ next if @line.sub!(/^#{WS}/, '')
53
+ next if @line.sub!(/^#{HASH}#{ANY}*/, '')
54
+ if @line.sub!(/^(#{TOKENS})/, '')
55
+ @tokens.push $1
56
+ else
57
+ fail_("Failed to get token here: '#{@line}'")
58
+ end
59
+ end
60
+ end
61
+
62
+ def parse_assignment
63
+ return unless peek(2) == '='
64
+ var, op = pop(2)
65
+ expr = parse_expression
66
+ pop if !done and peek == ';'
67
+ fail_() unless done
68
+ @function.statements.push TestML::Assignment.new(var, expr)
69
+ return true
70
+ end
71
+
72
+ def parse_assertion
73
+ return unless @tokens.grep /^#{COMP}$/
74
+ @points = []
75
+ left = parse_expression
76
+ token = pop
77
+ op =
78
+ token == '==' ? 'EQ' :
79
+ token == '~~' ? 'HAS' :
80
+ fail_
81
+ right = parse_expression
82
+ pop if !done and peek == ';'
83
+ fail_ unless done
84
+
85
+ @function.statements.push TestML::Statement.new(
86
+ left,
87
+ TestML::Assertion.new(
88
+ op,
89
+ right,
90
+ ),
91
+ points.empty? ? nil : points
92
+ )
93
+ return true
94
+ end
95
+
96
+ def parse_expression
97
+ calls = []
98
+ while !done and peek !~ /^(#{ENDING}|#{COMP})$/ do
99
+ token = pop
100
+ if token =~ /^#{NUM}$/
101
+ calls.push TestML::Num.new(token.to_i)
102
+ elsif token =~ /^#{QSTR}$/
103
+ str = token[1..-2]
104
+ calls.push TestML::Str.new(str)
105
+ elsif token =~ /^#{WORD}$/
106
+ call = TestML::Call.new(token)
107
+ if !done and peek == '('
108
+ call.args = parse_args
109
+ end
110
+ calls.push call
111
+ elsif token =~ /^#{POINT}$/
112
+ token =~ /(#{WORD})/ or fail
113
+ points.push $1
114
+ calls.push TestML::Point.new($1)
115
+ else
116
+ fail_("Unknown token '#{token}'")
117
+ end
118
+ if !done and peek == '.'
119
+ pop
120
+ end
121
+ end
122
+
123
+ return calls.size == 1 \
124
+ ? calls[0]
125
+ : TestML::Expression.new(calls)
126
+ end
127
+
128
+ def parse_args
129
+ pop == '(' or fail
130
+ args = []
131
+ while peek != ')' do
132
+ args.push parse_expression
133
+ pop if peek == ','
134
+ end
135
+ pop
136
+ return args
137
+ end
138
+
139
+ def compile_data
140
+ input = @data
141
+ input.gsub! /^#.*\n/, "\n"
142
+ input.gsub! /^\\/, ''
143
+ blocks = input.split(/(^===.*?(?=^===|\z))/m).select{|el|!el.empty?}
144
+ blocks.each{|block| block.sub! /\n+\z/, "\n"}
145
+
146
+ data = []
147
+ blocks.each do |string_block|
148
+ block = TestML::Block.new
149
+ string_block.gsub! /\A===\ +(.*?)\ *\n/, '' or
150
+ fail "No block label! #{string_block}"
151
+ block.label = $1
152
+ while !string_block.empty? do
153
+ next if string_block.sub! /\A\n+/, ''
154
+ key, value = nil, nil
155
+ if string_block.gsub!(/\A---\ +(\w+):\ +(.*)\n/, '') or
156
+ string_block.gsub!(/\A---\ +(\w+)\n(.*?)(?=^---|\z)/m, '')
157
+ key, value = $1, $2
158
+ else
159
+ fail "Failed to parse TestML string:\n#{string_block}"
160
+ end
161
+ block.points ||= {}
162
+ block.points[key] = value
163
+
164
+ if key =~ /^(ONLY|SKIP|LAST)$/
165
+ block.points[key] = true
166
+ end
167
+ end
168
+ data.push block
169
+ end
170
+ @function.data = data unless data.empty?
171
+ end
172
+
173
+ def done
174
+ tokens.empty?
175
+ end
176
+
177
+ def peek(index=1)
178
+ fail if index > @tokens.size
179
+ @tokens[index - 1]
180
+ end
181
+
182
+ def pop(count=1)
183
+ fail if count > @tokens.size
184
+ array = @tokens.slice! 0..(count-1)
185
+ count > 1 ? array : array[0]
186
+ end
187
+
188
+ def fail_(message=nil)
189
+ text = "Failed to compile TestML document.\n"
190
+ text << "Reason: #{message}\n" if message
191
+ text << "\nCode section of failure:\n#{@line}\n#{@code}\n"
192
+ fail text
193
+ end
194
+ end