diff --git a/tests/foo.cc b/tests/foo.cc index f93eb08..73b7120 100644 --- a/tests/foo.cc +++ b/tests/foo.cc @@ -26,18 +26,6 @@ int get_int_from_float(double from_float, int multiplier) return (int) from_float*multiplier; } -char *return_c_string_to_be_freed() -{ - char *test = (char *)malloc(12 * sizeof(char)); - strcpy(test, "testingonly"); - return test; -} - -ToBeFreed *return_class_to_be_freed() -{ - return new ToBeFreed(); -} - std::string SomeObject::staticData = std::string("Hello Static World!"); SomeObject::~SomeObject () @@ -568,3 +556,46 @@ test_args_kwargs(const char *args, const char *kwargs) return (int) (kwargs - args); } +ToBeFreed::ToBeFreed(int size) { + m_size = size; + m = return_c_string_to_be_freed(size); +} + +ToBeFreed::~ToBeFreed() { + free(m); + m = nullptr; +} + + ToBeFreed::ToBeFreed(const ToBeFreed& from) { + m_size = from.m_size; + m = return_c_string_to_be_freed(m_size); + } + +char *ToBeFreed::value() { + return m; +} + +char *return_c_string_to_be_freed(int size) +{ + char *test = (char *)malloc(size * sizeof(char)); + strcpy(test, "testingonly"); + return test; +} + +ToBeFreed * +return_class_to_be_freed(int size) +{ + return new ToBeFreed(size); +} + +char * +return_c_string_to_not_be_freed(int size) +{ + return return_c_string_to_be_freed(size); +} + +ToBeFreed * +return_class_to_not_be_freed(int size) +{ + return new ToBeFreed(size); +} \ No newline at end of file diff --git a/tests/foo.h b/tests/foo.h index 698ff54..c65e197 100644 --- a/tests/foo.h +++ b/tests/foo.h @@ -1370,20 +1370,27 @@ private: }; // -#- name=return_c_string_to_be_freed; @return(free_after_copy=true) -#- -char *return_c_string_to_be_freed(); +char *return_c_string_to_be_freed(int size); + +// -#- name=return_c_string_to_not_be_freed; @return(free_after_copy=false) -#- +char *return_c_string_to_not_be_freed(int size); class ToBeFreed { public: - ToBeFreed() { m = return_c_string_to_be_freed(); } - ~ToBeFreed() { free(m); } - + ToBeFreed(int size); + ~ToBeFreed(); + ToBeFreed(const ToBeFreed&); + char *value(); private: char *m; + int m_size; }; // -#- name=return_class_to_be_freed; @return(free_after_copy=true) -#- -ToBeFreed *return_class_to_be_freed(); +ToBeFreed *return_class_to_be_freed(int size); +// -#- name=return_class_to_not_be_freed; @return(free_after_copy=false) -#- +ToBeFreed *return_class_to_not_be_freed(int size); #endif /* !FOO_H_ */ diff --git a/tests/foomodulegen.py b/tests/foomodulegen.py index 4398430..1861d94 100755 --- a/tests/foomodulegen.py +++ b/tests/foomodulegen.py @@ -116,14 +116,22 @@ int %s::custom_method_added_by_a_hook(int x) ## test free_after_copy. mod.add_function('return_c_string_to_be_freed', - ReturnValue.new('char *', free_after_copy=True ), - []) + ReturnValue.new('char *', free_after_copy=True), + [Parameter.new('int', 'size')]) + mod.add_function('return_c_string_to_not_be_freed', + ReturnValue.new('char *', free_after_copy=False), + [Parameter.new('int', 'size')]) ToBeFreed = mod.add_class('ToBeFreed') + ToBeFreed.add_constructor([Parameter.new('int', 'size')]) ToBeFreed.add_copy_constructor() + ToBeFreed.add_method('value', ReturnValue.new('char *'), []) mod.add_function('return_class_to_be_freed', ReturnValue.new('ToBeFreed *', free_after_copy=True), - []) + [Parameter.new('int', 'size')]) + mod.add_function('return_class_to_not_be_freed', + ReturnValue.new('ToBeFreed *', free_after_copy=False), + [Parameter.new('int', 'size')]) SomeObject = mod.add_class('SomeObject', allow_subclassing=True) diff --git a/tests/footest.py b/tests/footest.py index b59d271..8619d8c 100644 --- a/tests/footest.py +++ b/tests/footest.py @@ -4,6 +4,8 @@ import weakref import gc import os.path import copy +import resource + sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'build', 'tests')) @@ -1536,7 +1538,7 @@ class TestFoo(unittest.TestCase): value = x.ReturnMyAStruct() self.assertEqual(value.a, 123) - def test_free_after_copy_string(self): + def test_free_after_copy_string_examine_code(self): seen_free_comment = False seen_free = False seen_delete_comment = False @@ -1556,5 +1558,42 @@ class TestFoo(unittest.TestCase): self.assertTrue(seen_free) self.assertTrue(seen_delete) + def test_free_after_copy(self): + v = foo.return_c_string_to_be_freed(20) + self.assertEqual(v, "testingonly") + c = foo.return_class_to_be_freed(20) + self.assertEqual(c.value(), "testingonly") + + + def _runner(self, n, size, fn): + while gc.collect(): + pass + before = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + for i in range(n): + fn(size) + + while gc.collect(): + pass + after = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + # maxrss is in kB. + return (after - before) * 1024 + + def test_free_after_copy_leak(self): + n = 200 + size = 1024*32 + # run once to bump up overall maxrss if's going to be bumped up. + self._runner(n, size, foo.return_c_string_to_be_freed) + diff = self._runner(n, size, foo.return_c_string_to_be_freed) + #diff += self._runner(n, size, foo.return_class_to_be_freed) + # maxrss should only grow marginally here. + self.assertTrue(diff < (size*2)) + + leaky = self._runner(n, size, foo.return_c_string_to_not_be_freed) + leaky += self._runner(n, size, foo.return_class_to_not_be_freed) + # maxrss should have grown significantly + self.assertTrue(diff < leaky) + self.assertTrue(leaky > size * 10) + + if __name__ == '__main__': unittest.main()