/test/ruby/test_gc.rb
Ruby | 454 lines | 394 code | 56 blank | 4 comment | 6 complexity | cfef37269899fe7e7844edabd5077286 MD5 | raw file
- # frozen_string_literal: false
- require 'test/unit'
- class TestGc < Test::Unit::TestCase
- class S
- def initialize(a)
- @a = a
- end
- end
- def test_gc
- prev_stress = GC.stress
- GC.stress = false
- assert_nothing_raised do
- tmp = nil
- 1.upto(10000) {
- tmp = [0,1,2,3,4,5,6,7,8,9]
- }
- end
- l=nil
- 100000.times {
- l = S.new(l)
- }
- GC.start
- assert true # reach here or dumps core
- l = []
- 100000.times {
- l.push([l])
- }
- GC.start
- assert true # reach here or dumps core
- GC.stress = prev_stress
- end
- def use_rgengc?
- GC::OPTS.include? 'USE_RGENGC'.freeze
- end
- def test_enable_disable
- GC.enable
- assert_equal(false, GC.enable)
- assert_equal(false, GC.disable)
- assert_equal(true, GC.disable)
- assert_equal(true, GC.disable)
- assert_nil(GC.start)
- assert_equal(true, GC.enable)
- assert_equal(false, GC.enable)
- ensure
- GC.enable
- end
- def test_start_full_mark
- return unless use_rgengc?
- GC.start(full_mark: false)
- assert_nil GC.latest_gc_info(:major_by)
- GC.start(full_mark: true)
- assert_not_nil GC.latest_gc_info(:major_by)
- end
- def test_start_immediate_sweep
- GC.start(immediate_sweep: false)
- assert_equal false, GC.latest_gc_info(:immediate_sweep)
- GC.start(immediate_sweep: true)
- assert_equal true, GC.latest_gc_info(:immediate_sweep)
- end
- def test_count
- c = GC.count
- GC.start
- assert_operator(c, :<, GC.count)
- end
- def test_stat
- res = GC.stat
- assert_equal(false, res.empty?)
- assert_kind_of(Integer, res[:count])
- arg = Hash.new
- res = GC.stat(arg)
- assert_equal(arg, res)
- assert_equal(false, res.empty?)
- assert_kind_of(Integer, res[:count])
- stat, count = {}, {}
- GC.start
- GC.stat(stat)
- ObjectSpace.count_objects(count)
- assert_equal(count[:TOTAL]-count[:FREE], stat[:heap_live_slots])
- assert_equal(count[:FREE], stat[:heap_free_slots])
- # measure again without GC.start
- 1000.times{ "a" + "b" }
- GC.stat(stat)
- ObjectSpace.count_objects(count)
- assert_equal(count[:FREE], stat[:heap_free_slots])
- end
- def test_stat_argument
- assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {GC.stat(:"\u{30eb 30d3 30fc}")}
- end
- def test_stat_single
- stat = GC.stat
- assert_equal stat[:count], GC.stat(:count)
- assert_raise(ArgumentError){ GC.stat(:invalid) }
- end
- def test_stat_constraints
- stat = GC.stat
- assert_equal stat[:total_allocated_pages], stat[:heap_allocated_pages] + stat[:total_freed_pages]
- assert_operator stat[:heap_sorted_length], :>=, stat[:heap_eden_pages] + stat[:heap_allocatable_pages], "stat is: " + stat.inspect
- assert_equal stat[:heap_available_slots], stat[:heap_live_slots] + stat[:heap_free_slots] + stat[:heap_final_slots]
- assert_equal stat[:heap_live_slots], stat[:total_allocated_objects] - stat[:total_freed_objects] - stat[:heap_final_slots]
- assert_equal stat[:heap_allocated_pages], stat[:heap_eden_pages] + stat[:heap_tomb_pages]
- if use_rgengc?
- assert_equal stat[:count], stat[:major_gc_count] + stat[:minor_gc_count]
- end
- end
- def test_latest_gc_info
- assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom'
- GC.start
- count = GC.stat(:heap_free_slots) + GC.stat(:heap_allocatable_pages) * GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
- count.times{ "a" + "b" }
- assert_equal :newobj, GC.latest_gc_info[:gc_by]
- eom
- GC.start
- assert_equal :force, GC.latest_gc_info[:major_by] if use_rgengc?
- assert_equal :method, GC.latest_gc_info[:gc_by]
- assert_equal true, GC.latest_gc_info[:immediate_sweep]
- GC.stress = true
- assert_equal :force, GC.latest_gc_info[:major_by]
- ensure
- GC.stress = false
- end
- def test_latest_gc_info_argument
- info = {}
- GC.latest_gc_info(info)
- assert_not_empty info
- assert_equal info[:gc_by], GC.latest_gc_info(:gc_by)
- assert_raise(ArgumentError){ GC.latest_gc_info(:invalid) }
- assert_raise_with_message(ArgumentError, /\u{30eb 30d3 30fc}/) {GC.latest_gc_info(:"\u{30eb 30d3 30fc}")}
- end
- def test_singleton_method
- assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "[ruby-dev:42832]")
- GC.stress = true
- 10.times do
- obj = Object.new
- def obj.foo() end
- def obj.bar() raise "obj.foo is called, but this is obj.bar" end
- obj.foo
- end
- EOS
- end
- def test_singleton_method_added
- assert_in_out_err(%w[--disable-gems], <<-EOS, [], [], "[ruby-dev:44436]")
- class BasicObject
- undef singleton_method_added
- def singleton_method_added(mid)
- raise
- end
- end
- b = proc {}
- class << b; end
- b.clone rescue nil
- GC.start
- EOS
- end
- def test_gc_parameter
- env = {
- "RUBY_GC_MALLOC_LIMIT" => "60000000",
- "RUBY_GC_HEAP_INIT_SLOTS" => "100000"
- }
- assert_normal_exit("exit", "[ruby-core:39777]", :child_env => env)
- env = {
- "RUBYOPT" => "",
- "RUBY_GC_HEAP_INIT_SLOTS" => "100000"
- }
- assert_in_out_err([env, "-e", "exit"], "", [], [], "[ruby-core:39795]")
- assert_in_out_err([env, "-W0", "-e", "exit"], "", [], [], "[ruby-core:39795]")
- assert_in_out_err([env, "-W1", "-e", "exit"], "", [], [], "[ruby-core:39795]")
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_INIT_SLOTS=100000/, "[ruby-core:39795]")
- env = {
- "RUBY_GC_HEAP_GROWTH_FACTOR" => "2.0",
- "RUBY_GC_HEAP_GROWTH_MAX_SLOTS" => "10000"
- }
- assert_normal_exit("exit", "", :child_env => env)
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_FACTOR=2.0/, "")
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_GROWTH_MAX_SLOTS=10000/, "[ruby-core:57928]")
- env = {
- "RUBY_GC_HEAP_INIT_SLOTS" => "100000",
- "RUBY_GC_HEAP_FREE_SLOTS" => "10000",
- "RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR" => "0.9",
- }
- assert_normal_exit("exit", "", :child_env => env)
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=0\.9/, "")
- # always full GC when RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR < 1.0
- assert_in_out_err([env, "-e", "1000_000.times{Object.new}; p(GC.stat[:minor_gc_count] < GC.stat[:major_gc_count])"], "", ['true'], //, "") if use_rgengc?
- # check obsolete
- assert_in_out_err([{'RUBY_FREE_MIN' => '100'}, '-w', '-eexit'], '', [],
- /RUBY_FREE_MIN is obsolete. Use RUBY_GC_HEAP_FREE_SLOTS instead/)
- assert_in_out_err([{'RUBY_HEAP_MIN_SLOTS' => '100'}, '-w', '-eexit'], '', [],
- /RUBY_HEAP_MIN_SLOTS is obsolete. Use RUBY_GC_HEAP_INIT_SLOTS instead/)
- env = {
- "RUBY_GC_MALLOC_LIMIT" => "60000000",
- "RUBY_GC_MALLOC_LIMIT_MAX" => "160000000",
- "RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR" => "2.0"
- }
- assert_normal_exit("exit", "", :child_env => env)
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_MALLOC_LIMIT=6000000/, "")
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_MALLOC_LIMIT_MAX=16000000/, "")
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR=2.0/, "")
- if use_rgengc?
- env = {
- "RUBY_GC_OLDMALLOC_LIMIT" => "60000000",
- "RUBY_GC_OLDMALLOC_LIMIT_MAX" => "160000000",
- "RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR" => "2.0"
- }
- assert_normal_exit("exit", "", :child_env => env)
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT=6000000/, "")
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_MAX=16000000/, "")
- assert_in_out_err([env, "-w", "-e", "exit"], "", [], /RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR=2.0/, "")
- end
- end
- def test_profiler_enabled
- GC::Profiler.enable
- assert_equal(true, GC::Profiler.enabled?)
- GC::Profiler.disable
- assert_equal(false, GC::Profiler.enabled?)
- ensure
- GC::Profiler.disable
- end
- def test_profiler_clear
- skip "for now"
- assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom', timeout: 30
- GC::Profiler.enable
- GC.start
- assert_equal(1, GC::Profiler.raw_data.size)
- GC::Profiler.clear
- assert_equal(0, GC::Profiler.raw_data.size)
- 200.times{ GC.start }
- assert_equal(200, GC::Profiler.raw_data.size)
- GC::Profiler.clear
- assert_equal(0, GC::Profiler.raw_data.size)
- eom
- end
- def test_profiler_total_time
- GC::Profiler.enable
- GC::Profiler.clear
- GC.start
- assert_operator(GC::Profiler.total_time, :>=, 0)
- ensure
- GC::Profiler.disable
- end
- def test_finalizing_main_thread
- assert_in_out_err(%w[--disable-gems], <<-EOS, ["\"finalize\""], [], "[ruby-dev:46647]")
- ObjectSpace.define_finalizer(Thread.main) { p 'finalize' }
- EOS
- end
- def test_expand_heap
- assert_separately %w[--disable-gem], __FILE__, __LINE__, <<-'eom'
- GC.start
- base_length = GC.stat[:heap_eden_pages]
- (base_length * 500).times{ 'a' }
- GC.start
- base_length = GC.stat[:heap_eden_pages]
- (base_length * 500).times{ 'a' }
- GC.start
- assert_in_epsilon base_length, (v = GC.stat[:heap_eden_pages]), 1/8r,
- "invalid heap expanding (base_length: #{base_length}, GC.stat[:heap_eden_pages]: #{v})"
- a = []
- (base_length * 500).times{ a << 'a'; nil }
- GC.start
- assert_operator base_length, :<, GC.stat[:heap_eden_pages] + 1
- eom
- end
- def test_gc_internals
- assert_not_nil GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
- assert_not_nil GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
- end
- def test_sweep_in_finalizer
- bug9205 = '[ruby-core:58833] [Bug #9205]'
- 2.times do
- assert_ruby_status([], <<-'end;', bug9205, timeout: 120)
- raise_proc = proc do |id|
- GC.start
- end
- 1000.times do
- ObjectSpace.define_finalizer(Object.new, raise_proc)
- end
- end;
- end
- end
- def test_exception_in_finalizer
- bug9168 = '[ruby-core:58652] [Bug #9168]'
- assert_normal_exit(<<-'end;', bug9168, encoding: Encoding::ASCII_8BIT)
- raise_proc = proc {raise}
- 10000.times do
- ObjectSpace.define_finalizer(Object.new, raise_proc)
- Thread.handle_interrupt(RuntimeError => :immediate) {break}
- Thread.handle_interrupt(RuntimeError => :on_blocking) {break}
- Thread.handle_interrupt(RuntimeError => :never) {break}
- end
- end;
- end
- def test_interrupt_in_finalizer
- bug10595 = '[ruby-core:66825] [Bug #10595]'
- src = <<-'end;'
- Signal.trap(:INT, 'DEFAULT')
- pid = $$
- Thread.start do
- 10.times {
- sleep 0.1
- Process.kill("INT", pid) rescue break
- }
- end
- f = proc {1000.times {}}
- loop do
- ObjectSpace.define_finalizer(Object.new, f)
- end
- end;
- out, err, status = assert_in_out_err(["-e", src], "", [], [], bug10595, signal: :SEGV) do |*result|
- break result
- end
- unless /mswin|mingw/ =~ RUBY_PLATFORM
- assert_equal("INT", Signal.signame(status.termsig), bug10595)
- end
- assert_match(/Interrupt/, err.first, proc {err.join("\n")})
- assert_empty(out)
- end
- def test_verify_internal_consistency
- assert_nil(GC.verify_internal_consistency)
- end
- def test_gc_stress_on_realloc
- assert_normal_exit(<<-'end;', '[Bug #9859]')
- class C
- def initialize
- @a = nil
- @b = nil
- @c = nil
- @d = nil
- @e = nil
- @f = nil
- end
- end
- GC.stress = true
- C.new
- end;
- end
- def test_gc_stress_at_startup
- assert_in_out_err([{"RUBY_DEBUG"=>"gc_stress"}], '', [], [], '[Bug #15784]', success: true, timeout: 60)
- end
- def test_gc_disabled_start
- begin
- disabled = GC.disable
- c = GC.count
- GC.start
- assert_equal 1, GC.count - c
- ensure
- GC.enable unless disabled
- end
- end
- def test_vm_object
- assert_normal_exit <<-'end', '[Bug #12583]'
- ObjectSpace.each_object{|o| o.singleton_class rescue 0}
- ObjectSpace.each_object{|o| case o when Module then o.instance_methods end}
- end
- end
- def test_exception_in_finalizer_procs
- result = []
- c1 = proc do
- result << :c1
- raise
- end
- c2 = proc do
- result << :c2
- raise
- end
- tap {
- tap {
- obj = Object.new
- ObjectSpace.define_finalizer(obj, c1)
- ObjectSpace.define_finalizer(obj, c2)
- obj = nil
- }
- }
- GC.start
- skip "finalizers did not get run" if result.empty?
- assert_equal([:c1, :c2], result)
- end
- def test_exception_in_finalizer_method
- @result = []
- def self.c1(x)
- @result << :c1
- raise
- end
- def self.c2(x)
- @result << :c2
- raise
- end
- tap {
- tap {
- obj = Object.new
- ObjectSpace.define_finalizer(obj, method(:c1))
- ObjectSpace.define_finalizer(obj, method(:c2))
- obj = nil
- }
- }
- GC.start
- skip "finalizers did not get run" if @result.empty?
- assert_equal([:c1, :c2], @result)
- end
- end