From ab70ef2c2448203b53d47da54e2cd1639dc5e799 Mon Sep 17 00:00:00 2001 From: Damian Rouson Date: Tue, 31 Dec 2024 22:14:01 -0500 Subject: [PATCH] chore(diagnosis_t): assert diagnostics allocated --- src/julienne/julienne_diagnosis_m.F90 | 59 ++++++++++++++++++++ src/julienne/julienne_diagnosis_s.f90 | 28 ++++++++++ src/julienne/julienne_test_description_m.f90 | 11 ++++ src/julienne/julienne_test_description_s.f90 | 12 +++- src/julienne/julienne_test_result_m.f90 | 11 +++- src/julienne/julienne_test_result_s.f90 | 8 +++ test/string_test.F90 | 38 +++++++++++-- 7 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 src/julienne/julienne_diagnosis_m.F90 create mode 100644 src/julienne/julienne_diagnosis_s.f90 diff --git a/src/julienne/julienne_diagnosis_m.F90 b/src/julienne/julienne_diagnosis_m.F90 new file mode 100644 index 00000000..ce8f9d5e --- /dev/null +++ b/src/julienne/julienne_diagnosis_m.F90 @@ -0,0 +1,59 @@ +! Copyright (c) 2024, The Regents of the University of California and Sourcery Institute +! Terms of use are as specified in LICENSE.txt +module julienne_diagnosis_m + !! Define an abstraction for describing test outcomes and diagnostic information + use julienne_string_m, only : string_t + implicit none + + private + public :: diagnosis_t + + type diagnosis_t + !! Encapsulate test outcome and diagnostic information + private + logical passed_ + character(len=:), allocatable :: diagnostics_ + contains + procedure passed + procedure diagnostics + end type + + interface diagnosis_t + + elemental module function construct_from_string_t(passed, diagnostics) result(diagnosis) + !! The result is a diagnosis_t object with the components defined by the dummy arguments + implicit none + logical, intent(in) :: passed + type(string_t), intent(in) :: diagnostics + type(diagnosis_t) diagnosis + end function + + elemental module function construct_from_character(passed, diagnostics) result(diagnosis) + !! The result is a diagnosis_t object with the components defined by the dummy arguments + implicit none + logical, intent(in) :: passed + character(len=*), intent(in) :: diagnostics + type(diagnosis_t) diagnosis + end function + + end interface + + interface + + elemental module function passed(self) result(test_passed) + !! The result is .true. if the test passed + implicit none + class(diagnosis_t), intent(in) :: self + logical test_passed + end function + + elemental module function diagnostics(self) result(diagnostics_string) + !! The result is a diagnostic string describing a failed test or a zero-length string if the test passed + implicit none + class(diagnosis_t), intent(in) :: self + type(string_t) diagnostics_string + end function + + end interface + +end module julienne_diagnosis_m diff --git a/src/julienne/julienne_diagnosis_s.f90 b/src/julienne/julienne_diagnosis_s.f90 new file mode 100644 index 00000000..57c919e4 --- /dev/null +++ b/src/julienne/julienne_diagnosis_s.f90 @@ -0,0 +1,28 @@ +! Copyright (c) 2024, The Regents of the University of California and Sourcery Institute +! Terms of use are as specified in LICENSE.txt + +#include "assert_macros.h" + +submodule(julienne_diagnosis_m) julienne_diagnosis_s + use assert_m + implicit none +contains + module procedure construct_from_string_t + diagnosis%passed_ = passed + diagnosis%diagnostics_ = diagnostics + end procedure + + module procedure construct_from_character + diagnosis%passed_ = passed + diagnosis%diagnostics_ = diagnostics + end procedure + + module procedure passed + test_passed = self%passed_ + end procedure + + module procedure diagnostics + call_assert(allocated(self%diagnostics_)) + diagnostics_string = string_t(self%diagnostics_) + end procedure +end submodule julienne_diagnosis_s diff --git a/src/julienne/julienne_test_description_m.f90 b/src/julienne/julienne_test_description_m.f90 index f4d48949..9fc4b174 100644 --- a/src/julienne/julienne_test_description_m.f90 +++ b/src/julienne/julienne_test_description_m.f90 @@ -4,17 +4,27 @@ module julienne_test_description_m !! Define an abstraction for describing test intentions and test functions use julienne_string_m, only : string_t use julienne_test_result_m, only : test_result_t + use julienne_diagnosis_m, only : diagnosis_t implicit none private public :: test_description_t public :: test_function_i + public :: diagnostic_function_i abstract interface + function test_function_i() result(passes) implicit none logical passes end function + + function diagnostic_function_i() result(diagnosis) + import diagnosis_t + implicit none + type(diagnosis_t) diagnosis + end function + end interface type test_description_t @@ -22,6 +32,7 @@ function test_function_i() result(passes) private character(len=:), allocatable :: description_ procedure(test_function_i), pointer, nopass :: test_function_ => null() + procedure(diagnostic_function_i), pointer, nopass :: diagnostic_function_ => null() contains procedure run generic :: contains_text => contains_string_t, contains_characters diff --git a/src/julienne/julienne_test_description_s.f90 b/src/julienne/julienne_test_description_s.f90 index e4e61c46..6db66ca5 100644 --- a/src/julienne/julienne_test_description_s.f90 +++ b/src/julienne/julienne_test_description_s.f90 @@ -1,5 +1,8 @@ ! Copyright (c) 2024, The Regents of the University of California and Sourcery Institute ! Terms of use are as specified in LICENSE.txt + +#include "assert_macros.h" + submodule(julienne_test_description_m) julienne_test_description_s implicit none contains @@ -14,7 +17,14 @@ end procedure module procedure run - test_result = test_result_t(self%description_, self%test_function_()) + associate(testing => associated(self%test_function_), diagnosing => associated(self%diagnostic_function_)) + call_assert(count([testing, diagnosing])==1) + if (testing) then + test_result = test_result_t(self%description_, self%test_function_()) + else + test_result = test_result_t(self%description_, self%diagnostic_function_()) + end if + end associate end procedure module procedure contains_string_t diff --git a/src/julienne/julienne_test_result_m.f90 b/src/julienne/julienne_test_result_m.f90 index 709f6db4..6f202726 100644 --- a/src/julienne/julienne_test_result_m.f90 +++ b/src/julienne/julienne_test_result_m.f90 @@ -3,6 +3,7 @@ module julienne_test_result_m !! Define an abstraction for describing test intentions and results use julienne_string_m, only : string_t + use julienne_diagnosis_m, only : diagnosis_t implicit none private @@ -11,7 +12,7 @@ module julienne_test_result_m type test_result_t !! Encapsulate test descriptions and outcomes private - character(len=:), allocatable :: description_ + character(len=:), allocatable :: description_, diagnostics_ logical passed_ contains procedure :: characterize @@ -39,6 +40,14 @@ elemental module function construct_from_string(description, passed) result(test type(test_result_t) test_result end function + elemental module function construct_from_diagnosis(description, diagnosis) result(test_result) + !! The result is a test_result_t object with the components defined by the dummy arguments + implicit none + character(len=*), intent(in) :: description + type(diagnosis_t), intent(in) :: diagnosis + type(test_result_t) test_result + end function + end interface interface diff --git a/src/julienne/julienne_test_result_s.f90 b/src/julienne/julienne_test_result_s.f90 index 48ea16de..6ef4b57d 100644 --- a/src/julienne/julienne_test_result_s.f90 +++ b/src/julienne/julienne_test_result_s.f90 @@ -16,8 +16,16 @@ test_result%passed_ = passed end procedure + module procedure construct_from_diagnosis + test_result%description_ = description + test_result%passed_ = diagnosis%passed() + test_result%diagnostics_ = diagnosis%diagnostics() + end procedure + module procedure characterize characterization = trim(merge("passes on ", "FAILS on ", self%passed_)) // " " // trim(self%description_) // "." + if (allocated(self%diagnostics_) .and. .not. self%passed_) & + characterization = characterization // new_line('') // " diagnostics: " // self%diagnostics_ end procedure module procedure passed diff --git a/test/string_test.F90 b/test/string_test.F90 index 8183e7b9..9f4dc64d 100644 --- a/test/string_test.F90 +++ b/test/string_test.F90 @@ -59,7 +59,9 @@ function results() result(test_results) test_description_t & (string_t('constructing from a default integer'), constructs_from_default_integer), & test_description_t & - (string_t('constructing from a real value'), constructs_from_real), & + (string_t('constructing from a default real value'), constructs_from_default_real), & + test_description_t & + (string_t('constructing from a double precision value'), constructs_from_double_precision), & test_description_t & (string_t('supporting unary operator(.cat.) for array arguments'), concatenates_elements), & test_description_t & @@ -97,8 +99,8 @@ function results() result(test_results) ! Work around missing Fortran 2008 feature: associating a procedure actual argument with a procedure pointer dummy argument: procedure(test_function_i), pointer :: & check_allocation_ptr, supports_equivalence_ptr, supports_non_equivalence_ptr, supports_concatenation_ptr, & - assigns_string_ptr, assigns_character_ptr, constructs_from_integer_ptr, constructs_from_real_ptr, concatenates_ptr, & - extracts_key_ptr, extracts_real_ptr, extracts_string_ptr, extracts_logical_ptr, extracts_integer_array_ptr, & + assigns_string_ptr, assigns_character_ptr, constructs_from_integer_ptr, constructs_from_default_real_ptr, constructs_from_double_precision_ptr, & + concatenates_ptr, extracts_key_ptr, extracts_real_ptr, extracts_string_ptr, extracts_logical_ptr, extracts_integer_array_ptr, & extracts_real_array_ptr, extracts_integer_ptr, extracts_file_base_ptr, extracts_file_name_ptr, & ! Remove code that exposes a gfortran compiler bug: ! extracts_string_array_ptr, & @@ -112,7 +114,8 @@ function results() result(test_results) assigns_string_ptr => assigns_string_t_to_character assigns_character_ptr => assigns_character_to_string_t constructs_from_integer_ptr => constructs_from_default_integer - constructs_from_real_ptr => constructs_from_real + constructs_from_double_precision_ptr => constructs_from_double_precision + constructs_from_default_real_ptr => constructs_from_default_real concatenates_ptr => concatenates_elements extracts_key_ptr => extracts_key extracts_real_ptr => extracts_real_value @@ -142,7 +145,8 @@ function results() result(test_results) test_description_t(string_t('assigning a string_t object to a character variable'), assigns_string_ptr), & test_description_t(string_t('assigning a character variable to a string_t object'), assigns_character_ptr), & test_description_t(string_t('constructing from a default integer'), constructs_from_integer_ptr), & - test_description_t(string_t('constructing from a real value'), constructs_from_real_ptr), & + test_description_t(string_t('constructing from a default real value'), constructs_from_default_real_ptr), & + test_description_t(string_t('constructing from a double precision value'), constructs_from_double_precision_ptr), & test_description_t(string_t('supporting unary operator(.cat.) for array arguments'), concatenates_ptr), & test_description_t(string_t("extracting a key string from a colon-separated key/value pair"), extracts_key_ptr), & test_description_t(string_t("extracting a real value from a colon-separated key/value pair"), extracts_real_ptr), & @@ -471,7 +475,7 @@ function constructs_from_default_integer() result(passed) #endif end function - function constructs_from_real() result(passed) + function constructs_from_default_real() result(passed) logical passed real, parameter :: real_value = -1./1024. ! use a negative power of 2 an exactly representable rational number real read_value @@ -492,7 +496,29 @@ function constructs_from_real() result(passed) passed = read_value == real_value end block #endif + end function + + function constructs_from_double_precision() result(passed) + logical passed + double precision, parameter :: real_value = -1./1024. ! use a negative power of 2 an exactly representable rational number + real read_value + character(len=:), allocatable :: character_representation +#ifndef _CRAYFTN + associate(string => string_t(real_value)) + character_representation = string%string() + read(character_representation, *) read_value + passed = read_value == real_value + end associate +#else + block + type(string_t) string + string = string_t(real_value) + character_representation = string%string() + read(character_representation, *) read_value + passed = read_value == real_value + end block +#endif end function function extracts_file_base_name() result(passed)