diff options
Diffstat (limited to 'toolkit/components/ctypes/tests/unit/test_finalizer.js')
-rw-r--r-- | toolkit/components/ctypes/tests/unit/test_finalizer.js | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/toolkit/components/ctypes/tests/unit/test_finalizer.js b/toolkit/components/ctypes/tests/unit/test_finalizer.js new file mode 100644 index 000000000..adfb4c4b4 --- /dev/null +++ b/toolkit/components/ctypes/tests/unit/test_finalizer.js @@ -0,0 +1,452 @@ +var TEST_SIZE = 100; + +function run_test() +{ + let library = open_ctypes_test_lib(); + + let start = library.declare("test_finalizer_start", ctypes.default_abi, + ctypes.void_t, + ctypes.size_t); + let stop = library.declare("test_finalizer_stop", ctypes.default_abi, + ctypes.void_t); + let status = library.declare("test_finalizer_resource_is_acquired", + ctypes.default_abi, + ctypes.bool, + ctypes.size_t); + let released = function released(value, witness) { + return witness == undefined; + }; + + let samples = []; + samples.push( + { + name: "size_t", + acquire: library.declare("test_finalizer_acq_size_t", + ctypes.default_abi, + ctypes.size_t, + ctypes.size_t), + release: library.declare("test_finalizer_rel_size_t", + ctypes.default_abi, + ctypes.void_t, + ctypes.size_t), + compare: library.declare("test_finalizer_cmp_size_t", + ctypes.default_abi, + ctypes.bool, + ctypes.size_t, + ctypes.size_t), + status: status, + released: released + }); + samples.push( + { + name: "size_t", + acquire: library.declare("test_finalizer_acq_size_t", + ctypes.default_abi, + ctypes.size_t, + ctypes.size_t), + release: library.declare("test_finalizer_rel_size_t_set_errno", + ctypes.default_abi, + ctypes.void_t, + ctypes.size_t), + compare: library.declare("test_finalizer_cmp_size_t", + ctypes.default_abi, + ctypes.bool, + ctypes.size_t, + ctypes.size_t), + status: status, + released: released + }); + samples.push( + { + name: "int32_t", + acquire: library.declare("test_finalizer_acq_int32_t", + ctypes.default_abi, + ctypes.int32_t, + ctypes.size_t), + release: library.declare("test_finalizer_rel_int32_t", + ctypes.default_abi, + ctypes.void_t, + ctypes.int32_t), + compare: library.declare("test_finalizer_cmp_int32_t", + ctypes.default_abi, + ctypes.bool, + ctypes.int32_t, + ctypes.int32_t), + status: status, + released: released + } + ); + samples.push( + { + name: "int64_t", + acquire: library.declare("test_finalizer_acq_int64_t", + ctypes.default_abi, + ctypes.int64_t, + ctypes.size_t), + release: library.declare("test_finalizer_rel_int64_t", + ctypes.default_abi, + ctypes.void_t, + ctypes.int64_t), + compare: library.declare("test_finalizer_cmp_int64_t", + ctypes.default_abi, + ctypes.bool, + ctypes.int64_t, + ctypes.int64_t), + status: status, + released: released + } + ); + samples.push( + { + name: "ptr", + acquire: library.declare("test_finalizer_acq_ptr_t", + ctypes.default_abi, + ctypes.PointerType(ctypes.void_t), + ctypes.size_t), + release: library.declare("test_finalizer_rel_ptr_t", + ctypes.default_abi, + ctypes.void_t, + ctypes.PointerType(ctypes.void_t)), + compare: library.declare("test_finalizer_cmp_ptr_t", + ctypes.default_abi, + ctypes.bool, + ctypes.void_t.ptr, + ctypes.void_t.ptr), + status: status, + released: released + } + ); + samples.push( + { + name: "string", + acquire: library.declare("test_finalizer_acq_string_t", + ctypes.default_abi, + ctypes.char.ptr, + ctypes.int), + release: library.declare("test_finalizer_rel_string_t", + ctypes.default_abi, + ctypes.void_t, + ctypes.char.ptr), + compare: library.declare("test_finalizer_cmp_string_t", + ctypes.default_abi, + ctypes.bool, + ctypes.char.ptr, + ctypes.char.ptr), + status: status, + released: released + } + ); + const rect_t = new ctypes.StructType("myRECT", + [{ top : ctypes.int32_t }, + { left : ctypes.int32_t }, + { bottom: ctypes.int32_t }, + { right : ctypes.int32_t }]); + samples.push( + { + name: "struct", + acquire: library.declare("test_finalizer_acq_struct_t", + ctypes.default_abi, + rect_t, + ctypes.int), + release: library.declare("test_finalizer_rel_struct_t", + ctypes.default_abi, + ctypes.void_t, + rect_t), + compare: library.declare("test_finalizer_cmp_struct_t", + ctypes.default_abi, + ctypes.bool, + rect_t, + rect_t), + status: status, + released: released + } + ); + samples.push( + { + name: "size_t, release returns size_t", + acquire: library.declare("test_finalizer_acq_size_t", + ctypes.default_abi, + ctypes.size_t, + ctypes.size_t), + release: library.declare("test_finalizer_rel_size_t_return_size_t", + ctypes.default_abi, + ctypes.size_t, + ctypes.size_t), + compare: library.declare("test_finalizer_cmp_size_t", + ctypes.default_abi, + ctypes.bool, + ctypes.size_t, + ctypes.size_t), + status: status, + released: function released_eq(i, witness) { + return i == witness; + } + } + ); + samples.push( + { + name: "size_t, release returns myRECT", + acquire: library.declare("test_finalizer_acq_size_t", + ctypes.default_abi, + ctypes.size_t, + ctypes.size_t), + release: library.declare("test_finalizer_rel_size_t_return_struct_t", + ctypes.default_abi, + rect_t, + ctypes.size_t), + compare: library.declare("test_finalizer_cmp_size_t", + ctypes.default_abi, + ctypes.bool, + ctypes.size_t, + ctypes.size_t), + status: status, + released: function released_rect_eq(i, witness) { + return witness.top == i + && witness.bottom == i + && witness.left == i + && witness.right == i; + } + } + ); + samples.push( + { + name: "using null", + acquire: library.declare("test_finalizer_acq_null_t", + ctypes.default_abi, + ctypes.PointerType(ctypes.void_t), + ctypes.size_t), + release: library.declare("test_finalizer_rel_null_t", + ctypes.default_abi, + ctypes.void_t, + ctypes.PointerType(ctypes.void_t)), + status: library.declare("test_finalizer_null_resource_is_acquired", + ctypes.default_abi, + ctypes.bool, + ctypes.size_t), + compare: library.declare("test_finalizer_cmp_null_t", + ctypes.default_abi, + ctypes.bool, + ctypes.void_t.ptr, + ctypes.void_t.ptr), + released: released + } + ); + + let tester = new ResourceTester(start, stop); + samples.forEach( + function run_sample(sample) { + dump("Executing finalization test for data " + sample.name + "\n"); + tester.launch(TEST_SIZE, test_executing_finalizers, sample); + tester.launch(TEST_SIZE, test_do_not_execute_finalizers_on_referenced_stuff, sample); + tester.launch(TEST_SIZE, test_executing_dispose, sample); + tester.launch(TEST_SIZE, test_executing_forget, sample); + tester.launch(TEST_SIZE, test_result_dispose, sample); + dump("Successfully completed finalization test for data " + sample.name + "\n"); + } + ); + + /* + * Following test deactivated: Cycle collection never takes place + * (see bug 727371) + tester.launch(TEST_SIZE, test_cycles, samples[0]); + */ + dump("Successfully completed all finalization tests\n"); + library.close(); +} + +// If only I could have Promises to test this :) +// There is only so much we can do at this stage, +// if we want to avoid tests overlapping. +function test_cycles(size, tc) { + // Now, restart this with unreferenced cycles + for (i = 0; i < size/2; ++i) { + let a = { + a: ctypes.CDataFinalizer(tc.acquire(i*2), tc.release), + b: { + b: ctypes.CDataFinalizer(tc.acquire(i*2+1), tc.release) + } + }; + a.b.a = a; + } + do_test_pending(); + + Components.utils.schedulePreciseGC( + function after_gc() { + // Check that _something_ has been finalized + do_check_true(count_finalized(size, tc) > 0); + do_test_finished(); + } + ); + + do_timeout(10000, do_throw); +} + + +function count_finalized(size, tc) { + let finalizedItems = 0; + for (let i = 0; i < size; ++i) { + if (!tc.status(i)) { + ++finalizedItems; + } + } + return finalizedItems; +} + +/** + * Test: + * - that (some) finalizers are executed; + * - that no finalizer is executed twice (this is done on the C side). + */ +function test_executing_finalizers(size, tc, cleanup) +{ + dump("test_executing_finalizers " + tc.name + "\n"); + // Allocate |size| items without references + for (let i = 0; i < size; ++i) { + cleanup.add(ctypes.CDataFinalizer(tc.acquire(i), tc.release)); + } + trigger_gc(); // This should trigger some finalizations, hopefully all + + // Check that _something_ has been finalized + do_check_true(count_finalized(size, tc) > 0); +} + +/** + * Check that + * - |dispose| returns the proper result + */ +function test_result_dispose(size, tc, cleanup) { + dump("test_result_dispose " + tc.name + "\n"); + let ref = []; + // Allocate |size| items with references + for (let i = 0; i < size; ++i) { + let value = ctypes.CDataFinalizer(tc.acquire(i), tc.release); + cleanup.add(value); + ref.push(value); + } + do_check_eq(count_finalized(size, tc), 0); + + for (i = 0; i < size; ++i) { + let witness = ref[i].dispose(); + ref[i] = null; + if (!tc.released(i, witness)) { + do_print("test_result_dispose failure at index "+i); + do_check_true(false); + } + } + + do_check_eq(count_finalized(size, tc), size); +} + + +/** + * Check that + * - |dispose| is executed properly + * - finalizers are not executed after |dispose| + */ +function test_executing_dispose(size, tc, cleanup) +{ + dump("test_executing_dispose " + tc.name + "\n"); + let ref = []; + // Allocate |size| items with references + for (let i = 0; i < size; ++i) { + let value = ctypes.CDataFinalizer(tc.acquire(i), tc.release); + cleanup.add(value); + ref.push(value); + } + do_check_eq(count_finalized(size, tc), 0); + + // Dispose of everything and make sure that everything has been cleaned up + ref.forEach( + function dispose(v) { + v.dispose(); + } + ); + do_check_eq(count_finalized(size, tc), size); + + // Remove references + ref = []; + + // Re-acquire data and make sure that everything has been reinialized + for (i = 0; i < size; ++i) { + tc.acquire(i); + } + + do_check_eq(count_finalized(size, tc), 0); + + + // Attempt to trigger finalizations, ensure that they do not take place + trigger_gc(); + + do_check_eq(count_finalized(size, tc), 0); +} + + +/** + * Check that + * - |forget| does not dispose + * - |forget| has the right content + * - finalizers are not executed after |forget| + */ +function test_executing_forget(size, tc, cleanup) +{ + dump("test_executing_forget " + tc.name + "\n"); + let ref = []; + // Allocate |size| items with references + for (let i = 0; i < size; ++i) { + let original = tc.acquire(i); + let finalizer = ctypes.CDataFinalizer(original, tc.release); + ref.push( + { + original: original, + finalizer: finalizer + } + ); + cleanup.add(finalizer); + do_check_true(tc.compare(original, finalizer)); + } + do_check_eq(count_finalized(size, tc), 0); + + // Forget everything, making sure that we recover the original info + ref.forEach( + function compare_original_to_recovered(v) { + let original = v.original; + let recovered = v.finalizer.forget(); + // Note: Cannot use do_check_eq on Uint64 et al. + do_check_true(tc.compare(original, recovered)); + do_check_eq(original.constructor, recovered.constructor); + } + ); + + // Also make sure that we have not performed any clean up + do_check_eq(count_finalized(size, tc), 0); + + // Remove references + ref = []; + + // Attempt to trigger finalizations, ensure that they have no effect + trigger_gc(); + + do_check_eq(count_finalized(size, tc), 0); +} + + +/** + * Check that finalizers are not executed + */ +function test_do_not_execute_finalizers_on_referenced_stuff(size, tc, cleanup) +{ + dump("test_do_not_execute_finalizers_on_referenced_stuff " + tc.name + "\n"); + + let ref = []; + // Allocate |size| items without references + for (let i = 0; i < size; ++i) { + let value = ctypes.CDataFinalizer(tc.acquire(i), tc.release); + cleanup.add(value); + ref.push(value); + } + trigger_gc(); // This might trigger some finalizations, but it should not + + // Check that _nothing_ has been finalized + do_check_eq(count_finalized(size, tc), 0); +} + |