# First stab at a weakref test suite

**URL:** <https://www.ruby-forum.com/t/first-stab-at-a-weakref-test-suite/123844>\
**Category:** Ruby\
**Created:** [December 21, 2007, 6:46am UTC](https://www.ruby-forum.com/t/first-stab-at-a-weakref-test-suite/123844 "2007-12-21T06:46:43Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![danielb](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/c57346/32.png) [@danielb](https://www.ruby-forum.com/u/danielb)\
**Post date:** [December 21, 2007, 6:46am UTC](https://www.ruby-forum.com/t/first-stab-at-a-weakref-test-suite/123844/1 "2007-12-21T06:46:43Z")

</div>

Hi all,

I decided to take a stab at creating a minimal test suite for WeakRef  
(based on a post by Charles Oliver N. for JRuby). The only problem  
is that I get two unexpected failures. I’m not sure if it’s Test::Unit  
fighting with me or what.

Consider the following example:

require ‘weakref’  
str = ‘hello’  
ref = WeakRef.new(str)

p ref ‘hello’  
GC.start  
p ref ‘hello’  
str = nil  
p ref # ‘hello’  
GC.start  
p ref # error

I follow the same pattern in the tests, but it doesn’t raise the  
expected error. Any ideas?

Thanks,

Dan

# tc\_weakref.rb

require ‘test/unit’  
require ‘weakref’

class TC\_WeakRef \< Test::Unit::TestCase  
def setup  
@ref = nil  
@str = ‘hello’  
GC.enable  
end

```
def test_weakref_constructor
   assert_respond_to(WeakRef, :new)
   assert_nothing_raised{ @ref = WeakRef.new(@str) }
   assert_kind_of(WeakRef, @ref)
end

# TODO: Figure out why last test fails
def test_weakref
   assert_nothing_raised{ @ref = WeakRef.new(@str) }
   assert_equal('hello', @ref)

   assert_nothing_raised{ GC.start }
   assert_equal('hello', @ref)

   assert_nothing_raised{ @str = nil }
   assert_equal('hello', @ref)

   assert_nothing_raised{ GC.start }
   assert_raise(WeakRef::RefError){ @str = @ref * 3 }
end

def test_weakref_is_alive_basic
   assert_nothing_raised{ @ref = WeakRef.new(@str) }
   assert_respond_to(@ref, :weakref_alive?)
end

# TODO: Figure out why last test fails
def test_weakref_is_alive
   assert_nothing_raised{ @ref = WeakRef.new(@str) }
   assert_equal(true, @ref.weakref_alive?)

   assert_nothing_raised{ GC.start }
   assert_equal(true, @ref.weakref_alive?)

   assert_nothing_raised{ @str = nil }
   assert_equal(true, @ref.weakref_alive?)

   assert_nothing_raised{ GC.start }
   assert_equal(false, @ref.weakref_alive?)
end

def teardown
   @str = nil
   @ref = nil
end

```

end

---

<div class="post-metadata">

**Author:** ![danielb](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/c57346/32.png) [@danielb](https://www.ruby-forum.com/u/danielb)\
**Post date:** [December 21, 2007, 8:40am UTC](https://www.ruby-forum.com/t/first-stab-at-a-weakref-test-suite/123844/2 "2007-12-21T08:40:42Z")

</div>

On Dec 20, 2007, at 21:45 PM, Daniel B. wrote:

> str = ‘hello’  
> I follow the same pattern in the tests, but it doesn’t raise the  
> expected error. Any ideas?

Since the GC is conservative, references may exist longer than you  
expect because the item that should be collected is still referenced  
from the stack. I don’t know of an easy way around this.

---

<div class="post-metadata">

**Author:** ![danielb](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/c57346/32.png) [@danielb](https://www.ruby-forum.com/u/danielb)\
**Post date:** [December 21, 2007, 2:51pm UTC](https://www.ruby-forum.com/t/first-stab-at-a-weakref-test-suite/123844/3 "2007-12-21T14:51:08Z")

</div>

Eric H. wrote:

> On Dec 20, 2007, at 21:45 PM, Daniel B. wrote:
> 
> > I follow the same pattern in the tests, but it doesn’t raise the  
> > expected error. Any ideas?
> 
> Since the GC is conservative, references may exist longer than you  
> expect because the item that should be collected is still referenced  
> from the stack. I don’t know of an easy way around this.

This is probably the largest reason why a weakref suite will be a little  
tricky. My best recommendation would be to loop calling GC.start until  
the reference gets collected; perhaps with an upper count… “if we  
reach 1000 iterations and it’s not collected, it isn’t working”.

- Charlie

---

<div class="post-metadata">

**Author:** ![danielb](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/c57346/32.png) [@danielb](https://www.ruby-forum.com/u/danielb)\
**Post date:** [December 21, 2007, 9:12pm UTC](https://www.ruby-forum.com/t/first-stab-at-a-weakref-test-suite/123844/4 "2007-12-21T21:12:52Z")

</div>

On Dec 21, 2007, at 05:50 AM, Charles Oliver N. wrote:

> GC.start until the reference gets collected; perhaps with an upper  
> count… “if we reach 1000 iterations and it’s not collected, it  
> isn’t working”.

If it isn’t collected after 1, I doubt it will be collected after  
1000. You’d have better luck manipulating the stack (calling methods  
recursively that eventually allocate things), but you’re still not  
guaranteed that any object will be collected when you want with ruby  
1.8 or 1.9.

---

<div class="post-metadata">

**Author:** ![danielb](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/c57346/32.png) [@danielb](https://www.ruby-forum.com/u/danielb)\
**Post date:** [December 21, 2007, 11:32pm UTC](https://www.ruby-forum.com/t/first-stab-at-a-weakref-test-suite/123844/5 "2007-12-21T23:32:55Z")

</div>

On 21/12/2007, Eric H. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> > little tricky. My best recommendation would be to loop calling

Yes, the behavior looks very non-deterministic from the outside, it  
even depends on naming of the tests. I had better luck making it work  
with 1.8 (works as is) but it won’t work with 1.9. The first test that  
tests the weakref dies does not succeed, and the second does.

This one works for me but it likely depends on exact state of the heap  
or something like that which is system specific:

require ‘test/unit’  
require ‘weakref’

class TC\_WeakRef \< Test::Unit::TestCase  
def setup  
@ref = nil  
@str = “hello”  
GC.enable  
end

# Fails in 1.9 because WeakRef does not have inspect,

# and it pretends to be String

def test\_weakref\_constructor  
assert\_respond\_to(WeakRef, :new)  
assert\_nothing\_raised{ @ref = WeakRef.new(@str) }  
assert\_kind\_of(WeakRef, @ref)  
end

# TODO: Figure out why last test fails

# somehow using the string makes a reference somewhere

def test\_weakref\_2  
str = @str.dup

```
  assert_nothing_raised{ @ref = WeakRef.new(@str) }
  assert_equal(true, @ref.weakref_alive?)
  assert_equal str, @ref

  assert_nothing_raised{ GC.start }
  assert_equal(true, @ref.weakref_alive?)
  assert_equal str, @ref

  assert_nothing_raised{ @str = nil }
  assert_equal(true, @ref.weakref_alive?)
  assert_equal str, @ref

  assert_nothing_raised{ GC.start }
  #assert_equal(false, @ref.weakref_alive?)
  #assert_raise(WeakRef::RefError){ @str = @ref * 3 }

```

end

def test\_weakref\_is\_alive\_basic  
assert\_nothing\_raised{ @ref = WeakRef.new(@str) }  
assert\_respond\_to(@ref, :weakref\_alive?)  
end

# TODO: Figure out why last test does not fail

def test\_weakref\_is\_alive  
assert\_nothing\_raised{ @ref = WeakRef.new(@str) }  
assert\_equal(true, @ref.weakref\_alive?)

```
  assert_nothing_raised{ GC.start }
  assert_equal(true, @ref.weakref_alive?)

  assert_nothing_raised{ @str = nil }
  assert_equal(true, @ref.weakref_alive?)

  assert_nothing_raised{ GC.start }
  assert_equal(false, @ref.weakref_alive?)
  assert_raise(WeakRef::RefError){ @str = @ref * 3 }

```

end

def teardown  
@str = nil  
@ref = nil  
end  
end

---

<div class="post-metadata">

**Author:** ![danielb](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/c57346/32.png) [@danielb](https://www.ruby-forum.com/u/danielb)\
**Post date:** [December 22, 2007, 8:09pm UTC](https://www.ruby-forum.com/t/first-stab-at-a-weakref-test-suite/123844/6 "2007-12-22T20:09:11Z")

</div>

On 22/12/2007, Daniel B. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> I took a look at the Python weakref test suite. I quickly realized  
> that it appeared to be a richer interface. Maybe that’s part of the  
> problem - the interface we have isn’t testable. Take a look here:
> 
> [http://www.python.org/doc/2.4/lib/module-weakref.html](http://www.python.org/doc/2.4/lib/module-weakref.html)
> 
> Then again, maybe I’m way off base. Anyway, I think we should at least  
> take a look at it and see if we want to steal any ideas from it. The  
> corresponding test file is test\_weakref.py, btw.

Apparently the reason the python weakref is testable is that you can  
explicitly delete an object. At least that’s what the test code seems  
to do. This is not possible in ruby, and the ruby gc is not  
deterministic enough to always free all unused objects. So it’s not  
the weakref what would need changing to make weakref testable but the  
memory allocation (and especially deallocation) interface.

Thanks

Michal

---

<div class="post-metadata">

**Author:** ![danielb](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/c57346/32.png) [@danielb](https://www.ruby-forum.com/u/danielb)\
**Post date:** [December 22, 2007, 2:21am UTC](https://www.ruby-forum.com/t/first-stab-at-a-weakref-test-suite/123844/7 "2007-12-22T02:21:55Z")

</div>

On Dec 21, 6:50 am, Charles Oliver N. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
wrote:

> tricky. My best recommendation would be to loop calling GC.start until  
> the reference gets collected; perhaps with an upper count… “if we  
> reach 1000 iterations and it’s not collected, it isn’t working”.

I took a look at the Python weakref test suite. I quickly realized  
that it appeared to be a richer interface. Maybe that’s part of the  
problem - the interface we have isn’t testable. Take a look here:

[http://www.python.org/doc/2.4/lib/module-weakref.html](http://www.python.org/doc/2.4/lib/module-weakref.html)

Then again, maybe I’m way off base. Anyway, I think we should at least  
take a look at it and see if we want to steal any ideas from it. The  
corresponding test file is test\_weakref.py, btw.

Regards,

Dan
