Refactor weak tables including proper weak key semantics and improved userdata handling.

This commit is contained in:
James Roseborough
2010-06-21 01:31:40 +00:00
parent 03cebfbf82
commit dc84bc9e8d
8 changed files with 523 additions and 414 deletions

View File

@@ -26,6 +26,7 @@ import junit.framework.TestSuite;
import org.luaj.vm2.WeakTableTest.WeakKeyTableTest;
import org.luaj.vm2.WeakTableTest.WeakKeyValueTableTest;
import org.luaj.vm2.WeakTableTest.WeakValueTableTest;
import org.luaj.vm2.compiler.CompilerUnitTests;
import org.luaj.vm2.compiler.DumpLoadEndianIntTest;
import org.luaj.vm2.compiler.RegressionTests;
@@ -52,7 +53,7 @@ public class AllTests {
table.addTestSuite(TableTest.class);
table.addTestSuite(TableArrayTest.class);
table.addTestSuite(TableHashTest.class);
table.addTestSuite(WeakTableTest.class);
table.addTestSuite(WeakValueTableTest.class);
table.addTestSuite(WeakKeyTableTest.class);
table.addTestSuite(WeakKeyValueTableTest.class);
suite.addTest(table);

View File

@@ -57,10 +57,10 @@ public class TableArrayTest extends TestCase {
}
// Ensure capacities make sense
assertEquals( 0, t.hashCapacity() );
assertEquals( 0, t.getHashLength() );
assertTrue( t.arrayCapacity() >= 32 );
assertTrue( t.arrayCapacity() <= 64 );
assertTrue( t.getArrayLength() >= 32 );
assertTrue( t.getArrayLength() <= 64 );
}
@@ -79,8 +79,8 @@ public class TableArrayTest extends TestCase {
assertEquals(LuaInteger.valueOf(i), t.get(i));
}
assertTrue( t.arrayCapacity() >= 0 && t.arrayCapacity() <= 2 );
assertTrue( t.hashCapacity() >= 4 );
assertTrue( t.getArrayLength() >= 0 && t.getArrayLength() <= 2 );
assertTrue( t.getHashLength() >= 4 );
}
public void testOutOfOrderIntegerKeyInsertion() {
@@ -96,11 +96,11 @@ public class TableArrayTest extends TestCase {
}
// Ensure capacities make sense
assertTrue( t.arrayCapacity() >= 0 );
assertTrue( t.arrayCapacity() <= 6 );
assertTrue( t.getArrayLength() >= 0 );
assertTrue( t.getArrayLength() <= 6 );
assertTrue( t.hashCapacity() >= 16 );
assertTrue( t.hashCapacity() <= 64 );
assertTrue( t.getHashLength() >= 16 );
assertTrue( t.getHashLength() <= 64 );
}
@@ -113,10 +113,10 @@ public class TableArrayTest extends TestCase {
t.set( str, LuaInteger.valueOf( i ) );
}
assertTrue( t.arrayCapacity() >= 9 ); // 1, 2, ..., 9
assertTrue( t.arrayCapacity() <= 18 );
assertTrue( t.hashCapacity() >= 11 ); // 0, "0", "1", ..., "9"
assertTrue( t.hashCapacity() <= 33 );
assertTrue( t.getArrayLength() >= 9 ); // 1, 2, ..., 9
assertTrue( t.getArrayLength() <= 18 );
assertTrue( t.getHashLength() >= 11 ); // 0, "0", "1", ..., "9"
assertTrue( t.getHashLength() <= 33 );
LuaValue[] keys = t.keys();

View File

@@ -44,7 +44,7 @@ public class TableHashTest extends TestCase {
public void testSetRemove() {
LuaTable t = new_Table();
assertEquals( 0, t.hashCapacity() );
assertEquals( 0, t.getHashLength() );
assertEquals( 0, t.length() );
assertEquals( 0, t.keyCount() );
@@ -52,13 +52,13 @@ public class TableHashTest extends TestCase {
"cd", "ef", "g", "hi", "jk", "lm", "no", "pq", "rs", };
int[] capacities = { 0, 2, 4, 4, 7, 7, 7, 10, 10, 14, 14, 14, 14, 19, 19, 19, 19, 25, 25, 25 };
for ( int i = 0; i < keys.length; ++i ) {
assertEquals( capacities[i], t.hashCapacity() );
assertEquals( capacities[i], t.getHashLength() );
String si = "Test Value! "+i;
t.set( keys[i], si );
assertEquals( 0, t.length() );
assertEquals( i+1, t.keyCount() );
}
assertEquals( capacities[keys.length], t.hashCapacity() );
assertEquals( capacities[keys.length], t.getHashLength() );
for ( int i = 0; i < keys.length; ++i ) {
LuaValue vi = LuaString.valueOf( "Test Value! "+i );
assertEquals( vi, t.get( keys[i] ) );
@@ -72,7 +72,7 @@ public class TableHashTest extends TestCase {
t.set( keys[i], LuaString.valueOf( "Replacement Value! "+i ) );
assertEquals( 0, t.length() );
assertEquals( keys.length, t.keyCount() );
assertEquals( capacities[keys.length], t.hashCapacity() );
assertEquals( capacities[keys.length], t.getHashLength() );
}
for ( int i = 0; i < keys.length; ++i ) {
LuaValue vi = LuaString.valueOf( "Replacement Value! "+i );
@@ -85,9 +85,9 @@ public class TableHashTest extends TestCase {
assertEquals( 0, t.length() );
assertEquals( keys.length-i-1, t.keyCount() );
if ( i<keys.length-1 )
assertEquals( capacities[keys.length], t.hashCapacity() );
assertEquals( capacities[keys.length], t.getHashLength() );
else
assertTrue( 0<=t.hashCapacity() );
assertTrue( 0<=t.getHashLength() );
}
for ( int i = 0; i < keys.length; ++i ) {
assertEquals( LuaValue.NIL, t.get( keys[i] ) );

View File

@@ -21,6 +21,7 @@
******************************************************************************/
package org.luaj.vm2;
import java.util.ArrayList;
import java.util.Vector;
import junit.framework.TestCase;
@@ -35,6 +36,23 @@ public class TableTest extends TestCase {
return new LuaTable(n,m);
}
private int keyCount(LuaTable t) {
return keys(t).length;
}
private LuaValue[] keys(LuaTable t) {
ArrayList<LuaValue> l = new ArrayList<LuaValue>();
LuaValue k = LuaValue.NIL;
while ( true ) {
Varargs n = t.next(k);
if ( (k = n.arg1()).isnil() )
break;
l.add( k );
}
return l.toArray(new LuaValue[t.length()]);
}
public void testInOrderIntegerKeyInsertion() {
LuaTable t = new_Table();
@@ -48,10 +66,10 @@ public class TableTest extends TestCase {
}
// Ensure capacities make sense
assertEquals( 0, t.hashCapacity() );
assertEquals( 0, t.getHashLength() );
assertTrue( t.arrayCapacity() >= 32 );
assertTrue( t.arrayCapacity() <= 64 );
assertTrue( t.getArrayLength() >= 32 );
assertTrue( t.getArrayLength() <= 64 );
}
@@ -70,8 +88,8 @@ public class TableTest extends TestCase {
assertEquals(LuaInteger.valueOf(i), t.get(i));
}
assertTrue( t.arrayCapacity() >= 0 && t.arrayCapacity() <= 2 );
assertTrue( t.hashCapacity() >= 4 );
assertTrue( t.getArrayLength() >= 0 && t.getArrayLength() <= 2 );
assertTrue( t.getHashLength() >= 4 );
}
public void testOutOfOrderIntegerKeyInsertion() {
@@ -87,11 +105,11 @@ public class TableTest extends TestCase {
}
// Ensure capacities make sense
assertTrue( t.arrayCapacity() >= 0 );
assertTrue( t.arrayCapacity() <= 6 );
assertTrue( t.getArrayLength() >= 0 );
assertTrue( t.getArrayLength() <= 6 );
assertTrue( t.hashCapacity() >= 16 );
assertTrue( t.hashCapacity() <= 64 );
assertTrue( t.getHashLength() >= 16 );
assertTrue( t.getHashLength() <= 64 );
}
@@ -104,12 +122,12 @@ public class TableTest extends TestCase {
t.set( str, LuaInteger.valueOf( i ) );
}
assertTrue( t.arrayCapacity() >= 9 ); // 1, 2, ..., 9
assertTrue( t.arrayCapacity() <= 18 );
assertTrue( t.hashCapacity() >= 11 ); // 0, "0", "1", ..., "9"
assertTrue( t.hashCapacity() <= 33 );
assertTrue( t.getArrayLength() >= 9 ); // 1, 2, ..., 9
assertTrue( t.getArrayLength() <= 18 );
assertTrue( t.getHashLength() >= 11 ); // 0, "0", "1", ..., "9"
assertTrue( t.getHashLength() <= 33 );
LuaValue[] keys = t.keys();
LuaValue[] keys = keys(t);
int intKeys = 0;
int stringKeys = 0;
@@ -145,7 +163,7 @@ public class TableTest extends TestCase {
t.set( "test", LuaValue.valueOf("foo") );
t.set( "explode", LuaValue.valueOf("explode") );
assertEquals( 2, t.keyCount() );
assertEquals( 2, keyCount(t) );
}
public void testRemove0() {
@@ -173,11 +191,11 @@ public class TableTest extends TestCase {
t.set( 42, LuaValue.NIL );
t.set( new_Table(), LuaValue.NIL );
t.set( "test", LuaValue.NIL );
assertEquals( 0, t.keyCount() );
assertEquals( 0, keyCount(t) );
t.set( 10, LuaInteger.valueOf( 5 ) );
t.set( 10, LuaValue.NIL );
assertEquals( 0, t.keyCount() );
assertEquals( 0, keyCount(t) );
}
public void testRemove2() {
@@ -185,23 +203,23 @@ public class TableTest extends TestCase {
t.set( "test", LuaValue.valueOf("foo") );
t.set( "string", LuaInteger.valueOf( 10 ) );
assertEquals( 2, t.keyCount() );
assertEquals( 2, keyCount(t) );
t.set( "string", LuaValue.NIL );
t.set( "three", LuaValue.valueOf( 3.14 ) );
assertEquals( 2, t.keyCount() );
assertEquals( 2, keyCount(t) );
t.set( "test", LuaValue.NIL );
assertEquals( 1, t.keyCount() );
assertEquals( 1, keyCount(t) );
t.set( 10, LuaInteger.valueOf( 5 ) );
assertEquals( 2, t.keyCount() );
assertEquals( 2, keyCount(t) );
t.set( 10, LuaValue.NIL );
assertEquals( 1, t.keyCount() );
assertEquals( 1, keyCount(t) );
t.set( "three", LuaValue.NIL );
assertEquals( 0, t.keyCount() );
assertEquals( 0, keyCount(t) );
}
public void testInOrderLuaLength() {

View File

@@ -22,64 +22,9 @@
package org.luaj.vm2;
import java.lang.ref.WeakReference;
import java.util.Random;
public class WeakTableTest extends TableTest {
abstract public class WeakTableTest extends TableTest {
protected LuaTable new_Table() { return new WeakTable(false, true); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(false, true); }
public static class WeakKeyTableTest extends TableTest {
protected LuaTable new_Table() { return new WeakTable(true, false); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(true, false); }
}
public static class WeakKeyValueTableTest extends TableTest {
protected LuaTable new_Table() { return new WeakTable(true, true); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(true, true); }
}
public void testWeakValuesTable() {
LuaTable t = new_Table();
Object obj = new Object();
LuaTable tableValue = new LuaTable();
LuaString stringValue = LuaString.valueOf("this is a test");
t.set("table", tableValue);
t.set("userdata", LuaValue.userdataOf(obj, null));
t.set("string", stringValue);
t.set("string2", LuaString.valueOf("another string"));
assertTrue("table must have at least 4 elements", t.hashKeys.length > 4);
// check that table can be used to get elements
assertEquals(tableValue, t.get("table"));
assertEquals(stringValue, t.get("string"));
assertEquals(obj, t.get("userdata").checkuserdata());
// nothing should be collected, since we have strong references here
System.gc();
// check that elements are still there
assertEquals(tableValue, t.get("table"));
assertEquals(stringValue, t.get("string"));
assertEquals(obj, t.get("userdata").checkuserdata());
// drop our strong references
obj = null;
tableValue = null;
stringValue = null;
// Garbage collection should cause weak entries to be dropped.
System.gc();
// check that they are dropped
assertEquals(LuaValue.NIL, t.get("table"));
assertEquals(LuaValue.NIL, t.get("userdata"));
assertFalse("strings should not be in weak references", t.get("string").isnil());
}
public static class MyData {
public final int value;
public MyData( int value ) {
@@ -91,98 +36,161 @@ public class WeakTableTest extends TableTest {
public boolean equals( Object o ) {
return (o instanceof MyData) && ((MyData)o).value == value;
}
public String toSting() {
public String toString() {
return "mydata-"+value;
}
}
public void testWeakKeysTable() {
LuaTable t = new WeakTable(true, false);
LuaValue key = LuaValue.userdataOf(new MyData(111));
LuaValue val = LuaValue.userdataOf(new MyData(222));
// set up the table
t.set( key, val );
assertEquals( val, t.get(key) );
System.gc();
assertEquals( val, t.get(key) );
// drop key and value references, replace them with new ones
WeakReference origkey = new WeakReference(key);
WeakReference origval = new WeakReference(val);
key = LuaValue.userdataOf(new MyData(111));
val = LuaValue.userdataOf(new MyData(222));
// new key and value should be interchangeable (feature of this test class
assertEquals( key, origkey.get() );
assertEquals( val, origval.get() );
assertEquals( val, t.get(key) );
assertEquals( val, t.get((LuaValue) origkey.get()) );
assertEquals( origval.get(), t.get(key) );
// value should not be reachable after gc
System.gc();
assertEquals( null, origkey.get() );
assertEquals( LuaValue.NIL, t.get(key) );
// value should also be gone after gc after access!
System.gc();
assertEquals( null, origkey.get() );
assertEquals( null, origval.get() );
static void collectGarbage() {
Runtime rt = Runtime.getRuntime();
rt.gc();
try {
Thread.sleep(20);
rt.gc();
Thread.sleep(20);
} catch ( Exception e ) {
e.printStackTrace();
}
rt.gc();
}
public void testWeakKeysValuesTable() {
LuaTable t = new WeakTable(true, true);
public static class WeakValueTableTest extends WeakTableTest {
protected LuaTable new_Table() { return new WeakTable(false, true); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(false, true); }
public void testWeakValuesTable() {
LuaTable t = new_Table();
Object obj = new Object();
LuaTable tableValue = new LuaTable();
LuaString stringValue = LuaString.valueOf("this is a test");
t.set("table", tableValue);
t.set("userdata", LuaValue.userdataOf(obj, null));
t.set("string", stringValue);
t.set("string2", LuaString.valueOf("another string"));
assertTrue("table must have at least 4 elements", t.getHashLength() > 4);
// check that table can be used to get elements
assertEquals(tableValue, t.get("table"));
assertEquals(stringValue, t.get("string"));
assertEquals(obj, t.get("userdata").checkuserdata());
// nothing should be collected, since we have strong references here
collectGarbage();
// check that elements are still there
assertEquals(tableValue, t.get("table"));
assertEquals(stringValue, t.get("string"));
assertEquals(obj, t.get("userdata").checkuserdata());
// drop our strong references
obj = null;
tableValue = null;
stringValue = null;
// Garbage collection should cause weak entries to be dropped.
collectGarbage();
// check that they are dropped
assertEquals(LuaValue.NIL, t.get("table"));
assertEquals(LuaValue.NIL, t.get("userdata"));
assertFalse("strings should not be in weak references", t.get("string").isnil());
}
}
public static class WeakKeyTableTest extends WeakTableTest {
protected LuaTable new_Table() { return new WeakTable(true, false); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(true, false); }
LuaValue key = LuaValue.userdataOf(new MyData(111));
LuaValue val = LuaValue.userdataOf(new MyData(222));
LuaValue key2 = LuaValue.userdataOf(new MyData(333));
LuaValue val2 = LuaValue.userdataOf(new MyData(444));
LuaValue key3 = LuaValue.userdataOf(new MyData(555));
LuaValue val3 = LuaValue.userdataOf(new MyData(666));
public void testWeakKeysTable() {
LuaTable t = new WeakTable(true, false);
LuaValue key = LuaValue.userdataOf(new MyData(111));
LuaValue val = LuaValue.userdataOf(new MyData(222));
// set up the table
t.set( key, val );
assertEquals( val, t.get(key) );
System.gc();
assertEquals( val, t.get(key) );
// drop key and value references, replace them with new ones
WeakReference origkey = new WeakReference(key);
WeakReference origval = new WeakReference(val);
key = LuaValue.userdataOf(new MyData(111));
val = LuaValue.userdataOf(new MyData(222));
// new key and value should be interchangeable (feature of this test class
assertEquals( key, origkey.get() );
assertEquals( val, origval.get() );
assertEquals( val, t.get(key) );
assertEquals( val, t.get((LuaValue) origkey.get()) );
assertEquals( origval.get(), t.get(key) );
// value should not be reachable after gc
collectGarbage();
assertEquals( null, origkey.get() );
assertEquals( null, origval.get() );
assertEquals( LuaValue.NIL, t.get(key) );
}
}
public static class WeakKeyValueTableTest extends WeakTableTest {
protected LuaTable new_Table() { return new WeakTable(true, true); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(true, true); }
// set up the table
t.set( key, val );
t.set( key2, val2 );
t.set( key3, val3 );
assertEquals( val, t.get(key) );
assertEquals( val2, t.get(key2) );
assertEquals( val3, t.get(key3) );
System.gc();
assertEquals( val, t.get(key) );
assertEquals( val2, t.get(key2) );
assertEquals( val3, t.get(key3) );
public void testWeakKeysValuesTable() {
LuaTable t = new WeakTable(true, true);
LuaValue key = LuaValue.userdataOf(new MyData(111));
LuaValue val = LuaValue.userdataOf(new MyData(222));
LuaValue key2 = LuaValue.userdataOf(new MyData(333));
LuaValue val2 = LuaValue.userdataOf(new MyData(444));
LuaValue key3 = LuaValue.userdataOf(new MyData(555));
LuaValue val3 = LuaValue.userdataOf(new MyData(666));
// set up the table
t.set( key, val );
t.set( key2, val2 );
t.set( key3, val3 );
assertEquals( val, t.get(key) );
assertEquals( val2, t.get(key2) );
assertEquals( val3, t.get(key3) );
System.gc();
assertEquals( val, t.get(key) );
assertEquals( val2, t.get(key2) );
assertEquals( val3, t.get(key3) );
// drop key and value references, replace them with new ones
WeakReference origkey = new WeakReference(key);
WeakReference origval = new WeakReference(val);
WeakReference origkey2 = new WeakReference(key2);
WeakReference origval2 = new WeakReference(val2);
WeakReference origkey3 = new WeakReference(key3);
WeakReference origval3 = new WeakReference(val3);
key = LuaValue.userdataOf(new MyData(111));
val = LuaValue.userdataOf(new MyData(222));
key2 = LuaValue.userdataOf(new MyData(333));
// don't drop val2, or key3
val3 = LuaValue.userdataOf(new MyData(666));
// drop key and value references, replace them with new ones
WeakReference origkey = new WeakReference(key);
WeakReference origval = new WeakReference(val);
WeakReference origkey2 = new WeakReference(key2);
WeakReference origval2 = new WeakReference(val2);
WeakReference origkey3 = new WeakReference(key3);
WeakReference origval3 = new WeakReference(val3);
key = LuaValue.userdataOf(new MyData(111));
val = LuaValue.userdataOf(new MyData(222));
key2 = LuaValue.userdataOf(new MyData(333));
// don't drop val2, or key3
val3 = LuaValue.userdataOf(new MyData(666));
// no values should be reachable after gc
System.gc();
assertEquals( null, origkey.get() );
assertEquals( null, origval.get() );
assertEquals( null, origkey2.get() );
assertEquals( null, origval3.get() );
assertEquals( LuaValue.NIL, t.get(key) );
assertEquals( LuaValue.NIL, t.get(key2) );
assertEquals( LuaValue.NIL, t.get(key3) );
// no values should be reachable after gc
collectGarbage();
assertEquals( null, origkey.get() );
assertEquals( null, origval.get() );
assertEquals( null, origkey2.get() );
assertEquals( null, origval3.get() );
assertEquals( LuaValue.NIL, t.get(key) );
assertEquals( LuaValue.NIL, t.get(key2) );
assertEquals( LuaValue.NIL, t.get(key3) );
// all originals should be gone after gc, then access
val2 = null;
key3 = null;
System.gc();
assertEquals( null, origval2.get() );
assertEquals( null, origkey3.get() );
}
// all originals should be gone after gc, then access
val2 = null;
key3 = null;
collectGarbage();
assertEquals( null, origval2.get() );
assertEquals( null, origkey3.get() );
}
}
}