Skip to content

Commit 8ff8d75

Browse files
committed
differences for PR #20
1 parent 1686a50 commit 8ff8d75

3 files changed

Lines changed: 143 additions & 1 deletion

File tree

5-testing-parallel-code.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
title: "Testing parallel code"
3+
teaching:
4+
exercises:
5+
---
6+
7+
:::::::::::::::::::::::::::::::::::::: questions
8+
9+
- How do I unit test a procedure which makes MPI calls?
10+
- How do I easily test different numbers of MPI ranks?
11+
- How do I test a procedure which uses OMP directives?
12+
- How do I easily test different numbers of OMP threads?
13+
14+
::::::::::::::::::::::::::::::::::::::::::::::::
15+
16+
::::::::::::::::::::::::::::::::::::: objectives
17+
18+
- Understand what is different when testing parallel vs serial code.
19+
- Assessment: Provide a test and/or a command to run a test for serial code and ask what needs to change to test parallel code
20+
- Assessment: Provide two versions of a code (serial and parallel) with a test for the serial version and ask learners to write a test for the parallel version.
21+
22+
::::::::::::::::::::::::::::::::::::::::::::::::
23+
24+
## What's the difference?
25+
26+
Depending on the parallelisation tool and strategy employed, the implementation of parallel code can be very different
27+
to that of serial code. This is especially true for code which utilises the message passing interface (MPI). These codes
28+
almost always contain some functionality in which processes, or ranks, communicate by exchanging messages. This message
29+
passing is often complex and will always benefit from testing.
30+
31+
There is added complexity when testing MPI code compared to serial as the logical path through the code is changed
32+
depending on the number of ranks with which the code is executed. Therefore, it is important that we test for a range
33+
of numbers of ranks. This will require controlling the number of ranks running the src and is not something we want
34+
to implement ourselves. This limits the tools available to us. pFUnit is currently the only tool which supports
35+
testing MPI code. Therefore, we will be focusing on pFUnit for this section.
36+
37+
## Tips for writing testable MPI code
38+
39+
### Where possible, separate calls to the MPI library into units (subroutines or functions).
40+
41+
If a procedure does not contain any calls to the MPI library, then it can be tested with a serial unit test. Therefore,
42+
separating MPI calls into their own units makes for a simpler test suite for most of your logic. Only, procedures with
43+
MPI library calls will require the more complex parallel pFUnit tests.
44+
45+
### Pass the MPI communicator information into each procedure to be tested.
46+
47+
If we pass the MPI communicator into a procedure, we can define this to be whatever we wish in our tests. This allows us
48+
to use the communicator provided by pFUnit or some other communicator specific to our problem.
49+
50+
Creating types to wrap this information along with any other MPI specific information (neighbour ranks, etc) can be a
51+
convenient approach.
52+
53+
## Syntax of writing MPI enabled pFUnit tests
54+
55+
Firstly, we must change how we define our test parameters:
56+
57+
- We now use `MPITestParameter` instead of `AbstractTestParameter`.
58+
- We can't know for certain the rank of each process for the pFUnit communicator until the test case runs. Therefore, we
59+
now need to build arrays of input parameters with the rank of a process matching the index of the parameter array. For
60+
example, rank 0 would access index 1 of the input array during testing, rank 1 would access index 2 and so on. See below
61+
for an example.
62+
63+
```F90
64+
@testParameter(constructor=new_exchange_boundaries_test_params)
65+
type, extends(MPITestParameter) :: my_test_params
66+
integer, allocatable :: input(:), expected_output(:)
67+
contains
68+
procedure :: toString => my_test_params_toString
69+
end type my_test_params
70+
```
71+
72+
We therefore need to update how we populate our test parameters to take into account the rank indexing:
73+
74+
```F90
75+
function my_test_suite() result(params)
76+
type(my_test_params), allocatable :: params(:)
77+
integer, allocatable :: input(:), expected_output(:)
78+
integer, max_number_of_ranks
79+
80+
max_number_of_ranks = 2
81+
allocate(params(max_number_of_ranks))
82+
allocate(input(max_number_of_ranks))
83+
allocate(expected_output(max_number_of_ranks))
84+
85+
! Tests with one rank
86+
input(1) = 1
87+
expected_output(1) = 2
88+
params(1) = my_test_params(1, input, expected_output)
89+
90+
! Tests with two ranks
91+
! rank 0
92+
input(1) = 1
93+
expected_output(1) = 1
94+
! rank 1
95+
input(2) = 1
96+
expected_output(2) = 1
97+
params(2) = my_test_params(2, input, expected_output)
98+
end function my_test_suite
99+
```
100+
101+
We also need to change how we define our test case:
102+
103+
- We now use `MPITestCase` instead of `ParameterizedTestCase`
104+
105+
```F90
106+
@TestCase(testParameters={my_test_suite()}, constructor=my_test_params_to_my_test_case)
107+
type, extends(MPITestCase) :: my_test_case
108+
type(my_test_params) :: params
109+
end type my_test_case
110+
```
111+
112+
Finally, we ensure each process accesses the correct rank index parameters during the test
113+
114+
```F90
115+
@Test
116+
subroutine TestMySrcProcedure(this)
117+
class (my_test_case), intent(inout) :: this
118+
119+
integer :: actual_output, rank_index
120+
121+
rank_index = this%getProcessRank() + 1
122+
123+
call my_src_procedure(this%params%input(rank_index), actual_output)
124+
125+
@assertEqual(this%params%expected_output(rank_index), actual_output, "Unexpected output from my_src_procedure")
126+
end subroutine TestMySrcProcedure
127+
```
128+
129+
::::::::::::::::::::::::::::::::::::: challenge
130+
131+
### Challenge 1: Testing MPI parallel code
132+
133+
Take a look at [5-testing-parallel-code/challenge-1](https://github.com/UCL-ARC/fortran-unit-testing-exercises/tree/main/episodes/5-testing-parallel-code/challenge-1) in the exercises repository.
134+
135+
:::::::::::::::::::::::::::::::: solution
136+
137+
A solution is provided in [episodes/5-testing-parallel-code/challenge-1/solution](https://github.com/UCL-ARC/fortran-unit-testing-exercises/tree/main/episodes/5-testing-parallel-code/challenge-1/solution).
138+
139+
:::::::::::::::::::::::::::::::::::::::::
140+
::::::::::::::::::::::::::::::::::::::::::::::::

config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ episodes:
7070
- 2-intro-to-fortran-unit-tests.md
7171
- 3-fortran-unit-test-syntax.md
7272
- 4-debugging-a-broken-test.md
73+
- 5-testing-parallel-code.md
7374

7475
# Information for Learners
7576
learners:

md5sum.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
"file" "checksum" "built" "date"
22
"CODE_OF_CONDUCT.md" "c93c83c630db2fe2462240bf72552548" "site/built/CODE_OF_CONDUCT.md" "2022-08-05"
33
"LICENSE.md" "16e8eaad880865bc4a41811b3e8fa945" "site/built/LICENSE.md" "2025-02-03"
4-
"config.yaml" "771ca11448c55dcc1b56a10e9fc67738" "site/built/config.yaml" "2025-07-30"
4+
"config.yaml" "dcf0220d637e299bc7585265b36af2b1" "site/built/config.yaml" "2025-07-30"
55
"index.md" "56da64ff09e4ecabeb2f4e14a69bde10" "site/built/index.md" "2025-07-30"
66
"links.md" "8184cf4149eafbf03ce8da8ff0778c14" "site/built/links.md" "2022-04-22"
77
"episodes/1-intro-to-unit-tests.md" "88862c5ce828f8f61806c811076f04dd" "site/built/1-intro-to-unit-tests.md" "2025-07-07"
88
"episodes/2-intro-to-fortran-unit-tests.md" "1d5081efc44daa9f73c653c94d95b0bb" "site/built/2-intro-to-fortran-unit-tests.md" "2025-07-30"
99
"episodes/3-fortran-unit-test-syntax.md" "ecace40e2f93bf3f62821e82770e19ab" "site/built/3-fortran-unit-test-syntax.md" "2025-07-30"
1010
"episodes/4-debugging-a-broken-test.md" "a492b0a1b576f95baa4d1baa0aa67712" "site/built/4-debugging-a-broken-test.md" "2025-07-30"
11+
"episodes/5-testing-parallel-code.md" "16c4ad9739db5e4f1835dcdfc6790673" "site/built/5-testing-parallel-code.md" "2025-07-30"
1112
"instructors/instructor-notes.md" "cae72b6712578d74a49fea7513099f8c" "site/built/instructor-notes.md" "2023-03-16"
1213
"learners/reference.md" "1c7cc4e229304d9806a13f69ca1b8ba4" "site/built/reference.md" "2023-03-16"
1314
"learners/setup.md" "ccfecb39370c85c66c1e4363d9c9a887" "site/built/setup.md" "2025-07-30"

0 commit comments

Comments
 (0)