test-unit-ruby-core 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []