xmp2assert 6 → 8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0a3d094937f1507beb58f9131f1779863df91f7e
4
- data.tar.gz: cc3f457c3d31739bc235222aa6b9fa1adbdd5287
3
+ metadata.gz: 14c558b16e211cc2198f003c708b87acebe83406
4
+ data.tar.gz: ed3a7c00a1d6b5bda7e734b00d0cd43ba1954acb
5
5
  SHA512:
6
- metadata.gz: 8b86def4caf24dfc2a2e656f3359bd66b77ebf1a3d6b5507b06d05435ba4e6e4692d0d601e6a2ba733fe0cce4f2a42f1d5ac729d5c3a8e0442d279666184ea14
7
- data.tar.gz: e1d3a2fdb734ba4d1f653c40d0ff727d006bc332370d4ebd713d8d664a0ba24a2e739639086e21ab58ab3cd01d9d30f99a24e2d6f1520e5b884c26cd410ca2df
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 this file is a rubocop bug.
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
@@ -44,9 +44,7 @@ class TC_Main < Test::Unit::TestCase
44
44
  end
45
45
  else
46
46
  test f.__FILE__ do
47
- t, o = XMP2Assert::Converter.convert f
48
- t.eval binding if k.include? :'=>'
49
- assert_capture2e o, f if k.include? :'>>'
47
+ assert_xmp f
50
48
  end
51
49
  end
52
50
  end
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 output.
41
+ # Run a ruby script and assert for its comment. This is the main API.
43
42
  #
44
43
  # ```ruby
45
- # assert_capture2e "foo\n", Quasifile.new("puts 'foo'")
44
+ # assert_xmp "1 + 2 # => 3"
46
45
  # ```
47
46
  #
48
- # @param expected [String] expected output.
49
- # @param script [Quasifile] a ruby script.
50
- # @param message [String] extra failure message.
51
- # @param rubyopts [Array<String>] extra opts to pass to ruby process.
52
- # @param opts [Hash{Symbol=>Object}] extra opts to pass to spawn.
53
- # @note
54
- # As the method name implies the assertion is against both stdin and stderr
55
- # at once. This is for convenience.
56
- def assert_capture2e expected, script, message = nil, rubyopts: nil, **opts
57
- qscript = XMP2Assert::Quasifile.new script
58
- actual, _ = ruby qscript, rubyopts: rubyopts, **opts
59
- actual.force_encoding expected.encoding
60
- return assert_xmp_raw expected, actual, message
61
- end
62
-
63
- # Assert if the given expression is in the same form of xmp.
64
- #
65
- # ```ruby
66
- # assert_xmp '#<Object:0x007f896c9b49c8>', Object.new
67
- # ```
68
- #
69
- # @param xmp [String] expected pattern of inspect.
70
- # @param expr [Object] object to check.
71
- # @param message [String] extra failure message.
72
- def assert_xmp xmp, expr, message = nil
73
- assert_xmp_raw xmp, expr.inspect, message
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
- private
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(msg,
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>] either empty, :=>, :>>, or both.
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
- return parser \
52
- .tokens \
53
- .map(&:to_sym) \
54
- .sort \
55
- .uniq \
56
- .map {|i| case i when :'=>', :'>>' then i else nil end } \
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
@@ -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 [(Quasifile,String)] tuple of generated file and its expected
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
- outputs = aggregate
68
- merge_xmp
68
+ understand
69
+ stdout, stderr, exceptions = aggregate
69
70
  render
70
71
  ret = XMP2Assert::Quasifile.new @tokens.join, *@program.locations
71
- return ret, outputs
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 xmp
85
- n = gensym xmp
86
- x = xmp.chomp.dump
87
- return sprintf ".tap {|%s| assert_xmp(%s, %s) }", n, x, n
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.same_line_as stop
213
+ line = @program.lines[stop.__LINE__]
148
214
  line.sort!
149
- line.select! {|i| i.__COLUMN__ <= stop.__COLUMN__ }
215
+ line = line.select {|i| i.__COLUMN__ <= stop.__COLUMN__ }
150
216
  line = line.drop_while {|i| not beginning_of_expr? i }
151
- until valid? line do
152
- line.shift
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 %r/(.+)_beg$/ =~ start then
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 aggregate
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 :sp then next
263
- when :'=>' then
264
- str = tok.to_s
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 << str
267
- str.replace "\n"
268
- tok.yylex = :nl
343
+ xmp.to_s.concat tok.to_s.sub(/^#/, '')
344
+ tok.yylval = "#\n"
269
345
  else
270
- xmp = str
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
- next unless tok.to_sym == :'=>'
281
- xmp = tok.to_s
282
- tap = gen_tap xmp
283
- xmp.replace "\n"
284
- x, y = rev_lookup_expr tok
285
- if x and x != y then
286
- x.to_s.sub! %r/^/, '('
287
- y.to_s.sub! %r/$/, ')'
288
- end
289
- y.to_s.sub! %r/$/ do
290
- tap # use block to prevent backslash substitution
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
@@ -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
@@ -48,21 +48,11 @@ class XMP2Assert::Parser < Ripper
48
48
  return @qfile.__FILE__, @qfile.__LINE__
49
49
  end
50
50
 
51
- # Find tokens that are in the same line as the argument.
51
+ # Split tokens into lines.
52
52
  #
53
- # ```ruby
54
- # [ 1, # => 1
55
- # 2, # => 2
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 /^# => / then
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
@@ -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
- eval src, binding, file, line
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.
@@ -24,4 +24,4 @@
24
24
  # SOFTWARE.
25
25
  ;
26
26
 
27
- XMP2Assert::VERSION = 6
27
+ XMP2Assert::VERSION = 8
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: '6'
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-09 00:00:00.000000000 Z
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.11
250
+ rubygems_version: 2.6.12
249
251
  signing_key:
250
252
  specification_version: 4
251
253
  summary: auto-generate assertions from `# =>` comments