about summary refs log tree commit
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-09-01 18:51:41 -0700
committerEric Wong <normalperson@yhbt.net>2010-09-01 18:53:45 -0700
commit916b7be928c1a7c6b4aa8ab2f58987487a78777e (patch)
treef0ec9a034870972323384de67e1019c5bc5de451
parent28316a1d951d8dd5a1c8b8eb31149f82dd29ff3b (diff)
downloadrpatricia-916b7be928c1a7c6b4aa8ab2f58987487a78777e.tar.gz
enable Patricia#dup to work
Instead of defining the Patricia.new singleton method, we
only need to define the allocation function.  The allocation
function allows "dup" and "clone" methods to be called on
Patricia instances and we will walk the original tree
to copy node prefixes and data into the new one.

This also allows us to subclass Patricia and create
objects from that subclass.
-rw-r--r--ext/rpatricia/rpatricia.c36
-rw-r--r--test/test_duplicate.rb28
-rw-r--r--test/test_gc.rb15
-rw-r--r--test/test_subclass.rb11
4 files changed, 84 insertions, 6 deletions
diff --git a/ext/rpatricia/rpatricia.c b/ext/rpatricia/rpatricia.c
index f395eea..f9564d2 100644
--- a/ext/rpatricia/rpatricia.c
+++ b/ext/rpatricia/rpatricia.c
@@ -6,6 +6,7 @@
 #include "ruby.h"
 #include <stdlib.h>
 #include "patricia.h"
+#include <assert.h>
 
 static VALUE cPatricia, cNode;
 
@@ -245,11 +246,37 @@ p_tree_free (void *ptr)
 }
 
 static VALUE
-p_new (VALUE self)
+p_alloc(VALUE klass)
 {
   patricia_tree_t *tree;
   tree = New_Patricia(32); /* assuming only IPv4 */
-  return Data_Wrap_Struct(cPatricia, p_tree_mark, p_tree_free, tree);
+
+  return Data_Wrap_Struct(klass, p_tree_mark, p_tree_free, tree);
+}
+
+static VALUE
+p_init_copy(VALUE self, VALUE orig)
+{
+  patricia_tree_t *orig_tree;
+
+  Data_Get_Struct(orig, patricia_tree_t, orig_tree);
+  if (orig_tree->head) {
+    patricia_tree_t *tree;
+    patricia_node_t *orig_node, *node;
+    prefix_t prefix;
+    VALUE user_data;
+
+    Data_Get_Struct(self, patricia_tree_t, tree);
+    PATRICIA_WALK(orig_tree->head, orig_node) {
+      node = patricia_lookup(tree, orig_node->prefix);
+      assert(node->prefix == orig_node->prefix);
+
+      user_data = (VALUE)(orig_node->data);
+      if (T_STRING == TYPE(user_data))
+        user_data = rb_str_dup(user_data);
+      PATRICIA_DATA_SET(node, user_data);
+    } PATRICIA_WALK_END;
+  }
 }
 
 void
@@ -258,8 +285,9 @@ Init_rpatricia (void)
   cPatricia = rb_define_class("Patricia", rb_cObject);
   cNode = rb_define_class_under(cPatricia, "Node", rb_cObject);
 
-  /* create new Patricia object */
-  rb_define_singleton_method(cPatricia, "new", p_new, 0);
+  /* allocate new Patricia object, called before initialize  */
+  rb_define_alloc_func(cPatricia, p_alloc);
+  rb_define_method(cPatricia, "initialize_copy", p_init_copy, 1);
 
   /*---------- methods to tree ----------*/
   /* add string */
diff --git a/test/test_duplicate.rb b/test/test_duplicate.rb
new file mode 100644
index 0000000..c50a211
--- /dev/null
+++ b/test/test_duplicate.rb
@@ -0,0 +1,28 @@
+require 'test/unit'
+require 'rpatricia'
+require 'stringio'
+
+class TestDuplicate < Test::Unit::TestCase
+  def test_dup
+    tmp = {}
+    t = Patricia.new
+    t.add('127.0.0.0/8', tmp)
+    t2 = t.dup
+    assert_equal 1, t2.num_nodes
+    assert_equal tmp.object_id, t2.match_best('127.0.0.1').data.object_id
+    t2.add('10.0.0.0/8', zz = [])
+    assert_equal 2, t2.num_nodes
+    assert_equal 1, t.num_nodes
+
+    oldout = $stdout
+    begin
+      $stdout = stringio = StringIO.new
+      t2.show_nodes
+      puts "--"
+      t.show_nodes
+    ensure
+      $stdout = oldout
+    end
+    p stringio.string
+  end
+end
diff --git a/test/test_gc.rb b/test/test_gc.rb
index 5829deb..316d355 100644
--- a/test/test_gc.rb
+++ b/test/test_gc.rb
@@ -15,9 +15,20 @@ class TestGc < Test::Unit::TestCase
     @strings.add('127.0.0.0/24', "localhost")
   end
 
+  def test_gc_dup
+    100000.times do
+      tmp = @strings.dup
+      tmp.remove '127.0.0.0/24'
+      tmp = @arrays.dup
+      tmp.remove '127.0.0.0/24'
+    end
+    assert_equal [], @arrays.match_best('127.0.0.1').data
+    assert_equal "localhost", @strings.match_best('127.0.0.1').data
+  end
+
   def test_gc
     assert_nothing_raised do
-      5_000_000.times do
+      500_000.times do
         t = Patricia.new
         t.add('10.0.0.0/8', {})
         t.add('127.0.0.0/24', "home sweet home")
@@ -25,7 +36,7 @@ class TestGc < Test::Unit::TestCase
     end
 
     # ensure what we created originally didn't get GC-ed'
-    5_000_000.times do
+    500_000.times do
       assert_equal [], @arrays.match_best('127.0.0.1').data
       assert_equal "localhost", @strings.match_best('127.0.0.1').data
     end
diff --git a/test/test_subclass.rb b/test/test_subclass.rb
new file mode 100644
index 0000000..013bfdd
--- /dev/null
+++ b/test/test_subclass.rb
@@ -0,0 +1,11 @@
+require 'test/unit'
+require 'rpatricia'
+
+class SubPatricia < Patricia
+end
+
+class TestSubclass < Test::Unit::TestCase
+  def test_new
+    assert_equal SubPatricia, SubPatricia.new.class
+  end
+end