From 63d79da61b810cf70e7789b2734705a092c3ae0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Aradi?= Date: Sun, 24 Mar 2024 15:38:46 +0100 Subject: [PATCH] Use character representable objects for failure details and test state --- example/CMakeLists.txt | 1 + example/fixtured_tests.f90 | 54 +++--- example/meson.build | 1 + example/mylib.f90 | 2 + example/parametrized_tests.f90 | 70 ++++++++ example/simple_tests.f90 | 12 +- example/testapp.f90 | 4 +- src/fortuno.f90 | 5 +- src/fortuno/CMakeLists.txt | 1 + src/fortuno/argumentparser.f90 | 2 +- src/fortuno/basetypes.f90 | 18 -- src/fortuno/chartypes.f90 | 223 +++++++++++++++++++++++++ src/fortuno/checkers.f90 | 39 +---- src/fortuno/consolelogger.f90 | 4 +- src/fortuno/meson.build | 1 + src/fortuno/testcontext.f90 | 34 ++++ src/fortuno/testdriver.f90 | 18 +- src/fortuno/testinfo.f90 | 38 +---- src/fortuno/utils.f90 | 28 ++++ src/fortuno_serial.f90 | 2 +- src/fortuno_serial/serialbasetypes.f90 | 2 +- src/fortuno_serial/serialcase.f90 | 2 +- src/fortuno_serial/serialconlogger.f90 | 2 +- src/fortuno_serial/serialglobalctx.f90 | 15 +- 24 files changed, 438 insertions(+), 140 deletions(-) create mode 100644 example/parametrized_tests.f90 create mode 100644 src/fortuno/chartypes.f90 diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 2426b0a..5c59a14 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -22,6 +22,7 @@ set_target_properties( target_sources( fortuno_example_testapp PRIVATE fixtured_tests.f90 + parametrized_tests.f90 simple_tests.f90 testapp.f90 ) diff --git a/example/fixtured_tests.f90 b/example/fixtured_tests.f90 index a2203b0..db9baf3 100644 --- a/example/fixtured_tests.f90 +++ b/example/fixtured_tests.f90 @@ -4,8 +4,9 @@ module fixtured_tests use mylib, only : factorial - use fortuno_serial, only : check => serial_check, test => serial_case_item,& - & suite => serial_suite_item, serial_case_base, test_item + use fortuno_serial, only : char_rep_int, check => serial_check, is_equal, named_state,& + & named_item, suite => serial_suite_item, store_state => serial_store_state,& + & serial_case_base, test_item implicit none private @@ -15,18 +16,14 @@ module fixtured_tests ! Fixtured test case creating a random number before running a test procedure. type, extends(serial_case_base) :: random_test_case - ! The random integer generated for a given test instance. - integer :: nn = -1 - - ! Test procedure to invoke once fixture had been created. + ! Test procedure to be invoked once fixture setup had been executed procedure(test_recursion_down), pointer, nopass :: proc + contains - ! Overrides run procedure to execute fixture. + ! Overrides run procedure to set up fixture before test procedure is invoked. procedure :: run => random_test_case_run - !> Provides character representation of the internal state. - procedure :: get_as_char => random_test_case_get_as_char end type random_test_case contains @@ -51,20 +48,18 @@ end function get_fixtured_tests ! TEST n! = n * (n - 1)! subroutine test_recursion_down(nn) integer, intent(in) :: nn - call check(factorial(nn) == nn * factorial(nn - 1)) + call check(is_equal(factorial(nn), nn * factorial(nn - 1))) end subroutine test_recursion_down ! TEST (n + 1)! = (n + 1) * n! subroutine test_recursion_up(nn) integer, intent(in) :: nn - call check(factorial(nn + 1) == (nn + 1) * factorial(nn)) - ! Creating a "random" error to demonstrate failure reporting with internal state - call check(nn < 15) + call check(is_equal(factorial(nn + 1), (nn + 1) * factorial(nn))) end subroutine test_recursion_up - ! Returns a random_test_case instance wrapped as test_item. + ! Convenience function returning a random_test_case instance wrapped as test_item. function random_test(name, proc) result(testitem) character(*), intent(in) :: name procedure(test_recursion_down) :: proc @@ -75,32 +70,25 @@ function random_test(name, proc) result(testitem) end function random_test - ! Runs procedure of the random_test_case class. + ! Run procedure of the random_test_case class. subroutine random_test_case_run(this) - class(random_test_case), intent(inout) :: this + class(random_test_case), intent(in) :: this real :: rand + integer :: nn ! Set-up fixture by creating a random number - ! Number is stored as instance variable so that character representation of the internal state - ! can be obtained after test had been executed. call random_number(rand) - this%nn = int(20.0 * rand) + 1 - call this%proc(this%nn) + ! Note: factorial(n) with n > 13 overflows with 32 bit integers + nn = int(13 * rand) + 1 + ! Store internal state to aid introspection/identification later + call store_state(& + named_state([& + named_item("n", char_rep_int(nn))& + &])& + ) + call this%proc(nn) end subroutine random_test_case_run - - ! Returns representation of the internal state of a random_test_case_instance. - subroutine random_test_case_get_as_char(this, repr) - class(random_test_case), intent(in) :: this - character(:), allocatable, intent(out) :: repr - - character(5) :: buffer - - write(buffer, "(a3, i2.2)") "n: ", this%nn - repr = buffer - - end subroutine random_test_case_get_as_char - end module fixtured_tests diff --git a/example/meson.build b/example/meson.build index 8ece888..a509d1f 100644 --- a/example/meson.build +++ b/example/meson.build @@ -15,6 +15,7 @@ example_testapp_exe = executable( 'testapp', sources: [ 'fixtured_tests.f90', + 'parametrized_tests.f90', 'simple_tests.f90', 'testapp.f90', ], diff --git a/example/mylib.f90 b/example/mylib.f90 index 39cffe3..7b8dbd5 100644 --- a/example/mylib.f90 +++ b/example/mylib.f90 @@ -26,6 +26,8 @@ function factorial(nn) result(fact) do ii = 2, nn fact = fact * ii end do + ! We create a "bug" which manifests only for certain input values + if (nn == 2 .or. nn > 10) fact = fact - 1 end function factorial diff --git a/example/parametrized_tests.f90 b/example/parametrized_tests.f90 new file mode 100644 index 0000000..30681cb --- /dev/null +++ b/example/parametrized_tests.f90 @@ -0,0 +1,70 @@ +! This file is part of Fortuno. +! Licensed under the BSD-2-Clause Plus Patent license. +! SPDX-License-Identifier: BSD-2-Clause-Patent + +module parametrized_tests + use mylib, only : factorial + use fortuno_serial, only : as_char, is_equal, serial_case_base, check => serial_check,& + & suite => serial_suite_item, test_item + implicit none + + private + public :: get_parametrized_tests + + + !> Contains argument and expected result of a factorial() invokation + type :: arg_res + integer :: arg, res + end type + + !> Argument/result pairs to check for + type(arg_res), parameter :: testcaseparams(*) = [& + & arg_res(1, 1), arg_res(2, 2), arg_res(3, 6), arg_res(4, 24), arg_res(5, 120)& + & ] + + + !> Parametrized test checking for an individual argument/result pair. + type, extends(serial_case_base) :: parametrized_test_case + type(arg_res) :: argres + contains + procedure :: run => parametrized_test_case_run + end type parametrized_test_case + +contains + + + !> Returns the tests of this module. + function get_parametrized_tests() result(testitems) + type(test_item), allocatable :: testitems(:) + + integer :: ii + + testitems = [& + suite("parametrized", [& + (parametrized_test("factorial", testcaseparams(ii)), ii = 1, size(testcaseparams))& + ])& + ] + + end function get_parametrized_tests + + + !> Convenience wrapper to generate a test case for a given argres pair. + function parametrized_test(prefix, argres) result(testitem) + character(*), intent(in) :: prefix + type(arg_res), intent(in) :: argres + type(test_item) :: testitem + + testitem%item = parametrized_test_case(name=prefix // "_" // as_char(argres%arg), argres=argres) + + end function parametrized_test + + + !> Run method of the parametrized test (executing the check directly) + subroutine parametrized_test_case_run(this) + class(parametrized_test_case), intent(in) :: this + + call check(is_equal(factorial(this%argres%arg), this%argres%res)) + + end subroutine parametrized_test_case_run + +end module parametrized_tests diff --git a/example/simple_tests.f90 b/example/simple_tests.f90 index 5325bde..d8ca117 100644 --- a/example/simple_tests.f90 +++ b/example/simple_tests.f90 @@ -18,12 +18,12 @@ function get_simple_tests() result(testitems) type(test_item), allocatable :: testitems(:) testitems = [& - ! Adding a single test + ! Adding a single test not belonging to any test suite test("factorial_0", test_factorial_0),& ! Packing further tests into a suite in order to introduce more structure ! (e.g. running only tests being part of a given suite) - suite("mysuite", [& + suite("simple", [& test("factorial_1", test_factorial_1),& test("factorial_2", test_factorial_2)& ])& @@ -41,14 +41,14 @@ subroutine test_factorial_1() call check(factorial(1) == 1) end subroutine test_factorial_1 - ! Test: 2! = 2 (will fail to demonstrate the output of a failing test) + ! Test: 2! = 2 (will fail due to the bug in the implementation of the factorial function) subroutine test_factorial_2() ! Two failing checks, you should see info about both in the output - call check(is_equal(factorial(2), 3),& + call check(is_equal(factorial(2), 2),& & msg="Test failed for demonstration purposes",& & file="simple_tests.f90",& - & line=43) - call check(factorial(2) == 3) + & line=45) + call check(factorial(2) == 2) end subroutine test_factorial_2 end module simple_tests diff --git a/example/testapp.f90 b/example/testapp.f90 index c844c7b..1667e8f 100644 --- a/example/testapp.f90 +++ b/example/testapp.f90 @@ -7,12 +7,14 @@ program testapp use fortuno_serial, only : execute_serial_cmd_app use simple_tests, only : get_simple_tests use fixtured_tests, only : get_fixtured_tests + use parametrized_tests, only : get_parametrized_tests implicit none call execute_serial_cmd_app(& testitems=[& get_simple_tests(),& - get_fixtured_tests()& + get_fixtured_tests(),& + get_parametrized_tests()& ]& ) diff --git a/src/fortuno.f90 b/src/fortuno.f90 index 9093535..52bc639 100644 --- a/src/fortuno.f90 +++ b/src/fortuno.f90 @@ -5,13 +5,14 @@ !> Interface module for the core library of the Fortuno testing framework module fortuno use fortuno_basetypes, only : test_base, test_case_base, test_item, test_ptr_item, test_suite_base + use fortuno_chartypes, only : char_rep, char_rep_int, named_details, named_item, named_state use fortuno_consolelogger, only : console_logger use fortuno_testcontext, only : context_factory, test_context use fortuno_checkers, only : is_equal use fortuno_cmdapp, only : cmd_app use fortuno_testdriver, only : init_test_driver, test_driver, test_runner, test_selection - use fortuno_testinfo, only : check_result, drive_result, failure_details, failure_info,& - & failure_location, init_drive_result, init_failure_location, test_result, teststatus + use fortuno_testinfo, only : check_result, drive_result, failure_info, failure_location,& + & init_drive_result, init_failure_location, test_result, teststatus use fortuno_utils, only : as_char, nl implicit none diff --git a/src/fortuno/CMakeLists.txt b/src/fortuno/CMakeLists.txt index 2e83984..7a6f2bd 100644 --- a/src/fortuno/CMakeLists.txt +++ b/src/fortuno/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources( fortuno PRIVATE argumentparser.f90 basetypes.f90 + chartypes.f90 checkers.f90 cmdapp.f90 consolelogger.f90 diff --git a/src/fortuno/argumentparser.f90 b/src/fortuno/argumentparser.f90 index e8a3be9..9133e37 100644 --- a/src/fortuno/argumentparser.f90 +++ b/src/fortuno/argumentparser.f90 @@ -120,7 +120,7 @@ subroutine argument_parser_parse_args(this, argumentvalues, logger, exitcode) type(string), allocatable :: cmdargs(:), posargs(:) logical, allocatable :: processed(:) - character(:), allocatable :: argname, errormsg + character(:), allocatable :: argname integer :: nargs, nargdefs, iarg, iargdef logical optionsallowed, islong, matches diff --git a/src/fortuno/basetypes.f90 b/src/fortuno/basetypes.f90 index eafac24..2c5494a 100644 --- a/src/fortuno/basetypes.f90 +++ b/src/fortuno/basetypes.f90 @@ -20,10 +20,6 @@ module fortuno_basetypes !> Name of the generic test character(:), allocatable :: name - contains - - procedure :: get_as_char => test_base_get_as_char - end type test_base @@ -58,18 +54,4 @@ module fortuno_basetypes end type test_suite_base -contains - - - !> Delivers the character representation of the internal state of a test object - subroutine test_base_get_as_char(this, repr) - - !> Instance - class(test_base), intent(in) :: this - - !> Character representation, or **unallocated** on exit, if there is none - character(:), allocatable, intent(out) :: repr - - end subroutine test_base_get_as_char - end module fortuno_basetypes diff --git a/src/fortuno/chartypes.f90 b/src/fortuno/chartypes.f90 new file mode 100644 index 0000000..fe673bf --- /dev/null +++ b/src/fortuno/chartypes.f90 @@ -0,0 +1,223 @@ +! This file is part of Fortuno. +! Licensed under the BSD-2-Clause Plus Patent license. +! SPDX-License-Identifier: BSD-2-Clause-Patent + +!> Contains various types related to character representations. +module fortuno_chartypes + use fortuno_utils, only : as_char, as_upper, nl, string + implicit none + + private + public :: char_rep + public :: char_rep_int + public :: named_item, named_details, named_state + + + !> Character representable object. + type, abstract :: char_rep + contains + procedure(char_rep_as_char), deferred :: as_char + end type char_rep + + + abstract interface + + !> Character representation of the char_rep object. + function char_rep_as_char(this) result(repr) + import :: char_rep + implicit none + + !> Instance + class(char_rep), intent(in) :: this + + !> Character representation of the object. + character(:), allocatable :: repr + + end function char_rep_as_char + + end interface + + + !> Implements a named item of arbitrary type + type :: named_item + + !> Name + character(:), allocatable :: name + + !> Value associated with the name + class(*), allocatable :: value + + end type named_item + + + ! Workaround:gfortran:13.2 + ! Needs user defined structure constructor as default constructor can not deal with class(*) field + interface named_item + module procedure new_named_item + end interface + + + !> Represents failure details with an array of named items. + type, extends(char_rep) :: named_details + + !> Items containing the information about the failure details + type(named_item), allocatable :: items(:) + + contains + procedure :: as_char => named_details_as_char + end type named_details + + + !> Represents test internal state with an array of named items. + type, extends(char_rep) :: named_state + + !> Items containing the information about the failure details + type(named_item), allocatable :: items(:) + + contains + procedure :: as_char => named_state_as_char + end type named_state + + + !> Character representable integer. + type, extends(char_rep) :: char_rep_int + + !> Value + integer :: value + + contains + procedure :: as_char => char_rep_int_as_char + end type char_rep_int + +contains + + + !> Returns the character representation of the failure details. + function named_details_as_char(this) result(repr) + + !> Instance + class(named_details), intent(in) :: this + + !> Character representation + character(:), allocatable :: repr + + if (.not. allocated(this%items)) then + repr = "" + return + end if + call get_named_items_as_char_(this%items, repr, itemsep=nl, namesep=": ",& + & capitalizename=.true.) + + end function named_details_as_char + + + !> Returns the character representation of an internal test state. + function named_state_as_char(this) result(repr) + + !> Instance + class(named_state), intent(in) :: this + + !> Character representation + character(:), allocatable :: repr + + if (.not. allocated(this%items)) then + repr = "" + return + end if + call get_named_items_as_char_(this%items, repr, itemsep=nl, namesep=":",& + & capitalizename=.false.) + + end function named_state_as_char + + + !> Integer with string representation. + function char_rep_int_as_char(this) result(repr) + + !> Instance + class(char_rep_int), intent(in) :: this + + !> Character representation + character(:), allocatable :: repr + + repr = as_char(this%value) + + end function char_rep_int_as_char + + + !> Explicit constructor for named_item (to avoid gfortran compilation problems) + function new_named_item(name, val) result(this) + + !> Name of the item + character(*), intent(in) :: name + + !> Value of the item + class(*), intent(in) :: val + + !> Initialized instance + type(named_item) :: this + + this%name = name + allocate(this%value, source=val) + + end function new_named_item + + + !! Returns the character representation of an array of named items. + subroutine get_named_items_as_char_(items, repr, itemsep, namesep, capitalizename) + type(named_item), intent(in) :: items(:) + character(:), allocatable, intent(out) :: repr + character(*), intent(in) :: itemsep, namesep + logical, intent(in) :: capitalizename + + integer :: nitems, iitem, pos, reprlen, itemseplen, nameseplen + type(string), allocatable :: valuestrings(:) + + nitems = size(items) + if (nitems == 0) then + repr = "" + return + end if + reprlen = 0 + allocate(valuestrings(size(items))) + do iitem = 1, nitems + reprlen = reprlen + len(items(iitem)%name) + select type (namedvalue => items(iitem)%value) + type is (character(*)) + valuestrings(iitem)%content = namedvalue + class is (string) + valuestrings(iitem)%content = namedvalue%content + class is (char_rep) + valuestrings(iitem)%content = namedvalue%as_char() + class default + valuestrings(iitem)%content = "???" + end select + reprlen = reprlen + len(valuestrings(iitem)%content) + end do + + nameseplen = len(namesep) + itemseplen = len(itemsep) + reprlen = reprlen + nitems * nameseplen + (nitems - 1) * itemseplen + allocate(character(reprlen) :: repr) + + pos = 1 + do iitem = 1, nitems + associate(name => items(iitem)%name, valstr => valuestrings(iitem)%content) + reprlen = len(name) + repr(pos : pos + reprlen - 1) = name + if (capitalizename) repr(pos:pos) = as_upper(repr(pos:pos)) + pos = pos + reprlen + repr(pos : pos + nameseplen - 1) = namesep + pos = pos + nameseplen + reprlen = len(valstr) + repr(pos : pos + reprlen - 1) = valstr + pos = pos + reprlen + if (iitem /= nitems) then + repr(pos : pos + itemseplen - 1) = itemsep + pos = pos + itemseplen + end if + end associate + end do + + end subroutine get_named_items_as_char_ + +end module fortuno_chartypes diff --git a/src/fortuno/checkers.f90 b/src/fortuno/checkers.f90 index 0c40df3..228bc06 100644 --- a/src/fortuno/checkers.f90 +++ b/src/fortuno/checkers.f90 @@ -4,8 +4,8 @@ !> Contains some built-in checkers module fortuno_checkers - use fortuno_testinfo, only : check_result, failure_details - use fortuno_utils, only : as_char, nl + use fortuno_chartypes, only : char_rep_int, named_details, named_item + use fortuno_testinfo, only : check_result implicit none private @@ -17,37 +17,8 @@ module fortuno_checkers module procedure is_equal_i0_i0 end interface is_equal - - !> Details of an integer-integer check - type, extends(failure_details) :: failure_details_i0_i0 - - !> Value obtained - integer :: obtained - - !> Value expected - integer :: expected - - contains - procedure :: as_char => failure_details_i0_i0_as_char - end type failure_details_i0_i0 - contains - !> Character representation of an integer-integer check - function failure_details_i0_i0_as_char(this) result(repr) - - !> Instance - class(failure_details_i0_i0), intent(in) :: this - - !> Character representation of the instance - character(:), allocatable :: repr - - repr = "Mismatching integer values" // nl& - & // "Obtained: " // as_char(this%obtained) // nl& - & // "Expected: " // as_char(this%expected) - - end function failure_details_i0_i0_as_char - !> Checks whether two integer values are equal function is_equal_i0_i0(obtained, expected) result(checkresult) @@ -63,7 +34,11 @@ function is_equal_i0_i0(obtained, expected) result(checkresult) checkresult%success = (obtained == expected) if (.not. checkresult%success) then - checkresult%details = failure_details_i0_i0(obtained, expected) + checkresult%details = named_details([& + & named_item("failure", "mismatching integer values"),& + & named_item("expected", char_rep_int(expected)),& + & named_item("obtained", char_rep_int(obtained))& + & ]) end if end function is_equal_i0_i0 diff --git a/src/fortuno/consolelogger.f90 b/src/fortuno/consolelogger.f90 index 00e81be..8e5e739 100644 --- a/src/fortuno/consolelogger.f90 +++ b/src/fortuno/consolelogger.f90 @@ -5,7 +5,7 @@ !> Contains the implementation of the test logger for logging on the console module fortuno_consolelogger use fortuno_testinfo, only : drive_result, failure_info, test_result, teststatus - use fortuno_testlogger, only : test_logger, testtypes + use fortuno_testlogger, only : test_logger use fortuno_utils, only : ansicolors, as_char, stderr, stdout implicit none @@ -90,8 +90,6 @@ subroutine console_logger_get_failure_info_repr(this, failureinfo, location, mes !> Details string (unallocated if not available or not relevant) character(:), allocatable, intent(out) :: details - character(:), allocatable :: buffer - location = failureinfo%location%as_char() if (allocated(failureinfo%message)) message = failureinfo%message if (allocated(failureinfo%details)) details = failureinfo%details%as_char() diff --git a/src/fortuno/meson.build b/src/fortuno/meson.build index 3493a78..df6e17e 100644 --- a/src/fortuno/meson.build +++ b/src/fortuno/meson.build @@ -5,6 +5,7 @@ fortuno_sources += files( 'argumentparser.f90', 'basetypes.f90', + 'chartypes.f90', 'checkers.f90', 'cmdapp.f90', 'consolelogger.f90', diff --git a/src/fortuno/testcontext.f90 b/src/fortuno/testcontext.f90 index 695e6bc..c3524a3 100644 --- a/src/fortuno/testcontext.f90 +++ b/src/fortuno/testcontext.f90 @@ -5,6 +5,7 @@ !> Contains the base context definition module fortuno_testcontext use fortuno_basetypes, only : test_base, test_ptr_item + use fortuno_chartypes, only : char_rep use fortuno_testinfo, only : check_result, failure_info, failure_location, init_failure_location,& & teststatus implicit none @@ -23,6 +24,9 @@ module fortuno_testcontext !> Info about check failures in current context type(failure_info), allocatable :: failureinfo_ + !> Info about the internal state of the test + class(char_rep), allocatable :: state_ + !> Status of the context integer :: status_ = teststatus%succeeded @@ -48,6 +52,8 @@ module fortuno_testcontext procedure :: push_scope_ptr => test_context_push_scope_ptr procedure :: scope_pointers => test_context_scope_pointers procedure :: create_failure_location => test_context_create_failure_location + procedure :: store_state => test_context_store_state + procedure :: pop_state => test_context_pop_state end type test_context @@ -311,4 +317,32 @@ subroutine test_context_create_failure_location(this, failureloc, file, line) end subroutine test_context_create_failure_location + + !> Stores the internal state of the test for better identification/introspection + subroutine test_context_store_state(this, state) + + !> Instane + class(test_context), intent(inout) :: this + + !> Arbitrary (character representable) state object + class(char_rep), intent(in) :: state + + this%state_ = state + + end subroutine test_context_store_state + + + !> Pops the test state from the context + subroutine test_context_pop_state(this, state) + + !> Instance + class(test_context), intent(inout) :: this + + !> Popped state object + class(char_rep), allocatable, intent(out) :: state + + if (allocated(this%state_)) call move_alloc(this%state_, state) + + end subroutine test_context_pop_state + end module fortuno_testcontext diff --git a/src/fortuno/testdriver.f90 b/src/fortuno/testdriver.f90 index 0188282..d4a3ecf 100644 --- a/src/fortuno/testdriver.f90 +++ b/src/fortuno/testdriver.f90 @@ -4,8 +4,8 @@ !> Implements a generic test driver module fortuno_testdriver - use fortuno_basetypes, only : test_item, test_base - use fortuno_basetypes, only : test_case_base, test_suite_base + use fortuno_basetypes, only : test_base, test_case_base, test_item, test_suite_base + use fortuno_chartypes, only : char_rep use fortuno_testcontext, only : context_factory, test_context use fortuno_testinfo, only : drive_result, init_drive_result, test_result, teststatus use fortuno_testlogger, only : test_logger, testtypes @@ -359,7 +359,7 @@ subroutine init_test_data_container(this, initsize) !> Initial container size integer, intent(in) :: initsize - allocate(this%storage_(100)) + allocate(this%storage_(initsize)) ! Setting testdata pointer up, so that it has size 0. this%testdata => this%storage_(1:0) @@ -406,6 +406,7 @@ recursive subroutine run_test_(testitems, identifier, ctx, runner, repr) character(:), allocatable, intent(out) :: repr class(test_base), pointer :: scopeptr + class(char_rep), allocatable :: state scopeptr => testitems(identifier(1))%item call ctx%push_scope_ptr(scopeptr) @@ -413,7 +414,8 @@ recursive subroutine run_test_(testitems, identifier, ctx, runner, repr) select type (item => testitems(identifier(1))%item) class is (test_case_base) call runner%run_test(item, ctx) - call item%get_as_char(repr) + call ctx%pop_state(state) + if (allocated(state)) repr = state%as_char() class default error stop "Internal error, unexpected test type in run_test_" end select @@ -439,6 +441,7 @@ recursive subroutine initialize_finalize_suite_(testitems, identifier, init, ctx character(:), allocatable, intent(out) :: repr class(test_base), pointer :: scopeptr + class(char_rep), allocatable :: state scopeptr => testitems(identifier(1))%item call ctx%push_scope_ptr(scopeptr) @@ -447,7 +450,8 @@ recursive subroutine initialize_finalize_suite_(testitems, identifier, init, ctx if (size(identifier) == 1) then if (init) then call runner%set_up_suite(item, ctx) - call item%get_as_char(repr) + call ctx%pop_state(state) + if (allocated(state)) repr = state%as_char() else call runner%tear_down_suite(item, ctx) end if @@ -479,7 +483,7 @@ subroutine set_repr_name_(testresults, ind, repr, dependencyresults) associate (testresult => testresults(ind)) name = basename(testresult%name) - if (allocated(repr)) name = name // " {" // repr // "}" + if (allocated(repr)) name = name // "{" // repr // "}" if (size(testresult%dependencies) > 0) then testresult%reprname = depresults(testresult%dependencies(1))%reprname // "/" // name else @@ -542,4 +546,4 @@ subroutine get_selected_suites_and_tests_(suitedata, testdata, selectedsuites, s end subroutine get_selected_suites_and_tests_ -end module fortuno_testdriver \ No newline at end of file +end module fortuno_testdriver diff --git a/src/fortuno/testinfo.f90 b/src/fortuno/testinfo.f90 index 2016ce4..0e4c0ec 100644 --- a/src/fortuno/testinfo.f90 +++ b/src/fortuno/testinfo.f90 @@ -4,11 +4,12 @@ !> Types containing informations about tests and checks module fortuno_testinfo - use fortuno_utils, only : as_char, nl + use fortuno_chartypes, only : char_rep + use fortuno_utils, only : as_char implicit none private - public :: check_result, failure_details, failure_info, test_result + public :: check_result, failure_info, test_result public :: init_failure_location, failure_location public :: init_drive_result, drive_result public :: teststatus, nteststatusvals @@ -37,39 +38,14 @@ module fortuno_testinfo integer, parameter :: nteststatusvals = 5 - !> Contains details about a failed check - type, abstract :: failure_details - contains - procedure(failure_details_as_char), deferred :: as_char - end type failure_details - - - abstract interface - - !> Character representation of the failure details (typically the failure reason) - function failure_details_as_char(this) result(repr) - import :: failure_details - implicit none - - !> Instance - class(failure_details), intent(in) :: this - - !> Character representation - character(:), allocatable :: repr - - end function failure_details_as_char - - end interface - - !> Contains the result of a check type :: check_result !> Whether the check was successful logical :: success = .false. - !> Further information about the check (reason of failure) - class(failure_details), allocatable :: details + !> Further character representable information about the check (reason of failure) + class(char_rep), allocatable :: details end type check_result @@ -100,8 +76,8 @@ end function failure_details_as_char !> Failure location information class(failure_location), allocatable :: location - !> Internal details of the check (with method to render it as text) - class(failure_details), allocatable :: details + !> Character representable internal details of the check + class(char_rep), allocatable :: details !> Contains previous failure_info (to be able to chain check infos) type(failure_info), allocatable :: previous diff --git a/src/fortuno/utils.f90 b/src/fortuno/utils.f90 index cc81128..0dc4500 100644 --- a/src/fortuno/utils.f90 +++ b/src/fortuno/utils.f90 @@ -14,6 +14,7 @@ module fortuno_utils public :: nl public :: stderr, stdout public :: string, string_list + public :: as_upper !> New line character character(*), parameter :: nl = new_line("") @@ -95,4 +96,31 @@ pure function basename(path) end function basename + + !> Converts a string to upper-case. + pure function as_upper(str) result(upperstr) + + !> String to convert + character(*), intent(in) :: str + + !> Upper-case string + character(len(str)) :: upperstr + + integer, parameter :: lowerstart = iachar("a") + integer, parameter :: lowerend = iachar("z") + integer, parameter :: shift = iachar("A") - lowerstart + + integer :: ii, ord + + do ii = 1, len(str) + ord = iachar(str(ii:ii)) + if (ord >= lowerstart .and. ord <= lowerend) then + upperstr(ii:ii) = achar(iachar(str(ii:ii)) + shift) + else + upperstr(ii:ii) = str(ii:ii) + end if + end do + + end function as_upper + end module fortuno_utils \ No newline at end of file diff --git a/src/fortuno_serial.f90 b/src/fortuno_serial.f90 index a9e0e0a..8b0af3a 100644 --- a/src/fortuno_serial.f90 +++ b/src/fortuno_serial.f90 @@ -9,7 +9,7 @@ module fortuno_serial use fortuno_serial_serialcmdapp, only : execute_serial_cmd_app, init_serial_cmd_app,& & serial_cmd_app use fortuno_serial_serialglobalctx, only : serial_check, serial_check_failed, serial_failed,& - & serial_scope_pointers, serial_skip + & serial_scope_pointers, serial_skip, serial_store_state use fortuno_serial_serialcase, only : serial_case, serial_case_item use fortuno_serial_serialsuite, only : serial_suite, serial_suite_item implicit none diff --git a/src/fortuno_serial/serialbasetypes.f90 b/src/fortuno_serial/serialbasetypes.f90 index f203f01..86efb16 100644 --- a/src/fortuno_serial/serialbasetypes.f90 +++ b/src/fortuno_serial/serialbasetypes.f90 @@ -25,7 +25,7 @@ module fortuno_serial_serialbasetypes subroutine serial_case_base_run(this) import serial_case_base implicit none - class(serial_case_base), intent(inout) :: this + class(serial_case_base), intent(in) :: this end subroutine serial_case_base_run end interface diff --git a/src/fortuno_serial/serialcase.f90 b/src/fortuno_serial/serialcase.f90 index 90b1f82..d4c2d0c 100644 --- a/src/fortuno_serial/serialcase.f90 +++ b/src/fortuno_serial/serialcase.f90 @@ -52,7 +52,7 @@ end function serial_case_item subroutine serial_case_run(this) !> Instance - class(serial_case), intent(inout) :: this + class(serial_case), intent(in) :: this call this%proc() diff --git a/src/fortuno_serial/serialconlogger.f90 b/src/fortuno_serial/serialconlogger.f90 index a394e62..a62100a 100644 --- a/src/fortuno_serial/serialconlogger.f90 +++ b/src/fortuno_serial/serialconlogger.f90 @@ -4,7 +4,7 @@ !> Contains a serial logger implementation module fortuno_serial_serialconlogger - use fortuno, only : console_logger, failure_info + use fortuno, only : console_logger implicit none private diff --git a/src/fortuno_serial/serialglobalctx.f90 b/src/fortuno_serial/serialglobalctx.f90 index c240d3a..4b87558 100644 --- a/src/fortuno_serial/serialglobalctx.f90 +++ b/src/fortuno_serial/serialglobalctx.f90 @@ -4,14 +4,14 @@ !> Global serial context to avoid explicit passing of context when using non-threaded serial driver module fortuno_serial_serialglobalctx - use fortuno, only : check_result, test_ptr_item + use fortuno, only : check_result, char_rep, test_ptr_item use fortuno_serial_serialcontext, only : serial_context implicit none private public :: serialglobalctx public :: set_serial_global_context - public :: serial_check, serial_check_failed, serial_failed, serial_skip + public :: serial_check, serial_check_failed, serial_failed, serial_skip, serial_store_state public :: serial_scope_pointers @@ -121,4 +121,15 @@ function serial_scope_pointers() result(scopeptrs) end function serial_scope_pointers + + !> Stores the test state for later introspection + subroutine serial_store_state(state) + + !> State to store + class(char_rep), intent(in) :: state + + call serialglobalctx%store_state(state) + + end subroutine serial_store_state + end module fortuno_serial_serialglobalctx