aboutsummaryrefslogtreecommitdiff
path: root/test/fpm_test/test_module_dependencies.f90
diff options
context:
space:
mode:
Diffstat (limited to 'test/fpm_test/test_module_dependencies.f90')
-rw-r--r--test/fpm_test/test_module_dependencies.f90666
1 files changed, 666 insertions, 0 deletions
diff --git a/test/fpm_test/test_module_dependencies.f90 b/test/fpm_test/test_module_dependencies.f90
new file mode 100644
index 0000000..f193646
--- /dev/null
+++ b/test/fpm_test/test_module_dependencies.f90
@@ -0,0 +1,666 @@
+!> Define tests for the `fpm_sources` module (module dependency checking)
+module test_module_dependencies
+ use testsuite, only : new_unittest, unittest_t, error_t, test_failed
+ use fpm_targets, only: targets_from_sources, resolve_module_dependencies, &
+ resolve_target_linking, build_target_t, build_target_ptr, &
+ FPM_TARGET_EXECUTABLE, FPM_TARGET_OBJECT, FPM_TARGET_ARCHIVE
+ use fpm_model, only: fpm_model_t, srcfile_t, &
+ FPM_UNIT_UNKNOWN, FPM_UNIT_PROGRAM, FPM_UNIT_MODULE, &
+ FPM_UNIT_SUBMODULE, FPM_UNIT_SUBPROGRAM, FPM_UNIT_CSOURCE, &
+ FPM_UNIT_CHEADER, FPM_SCOPE_UNKNOWN, FPM_SCOPE_LIB, &
+ FPM_SCOPE_DEP, FPM_SCOPE_APP, FPM_SCOPE_TEST
+ use fpm_strings, only: string_t, operator(.in.)
+ use fpm, only: check_modules_for_duplicates
+ implicit none
+ private
+
+ public :: collect_module_dependencies, operator(.in.)
+
+ interface operator(.in.)
+ module procedure target_in
+ end interface
+
+contains
+
+
+ !> Collect all exported unit tests
+ subroutine collect_module_dependencies(testsuite)
+
+ !> Collection of tests
+ type(unittest_t), allocatable, intent(out) :: testsuite(:)
+
+ testsuite = [ &
+ & new_unittest("library-module-use", test_library_module_use), &
+ & new_unittest("program-module-use", test_program_module_use), &
+ & new_unittest("program-with-module", test_program_with_module), &
+ & new_unittest("program-own-module-use", test_program_own_module_use), &
+ & new_unittest("missing-library-use", &
+ test_missing_library_use, should_fail=.true.), &
+ & new_unittest("missing-program-use", &
+ test_missing_program_use, should_fail=.true.), &
+ & new_unittest("invalid-library-use", &
+ test_invalid_library_use, should_fail=.true.), &
+ & new_unittest("package-with-no-duplicates", &
+ test_package_with_no_module_duplicates), &
+ & new_unittest("package-with-duplicates-in-same-source", &
+ test_package_module_duplicates_same_source, should_fail=.true.), &
+ & new_unittest("package-with-duplicates-in-one-package", &
+ test_package_module_duplicates_one_package, should_fail=.true.), &
+ & new_unittest("package-with-duplicates-in-two-packages", &
+ test_package_module_duplicates_two_packages, should_fail=.true.), &
+ & new_unittest("subdirectory-module-use", &
+ test_subdirectory_module_use), &
+ & new_unittest("invalid-subdirectory-module-use", &
+ test_invalid_subdirectory_module_use, should_fail=.true.) &
+ ]
+
+ end subroutine collect_module_dependencies
+
+
+ !> Check library module using another library module
+ subroutine test_library_module_use(error)
+
+ !> Error handling
+ type(error_t), allocatable, intent(out) :: error
+
+ type(fpm_model_t) :: model
+ type(build_target_ptr), allocatable :: targets(:)
+
+ model%output_directory = ''
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(2))
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1.f90", &
+ scope = FPM_SCOPE_LIB, &
+ provides=[string_t('my_mod_1')])
+
+ model%packages(1)%sources(2) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_2.f90", &
+ scope = FPM_SCOPE_LIB, &
+ provides=[string_t('my_mod_2')], &
+ uses=[string_t('my_mod_1')])
+
+ call targets_from_sources(targets,model,error)
+ if (allocated(error)) return
+
+ if (allocated(error)) then
+ return
+ end if
+ if (size(targets) /= 3) then
+ call test_failed(error,'Incorrect number of targets - expecting three')
+ return
+ end if
+
+ call check_target(targets(1)%ptr,type=FPM_TARGET_ARCHIVE,n_depends=2, &
+ deps = [targets(2),targets(3)], &
+ links = targets(2:3), error=error)
+
+ if (allocated(error)) return
+
+
+ call check_target(targets(2)%ptr,type=FPM_TARGET_OBJECT,n_depends=0, &
+ source=model%packages(1)%sources(1),error=error)
+
+ if (allocated(error)) return
+
+
+ call check_target(targets(3)%ptr,type=FPM_TARGET_OBJECT,n_depends=1, &
+ deps=[targets(2)],source=model%packages(1)%sources(2),error=error)
+
+ if (allocated(error)) return
+
+ end subroutine test_library_module_use
+
+
+ !> Check a program using a library module
+ !> Each program generates two targets: object file and executable
+ !>
+ subroutine test_program_module_use(error)
+
+ !> Error handling
+ type(error_t), allocatable, intent(out) :: error
+
+ call test_scope(FPM_SCOPE_APP,error)
+ if (allocated(error)) return
+
+ call test_scope(FPM_SCOPE_TEST,error)
+ if (allocated(error)) return
+
+ contains
+
+ subroutine test_scope(exe_scope,error)
+ integer, intent(in) :: exe_scope
+ type(error_t), allocatable, intent(out) :: error
+
+ integer :: i
+ type(fpm_model_t) :: model
+ type(build_target_ptr), allocatable :: targets(:)
+ character(:), allocatable :: scope_str
+
+ model%output_directory = ''
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(2))
+
+ scope_str = merge('FPM_SCOPE_APP ','FPM_SCOPE_TEST',exe_scope==FPM_SCOPE_APP)//' - '
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1.f90", &
+ scope = FPM_SCOPE_LIB, &
+ provides=[string_t('my_mod_1')])
+
+ model%packages(1)%sources(2) = new_test_source(FPM_UNIT_PROGRAM,file_name="app/my_program.f90", &
+ scope=exe_scope, &
+ uses=[string_t('my_mod_1')])
+
+ call targets_from_sources(targets,model,error)
+ if (allocated(error)) return
+
+ if (size(targets) /= 4) then
+ call test_failed(error,scope_str//'Incorrect number of targets - expecting three')
+ return
+ end if
+
+ call check_target(targets(1)%ptr,type=FPM_TARGET_ARCHIVE,n_depends=1, &
+ deps=[targets(2)],links=[targets(2)],error=error)
+
+ if (allocated(error)) return
+
+ call check_target(targets(2)%ptr,type=FPM_TARGET_OBJECT,n_depends=0, &
+ source=model%packages(1)%sources(1),error=error)
+
+ if (allocated(error)) return
+
+ call check_target(targets(3)%ptr,type=FPM_TARGET_OBJECT,n_depends=1, &
+ deps=[targets(2)],source=model%packages(1)%sources(2),error=error)
+
+ if (allocated(error)) return
+
+ call check_target(targets(4)%ptr,type=FPM_TARGET_EXECUTABLE,n_depends=2, &
+ deps=[targets(1),targets(3)], &
+ links=[targets(3)], error=error)
+
+ if (allocated(error)) return
+
+ end subroutine test_scope
+
+ end subroutine test_program_module_use
+
+
+ !> Check program with module in single source file
+ !> (Resulting target should not include itself as a dependency)
+ subroutine test_program_with_module(error)
+
+ !> Error handling
+ type(error_t), allocatable, intent(out) :: error
+
+ integer :: i
+ type(fpm_model_t) :: model
+ type(build_target_ptr), allocatable :: targets(:)
+
+ model%output_directory = ''
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(1))
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_PROGRAM,file_name="app/my_program.f90", &
+ scope = FPM_SCOPE_APP, &
+ provides=[string_t('app_mod')], &
+ uses=[string_t('app_mod')])
+
+ call targets_from_sources(targets,model,error)
+ if (allocated(error)) return
+
+ if (size(targets) /= 2) then
+ write(*,*) size(targets)
+ call test_failed(error,'Incorrect number of targets - expecting two')
+ return
+ end if
+
+ call check_target(targets(1)%ptr,type=FPM_TARGET_OBJECT,n_depends=0, &
+ source=model%packages(1)%sources(1),error=error)
+
+ if (allocated(error)) return
+
+ call check_target(targets(2)%ptr,type=FPM_TARGET_EXECUTABLE,n_depends=1, &
+ deps=[targets(1)],links=[targets(1)],error=error)
+
+ if (allocated(error)) return
+
+ end subroutine test_program_with_module
+
+
+ !> Check program using modules in same directory
+ subroutine test_program_own_module_use(error)
+
+ !> Error handling
+ type(error_t), allocatable, intent(out) :: error
+
+ call test_scope(FPM_SCOPE_APP,error)
+ if (allocated(error)) return
+
+ call test_scope(FPM_SCOPE_TEST,error)
+ if (allocated(error)) return
+
+ contains
+
+ subroutine test_scope(exe_scope,error)
+ integer, intent(in) :: exe_scope
+ type(error_t), allocatable, intent(out) :: error
+
+ type(fpm_model_t) :: model
+ type(build_target_ptr), allocatable :: targets(:)
+ character(:), allocatable :: scope_str
+
+ model%output_directory = ''
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(3))
+
+ scope_str = merge('FPM_SCOPE_APP ','FPM_SCOPE_TEST',exe_scope==FPM_SCOPE_APP)//' - '
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="app/app_mod1.f90", &
+ scope = exe_scope, &
+ provides=[string_t('app_mod1')])
+
+ model%packages(1)%sources(2) = new_test_source(FPM_UNIT_MODULE,file_name="app/app_mod2.f90", &
+ scope = exe_scope, &
+ provides=[string_t('app_mod2')],uses=[string_t('app_mod1')])
+
+ model%packages(1)%sources(3) = new_test_source(FPM_UNIT_PROGRAM,file_name="app/my_program.f90", &
+ scope=exe_scope, &
+ uses=[string_t('app_mod2')])
+
+ call targets_from_sources(targets,model,error)
+ if (allocated(error)) return
+
+ if (size(targets) /= 4) then
+ call test_failed(error,scope_str//'Incorrect number of targets - expecting three')
+ return
+ end if
+
+ call check_target(targets(1)%ptr,type=FPM_TARGET_OBJECT,n_depends=0, &
+ source=model%packages(1)%sources(1),error=error)
+
+ if (allocated(error)) return
+
+ call check_target(targets(2)%ptr,type=FPM_TARGET_OBJECT,n_depends=1, &
+ source=model%packages(1)%sources(2),deps=[targets(1)],error=error)
+
+ if (allocated(error)) return
+
+ call check_target(targets(3)%ptr,type=FPM_TARGET_OBJECT,n_depends=1, &
+ source=model%packages(1)%sources(3),deps=[targets(2)],error=error)
+
+ if (allocated(error)) return
+
+ call check_target(targets(4)%ptr,type=FPM_TARGET_EXECUTABLE,n_depends=1, &
+ deps=[targets(3)],links=targets(1:3), error=error)
+
+ if (allocated(error)) return
+
+ end subroutine test_scope
+ end subroutine test_program_own_module_use
+
+
+ !> Check missing library module dependency
+ subroutine test_missing_library_use(error)
+
+ !> Error handling
+ type(error_t), allocatable, intent(out) :: error
+
+ type(fpm_model_t) :: model
+ type(build_target_ptr), allocatable :: targets(:)
+
+ model%output_directory = ''
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(2))
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1.f90", &
+ scope = FPM_SCOPE_LIB, &
+ provides=[string_t('my_mod_1')])
+
+ model%packages(1)%sources(2) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_2.f90", &
+ scope = FPM_SCOPE_LIB, &
+ provides=[string_t('my_mod_2')], &
+ uses=[string_t('my_mod_3')])
+
+ call targets_from_sources(targets,model,error)
+
+ end subroutine test_missing_library_use
+
+
+ !> Check missing program module dependency
+ subroutine test_missing_program_use(error)
+
+ !> Error handling
+ type(error_t), allocatable, intent(out) :: error
+
+ type(fpm_model_t) :: model
+ type(build_target_ptr), allocatable :: targets(:)
+
+ model%output_directory = ''
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(2))
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1.f90", &
+ scope = FPM_SCOPE_LIB, &
+ provides=[string_t('my_mod_1')])
+
+ model%packages(1)%sources(2) = new_test_source(FPM_UNIT_PROGRAM,file_name="app/my_program.f90", &
+ scope=FPM_SCOPE_APP, &
+ uses=[string_t('my_mod_2')])
+
+ call targets_from_sources(targets,model,error)
+
+ end subroutine test_missing_program_use
+
+
+ !> Check library module using a non-library module
+ subroutine test_invalid_library_use(error)
+
+ !> Error handling
+ type(error_t), allocatable, intent(out) :: error
+
+ type(fpm_model_t) :: model
+ type(build_target_ptr), allocatable :: targets(:)
+
+ model%output_directory = ''
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(2))
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="app/app_mod.f90", &
+ scope = FPM_SCOPE_APP, &
+ provides=[string_t('app_mod')])
+
+ model%packages(1)%sources(2) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod.f90", &
+ scope = FPM_SCOPE_LIB, &
+ provides=[string_t('my_mod')], &
+ uses=[string_t('app_mod')])
+
+ call targets_from_sources(targets,model,error)
+
+ end subroutine test_invalid_library_use
+
+
+ !> Check program using a non-library module in a sub-directory
+ subroutine test_subdirectory_module_use(error)
+
+ !> Error handling
+ type(error_t), allocatable, intent(out) :: error
+
+ type(fpm_model_t) :: model
+ type(build_target_ptr), allocatable :: targets(:)
+
+ model%output_directory = ''
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(2))
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="app/subdir/app_mod.f90", &
+ scope = FPM_SCOPE_APP, &
+ provides=[string_t('app_mod')])
+
+ model%packages(1)%sources(2) = new_test_source(FPM_UNIT_PROGRAM,file_name="app/my_program.f90", &
+ scope=FPM_SCOPE_APP, &
+ uses=[string_t('app_mod')])
+
+ call targets_from_sources(targets,model,error)
+
+ end subroutine test_subdirectory_module_use
+
+ !> Check program with no duplicate modules
+ subroutine test_package_with_no_module_duplicates(error)
+
+ type(error_t), allocatable, intent(out) :: error
+
+ type(fpm_model_t) :: model
+ logical :: duplicates_found = .false.
+
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(2))
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1.f90", &
+ scope = FPM_SCOPE_LIB, provides=[string_t('my_mod_1')])
+
+ model%packages(1)%sources(2) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_2.f90", &
+ scope = FPM_SCOPE_LIB, provides=[string_t('my_mod_2')])
+
+ call check_modules_for_duplicates(model, duplicates_found)
+ if (duplicates_found) then
+ call test_failed(error,'Duplicate modules found')
+ return
+ end if
+ end subroutine test_package_with_no_module_duplicates
+
+ !> Check program with duplicate modules in same source file
+ subroutine test_package_module_duplicates_same_source(error)
+
+ type(error_t), allocatable, intent(out) :: error
+
+ type(fpm_model_t) :: model
+ logical :: duplicates_found
+
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(1))
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1.f90", &
+ scope = FPM_SCOPE_LIB, provides=[string_t('my_mod_1'), string_t('my_mod_1')])
+
+ call check_modules_for_duplicates(model, duplicates_found)
+ if (duplicates_found) then
+ call test_failed(error,'Duplicate modules found')
+ return
+ end if
+ end subroutine test_package_module_duplicates_same_source
+
+ !> Check program with duplicate modules in two different source files in one package
+ subroutine test_package_module_duplicates_one_package(error)
+
+ type(error_t), allocatable, intent(out) :: error
+
+ type(fpm_model_t) :: model
+ logical :: duplicates_found
+
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(2))
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1_a.f90", &
+ scope = FPM_SCOPE_LIB, provides=[string_t('my_mod_1')])
+
+ model%packages(1)%sources(2) = new_test_source(FPM_UNIT_MODULE,file_name="src/my_mod_1_b.f90", &
+ scope = FPM_SCOPE_LIB, provides=[string_t('my_mod_1')])
+
+ call check_modules_for_duplicates(model, duplicates_found)
+ if (duplicates_found) then
+ call test_failed(error,'Duplicate modules found')
+ return
+ end if
+ end subroutine test_package_module_duplicates_one_package
+
+ !> Check program with duplicate modules in two different packages
+ subroutine test_package_module_duplicates_two_packages(error)
+
+ type(error_t), allocatable, intent(out) :: error
+
+ type(fpm_model_t) :: model
+ logical :: duplicates_found
+
+ allocate(model%packages(2))
+ allocate(model%packages(1)%sources(1))
+ allocate(model%packages(2)%sources(1))
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/subdir1/my_mod_1.f90", &
+ scope = FPM_SCOPE_LIB, provides=[string_t('my_mod_1')])
+
+ model%packages(2)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="src/subdir2/my_mod_1.f90", &
+ scope = FPM_SCOPE_LIB, provides=[string_t('my_mod_1')])
+
+ call check_modules_for_duplicates(model, duplicates_found)
+ if (duplicates_found) then
+ call test_failed(error,'Duplicate modules found')
+ return
+ end if
+ end subroutine test_package_module_duplicates_two_packages
+
+ !> Check program using a non-library module in a differente sub-directory
+ subroutine test_invalid_subdirectory_module_use(error)
+
+ !> Error handling
+ type(error_t), allocatable, intent(out) :: error
+
+ type(fpm_model_t) :: model
+ type(build_target_ptr), allocatable :: targets(:)
+
+ model%output_directory = ''
+ allocate(model%packages(1))
+ allocate(model%packages(1)%sources(2))
+
+ model%packages(1)%sources(1) = new_test_source(FPM_UNIT_MODULE,file_name="app/diff_dir/app_mod.f90", &
+ scope = FPM_SCOPE_APP, &
+ provides=[string_t('app_mod')])
+
+ model%packages(1)%sources(2) = new_test_source(FPM_UNIT_PROGRAM,file_name="app/prog_dir/my_program.f90", &
+ scope=FPM_SCOPE_APP, &
+ uses=[string_t('app_mod')])
+
+ call targets_from_sources(targets,model,error)
+
+ end subroutine test_invalid_subdirectory_module_use
+
+ !> Helper to create a new srcfile_t
+ function new_test_source(type,file_name, scope, uses, provides) result(src)
+ integer, intent(in) :: type
+ character(*), intent(in) :: file_name
+ integer, intent(in) :: scope
+ type(string_t), intent(in), optional :: uses(:)
+ type(string_t), intent(in), optional :: provides(:)
+ type(srcfile_t) :: src
+
+ src%file_name = file_name
+ src%unit_scope = scope
+ src%unit_type = type
+
+ if (present(provides)) then
+ src%modules_provided = provides
+ else
+ allocate(src%modules_provided(0))
+ end if
+
+ if (present(uses)) then
+ src%modules_used = uses
+ else
+ allocate(src%modules_used(0))
+ end if
+
+ allocate(src%include_dependencies(0))
+
+ end function new_test_source
+
+
+ !> Helper to check an expected output target
+ subroutine check_target(target,type,n_depends,deps,links,source,error)
+ type(build_target_t), intent(in) :: target
+ integer, intent(in) :: type
+ integer, intent(in) :: n_depends
+ type(srcfile_t), intent(in), optional :: source
+ type(build_target_ptr), intent(in), optional :: deps(:)
+ type(build_target_ptr), intent(in), optional :: links(:)
+ type(error_t), intent(out), allocatable :: error
+
+ integer :: i
+
+ if (target%target_type /= type) then
+ call test_failed(error,'Unexpected target_type for target "'//target%output_file//'"')
+ return
+ end if
+
+ if (size(target%dependencies) /= n_depends) then
+ call test_failed(error,'Wrong number of dependencies for target "'//target%output_file//'"')
+ return
+ end if
+
+ if (present(deps)) then
+
+ do i=1,size(deps)
+
+ if (.not.(deps(i)%ptr .in. target%dependencies)) then
+ call test_failed(error,'Missing dependency ('//deps(i)%ptr%output_file//&
+ ') for target "'//target%output_file//'"')
+ return
+ end if
+
+ end do
+
+ end if
+
+ if (present(links)) then
+
+ do i=1,size(links)
+
+ if (.not.(links(i)%ptr%output_file .in. target%link_objects)) then
+ call test_failed(error,'Missing object ('//links(i)%ptr%output_file//&
+ ') for executable "'//target%output_file//'"')
+ return
+ end if
+
+ end do
+
+ if (size(links) > size(target%link_objects)) then
+
+ call test_failed(error,'There are missing link objects for target "'&
+ //target%output_file//'"')
+ return
+
+ elseif (size(links) < size(target%link_objects)) then
+
+ call test_failed(error,'There are more link objects than expected for target "'&
+ //target%output_file//'"')
+ return
+
+ end if
+
+ end if
+
+ if (present(source)) then
+
+ if (allocated(target%source)) then
+ if (target%source%file_name /= source%file_name) then
+ call test_failed(error,'Incorrect source ('//target%source%file_name//') for target "'//&
+ target%output_file//'"'//new_line('a')//' expected "'//source%file_name//'"')
+ return
+ end if
+
+ else
+ call test_failed(error,'Expecting source for target "'//target%output_file//'" but none found')
+ return
+ end if
+
+ else
+
+ if (allocated(target%source)) then
+ call test_failed(error,'Found source ('//target%source%file_name//') for target "'//&
+ target%output_file//'" but none expected')
+ return
+ end if
+
+ end if
+
+ end subroutine check_target
+
+
+ !> Helper to check if a build target is in a list of build_target_ptr
+ logical function target_in(needle,haystack)
+ type(build_target_t), intent(in), target :: needle
+ type(build_target_ptr), intent(in) :: haystack(:)
+
+ integer :: i
+
+ target_in = .false.
+ do i=1,size(haystack)
+
+ if (associated(haystack(i)%ptr,needle)) then
+ target_in = .true.
+ return
+ end if
+
+ end do
+
+ end function target_in
+
+
+end module test_module_dependencies