fortran-assert-3.1.0/0000775000175000017500000000000015151611176014655 5ustar alastairalastairfortran-assert-3.1.0/src/0000775000175000017500000000000015151611147015442 5ustar alastairalastairfortran-assert-3.1.0/src/assert_m.F900000664000175000017500000001271615151611147017546 0ustar alastairalastair! (c) 2024-2025 UC Regents, see LICENSE file for detailed terms. ! ! (c) 2019-2020 Guide Star Engineering, LLC ! This Software was developed for the US Nuclear Regulatory Commission (US NRC) under contract ! "Multi-Dimensional Physics Implementation into Fuel Analysis under Steady-state and Transients (FAST)", ! contract # NRC-HQ-60-17-C-0007 ! #include "assert_macros.h" #include "assert_features.h" module assert_m !! summary: Utility for runtime enforcement of logical assertions. !! usage: error-terminate if the assertion fails: !! !! use assertions_m, only : assert !! call assert( 2 > 1, "2 > 1") !! !! Assertion enforcement is controlled via the `ASSERTIONS` preprocessor macro, !! which can be defined to non-zero or zero at compilation time to !! respectively enable or disable runtime assertion enforcement. !! !! When the `ASSERTIONS` preprocessor macro is not defined to any value, !! the default is that assertions are *disabled* and will not check the condition. !! !! Disabling assertion enforcement may eliminate any associated runtime !! overhead by enabling optimizing compilers to ignore the assertion procedure !! body during a dead-code-removal phase of optimization. !! !! To enable assertion enforcement (e.g., for a debug build), define the preprocessor ASSERTIONS to non-zero. !! This file's capitalized .F90 extension causes most Fortran compilers to preprocess this file so !! that building as follows enables assertion enforcement: !! !! fpm build --flag "-DASSERTIONS" !! implicit none private public :: assert, assert_always #if ASSERT_PARALLEL_CALLBACKS public :: assert_this_image_interface, assert_this_image public :: assert_error_stop_interface, assert_error_stop abstract interface pure function assert_this_image_interface() result(this_image_id) implicit none integer :: this_image_id end function end interface procedure(assert_this_image_interface), pointer :: assert_this_image abstract interface pure subroutine assert_error_stop_interface(stop_code_char) implicit none character(len=*), intent(in) :: stop_code_char end subroutine end interface procedure(assert_error_stop_interface), pointer :: assert_error_stop #endif #ifndef USE_ASSERTIONS # if ASSERTIONS # define USE_ASSERTIONS .true. # else # define USE_ASSERTIONS .false. # endif #endif logical, parameter :: enforce_assertions=USE_ASSERTIONS contains pure subroutine assert(assertion, description) !! If assertion is .false. and enforcement is enabled (e.g. via -DASSERTIONS=1), !! then error-terminate with a character stop code that contains the description argument if present implicit none logical, intent(in) :: assertion !! Most assertions will be expressions such as i>0 character(len=*), intent(in) :: description !! A brief statement of what is being asserted such as "i>0" or "positive i" toggle_assertions: & if (enforce_assertions) then call assert_always(assertion, description) end if toggle_assertions end subroutine pure subroutine assert_always(assertion, description, file, line) !! Same as above but always enforces the assertion (regardless of ASSERTIONS) implicit none logical, intent(in) :: assertion character(len=*), intent(in) :: description character(len=*), intent(in), optional :: file integer, intent(in), optional :: line character(len=:), allocatable :: message character(len=:), allocatable :: location integer me check_assertion: & if (.not. assertion) then ! Avoid harmless warnings from Cray Fortran: allocate(character(len=0)::message) allocate(character(len=0)::location) ! format source location, if known location = '' if (present(file)) then location = ' at ' // file // ':' if (present(line)) then ! only print line number if file is also known block character(len=128) line_str write(line_str, '(i0)') line location = location // trim(adjustl(line_str)) end block else location = location // '' endif endif #if ASSERT_MULTI_IMAGE # if ASSERT_PARALLEL_CALLBACKS if (associated(assert_this_image)) then me = assert_this_image() else me = 0 endif # else me = this_image() # endif block character(len=128) image_number write(image_number, *) me message = 'Assertion failure on image ' // trim(adjustl(image_number)) // location // ': ' // description end block #else message = 'Assertion failure' // location // ': ' // description me = 0 ! avoid a harmless warning #endif #if ASSERT_PARALLEL_CALLBACKS if (associated(assert_this_image)) then call assert_error_stop(message) else ; ! deliberate fall-thru endif #endif #ifdef __LFORTRAN__ ! workaround a defect observed in LFortran 0.54: ! error stop with an allocatable character argument prints garbage error stop message//'', QUIET=.false. #elif __GNUC__ && __GNUC__ < 12 ! old GFortran lacks the QUIET optional arg added in F2018 error stop message #else error stop message, QUIET=.false. #endif end if check_assertion end subroutine end module assert_m fortran-assert-3.1.0/test/0000775000175000017500000000000015151611147015632 5ustar alastairalastairfortran-assert-3.1.0/test/test-assert-macro.F900000664000175000017500000000566315151611147021501 0ustar alastairalastairprogram test_assert_macros use assert_m implicit none print * print '(a)',"The call_assert macro" #undef ASSERTIONS #define ASSERTIONS 1 #include "assert_macros.h" call_assert(1==1) print '(a)'," passes on not error-terminating when an assertion expression evaluating to .true. is the only argument" #undef ASSERTIONS #include "assert_macros.h" call_assert(.false.) print '(a)'," passes on being removed by the preprocessor when ASSERTIONS is undefined" // new_line('') !------------------------------------------ print '(a)',"The call_assert_describe macro" #undef ASSERTIONS #define ASSERTIONS 1 #include "assert_macros.h" call_assert_describe(.true., ".true.") print '(a)'," passes on not error-terminating when assertion = .true. and a description is present" #undef ASSERTIONS #include "assert_macros.h" call_assert_describe(.false., "") print '(a)'," passes on being removed by the preprocessor when ASSERTIONS is undefined" // new_line('') !------------------------------------------ #undef ASSERTIONS #define ASSERTIONS 1 #include "assert_macros.h" print '(a)',"The call_assert_* macros" block logical :: foo foo = check_assert(.true.) print '(a)'," pass on invocation from a pure function" end block !------------------------------------------ #undef ASSERTIONS #define ASSERTIONS 1 #include "assert_macros.h" ! The following examples are taken from README.md and should be kept in sync with that document: block integer :: computed_checksum = 37, expected_checksum = 37 #if defined(_CRAYFTN) || defined(__LFORTRAN__) ! Cray Fortran uses different line continuations in macro invocations call_assert_describe( computed_checksum == expected_checksum, & "Checksum mismatch failure!" & ) print *," passes with line breaks inside macro invocation" call_assert_describe( computed_checksum == expected_checksum, & ! ensured since version 3.14 "Checksum mismatch failure!" & ! TODO: write a better message here ) print *," passes with C block comments embedded in macro invocation" #else call_assert_describe( computed_checksum == expected_checksum, \ "Checksum mismatch failure!" \ ) print *," passes with line breaks inside macro invocation" call_assert_describe( computed_checksum == expected_checksum, /* ensured since version 3.14 */ \ "Checksum mismatch failure!" /* TODO: write a better message here */ \ ) print *," passes with C block comments embedded in macro invocation" #endif end block !------------------------------------------ contains pure function check_assert(cond) result(ok) logical, intent(in) :: cond logical ok call_assert(cond) call_assert_describe(cond, "check_assert") ok = .true. end function end program fortran-assert-3.1.0/test/run-false-assertion-intel.sh0000775000175000017500000000021615151611147023202 0ustar alastairalastair#!/bin/bash output=$(fpm run --example false-assertion --compiler ifx --flag '-O3 -DASSERTIONS' > /dev/null 2>&1) echo $? > build/exit_status fortran-assert-3.1.0/test/test-assert-subroutine-normal-termination.F900000664000175000017500000000140315151611147026400 0ustar alastairalastair#include "assert_features.h" program test_assert_subroutine_normal_termination !! Test direct calls to the "assert" subroutine that don't error-terminate use assert_m, only : assert implicit none #if ASSERT_MULTI_IMAGE if (this_image()==1) then #endif print *, new_line(''), "The assert subroutine" #if ASSERT_MULTI_IMAGE end if sync all #endif call assert(assertion = .true., description = "2 keyword arguments") call assert( .true., description = "1 keyword arguments") call assert( .true., "0 keyword arguments") #if ASSERT_MULTI_IMAGE sync all if (this_image()==1) then #endif print *," passes on not error-terminating when assertion=.true." #if ASSERT_MULTI_IMAGE end if #endif end program fortran-assert-3.1.0/test/run-false-assertion.sh0000775000175000017500000000025715151611147022076 0ustar alastairalastair#!/bin/bash output=$(fpm run --example false-assertion --compiler flang-new --flag '-mmlir -allow-assumed-rank -O3 -DASSERTIONS' > /dev/null 2>&1) echo $? > build/exit_status fortran-assert-3.1.0/test/test-assert-subroutine-error-termination.F900000664000175000017500000000462115151611147026246 0ustar alastairalastair#include "assert_features.h" program test_assert_subroutine_error_termination !! Test "assert" subroutine calls that are intended to error terminate use assert_m, only : assert implicit none integer exit_status #if ASSERT_MULTI_IMAGE if (this_image()==1) then #endif print *, new_line(''), "The assert subroutine" #if ASSERT_MULTI_IMAGE end if #endif ! TODO: The following is a HORRIBLY fragile test. ! Specifically, it encodes a bunch of compiler-specific flags into an fpm command, ! and if fpm fails for any unrelated reason (broken command, compile error, I/O error, etc) ! we will mistakenly interpret that as a passing test! call execute_command_line( & #ifdef __GFORTRAN__ command = "fpm run --example false-assertion --profile release --flag '-DASSERTIONS -ffree-line-length-0' > /dev/null 2>&1", & #elif NAGFOR command = "fpm run --example false-assertion --compiler nagfor --flag '-DASSERTIONS -fpp' > /dev/null 2>&1", & #elif __flang__ command = "./test/run-false-assertion.sh", & # define RESULT_FROM_FILE 1 #elif __INTEL_COMPILER command = "./test/run-false-assertion-intel.sh", & # define RESULT_FROM_FILE 1 #elif _CRAYFTN command = "fpm run --example false-assertion --profile release --compiler crayftn.sh --flag '-DASSERTIONS' > /dev/null 2>&1", & #elif __LFORTRAN__ command = "fpm run --example false-assertion --profile release --flag '-DASSERTIONS -ffree-line-length-0' > /dev/null 2>&1", & #else ! All other compilers need their command manually validated and added to the list above command = "echo 'example/false_assertion.F90: unsupported compiler' && exit 1", & #endif wait = .true., & exitstat = exit_status & ) #if RESULT_FROM_FILE ! some compilers don't provide a reliable exitstat for the command above, ! so for those we write it to a file and retrieve it here block integer unit open(newunit=unit, file="build/exit_status", status="old") read(unit,*) exit_status close(unit) end block #endif #if ASSERT_MULTI_IMAGE exit_status = abs(exit_status) call co_max(exit_status) if (this_image()==1) then print *,trim(merge("passes","FAILS ",exit_status/=0)) // " on error-terminating when assertion = .false." end if #else print *,trim(merge("passes","FAILS ",exit_status/=0)) // " on error-terminating when assertion = .false." #endif end program test_assert_subroutine_error_termination fortran-assert-3.1.0/README.md0000664000175000017500000003362215151611147016140 0ustar alastairalastairAssert ====== An assertion utility that combines variable stop codes and error termination in `pure` procedures to produce descriptive messages when a program detects violations of the requirements for correct execution. Motivations ----------- 1. To mitigate against a reason developers often cite for not writing `pure` procedures: their inability to produce output in normal execution. 2. To promote the enforcement of programming contracts. Overview -------- This assertion utility contains four public entities: * An `assert_macros.h` file defining the recommended preprocessor macros for writing assertions: - `call_assert(assertion)` - `call_assert_describe(assertion, description)` * An `assert` subroutine The `assert` subroutine * Error-terminates with a variable stop code when a caller-provided logical assertion fails, * Is callable inside `pure` procedures, and * Can be eliminated by not defining the `ASSERTIONS` preprocessor macro. Assertion enforcement is controlled via the `ASSERTIONS` preprocessor macro, which can be defined to non-zero or zero at compilation time to respectively enable or disable run-time assertion enforcement. When the `ASSERTIONS` preprocessor macro is not defined to any value, the default is that assertions are *disabled* and will not check the condition. To enable assertion enforcement (e.g., for a debug build), define the preprocessor ASSERTIONS to non-zero, e.g., ``` fpm build --flag "-DASSERTIONS" ``` The program [example/invoke-via-macro.F90] demonstrates the preferred way to invoke assertions via the three provided macros. Invoking assertions this way ensures such calls will be completely removed whenever the `ASSERTIONS` macro is undefined (or defined to zero) during compilation. Due to a limitation of `fpm`, this approach works best if the project using Assert is also a `fpm` project. If instead `fpm install` is used, then either the user must copy `include/assert_macros.h` to the installation directory (default: `~/.local/include`) or the user must invoke `assert` directly (via `call assert(...)`). In the latter approach when the assertions are disabled, the `assert` procedure will start and end with `if (.false.) then ... end if`, which might facilitate automatic removal of `assert` during the dead-code removal phase of optimizing compilers. Use Cases --------- Two common use cases include 1. [Supporting output in pure procedures](#supporting-output-in-pure-procedures) for debugging purposes. 2. [Enforcing programming contracts](#enforcing-programming-contracts) throughout a project via runtime checks. ### Supporting output in pure procedures Writing pure procedures communicates useful information to a compiler or a developer. Specifically, the pure attribute conveys compliance with several constraints that clarify data dependencies and preclude most side effects. For a compiler, these constraints support optimizations, including automatic parallelization on a central processing unit (CPU) or offloading to a graphics processing unit (GPU). For a developer, the constraints support refactoring tasks such as code movement. The Fortran standard prohibits input or output in pure procedures, which precludes a common debugging mechanism. A developer seeking output inside a procedure presumably has an expectation regarding what ranges of output values represent correct program execution. A developer can state such expectations in an assertion such as `call_assert(i>0 .and. j<0)`. Enforce the assertion by defining the `ASSERTIONS` macro when compiling. If the expectation is not met, the program error terminates and prints a stop code showing the assertion's file and line location and a description. By default, the description is the literal text of what was asserted: `i>0 .and. j<0` in the aforementioned example. Alternatively, the user can provide a custom description. ### Enforcing programming contracts Programming can be thought of as requirements for correct execution of a procedure and assurances for the result of correct execution. The requirements and assurances might be constraints of three kinds: 1. **Preconditions (requirements):** `logical` expressions that must evaluate to `.true.` when a procedure starts execution, 2. **Postconditions (assurances):** expressions that must evaluate to `.true.` when a procedure finishes execution, and 3. **Invariants:** universal pre- and postconditions that must always be true when all procedures in a class start or finish executing. The [example/README.md] file shows examples of writing constraints in notes on class diagrams using the formal syntax of the Object Constraint Language ([OCL]). Running the Examples -------------------- See the [./example](./example) subdirectory. Building and Testing -------------------- - [General Build Knobs](#general-build-knobs) - [Cray Compiler Environment (CCE) `ftn`](#cray-compiler-environment-cce-ftn) - [GNU Compiler Collection (GCC) `gfortran`](#gnu-compiler-collection-gcc-gfortran)) - [Intel `ifx`](#intel-ifx)) - [LFortran `lfortran`](#lfortran-lfortran) - [LLVM `flang-new`](#llvm-flang-new) - [Numerical Algorithms Group (NAG) `nagfor`](#numerical-algorithms-group-nag-nagfor) ### General Build Knobs The following build-time preprocessor knobs can be used to control the behavior of Assert. When using `fpm` to build, these boolean flags can be passed on the command-line using syntax like: `fpm --flag "-DASSERTIONS=1"` * `ASSERTIONS` : Controls the whether assertions are checked/enforced at runtime. The default is 0 (assertions disabled). Assertions can be enabled using `-DASSERTIONS=1`. * `ASSERT_MULTI_IMAGE`: Controls whether the library attempts to use multi-image Fortran features (e.g. to report the image number of an assertion failure). The default is compiler-specific. Multi-image support can be disabled using `-DASSERT_MULTI_IMAGE=0`. * `ASSERT_PARALLEL_CALLBACKS`: Controls the use of a callback interface for multi-process features. Contact us for more details. ### Cray Compiler Environment (CCE) `ftn` Because `fpm` uses the compiler name to determine the compiler identity and because CCE provides one compiler wrapper, `ftn`, for invoking all compilers, you will need to invoke `ftn` in a shell script named to identify CCE compiler. For example, place a script named `crayftn.sh` in your path with the following contents and with executable privileges set appropriately: ``` #!/bin/bash ftn $@ ``` Then build and test Assert with the command ``` fpm test --compiler crayftn.sh --profile release ``` ### GNU Compiler Collection (GCC) `gfortran` #### Single-image (serial) execution With `gfortran` 14 or later, use ``` fpm test --profile release ``` With `gfortran` 13 or earlier, use ``` fpm test --profile release --flag "-ffree-line-length-0" ``` The above commands build the Assert library (with the default of assertion enforcement disabled) and runs the test suite. #### Multi-image (parallel) execution With `gfortran` 14 or later versions and OpenCoarrays installed, use ``` fpm test --compiler caf --profile release --runner "cafrun -n 2" ``` With `gfortran` 13 or earlier versions and OpenCoarrays installed, ``` fpm test --compiler caf --profile release --runner "cafrun -n 2" --flag "-ffree-line-length-0" ``` ### Intel `ifx` #### Single-image (serial) execution ``` fpm test --compiler ifx --profile release ``` #### Multi-image (parallel) execution With Intel Fortran and Intel MPI installed, ``` fpm test --compiler ifx --profile release --flag "-coarray -DASSERT_MULTI_IMAGE" ``` ### LLVM `flang-new` #### Single-image (serial) execution With `flang-new` version 19, use ``` fpm test --compiler flang-new --flag "-mmlir -allow-assumed-rank -O3" ``` With `flang-new` version 20 or later, use ``` fpm test --compiler flang-new --flag "-O3" ``` ### LFortran `lfortran` #### Single-image (serial) execution ``` fpm test --compiler lfortran --profile release --flag --cpp ``` ### Numerical Algorithms Group (NAG) `nagfor` #### Single-image (serial) execution With `nagfor` version 7.1 or later, use ``` fpm test --compiler nagfor --flag -fpp ``` #### Multi-image execution With `nagfor` 7.1, use ``` fpm test --compiler nagfor --profile release --flag "-fpp -coarray=cosmp -f2018" ``` With `nagfor` 7.2 or later, use ``` fpm test --compiler nagfor --flag -fpp ``` Documentation ------------- Please see [example/README.md] and the [tests] for examples of how to use Assert. ### Potential pitfalls of `call_assert` macros: The `call_assert*` macros from the `assert_macros.h` header file provide the attractive guarantee that they will always compile *completely* away when assertions are disabled, regardless of compiler analyses and optimization level. This means users can reap the maintainability and correctness benefits of aggressively asserting invariants throughout their code, without needing to balance any potential performance cost associated with such assertions when the code runs in production. Unfortunately, preprocessor macros do not integrate cleanly with some aspects of the Fortran language. As such, you might encounter one or more of the following pitfalls when using these macros. #### Line length limit Up to and including the Fortran 2018 language standard, compilers were only required to support up to 132 characters per free-form source line. Preprocessor macro invocations are always expanded to a single line during compilation, so when passing non-trivial arguments to macros including `call_assert*` it becomes easy for the expansion to exceed this line length limit. This can result in compile-time errors like the following from gfortran: ``` Error: Line truncated at (1) [-Werror=line-truncation] ``` Some compilers offer a command-line argument that can be used to workaround this legacy limit, e.g., * `gfortran -ffree-line-length-0` or equivalently `gfortran -ffree-line-length-none` When using `fpm`, one can pass such a flag to the compiler using the `fpm --flag` option, e.g., ```shell $ fpm test --profile release --flag -ffree-line-length-0 ``` Thankfully, Fortran 2023 raised this obsolescent line limit to 10,000 characters, so by using newer compilers you might never encounter this problem. In the case of gfortran, this appears to have been resolved by default starting in release 14.1.0. #### Line breaks in macro invocations The preprocessor is not currently specified by any Fortran standard, and as of 2025 its operation differs in subtle ways between compilers. One way in which compilers differ is how macro invocations can safely be broken across multiple lines. For example, gfortran and flang-new both accept backslash `\` continuation character for line-breaks in a macro invocation: ```fortran ! OK for flang-new and gfortran call_assert_describe( computed_checksum == expected_checksum, \ "Checksum mismatch failure!" \ ) ``` Whereas Cray Fortran wants `&` line continuation characters, even inside a macro invocation: ```fortran ! OK for Cray Fortran call_assert_describe( computed_checksum == expected_checksum, & "Checksum mismatch failure!" & ) ``` There appears to be no syntax acceptable to all compilers, so when writing portable code it's probably best to avoid line breaks inside a macro invocation. #### Comments in macro invocations Fortran does not support comments with an end delimiter, only to-end-of-line comments. As such, there is no portable way to safely insert a Fortran comment into the middle of a macro invocation. For example, the following seemingly reasonable code results in a syntax error after macro expansion (on gfortran and flang-new): ```fortran ! INCORRECT: cannot use Fortran comments inside macro invocation call_assert_describe( computed_checksum == expected_checksum, ! ensured since version 3.14 "Checksum mismatch failure!" ! TODO: write a better message here ) ``` Depending on your compiler it *might* be possible to use a C-style block comment (because they are often removed by the preprocessor), for example with gfortran one can instead write the following: ```fortran call_assert_describe( computed_checksum == expected_checksum, /* ensured since version 3.14 */ \ "Checksum mismatch failure!" /* TODO: write a better message here */ \ ) ``` However that capability might not be portable to other Fortran compilers. When in doubt, one can always move the comment outside the macro invocation: ```fortran ! assert a property ensured since version 3.14 call_assert_describe( computed_checksum == expected_checksum, \ "Checksum mismatch failure!" \ ) ! TODO: write a better message above ``` Clients of Assert ----------------- A few packages that use Assert include * The [Julienne](https://go.lbl.gov/julienne) correctness-checking framework wraps Assert and defines idioms that automatically generate diagnostic messages containing program data. * The [Caffeine](https://go.lbl.gov/caffeine) multi-image Fortran compiler runtime library uses Assert for internal sanity checks and interface validation. * The [Fiats](https://go.lbl.gov/fiats) deep learning library uses Assert and Julienne. * The [Matcha](https://go.lbl.gov/matcha) T-cell motility simulator also uses Assert and Julienne. Legal Information ----------------- See the [LICENSE](LICENSE) file for copyright and licensing information. [Hyperlinks]:# [OpenCoarrays]: https://github.com/sourceryinstitute/opencoarrays [Enforcing programming contracts]: #enforcing-programming-contracts [Single-image execution]: #single-image-execution [example/README.md]: ./example/README.md [tests]: ./tests [Fortran Package Manager]: https://github.com/fortran-lang/fpm [OCL]: https://en.wikipedia.org/wiki/Object_Constraint_Language [example/invoke-via-macro.F90]: ./example/invoke-via-macro.F90 [Producing output in pure procedures]: #producing-output-in-pure-procedures [Julienne]: https://go.lbl.gov/julienne fortran-assert-3.1.0/example/0000775000175000017500000000000015151611147016306 5ustar alastairalastairfortran-assert-3.1.0/example/simple-assertions.f900000664000175000017500000000226415151611147022313 0ustar alastairalastairprogram assertion_examples !! Demonstrate the use of assertions as runtime checks on the satisfaction of !! of two kinds of constraints: !! 1. Preconditions: requirements for correct execution at the start of a procedure and !! 2. Postconditions: requirements for correct execution at the end of a procedure. use assert_m, only : assert implicit none print *, "roots: ", roots(a=1.,b=0.,c=-4.) contains pure function roots(a,b,c) result(zeros) !! Calculate the roots of a quadratic polynomial real, intent(in) :: a, b, c real, allocatable :: zeros(:) real, parameter :: tolerance = 1E-06 associate(discriminant => b**2 - 4*a*c) call assert(assertion = discriminant >= 0., description = "discriminant >= 0") ! precondition allocate(zeros(2)) ! there's a deliberate math bug in the following line, to help demonstrate assertion failure zeros = -b + [sqrt(discriminant), -sqrt(discriminant)] end associate ! This assertion will fail (due to the defect above) when ASSERTIONS are enabled: call assert(all(abs(a*zeros**2 + b*zeros + c) < tolerance), "All residuals within tolerance.") ! postcondition end function end program fortran-assert-3.1.0/example/README.md0000664000175000017500000000460115151611147017566 0ustar alastairalastairExamples ======== This directory contains several example programs. Example of recommended use -------------------------- The [invoke-via-macro.F90] example demonstrates the recommended ways to write assertions in code. These leverage the function-like macros `call_assert` and `call_assert_describe`. The primary advantage of using these macros is that they are completely eliminated when a file is compiled with the `ASSERTIONS` macro undefined or defined as `0`. To run this example with assertions off, use the command ``` fpm run --example invoke-via-macro ``` To run the example with assertions on, use either of the following commands: ``` fpm run --example invoke-via-macro --flag "-DASSERTIONS" ``` Simple examples --------------- The [simple-assertions.f90] example demonstrates a precondition and a postcondition, each with an assertion that checks the truth of a logical expression based on scalar, real values. Running the examples -------------------- ### Single-image execution ``` fpm run --example simple-assertions --flag "-DASSERTIONS" ``` where `fpm run` automatically invokes `fpm build` if necessary, .e.g., if the package's source code has changed since the most recent build. If `assert` is working correctly, the `fpm run` above will error-terminate with the character stop code similar to the following ``` Assertion failure on image 1: All residuals within tolerance ``` ### Multi-image execution with `gfortran` and OpenCoarrays ``` git clone git@github.com/sourceryinstitute/assert cd assert fpm run --compiler caf --runner "cafrun -n 2" --example simple-assertions --flag "-DASSERTIONS" ``` Replace either instance of `2` above with the desired number of images to run for parallel execution. If `assert` is working correctly, both of the latter `fpm run` commands will error-terminate with one or more images providing stop codes analogous to those quoted in the [Single-image execution] section. [Hyperlinks]:# [OpenCoarrays]: https://github.com/sourceryinstitute/opencoarrays [Enforcing programming contracts]: #enforcing-programming-contracts [Single-image execution]: #single-image-execution [simple-assertions.f90]: ./simple-assertions.f90 [invoke-via-macro.F90]: ./invoke-via-macro.F90 [UML]: https://en.wikipedia.org/wiki/Unified_Modeling_Language [OCL]: https://en.wikipedia.org/wiki/Object_Constraint_Language [Atom]: https://atom.io [PlantUML]: https://plantuml.com [doc]: ../doc/ fortran-assert-3.1.0/example/false-assertion.F900000664000175000017500000000250215151611147021664 0ustar alastairalastairprogram false_assertion use assert_m implicit none #if ASSERT_PARALLEL_CALLBACKS assert_this_image => assert_callback_this_image assert_error_stop => assert_callback_error_stop #endif call assert(.false., "false-assertion: unconditionally failing test") #if ASSERT_PARALLEL_CALLBACKS ! By default, assert uses `THIS_IMAGE()` in multi-image mode while ! composing assertion output, and invokes `ERROR STOP` to print the ! assertion and terminate execution. ! ! The ASSERT_PARALLEL_CALLBACKS preprocessor flag enables the client to replace ! the default use of these two Fortran features with client-provided callbacks. ! To use this feature, the client must build the library with `-DASSERT_PARALLEL_CALLBACKS`, ! and then at startup set the `assert_this_image` and `assert_error_stop` ! procedure pointers to reference the desired callbacks. contains pure function assert_callback_this_image() result(this_image_id) implicit none integer :: this_image_id this_image_id = 42 end function pure subroutine assert_callback_error_stop(stop_code_char) implicit none character(len=*), intent(in) :: stop_code_char error stop "Hello from assert_callback_error_stop!" // NEW_LINE('a') // & "Your assertion: " // NEW_LINE('a') // stop_code_char end subroutine #endif end program fortran-assert-3.1.0/example/invoke-via-macro.F900000664000175000017500000000277615151611147021751 0ustar alastairalastair#include "assert_macros.h" program invoke_via_macro !! Demonstrate how to invoke the 'assert' subroutine using a preprocessor macro that facilitates !! the complete removal of the call in the absence of the compiler flag: -DASSERTIONS use assert_m ! <--- this is the recommended use statement !! If an "only" clause is employed above, the symbols required by the !! macro expansion are subject to change without notice between versions. !! You have been warned! implicit none #if !ASSERTIONS print * print *,'To enable the "call_assert" invocations, define the ASSERTIONS macro. e.g.:' print *,' fpm run --example invoke-via-macro --flag "-DASSERTIONS -fcoarray=single -ffree-line-length-0"' print * #endif ! The C preprocessor will convert each call_assert* macro below into calls that enforce the assertion ! whenever the ASSERTIONS macro is defined to non-zero (e.g. via the -DASSERTIONS compiler flag). ! Whenever the ASSERTIONS macro is undefined or defined to zero (e.g. via the -DASSERTIONS=0 compiler flag), ! these calls will be entirely removed by the preprocessor. call_assert(1==1) ! true assertion call_assert_describe(2>0, "example assertion invocation via macro") ! true assertion #if ASSERTIONS print * print *,'Here comes the expected assertion failure:' print * #endif !call_assert(1+1>2) ! example false assertion without description call_assert_describe(1+1>2, "Mathematics is broken!") ! false assertion with description end program invoke_via_macro fortran-assert-3.1.0/fpm.toml0000664000175000017500000000005215151611147016327 0ustar alastairalastairname = "assert" [install] library = true fortran-assert-3.1.0/.gitignore0000664000175000017500000000053415151611147016645 0ustar alastairalastair# Build tree build # Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Fortran (sub)module files *.mod *.smod *.sub # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app # FORD-generated documentation files doc/html fortran-assert-3.1.0/LICENSE0000664000175000017500000001216115151611147015661 0ustar alastairalastair***************************** *** Assert LICENSE file *** ***************************** All files in this directory and all sub-directories (except where otherwise noted) are subject to the following copyright and licensing terms. *** Copyright Notice *** Assert v2.0.0 Copyright (c) 2024-2025, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy) and Sourcery Institute. All rights reserved. If you have questions about your rights to use or distribute this software, please contact Berkeley Lab's Intellectual Property Office at IPO@lbl.gov. NOTICE. This Software was developed under funding from the U.S. Department of Energy and the U.S. Government consequently retains certain rights. As such, the U.S. Government has been granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software to reproduce, distribute copies to the public, prepare derivative works, and perform publicly and display publicly, and to permit others to do so. *** License Agreement *** Assert v2.0.0 Copyright (c) 2024-2025, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy) and Sourcery Institute. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory, U.S. Dept. of Energy, Sourcery Institute, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. You are under no obligation whatsoever to provide any bug fixes, patches, or upgrades to the features, functionality or performance of the source code ("Enhancements") to anyone; however, if you choose to make your Enhancements available either publicly, or directly to Lawrence Berkeley National Laboratory, without imposing a separate written license agreement for such Enhancements, then you hereby grant the following license: a non-exclusive, royalty-free perpetual license to install, use, modify, prepare derivative works, incorporate into other computer software, distribute, and sublicense such enhancements or derivative works thereof, in binary and source code form. *** Prior Licenses *** Prior versions of assert were distributed under the following license: BSD 3-Clause License Copyright (c) 2021, Sourcery Institute All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. fortran-assert-3.1.0/include/0000775000175000017500000000000015151611147016276 5ustar alastairalastairfortran-assert-3.1.0/include/assert_features.h0000664000175000017500000000077215151611147021654 0ustar alastairalastair#ifndef _ASSERT_FEATURES_H #define _ASSERT_FEATURES_H ! Whether or not the assert library may use multi-image features ! Default is compiler-dependent #ifndef ASSERT_MULTI_IMAGE # if defined(__flang__) || defined(__INTEL_COMPILER) || defined(__LFORTRAN__) # define ASSERT_MULTI_IMAGE 0 # else # define ASSERT_MULTI_IMAGE 1 # endif #endif ! Whether the library should use client callbacks for parallel features #ifndef ASSERT_PARALLEL_CALLBACKS #define ASSERT_PARALLEL_CALLBACKS 0 #endif #endif fortran-assert-3.1.0/include/assert_macros.h0000664000175000017500000000177415151611147021325 0ustar alastairalastair! assert_macros.h: provides preprocessor-based assertion macros ! that are guaranteed to compile away statically when disabled. ! Enable repeated includes to toggle assertions based on current settings: #undef call_assert #undef call_assert_describe #ifndef ASSERTIONS ! Assertions are off by default #define ASSERTIONS 0 #endif ! Deal with stringification issues: ! https://gcc.gnu.org/legacy-ml/fortran/2009-06/msg00131.html #ifndef CPP_STRINGIFY_SOURCE # if defined(__GFORTRAN__) || defined(_CRAYFTN) || defined(NAGFOR) || defined(__LFORTRAN__) # define CPP_STRINGIFY_SOURCE(x) "x" # else # define CPP_STRINGIFY_SOURCE(x) #x # endif #endif #if ASSERTIONS # define call_assert(assertion) call assert_always(assertion, "call_assert(" // CPP_STRINGIFY_SOURCE(assertion) // ")", __FILE__, __LINE__) # define call_assert_describe(assertion, description) call assert_always(assertion, description, __FILE__, __LINE__) #else # define call_assert(assertion) # define call_assert_describe(assertion, description) #endif fortran-assert-3.1.0/.github/0000775000175000017500000000000015151611147016213 5ustar alastairalastairfortran-assert-3.1.0/.github/workflows/0000775000175000017500000000000015151611147020250 5ustar alastairalastairfortran-assert-3.1.0/.github/workflows/build.yml0000664000175000017500000002452715151611147022104 0ustar alastairalastairname: Build on: [push, pull_request] jobs: build: name: ${{ matrix.compiler }}-${{ matrix.version }} (${{ matrix.os }}) runs-on: ${{ matrix.os }} defaults: run: shell: bash strategy: fail-fast: false matrix: os: [ macos-14, macos-15, macos-15-intel, macos-26, ubuntu-24.04 ] compiler: [ gfortran ] version: [ 12, 13, 14, 15 ] extra_flags: [ -g ] include: - os: ubuntu-24.04 compiler: gfortran version: 9 - os: ubuntu-24.04 compiler: gfortran version: 10 - os: ubuntu-24.04 compiler: gfortran version: 11 - os: ubuntu-22.04 compiler: gfortran version: 12 # no package available for gfortran 13+ # --- LLVM flang coverage --- - os: macos-14 compiler: flang version: 21 - os: macos-15 compiler: flang version: 21 - os: macos-15-intel compiler: flang version: 21 # https://hub.docker.com/r/snowstep/llvm/tags - os: ubuntu-24.04 compiler: flang version: latest container: snowstep/llvm:noble - os: ubuntu-22.04 compiler: flang version: latest container: snowstep/llvm:jammy # https://hub.docker.com/r/phhargrove/llvm-flang/tags - os: ubuntu-24.04 compiler: flang version: 21 network: smp container: phhargrove/llvm-flang:21.1.0-latest - os: ubuntu-24.04 compiler: flang version: 20 container: phhargrove/llvm-flang:20.1.0-latest - os: ubuntu-24.04 compiler: flang version: 19 extra_flags: -g -mmlir -allow-assumed-rank container: phhargrove/llvm-flang:19.1.1-latest # - os: ubuntu-24.04 # compiler: flang # version: new # container: gmao/llvm-flang:latest # --- Intel coverage --- # https://hub.docker.com/r/intel/fortran-essentials/tags - os: ubuntu-24.04 compiler: ifx version: latest error_stop_code: 128 container: intel/fortran-essentials:latest - os: ubuntu-24.04 compiler: ifx version: 2025.2.0 error_stop_code: 128 container: intel/fortran-essentials:2025.2.0-0-devel-ubuntu24.04 - os: ubuntu-24.04 compiler: ifx version: 2025.1.0 error_stop_code: 128 container: intel/fortran-essentials:2025.1.0-0-devel-ubuntu24.04 - os: ubuntu-22.04 compiler: ifx version: 2025.0.0 error_stop_code: 128 container: intel/fortran-essentials:2025.0.0-0-devel-ubuntu22.04 # --- LFortran coverage --- # https://hub.docker.com/r/phhargrove/lfortran/tags - os: ubuntu-24.04 compiler: lfortran version: 0.54.0 container: phhargrove/lfortran:0.54.0-1 - os: ubuntu-24.04 compiler: lfortran version: 0.55.0 container: phhargrove/lfortran:0.55.0-1 - os: ubuntu-24.04 compiler: lfortran version: 0.56.0 container: phhargrove/lfortran:0.56.0-1 - os: ubuntu-24.04 compiler: lfortran version: 0.57.0 container: phhargrove/lfortran:0.57.0-1 - os: ubuntu-24.04 compiler: lfortran version: 0.58.0 container: phhargrove/lfortran:0.58.0-1 # https://github.com/lfortran/lfortran/pkgs/container/lfortran - os: ubuntu-22.04 compiler: lfortran version: latest container: ghcr.io/lfortran/lfortran:latest container: image: ${{ matrix.container }} env: COMPILER_VERSION: ${{ matrix.version }} FC: ${{ matrix.compiler }} FFLAGS: ${{ matrix.extra_flags }} FPM_FLAGS: --profile release --verbose CHECK_ASSERT: --flag -DASSERTIONS 2>&1 | tee output ; test ${PIPESTATUS[0]} = $ERROR_STOP_CODE && grep -q "Assertion failure" output steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Ubuntu Native Dependencies if: ${{ contains(matrix.os, 'ubuntu') && matrix.container == '' && matrix.compiler == 'gfortran' }} run: | set -x sudo apt update #sudo apt list -a 'gfortran-*' sudo apt install -y build-essential if (( ${COMPILER_VERSION} < 15 )) ; then \ sudo apt install -y gfortran-${COMPILER_VERSION} ; \ else \ curl -L https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh -o install-homebrew.sh ; \ chmod +x install-homebrew.sh ; \ env CI=1 ./install-homebrew.sh ; \ HOMEBREW_PREFIX="/home/linuxbrew/.linuxbrew" ; \ ${HOMEBREW_PREFIX}/bin/brew install -v gcc@${COMPILER_VERSION} binutils ; \ ls -al ${HOMEBREW_PREFIX}/bin ; \ echo "PATH=${HOMEBREW_PREFIX}/bin:${PATH}" >> "$GITHUB_ENV" ; \ : Homebrew GCC@15 needs binutils 2.44+ ; \ HOMEBREW_BINUTILS=$(ls -d ${HOMEBREW_PREFIX}/Cellar/binutils/2.*/bin ) ; \ ls -al ${HOMEBREW_BINUTILS} ; \ echo "FFLAGS=$FFLAGS -B ${HOMEBREW_BINUTILS}" >> "$GITHUB_ENV" ; \ echo "CFLAGS=$CFLAGS -B ${HOMEBREW_BINUTILS}" >> "$GITHUB_ENV" ; \ echo "CXXFLAGS=$CXXFLAGS -B ${HOMEBREW_BINUTILS}" >> "$GITHUB_ENV" ; \ fi - name: Install Ubuntu Container Dependencies if: ${{ contains(matrix.os, 'ubuntu') && matrix.container != '' && !contains(matrix.container, 'phhargrove') }} run: | set -x apt update apt install -y build-essential # pkg-config make git curl # Add container lfortran to PATH: if test "$FC" = "lfortran"; then \ echo "/app/bin" >> "$GITHUB_PATH" ; \ ls -alh /app/bin ; \ ls -alh /app/share/lfortran/lib/ ; \ fi - name: Install macOS Dependencies if: contains(matrix.os, 'macos') run: | set -x brew update # fpm binary distribution for macOS requires gfortran shared libraries from gcc@12 brew install gcc@12 - name: Install LLVM flang on macOS if: contains(matrix.os, 'macos') && matrix.compiler == 'flang' run: | set -x brew install llvm@${COMPILER_VERSION} flang # workaround issue #228: clang cannot find homebrew flang's C header for p in /opt/homebrew /usr/local $(brew --prefix) ; do find $p/Cellar/flang -name ISO_Fortran_binding.h 2>/dev/null || true ; done echo "CFLAGS=-I$(dirname $(find $(brew --prefix)/Cellar/flang -name ISO_Fortran_binding.h | head -1)) ${CFLAGS}" >> "$GITHUB_ENV" # Prepend homebrew clang to PATH: echo "PATH=$(brew --prefix)/opt/llvm/bin:${PATH}" >> "$GITHUB_ENV" - name: Setup Compilers run: | set -x if test "$FC" = "flang" ; then \ echo "FPM_FC=flang-new" >> "$GITHUB_ENV" ; \ elif test "$FC" = "ifx" ; then \ echo "FPM_FC=ifx" >> "$GITHUB_ENV" ; \ elif test "$FC" = "lfortran" ; then \ echo "FPM_FC=lfortran" >> "$GITHUB_ENV" ; \ echo "FFLAGS=--cpp $FFLAGS" >> "$GITHUB_ENV" ; \ else \ echo "FPM_FC=gfortran-${COMPILER_VERSION}" >> "$GITHUB_ENV" ; \ echo "FFLAGS=-ffree-line-length-0 $FFLAGS" >> "$GITHUB_ENV" ; \ fi if [[ "${{ matrix.container }}" =~ "snowstep/llvm" ]] ; then \ echo "LD_LIBRARY_PATH=/usr/lib/llvm-22/lib:$LD_LIBRARY_PATH" >> "$GITHUB_ENV" ; \ fi if test -n "${{ matrix.error_stop_code }}" ; then \ echo "ERROR_STOP_CODE=${{ matrix.error_stop_code }}" >> "$GITHUB_ENV" ; \ else \ echo "ERROR_STOP_CODE=1" >> "$GITHUB_ENV" ; \ fi - name: Setup FPM uses: fortran-lang/setup-fpm@main with: github-token: ${{ secrets.GITHUB_TOKEN }} fpm-version: latest - name: Build FPM if: false run: | set -x curl --retry 5 -LOsS https://github.com/fortran-lang/fpm/releases/download/v0.11.0/fpm-0.11.0.F90 mkdir fpm-temp gfortran-14 -o fpm-temp/fpm fpm-0.11.0.F90 echo "PATH=`pwd`/fpm-temp:${PATH}" >> "$GITHUB_ENV" - name: Version info run: | echo == TOOL VERSIONS == echo Platform version info: uname -a if test -r /etc/os-release ; then grep -e NAME -e VERSION /etc/os-release ; fi if test -x /usr/bin/sw_vers ; then /usr/bin/sw_vers ; fi echo echo PATH="$PATH" for tool in ${FPM_FC} fpm ; do ( echo ; set -x ; w=$(which $tool) ; ls -al $w ; ls -alhL $w ; $tool --version ) done - name: Build and Test (Assertions OFF) run: | set -x fpm test ${FPM_FLAGS} --flag "$FFLAGS" fpm run --example false-assertion ${FPM_FLAGS} --flag "$FFLAGS" fpm run --example simple-assertions ${FPM_FLAGS} --flag "$FFLAGS" fpm run --example invoke-via-macro ${FPM_FLAGS} --flag "$FFLAGS" - name: Build and Test (Assertions ON) run: | set -x fpm test ${FPM_FLAGS} --flag "$FFLAGS" --flag -DASSERTIONS ( set +e ; eval fpm run --example false-assertion ${FPM_FLAGS} --flag \"$FFLAGS\" $CHECK_ASSERT ) ( set +e ; eval fpm run --example simple-assertions ${FPM_FLAGS} --flag \"$FFLAGS\" $CHECK_ASSERT ) ( set +e ; eval fpm run --example invoke-via-macro ${FPM_FLAGS} --flag \"$FFLAGS\" $CHECK_ASSERT ) - name: Test Assertions w/ Parallel Callbacks if: ${{ matrix.compiler != 'lfortran' || matrix.version == 'latest' }} # issue #68 env: FPM_FLAGS: ${{ env.FPM_FLAGS }} --flag -DASSERT_MULTI_IMAGE --flag -DASSERT_PARALLEL_CALLBACKS run: | set -x fpm run --example false-assertion ${FPM_FLAGS} --flag "$FFLAGS" fpm run --example invoke-via-macro ${FPM_FLAGS} --flag "$FFLAGS" ( set +e ; eval fpm run --example false-assertion ${FPM_FLAGS} --flag \"$FFLAGS\" $CHECK_ASSERT ) ( set +e ; eval fpm run --example invoke-via-macro ${FPM_FLAGS} --flag \"$FFLAGS\" $CHECK_ASSERT )