xmp2assert 6 → 8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +28 -1
- data/README.md +48 -0
- data/exe/xmpcheck.rb +1 -3
- data/lib/xmp2assert.rb +0 -3
- data/lib/xmp2assert/assertions.rb +38 -102
- data/lib/xmp2assert/classifier.rb +8 -7
- data/lib/xmp2assert/converter.rb +147 -44
- data/lib/xmp2assert/namespace.rb +0 -3
- data/lib/xmp2assert/parser.rb +6 -19
- data/lib/xmp2assert/renderer.rb +77 -0
- data/lib/xmp2assert/spawn.rb +81 -0
- data/lib/xmp2assert/template.erb +55 -1
- data/lib/xmp2assert/version.rb +1 -1
- data/xmp2assert.gemspec +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14c558b16e211cc2198f003c708b87acebe83406
|
4
|
+
data.tar.gz: ed3a7c00a1d6b5bda7e734b00d0cd43ba1954acb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff38753c94e6c2b553efca12c639c77d91c4872cb5d539f26bb862e92dba881288e469c7fd67063a055b884b0f94863aed614befe7693aa61b74728c12bdaefe
|
7
|
+
data.tar.gz: ce410e98585873f3064f901d6803a86b69f1af44aa0053017b429f7ef8d01e2ef713ab883142d5a06f2fbbe49604fdfe3b39451dd42667b0fbd0e61afdbe8af3
|
data/.rubocop.yml
CHANGED
@@ -26,6 +26,7 @@ AllCops:
|
|
26
26
|
DisplayCopNames: true
|
27
27
|
Exclude:
|
28
28
|
- "vendor/**/*"
|
29
|
+
- "samples/**/*"
|
29
30
|
TargetRubyVersion: 2.2 # 2.3 # 2.4 # 2.5
|
30
31
|
|
31
32
|
Lint/AmbiguousBlockAssociation:
|
@@ -34,9 +35,19 @@ Lint/AmbiguousBlockAssociation:
|
|
34
35
|
Lint/EmptyWhen:
|
35
36
|
Enabled: false
|
36
37
|
|
38
|
+
Metrics/ParameterLists:
|
39
|
+
Enabled: false
|
40
|
+
|
37
41
|
Rails:
|
38
42
|
Enabled: false
|
39
43
|
|
44
|
+
Security/Eval:
|
45
|
+
# I KNOW WHAT I DO. Please stop annoying me.
|
46
|
+
Enabled: false
|
47
|
+
|
48
|
+
Security/MarshalLoad:
|
49
|
+
Enabled: false
|
50
|
+
|
40
51
|
Style/Alias:
|
41
52
|
Enabled: false
|
42
53
|
|
@@ -94,6 +105,9 @@ Style/FormatString:
|
|
94
105
|
Style/GuardClause:
|
95
106
|
Enabled: false
|
96
107
|
|
108
|
+
Style/IdenticalConditionalBranches:
|
109
|
+
Enabled: false
|
110
|
+
|
97
111
|
Style/IndentHash:
|
98
112
|
Enabled: false
|
99
113
|
|
@@ -115,6 +129,9 @@ Style/MultilineIfThen:
|
|
115
129
|
Style/MultilineMethodCallIndentation:
|
116
130
|
Enabled: false
|
117
131
|
|
132
|
+
Style/Not:
|
133
|
+
Enabled: false
|
134
|
+
|
118
135
|
Style/ParallelAssignment:
|
119
136
|
Enabled: false
|
120
137
|
|
@@ -154,6 +171,9 @@ Style/StringLiteralsInInterpolation:
|
|
154
171
|
Style/SymbolArray:
|
155
172
|
Enabled: false
|
156
173
|
|
174
|
+
Style/TernaryParentheses:
|
175
|
+
Enabled: false
|
176
|
+
|
157
177
|
Style/TrailingCommaInLiteral:
|
158
178
|
Enabled: false
|
159
179
|
|
@@ -175,9 +195,16 @@ Style/WhileUntilDo:
|
|
175
195
|
Style/WhileUntilModifier:
|
176
196
|
Enabled: false
|
177
197
|
|
198
|
+
Lint/AssignmentInCondition:
|
199
|
+
Exclude:
|
200
|
+
# I know what I do.
|
201
|
+
- 'lib/xmp2assert/assertions.rb'
|
202
|
+
- 'lib/xmp2assert/converter.rb'
|
203
|
+
|
178
204
|
Lint/UselessAccessModifier:
|
179
205
|
Exclude:
|
180
|
-
# I think detecting
|
206
|
+
# I think detecting these files is a rubocop bug.
|
207
|
+
- 'lib/xmp2assert/renderer.rb'
|
181
208
|
- 'lib/xmp2assert/validator.rb'
|
182
209
|
|
183
210
|
Lint/UselessAssignment:
|
data/README.md
CHANGED
@@ -26,6 +26,54 @@ It automatically checks for those comments and see if they are right.
|
|
26
26
|
|
27
27
|
Really sorry but we have no dedicated document than the YARD. Pro-tip: look at `XMP2Assert::Assertions`
|
28
28
|
|
29
|
+
## Languages understood by this library
|
30
|
+
|
31
|
+
- Everything but comments are passed verbatimly to underlying ruby interpreter. We don't go deep in this area.
|
32
|
+
- There are currently four kinds of special comments that make sense. All other comments are verbatimly passed to the underlying ruby interpreter (and then, silently ignored there).
|
33
|
+
- The most basic `=>` comment describes the value of an expression that is immediately leading the comment.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
1 + 2 # => 3
|
37
|
+
```
|
38
|
+
|
39
|
+
- An exceptions is described by a `~>` comment. Because exceptions are kind of control flows, the thing the comment describes tends to be a statement, not expression-in-general.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
raise "foo" # ~> foo
|
43
|
+
```
|
44
|
+
|
45
|
+
- Outputs are also described. There are two kinds of IO comments; `>>` for stdout and `!>` for stderr. Note however that They are checked buffered, not line-by-line.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
puts "foo" # >> foo
|
49
|
+
```
|
50
|
+
|
51
|
+
- Comments are not mixed i.e. you can't describe stderr and stdout in a same line. You have to separate them in dedicated lines.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
STDOUT.puts "foo"; STDERR.puts "bar"
|
55
|
+
# => nil
|
56
|
+
# >> foo
|
57
|
+
# ~> bar
|
58
|
+
```
|
59
|
+
|
60
|
+
- The "expression that is immediately leading the comment" is not that obvious than you think. For instance,
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
<<END + <<END.lines.length
|
64
|
+
foo
|
65
|
+
END
|
66
|
+
#{<<END}
|
67
|
+
bar
|
68
|
+
END
|
69
|
+
END
|
70
|
+
# => ...?
|
71
|
+
```
|
72
|
+
|
73
|
+
This is a valid ruby script but extraordinary complicated. What is the expression that the comment at the last line describes? It is strongly advised that you should not write such things and go concise.
|
74
|
+
|
75
|
+
Understanding of non-comment ruby expression is best effort; done using heuritics.
|
76
|
+
|
29
77
|
## What if I want to contribute?
|
30
78
|
|
31
79
|
Before proceeding any further, you have to take this action:
|
data/exe/xmpcheck.rb
CHANGED
data/lib/xmp2assert.rb
CHANGED
@@ -25,8 +25,5 @@
|
|
25
25
|
|
26
26
|
require_relative 'xmp2assert/namespace' # needs be first
|
27
27
|
require_relative 'xmp2assert/version'
|
28
|
-
require_relative 'xmp2assert/prettier_inspect'
|
29
|
-
require_relative 'xmp2assert/quasifile'
|
30
|
-
require_relative 'xmp2assert/converter'
|
31
28
|
require_relative 'xmp2assert/classifier'
|
32
29
|
require_relative 'xmp2assert/assertions'
|
@@ -23,69 +23,70 @@
|
|
23
23
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
24
|
# SOFTWARE.
|
25
25
|
|
26
|
-
require 'rbconfig'
|
27
|
-
require 'open3'
|
28
|
-
require 'tempfile'
|
29
|
-
require 'erb'
|
30
26
|
require 'test/unit'
|
31
27
|
require 'test/unit/assertions'
|
32
28
|
require 'test/unit/assertion-failed-error'
|
33
29
|
require_relative 'namespace'
|
34
30
|
require_relative 'quasifile'
|
35
31
|
require_relative 'xmp2rexp'
|
32
|
+
require_relative 'renderer'
|
33
|
+
require_relative 'spawn'
|
36
34
|
|
37
35
|
# Helper module that implements assertions.
|
38
36
|
module XMP2Assert::Assertions
|
39
37
|
include Test::Unit::Assertions
|
40
38
|
include XMP2Assert::XMP2Rexp
|
39
|
+
include XMP2Assert::Renderer
|
41
40
|
|
42
|
-
# Run a ruby script and assert for its
|
41
|
+
# Run a ruby script and assert for its comment. This is the main API.
|
43
42
|
#
|
44
43
|
# ```ruby
|
45
|
-
#
|
44
|
+
# assert_xmp "1 + 2 # => 3"
|
46
45
|
# ```
|
47
46
|
#
|
48
|
-
# @param
|
49
|
-
# @param
|
50
|
-
# @param
|
51
|
-
# @param
|
52
|
-
# @param opts
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
47
|
+
# @param script [Quasifile] a ruby script.
|
48
|
+
# @param message [String] extra failure message.
|
49
|
+
# @param rubyopts [Array<String>] extra opts to pass to ruby process.
|
50
|
+
# @param stdin_data [String] extra stdin to pass to ruby process.
|
51
|
+
# @param opts [Hash] extra opts to pass to Kernel.spawn.
|
52
|
+
def assert_xmp script, message = nil, stdin_data: '', **opts
|
53
|
+
qscript = XMP2Assert::Quasifile.new script
|
54
|
+
qf, qo, qe, qx = XMP2Assert::Converter.convert qscript
|
55
|
+
render qf, qx do |f|
|
56
|
+
XMP2Assert::Spawn.new f, **opts do |_, i, o, e, r, t|
|
57
|
+
i.write stdin_data
|
58
|
+
i.close
|
59
|
+
out = Thread.new { o.read }
|
60
|
+
err = Thread.new { e.read }
|
61
|
+
while n = t.gets do
|
62
|
+
x = t.read n.to_i
|
63
|
+
expected, actual, bt = *Marshal.load(x)
|
64
|
+
begin
|
65
|
+
assert_xmp_raw expected, actual, message
|
66
|
+
rescue Test::Unit::AssertionFailedError => x
|
67
|
+
r.close
|
68
|
+
x.set_backtrace bt
|
69
|
+
raise x
|
70
|
+
else
|
71
|
+
r.puts
|
72
|
+
end
|
73
|
+
end
|
74
|
+
assert_xmp_raw qo, out.value, message unless qo.empty?
|
75
|
+
assert_xmp_raw qe, err.value, message unless qe.empty?
|
76
|
+
end
|
77
|
+
end
|
74
78
|
end
|
75
79
|
|
76
|
-
|
77
|
-
|
78
|
-
# :TODO: is it private?
|
80
|
+
# :TODO: tbw
|
79
81
|
def assert_xmp_raw xmp, actual, message = nil
|
80
82
|
expected = xmp2rexp xmp
|
81
83
|
|
82
84
|
raise unless expected.match actual
|
83
85
|
rescue
|
84
86
|
# Regexp#match can raise. That should also be a failure.
|
85
|
-
msg = genmsg xmp, actual, message
|
86
87
|
ix = Test::Unit::Assertions::AssertionMessage.convert xmp
|
87
88
|
ia = Test::Unit::Assertions::AssertionMessage.convert actual
|
88
|
-
ex = Test::Unit::AssertionFailedError.new(
|
89
|
+
ex = Test::Unit::AssertionFailedError.new(message,
|
89
90
|
expected: xmp,
|
90
91
|
actual: actual,
|
91
92
|
inspected_expected: ix,
|
@@ -95,69 +96,4 @@ module XMP2Assert::Assertions
|
|
95
96
|
else
|
96
97
|
return self # or...?
|
97
98
|
end
|
98
|
-
|
99
|
-
# We support pre-&. versions
|
100
|
-
def try obj, msg
|
101
|
-
return obj.send msg
|
102
|
-
rescue NoMethodError
|
103
|
-
return nil
|
104
|
-
end
|
105
|
-
|
106
|
-
def genmsg x, y, z = nil
|
107
|
-
diff = Test::Unit::Assertions::AssertionMessage.delayed_diff x, y
|
108
|
-
if try(x, :ascii_only?) && try(y, :ascii_only?) then
|
109
|
-
fmt = "<?> expected but was\n<?>.?"
|
110
|
-
argv = [x, y, diff]
|
111
|
-
elsif try(x, :encoding) != try(y, :encoding) then
|
112
|
-
fmt = "<?>(?) expected but was\n<?>(?).?"
|
113
|
-
argv = [x, x.encoding.name, y, y.encoding.name, diff]
|
114
|
-
else
|
115
|
-
fmt = "<?> expected but was\n<?>.?"
|
116
|
-
argv = [x, y, diff]
|
117
|
-
end
|
118
|
-
return Test::Unit::Assertions::AssertionMessage.new z, fmt, argv
|
119
|
-
end
|
120
|
-
|
121
|
-
def erb
|
122
|
-
unless defined? @@erb
|
123
|
-
myself = Pathname.new __FILE__
|
124
|
-
path = myself + '../template.erb'
|
125
|
-
src = path.read mode: 'rb:binary:binary'
|
126
|
-
@@erb = ERB.new src, nil, '%-'
|
127
|
-
@@erb.filename = path.realpath.to_path if defined? $DEBUG
|
128
|
-
end
|
129
|
-
return @@erb
|
130
|
-
end
|
131
|
-
|
132
|
-
def empty_binding
|
133
|
-
# This `eval 'binding'` does not return the current binding but creates one
|
134
|
-
# on top of it. To make it really empty, this method has to have zero
|
135
|
-
# arity, and zero local variables.
|
136
|
-
return eval 'binding'
|
137
|
-
end
|
138
|
-
|
139
|
-
def empty_binding_with hash
|
140
|
-
return empty_binding.tap do |b|
|
141
|
-
hash.each_pair do |k, v|
|
142
|
-
b.local_variable_set k, v
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def ruby script, rubyopts: nil, **opts
|
148
|
-
Tempfile.create '' do |f|
|
149
|
-
b = empty_binding_with script: script
|
150
|
-
s = erb.result b
|
151
|
-
f.write s
|
152
|
-
argv = [RbConfig.ruby, rubyopts, f.path]
|
153
|
-
if defined? ENV['BUNDLE_BIN_PATH']
|
154
|
-
argv = [ENV['BUNDLE_BIN_PATH'], 'exec'] + argv
|
155
|
-
end
|
156
|
-
argv.flatten!
|
157
|
-
argv.compact!
|
158
|
-
f.flush
|
159
|
-
# STDERR.puts(f.path) ; sleep # for debug
|
160
|
-
return Open3.capture2e(*argv, binmode: true, **opts)
|
161
|
-
end
|
162
|
-
end
|
163
99
|
end
|
@@ -41,19 +41,20 @@ require_relative 'parser'
|
|
41
41
|
# ```
|
42
42
|
module XMP2Assert::Classifier
|
43
43
|
# @param (see XMP2Assert::Parser.new)
|
44
|
-
# @return [<Symbol>]
|
44
|
+
# @return [<Symbol>] any combination of :=>, :>>, :!>, :~>, or empty.
|
45
45
|
# @note syntax error results in empty return value.
|
46
46
|
def self.classify obj, file = nil, line = nil
|
47
47
|
parser = XMP2Assert::Parser.new obj, file, line
|
48
48
|
rescue SyntaxError
|
49
49
|
return []
|
50
50
|
else
|
51
|
-
|
52
|
-
|
53
|
-
.
|
54
|
-
.
|
55
|
-
.
|
56
|
-
.
|
51
|
+
mid = %i[=> >> !> ~>]
|
52
|
+
return parser \
|
53
|
+
.tokens \
|
54
|
+
.map(&:to_sym) \
|
55
|
+
.sort \
|
56
|
+
.uniq \
|
57
|
+
.map {|i| case i when *mid then i else nil end } \
|
57
58
|
.compact
|
58
59
|
end
|
59
60
|
end
|
data/lib/xmp2assert/converter.rb
CHANGED
@@ -23,7 +23,6 @@
|
|
23
23
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
24
|
# SOFTWARE.
|
25
25
|
|
26
|
-
require 'ripper'
|
27
26
|
require 'uuid'
|
28
27
|
require_relative 'namespace'
|
29
28
|
require_relative 'parser'
|
@@ -48,8 +47,7 @@ class XMP2Assert::Converter
|
|
48
47
|
# ```
|
49
48
|
#
|
50
49
|
# @param (see #initialize)
|
51
|
-
# @return
|
52
|
-
# output.
|
50
|
+
# @return (see #convert)
|
53
51
|
def self.convert obj, file = nil, line = nil
|
54
52
|
this = new obj, file, line
|
55
53
|
return this.send :convert
|
@@ -62,13 +60,16 @@ class XMP2Assert::Converter
|
|
62
60
|
@program = XMP2Assert::Parser.new obj, file, line
|
63
61
|
end
|
64
62
|
|
63
|
+
# @return [(Quasifile,String,String,String)]
|
64
|
+
# a tuple of comment-converted source, its expected stdout, expected
|
65
|
+
# stderr, and expected exception.
|
65
66
|
def convert
|
66
67
|
@tokens = @program.tokens
|
67
|
-
|
68
|
-
|
68
|
+
understand
|
69
|
+
stdout, stderr, exceptions = aggregate
|
69
70
|
render
|
70
71
|
ret = XMP2Assert::Quasifile.new @tokens.join, *@program.locations
|
71
|
-
return ret,
|
72
|
+
return ret, stdout, stderr, exceptions
|
72
73
|
end
|
73
74
|
|
74
75
|
def gensym expr
|
@@ -81,19 +82,27 @@ class XMP2Assert::Converter
|
|
81
82
|
Namespace = UUID.create_random
|
82
83
|
private_constant :Namespace
|
83
84
|
|
84
|
-
def gen_tap
|
85
|
-
|
86
|
-
|
87
|
-
|
85
|
+
def gen_tap tok
|
86
|
+
xmp = tok.to_s.chomp.dump
|
87
|
+
case tok.to_sym
|
88
|
+
when :'~>' then
|
89
|
+
return sprintf " rescue (xmp2assert_assert(%s, $!) and raise)", xmp
|
90
|
+
when :'=>' then
|
91
|
+
nam = gensym xmp
|
92
|
+
return sprintf ".tap {|%s| xmp2assert_assert(%s, %s) }", nam, xmp, nam
|
93
|
+
end
|
88
94
|
end
|
89
95
|
|
90
96
|
def end_of_expr? tok
|
91
97
|
case tok.to_sym
|
92
98
|
when :sp then return false
|
99
|
+
when :period then return false
|
93
100
|
when :comma then return false
|
94
101
|
when :semicolon then return false
|
95
102
|
when :comment then return false
|
96
103
|
when :'=>' then return false
|
104
|
+
when :'!>' then return false
|
105
|
+
when :'~>' then return false
|
97
106
|
when :>> then return false
|
98
107
|
when :nl then return false
|
99
108
|
when :ignored_nl then return false
|
@@ -143,21 +152,83 @@ class XMP2Assert::Converter
|
|
143
152
|
return true
|
144
153
|
end
|
145
154
|
|
155
|
+
def tmp_reroute_heredoc line
|
156
|
+
return line.map do |tok|
|
157
|
+
case tok.to_sym when :heredoc_beg then
|
158
|
+
next XMP2Assert::Token.new :'""', '""', tok.yylloc
|
159
|
+
else
|
160
|
+
next tok
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def revert_heredoc before, after
|
166
|
+
after.map! do |tok|
|
167
|
+
case tok.to_sym when :'""' then
|
168
|
+
next before.find {|i| i.yylloc == tok.yylloc }
|
169
|
+
else
|
170
|
+
next tok
|
171
|
+
end
|
172
|
+
end
|
173
|
+
before.replace after
|
174
|
+
end
|
175
|
+
|
176
|
+
def effective_first_token_of line
|
177
|
+
line.each do |tok|
|
178
|
+
return tok unless tok.to_sym == :sp
|
179
|
+
end
|
180
|
+
return nil
|
181
|
+
end
|
182
|
+
|
183
|
+
def effective_last_token_of line
|
184
|
+
line.reverse_each do |tok|
|
185
|
+
case tok.to_sym
|
186
|
+
when :sp then next
|
187
|
+
when :comment then next
|
188
|
+
when :'=>' then next
|
189
|
+
when :'!>' then next
|
190
|
+
when :'~>' then next
|
191
|
+
when :>> then next
|
192
|
+
when :nl then next
|
193
|
+
when :ignored_nl then next
|
194
|
+
else return tok
|
195
|
+
end
|
196
|
+
end
|
197
|
+
return nil
|
198
|
+
end
|
199
|
+
|
200
|
+
def period_continued? tok
|
201
|
+
line = @program.lines[tok.__LINE__ ]
|
202
|
+
t = effective_first_token_of line
|
203
|
+
return true if t.to_sym == :period
|
204
|
+
return false if tok.__LINE__ <= 1
|
205
|
+
|
206
|
+
line = @program.lines[tok.__LINE__ - 1]
|
207
|
+
return false unless t = effective_last_token_of(line)
|
208
|
+
return true if t.to_sym == :period
|
209
|
+
return false
|
210
|
+
end
|
211
|
+
|
146
212
|
def find_start stop
|
147
|
-
line = @program.
|
213
|
+
line = @program.lines[stop.__LINE__]
|
148
214
|
line.sort!
|
149
|
-
line.select
|
215
|
+
line = line.select {|i| i.__COLUMN__ <= stop.__COLUMN__ }
|
150
216
|
line = line.drop_while {|i| not beginning_of_expr? i }
|
151
|
-
|
152
|
-
|
217
|
+
line2 = tmp_reroute_heredoc line
|
218
|
+
until valid? line2 do
|
219
|
+
line2.shift
|
153
220
|
end
|
221
|
+
revert_heredoc line, line2
|
154
222
|
return line
|
155
223
|
end
|
156
224
|
|
157
225
|
def need_paren? list
|
158
226
|
start = list.first.to_sym.to_s
|
159
227
|
|
160
|
-
if
|
228
|
+
if "embexpr_beg" == start then
|
229
|
+
return false # paren breaks expression
|
230
|
+
|
231
|
+
elsif %r/(.+)_beg$/ =~ start then
|
161
232
|
t = $1
|
162
233
|
list.inject 0 do |i, tok|
|
163
234
|
tt = tok.to_sym.to_s
|
@@ -232,9 +303,18 @@ class XMP2Assert::Converter
|
|
232
303
|
# one: 1,
|
233
304
|
# } # => {:one=>1}
|
234
305
|
# ```
|
306
|
+
#
|
307
|
+
# 6. special case for periods... you can end a line with period, or
|
308
|
+
# begin with it; that indicates the line continues. We detect
|
309
|
+
# that and handle as continuous lines.
|
310
|
+
#
|
311
|
+
# A backslash-newline can also continue a line but we are dealing
|
312
|
+
# with comments so that can never happen here.
|
235
313
|
def rev_lookup_expr xmp
|
236
314
|
needs_start, stop = find_stop xmp
|
237
315
|
return nil, stop unless needs_start
|
316
|
+
return nil, stop if period_continued? stop
|
317
|
+
|
238
318
|
list = find_start stop
|
239
319
|
|
240
320
|
if list.empty? then
|
@@ -246,48 +326,71 @@ class XMP2Assert::Converter
|
|
246
326
|
end
|
247
327
|
end
|
248
328
|
|
249
|
-
def
|
250
|
-
return @tokens.each_with_object String.new do |tok, r|
|
251
|
-
next unless tok.to_sym == :>>
|
252
|
-
str = tok.to_s
|
253
|
-
r << str
|
254
|
-
str.replace "\n"
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
def merge_xmp
|
329
|
+
def understand
|
259
330
|
xmp = nil
|
260
331
|
@tokens.each do |tok|
|
261
|
-
case tok.to_sym
|
262
|
-
when :
|
263
|
-
|
264
|
-
|
332
|
+
case sym = tok.to_sym
|
333
|
+
when :'=>', :>>, :'!>', :'~>' then
|
334
|
+
if xmp and xmp.to_sym == sym then
|
335
|
+
xmp.to_s.concat tok.to_s
|
336
|
+
tok.yylex = :comment
|
337
|
+
tok.yylval = "#\n"
|
338
|
+
else
|
339
|
+
xmp = tok
|
340
|
+
end
|
341
|
+
when :comment then
|
265
342
|
if xmp then
|
266
|
-
xmp
|
267
|
-
|
268
|
-
tok.yylex = :nl
|
343
|
+
xmp.to_s.concat tok.to_s.sub(/^#/, '')
|
344
|
+
tok.yylval = "#\n"
|
269
345
|
else
|
270
|
-
xmp =
|
346
|
+
xmp = nil
|
271
347
|
end
|
348
|
+
when :sp then
|
349
|
+
next # ignore spaces
|
272
350
|
else
|
273
351
|
xmp = nil
|
274
352
|
end
|
275
353
|
end
|
276
354
|
end
|
277
355
|
|
356
|
+
def aggregate
|
357
|
+
buf = Hash.new "" # ok
|
358
|
+
@tokens.reverse_each do |tok|
|
359
|
+
case sym = tok.to_sym
|
360
|
+
when :>>, :'!>', :'~>' then
|
361
|
+
buf[sym] = tok.to_s << buf[sym]
|
362
|
+
tok.yylex = :comment
|
363
|
+
tok.yylval = "#\n"
|
364
|
+
when :sp, :nl then
|
365
|
+
next
|
366
|
+
else
|
367
|
+
break
|
368
|
+
end
|
369
|
+
end
|
370
|
+
# aggregation of ~> stops here, others continue.
|
371
|
+
@tokens.reverse_each do |tok|
|
372
|
+
case sym = tok.to_sym when :>>, :'!>' then
|
373
|
+
buf[sym] = tok.to_s << buf[sym]
|
374
|
+
tok.yylex = :comment
|
375
|
+
tok.yylval = "#\n"
|
376
|
+
end
|
377
|
+
end
|
378
|
+
return buf.values_at :>>, :'!>', :'~>'
|
379
|
+
end
|
380
|
+
|
278
381
|
def render
|
279
382
|
@tokens.each do |tok|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
383
|
+
case tok.to_sym when :'=>', :'~>' then
|
384
|
+
tap = gen_tap tok
|
385
|
+
tok.to_s.replace "\n"
|
386
|
+
x, y = rev_lookup_expr tok
|
387
|
+
if x and x != y then
|
388
|
+
x.to_s.sub! %r/^/, '('
|
389
|
+
y.to_s.sub! %r/$/, ')'
|
390
|
+
end
|
391
|
+
y.to_s.sub! %r/$/ do
|
392
|
+
tap # use block to prevent backslash substitution
|
393
|
+
end
|
291
394
|
end
|
292
395
|
end
|
293
396
|
end
|
data/lib/xmp2assert/namespace.rb
CHANGED
@@ -28,7 +28,4 @@
|
|
28
28
|
#
|
29
29
|
# - {XMP2Assert::Assertions} The assertion framework.
|
30
30
|
# - {XMP2Assert::Classifier} Check if the given file actually has the comment.
|
31
|
-
# - {XMP2Assert::Converter} Source code in-place editor using Ripper.
|
32
|
-
# - {XMP2Assert::Quasifile} IO/String abstraction layer
|
33
|
-
# - {XMP2Assert::PrettierInspect} Helper module to ease inspection.
|
34
31
|
module XMP2Assert; end
|
data/lib/xmp2assert/parser.rb
CHANGED
@@ -48,21 +48,11 @@ class XMP2Assert::Parser < Ripper
|
|
48
48
|
return @qfile.__FILE__, @qfile.__LINE__
|
49
49
|
end
|
50
50
|
|
51
|
-
#
|
51
|
+
# Split tokens into lines.
|
52
52
|
#
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
# ] # => [1, 2]
|
57
|
-
# ```
|
58
|
-
#
|
59
|
-
# It will return `[(:sp ' ') (:int 2) (:'=>' "2")]` for `(:'=>' "2")`.
|
60
|
-
#
|
61
|
-
# @param tok [Token] a token to look at.
|
62
|
-
# @return [Array] tokens of the same line as the argument.
|
63
|
-
def same_line_as tok
|
64
|
-
f, l = tok.__FILE__, tok.__LINE__
|
65
|
-
return @tokens.select {|i| i.__FILE__ == f }.select {|i| i.__LINE__ == l }
|
53
|
+
# @return [Array<Array<Token>>] tokens, split into lines.
|
54
|
+
def lines
|
55
|
+
return @lines ||= @tokens.group_by(&:__LINE__)
|
66
56
|
end
|
67
57
|
|
68
58
|
private
|
@@ -73,11 +63,8 @@ class XMP2Assert::Parser < Ripper
|
|
73
63
|
|
74
64
|
def on_comment c
|
75
65
|
case c
|
76
|
-
when /^#
|
77
|
-
yylex =
|
78
|
-
yylval = $'
|
79
|
-
when /^# [~>]> / then
|
80
|
-
yylex = :>>
|
66
|
+
when /^# ([~=!>]>) / then
|
67
|
+
yylex = $1.intern
|
81
68
|
yylval = $'
|
82
69
|
else
|
83
70
|
yylex = :comment
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#! /your/favourite/path/to/ruby
|
2
|
+
# -*- mode: ruby; coding: utf-8; indent-tabs-mode: nil; ruby-indent-level 2 -*-
|
3
|
+
# -*- frozen_string_literal: true -*-
|
4
|
+
# -*- warn_indent: true -*-
|
5
|
+
|
6
|
+
# Copyright (c) 2017 Urabe, Shyouhei
|
7
|
+
#
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
# of this software and associated documentation files (the "Software"), to deal
|
10
|
+
# in the Software without restriction, including without limitation the rights
|
11
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
# copies of the Software, and to permit persons to whom the Software is
|
13
|
+
# furnished to do so, subject to the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
|
+
# SOFTWARE.
|
25
|
+
|
26
|
+
require 'erb'
|
27
|
+
require 'pathname'
|
28
|
+
require 'tempfile'
|
29
|
+
require_relative 'namespace'
|
30
|
+
require_relative 'quasifile'
|
31
|
+
|
32
|
+
# Compiles a {XMP2Assert::Quasifile} into a separate ruby script.
|
33
|
+
module XMP2Assert::Renderer
|
34
|
+
|
35
|
+
# I learned this handy "super-private" maneuver from @a_matsuda
|
36
|
+
# cf: https://github.com/rails/rails/pull/27363/files
|
37
|
+
using Module.new {
|
38
|
+
refine XMP2Assert::Renderer do
|
39
|
+
private
|
40
|
+
|
41
|
+
myself = Pathname.new __FILE__
|
42
|
+
path = myself + '../template.erb'
|
43
|
+
src = path.read mode: 'rb:binary:binary'
|
44
|
+
erb = ERB.new src, nil, '%-'
|
45
|
+
eval <<-"end", binding, path.realpath.to_path, -1
|
46
|
+
def erb(script, exception)\n#{erb.src}\nend
|
47
|
+
end
|
48
|
+
end
|
49
|
+
}
|
50
|
+
|
51
|
+
public
|
52
|
+
|
53
|
+
# Compiles a {Quasifile} into a separate ruby script. Generated file should
|
54
|
+
# be passable to a separate ruby process. This method yields that file if a
|
55
|
+
# block is given, and deletes it afterwards. When no block is passed, leaves
|
56
|
+
# it undeleted; to clean it up is up to the caller then.
|
57
|
+
#
|
58
|
+
# @param qfile [Quasifile] a file to convert to.
|
59
|
+
# @param exception [String] :TBD:
|
60
|
+
# @return [File] rendered file, if no block is given.
|
61
|
+
# @yieldparam [File] rendered file, if block is given.
|
62
|
+
def render qfile, exception = nil
|
63
|
+
s = erb qfile, exception
|
64
|
+
if defined? yield
|
65
|
+
Tempfile.create '' do |f|
|
66
|
+
f.write s
|
67
|
+
f.flush
|
68
|
+
return yield f
|
69
|
+
end
|
70
|
+
else
|
71
|
+
f = Tempfile.create ''
|
72
|
+
f.write s
|
73
|
+
f.flush
|
74
|
+
return f
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#! /your/favourite/path/to/ruby
|
2
|
+
# -*- mode: ruby; coding: utf-8; indent-tabs-mode: nil; ruby-indent-level 2 -*-
|
3
|
+
# -*- frozen_string_literal: true -*-
|
4
|
+
# -*- warn_indent: true -*-
|
5
|
+
|
6
|
+
# Copyright (c) 2017 Urabe, Shyouhei
|
7
|
+
#
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
# of this software and associated documentation files (the "Software"), to deal
|
10
|
+
# in the Software without restriction, including without limitation the rights
|
11
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
# copies of the Software, and to permit persons to whom the Software is
|
13
|
+
# furnished to do so, subject to the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
|
+
# SOFTWARE.
|
25
|
+
|
26
|
+
require 'rbconfig'
|
27
|
+
require_relative 'namespace'
|
28
|
+
|
29
|
+
# Helper class to spawn a ruby process, and communicate with it.
|
30
|
+
class XMP2Assert::Spawn
|
31
|
+
|
32
|
+
# @param path [#to_path] a path to a ruby program.
|
33
|
+
# @param rubyopts [Array<String>] extra opts to pass to ruby interpreter.
|
34
|
+
# @param opts [Hash] extra opts to pass to Process.spawn
|
35
|
+
# @yieldparam pid [Integer] child process' pid.
|
36
|
+
# @yieldparam stdin [IO] child process stdin.
|
37
|
+
# @yieldparam stdout [IO] child process stdout.
|
38
|
+
# @yieldparam stderr [IO] child process stderr.
|
39
|
+
# @yieldparam tx [IO] pipe to communicate.
|
40
|
+
# @yieldparam rx [IO] pipe to communicate.
|
41
|
+
def initialize path, rubyopts: nil, **opts
|
42
|
+
ours, theirs, spec = pipes %i'r w w r w'
|
43
|
+
opts.update Hash[spec]
|
44
|
+
|
45
|
+
argv = [RbConfig.ruby, rubyopts, path.to_path]
|
46
|
+
argv << theirs[3..4].map {|i| i.to_i.to_s }
|
47
|
+
argv.flatten!
|
48
|
+
argv.compact!
|
49
|
+
|
50
|
+
begin
|
51
|
+
pid = Process.spawn(*argv, opts)
|
52
|
+
theirs.each(&:close)
|
53
|
+
stdin, stdout, stdrrr, rx, tx = * ours
|
54
|
+
yield pid, stdin, stdout, stdrrr, rx, tx
|
55
|
+
ensure
|
56
|
+
ours.each(&:close)
|
57
|
+
Process.waitpid pid if pid
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def pipes directions
|
64
|
+
ours = []
|
65
|
+
theirs = []
|
66
|
+
directions.each_with_index do |d, i|
|
67
|
+
r, w = IO.pipe
|
68
|
+
t = (d == :r) ? r : w
|
69
|
+
o = (d == :r) ? w : r
|
70
|
+
theirs[i] = t
|
71
|
+
ours[i] = o
|
72
|
+
o.sync = true
|
73
|
+
o.binmode
|
74
|
+
end
|
75
|
+
iospec = theirs.map {|i| [i, i] }
|
76
|
+
iospec[0][0] = 0
|
77
|
+
iospec[1][0] = 1
|
78
|
+
iospec[2][0] = 2
|
79
|
+
return ours, theirs, iospec
|
80
|
+
end
|
81
|
+
end
|
data/lib/xmp2assert/template.erb
CHANGED
@@ -26,10 +26,64 @@
|
|
26
26
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
27
27
|
# SOFTWARE.
|
28
28
|
|
29
|
+
# Because we don't want to pollute $", we write everything needed in this file.
|
30
|
+
|
31
|
+
saved_binding = TOPLEVEL_BINDING.dup
|
32
|
+
rx = IO.for_fd ARGV.shift.to_i, 'rb'
|
33
|
+
tx = IO.for_fd ARGV.shift.to_i, 'wb'
|
34
|
+
|
35
|
+
using Module.new {
|
36
|
+
refine Kernel do
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
Kernel.class_variable_set('@@rx', rx)
|
41
|
+
Kernel.class_variable_set('@@tx', tx)
|
42
|
+
|
43
|
+
def assert_xmp_send *argv
|
44
|
+
rx = Kernel.class_variable_get('@@rx')
|
45
|
+
tx = Kernel.class_variable_get('@@tx')
|
46
|
+
str = Marshal.dump argv
|
47
|
+
tx.puts str.length
|
48
|
+
tx.write str
|
49
|
+
tx.flush
|
50
|
+
if rx.gets then
|
51
|
+
return true
|
52
|
+
else
|
53
|
+
Process.exit false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
}
|
58
|
+
|
59
|
+
# Simulates test-unit.
|
60
|
+
# @param xmp [String] expected pattern of inspect.
|
61
|
+
# @param expr [Object] object to check.
|
62
|
+
# @param message [String] extra failure message.
|
63
|
+
# @note assertion is done in parent process, not in it.
|
64
|
+
def xmp2assert_assert xmp, expr, c = caller(3)
|
65
|
+
c.reject! {|i| i.start_with? __FILE__ }
|
66
|
+
return assert_xmp_send xmp, expr.inspect, c
|
67
|
+
end
|
68
|
+
|
69
|
+
# Here we go.
|
70
|
+
|
29
71
|
file = "<%= script.__FILE__ %>" #=
|
30
72
|
line = <%= script.__LINE__ %> #=
|
31
73
|
src = ::DATA.read
|
32
|
-
|
74
|
+
% if exception and not exception.empty?
|
75
|
+
begin
|
76
|
+
eval src, saved_binding, file, line
|
77
|
+
rescue Exception => e
|
78
|
+
b = e.backtrace
|
79
|
+
b.reject! {|i| i.start_with? __FILE__ }
|
80
|
+
e.set_backtrace b
|
81
|
+
expected = <%= exception.dump %> #=
|
82
|
+
xmp2assert_assert expected, e, e.backtrace
|
83
|
+
end
|
84
|
+
% else
|
85
|
+
eval src, saved_binding, file, line
|
86
|
+
% end
|
33
87
|
|
34
88
|
# Below is a generated software, sourced from <%= script.__FILE__ %>.
|
35
89
|
# Above copyright notice does not apply any further. Consult the original.
|
data/lib/xmp2assert/version.rb
CHANGED
data/xmp2assert.gemspec
CHANGED
@@ -49,7 +49,7 @@ Gem::Specification.new do |spec|
|
|
49
49
|
spec.homepage = 'https://github.com/shyouhei/xmp2assert'
|
50
50
|
spec.license = 'MIT'
|
51
51
|
spec.files = `git ls-files -z`.split("\x0").reject { |f|
|
52
|
-
f.match(%r'^(test|spec|features)/')
|
52
|
+
f.match(%r'^(test|spec|features|samples)/')
|
53
53
|
}
|
54
54
|
spec.bindir = 'exe'
|
55
55
|
spec.executables = spec.files.grep(%r'^exe/') { |f| File.basename(f) }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xmp2assert
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '
|
4
|
+
version: '8'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Urabe, Shyouhei
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-05-
|
11
|
+
date: 2017-05-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -220,6 +220,8 @@ files:
|
|
220
220
|
- lib/xmp2assert/parser.rb
|
221
221
|
- lib/xmp2assert/prettier_inspect.rb
|
222
222
|
- lib/xmp2assert/quasifile.rb
|
223
|
+
- lib/xmp2assert/renderer.rb
|
224
|
+
- lib/xmp2assert/spawn.rb
|
223
225
|
- lib/xmp2assert/template.erb
|
224
226
|
- lib/xmp2assert/token.rb
|
225
227
|
- lib/xmp2assert/version.rb
|
@@ -245,7 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
245
247
|
version: '0'
|
246
248
|
requirements: []
|
247
249
|
rubyforge_project:
|
248
|
-
rubygems_version: 2.6.
|
250
|
+
rubygems_version: 2.6.12
|
249
251
|
signing_key:
|
250
252
|
specification_version: 4
|
251
253
|
summary: auto-generate assertions from `# =>` comments
|