Quickstart (using Meson)

Quickstart (using Meson)#

You will learn to

  • create a minimal project with Meson,

  • add unit tests to the project,

  • build the project and run the tests.

Before jumping in#

To begin this quickstart tutorial on Fortuno, ensure to have a recent version of the Meson build system (version 1.5 or newer) and a Fortran compiler that implements the Fortran 2018 standard. Fortuno operates smoothly with recent versions of several popular Fortran compilers, but older Fortran compilers are known to fail to build it. Please check the minimal compiler versions in the Fortuno readme.

Getting comfortable#

We’ll create a library named mylib containing a single function factorial() to calculate the factorial of an integer. The testing of the library shall be automated using unit tests.

Create a directory (e.g. mylib) for the project with the 4 subdirectories src, app, test and subprojects.

mkdir mylib
cd mylib
mkdir src app test subprojects

We develop the first version of our library by creating src/mylib.f90:

src/mylib.f90#
!> Demo library to be unit-tested.
module mylib
  implicit none

  private
  public :: factorial

contains

  !> Calculates the factorial of a number.
  function factorial(nn) result(fact)

    !> number to calculate the factorial of
    integer, intent(in) :: nn

    !> factorial (note, there is no check made for integer overflow!)
    integer :: fact

    integer :: ii

    fact = 1
    do ii = 2, nn
      fact = fact * ii
    end do

  end function factorial

end module mylib

The main executable of our project (app/main.f90) should just print out the factorial for three specific values, so that we can check whether our factorial() function works as expected:

app/main.f90#
program main
  use mylib, only: factorial
  implicit none

  print "('factorial(', i0, ') = ', i0)",&
      & 0, factorial(0),&
      & 1, factorial(1),&
      & 2, factorial(2)

end program main

Now, let’s automatize the testing procedure. We will write three unit tests, which check the factorial function for the specific input values 0, 1 and 2. The last test should intentionally fail to demonstrate the error reporting. Create a file test/testapp.f90 with following content:

test/testapp.f90#
!> Fortuno unit tests
module test_mylib
  use mylib, only : factorial
  use fortuno_serial, only : is_equal, test => serial_case_item, check => serial_check, test_list
  implicit none

contains

  function tests()
    type(test_list) :: tests

    tests = test_list([&
        test("factorial_0", test_factorial_0),&
        test("factorial_1", test_factorial_1),&
        test("factorial_2", test_factorial_2)&
    ])

  end function tests

  ! Test: 0! = 1
  subroutine test_factorial_0()
    call check(factorial(0) == 1)
  end subroutine test_factorial_0

  ! Test: 1! = 1
  subroutine test_factorial_1()
    call check(is_equal(factorial(1), 1))
  end subroutine test_factorial_1

  ! Test: 2! = 3 (will fail to demonstrate the output of a failing test)
  subroutine test_factorial_2()
    ! Failing check, you should obtain detailed info about the failure.
    call check(&
        & is_equal(factorial(2), 3),&
        & msg="Test failed for demonstration purposes"&
    )
  end subroutine test_factorial_2

end module test_mylib


!> Test app driving Fortuno unit tests.
program testapp
  use test_mylib, only : tests
  use fortuno_serial, only : execute_serial_cmd_app
  implicit none

  call execute_serial_cmd_app(tests())

end program testapp

Finally, we create a meson.build in the main folder to describe the project for Meson:

Note

This is a highly minimalistic Meson configuration file created for demonstration purpose only.

meson.build#
project('mylib', 'fortran')

fortuno_serial_dep = dependency('fortuno-serial', fallback: ['fortuno', 'fortuno_serial_dep'])

mylib_lib = library('mylib', sources: files('src/mylib.f90'))
mylib_dep = declare_dependency(link_with: mylib_lib)

mylib_app_exe = executable(
  'mylib_app',
  sources: files('app/main.f90'),
  dependencies: [mylib_dep]
)

testapp_exe = executable(
  'testapp',
  sources: files('test/testapp.f90'),
  dependencies: [mylib_dep, fortuno_serial_dep]
)
test('testapp', testapp_exe)

Additionally, in order to be able to fetch and build Fortuno during the build, an appropriate wrap-file fortuno.wrap must be created in the subprojects folder.

subprojects/fortuno.wrap#
[wrap-git]
directory=fortuno
url=https://github.com/fortuno-repos/fortuno.git
revision=v0.1.0

Now, we configure the project by running:

FC=gfortran meson setup build

Then we build our library, the main app and the tests.

ninja -C build

Finally we run the unit tests by invoking meson again:

meson test -v -C build

The expected output will show a test failure for the test application. The verbose output reveals that the test app launched three unit tests in total, one of them failing.

Output of the “ctest” command#
=== Fortuno - flextensible unit testing framework for Fortran ===

# Executing test items
..F

# Logged event(s)

Failed     [run] factorial_2

-> Unsuccessful check
Check: 1
Msg: Test failed for demonstration purposes
Failure: mismatching integer values
Value1: 2
Value2: 3


# Test runs
Total:      3
Succeeded:  2  ( 66.7%)
Failed:     1  ( 33.3%)

=== FAILED ===
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
1/1 testapp FAIL            0.01s   exit status 1


Summary of Failures:

1/1 testapp FAIL            0.01s   exit status 1

Ok:                 0   
Expected Fail:      0   
Fail:               1   
Unexpected Pass:    0   
Skipped:            0   
Timeout:            0   

Congratulations! You’ve now implemented and completed your first set of Fortuno unit tests, assessing your project’s integrity.

See also

  • Section Key concepts contains a detailed analyzis of this minimal test application and also more information on some key concepts.

  • For real projects, consider to use the Fortran project cookiecutter template to generate a fully featured Meson setup following best practices.