First stab at a weakref test suite

Discussion in 'Ruby' started by Daniel Berger, Dec 21, 2007.

  1. Hi all,

    I decided to take a stab at creating a minimal test suite for WeakRef
    (based on a post by Charles Oliver Nutter 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
    Daniel Berger, Dec 21, 2007
    #1
    1. Advertising

  2. Daniel Berger

    Eric Hodel Guest

    On Dec 20, 2007, at 21:45 PM, Daniel Berger wrote:
    > Hi all,
    >
    > I decided to take a stab at creating a minimal test suite for
    > WeakRef (based on a post by Charles Oliver Nutter 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?


    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.
    Eric Hodel, Dec 21, 2007
    #2
    1. Advertising

  3. Eric Hodel wrote:
    > On Dec 20, 2007, at 21:45 PM, Daniel Berger 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
    Charles Oliver Nutter, Dec 21, 2007
    #3
  4. Daniel Berger

    Eric Hodel Guest

    On Dec 21, 2007, at 05:50 AM, Charles Oliver Nutter wrote:
    > Eric Hodel wrote:
    >> On Dec 20, 2007, at 21:45 PM, Daniel Berger 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".


    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.
    Eric Hodel, Dec 21, 2007
    #4
  5. On 21/12/2007, Eric Hodel <> wrote:
    > On Dec 21, 2007, at 05:50 AM, Charles Oliver Nutter wrote:
    > > Eric Hodel wrote:
    > >> On Dec 20, 2007, at 21:45 PM, Daniel Berger 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".

    >
    > 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.
    >
    >

    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
    Michal Suchanek, Dec 21, 2007
    #5
  6. On Dec 21, 6:50 am, Charles Oliver Nutter <>
    wrote:
    > Eric Hodel wrote:
    > > On Dec 20, 2007, at 21:45 PM, Daniel Berger 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".


    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

    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
    Daniel Berger, Dec 22, 2007
    #6
  7. On 22/12/2007, Daniel Berger <> 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
    >
    > 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
    Michal Suchanek, Dec 22, 2007
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Maziar Aflatoun

    Looking for free online test suite

    Maziar Aflatoun, Jul 19, 2005, in forum: ASP .Net
    Replies:
    0
    Views:
    376
    Maziar Aflatoun
    Jul 19, 2005
  2. Bengt Richter

    code suite as first class object ?

    Bengt Richter, Jun 28, 2003, in forum: Python
    Replies:
    2
    Views:
    752
    Sean Ross
    Jun 29, 2003
  3. Cirene
    Replies:
    6
    Views:
    427
    Cowboy \(Gregory A. Beamer\)
    Sep 8, 2008
  4. D. Krmpotic
    Replies:
    4
    Views:
    231
    NAKAMURA, Hiroshi
    Aug 3, 2007
  5. Noob

    Taking a stab at getline

    Noob, Feb 7, 2013, in forum: C Programming
    Replies:
    40
    Views:
    798
Loading...

Share This Page