test-unit-ruby-core 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8ce1e0a5d55058ecfbee2cd7181b21cac4b1edd85e94e5ccae89d2ac46ef870f
4
+ data.tar.gz: 1c87b3bd31f58b06bf04b0cf8ad51b82c1d18c8ac373708c8eda766baf2877c1
5
+ SHA512:
6
+ metadata.gz: 3e88a5ae66883f86406139338bcb9d58b8e85fcca7712b71e929c04aef36c5e94a68f8f5209f0f653d19a2142bc81e2afbb873cc74badad129ad7d51570845f1
7
+ data.tar.gz: 6232de618f5c4b20c3574aa6d0708f2e9a9b44d7d36367808a5d15b5e7b8aff7ff585f9c04d79bedb9ffa4c2c714bae34cf5e6ca60b222fdab8c8a60003fd45a
@@ -0,0 +1,817 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Test
4
+ module Unit
5
+ module Assertions
6
+ def assert_raises(*exp, &b)
7
+ raise NoMethodError, "use assert_raise", caller
8
+ end
9
+
10
+ def _assertions= n # :nodoc:
11
+ @_assertions = n
12
+ end
13
+
14
+ def _assertions # :nodoc:
15
+ @_assertions ||= 0
16
+ end
17
+
18
+ ##
19
+ # Returns a proc that will output +msg+ along with the default message.
20
+
21
+ def message msg = nil, ending = nil, &default
22
+ proc {
23
+ ending ||= (ending_pattern = /(?<!\.)\z/; ".")
24
+ ending_pattern ||= /(?<!#{Regexp.quote(ending)})\z/
25
+ msg = msg.call if Proc === msg
26
+ ary = [msg, (default.call if default)].compact.reject(&:empty?)
27
+ ary.map! {|str| str.to_s.sub(ending_pattern, ending) }
28
+ begin
29
+ ary.join("\n")
30
+ rescue Encoding::CompatibilityError
31
+ ary.map(&:b).join("\n")
32
+ end
33
+ }
34
+ end
35
+ end
36
+
37
+ module CoreAssertions
38
+ require_relative 'envutil'
39
+ require 'pp'
40
+ nil.pretty_inspect
41
+
42
+ def mu_pp(obj) #:nodoc:
43
+ obj.pretty_inspect.chomp
44
+ end
45
+
46
+ def assert_file
47
+ AssertFile
48
+ end
49
+
50
+ FailDesc = proc do |status, message = "", out = ""|
51
+ now = Time.now
52
+ proc do
53
+ EnvUtil.failure_description(status, now, message, out)
54
+ end
55
+ end
56
+
57
+ def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil,
58
+ success: nil, **opt)
59
+ args = Array(args).dup
60
+ args.insert((Hash === args[0] ? 1 : 0), '--disable=gems')
61
+ stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt)
62
+ desc = FailDesc[status, message, stderr]
63
+ if block_given?
64
+ raise "test_stdout ignored, use block only or without block" if test_stdout != []
65
+ raise "test_stderr ignored, use block only or without block" if test_stderr != []
66
+ yield(stdout.lines.map {|l| l.chomp }, stderr.lines.map {|l| l.chomp }, status)
67
+ else
68
+ all_assertions(desc) do |a|
69
+ [["stdout", test_stdout, stdout], ["stderr", test_stderr, stderr]].each do |key, exp, act|
70
+ a.for(key) do
71
+ if exp.is_a?(Regexp)
72
+ assert_match(exp, act)
73
+ elsif exp.all? {|e| String === e}
74
+ assert_equal(exp, act.lines.map {|l| l.chomp })
75
+ else
76
+ assert_pattern_list(exp, act)
77
+ end
78
+ end
79
+ end
80
+ unless success.nil?
81
+ a.for("success?") do
82
+ if success
83
+ assert_predicate(status, :success?)
84
+ else
85
+ assert_not_predicate(status, :success?)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ status
91
+ end
92
+ end
93
+
94
+ if defined?(RubyVM::InstructionSequence)
95
+ def syntax_check(code, fname, line)
96
+ code = code.dup.force_encoding(Encoding::UTF_8)
97
+ RubyVM::InstructionSequence.compile(code, fname, fname, line)
98
+ :ok
99
+ ensure
100
+ raise if SyntaxError === $!
101
+ end
102
+ else
103
+ def syntax_check(code, fname, line)
104
+ code = code.b
105
+ code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) {
106
+ "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n"
107
+ }
108
+ code = code.force_encoding(Encoding::UTF_8)
109
+ catch {|tag| eval(code, binding, fname, line - 1)}
110
+ end
111
+ end
112
+
113
+ def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt)
114
+ # TODO: consider choosing some appropriate limit for RJIT and stop skipping this once it does not randomly fail
115
+ pend 'assert_no_memory_leak may consider RJIT memory usage as leak' if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
116
+ # For previous versions which implemented MJIT
117
+ pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled?
118
+
119
+ require_relative 'memory_status'
120
+ raise Test::Unit::PendedError, "unsupported platform" unless defined?(Memory::Status)
121
+
122
+ token_dump, token_re = new_test_token
123
+ envs = args.shift if Array === args and Hash === args.first
124
+ args = [
125
+ "--disable=gems",
126
+ "-r", File.expand_path("../memory_status", __FILE__),
127
+ *args,
128
+ "-v", "-",
129
+ ]
130
+ if defined? Memory::NO_MEMORY_LEAK_ENVS then
131
+ envs ||= {}
132
+ newenvs = envs.merge(Memory::NO_MEMORY_LEAK_ENVS) { |_, _, _| break }
133
+ envs = newenvs if newenvs
134
+ end
135
+ args.unshift(envs) if envs
136
+ cmd = [
137
+ 'END {STDERR.puts '"#{token_dump}"'"FINAL=#{Memory::Status.new}"}',
138
+ prepare,
139
+ 'STDERR.puts('"#{token_dump}"'"START=#{$initial_status = Memory::Status.new}")',
140
+ '$initial_size = $initial_status.size',
141
+ code,
142
+ 'GC.start',
143
+ ].join("\n")
144
+ _, err, status = EnvUtil.invoke_ruby(args, cmd, true, true, **opt)
145
+ before = err.sub!(/^#{token_re}START=(\{.*\})\n/, '') && Memory::Status.parse($1)
146
+ after = err.sub!(/^#{token_re}FINAL=(\{.*\})\n/, '') && Memory::Status.parse($1)
147
+ assert(status.success?, FailDesc[status, message, err])
148
+ ([:size, (rss && :rss)] & after.members).each do |n|
149
+ b = before[n]
150
+ a = after[n]
151
+ next unless a > 0 and b > 0
152
+ assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"})
153
+ end
154
+ rescue LoadError
155
+ pend
156
+ end
157
+
158
+ # :call-seq:
159
+ # assert_nothing_raised( *args, &block )
160
+ #
161
+ #If any exceptions are given as arguments, the assertion will
162
+ #fail if one of those exceptions are raised. Otherwise, the test fails
163
+ #if any exceptions are raised.
164
+ #
165
+ #The final argument may be a failure message.
166
+ #
167
+ # assert_nothing_raised RuntimeError do
168
+ # raise Exception #Assertion passes, Exception is not a RuntimeError
169
+ # end
170
+ #
171
+ # assert_nothing_raised do
172
+ # raise Exception #Assertion fails
173
+ # end
174
+ def assert_nothing_raised(*args)
175
+ self._assertions += 1
176
+ if Module === args.last
177
+ msg = nil
178
+ else
179
+ msg = args.pop
180
+ end
181
+ begin
182
+ yield
183
+ rescue Test::Unit::PendedError, *(Test::Unit::AssertionFailedError if args.empty?)
184
+ raise
185
+ rescue *(args.empty? ? Exception : args) => e
186
+ msg = message(msg) {
187
+ "Exception raised:\n<#{mu_pp(e)}>\n""Backtrace:\n" <<
188
+ Test.filter_backtrace(e.backtrace).map{|frame| " #{frame}"}.join("\n")
189
+ }
190
+ raise Test::Unit::AssertionFailedError, msg.call, e.backtrace
191
+ end
192
+ end
193
+
194
+ def prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil)
195
+ fname ||= caller_locations(2, 1)[0]
196
+ mesg ||= fname.to_s
197
+ verbose, $VERBOSE = $VERBOSE, verbose
198
+ case
199
+ when Array === fname
200
+ fname, line = *fname
201
+ when defined?(fname.path) && defined?(fname.lineno)
202
+ fname, line = fname.path, fname.lineno
203
+ else
204
+ line = 1
205
+ end
206
+ yield(code, fname, line, message(mesg) {
207
+ if code.end_with?("\n")
208
+ "```\n#{code}```\n"
209
+ else
210
+ "```\n#{code}\n```\n""no-newline"
211
+ end
212
+ })
213
+ ensure
214
+ $VERBOSE = verbose
215
+ end
216
+
217
+ def assert_valid_syntax(code, *args, **opt)
218
+ prepare_syntax_check(code, *args, **opt) do |src, fname, line, mesg|
219
+ yield if defined?(yield)
220
+ assert_nothing_raised(SyntaxError, mesg) do
221
+ assert_equal(:ok, syntax_check(src, fname, line), mesg)
222
+ end
223
+ end
224
+ end
225
+
226
+ def assert_normal_exit(testsrc, message = '', child_env: nil, **opt)
227
+ assert_valid_syntax(testsrc, caller_locations(1, 1)[0])
228
+ if child_env
229
+ child_env = [child_env]
230
+ else
231
+ child_env = []
232
+ end
233
+ out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt)
234
+ assert !status.signaled?, FailDesc[status, message, out]
235
+ end
236
+
237
+ def assert_ruby_status(args, test_stdin="", message=nil, **opt)
238
+ out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt)
239
+ desc = FailDesc[status, message, out]
240
+ assert(!status.signaled?, desc)
241
+ message ||= "ruby exit status is not success:"
242
+ assert(status.success?, desc)
243
+ end
244
+
245
+ ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM")
246
+
247
+ def separated_runner(token, out = nil)
248
+ include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) })
249
+ out = out ? IO.new(out, 'w') : STDOUT
250
+ at_exit {
251
+ out.puts "#{token}<error>", [Marshal.dump($!)].pack('m'), "#{token}</error>", "#{token}assertions=#{self._assertions}"
252
+ }
253
+ if defined?(Test::Unit::Runner)
254
+ Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true)
255
+ elsif defined?(Test::Unit::AutoRunner)
256
+ Test::Unit::AutoRunner.need_auto_run = false
257
+ end
258
+ end
259
+
260
+ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt)
261
+ unless file and line
262
+ loc, = caller_locations(1,1)
263
+ file ||= loc.path
264
+ line ||= loc.lineno
265
+ end
266
+ capture_stdout = true
267
+ unless /mswin|mingw/ =~ RbConfig::CONFIG['host_os']
268
+ capture_stdout = false
269
+ opt[:out] = Test::Unit::Runner.output if defined?(Test::Unit::Runner)
270
+ res_p, res_c = IO.pipe
271
+ opt[:ios] = [res_c]
272
+ end
273
+ token_dump, token_re = new_test_token
274
+ src = <<eom
275
+ # -*- coding: #{line += __LINE__; src.encoding}; -*-
276
+ BEGIN {
277
+ require "test/unit";include Test::Unit::Assertions;require #{__FILE__.dump};include Test::Unit::CoreAssertions
278
+ separated_runner #{token_dump}, #{res_c&.fileno || 'nil'}
279
+ }
280
+ #{line -= __LINE__; src}
281
+ eom
282
+ args = args.dup
283
+ args.insert((Hash === args.first ? 1 : 0), "-w", "--disable=gems", *$:.map {|l| "-I#{l}"})
284
+ args << "--debug" if RUBY_ENGINE == 'jruby' # warning: tracing (e.g. set_trace_func) will not capture all events without --debug flag
285
+ stdout, stderr, status = EnvUtil.invoke_ruby(args, src, capture_stdout, true, **opt)
286
+ ensure
287
+ if res_c
288
+ res_c.close
289
+ res = res_p.read
290
+ res_p.close
291
+ else
292
+ res = stdout
293
+ end
294
+ raise if $!
295
+ abort = status.coredump? || (status.signaled? && ABORT_SIGNALS.include?(status.termsig))
296
+ assert(!abort, FailDesc[status, nil, stderr])
297
+ self._assertions += res[/^#{token_re}assertions=(\d+)/, 1].to_i
298
+ begin
299
+ res = Marshal.load(res[/^#{token_re}<error>\n\K.*\n(?=#{token_re}<\/error>$)/m].unpack1("m"))
300
+ rescue => marshal_error
301
+ ignore_stderr = nil
302
+ res = nil
303
+ end
304
+ if res and !(SystemExit === res)
305
+ if bt = res.backtrace
306
+ bt.each do |l|
307
+ l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"}
308
+ end
309
+ bt.concat(caller)
310
+ else
311
+ res.set_backtrace(caller)
312
+ end
313
+ raise res
314
+ end
315
+
316
+ # really is it succeed?
317
+ unless ignore_stderr
318
+ # the body of assert_separately must not output anything to detect error
319
+ assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr])
320
+ end
321
+ assert(status.success?, FailDesc[status, "assert_separately failed", stderr])
322
+ raise marshal_error if marshal_error
323
+ end
324
+
325
+ # Run Ractor-related test without influencing the main test suite
326
+ def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt)
327
+ return unless defined?(Ractor)
328
+
329
+ require = "require #{require.inspect}" if require
330
+ if require_relative
331
+ dir = File.dirname(caller_locations[0,1][0].absolute_path)
332
+ full_path = File.expand_path(require_relative, dir)
333
+ require = "#{require}; require #{full_path.inspect}"
334
+ end
335
+
336
+ assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt)
337
+ #{require}
338
+ previous_verbose = $VERBOSE
339
+ $VERBOSE = nil
340
+ Ractor.new {} # trigger initial warning
341
+ $VERBOSE = previous_verbose
342
+ #{src}
343
+ RUBY
344
+ end
345
+
346
+ # :call-seq:
347
+ # assert_throw( tag, failure_message = nil, &block )
348
+ #
349
+ #Fails unless the given block throws +tag+, returns the caught
350
+ #value otherwise.
351
+ #
352
+ #An optional failure message may be provided as the final argument.
353
+ #
354
+ # tag = Object.new
355
+ # assert_throw(tag, "#{tag} was not thrown!") do
356
+ # throw tag
357
+ # end
358
+ def assert_throw(tag, msg = nil)
359
+ ret = catch(tag) do
360
+ begin
361
+ yield(tag)
362
+ rescue UncaughtThrowError => e
363
+ thrown = e.tag
364
+ end
365
+ msg = message(msg) {
366
+ "Expected #{mu_pp(tag)} to have been thrown"\
367
+ "#{%Q[, not #{thrown}] if thrown}"
368
+ }
369
+ assert(false, msg)
370
+ end
371
+ assert(true)
372
+ ret
373
+ end
374
+
375
+ # :call-seq:
376
+ # assert_raise( *args, &block )
377
+ #
378
+ #Tests if the given block raises an exception. Acceptable exception
379
+ #types may be given as optional arguments. If the last argument is a
380
+ #String, it will be used as the error message.
381
+ #
382
+ # assert_raise do #Fails, no Exceptions are raised
383
+ # end
384
+ #
385
+ # assert_raise NameError do
386
+ # puts x #Raises NameError, so assertion succeeds
387
+ # end
388
+ def assert_raise(*exp, &b)
389
+ case exp.last
390
+ when String, Proc
391
+ msg = exp.pop
392
+ end
393
+
394
+ begin
395
+ yield
396
+ rescue Test::Unit::PendedError => e
397
+ return e if exp.include? Test::Unit::PendedError
398
+ raise e
399
+ rescue Exception => e
400
+ expected = exp.any? { |ex|
401
+ if ex.instance_of? Module then
402
+ e.kind_of? ex
403
+ else
404
+ e.instance_of? ex
405
+ end
406
+ }
407
+
408
+ assert expected, proc {
409
+ flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"})
410
+ }
411
+
412
+ return e
413
+ ensure
414
+ unless e
415
+ exp = exp.first if exp.size == 1
416
+
417
+ flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"})
418
+ end
419
+ end
420
+ end
421
+
422
+ # :call-seq:
423
+ # assert_raise_with_message(exception, expected, msg = nil, &block)
424
+ #
425
+ #Tests if the given block raises an exception with the expected
426
+ #message.
427
+ #
428
+ # assert_raise_with_message(RuntimeError, "foo") do
429
+ # nil #Fails, no Exceptions are raised
430
+ # end
431
+ #
432
+ # assert_raise_with_message(RuntimeError, "foo") do
433
+ # raise ArgumentError, "foo" #Fails, different Exception is raised
434
+ # end
435
+ #
436
+ # assert_raise_with_message(RuntimeError, "foo") do
437
+ # raise "bar" #Fails, RuntimeError is raised but the message differs
438
+ # end
439
+ #
440
+ # assert_raise_with_message(RuntimeError, "foo") do
441
+ # raise "foo" #Raises RuntimeError with the message, so assertion succeeds
442
+ # end
443
+ def assert_raise_with_message(exception, expected, msg = nil, &block)
444
+ case expected
445
+ when String
446
+ assert = :assert_equal
447
+ when Regexp
448
+ assert = :assert_match
449
+ else
450
+ raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}"
451
+ end
452
+
453
+ ex = m = nil
454
+ EnvUtil.with_default_internal(expected.encoding) do
455
+ ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do
456
+ yield
457
+ end
458
+ m = ex.message
459
+ end
460
+ msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"}
461
+
462
+ if assert == :assert_equal
463
+ assert_equal(expected, m, msg)
464
+ else
465
+ msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" }
466
+ assert expected =~ m, msg
467
+ block.binding.eval("proc{|_|$~=_}").call($~)
468
+ end
469
+ ex
470
+ end
471
+
472
+ TEST_DIR = File.join(__dir__, "test/unit") #:nodoc:
473
+
474
+ # :call-seq:
475
+ # assert(test, [failure_message])
476
+ #
477
+ #Tests if +test+ is true.
478
+ #
479
+ #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used
480
+ #as the failure message. Otherwise, the result of calling +msg+ will be
481
+ #used as the message if the assertion fails.
482
+ #
483
+ #If no +msg+ is given, a default message will be used.
484
+ #
485
+ # assert(false, "This was expected to be true")
486
+ def assert(test, *msgs)
487
+ case msg = msgs.first
488
+ when String, Proc
489
+ when nil
490
+ msgs.shift
491
+ else
492
+ bt = caller.reject { |s| s.start_with?(TEST_DIR) }
493
+ raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt
494
+ end unless msgs.empty?
495
+ super
496
+ end
497
+
498
+ # :call-seq:
499
+ # assert_respond_to( object, method, failure_message = nil )
500
+ #
501
+ #Tests if the given Object responds to +method+.
502
+ #
503
+ #An optional failure message may be provided as the final argument.
504
+ #
505
+ # assert_respond_to("hello", :reverse) #Succeeds
506
+ # assert_respond_to("hello", :does_not_exist) #Fails
507
+ def assert_respond_to(obj, (meth, *priv), msg = nil)
508
+ unless priv.empty?
509
+ msg = message(msg) {
510
+ "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}"
511
+ }
512
+ return assert obj.respond_to?(meth, *priv), msg
513
+ end
514
+ #get rid of overcounting
515
+ if caller_locations(1, 1)[0].path.start_with?(TEST_DIR)
516
+ return if obj.respond_to?(meth)
517
+ end
518
+ super(obj, meth, msg)
519
+ end
520
+
521
+ # :call-seq:
522
+ # assert_not_respond_to( object, method, failure_message = nil )
523
+ #
524
+ #Tests if the given Object does not respond to +method+.
525
+ #
526
+ #An optional failure message may be provided as the final argument.
527
+ #
528
+ # assert_not_respond_to("hello", :reverse) #Fails
529
+ # assert_not_respond_to("hello", :does_not_exist) #Succeeds
530
+ def assert_not_respond_to(obj, (meth, *priv), msg = nil)
531
+ unless priv.empty?
532
+ msg = message(msg) {
533
+ "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}"
534
+ }
535
+ return assert !obj.respond_to?(meth, *priv), msg
536
+ end
537
+ #get rid of overcounting
538
+ if caller_locations(1, 1)[0].path.start_with?(TEST_DIR)
539
+ return unless obj.respond_to?(meth)
540
+ end
541
+ refute_respond_to(obj, meth, msg)
542
+ end
543
+
544
+ # pattern_list is an array which contains regexp, string and :*.
545
+ # :* means any sequence.
546
+ #
547
+ # pattern_list is anchored.
548
+ # Use [:*, regexp/string, :*] for non-anchored match.
549
+ def assert_pattern_list(pattern_list, actual, message=nil)
550
+ rest = actual
551
+ anchored = true
552
+ pattern_list.each_with_index {|pattern, i|
553
+ if pattern == :*
554
+ anchored = false
555
+ else
556
+ if anchored
557
+ match = rest.rindex(pattern, 0)
558
+ else
559
+ match = rest.index(pattern)
560
+ end
561
+ if match
562
+ post_match = $~ ? $~.post_match : rest[match+pattern.size..-1]
563
+ else
564
+ msg = message(msg) {
565
+ expect_msg = "Expected #{mu_pp pattern}\n"
566
+ if /\n[^\n]/ =~ rest
567
+ actual_mesg = +"to match\n"
568
+ rest.scan(/.*\n+/) {
569
+ actual_mesg << ' ' << $&.inspect << "+\n"
570
+ }
571
+ actual_mesg.sub!(/\+\n\z/, '')
572
+ else
573
+ actual_mesg = "to match " + mu_pp(rest)
574
+ end
575
+ actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters"
576
+ expect_msg + actual_mesg
577
+ }
578
+ assert false, msg
579
+ end
580
+ rest = post_match
581
+ anchored = true
582
+ end
583
+ }
584
+ if anchored
585
+ assert_equal("", rest)
586
+ end
587
+ end
588
+
589
+ def assert_warning(pat, msg = nil)
590
+ result = nil
591
+ stderr = EnvUtil.with_default_internal(pat.encoding) {
592
+ EnvUtil.verbose_warning {
593
+ result = yield
594
+ }
595
+ }
596
+ msg = message(msg) {diff pat, stderr}
597
+ assert(pat === stderr, msg)
598
+ result
599
+ end
600
+
601
+ def assert_warn(*args)
602
+ assert_warning(*args) {$VERBOSE = false; yield}
603
+ end
604
+
605
+ def assert_deprecated_warning(mesg = /deprecated/)
606
+ assert_warning(mesg) do
607
+ Warning[:deprecated] = true if Warning.respond_to?(:[]=)
608
+ yield
609
+ end
610
+ end
611
+
612
+ def assert_deprecated_warn(mesg = /deprecated/)
613
+ assert_warn(mesg) do
614
+ Warning[:deprecated] = true if Warning.respond_to?(:[]=)
615
+ yield
616
+ end
617
+ end
618
+
619
+ class << (AssertFile = Struct.new(:failure_message).new)
620
+ include Assertions
621
+ include CoreAssertions
622
+ def assert_file_predicate(predicate, *args)
623
+ if /\Anot_/ =~ predicate
624
+ predicate = $'
625
+ neg = " not"
626
+ end
627
+ result = File.__send__(predicate, *args)
628
+ result = !result if neg
629
+ mesg = "Expected file ".dup << args.shift.inspect
630
+ mesg << "#{neg} to be #{predicate}"
631
+ mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty?
632
+ mesg << " #{failure_message}" if failure_message
633
+ assert(result, mesg)
634
+ end
635
+ alias method_missing assert_file_predicate
636
+
637
+ def for(message)
638
+ clone.tap {|a| a.failure_message = message}
639
+ end
640
+ end
641
+
642
+ class AllFailures
643
+ attr_reader :failures
644
+
645
+ def initialize
646
+ @count = 0
647
+ @failures = {}
648
+ end
649
+
650
+ def for(key)
651
+ @count += 1
652
+ yield key
653
+ rescue Exception => e
654
+ @failures[key] = [@count, e]
655
+ end
656
+
657
+ def foreach(*keys)
658
+ keys.each do |key|
659
+ @count += 1
660
+ begin
661
+ yield key
662
+ rescue Exception => e
663
+ @failures[key] = [@count, e]
664
+ end
665
+ end
666
+ end
667
+
668
+ def message
669
+ i = 0
670
+ total = @count.to_s
671
+ fmt = "%#{total.size}d"
672
+ @failures.map {|k, (n, v)|
673
+ v = v.message
674
+ "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}"
675
+ }.join("\n")
676
+ end
677
+
678
+ def pass?
679
+ @failures.empty?
680
+ end
681
+ end
682
+
683
+ # threads should respond to shift method.
684
+ # Array can be used.
685
+ def assert_join_threads(threads, message = nil)
686
+ errs = []
687
+ values = []
688
+ while th = threads.shift
689
+ begin
690
+ values << th.value
691
+ rescue Exception
692
+ errs << [th, $!]
693
+ th = nil
694
+ end
695
+ end
696
+ values
697
+ ensure
698
+ if th&.alive?
699
+ th.raise(Timeout::Error.new)
700
+ th.join rescue errs << [th, $!]
701
+ end
702
+ if !errs.empty?
703
+ msg = "exceptions on #{errs.length} threads:\n" +
704
+ errs.map {|t, err|
705
+ "#{t.inspect}:\n" +
706
+ (err.respond_to?(:full_message) ? err.full_message(highlight: false, order: :top) : err.message)
707
+ }.join("\n---\n")
708
+ if message
709
+ msg = "#{message}\n#{msg}"
710
+ end
711
+ raise Test::Unit::AssertionFailedError, msg
712
+ end
713
+ end
714
+
715
+ def assert_all?(obj, m = nil, &blk)
716
+ failed = []
717
+ obj.each do |*a, &b|
718
+ unless blk.call(*a, &b)
719
+ failed << (a.size > 1 ? a : a[0])
720
+ end
721
+ end
722
+ assert(failed.empty?, message(m) {failed.pretty_inspect})
723
+ end
724
+
725
+ def assert_all_assertions(msg = nil)
726
+ all = AllFailures.new
727
+ yield all
728
+ ensure
729
+ assert(all.pass?, message(msg) {all.message.chomp(".")})
730
+ end
731
+ alias all_assertions assert_all_assertions
732
+
733
+ def assert_all_assertions_foreach(msg = nil, *keys, &block)
734
+ all = AllFailures.new
735
+ all.foreach(*keys, &block)
736
+ ensure
737
+ assert(all.pass?, message(msg) {all.message.chomp(".")})
738
+ end
739
+ alias all_assertions_foreach assert_all_assertions_foreach
740
+
741
+ %w[
742
+ CLOCK_THREAD_CPUTIME_ID CLOCK_PROCESS_CPUTIME_ID
743
+ CLOCK_MONOTONIC
744
+ ].find do |clk|
745
+ if Process.const_defined?(clk)
746
+ [clk.to_sym, Process.const_get(clk)].find do |clk|
747
+ Process.clock_gettime(clk)
748
+ rescue
749
+ # Constants may be defined but not implemented, e.g., mingw.
750
+ else
751
+ PERFORMANCE_CLOCK = clk
752
+ end
753
+ end
754
+ end
755
+
756
+ # Expect +seq+ to respond to +first+ and +each+ methods, e.g.,
757
+ # Array, Range, Enumerator::ArithmeticSequence and other
758
+ # Enumerable-s, and each elements should be size factors.
759
+ #
760
+ # :yield: each elements of +seq+.
761
+ def assert_linear_performance(seq, rehearsal: nil, pre: ->(n) {n})
762
+ pend "No PERFORMANCE_CLOCK found" unless defined?(PERFORMANCE_CLOCK)
763
+
764
+ # Timeout testing generally doesn't work when RJIT compilation happens.
765
+ rjit_enabled = defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled?
766
+ measure = proc do |arg, message|
767
+ st = Process.clock_gettime(PERFORMANCE_CLOCK)
768
+ yield(*arg)
769
+ t = (Process.clock_gettime(PERFORMANCE_CLOCK) - st)
770
+ assert_operator 0, :<=, t, message unless rjit_enabled
771
+ t
772
+ end
773
+
774
+ first = seq.first
775
+ *arg = pre.call(first)
776
+ times = (0..(rehearsal || (2 * first))).map do
777
+ measure[arg, "rehearsal"].nonzero?
778
+ end
779
+ times.compact!
780
+ tmin, tmax = times.minmax
781
+ tbase = 10 ** Math.log10(tmax * ([(tmax / tmin), 2].max ** 2)).ceil
782
+ info = "(tmin: #{tmin}, tmax: #{tmax}, tbase: #{tbase})"
783
+
784
+ seq.each do |i|
785
+ next if i == first
786
+ t = tbase * i.fdiv(first)
787
+ *arg = pre.call(i)
788
+ message = "[#{i}]: in #{t}s #{info}"
789
+ Timeout.timeout(t, Timeout::Error, message) do
790
+ measure[arg, message]
791
+ end
792
+ end
793
+ end
794
+
795
+ def diff(exp, act)
796
+ require 'pp'
797
+ q = PP.new(+"")
798
+ q.guard_inspect_key do
799
+ q.group(2, "expected: ") do
800
+ q.pp exp
801
+ end
802
+ q.text q.newline
803
+ q.group(2, "actual: ") do
804
+ q.pp act
805
+ end
806
+ q.flush
807
+ end
808
+ q.output
809
+ end
810
+
811
+ def new_test_token
812
+ token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m"
813
+ return token.dump, Regexp.quote(token)
814
+ end
815
+ end
816
+ end
817
+ end
data/lib/envutil.rb ADDED
@@ -0,0 +1,380 @@
1
+ # -*- coding: us-ascii -*-
2
+ # frozen_string_literal: true
3
+ require "open3"
4
+ require "timeout"
5
+ require_relative "find_executable"
6
+ begin
7
+ require 'rbconfig'
8
+ rescue LoadError
9
+ end
10
+ begin
11
+ require "rbconfig/sizeof"
12
+ rescue LoadError
13
+ end
14
+
15
+ module EnvUtil
16
+ def rubybin
17
+ if ruby = ENV["RUBY"]
18
+ return ruby
19
+ end
20
+ ruby = "ruby"
21
+ exeext = RbConfig::CONFIG["EXEEXT"]
22
+ rubyexe = (ruby + exeext if exeext and !exeext.empty?)
23
+ 3.times do
24
+ if File.exist? ruby and File.executable? ruby and !File.directory? ruby
25
+ return File.expand_path(ruby)
26
+ end
27
+ if rubyexe and File.exist? rubyexe and File.executable? rubyexe
28
+ return File.expand_path(rubyexe)
29
+ end
30
+ ruby = File.join("..", ruby)
31
+ end
32
+ if defined?(RbConfig.ruby)
33
+ RbConfig.ruby
34
+ else
35
+ "ruby"
36
+ end
37
+ end
38
+ module_function :rubybin
39
+
40
+ LANG_ENVS = %w"LANG LC_ALL LC_CTYPE"
41
+
42
+ DEFAULT_SIGNALS = Signal.list
43
+ DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM
44
+
45
+ RUBYLIB = ENV["RUBYLIB"]
46
+
47
+ class << self
48
+ attr_accessor :timeout_scale
49
+ attr_reader :original_internal_encoding, :original_external_encoding,
50
+ :original_verbose, :original_warning
51
+
52
+ def capture_global_values
53
+ @original_internal_encoding = Encoding.default_internal
54
+ @original_external_encoding = Encoding.default_external
55
+ @original_verbose = $VERBOSE
56
+ @original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil
57
+ end
58
+ end
59
+
60
+ def apply_timeout_scale(t)
61
+ if scale = EnvUtil.timeout_scale
62
+ t * scale
63
+ else
64
+ t
65
+ end
66
+ end
67
+ module_function :apply_timeout_scale
68
+
69
+ def timeout(sec, klass = nil, message = nil, &blk)
70
+ return yield(sec) if sec == nil or sec.zero?
71
+ sec = apply_timeout_scale(sec)
72
+ Timeout.timeout(sec, klass, message, &blk)
73
+ end
74
+ module_function :timeout
75
+
76
+ def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1)
77
+ reprieve = apply_timeout_scale(reprieve) if reprieve
78
+
79
+ signals = Array(signal).select do |sig|
80
+ DEFAULT_SIGNALS[sig.to_s] or
81
+ DEFAULT_SIGNALS[Signal.signame(sig)] rescue false
82
+ end
83
+ signals |= [:ABRT, :KILL]
84
+ case pgroup
85
+ when 0, true
86
+ pgroup = -pid
87
+ when nil, false
88
+ pgroup = pid
89
+ end
90
+
91
+ lldb = true if /darwin/ =~ RUBY_PLATFORM
92
+
93
+ while signal = signals.shift
94
+
95
+ if lldb and [:ABRT, :KILL].include?(signal)
96
+ lldb = false
97
+ # sudo -n: --non-interactive
98
+ # lldb -p: attach
99
+ # -o: run command
100
+ system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit])
101
+ true
102
+ end
103
+
104
+ begin
105
+ Process.kill signal, pgroup
106
+ rescue Errno::EINVAL
107
+ next
108
+ rescue Errno::ESRCH
109
+ break
110
+ end
111
+ if signals.empty? or !reprieve
112
+ Process.wait(pid)
113
+ else
114
+ begin
115
+ Timeout.timeout(reprieve) {Process.wait(pid)}
116
+ rescue Timeout::Error
117
+ else
118
+ break
119
+ end
120
+ end
121
+ end
122
+ $?
123
+ end
124
+ module_function :terminate
125
+
126
+ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false,
127
+ encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error,
128
+ stdout_filter: nil, stderr_filter: nil, ios: nil,
129
+ signal: :TERM,
130
+ rubybin: EnvUtil.rubybin, precommand: nil,
131
+ **opt)
132
+ timeout = apply_timeout_scale(timeout)
133
+
134
+ in_c, in_p = IO.pipe
135
+ out_p, out_c = IO.pipe if capture_stdout
136
+ err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout
137
+ opt[:in] = in_c
138
+ opt[:out] = out_c if capture_stdout
139
+ opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr
140
+ if encoding
141
+ out_p.set_encoding(encoding) if out_p
142
+ err_p.set_encoding(encoding) if err_p
143
+ end
144
+ ios.each {|i, o = i|opt[i] = o} if ios
145
+
146
+ c = "C"
147
+ child_env = {}
148
+ LANG_ENVS.each {|lc| child_env[lc] = c}
149
+ if Array === args and Hash === args.first
150
+ child_env.update(args.shift)
151
+ end
152
+ if RUBYLIB and lib = child_env["RUBYLIB"]
153
+ child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR)
154
+ end
155
+
156
+ # remain env
157
+ %w(ASAN_OPTIONS RUBY_ON_BUG).each{|name|
158
+ child_env[name] = ENV[name] if ENV[name]
159
+ }
160
+
161
+ args = [args] if args.kind_of?(String)
162
+ pid = spawn(child_env, *precommand, rubybin, *args, opt)
163
+ in_c.close
164
+ out_c&.close
165
+ out_c = nil
166
+ err_c&.close
167
+ err_c = nil
168
+ if block_given?
169
+ return yield in_p, out_p, err_p, pid
170
+ else
171
+ th_stdout = Thread.new { out_p.read } if capture_stdout
172
+ th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout
173
+ in_p.write stdin_data.to_str unless stdin_data.empty?
174
+ in_p.close
175
+ if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout))
176
+ timeout_error = nil
177
+ else
178
+ status = terminate(pid, signal, opt[:pgroup], reprieve)
179
+ terminated = Time.now
180
+ end
181
+ stdout = th_stdout.value if capture_stdout
182
+ stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout
183
+ out_p.close if capture_stdout
184
+ err_p.close if capture_stderr && capture_stderr != :merge_to_stdout
185
+ status ||= Process.wait2(pid)[1]
186
+ stdout = stdout_filter.call(stdout) if stdout_filter
187
+ stderr = stderr_filter.call(stderr) if stderr_filter
188
+ if timeout_error
189
+ bt = caller_locations
190
+ msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)"
191
+ msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n"))
192
+ raise timeout_error, msg, bt.map(&:to_s)
193
+ end
194
+ return stdout, stderr, status
195
+ end
196
+ ensure
197
+ [th_stdout, th_stderr].each do |th|
198
+ th.kill if th
199
+ end
200
+ [in_c, in_p, out_c, out_p, err_c, err_p].each do |io|
201
+ io&.close
202
+ end
203
+ [th_stdout, th_stderr].each do |th|
204
+ th.join if th
205
+ end
206
+ end
207
+ module_function :invoke_ruby
208
+
209
+ def verbose_warning
210
+ class << (stderr = "".dup)
211
+ alias write concat
212
+ def flush; end
213
+ end
214
+ stderr, $stderr = $stderr, stderr
215
+ $VERBOSE = true
216
+ yield stderr
217
+ return $stderr
218
+ ensure
219
+ stderr, $stderr = $stderr, stderr
220
+ $VERBOSE = EnvUtil.original_verbose
221
+ EnvUtil.original_warning&.each {|i, v| Warning[i] = v}
222
+ end
223
+ module_function :verbose_warning
224
+
225
+ def default_warning
226
+ $VERBOSE = false
227
+ yield
228
+ ensure
229
+ $VERBOSE = EnvUtil.original_verbose
230
+ end
231
+ module_function :default_warning
232
+
233
+ def suppress_warning
234
+ $VERBOSE = nil
235
+ yield
236
+ ensure
237
+ $VERBOSE = EnvUtil.original_verbose
238
+ end
239
+ module_function :suppress_warning
240
+
241
+ def under_gc_stress(stress = true)
242
+ stress, GC.stress = GC.stress, stress
243
+ yield
244
+ ensure
245
+ GC.stress = stress
246
+ end
247
+ module_function :under_gc_stress
248
+
249
+ def with_default_external(enc)
250
+ suppress_warning { Encoding.default_external = enc }
251
+ yield
252
+ ensure
253
+ suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding }
254
+ end
255
+ module_function :with_default_external
256
+
257
+ def with_default_internal(enc)
258
+ suppress_warning { Encoding.default_internal = enc }
259
+ yield
260
+ ensure
261
+ suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding }
262
+ end
263
+ module_function :with_default_internal
264
+
265
+ def labeled_module(name, &block)
266
+ Module.new do
267
+ singleton_class.class_eval {
268
+ define_method(:to_s) {name}
269
+ alias inspect to_s
270
+ alias name to_s
271
+ }
272
+ class_eval(&block) if block
273
+ end
274
+ end
275
+ module_function :labeled_module
276
+
277
+ def labeled_class(name, superclass = Object, &block)
278
+ Class.new(superclass) do
279
+ singleton_class.class_eval {
280
+ define_method(:to_s) {name}
281
+ alias inspect to_s
282
+ alias name to_s
283
+ }
284
+ class_eval(&block) if block
285
+ end
286
+ end
287
+ module_function :labeled_class
288
+
289
+ if /darwin/ =~ RUBY_PLATFORM
290
+ DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports")
291
+ DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S'
292
+ @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME']
293
+
294
+ def self.diagnostic_reports(signame, pid, now)
295
+ return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame)
296
+ cmd = File.basename(rubybin)
297
+ cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd
298
+ path = DIAGNOSTIC_REPORTS_PATH
299
+ timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT
300
+ pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.{crash,ips}"
301
+ first = true
302
+ 30.times do
303
+ first ? (first = false) : sleep(0.1)
304
+ Dir.glob(pat) do |name|
305
+ log = File.read(name) rescue next
306
+ case name
307
+ when /\.crash\z/
308
+ if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log
309
+ File.unlink(name)
310
+ File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil
311
+ return log
312
+ end
313
+ when /\.ips\z/
314
+ if /^ *"pid" *: *#{pid},/ =~ log
315
+ File.unlink(name)
316
+ return log
317
+ end
318
+ end
319
+ end
320
+ end
321
+ nil
322
+ end
323
+ else
324
+ def self.diagnostic_reports(signame, pid, now)
325
+ end
326
+ end
327
+
328
+ def self.failure_description(status, now, message = "", out = "")
329
+ pid = status.pid
330
+ if signo = status.termsig
331
+ signame = Signal.signame(signo)
332
+ sigdesc = "signal #{signo}"
333
+ end
334
+ log = diagnostic_reports(signame, pid, now)
335
+ if signame
336
+ sigdesc = "SIG#{signame} (#{sigdesc})"
337
+ end
338
+ if status.coredump?
339
+ sigdesc = "#{sigdesc} (core dumped)"
340
+ end
341
+ full_message = ''.dup
342
+ message = message.call if Proc === message
343
+ if message and !message.empty?
344
+ full_message << message << "\n"
345
+ end
346
+ full_message << "pid #{pid}"
347
+ full_message << " exit #{status.exitstatus}" if status.exited?
348
+ full_message << " killed by #{sigdesc}" if sigdesc
349
+ if out and !out.empty?
350
+ full_message << "\n" << out.b.gsub(/^/, '| ')
351
+ full_message.sub!(/(?<!\n)\z/, "\n")
352
+ end
353
+ if log
354
+ full_message << "Diagnostic reports:\n" << log.b.gsub(/^/, '| ')
355
+ end
356
+ full_message
357
+ end
358
+
359
+ def self.gc_stress_to_class?
360
+ unless defined?(@gc_stress_to_class)
361
+ _, _, status = invoke_ruby(["-e""exit GC.respond_to?(:add_stress_to_class)"])
362
+ @gc_stress_to_class = status.success?
363
+ end
364
+ @gc_stress_to_class
365
+ end
366
+ end
367
+
368
+ if defined?(RbConfig)
369
+ module RbConfig
370
+ @ruby = EnvUtil.rubybin
371
+ class << self
372
+ undef ruby if method_defined?(:ruby)
373
+ attr_reader :ruby
374
+ end
375
+ dir = File.dirname(ruby)
376
+ CONFIG['bindir'] = dir
377
+ end
378
+ end
379
+
380
+ EnvUtil.capture_global_values
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ require "rbconfig"
3
+
4
+ module EnvUtil
5
+ def find_executable(cmd, *args)
6
+ exts = RbConfig::CONFIG["EXECUTABLE_EXTS"].split | [RbConfig::CONFIG["EXEEXT"]]
7
+ ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
8
+ next if path.empty?
9
+ path = File.join(path, cmd)
10
+ exts.each do |ext|
11
+ cmdline = [path + ext, *args]
12
+ begin
13
+ return cmdline if yield(IO.popen(cmdline, "r", err: [:child, :out], &:read))
14
+ rescue
15
+ next
16
+ end
17
+ end
18
+ end
19
+ nil
20
+ end
21
+ module_function :find_executable
22
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: test-unit-ruby-core
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Hiroshi SHIBATA
8
+ - Nobu Nakada
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2023-06-06 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Additional test assertions for Ruby standard libraries.
15
+ email:
16
+ - hsbt@ruby-lang.org
17
+ - nobu@ruby-lang.org
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - lib/core_assertions.rb
23
+ - lib/envutil.rb
24
+ - lib/find_executable.rb
25
+ homepage: https://github.com/ruby/test-unit-ruby-core
26
+ licenses:
27
+ - Ruby
28
+ - BSD-2-Clause
29
+ metadata:
30
+ homepage_uri: https://github.com/ruby/test-unit-ruby-core
31
+ source_code_uri: https://github.com/ruby/test-unit-ruby-core
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.6.0
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubygems_version: 3.5.0.dev
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: Additional test assertions for Ruby standard libraries.
51
+ test_files: []