diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 4a10a90..dd6d059 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -47,3 +47,6 @@ jobs: - name: Test episode 5 run: docker build . -f docker/Dockerfile.ep-5 + + - name: Test episode 6 + run: docker build . -f docker/Dockerfile.ep-6 diff --git a/docker/Dockerfile.ep-3 b/docker/Dockerfile.ep-3 index 9665d9d..4b4c5de 100644 --- a/docker/Dockerfile.ep-3 +++ b/docker/Dockerfile.ep-3 @@ -1,13 +1,40 @@ FROM ghcr.io/ucl-arc/fortran-unit-testing-exercises:main -COPY --chown=vscode episodes/3-fortran-unit-test-syntax /home/vscode/3-fortran-unit-test-syntax +COPY --chown=vscode episodes/3-writing-your-first-unit-test /home/vscode/3-writing-your-first-unit-test -WORKDIR /home/vscode/3-fortran-unit-test-syntax/solution +# Test the Challenge code -# build tests with cmake -RUN cmake -B build -DCMAKE_PREFIX_PATH=/home/vscode/pfunit/build/installed && \ - cmake --build build +WORKDIR /home/vscode/3-writing-your-first-unit-test/challenge -# test pfunit with ctest -RUN ctest --test-dir build --output-on-failure +# Rebuild without pFUnit +RUN cmake -B build-std && \ + cmake --build build-std + +# Test without pFUnit +RUN ctest --test-dir build-std --output-on-failure + +# Test the Solution code + +# Cleanup directories +RUN rm -rf build-std build-pf + +# Copy solution code +RUN cp ../solution/test_temp_conversions.f90 test/standard_fortran/ && \ + cp ../solution/test_temp_conversions.pf test/pfunit/ + +RUN ls test/pfunit/ + +# Rebuild without pFUnit +RUN cmake -B build-std && \ + cmake --build build-std + +# Rebuild with pFUnit +RUN cmake -B build-pf -DCMAKE_PREFIX_PATH=/home/vscode/pfunit/build/installed && \ + cmake --build build-pf + +# Test without pFUnit +RUN ctest --test-dir build-std --output-on-failure + +# Test with pFUnit +RUN ctest --test-dir build-pf --output-on-failure diff --git a/docker/Dockerfile.ep-4 b/docker/Dockerfile.ep-4 index 92ca61f..9dd6ec4 100644 --- a/docker/Dockerfile.ep-4 +++ b/docker/Dockerfile.ep-4 @@ -1,11 +1,8 @@ FROM ghcr.io/ucl-arc/fortran-unit-testing-exercises:main -COPY --chown=vscode episodes/4-debugging-a-broken-test /home/vscode/4-debugging-a-broken-test +COPY --chown=vscode episodes/4-fortran-unit-test-syntax /home/vscode/4-fortran-unit-test-syntax -WORKDIR /home/vscode/4-debugging-a-broken-test/challenge - -# Fix intentional bug in code -RUN sed -i -E 's/.*matrix\(row, col\) = temp_matrix\(row, col\)/matrix\(col, row\) = temp_matrix\(row, col\)/g' src/matrix_transforms.f90 +WORKDIR /home/vscode/4-fortran-unit-test-syntax/solution # build tests with cmake RUN cmake -B build -DCMAKE_PREFIX_PATH=/home/vscode/pfunit/build/installed && \ @@ -13,3 +10,4 @@ RUN cmake -B build -DCMAKE_PREFIX_PATH=/home/vscode/pfunit/build/installed && \ # test pfunit with ctest RUN ctest --test-dir build --output-on-failure + diff --git a/docker/Dockerfile.ep-5 b/docker/Dockerfile.ep-5 index 61b8745..3bffc6c 100644 --- a/docker/Dockerfile.ep-5 +++ b/docker/Dockerfile.ep-5 @@ -1,24 +1,15 @@ FROM ghcr.io/ucl-arc/fortran-unit-testing-exercises:main -COPY --chown=vscode episodes/5-testing-parallel-code /home/vscode/5-testing-parallel-code +COPY --chown=vscode episodes/5-debugging-a-broken-test /home/vscode/5-debugging-a-broken-test -WORKDIR /home/vscode/5-testing-parallel-code/challenge +WORKDIR /home/vscode/5-debugging-a-broken-test/challenge # Fix intentional bug in code -RUN mv ../solution/test_find_steady_state.pf ./test/ && \ - echo "set(test_find_steady_state_src \${test_srcs})\n\ -list(FILTER test_find_steady_state_src INCLUDE REGEX \".*test_find_steady_state.pf\")\n\ -\n\ -add_pfunit_ctest (pfunit_find_steady_state_tests\n\ - TEST_SOURCES \${test_find_steady_state_src}\n\ - LINK_LIBRARIES sut\n\ - MAX_PES 8\n\ - )\n\ -" >> test/CMakeLists.txt +RUN sed -i -E 's/.*matrix\(row, col\) = temp_matrix\(row, col\)/matrix\(col, row\) = temp_matrix\(row, col\)/g' src/matrix_transforms.f90 # build tests with cmake -RUN cmake -B build-cmake -DCMAKE_PREFIX_PATH=/home/vscode/pfunit/build/installed && \ - cmake --build build-cmake +RUN cmake -B build -DCMAKE_PREFIX_PATH=/home/vscode/pfunit/build/installed && \ + cmake --build build -# test with ctest, allowing MPI to oversubscribe -RUN ctest --test-dir build-cmake --output-on-failure +# test pfunit with ctest +RUN ctest --test-dir build --output-on-failure diff --git a/docker/Dockerfile.ep-6 b/docker/Dockerfile.ep-6 new file mode 100644 index 0000000..94d181e --- /dev/null +++ b/docker/Dockerfile.ep-6 @@ -0,0 +1,24 @@ +FROM ghcr.io/ucl-arc/fortran-unit-testing-exercises:main + +COPY --chown=vscode episodes/6-testing-parallel-code /home/vscode/6-testing-parallel-code + +WORKDIR /home/vscode/6-testing-parallel-code/challenge + +# Fix intentional bug in code +RUN mv ../solution/test_find_steady_state.pf ./test/ && \ + echo "set(test_find_steady_state_src \${test_srcs})\n\ +list(FILTER test_find_steady_state_src INCLUDE REGEX \".*test_find_steady_state.pf\")\n\ +\n\ +add_pfunit_ctest (pfunit_find_steady_state_tests\n\ + TEST_SOURCES \${test_find_steady_state_src}\n\ + LINK_LIBRARIES sut\n\ + MAX_PES 8\n\ + )\n\ +" >> test/CMakeLists.txt + +# build tests with cmake +RUN cmake -B build-cmake -DCMAKE_PREFIX_PATH=/home/vscode/pfunit/build/installed && \ + cmake --build build-cmake + +# test with ctest, allowing MPI to oversubscribe +RUN ctest --test-dir build-cmake --output-on-failure diff --git a/episodes/3-writing-your-first-unit-test/challenge/CMakeLists.txt b/episodes/3-writing-your-first-unit-test/challenge/CMakeLists.txt new file mode 100644 index 0000000..945d109 --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/challenge/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +# Set project name +project( + "temp_conversions" + LANGUAGES "Fortran" + VERSION "0.0.1" + DESCRIPTION "Library for converting between various temperatures" +) + +# Define src file(s) +set(PROJ_SRC_FILES "${PROJECT_SOURCE_DIR}/src/temp_conversions.f90") + +enable_testing() +add_subdirectory("test") diff --git a/episodes/3-writing-your-first-unit-test/challenge/README.md b/episodes/3-writing-your-first-unit-test/challenge/README.md new file mode 100644 index 0000000..27a7d10 --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/challenge/README.md @@ -0,0 +1,63 @@ +# Writing your first unit test - Standard Fortran + +This exercise aims to teach the principles of unit testing and how to write a good +unit test. The tests within this challenge are intended to be written using standard +Fortran without the use of a testing framework, in order to teach the principles alone. + +## The src code + +In [src](./src) you will find a library [temp_conversions.f90](./src/temp_conversions.f90) +which provides functions for converting between various units of temperature. The functions +provided are... + +- **fahrenheit_to_celsius**: Which takes in a temperature in Fahrenheit and returns a temperature in Celsius. +- **celsius_to_kelvin**: Which takes in a temperature in Celsius and returns a temperature in Kelvin. + +## The tasks + +### Part 1 - Test with Standard Fortran + +Imagine you wish to use the temp_conversions library to convert Fahrenheit to Kelvin. We +know that there is no function which does this direct conversion. With this is mind, write +a test, or tests, to give you confidence that temp_conversions can correctly convert +Fahrenheit to Kelvin. + +To get you started, the file [test_temp_conversions.f90](./test/standard_fortran/test_temp_conversions.f90) +has been provided. `test_temp_conversions.f90` contains some boilerplate to make writing a +test easier. There is an empty test subroutine `test` provided which takes in a logical +`passed` and a character array `failure_message`. The logical `passed` should indicate if +the test was successful. The character array `failure_message`, should be populated with a +message that will be printed to the terminal in the event that `passed` is `.false.`. Once +the test subroutine is written it should be called within the main body of the test program +as indicated in `test_temp_conversions.f90`. + +> Not: If you add a new test file or change the name of `test_temp_conversions.f90`, you will +> need to update list of tests (`test_src`) in [test/pfunit/CMakeLists.txt](./test/pfunit/CMakeLists.txt) + +### Part 2 - Convert tests to use pFUnit + +Convert your tests from [Part 1](#part-1---test-with-standard-fortran), to use +[pFUnit](https://github.com/Goddard-Fortran-Ecosystem/pFUnit). + +A file [test_temp_conversions.pf](./test/pfunit/test_temp_conversions.pf) containing a template +for your pFUnit test(s) has been provided. Comments within this file indicate the aspects of +the pFUnit test you must write. + +> Note: This template has been written to facilitate conversion of +> [test_temp_conversions.f90](./test/standard_fortran/test_temp_conversions.f90) as provided with this repo +> to pFUnit. If your version of test_temp_conversions.f90, produced in Part 1, is significantly +> different, You may prefer to use a different structure to the one provided in the template. + +To build and run your pFUnit test(s) you must add the pFUnit lib to the `CMAKE_PREFIX_PATH` +when building via the following command. + +```bash +cmake -B build -DCMAKE_PREFIX_PATH=/path/to/pfunit/install +cmake --build build +ctest --test-dir build --output-on-failure +``` + +If your test does not get built, ensure you have added it to the list of tests (`test_src`) +in [test/pfunit/CMakeLists.txt](./test/pfunit/CMakeLists.txt) + +> If you are using the devcontainer, there is an installation of pFUnit at /home/vscode/pfunit/build/installed diff --git a/episodes/3-writing-your-first-unit-test/challenge/src/temp_conversions.f90 b/episodes/3-writing-your-first-unit-test/challenge/src/temp_conversions.f90 new file mode 100644 index 0000000..b4a3e66 --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/challenge/src/temp_conversions.f90 @@ -0,0 +1,20 @@ +module temp_conversions + implicit none + private + public :: fahrenheit_to_celsius, celsius_to_kelvin + +contains + + function fahrenheit_to_celsius(fahrenheit) result(celsius) + real, intent(in) :: fahrenheit + real :: celsius + celsius = (fahrenheit - 32.0) * 5.0 / 9.0 + end function fahrenheit_to_celsius + + function celsius_to_kelvin(celsius) result(kelvin) + real, intent(in) :: celsius + real :: kelvin + kelvin = celsius + 273.15 + end function celsius_to_kelvin + +end module temp_conversions \ No newline at end of file diff --git a/episodes/3-writing-your-first-unit-test/challenge/test/CMakeLists.txt b/episodes/3-writing-your-first-unit-test/challenge/test/CMakeLists.txt new file mode 100644 index 0000000..1437d68 --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/challenge/test/CMakeLists.txt @@ -0,0 +1,7 @@ +find_package(PFUNIT QUIET) + +add_subdirectory("standard_fortran") + +if (PFUNIT_FOUND) + add_subdirectory("pfunit") +endif() diff --git a/episodes/3-writing-your-first-unit-test/challenge/test/pfunit/.gitignore b/episodes/3-writing-your-first-unit-test/challenge/test/pfunit/.gitignore new file mode 100644 index 0000000..4fa917f --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/challenge/test/pfunit/.gitignore @@ -0,0 +1,2 @@ +*.f90 +*.F90 diff --git a/episodes/3-writing-your-first-unit-test/challenge/test/pfunit/CMakeLists.txt b/episodes/3-writing-your-first-unit-test/challenge/test/pfunit/CMakeLists.txt new file mode 100644 index 0000000..6808d33 --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/challenge/test/pfunit/CMakeLists.txt @@ -0,0 +1,15 @@ +message(STATUS "Using pFUnit") + +set(TEST_DIR "${PROJECT_SOURCE_DIR}/test/pfunit") + +# Create library for src code +add_library (sut STATIC ${PROJ_SRC_FILES}) + +# List all test files +set(test_srcs "test_temp_conversions.pf") + +add_pfunit_ctest (pfunit_test_temp_conversions_exec + TEST_SOURCES ${test_srcs} + LINK_LIBRARIES sut # your application library + ) + diff --git a/episodes/3-writing-your-first-unit-test/challenge/test/pfunit/test_temp_conversions.pf b/episodes/3-writing-your-first-unit-test/challenge/test/pfunit/test_temp_conversions.pf new file mode 100644 index 0000000..9a3589a --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/challenge/test/pfunit/test_temp_conversions.pf @@ -0,0 +1,47 @@ +module test_temp_conversions + use temp_conversions, only : fahrenheit_to_celsius, celsius_to_kelvin + ! allow(C121) + use funit + implicit none + + public + + !> Test parameter type to package the test parameters + ! Your changes here... + + !> Test case type to specify the style of test (paramaterized) + ! Your changes here... + +contains + + !*************************************************************************! + ! Test fahrenheit_to_celsius ! + !*************************************************************************! + + !> Test suite for tests of fahrenheit_to_celsius + ! Your changes here... + + !> Unit test subroutine for fahrenheit_to_celsius + ! Your changes here... + + !*************************************************************************! + ! Test celsius_to_kelvin ! + !*************************************************************************! + + !> Test suite for tests of celsius_to_kelvin + ! Your changes here... + + !> Unit test subroutine for celsius_to_kelvin + ! Your changes here... + + !*************************************************************************! + ! Constructors ! + !*************************************************************************! + + !> Constructor for converting test parameters into a test case + ! Your changes here... + + !> Constructor for converting test parameters into a string + ! Your changes here... + +end module test_temp_conversions diff --git a/episodes/3-writing-your-first-unit-test/challenge/test/standard_fortran/CMakeLists.txt b/episodes/3-writing-your-first-unit-test/challenge/test/standard_fortran/CMakeLists.txt new file mode 100644 index 0000000..ac9d910 --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/challenge/test/standard_fortran/CMakeLists.txt @@ -0,0 +1,16 @@ +message(STATUS "Using standard Fortran") + +set(TEST_DIR "${PROJECT_SOURCE_DIR}/test/standard_fortran") + +# Define test file(s) +set(test_srcs "test_temp_conversions.f90") + +# Build test executable +add_executable(test_temp_conversions_exec + "${PROJ_SRC_FILES}" + "${test_srcs}" +) + +# Add test as ctest +add_test(NAME test_temp_conversions COMMAND test_temp_conversions_exec) + diff --git a/episodes/3-writing-your-first-unit-test/challenge/test/standard_fortran/test_temp_conversions.f90 b/episodes/3-writing-your-first-unit-test/challenge/test/standard_fortran/test_temp_conversions.f90 new file mode 100644 index 0000000..8dc77bf --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/challenge/test/standard_fortran/test_temp_conversions.f90 @@ -0,0 +1,41 @@ +program test_temp_conversions + use temp_conversions, only : fahrenheit_to_celsius, celsius_to_kelvin + implicit none + + integer :: i + + ! Declare passed and failure message arrays to be set by a test subroutine(s) + logical :: passed(1) + character(len=200) :: failure_message(1) + + ! Call your test subroutine(s) here + call test(passed(1), failure_message(1)) + + if (all(passed)) then + write(*,*) "All tests passed!" + else + do i = 1, size(passed) + if (.not. passed(i)) then + write(*,*) "FAIL: ", trim(failure_message(i)) + end if + end do + stop 1 + end if + +contains + !> The test subroutine + subroutine test(passed, failure_message) + !> A logical to track whether the test passed or not + logical, intent(out) :: passed + !> A failure message to be displayed if passed is false + character(len=200), intent(out) :: failure_message + + ! No test has been written yet so just default passed to .true. + passed = .true. + + ! Populate the failure message + write(failure_message, '(A,A,A)') "It is useful to include input, expected output and actual output values here. To do ", & + "that, replace (A,A) with the correct format for your values, for example ", & + "(A,F7.2,A,F7.2,A,F7.2)." + end subroutine test +end program test_temp_conversions diff --git a/episodes/3-writing-your-first-unit-test/solution/README.md b/episodes/3-writing-your-first-unit-test/solution/README.md new file mode 100644 index 0000000..ff99e20 --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/solution/README.md @@ -0,0 +1,57 @@ +# Writing your first unit test - Standard Fortran - Solution + +## Part 1 - Test with Standard Fortran + +The solution is provided in the form of a single test file [test_temp_conversions.f90](./test_temp_conversions.f90) +which replaces the file of the same name provided in the +[challenge/test/standard_fortran](../challenge/test/standard_fortran/) directory. + +### Key points + +There are several key aspects within the solution that are important to implement in any test. + +#### Isolated test subroutine + +Each test subroutine in [test_temp_conversions.f90](./test_temp_conversions.f90) calls and tests only one src +function. For example, `test_fahrenheit_to_celsius` calls and tests `fahrenheit_to_celsius` and +`test_celsius_to_kelvin` calls and tests `celsius_to_kelvin`. + +This is important as, in the event of a test failing it will be clear which src function is the cause of the failure. +If instead we had implemented a single test subroutine which calls both `test_fahrenheit_to_celsius` and +`fahrenheit_to_celsius`, then a failure in such a test could have been caused by either of those src functions and further +investigation would be required. + +#### Parameterised tests + +Each test subroutine in [test_temp_conversions.f90](./test_temp_conversions.f90) takes in an `input` and an `expected_output`. +This allows the same test subroutine to be called with multiple different inputs to test several scenarios with the same test +code. Therefore, we are able to test edge cases and other key scenarios more easily. + +#### Clear failure message + +Each test subroutine in [test_temp_conversions.f90](./test_temp_conversions.f90) populates `failure_message` with a clear +message that aims to make it as easy as possible to diagnose a failing test. Importantly, the `failure_message` includes the +`input`, the `expected_output` and the actual value which we have compared to the `expected_output`. + +#### Comparing floats within an appropriate tolerance + +We cannot compare two floats directly as due to rounding errors they will almost always +not be exactly the same. Therefore, we must check the difference between two floats that +we expect to be equal and ensure it is less than some appropriate tolerance. This tolerance +should be as small as possible whilst still making sense with the code we are testing. + +## Part 2 - Convert tests to use pFUnit + +The solution is provided in the form of a single test file [test_temp_conversions.pf](./test_temp_conversions.pf) +which replaces the file of the same name provided in the [challenge/test/pfunit](../challenge/test/pfunit/) directory. + +### Key points + +All the key points mention within [Part 1](#key-points) are upheld in this solution. Any additional points are +detailed below. + +#### Add a description for each test + +For each set of parameters in [test_temp_conversions.pf](./test_temp_conversions.pf) there is a description provided +which aims to make clear what is being tested. This can be useful when thinking about test coverage of edge cases. +It can also help in the event of a test failure to hint at what the problem may be. diff --git a/episodes/3-writing-your-first-unit-test/solution/test_temp_conversions.f90 b/episodes/3-writing-your-first-unit-test/solution/test_temp_conversions.f90 new file mode 100644 index 0000000..9f44cd5 --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/solution/test_temp_conversions.f90 @@ -0,0 +1,84 @@ +program test_temp_conversions + use temp_conversions, only : fahrenheit_to_celsius, celsius_to_kelvin + implicit none + + ! The tolerance to use when comparing floats + real, parameter :: tolerance = 1e-6 + + integer :: i + + ! Declare passed and failure message arrays to be set by a test subroutine(s) + logical :: passed(7) + character(len=200) :: failure_message(7) + + ! Define set of tests for fahrenheit_to_celsius by calling test with various inputs and expected outputs + call test_fahrenheit_to_celsius(0.0, -17.777779, passed(1), failure_message(1)) + call test_fahrenheit_to_celsius(32.0, 0.0, passed(2), failure_message(2)) + call test_fahrenheit_to_celsius(-100.0, -73.333336, passed(3), failure_message(3)) + call test_fahrenheit_to_celsius(1.23,-17.094444, passed(4), failure_message(4)) + + ! Define set of tests for celsius_to_kelvin by calling test with various inputs and expected outputs + call test_celsius_to_kelvin(0.0, 273.15, passed(5), failure_message(5)) + call test_celsius_to_kelvin(-273.15, 0.0, passed(6), failure_message(6)) + call test_celsius_to_kelvin(-173.15, 100.0, passed(7), failure_message(7)) + + if (all(passed)) then + write(*,*) "All tests passed!" + else + do i = 1, size(passed) + if (.not. passed(i)) then + write(*,*) "FAIL: ", trim(failure_message(i)) + end if + end do + stop 1 + end if + +contains + !> Unit test subroutine for celsius_to_kelvin + subroutine test_fahrenheit_to_celsius(input, expected_output, passed, failure_message) + !> The input fahrenheit value to pass to fahrenheit_to_celsius + real, intent(in) :: input + !> The celsius value we expect to be returner from fahrenheit_to_celsius + real, intent(in) :: expected_output + !> A logical to track whether the test passed or not + logical, intent(out) :: passed + !> A failure message to be displayed if passed is false + character(len=200), intent(out) :: failure_message + + real :: actual_output + + ! Get the actual celsius value returned from fahrenheit_to_celsius + actual_output = fahrenheit_to_celsius(input) + + ! Check that the actual value is within some tolerance of the expected value + passed = abs(actual_output - expected_output) < tolerance + + ! Populate the failure message + write(failure_message, '(A,F7.2,A,F7.2,A,F7.2,A)') "Failed With ", input, "°F: Expected ", expected_output, & + "°C but got ", actual_output, "°C" + end subroutine test_fahrenheit_to_celsius + + !> Unit test subroutine for celsius_to_kelvin + subroutine test_celsius_to_kelvin(input, expected_output, passed, failure_message) + !> The input celsius value to pass to celsius_to_kelvin + real, intent(in) :: input + !> The kelvin value we expect to be returner from celsius_to_kelvin + real, intent(in) :: expected_output + !> A logical to track whether the test passed or not + logical, intent(out) :: passed + !> A failure message to be displayed if passed is false + character(len=200), intent(out) :: failure_message + + real :: actual_output + + ! Get the actual celsius value returned from celsius_to_kelvin + actual_output = celsius_to_kelvin(input) + + ! Check that the actual value is within some tolerance of the expected value + passed = abs(actual_output - expected_output) < tolerance + + ! Populate the failure message + write(failure_message, '(A,F7.2,A,F7.2,A,F7.2,A)') "Failed With ", input, "°C: Expected ", expected_output, & + "°K but got ", actual_output, "°K" + end subroutine test_celsius_to_kelvin +end program test_temp_conversions diff --git a/episodes/3-writing-your-first-unit-test/solution/test_temp_conversions.pf b/episodes/3-writing-your-first-unit-test/solution/test_temp_conversions.pf new file mode 100644 index 0000000..0fe7541 --- /dev/null +++ b/episodes/3-writing-your-first-unit-test/solution/test_temp_conversions.pf @@ -0,0 +1,124 @@ +module test_temp_conversions + use temp_conversions, only : fahrenheit_to_celsius, celsius_to_kelvin + ! allow(C121) + use funit + implicit none + + public + + !> Type to package the test parameters + @TestParameter + type, extends(AbstractTestParameter) :: temp_conversions_test_params_t + !> The temperature to input into the function being tested + real :: input + !> Theb temperature expected to be returned from the function being tested + real :: expected_output + !> A description of the test to be outputted for logging + character(len=100) :: description + contains + procedure :: toString => temp_conversions_test_params_t_toString + end type temp_conversions_test_params_t + + !> Type to specify the style of test (paramaterized) + @TestCase(constructor=new_test_case) + type, extends(ParameterizedTestCase) :: temp_conversions_test_case_t + type(temp_conversions_test_params_t) :: params + end type temp_conversions_test_case_t + +contains + + !*************************************************************************! + ! Test fahrenheit_to_celsius ! + !*************************************************************************! + + !> Test suite for tests of fahrenheit_to_celsius + function fahrenheit_to_celsius_testsuite() result(params) + !> An array of test parameters, each specifying an individual test + class(temp_conversions_test_params_t), allocatable :: params(:) + + params = [ & + temp_conversions_test_params_t(0.0, -17.777779, "0.0 °F"), & + temp_conversions_test_params_t(32.0, 0.0, "0.0 °C"), & + temp_conversions_test_params_t(-100.0, -73.333336, "100 °F"), & + temp_conversions_test_params_t(1.23,-17.094444, "Decimal °F") & + ] + end function fahrenheit_to_celsius_testsuite + + !> Unit test subroutine for fahrenheit_to_celsius + @Test(testParameters={fahrenheit_to_celsius_testsuite()}) + subroutine test_fahrenheit_to_celsius(this) + !> The test case which indicates the type of test we are running + class(temp_conversions_test_case_t), intent(inout) :: this + + character(len=200) :: failure_message + real :: actual_output + + ! Get the actual celsius value returned from fahrenheit_to_celsius + actual_output = fahrenheit_to_celsius(this%params%input) + + ! Populate the failure message + write(failure_message, '(A,F7.2,A,F7.2,A,F7.2,A)') "Failed With ", this%params%input, " °F: Expected ", & + this%params%expected_output, "°C but got ", actual_output, "°C" + @assertEqual(this%params%expected_output, actual_output, tolerance=1e-6, message=trim(failure_message)) + + end subroutine test_fahrenheit_to_celsius + + !*************************************************************************! + ! Test celsius_to_kelvin ! + !*************************************************************************! + + !> Test suite for tests of celsius_to_kelvin + function celsius_to_kelvin_testsuite() result(params) + !> An array of test parameters, each specifying an individual test + class(temp_conversions_test_params_t), allocatable :: params(:) + + params = [ & + temp_conversions_test_params_t(0.0, 273.15, "0.0 °C"), & + temp_conversions_test_params_t(-273.15, 0.0, "0.0 °K"), & + temp_conversions_test_params_t(-173.15, 100.0, "100.0 °K") & + ] + end function celsius_to_kelvin_testsuite + + !> Unit test subroutine for celsius_to_kelvin + @Test(testParameters={celsius_to_kelvin_testsuite()}) + subroutine test_celsius_to_kelvin(this) + !> The test case which indicates the type of test we are running + class(temp_conversions_test_case_t), intent(inout) :: this + + character(len=200) :: failure_message + real :: actual_output + + ! Get the actual kelvin value returned from celsius_to_kelvin + actual_output = celsius_to_kelvin(this%params%input) + + ! Populate the failure message + write(failure_message, '(A,F7.2,A,F7.2,A,F7.2,A)') "Failed With ", this%params%input, "°C: Expected ", & + this%params%expected_output, "°K but got ", actual_output, "°K" + @assertEqual(this%params%expected_output, actual_output, tolerance=1e-6, message=trim(failure_message)) + + end subroutine test_celsius_to_kelvin + + !*************************************************************************! + ! Constructors ! + !*************************************************************************! + + !> Constructor for converting test parameters into a test case + function new_test_case(testParameter) result(tst) + !> The parameters to be converted to a test case + type(temp_conversions_test_params_t), intent(in) :: testParameter + !> The test case to return after conversion from parameters + type(temp_conversions_test_case_t) :: tst + + tst%params = testParameter + end function new_test_case + + !> Constructor for converting test parameters into a string + function temp_conversions_test_params_t_toString(this) result(string) + !> The parameters to be converted to a string + class(temp_conversions_test_params_t), intent(in) :: this + character(:), allocatable :: string + + string = trim(this%description) + end function temp_conversions_test_params_t_toString + +end module test_temp_conversions diff --git a/episodes/3-fortran-unit-test-syntax/challenge/.gitignore b/episodes/4-fortran-unit-test-syntax/challenge/.gitignore similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/.gitignore rename to episodes/4-fortran-unit-test-syntax/challenge/.gitignore diff --git a/episodes/3-fortran-unit-test-syntax/challenge/CMakeLists.txt b/episodes/4-fortran-unit-test-syntax/challenge/CMakeLists.txt similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/CMakeLists.txt rename to episodes/4-fortran-unit-test-syntax/challenge/CMakeLists.txt diff --git a/episodes/3-fortran-unit-test-syntax/challenge/README.md b/episodes/4-fortran-unit-test-syntax/challenge/README.md similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/README.md rename to episodes/4-fortran-unit-test-syntax/challenge/README.md diff --git a/episodes/3-fortran-unit-test-syntax/challenge/src/game_of_life.f90 b/episodes/4-fortran-unit-test-syntax/challenge/src/game_of_life.f90 similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/src/game_of_life.f90 rename to episodes/4-fortran-unit-test-syntax/challenge/src/game_of_life.f90 diff --git a/episodes/3-fortran-unit-test-syntax/challenge/src/game_of_life_mod.f90 b/episodes/4-fortran-unit-test-syntax/challenge/src/game_of_life_mod.f90 similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/src/game_of_life_mod.f90 rename to episodes/4-fortran-unit-test-syntax/challenge/src/game_of_life_mod.f90 diff --git a/episodes/3-fortran-unit-test-syntax/challenge/test/CMakeLists.txt b/episodes/4-fortran-unit-test-syntax/challenge/test/CMakeLists.txt similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/test/CMakeLists.txt rename to episodes/4-fortran-unit-test-syntax/challenge/test/CMakeLists.txt diff --git a/episodes/3-fortran-unit-test-syntax/challenge/test/models/empty_-10_10.dat b/episodes/4-fortran-unit-test-syntax/challenge/test/models/empty_-10_10.dat similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/test/models/empty_-10_10.dat rename to episodes/4-fortran-unit-test-syntax/challenge/test/models/empty_-10_10.dat diff --git a/episodes/3-fortran-unit-test-syntax/challenge/test/models/empty_10_-10.dat b/episodes/4-fortran-unit-test-syntax/challenge/test/models/empty_10_-10.dat similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/test/models/empty_10_-10.dat rename to episodes/4-fortran-unit-test-syntax/challenge/test/models/empty_10_-10.dat diff --git a/episodes/3-fortran-unit-test-syntax/challenge/test/models/zeros_31_31.dat b/episodes/4-fortran-unit-test-syntax/challenge/test/models/zeros_31_31.dat similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/test/models/zeros_31_31.dat rename to episodes/4-fortran-unit-test-syntax/challenge/test/models/zeros_31_31.dat diff --git a/episodes/3-fortran-unit-test-syntax/challenge/test/test_check_for_steady_state.pf b/episodes/4-fortran-unit-test-syntax/challenge/test/test_check_for_steady_state.pf similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/test/test_check_for_steady_state.pf rename to episodes/4-fortran-unit-test-syntax/challenge/test/test_check_for_steady_state.pf diff --git a/episodes/3-fortran-unit-test-syntax/challenge/test/test_evolve_board.pf b/episodes/4-fortran-unit-test-syntax/challenge/test/test_evolve_board.pf similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/test/test_evolve_board.pf rename to episodes/4-fortran-unit-test-syntax/challenge/test/test_evolve_board.pf diff --git a/episodes/3-fortran-unit-test-syntax/challenge/test/test_find_steady_state.pf b/episodes/4-fortran-unit-test-syntax/challenge/test/test_find_steady_state.pf similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/test/test_find_steady_state.pf rename to episodes/4-fortran-unit-test-syntax/challenge/test/test_find_steady_state.pf diff --git a/episodes/3-fortran-unit-test-syntax/challenge/test/test_read_model_from_file.pf b/episodes/4-fortran-unit-test-syntax/challenge/test/test_read_model_from_file.pf similarity index 100% rename from episodes/3-fortran-unit-test-syntax/challenge/test/test_read_model_from_file.pf rename to episodes/4-fortran-unit-test-syntax/challenge/test/test_read_model_from_file.pf diff --git a/episodes/3-fortran-unit-test-syntax/models/model-1.dat b/episodes/4-fortran-unit-test-syntax/models/model-1.dat similarity index 100% rename from episodes/3-fortran-unit-test-syntax/models/model-1.dat rename to episodes/4-fortran-unit-test-syntax/models/model-1.dat diff --git a/episodes/3-fortran-unit-test-syntax/models/model-2.dat b/episodes/4-fortran-unit-test-syntax/models/model-2.dat similarity index 100% rename from episodes/3-fortran-unit-test-syntax/models/model-2.dat rename to episodes/4-fortran-unit-test-syntax/models/model-2.dat diff --git a/episodes/3-fortran-unit-test-syntax/models/model-3.dat b/episodes/4-fortran-unit-test-syntax/models/model-3.dat similarity index 100% rename from episodes/3-fortran-unit-test-syntax/models/model-3.dat rename to episodes/4-fortran-unit-test-syntax/models/model-3.dat diff --git a/episodes/3-fortran-unit-test-syntax/models/model-4.dat b/episodes/4-fortran-unit-test-syntax/models/model-4.dat similarity index 100% rename from episodes/3-fortran-unit-test-syntax/models/model-4.dat rename to episodes/4-fortran-unit-test-syntax/models/model-4.dat diff --git a/episodes/3-fortran-unit-test-syntax/solution/.gitignore b/episodes/4-fortran-unit-test-syntax/solution/.gitignore similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/.gitignore rename to episodes/4-fortran-unit-test-syntax/solution/.gitignore diff --git a/episodes/3-fortran-unit-test-syntax/solution/CMakeLists.txt b/episodes/4-fortran-unit-test-syntax/solution/CMakeLists.txt similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/CMakeLists.txt rename to episodes/4-fortran-unit-test-syntax/solution/CMakeLists.txt diff --git a/episodes/3-fortran-unit-test-syntax/solution/README.md b/episodes/4-fortran-unit-test-syntax/solution/README.md similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/README.md rename to episodes/4-fortran-unit-test-syntax/solution/README.md diff --git a/episodes/3-fortran-unit-test-syntax/solution/src/game_of_life.f90 b/episodes/4-fortran-unit-test-syntax/solution/src/game_of_life.f90 similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/src/game_of_life.f90 rename to episodes/4-fortran-unit-test-syntax/solution/src/game_of_life.f90 diff --git a/episodes/3-fortran-unit-test-syntax/solution/src/game_of_life_mod.f90 b/episodes/4-fortran-unit-test-syntax/solution/src/game_of_life_mod.f90 similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/src/game_of_life_mod.f90 rename to episodes/4-fortran-unit-test-syntax/solution/src/game_of_life_mod.f90 diff --git a/episodes/3-fortran-unit-test-syntax/solution/test/CMakeLists.txt b/episodes/4-fortran-unit-test-syntax/solution/test/CMakeLists.txt similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/test/CMakeLists.txt rename to episodes/4-fortran-unit-test-syntax/solution/test/CMakeLists.txt diff --git a/episodes/3-fortran-unit-test-syntax/solution/test/models/empty_-10_10.dat b/episodes/4-fortran-unit-test-syntax/solution/test/models/empty_-10_10.dat similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/test/models/empty_-10_10.dat rename to episodes/4-fortran-unit-test-syntax/solution/test/models/empty_-10_10.dat diff --git a/episodes/3-fortran-unit-test-syntax/solution/test/models/empty_10_-10.dat b/episodes/4-fortran-unit-test-syntax/solution/test/models/empty_10_-10.dat similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/test/models/empty_10_-10.dat rename to episodes/4-fortran-unit-test-syntax/solution/test/models/empty_10_-10.dat diff --git a/episodes/3-fortran-unit-test-syntax/solution/test/models/zeros_31_31.dat b/episodes/4-fortran-unit-test-syntax/solution/test/models/zeros_31_31.dat similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/test/models/zeros_31_31.dat rename to episodes/4-fortran-unit-test-syntax/solution/test/models/zeros_31_31.dat diff --git a/episodes/3-fortran-unit-test-syntax/solution/test/test_check_for_steady_state.pf b/episodes/4-fortran-unit-test-syntax/solution/test/test_check_for_steady_state.pf similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/test/test_check_for_steady_state.pf rename to episodes/4-fortran-unit-test-syntax/solution/test/test_check_for_steady_state.pf diff --git a/episodes/3-fortran-unit-test-syntax/solution/test/test_evolve_board.pf b/episodes/4-fortran-unit-test-syntax/solution/test/test_evolve_board.pf similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/test/test_evolve_board.pf rename to episodes/4-fortran-unit-test-syntax/solution/test/test_evolve_board.pf diff --git a/episodes/3-fortran-unit-test-syntax/solution/test/test_find_steady_state.pf b/episodes/4-fortran-unit-test-syntax/solution/test/test_find_steady_state.pf similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/test/test_find_steady_state.pf rename to episodes/4-fortran-unit-test-syntax/solution/test/test_find_steady_state.pf diff --git a/episodes/3-fortran-unit-test-syntax/solution/test/test_read_model_from_file.pf b/episodes/4-fortran-unit-test-syntax/solution/test/test_read_model_from_file.pf similarity index 100% rename from episodes/3-fortran-unit-test-syntax/solution/test/test_read_model_from_file.pf rename to episodes/4-fortran-unit-test-syntax/solution/test/test_read_model_from_file.pf diff --git a/episodes/4-debugging-a-broken-test/challenge/CMakeLists.txt b/episodes/5-debugging-a-broken-test/challenge/CMakeLists.txt similarity index 100% rename from episodes/4-debugging-a-broken-test/challenge/CMakeLists.txt rename to episodes/5-debugging-a-broken-test/challenge/CMakeLists.txt diff --git a/episodes/4-debugging-a-broken-test/challenge/README.md b/episodes/5-debugging-a-broken-test/challenge/README.md similarity index 100% rename from episodes/4-debugging-a-broken-test/challenge/README.md rename to episodes/5-debugging-a-broken-test/challenge/README.md diff --git a/episodes/4-debugging-a-broken-test/challenge/src/matrix_transforms.f90 b/episodes/5-debugging-a-broken-test/challenge/src/matrix_transforms.f90 similarity index 100% rename from episodes/4-debugging-a-broken-test/challenge/src/matrix_transforms.f90 rename to episodes/5-debugging-a-broken-test/challenge/src/matrix_transforms.f90 diff --git a/episodes/4-debugging-a-broken-test/challenge/test/.gitignore b/episodes/5-debugging-a-broken-test/challenge/test/.gitignore similarity index 100% rename from episodes/4-debugging-a-broken-test/challenge/test/.gitignore rename to episodes/5-debugging-a-broken-test/challenge/test/.gitignore diff --git a/episodes/4-debugging-a-broken-test/challenge/test/CMakeLists.txt b/episodes/5-debugging-a-broken-test/challenge/test/CMakeLists.txt similarity index 100% rename from episodes/4-debugging-a-broken-test/challenge/test/CMakeLists.txt rename to episodes/5-debugging-a-broken-test/challenge/test/CMakeLists.txt diff --git a/episodes/4-debugging-a-broken-test/challenge/test/test_transpose.pf b/episodes/5-debugging-a-broken-test/challenge/test/test_transpose.pf similarity index 100% rename from episodes/4-debugging-a-broken-test/challenge/test/test_transpose.pf rename to episodes/5-debugging-a-broken-test/challenge/test/test_transpose.pf diff --git a/episodes/4-debugging-a-broken-test/solution/README.md b/episodes/5-debugging-a-broken-test/solution/README.md similarity index 100% rename from episodes/4-debugging-a-broken-test/solution/README.md rename to episodes/5-debugging-a-broken-test/solution/README.md diff --git a/episodes/5-testing-parallel-code/challenge/.gitignore b/episodes/6-testing-parallel-code/challenge/.gitignore similarity index 100% rename from episodes/5-testing-parallel-code/challenge/.gitignore rename to episodes/6-testing-parallel-code/challenge/.gitignore diff --git a/episodes/5-testing-parallel-code/challenge/CMakeLists.txt b/episodes/6-testing-parallel-code/challenge/CMakeLists.txt similarity index 100% rename from episodes/5-testing-parallel-code/challenge/CMakeLists.txt rename to episodes/6-testing-parallel-code/challenge/CMakeLists.txt diff --git a/episodes/5-testing-parallel-code/challenge/README.md b/episodes/6-testing-parallel-code/challenge/README.md similarity index 100% rename from episodes/5-testing-parallel-code/challenge/README.md rename to episodes/6-testing-parallel-code/challenge/README.md diff --git a/episodes/5-testing-parallel-code/challenge/src/game_of_life.f90 b/episodes/6-testing-parallel-code/challenge/src/game_of_life.f90 similarity index 100% rename from episodes/5-testing-parallel-code/challenge/src/game_of_life.f90 rename to episodes/6-testing-parallel-code/challenge/src/game_of_life.f90 diff --git a/episodes/5-testing-parallel-code/challenge/src/game_of_life_mod.f90 b/episodes/6-testing-parallel-code/challenge/src/game_of_life_mod.f90 similarity index 100% rename from episodes/5-testing-parallel-code/challenge/src/game_of_life_mod.f90 rename to episodes/6-testing-parallel-code/challenge/src/game_of_life_mod.f90 diff --git a/episodes/5-testing-parallel-code/challenge/test/CMakeLists.txt b/episodes/6-testing-parallel-code/challenge/test/CMakeLists.txt similarity index 100% rename from episodes/5-testing-parallel-code/challenge/test/CMakeLists.txt rename to episodes/6-testing-parallel-code/challenge/test/CMakeLists.txt diff --git a/episodes/5-testing-parallel-code/challenge/test/models/empty_-10_10.dat b/episodes/6-testing-parallel-code/challenge/test/models/empty_-10_10.dat similarity index 100% rename from episodes/5-testing-parallel-code/challenge/test/models/empty_-10_10.dat rename to episodes/6-testing-parallel-code/challenge/test/models/empty_-10_10.dat diff --git a/episodes/5-testing-parallel-code/challenge/test/models/empty_10_-10.dat b/episodes/6-testing-parallel-code/challenge/test/models/empty_10_-10.dat similarity index 100% rename from episodes/5-testing-parallel-code/challenge/test/models/empty_10_-10.dat rename to episodes/6-testing-parallel-code/challenge/test/models/empty_10_-10.dat diff --git a/episodes/5-testing-parallel-code/challenge/test/models/zeros_31_31.dat b/episodes/6-testing-parallel-code/challenge/test/models/zeros_31_31.dat similarity index 100% rename from episodes/5-testing-parallel-code/challenge/test/models/zeros_31_31.dat rename to episodes/6-testing-parallel-code/challenge/test/models/zeros_31_31.dat diff --git a/episodes/5-testing-parallel-code/challenge/test/test_check_for_steady_state.pf b/episodes/6-testing-parallel-code/challenge/test/test_check_for_steady_state.pf similarity index 100% rename from episodes/5-testing-parallel-code/challenge/test/test_check_for_steady_state.pf rename to episodes/6-testing-parallel-code/challenge/test/test_check_for_steady_state.pf diff --git a/episodes/5-testing-parallel-code/challenge/test/test_evolve_board.pf b/episodes/6-testing-parallel-code/challenge/test/test_evolve_board.pf similarity index 100% rename from episodes/5-testing-parallel-code/challenge/test/test_evolve_board.pf rename to episodes/6-testing-parallel-code/challenge/test/test_evolve_board.pf diff --git a/episodes/5-testing-parallel-code/challenge/test/test_exchange_boundaries.pf b/episodes/6-testing-parallel-code/challenge/test/test_exchange_boundaries.pf similarity index 100% rename from episodes/5-testing-parallel-code/challenge/test/test_exchange_boundaries.pf rename to episodes/6-testing-parallel-code/challenge/test/test_exchange_boundaries.pf diff --git a/episodes/5-testing-parallel-code/challenge/test/test_find_steady_state.pf b/episodes/6-testing-parallel-code/challenge/test/test_find_steady_state.pf similarity index 100% rename from episodes/5-testing-parallel-code/challenge/test/test_find_steady_state.pf rename to episodes/6-testing-parallel-code/challenge/test/test_find_steady_state.pf diff --git a/episodes/5-testing-parallel-code/challenge/test/test_get_local_grid_info.pf b/episodes/6-testing-parallel-code/challenge/test/test_get_local_grid_info.pf similarity index 100% rename from episodes/5-testing-parallel-code/challenge/test/test_get_local_grid_info.pf rename to episodes/6-testing-parallel-code/challenge/test/test_get_local_grid_info.pf diff --git a/episodes/5-testing-parallel-code/challenge/test/test_read_model_from_file.pf b/episodes/6-testing-parallel-code/challenge/test/test_read_model_from_file.pf similarity index 100% rename from episodes/5-testing-parallel-code/challenge/test/test_read_model_from_file.pf rename to episodes/6-testing-parallel-code/challenge/test/test_read_model_from_file.pf diff --git a/episodes/5-testing-parallel-code/models/model-1.dat b/episodes/6-testing-parallel-code/models/model-1.dat similarity index 100% rename from episodes/5-testing-parallel-code/models/model-1.dat rename to episodes/6-testing-parallel-code/models/model-1.dat diff --git a/episodes/5-testing-parallel-code/solution/README.md b/episodes/6-testing-parallel-code/solution/README.md similarity index 100% rename from episodes/5-testing-parallel-code/solution/README.md rename to episodes/6-testing-parallel-code/solution/README.md diff --git a/episodes/5-testing-parallel-code/solution/test_find_steady_state.pf b/episodes/6-testing-parallel-code/solution/test_find_steady_state.pf similarity index 100% rename from episodes/5-testing-parallel-code/solution/test_find_steady_state.pf rename to episodes/6-testing-parallel-code/solution/test_find_steady_state.pf