json 2.4.1-java → 2.5.0-java
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.
- checksums.yaml +4 -4
- data/lib/json/common.rb +32 -13
- data/lib/json/ext/generator.jar +0 -0
- data/lib/json/ext/parser.jar +0 -0
- data/lib/json/version.rb +1 -1
- data/tests/json_generator_test.rb +4 -37
- data/tests/lib/core_assertions.rb +763 -0
- data/tests/lib/envutil.rb +365 -0
- data/tests/lib/find_executable.rb +22 -0
- data/tests/lib/helper.rb +4 -0
- data/tests/ractor_test.rb +30 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74f743d2be1a68eab16cd2de890410f556ff0bf02d859277eebc29b3a02f0e21
|
4
|
+
data.tar.gz: 8bbc870feb59d924a89a348c9dbafd217686963dd218bf315163fc73ebee9939
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aea35af3b562aa73b55f252ea46288752ffe395609183aecccdb5b3fb28e6675135afc1e8c3d32bd98a632faee73f976d3bace9756cc5bda393f0e38435c182c
|
7
|
+
data.tar.gz: a0420452eaa37283105ae2d6a17414d020ba09a66a7674be776fc77f491d1f5529431e380bc0defde1d6a4ac2750506a3ac8f63e4ebab27ee7e6111296659eaa
|
data/lib/json/common.rb
CHANGED
@@ -71,22 +71,28 @@ module JSON
|
|
71
71
|
end
|
72
72
|
self.state = generator::State
|
73
73
|
const_set :State, self.state
|
74
|
-
const_set :SAFE_STATE_PROTOTYPE, State.new
|
75
|
-
|
74
|
+
const_set :SAFE_STATE_PROTOTYPE, State.new # for JRuby
|
75
|
+
ensure
|
76
|
+
$VERBOSE = old
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_fast_state
|
80
|
+
State.new(
|
76
81
|
:indent => '',
|
77
82
|
:space => '',
|
78
83
|
:object_nl => "",
|
79
84
|
:array_nl => "",
|
80
85
|
:max_nesting => false
|
81
86
|
)
|
82
|
-
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_pretty_state
|
90
|
+
State.new(
|
83
91
|
:indent => ' ',
|
84
92
|
:space => ' ',
|
85
93
|
:object_nl => "\n",
|
86
94
|
:array_nl => "\n"
|
87
95
|
)
|
88
|
-
ensure
|
89
|
-
$VERBOSE = old
|
90
96
|
end
|
91
97
|
|
92
98
|
# Returns the JSON generator module that is used by JSON. This is
|
@@ -98,13 +104,26 @@ module JSON
|
|
98
104
|
# either JSON::Ext::Generator::State or JSON::Pure::Generator::State:
|
99
105
|
# JSON.state # => JSON::Ext::Generator::State
|
100
106
|
attr_accessor :state
|
107
|
+
end
|
108
|
+
|
109
|
+
DEFAULT_CREATE_ID = 'json_class'.freeze
|
110
|
+
private_constant :DEFAULT_CREATE_ID
|
111
|
+
|
112
|
+
CREATE_ID_TLS_KEY = "JSON.create_id".freeze
|
113
|
+
private_constant :CREATE_ID_TLS_KEY
|
114
|
+
|
115
|
+
# Sets create identifier, which is used to decide if the _json_create_
|
116
|
+
# hook of a class should be called; initial value is +json_class+:
|
117
|
+
# JSON.create_id # => 'json_class'
|
118
|
+
def self.create_id=(new_value)
|
119
|
+
Thread.current[CREATE_ID_TLS_KEY] = new_value.dup.freeze
|
120
|
+
end
|
101
121
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
122
|
+
# Returns the current create identifier.
|
123
|
+
# See also JSON.create_id=.
|
124
|
+
def self.create_id
|
125
|
+
Thread.current[CREATE_ID_TLS_KEY] || DEFAULT_CREATE_ID
|
106
126
|
end
|
107
|
-
self.create_id = 'json_class'
|
108
127
|
|
109
128
|
NaN = 0.0/0
|
110
129
|
|
@@ -276,7 +295,7 @@ module JSON
|
|
276
295
|
if State === opts
|
277
296
|
state, opts = opts, nil
|
278
297
|
else
|
279
|
-
state =
|
298
|
+
state = State.new
|
280
299
|
end
|
281
300
|
if opts
|
282
301
|
if opts.respond_to? :to_hash
|
@@ -315,7 +334,7 @@ module JSON
|
|
315
334
|
if State === opts
|
316
335
|
state, opts = opts, nil
|
317
336
|
else
|
318
|
-
state =
|
337
|
+
state = JSON.create_fast_state
|
319
338
|
end
|
320
339
|
if opts
|
321
340
|
if opts.respond_to? :to_hash
|
@@ -370,7 +389,7 @@ module JSON
|
|
370
389
|
if State === opts
|
371
390
|
state, opts = opts, nil
|
372
391
|
else
|
373
|
-
state =
|
392
|
+
state = JSON.create_pretty_state
|
374
393
|
end
|
375
394
|
if opts
|
376
395
|
if opts.respond_to? :to_hash
|
data/lib/json/ext/generator.jar
CHANGED
Binary file
|
data/lib/json/ext/parser.jar
CHANGED
Binary file
|
data/lib/json/version.rb
CHANGED
@@ -48,35 +48,6 @@ EOT
|
|
48
48
|
$VERBOSE = v
|
49
49
|
end
|
50
50
|
|
51
|
-
def test_remove_const_segv
|
52
|
-
stress = GC.stress
|
53
|
-
const = JSON::SAFE_STATE_PROTOTYPE.dup
|
54
|
-
|
55
|
-
bignum_too_long_to_embed_as_string = 1234567890123456789012345
|
56
|
-
expect = bignum_too_long_to_embed_as_string.to_s
|
57
|
-
GC.stress = true
|
58
|
-
|
59
|
-
10.times do |i|
|
60
|
-
tmp = bignum_too_long_to_embed_as_string.to_json
|
61
|
-
raise "'\#{expect}' is expected, but '\#{tmp}'" unless tmp == expect
|
62
|
-
end
|
63
|
-
|
64
|
-
silence do
|
65
|
-
JSON.const_set :SAFE_STATE_PROTOTYPE, nil
|
66
|
-
end
|
67
|
-
|
68
|
-
10.times do |i|
|
69
|
-
assert_raise TypeError do
|
70
|
-
bignum_too_long_to_embed_as_string.to_json
|
71
|
-
end
|
72
|
-
end
|
73
|
-
ensure
|
74
|
-
GC.stress = stress
|
75
|
-
silence do
|
76
|
-
JSON.const_set :SAFE_STATE_PROTOTYPE, const
|
77
|
-
end
|
78
|
-
end if JSON.const_defined?("Ext") && RUBY_ENGINE != 'jruby'
|
79
|
-
|
80
51
|
def test_generate
|
81
52
|
json = generate(@hash)
|
82
53
|
assert_equal(parse(@json2), parse(json))
|
@@ -171,7 +142,7 @@ EOT
|
|
171
142
|
end
|
172
143
|
|
173
144
|
def test_pretty_state
|
174
|
-
state =
|
145
|
+
state = JSON.create_pretty_state
|
175
146
|
assert_equal({
|
176
147
|
:allow_nan => false,
|
177
148
|
:array_nl => "\n",
|
@@ -188,7 +159,7 @@ EOT
|
|
188
159
|
end
|
189
160
|
|
190
161
|
def test_safe_state
|
191
|
-
state =
|
162
|
+
state = JSON::State.new
|
192
163
|
assert_equal({
|
193
164
|
:allow_nan => false,
|
194
165
|
:array_nl => "",
|
@@ -205,7 +176,7 @@ EOT
|
|
205
176
|
end
|
206
177
|
|
207
178
|
def test_fast_state
|
208
|
-
state =
|
179
|
+
state = JSON.create_fast_state
|
209
180
|
assert_equal({
|
210
181
|
:allow_nan => false,
|
211
182
|
:array_nl => "",
|
@@ -241,12 +212,8 @@ EOT
|
|
241
212
|
|
242
213
|
def test_depth
|
243
214
|
ary = []; ary << ary
|
244
|
-
assert_equal 0, JSON::SAFE_STATE_PROTOTYPE.depth
|
245
215
|
assert_raise(JSON::NestingError) { generate(ary) }
|
246
|
-
assert_equal 0, JSON::SAFE_STATE_PROTOTYPE.depth
|
247
|
-
assert_equal 0, JSON::PRETTY_STATE_PROTOTYPE.depth
|
248
216
|
assert_raise(JSON::NestingError) { JSON.pretty_generate(ary) }
|
249
|
-
assert_equal 0, JSON::PRETTY_STATE_PROTOTYPE.depth
|
250
217
|
s = JSON.state.new
|
251
218
|
assert_equal 0, s.depth
|
252
219
|
assert_raise(JSON::NestingError) { ary.to_json(s) }
|
@@ -265,7 +232,7 @@ EOT
|
|
265
232
|
end
|
266
233
|
|
267
234
|
def test_gc
|
268
|
-
if respond_to?(:assert_in_out_err)
|
235
|
+
if respond_to?(:assert_in_out_err) && !(RUBY_PLATFORM =~ /java/)
|
269
236
|
assert_in_out_err(%w[-rjson --disable-gems], <<-EOS, [], [])
|
270
237
|
bignum_too_long_to_embed_as_string = 1234567890123456789012345
|
271
238
|
expect = bignum_too_long_to_embed_as_string.to_s
|
@@ -0,0 +1,763 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Test
|
4
|
+
module Unit
|
5
|
+
module Assertions
|
6
|
+
def _assertions= n # :nodoc:
|
7
|
+
@_assertions = n
|
8
|
+
end
|
9
|
+
|
10
|
+
def _assertions # :nodoc:
|
11
|
+
@_assertions ||= 0
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Returns a proc that will output +msg+ along with the default message.
|
16
|
+
|
17
|
+
def message msg = nil, ending = nil, &default
|
18
|
+
proc {
|
19
|
+
msg = msg.call.chomp(".") if Proc === msg
|
20
|
+
custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty?
|
21
|
+
"#{custom_message}#{default.call}#{ending || "."}"
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module CoreAssertions
|
27
|
+
if defined?(MiniTest)
|
28
|
+
require_relative '../../envutil'
|
29
|
+
# for ruby core testing
|
30
|
+
include MiniTest::Assertions
|
31
|
+
|
32
|
+
# Compatibility hack for assert_raise
|
33
|
+
Test::Unit::AssertionFailedError = MiniTest::Assertion
|
34
|
+
else
|
35
|
+
module MiniTest
|
36
|
+
class Assertion < Exception; end
|
37
|
+
class Skip < Assertion; end
|
38
|
+
end
|
39
|
+
|
40
|
+
require 'pp'
|
41
|
+
require_relative 'envutil'
|
42
|
+
include Test::Unit::Assertions
|
43
|
+
end
|
44
|
+
|
45
|
+
def mu_pp(obj) #:nodoc:
|
46
|
+
obj.pretty_inspect.chomp
|
47
|
+
end
|
48
|
+
|
49
|
+
def assert_file
|
50
|
+
AssertFile
|
51
|
+
end
|
52
|
+
|
53
|
+
FailDesc = proc do |status, message = "", out = ""|
|
54
|
+
now = Time.now
|
55
|
+
proc do
|
56
|
+
EnvUtil.failure_description(status, now, message, out)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil,
|
61
|
+
success: nil, **opt)
|
62
|
+
args = Array(args).dup
|
63
|
+
args.insert((Hash === args[0] ? 1 : 0), '--disable=gems')
|
64
|
+
stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt)
|
65
|
+
desc = FailDesc[status, message, stderr]
|
66
|
+
if block_given?
|
67
|
+
raise "test_stdout ignored, use block only or without block" if test_stdout != []
|
68
|
+
raise "test_stderr ignored, use block only or without block" if test_stderr != []
|
69
|
+
yield(stdout.lines.map {|l| l.chomp }, stderr.lines.map {|l| l.chomp }, status)
|
70
|
+
else
|
71
|
+
all_assertions(desc) do |a|
|
72
|
+
[["stdout", test_stdout, stdout], ["stderr", test_stderr, stderr]].each do |key, exp, act|
|
73
|
+
a.for(key) do
|
74
|
+
if exp.is_a?(Regexp)
|
75
|
+
assert_match(exp, act)
|
76
|
+
elsif exp.all? {|e| String === e}
|
77
|
+
assert_equal(exp, act.lines.map {|l| l.chomp })
|
78
|
+
else
|
79
|
+
assert_pattern_list(exp, act)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
unless success.nil?
|
84
|
+
a.for("success?") do
|
85
|
+
if success
|
86
|
+
assert_predicate(status, :success?)
|
87
|
+
else
|
88
|
+
assert_not_predicate(status, :success?)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
status
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if defined?(RubyVM::InstructionSequence)
|
98
|
+
def syntax_check(code, fname, line)
|
99
|
+
code = code.dup.force_encoding(Encoding::UTF_8)
|
100
|
+
RubyVM::InstructionSequence.compile(code, fname, fname, line)
|
101
|
+
:ok
|
102
|
+
ensure
|
103
|
+
raise if SyntaxError === $!
|
104
|
+
end
|
105
|
+
else
|
106
|
+
def syntax_check(code, fname, line)
|
107
|
+
code = code.b
|
108
|
+
code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) {
|
109
|
+
"#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n"
|
110
|
+
}
|
111
|
+
code = code.force_encoding(Encoding::UTF_8)
|
112
|
+
catch {|tag| eval(code, binding, fname, line - 1)}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt)
|
117
|
+
# TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail
|
118
|
+
pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
|
119
|
+
|
120
|
+
require_relative '../../memory_status'
|
121
|
+
raise MiniTest::Skip, "unsupported platform" unless defined?(Memory::Status)
|
122
|
+
|
123
|
+
token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m"
|
124
|
+
token_dump = token.dump
|
125
|
+
token_re = Regexp.quote(token)
|
126
|
+
envs = args.shift if Array === args and Hash === args.first
|
127
|
+
args = [
|
128
|
+
"--disable=gems",
|
129
|
+
"-r", File.expand_path("../../../memory_status", __FILE__),
|
130
|
+
*args,
|
131
|
+
"-v", "-",
|
132
|
+
]
|
133
|
+
if defined? Memory::NO_MEMORY_LEAK_ENVS then
|
134
|
+
envs ||= {}
|
135
|
+
newenvs = envs.merge(Memory::NO_MEMORY_LEAK_ENVS) { |_, _, _| break }
|
136
|
+
envs = newenvs if newenvs
|
137
|
+
end
|
138
|
+
args.unshift(envs) if envs
|
139
|
+
cmd = [
|
140
|
+
'END {STDERR.puts '"#{token_dump}"'"FINAL=#{Memory::Status.new}"}',
|
141
|
+
prepare,
|
142
|
+
'STDERR.puts('"#{token_dump}"'"START=#{$initial_status = Memory::Status.new}")',
|
143
|
+
'$initial_size = $initial_status.size',
|
144
|
+
code,
|
145
|
+
'GC.start',
|
146
|
+
].join("\n")
|
147
|
+
_, err, status = EnvUtil.invoke_ruby(args, cmd, true, true, **opt)
|
148
|
+
before = err.sub!(/^#{token_re}START=(\{.*\})\n/, '') && Memory::Status.parse($1)
|
149
|
+
after = err.sub!(/^#{token_re}FINAL=(\{.*\})\n/, '') && Memory::Status.parse($1)
|
150
|
+
assert(status.success?, FailDesc[status, message, err])
|
151
|
+
([:size, (rss && :rss)] & after.members).each do |n|
|
152
|
+
b = before[n]
|
153
|
+
a = after[n]
|
154
|
+
next unless a > 0 and b > 0
|
155
|
+
assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"})
|
156
|
+
end
|
157
|
+
rescue LoadError
|
158
|
+
pend
|
159
|
+
end
|
160
|
+
|
161
|
+
# :call-seq:
|
162
|
+
# assert_nothing_raised( *args, &block )
|
163
|
+
#
|
164
|
+
#If any exceptions are given as arguments, the assertion will
|
165
|
+
#fail if one of those exceptions are raised. Otherwise, the test fails
|
166
|
+
#if any exceptions are raised.
|
167
|
+
#
|
168
|
+
#The final argument may be a failure message.
|
169
|
+
#
|
170
|
+
# assert_nothing_raised RuntimeError do
|
171
|
+
# raise Exception #Assertion passes, Exception is not a RuntimeError
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# assert_nothing_raised do
|
175
|
+
# raise Exception #Assertion fails
|
176
|
+
# end
|
177
|
+
def assert_nothing_raised(*args)
|
178
|
+
self._assertions += 1
|
179
|
+
if Module === args.last
|
180
|
+
msg = nil
|
181
|
+
else
|
182
|
+
msg = args.pop
|
183
|
+
end
|
184
|
+
begin
|
185
|
+
line = __LINE__; yield
|
186
|
+
rescue MiniTest::Skip
|
187
|
+
raise
|
188
|
+
rescue Exception => e
|
189
|
+
bt = e.backtrace
|
190
|
+
as = e.instance_of?(MiniTest::Assertion)
|
191
|
+
if as
|
192
|
+
ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o
|
193
|
+
bt.reject! {|ln| ans =~ ln}
|
194
|
+
end
|
195
|
+
if ((args.empty? && !as) ||
|
196
|
+
args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a })
|
197
|
+
msg = message(msg) {
|
198
|
+
"Exception raised:\n<#{mu_pp(e)}>\n" +
|
199
|
+
"Backtrace:\n" +
|
200
|
+
e.backtrace.map{|frame| " #{frame}"}.join("\n")
|
201
|
+
}
|
202
|
+
raise MiniTest::Assertion, msg.call, bt
|
203
|
+
else
|
204
|
+
raise
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil)
|
210
|
+
fname ||= caller_locations(2, 1)[0]
|
211
|
+
mesg ||= fname.to_s
|
212
|
+
verbose, $VERBOSE = $VERBOSE, verbose
|
213
|
+
case
|
214
|
+
when Array === fname
|
215
|
+
fname, line = *fname
|
216
|
+
when defined?(fname.path) && defined?(fname.lineno)
|
217
|
+
fname, line = fname.path, fname.lineno
|
218
|
+
else
|
219
|
+
line = 1
|
220
|
+
end
|
221
|
+
yield(code, fname, line, message(mesg) {
|
222
|
+
if code.end_with?("\n")
|
223
|
+
"```\n#{code}```\n"
|
224
|
+
else
|
225
|
+
"```\n#{code}\n```\n""no-newline"
|
226
|
+
end
|
227
|
+
})
|
228
|
+
ensure
|
229
|
+
$VERBOSE = verbose
|
230
|
+
end
|
231
|
+
|
232
|
+
def assert_valid_syntax(code, *args, **opt)
|
233
|
+
prepare_syntax_check(code, *args, **opt) do |src, fname, line, mesg|
|
234
|
+
yield if defined?(yield)
|
235
|
+
assert_nothing_raised(SyntaxError, mesg) do
|
236
|
+
assert_equal(:ok, syntax_check(src, fname, line), mesg)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def assert_normal_exit(testsrc, message = '', child_env: nil, **opt)
|
242
|
+
assert_valid_syntax(testsrc, caller_locations(1, 1)[0])
|
243
|
+
if child_env
|
244
|
+
child_env = [child_env]
|
245
|
+
else
|
246
|
+
child_env = []
|
247
|
+
end
|
248
|
+
out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt)
|
249
|
+
assert !status.signaled?, FailDesc[status, message, out]
|
250
|
+
end
|
251
|
+
|
252
|
+
def assert_ruby_status(args, test_stdin="", message=nil, **opt)
|
253
|
+
out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt)
|
254
|
+
desc = FailDesc[status, message, out]
|
255
|
+
assert(!status.signaled?, desc)
|
256
|
+
message ||= "ruby exit status is not success:"
|
257
|
+
assert(status.success?, desc)
|
258
|
+
end
|
259
|
+
|
260
|
+
ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM")
|
261
|
+
|
262
|
+
def separated_runner(out = nil)
|
263
|
+
out = out ? IO.new(out, 'w') : STDOUT
|
264
|
+
at_exit {
|
265
|
+
out.puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}"
|
266
|
+
}
|
267
|
+
Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner)
|
268
|
+
end
|
269
|
+
|
270
|
+
def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt)
|
271
|
+
unless file and line
|
272
|
+
loc, = caller_locations(1,1)
|
273
|
+
file ||= loc.path
|
274
|
+
line ||= loc.lineno
|
275
|
+
end
|
276
|
+
capture_stdout = true
|
277
|
+
unless /mswin|mingw/ =~ RUBY_PLATFORM
|
278
|
+
capture_stdout = false
|
279
|
+
opt[:out] = MiniTest::Unit.output if defined?(MiniTest::Unit)
|
280
|
+
res_p, res_c = IO.pipe
|
281
|
+
opt[res_c.fileno] = res_c.fileno
|
282
|
+
end
|
283
|
+
src = <<eom
|
284
|
+
# -*- coding: #{line += __LINE__; src.encoding}; -*-
|
285
|
+
BEGIN {
|
286
|
+
require "test/unit";include Test::Unit::Assertions;require #{(__dir__ + "/core_assertions").dump};include Test::Unit::CoreAssertions
|
287
|
+
separated_runner #{res_c&.fileno}
|
288
|
+
}
|
289
|
+
#{line -= __LINE__; src}
|
290
|
+
eom
|
291
|
+
args = args.dup
|
292
|
+
args.insert((Hash === args.first ? 1 : 0), "-w", "--disable=gems", *$:.map {|l| "-I#{l}"})
|
293
|
+
stdout, stderr, status = EnvUtil.invoke_ruby(args, src, capture_stdout, true, **opt)
|
294
|
+
ensure
|
295
|
+
if res_c
|
296
|
+
res_c.close
|
297
|
+
res = res_p.read
|
298
|
+
res_p.close
|
299
|
+
else
|
300
|
+
res = stdout
|
301
|
+
end
|
302
|
+
raise if $!
|
303
|
+
abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig))
|
304
|
+
assert(!abort, FailDesc[status, nil, stderr])
|
305
|
+
self._assertions += res[/^assertions=(\d+)/, 1].to_i
|
306
|
+
begin
|
307
|
+
res = Marshal.load(res.unpack1("m"))
|
308
|
+
rescue => marshal_error
|
309
|
+
ignore_stderr = nil
|
310
|
+
res = nil
|
311
|
+
end
|
312
|
+
if res and !(SystemExit === res)
|
313
|
+
if bt = res.backtrace
|
314
|
+
bt.each do |l|
|
315
|
+
l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"}
|
316
|
+
end
|
317
|
+
bt.concat(caller)
|
318
|
+
else
|
319
|
+
res.set_backtrace(caller)
|
320
|
+
end
|
321
|
+
raise res
|
322
|
+
end
|
323
|
+
|
324
|
+
# really is it succeed?
|
325
|
+
unless ignore_stderr
|
326
|
+
# the body of assert_separately must not output anything to detect error
|
327
|
+
assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr])
|
328
|
+
end
|
329
|
+
assert(status.success?, FailDesc[status, "assert_separately failed", stderr])
|
330
|
+
raise marshal_error if marshal_error
|
331
|
+
end
|
332
|
+
|
333
|
+
# Run Ractor-related test without influencing the main test suite
|
334
|
+
def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt)
|
335
|
+
return unless defined?(Ractor)
|
336
|
+
|
337
|
+
require = "require #{require.inspect}" if require
|
338
|
+
if require_relative
|
339
|
+
dir = File.dirname(caller_locations[0,1][0].absolute_path)
|
340
|
+
full_path = File.expand_path(require_relative, dir)
|
341
|
+
require = "#{require}; require #{full_path.inspect}"
|
342
|
+
end
|
343
|
+
|
344
|
+
assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt)
|
345
|
+
#{require}
|
346
|
+
previous_verbose = $VERBOSE
|
347
|
+
$VERBOSE = nil
|
348
|
+
Ractor.new {} # trigger initial warning
|
349
|
+
$VERBOSE = previous_verbose
|
350
|
+
#{src}
|
351
|
+
RUBY
|
352
|
+
end
|
353
|
+
|
354
|
+
# :call-seq:
|
355
|
+
# assert_throw( tag, failure_message = nil, &block )
|
356
|
+
#
|
357
|
+
#Fails unless the given block throws +tag+, returns the caught
|
358
|
+
#value otherwise.
|
359
|
+
#
|
360
|
+
#An optional failure message may be provided as the final argument.
|
361
|
+
#
|
362
|
+
# tag = Object.new
|
363
|
+
# assert_throw(tag, "#{tag} was not thrown!") do
|
364
|
+
# throw tag
|
365
|
+
# end
|
366
|
+
def assert_throw(tag, msg = nil)
|
367
|
+
ret = catch(tag) do
|
368
|
+
begin
|
369
|
+
yield(tag)
|
370
|
+
rescue UncaughtThrowError => e
|
371
|
+
thrown = e.tag
|
372
|
+
end
|
373
|
+
msg = message(msg) {
|
374
|
+
"Expected #{mu_pp(tag)} to have been thrown"\
|
375
|
+
"#{%Q[, not #{thrown}] if thrown}"
|
376
|
+
}
|
377
|
+
assert(false, msg)
|
378
|
+
end
|
379
|
+
assert(true)
|
380
|
+
ret
|
381
|
+
end
|
382
|
+
|
383
|
+
# :call-seq:
|
384
|
+
# assert_raise( *args, &block )
|
385
|
+
#
|
386
|
+
#Tests if the given block raises an exception. Acceptable exception
|
387
|
+
#types may be given as optional arguments. If the last argument is a
|
388
|
+
#String, it will be used as the error message.
|
389
|
+
#
|
390
|
+
# assert_raise do #Fails, no Exceptions are raised
|
391
|
+
# end
|
392
|
+
#
|
393
|
+
# assert_raise NameError do
|
394
|
+
# puts x #Raises NameError, so assertion succeeds
|
395
|
+
# end
|
396
|
+
def assert_raise(*exp, &b)
|
397
|
+
case exp.last
|
398
|
+
when String, Proc
|
399
|
+
msg = exp.pop
|
400
|
+
end
|
401
|
+
|
402
|
+
begin
|
403
|
+
yield
|
404
|
+
rescue MiniTest::Skip => e
|
405
|
+
return e if exp.include? MiniTest::Skip
|
406
|
+
raise e
|
407
|
+
rescue Exception => e
|
408
|
+
expected = exp.any? { |ex|
|
409
|
+
if ex.instance_of? Module then
|
410
|
+
e.kind_of? ex
|
411
|
+
else
|
412
|
+
e.instance_of? ex
|
413
|
+
end
|
414
|
+
}
|
415
|
+
|
416
|
+
assert expected, proc {
|
417
|
+
flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"})
|
418
|
+
}
|
419
|
+
|
420
|
+
return e
|
421
|
+
ensure
|
422
|
+
unless e
|
423
|
+
exp = exp.first if exp.size == 1
|
424
|
+
|
425
|
+
flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"})
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
# :call-seq:
|
431
|
+
# assert_raise_with_message(exception, expected, msg = nil, &block)
|
432
|
+
#
|
433
|
+
#Tests if the given block raises an exception with the expected
|
434
|
+
#message.
|
435
|
+
#
|
436
|
+
# assert_raise_with_message(RuntimeError, "foo") do
|
437
|
+
# nil #Fails, no Exceptions are raised
|
438
|
+
# end
|
439
|
+
#
|
440
|
+
# assert_raise_with_message(RuntimeError, "foo") do
|
441
|
+
# raise ArgumentError, "foo" #Fails, different Exception is raised
|
442
|
+
# end
|
443
|
+
#
|
444
|
+
# assert_raise_with_message(RuntimeError, "foo") do
|
445
|
+
# raise "bar" #Fails, RuntimeError is raised but the message differs
|
446
|
+
# end
|
447
|
+
#
|
448
|
+
# assert_raise_with_message(RuntimeError, "foo") do
|
449
|
+
# raise "foo" #Raises RuntimeError with the message, so assertion succeeds
|
450
|
+
# end
|
451
|
+
def assert_raise_with_message(exception, expected, msg = nil, &block)
|
452
|
+
case expected
|
453
|
+
when String
|
454
|
+
assert = :assert_equal
|
455
|
+
when Regexp
|
456
|
+
assert = :assert_match
|
457
|
+
else
|
458
|
+
raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}"
|
459
|
+
end
|
460
|
+
|
461
|
+
ex = m = nil
|
462
|
+
EnvUtil.with_default_internal(expected.encoding) do
|
463
|
+
ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do
|
464
|
+
yield
|
465
|
+
end
|
466
|
+
m = ex.message
|
467
|
+
end
|
468
|
+
msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"}
|
469
|
+
|
470
|
+
if assert == :assert_equal
|
471
|
+
assert_equal(expected, m, msg)
|
472
|
+
else
|
473
|
+
msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" }
|
474
|
+
assert expected =~ m, msg
|
475
|
+
block.binding.eval("proc{|_|$~=_}").call($~)
|
476
|
+
end
|
477
|
+
ex
|
478
|
+
end
|
479
|
+
|
480
|
+
MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc:
|
481
|
+
|
482
|
+
# :call-seq:
|
483
|
+
# assert(test, [failure_message])
|
484
|
+
#
|
485
|
+
#Tests if +test+ is true.
|
486
|
+
#
|
487
|
+
#+msg+ may be a String or a Proc. If +msg+ is a String, it will be used
|
488
|
+
#as the failure message. Otherwise, the result of calling +msg+ will be
|
489
|
+
#used as the message if the assertion fails.
|
490
|
+
#
|
491
|
+
#If no +msg+ is given, a default message will be used.
|
492
|
+
#
|
493
|
+
# assert(false, "This was expected to be true")
|
494
|
+
def assert(test, *msgs)
|
495
|
+
case msg = msgs.first
|
496
|
+
when String, Proc
|
497
|
+
when nil
|
498
|
+
msgs.shift
|
499
|
+
else
|
500
|
+
bt = caller.reject { |s| s.start_with?(MINI_DIR) }
|
501
|
+
raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt
|
502
|
+
end unless msgs.empty?
|
503
|
+
super
|
504
|
+
end
|
505
|
+
|
506
|
+
# :call-seq:
|
507
|
+
# assert_respond_to( object, method, failure_message = nil )
|
508
|
+
#
|
509
|
+
#Tests if the given Object responds to +method+.
|
510
|
+
#
|
511
|
+
#An optional failure message may be provided as the final argument.
|
512
|
+
#
|
513
|
+
# assert_respond_to("hello", :reverse) #Succeeds
|
514
|
+
# assert_respond_to("hello", :does_not_exist) #Fails
|
515
|
+
def assert_respond_to(obj, (meth, *priv), msg = nil)
|
516
|
+
unless priv.empty?
|
517
|
+
msg = message(msg) {
|
518
|
+
"Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}"
|
519
|
+
}
|
520
|
+
return assert obj.respond_to?(meth, *priv), msg
|
521
|
+
end
|
522
|
+
#get rid of overcounting
|
523
|
+
if caller_locations(1, 1)[0].path.start_with?(MINI_DIR)
|
524
|
+
return if obj.respond_to?(meth)
|
525
|
+
end
|
526
|
+
super(obj, meth, msg)
|
527
|
+
end
|
528
|
+
|
529
|
+
# :call-seq:
|
530
|
+
# assert_not_respond_to( object, method, failure_message = nil )
|
531
|
+
#
|
532
|
+
#Tests if the given Object does not respond to +method+.
|
533
|
+
#
|
534
|
+
#An optional failure message may be provided as the final argument.
|
535
|
+
#
|
536
|
+
# assert_not_respond_to("hello", :reverse) #Fails
|
537
|
+
# assert_not_respond_to("hello", :does_not_exist) #Succeeds
|
538
|
+
def assert_not_respond_to(obj, (meth, *priv), msg = nil)
|
539
|
+
unless priv.empty?
|
540
|
+
msg = message(msg) {
|
541
|
+
"Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}"
|
542
|
+
}
|
543
|
+
return assert !obj.respond_to?(meth, *priv), msg
|
544
|
+
end
|
545
|
+
#get rid of overcounting
|
546
|
+
if caller_locations(1, 1)[0].path.start_with?(MINI_DIR)
|
547
|
+
return unless obj.respond_to?(meth)
|
548
|
+
end
|
549
|
+
refute_respond_to(obj, meth, msg)
|
550
|
+
end
|
551
|
+
|
552
|
+
# pattern_list is an array which contains regexp and :*.
|
553
|
+
# :* means any sequence.
|
554
|
+
#
|
555
|
+
# pattern_list is anchored.
|
556
|
+
# Use [:*, regexp, :*] for non-anchored match.
|
557
|
+
def assert_pattern_list(pattern_list, actual, message=nil)
|
558
|
+
rest = actual
|
559
|
+
anchored = true
|
560
|
+
pattern_list.each_with_index {|pattern, i|
|
561
|
+
if pattern == :*
|
562
|
+
anchored = false
|
563
|
+
else
|
564
|
+
if anchored
|
565
|
+
match = /\A#{pattern}/.match(rest)
|
566
|
+
else
|
567
|
+
match = pattern.match(rest)
|
568
|
+
end
|
569
|
+
unless match
|
570
|
+
msg = message(msg) {
|
571
|
+
expect_msg = "Expected #{mu_pp pattern}\n"
|
572
|
+
if /\n[^\n]/ =~ rest
|
573
|
+
actual_mesg = +"to match\n"
|
574
|
+
rest.scan(/.*\n+/) {
|
575
|
+
actual_mesg << ' ' << $&.inspect << "+\n"
|
576
|
+
}
|
577
|
+
actual_mesg.sub!(/\+\n\z/, '')
|
578
|
+
else
|
579
|
+
actual_mesg = "to match " + mu_pp(rest)
|
580
|
+
end
|
581
|
+
actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters"
|
582
|
+
expect_msg + actual_mesg
|
583
|
+
}
|
584
|
+
assert false, msg
|
585
|
+
end
|
586
|
+
rest = match.post_match
|
587
|
+
anchored = true
|
588
|
+
end
|
589
|
+
}
|
590
|
+
if anchored
|
591
|
+
assert_equal("", rest)
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
def assert_warning(pat, msg = nil)
|
596
|
+
result = nil
|
597
|
+
stderr = EnvUtil.with_default_internal(pat.encoding) {
|
598
|
+
EnvUtil.verbose_warning {
|
599
|
+
result = yield
|
600
|
+
}
|
601
|
+
}
|
602
|
+
msg = message(msg) {diff pat, stderr}
|
603
|
+
assert(pat === stderr, msg)
|
604
|
+
result
|
605
|
+
end
|
606
|
+
|
607
|
+
def assert_warn(*args)
|
608
|
+
assert_warning(*args) {$VERBOSE = false; yield}
|
609
|
+
end
|
610
|
+
|
611
|
+
def assert_deprecated_warning(mesg = /deprecated/)
|
612
|
+
assert_warning(mesg) do
|
613
|
+
Warning[:deprecated] = true
|
614
|
+
yield
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
def assert_deprecated_warn(mesg = /deprecated/)
|
619
|
+
assert_warn(mesg) do
|
620
|
+
Warning[:deprecated] = true
|
621
|
+
yield
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
class << (AssertFile = Struct.new(:failure_message).new)
|
626
|
+
include CoreAssertions
|
627
|
+
def assert_file_predicate(predicate, *args)
|
628
|
+
if /\Anot_/ =~ predicate
|
629
|
+
predicate = $'
|
630
|
+
neg = " not"
|
631
|
+
end
|
632
|
+
result = File.__send__(predicate, *args)
|
633
|
+
result = !result if neg
|
634
|
+
mesg = "Expected file ".dup << args.shift.inspect
|
635
|
+
mesg << "#{neg} to be #{predicate}"
|
636
|
+
mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty?
|
637
|
+
mesg << " #{failure_message}" if failure_message
|
638
|
+
assert(result, mesg)
|
639
|
+
end
|
640
|
+
alias method_missing assert_file_predicate
|
641
|
+
|
642
|
+
def for(message)
|
643
|
+
clone.tap {|a| a.failure_message = message}
|
644
|
+
end
|
645
|
+
end
|
646
|
+
|
647
|
+
class AllFailures
|
648
|
+
attr_reader :failures
|
649
|
+
|
650
|
+
def initialize
|
651
|
+
@count = 0
|
652
|
+
@failures = {}
|
653
|
+
end
|
654
|
+
|
655
|
+
def for(key)
|
656
|
+
@count += 1
|
657
|
+
yield
|
658
|
+
rescue Exception => e
|
659
|
+
@failures[key] = [@count, e]
|
660
|
+
end
|
661
|
+
|
662
|
+
def foreach(*keys)
|
663
|
+
keys.each do |key|
|
664
|
+
@count += 1
|
665
|
+
begin
|
666
|
+
yield key
|
667
|
+
rescue Exception => e
|
668
|
+
@failures[key] = [@count, e]
|
669
|
+
end
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
def message
|
674
|
+
i = 0
|
675
|
+
total = @count.to_s
|
676
|
+
fmt = "%#{total.size}d"
|
677
|
+
@failures.map {|k, (n, v)|
|
678
|
+
v = v.message
|
679
|
+
"\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}"
|
680
|
+
}.join("\n")
|
681
|
+
end
|
682
|
+
|
683
|
+
def pass?
|
684
|
+
@failures.empty?
|
685
|
+
end
|
686
|
+
end
|
687
|
+
|
688
|
+
# threads should respond to shift method.
|
689
|
+
# Array can be used.
|
690
|
+
def assert_join_threads(threads, message = nil)
|
691
|
+
errs = []
|
692
|
+
values = []
|
693
|
+
while th = threads.shift
|
694
|
+
begin
|
695
|
+
values << th.value
|
696
|
+
rescue Exception
|
697
|
+
errs << [th, $!]
|
698
|
+
th = nil
|
699
|
+
end
|
700
|
+
end
|
701
|
+
values
|
702
|
+
ensure
|
703
|
+
if th&.alive?
|
704
|
+
th.raise(Timeout::Error.new)
|
705
|
+
th.join rescue errs << [th, $!]
|
706
|
+
end
|
707
|
+
if !errs.empty?
|
708
|
+
msg = "exceptions on #{errs.length} threads:\n" +
|
709
|
+
errs.map {|t, err|
|
710
|
+
"#{t.inspect}:\n" +
|
711
|
+
RUBY_VERSION >= "2.5.0" ? err.full_message(highlight: false, order: :top) : err.message
|
712
|
+
}.join("\n---\n")
|
713
|
+
if message
|
714
|
+
msg = "#{message}\n#{msg}"
|
715
|
+
end
|
716
|
+
raise MiniTest::Assertion, msg
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
def assert_all_assertions(msg = nil)
|
721
|
+
all = AllFailures.new
|
722
|
+
yield all
|
723
|
+
ensure
|
724
|
+
assert(all.pass?, message(msg) {all.message.chomp(".")})
|
725
|
+
end
|
726
|
+
alias all_assertions assert_all_assertions
|
727
|
+
|
728
|
+
def message(msg = nil, *args, &default) # :nodoc:
|
729
|
+
if Proc === msg
|
730
|
+
super(nil, *args) do
|
731
|
+
ary = [msg.call, (default.call if default)].compact.reject(&:empty?)
|
732
|
+
if 1 < ary.length
|
733
|
+
ary[0...-1] = ary[0...-1].map {|str| str.sub(/(?<!\.)\z/, '.') }
|
734
|
+
end
|
735
|
+
begin
|
736
|
+
ary.join("\n")
|
737
|
+
rescue Encoding::CompatibilityError
|
738
|
+
ary.map(&:b).join("\n")
|
739
|
+
end
|
740
|
+
end
|
741
|
+
else
|
742
|
+
super
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
746
|
+
def diff(exp, act)
|
747
|
+
require 'pp'
|
748
|
+
q = PP.new(+"")
|
749
|
+
q.guard_inspect_key do
|
750
|
+
q.group(2, "expected: ") do
|
751
|
+
q.pp exp
|
752
|
+
end
|
753
|
+
q.text q.newline
|
754
|
+
q.group(2, "actual: ") do
|
755
|
+
q.pp act
|
756
|
+
end
|
757
|
+
q.flush
|
758
|
+
end
|
759
|
+
q.output
|
760
|
+
end
|
761
|
+
end
|
762
|
+
end
|
763
|
+
end
|