Skip to content

Commit f0fadea

Browse files
authored
Merge pull request #20 from UCL-ARC/test-drive-mesh-generator
Test drive mesh generator
2 parents 12b6c56 + 23b9db8 commit f0fadea

File tree

14 files changed

+338
-267
lines changed

14 files changed

+338
-267
lines changed

CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ project(
88
DESCRIPTION "Fortran tooling"
99
)
1010

11-
set(GFORTRANFLAGS "-ffree-line-length-none")
11+
set(GFORTRAN_FLAGS "-ffree-line-length-none")
1212
set(CMAKE_Fortran_FLAGS "${GFORTRAN_FLAGS}") # Update this to match the flags for the chosen compiler
1313

1414
# Define src files
@@ -19,3 +19,10 @@ file(GLOB PROJ_POISSON_SOURCES "${PROJ_SRC_DIR}/poisson/*.f90")
1919
# Build src executables
2020
add_executable("${PROJECT_NAME}-mesh-generator" "${PROJ_MESH_GENERATOR_SOURCES}")
2121
add_executable("${PROJECT_NAME}-poisson" "${PROJ_POISSON_SOURCES}")
22+
23+
enable_testing()
24+
25+
#--------------------------------------#
26+
# test-drive #
27+
#--------------------------------------#
28+
add_subdirectory("testing/test-drive")

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,7 @@ This will produce executables for the two src codes, `fortran-tooling-mesh-gener
5858
```sh
5959
./build/fortran-tooling-poisson # then respond to prompt with the mesh name, likely to be `square_mesh`
6060
```
61+
62+
## Running the tests
63+
64+
To run the tests run `ctest` from within the `build` directory.

src/mesh_generator/mesh_generator.f90

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ subroutine calculate_mesh(num_edges_per_boundary, num_nodes, num_elements, num_b
6969
counter = 1
7070
do i = 1, num_nodes_per_boundary
7171
do j = 1, num_nodes_per_boundary
72-
nodes(1, counter) = i
73-
nodes(2, counter) = j
72+
nodes(1, counter) = i ! x coordinate
73+
nodes(2, counter) = j ! y coordinate
7474
counter = counter + 1
7575
end do
7676
end do
@@ -80,9 +80,9 @@ subroutine calculate_mesh(num_edges_per_boundary, num_nodes, num_elements, num_b
8080
do j = 1, num_edges_per_boundary
8181
bottom_left_node = j + (i - 1) * num_nodes_per_boundary
8282

83-
elements(1, counter) = bottom_left_node
84-
elements(2, counter) = bottom_left_node + 1
85-
elements(3, counter) = bottom_left_node + 1 + num_nodes_per_boundary
83+
elements(1, counter) = bottom_left_node ! bottom left node
84+
elements(2, counter) = bottom_left_node + 1 ! Next node anti-clockwise
85+
elements(3, counter) = bottom_left_node + 1 + num_nodes_per_boundary ! Next node anti-clockwise
8686

8787
elements(1, counter + num_edges_per_boundary) = bottom_left_node
8888
elements(2, counter + num_edges_per_boundary) = bottom_left_node + num_nodes_per_boundary + 1
@@ -98,7 +98,7 @@ subroutine calculate_mesh(num_edges_per_boundary, num_nodes, num_elements, num_b
9898
! bottom boundary
9999
boundary_edges(1, i) = i ! left node
100100
boundary_edges(2, i) = i + 1 ! right node
101-
boundary_edges(3, i) = i*2 - 1 ! element
101+
boundary_edges(3, i) = i ! element
102102

103103
! right boundary
104104
boundary_edges(1, i + num_edges_per_boundary) = i * num_nodes_per_boundary
@@ -108,7 +108,7 @@ subroutine calculate_mesh(num_edges_per_boundary, num_nodes, num_elements, num_b
108108
! top boundary
109109
boundary_edges(1, i + num_edges_per_boundary * 2) = num_nodes - i + 1
110110
boundary_edges(2, i + num_edges_per_boundary * 2) = num_nodes - i
111-
boundary_edges(2, i + num_edges_per_boundary * 2) = num_elements - i + 1
111+
boundary_edges(3, i + num_edges_per_boundary * 2) = num_elements - i + 1
112112

113113
! left boundary
114114
boundary_edges(1, i + num_edges_per_boundary * 3) = (num_nodes_per_boundary - i) * num_nodes_per_boundary + 1

testing/README.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
# fortran-unit-testing
2-
Fortran code bases often have next to zero test coverage. Any tests implemented are fully end to end and often just check that a single value in some
3-
output file is what we expect it to be. Whilst a test like this can catch a breaking change or bug, it will be unlikely to indicate where that
4-
breaking change has been introduced. The solution to this issue is unit testing.
2+
Fortran code bases often have next to zero test coverage. Any tests implemented are fully end to end and often just check that a single value in some output file is what we expect it to be. Whilst a test like this can catch a breaking change or bug, it will be unlikely to indicate where that breaking change has been introduced. The solution to this issue is unit testing.
53

6-
There are several tools/frameworks written for unit testing Fortran code. However, these are not widely adopted by research codebases. We would like
7-
to understand why that is and help to change it.
4+
There are several tools/frameworks written for unit testing Fortran code. However, these are not widely adopted by research codebases. We would like to understand why that is and help to change it.
85

96
There are several examples of good unit testing tools for other languages, such as
107

118
- pytest (Python)
129
- QtTest (Qt in c++)
1310
- J-Unit (JavaScript)
1411

15-
These will be used as the basis for what the recommended Fortran unit testing tool should look like. Therefore, key features from these tools shall be
16-
individually tested for each Fortran unit testing tool we select to test.
12+
These will be used as the basis for what the recommended Fortran unit testing tool should look like. Therefore, key features from these tools shall be individually tested for each Fortran unit testing tool we select to test.
1713

1814
## Aims
1915
- Test multiple fortran unit testing tools to determine which we would recommend. To do this we will
File renamed without changes.

testing/src/calc_pi.f90

Lines changed: 0 additions & 60 deletions
This file was deleted.

testing/src/main.f90

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,4 @@
1-
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
2-
3-
# Set project name
4-
project(
5-
"fortran-unit-testing-with-test-drive"
6-
LANGUAGES "Fortran"
7-
VERSION "0.0.1"
8-
DESCRIPTION "Playground project for testing test-drive"
9-
)
10-
11-
# Get test-drive lib
1+
# Install the test-drive dependency
122
set(test_lib "test-drive")
133
set(test_pkg "TEST_DRIVE")
144
set(test_url "https://github.com/fortran-lang/test-drive")
@@ -32,42 +22,43 @@ if(NOT EXISTS "${${test_pkg}_BINARY_DIR}/include")
3222
make_directory("${${test_pkg}_BINARY_DIR}/include")
3323
endif()
3424

35-
# Define src and test files
36-
set(PROJ_SRC_DIR "${PROJECT_SOURCE_DIR}/../../src")
37-
set(PROJ_TEST_DIR "${PROJECT_SOURCE_DIR}")
38-
39-
file(GLOB PROJ_SOURCES "${PROJ_SRC_DIR}/*.f90")
40-
file(GLOB PROJ_TEST_SOURCES "${PROJ_TEST_DIR}/tests/*.f90")
25+
# Define test files
26+
set(PROJ_TEST_DRIVE_DIR "${PROJECT_SOURCE_DIR}/testing/test-drive")
27+
file(GLOB PROJ_TEST_DRIVE_SOURCES "${PROJ_TEST_DRIVE_DIR}/tests/*.f90")
4128

42-
# Filter out the main.cxx file. We can only have one main() function in our tests
43-
set(PROJ_SOURCES_EXC_MAIN ${PROJ_SOURCES})
44-
list(FILTER PROJ_SOURCES_EXC_MAIN EXCLUDE REGEX ".*main.f90")
45-
46-
# Build src executable
47-
add_executable("${PROJECT_NAME}" "${PROJ_SOURCES}")
29+
# Filter out the main.f90 files. We can only have one main() function in our tests
30+
set(PROJ_POISSON_SOURCES_EXC_MAIN ${PROJ_POISSON_SOURCES})
31+
set(PROJ_MESH_GENERATOR_SOURCES_EXC_MAIN ${PROJ_MESH_GENERATOR_SOURCES})
32+
list(FILTER PROJ_POISSON_SOURCES_EXC_MAIN EXCLUDE REGEX ".*main.f90")
33+
list(FILTER PROJ_MESH_GENERATOR_SOURCES_EXC_MAIN EXCLUDE REGEX ".*main.f90")
4834

4935
# Unit testing
5036
set(
5137
tests
52-
"calc_pi"
38+
"mesh_generator"
39+
)
40+
set(
41+
test-srcs
42+
"main.f90"
5343
)
5444
foreach(t IN LISTS tests)
5545
string(MAKE_C_IDENTIFIER ${t} t)
56-
list(APPEND test-srcs "${PROJ_TEST_DIR}/test_${t}.f90")
46+
list(APPEND test-srcs "${PROJ_TEST_DRIVE_DIR}/test_${t}.f90")
5747
endforeach()
5848

5949
add_executable(
60-
"${PROJECT_NAME}-tester"
61-
"${PROJ_TEST_DIR}/main.f90"
62-
"${PROJ_TEST_SOURCES}"
63-
"${PROJ_SOURCES_EXC_MAIN}"
50+
"test_${PROJECT_NAME}-test-drive"
51+
"${test-srcs}"
52+
"${PROJ_TEST_DRIVE_SOURCES}"
53+
"${PROJ_POISSON_SOURCES_EXC_MAIN}"
54+
"${PROJ_MESH_GENERATOR_SOURCES_EXC_MAIN}"
6455
)
6556
target_link_libraries(
66-
"${PROJECT_NAME}-tester"
57+
"test_${PROJECT_NAME}-test-drive"
6758
PRIVATE
6859
"test-drive::test-drive"
6960
)
7061

7162
foreach(t IN LISTS tests)
72-
add_test("${PROJECT_NAME}/${t}" "${PROJECT_NAME}-tester" "${t}")
63+
add_test("${PROJECT_NAME}-test-drive/${t}" "test_${PROJECT_NAME}-test-drive" "${t}")
7364
endforeach()

testing/test-drive/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# test-drive
2+
This project offers a lightweight, procedural unit testing framework based on nothing but standard Fortran. Integration with [meson](https://mesonbuild.com/), [cmake](https://cmake.org/) and [Fortran package manager (fpm)](https://github.com/fortran-lang/fpm) is available. Alternatively, the testdrive.F90 source file can be redistributed in the project's testsuite as well.
3+
4+
## Running the tests
5+
6+
The test-drive tests will run with the rest of the [tests](../README.md#running-the-tests) in the repo.
7+
8+
There is a known issue that the tests for test-drive itself will also be ran by `ctest` as shown below
9+
```sh
10+
$ ctest
11+
Test project /Users/connoraird/work/fortran-tooling/build
12+
Start 1: fortran-tooling-test-drive/mesh_generator
13+
1/4 Test #1: fortran-tooling-test-drive/mesh_generator ... Passed 0.33 sec
14+
Start 2: test-drive/all-tests
15+
2/4 Test #2: test-drive/all-tests ........................ Passed 0.33 sec
16+
Start 3: test-drive/check
17+
3/4 Test #3: test-drive/check ............................ Passed 0.01 sec
18+
Start 4: test-drive/select
19+
4/4 Test #4: test-drive/select ........................... Passed 0.01 sec
20+
21+
100% tests passed, 0 tests failed out of 4
22+
23+
Total Test time (real) = 0.69 sec
24+
```
25+
26+
27+
## Features matrix
28+
29+
Compilers tested: gfortran (homebrew)
30+
31+
| Feature | Implemented natively | Implemented manually |
32+
|---------|----------------------|----------------------|
33+
| Can run individual tests | No | Yes, see [main.f90](./main.f90). However, this requires running the test executable directly without ctest. |
34+
| Mocking | No | Not implemented |
35+
| Stubbing | No | Not implemented |
36+
| Data driven tests | No | Yes, see verify_calculate_mesh_parameters and verify_calculate_mesh in [test_mesh_generator.f90](./test_mesh_generator.f90)
37+
| Coverage report | Yes, with fpm | N/A |
38+
| Skip tests | Yes, see test_skip_example in [test_calc_pi.f90](./test_mesh_generator.f90) | N/A |
39+
40+
## Pros
41+
- Lightweight, procedural unit testing framework based on nothing but standard Fortran.
42+
- Since test-drive is written is standard fortran, any customisation we implement could be carried over to the open-source test-drive code.
43+
44+
## Cons
45+
- Slow at releasing. Last release was in 2021 (3 years ago).
46+
- Most standard test festures such as test paramaterisation and mocking are not provided by the test-drive library.
47+
- A fair amount of boiler plate code is required to get things up and running.
48+
49+
## Building
50+
- `fpm` provided a very convenient way to [get started](https://fpm.fortran-lang.org/tutorial/dependencies.html#adding-a-testing-framework) with `test-drive`. However, it is unlikely that a long running Fortran project written in Fortran 95, for example, will be using `fpm`. It is more likely that `cmake` is in use.
51+
- `cmake` can be used to install
52+
53+
## Resources
54+
- Repository: https://github.com/fortran-lang/test-drive

testing/tests/test-drive/main.f90 renamed to testing/test-drive/main.f90

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ program tester
44
use testdrive, only : run_testsuite, new_testsuite, testsuite_type, &
55
& select_suite, run_selected, get_argument
66

7-
use test_cal_pi, only : collect_calc_pi_suite
7+
use test_mesh_generator, only : collect_mesh_generator_testsuite
88

99
implicit none
1010

@@ -16,7 +16,7 @@ program tester
1616
stat = 0
1717

1818
testsuites = [ &
19-
new_testsuite("suite1", collect_calc_pi_suite) &
19+
new_testsuite("mesh_generator", collect_mesh_generator_testsuite) &
2020
]
2121

2222
call get_argument(1, suite_name)
@@ -25,27 +25,27 @@ program tester
2525
if (allocated(suite_name)) then
2626
is = select_suite(testsuites, suite_name)
2727
if (is > 0 .and. is <= size(testsuites)) then
28-
if (allocated(test_name)) then
29-
write(error_unit, fmt) "Suite:", testsuites(is)%name
30-
call run_selected(testsuites(is)%collect, test_name, error_unit, stat)
31-
if (stat < 0) then
32-
error stop 1
28+
if (allocated(test_name)) then
29+
write(error_unit, fmt) "Suite:", testsuites(is)%name
30+
call run_selected(testsuites(is)%collect, test_name, error_unit, stat)
31+
if (stat < 0) then
32+
error stop 1
33+
end if
34+
else
35+
write(error_unit, fmt) "Testing:", testsuites(is)%name
36+
call run_testsuite(testsuites(is)%collect, error_unit, stat)
3337
end if
3438
else
35-
write(error_unit, fmt) "Testing:", testsuites(is)%name
36-
call run_testsuite(testsuites(is)%collect, error_unit, stat)
37-
end if
38-
else
39-
write(error_unit, fmt) "Available testsuites"
40-
do is = 1, size(testsuites)
41-
write(error_unit, fmt) "-", testsuites(is)%name
42-
end do
43-
error stop 1
39+
write(error_unit, fmt) "Available testsuites"
40+
do is = 1, size(testsuites)
41+
write(error_unit, fmt) "-", testsuites(is)%name
42+
end do
43+
error stop 1
4444
end if
4545
else
4646
do is = 1, size(testsuites)
47-
write(error_unit, fmt) "Testing:", testsuites(is)%name
48-
call run_testsuite(testsuites(is)%collect, error_unit, stat)
47+
write(error_unit, fmt) "Testing:", testsuites(is)%name
48+
call run_testsuite(testsuites(is)%collect, error_unit, stat)
4949
end do
5050
end if
5151

0 commit comments

Comments
 (0)