testml 0.0.1 → 0.0.2

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.
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