Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #235 allocatable classes #236

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ build
*.mod
*.a
*.so
*.x
f90wrap*.f90
*.pyc
.pydevproject
Expand All @@ -20,3 +21,4 @@ src.*
.ipynb_checkpoints
.idea/
*.swp
itest/
5 changes: 4 additions & 1 deletion examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ EXAMPLES = arrayderivedtypes \
intent_out_size \
output_kind \
remove_pointer_arg \
fortran_oo
fortran_oo \
issue206_subroutine_oldstyle \
issue227_allocatable \
issue235_allocatable_classes

PYTHON = python

Expand Down
65 changes: 26 additions & 39 deletions examples/issue227_allocatable/run.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,41 @@
#!/usr/bin/env python
import os
import unittest
import gc
import tracemalloc

import itest


def main():
test_type_output_is_wrapped()
test_intrinsic_output_is_not_wrapped()
test_array_output_is_not_wrapped()
test_type_output_wrapper()
test_memory_leak()


def test_type_output_is_wrapped():
assert hasattr(itest.alloc_output, 'alloc_output_type_func')


def test_intrinsic_output_is_not_wrapped():
assert (not hasattr(itest.alloc_output, 'alloc_output_intrinsic_func'))


def test_array_output_is_not_wrapped():
assert (not hasattr(itest.alloc_output, 'alloc_output_array_func'))


VAL = 10.0
TOL = 1e-13

class TestAllocOutput(unittest.TestCase):

def test_type_output_is_wrapped(self):
self.assertTrue(hasattr(itest.alloc_output, 'alloc_output_type_func'))

def test_type_output_wrapper():
t = itest.alloc_output.alloc_output_type_func(VAL)
assert(abs(t.a - VAL) < TOL)
def test_intrinsic_output_is_not_wrapped(self):
self.assertFalse(hasattr(itest.alloc_output, 'alloc_output_intrinsic_func'))

def test_array_output_is_not_wrapped(self):
self.assertFalse(hasattr(itest.alloc_output, 'alloc_output_array_func'))

def test_memory_leak():
gc.collect()
t = []
tracemalloc.start()
start_snapshot = tracemalloc.take_snapshot()
for i in range(2048):
t.append(itest.alloc_output.alloc_output_type_func(VAL))
del t
gc.collect()
end_snapshot = tracemalloc.take_snapshot()
tracemalloc.stop()
stats = end_snapshot.compare_to(start_snapshot, 'lineno')
assert sum(stat.size_diff for stat in stats) < 1024
def test_type_output_wrapper(self):
t = itest.alloc_output.alloc_output_type_func(VAL)
self.assertAlmostEqual(t.a, VAL, delta=TOL)

def test_memory_leak(self):
gc.collect()
t = []
tracemalloc.start()
start_snapshot = tracemalloc.take_snapshot()
for i in range(8192):
t.append(itest.alloc_output.alloc_output_type_func(VAL))
del t
gc.collect()
end_snapshot = tracemalloc.take_snapshot()
tracemalloc.stop()
stats = end_snapshot.compare_to(start_snapshot, 'lineno')
self.assertLess(sum(stat.size_diff for stat in stats), 4096)

if __name__ == '__main__':
main()
unittest.main()
25 changes: 25 additions & 0 deletions examples/issue235_allocatable_classes/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FC = gfortran
FCFLAGS = -fPIC
PYTHON = python

all: wrapper

test: wrapper
$(PYTHON) run.py

wrapper: f90wrapper mytype.o myclass.o myclass_factory.o
$(PYTHON) -m f90wrap --f2py-f90wrap --build-dir . -c -m _itest --opt="-O0 -g" \
f90wrap_mytype.f90 f90wrap_myclass.f90 f90wrap_myclass_factory.f90 \
mytype.o myclass.o myclass_factory.o --lower

f90wrapper: mytype.f90 myclass.f90 myclass_factory.f90
$(PYTHON) -m f90wrap -m itest mytype.f90 myclass.f90 myclass_factory.f90 -v

%.o : %.f90
$(FC) $(FCFLAGS) -c -g -O0 $< -o $@

clean:
rm -f *.o f90wrap*.f90 *.so *.mod
rm -rf src.*/
rm -rf itest/
-rm -rf src.*/ .f2py_f2cmap .libs/ __pycache__/
6 changes: 6 additions & 0 deletions examples/issue235_allocatable_classes/Makefile.meson
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
include ../make.meson.inc

NAME := itest

test: build
$(PYTHON) run.py
39 changes: 39 additions & 0 deletions examples/issue235_allocatable_classes/myclass.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module myclass

implicit none

integer :: create_count = 0
integer :: destroy_count = 0

type :: myclass_t
real :: val
contains
procedure :: get_val => myclass_get_val
procedure :: set_val => myclass_set_val
final :: myclass_destroy
end type myclass_t

contains

subroutine myclass_get_val(self, val)
class(myclass_t), intent(in) :: self
real, intent(out) :: val

val = self%val
end subroutine myclass_get_val

subroutine myclass_set_val(self, val)
class(myclass_t), intent(inout) :: self
real, intent(in) :: val

self%val = val
end subroutine myclass_set_val

subroutine myclass_destroy(self)
type(myclass_t), intent(inout) :: self

destroy_count = destroy_count + 1
print *, 'Destroying class_t with val = ', self%val
end subroutine myclass_destroy

end module myclass
18 changes: 18 additions & 0 deletions examples/issue235_allocatable_classes/myclass_factory.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module myclass_factory

use myclass, only: myclass_t, create_count
implicit none

contains

function myclass_create(val) result(myobject)
class(myclass_t), allocatable :: myobject
real, intent(in) :: val

allocate(myclass_t :: myobject)
call myobject%set_val(val)
create_count = create_count + 1

end function myclass_create

end module myclass_factory
31 changes: 31 additions & 0 deletions examples/issue235_allocatable_classes/mytype.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module mytype

implicit none

integer :: create_count = 0
integer :: destroy_count = 0

type :: mytype_t
real :: val
contains
final :: mytype_destroy
end type mytype_t

contains

function mytype_create(val) result(self)
type(mytype_t) :: self
real, intent(in) :: val

self%val = val
create_count = create_count + 1
end function mytype_create

subroutine mytype_destroy(self)
type(mytype_t), intent(inout) :: self

destroy_count = destroy_count + 1
print *, 'Destroying mytype_t with val = ', self%val
end subroutine mytype_destroy

end module mytype
83 changes: 83 additions & 0 deletions examples/issue235_allocatable_classes/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python
import unittest
from itest import mytype, myclass, myclass_factory

REF = 3.1415
TOL = 1.0e-6

class TestMyType(unittest.TestCase):

def test_create_destroy_type_object(self):
"""Object creation and destruction should happen only once."""
mytype.create_count = 0
mytype.destroy_count = 0

obj = mytype.mytype_create(REF)

self.assertEqual(mytype.create_count, 1)

self.assertTrue(abs(obj.val - REF) < TOL)

del obj

self.assertEqual(mytype.create_count, 1)
self.assertGreaterEqual(mytype.destroy_count, 1)

def test_type_member_access(self):
"""Direct access of member variables."""
obj = mytype.mytype_create(REF)

self.assertTrue(abs(obj.val - REF) < TOL)

obj.val = 2.0 * REF

self.assertTrue(abs(obj.val - 2.0 * REF) < TOL)

del obj


class TestMyClass(unittest.TestCase):

def test_create_destroy_class_object(self):
"""Object creation and destruction should happen only once."""
myclass.create_count = 0
myclass.destroy_count = 0

obj = myclass_factory.myclass_create(REF)

self.assertEqual(myclass.create_count, 1)

self.assertTrue(abs(obj.get_val() - REF) < TOL)

del obj

self.assertEqual(myclass.create_count, 1)
self.assertGreaterEqual(myclass.destroy_count, 1)

def test_class_getter_setter(self):
"""Getters and setters defined in Fortran should work."""
obj = myclass_factory.myclass_create(REF)

self.assertTrue(abs(obj.get_val() - REF) < TOL)

obj.set_val(2.0 * REF)

self.assertTrue(abs(obj.get_val() - 2.0 * REF) < TOL)

del obj

def test_class_member_access(self):
"""Direct access of member variables."""
obj = myclass_factory.myclass_create(REF)

self.assertTrue(abs(obj.val - REF) < TOL)

obj.val = 2.0 * REF

self.assertTrue(abs(obj.val - 2.0 * REF) < TOL)

del obj


if __name__ == "__main__":
unittest.main()
Loading
Loading