Skip to content

Commit 6047dd8

Browse files
authored
Merge pull request #41 from bonachea/assertions-knob
Change assertion enforcement control knob to ASSERTIONS=1/0
2 parents fc867ce + 22080ee commit 6047dd8

6 files changed

Lines changed: 95 additions & 46 deletions

File tree

.github/workflows/deploy-docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ jobs:
2323
2424
- name: Build Developer Documentation
2525
run: |
26+
ford -I include doc-generator.md > ford_output.txt
2627
# Turn warnings into errors
27-
ford doc-generator.md > ford_output.txt
2828
cat ford_output.txt; if grep -q -i Warning ford_output.txt; then exit 1; fi
2929
cp ./README.md ./doc/html
3030

README.md

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
Assert
22
======
33

4-
A simple assertion utility taking advantage of the Fortran 2018 standard's introduction of variable stop codes
5-
and error termination inside pure procedures.
4+
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.
65

76
Motivations
87
-----------
@@ -11,18 +10,37 @@ Motivations
1110

1211
Overview
1312
--------
14-
This assertion utility contains three public entities:
13+
This assertion utility contains four public entities:
1514

1615
1. An `assert` subroutine,
1716
2. A `characterizable_t` abstract type supporting `assert`, and
1817
3. An `intrinsic_array_t` non-abstract type extending `characterizable_t`.
18+
4. An `assert_macros.h` header file containing C-preprocessor macros.
1919

2020
The `assert` subroutine
21-
22-
* Error-terminates with a variable stop code when a user-defined logical assertion fails,
21+
* Error-terminates with a variable stop code when a caller-provided logical assertion fails,
2322
* Includes user-supplied diagnostic data in the output if provided by the calling procedure,
2423
* Is callable inside `pure` procedures, and
25-
* Can be eliminated during an optimizing compiler's dead-code removal phase based on a preprocessor macro: `-DUSE_ASSERTIONS=.false.`.
24+
* Can be eliminated at compile-time, as controlled by the `ASSERTIONS` preprocessor define.
25+
26+
Assertion enforcement is controlled via the `ASSERTIONS` preprocessor macro,
27+
which can be defined to non-zero or zero at compilation time to
28+
respectively enable or disable run-time assertion enforcement.
29+
30+
When the `ASSERTIONS` preprocessor macro is not defined to any value,
31+
the default is that assertions are *disabled* and will not check the condition.
32+
33+
To enable assertion enforcement (e.g., for a debug build), define the
34+
preprocessor ASSERTIONS to non-zero, eg:
35+
```
36+
fpm build --flag "-DASSERTIONS"
37+
```
38+
The program [example/invoke-via-macro.F90] demonstrates the preferred way to invoke the `assert` subroutine via the three provided macros.
39+
Invoking `assert` this way insures that `assert` invocations will be completely removed whenever the `ASSERTIONS` macro is undefined (or defined to zero) during compilation.
40+
Due to a limitation of `fpm`, this approach works best if the project using Assert is also a `fpm` project.
41+
If instead `fpm install` is used, then either the user must copy `include/assert_macros.h` to the installation directory (default: `~/.local/include`) or
42+
the user must invoke `assert` directly (via `call assert(...)`).
43+
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.
2644

2745
The `characterizable_t` type defines an `as_character()` deferred binding that produces `character` strings for use as diagnostic output from a user-defined derived type that extends `characterizable_t` and implements the deferred binding.
2846

@@ -43,7 +61,7 @@ The requirements and assurances might be constraints of three kinds:
4361
2. **Postconditions (assurances):** expressions that must evaluate to `.true.` when a procedure finishes execution, and
4462
3. **Invariants:** universal pre- and postconditions that must always be true when all procedures in a class start or finish executing.
4563

46-
The [examples/README.md] file shows examples of writing constraints in notes on class diagrams using the formal syntax of the Object Constraint Language ([OCL]).
64+
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]).
4765

4866
Downloading, Building, and Running Examples
4967
-------------------------------------------
@@ -58,14 +76,14 @@ cd assert
5876
#### Single-image (serial) execution
5977
The following command builds Assert and runs the full test suite in a single image:
6078
```
61-
fpm test --profile release
79+
fpm test --profile release --flag "-ffree-line-length-0"
6280
```
63-
which builds the Assert library and runs the test suite.
81+
which builds the Assert library (with the default of assertion enforcement disabled) and runs the test suite.
6482

6583
#### Multi-image (parallel) execution
6684
With `gfortran` and OpenCoarrays installed,
6785
```
68-
fpm test --compiler caf --profile release --runner "cafrun -n 2"
86+
fpm test --compiler caf --profile release --runner "cafrun -n 2" --flag "-ffree-line-length-0"
6987
```
7088
To build and test with the Numerical Algorithms Group (NAG) Fortran compiler version
7189
7.1 or later, use
@@ -80,7 +98,6 @@ fpm test --compiler ifx --profile release --flag -coarray
8098
### Building and testing with the LLVM `flang-new` compiler
8199
```
82100
fpm test --compiler flang-new --flag "-mmlir -allow-assumed-rank -O3"
83-
84101
```
85102

86103
### Building and testing with the Numerical Algorithms Group (NAG) compiler
@@ -180,9 +197,9 @@ Instead when breaking long lines in a macro invocation, just break the line (no
180197
continuation character!), eg:
181198

182199
```fortran
183-
! When breaking a lines in a macro invocation, just use new-line with no `&` continuation character:
184-
call_assert_diagnose( computed_checksum == expected_checksum,
185-
"Checksum mismatch failure!",
200+
! When breaking a line in a macro invocation, use backslash `\` continuation character:
201+
call_assert_diagnose( computed_checksum == expected_checksum, \
202+
"Checksum mismatch failure!", \
186203
expected_checksum )
187204
```
188205

@@ -206,8 +223,8 @@ comment (because they are removed by the preprocessor), for example with
206223
gfortran one can instead write the following:
207224

208225
```fortran
209-
call_assert_diagnose( computed_checksum == expected_checksum, /* ensured since version 3.14 */
210-
"Checksum mismatch failure!", /* TODO: write a better message here */
226+
call_assert_diagnose( computed_checksum == expected_checksum, /* ensured since version 3.14 */ \
227+
"Checksum mismatch failure!", /* TODO: write a better message here */ \
211228
computed_checksum )
212229
```
213230

@@ -216,8 +233,8 @@ When in doubt, one can always move the comment outside the macro invocation:
216233

217234
```fortran
218235
! assert a property ensured since version 3.14
219-
call_assert_diagnose( computed_checksum == expected_checksum,
220-
"Checksum mismatch failure!",
236+
call_assert_diagnose( computed_checksum == expected_checksum, \
237+
"Checksum mismatch failure!", \
221238
computed_checksum ) ! TODO: write a better message above
222239
```
223240

@@ -237,3 +254,4 @@ See the [LICENSE](LICENSE) file for copyright and licensing information.
237254
[OCL]: https://en.wikipedia.org/wiki/Object_Constraint_Language
238255
[Assert's GitHub Pages site]: https://berkeleylab.github.io/assert/
239256
[`ford`]: https://github.com/Fortran-FOSS-Programmers/ford
257+
[example/invoke-via-macro.F90]: ./example/invoke-via-macro.F90

example/invoke-via-macro.F90

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,32 @@
22

33
program invoke_via_macro
44
!! Demonstrate how to invoke the 'assert' subroutine using a preprocessor macro that facilitates
5-
!! the complete removal of the call in the absence of the compiler flag -DDEBUG.
5+
!! the complete removal of the call in the absence of the compiler flag: -DASSERTIONS
66
use assert_m, only : assert, intrinsic_array_t, string
77
!! If an "only" clause is employed as above, it must include the "string" function that the
88
!! call_assert* macros reference when transforming the code below into "assert" subroutine calls.
99
implicit none
1010

11-
#ifndef DEBUG
11+
#if !ASSERTIONS
1212
print *
13-
print *,'To enable the "assert" call, define -DDEBUG, e.g., fpm run --example invoke-via-macro --flag "-DDEBUG -fcoarray=single"'
13+
print *,'To enable the "call_assert" invocations, define the ASSERTIONS macro. e.g.:'
14+
print *,' fpm run --example invoke-via-macro --flag "-DASSERTIONS -fcoarray=single -ffree-line-length-0"'
1415
print *
1516
#endif
1617

1718
! The C preprocessor will convert each call_assert* macro below into calls to the "assert" subroutine
18-
! (if -DDEBUG is in the compiler command) or into nothing (if -DDEBUG is not in the compiler command).
19+
! whenever the ASSERTIONS macro is defined to non-zero (e.g. via the -DASSERTIONS compiler flag).
20+
! Whenever the ASSERTIONS macro is undefined or defined to zero (e.g. via the -DASSERTIONS=0 compiler flag),
21+
! these calls will be entirely removed by the preprocessor.
1922

2023
call_assert(1==1) ! true assertion
2124
call_assert_describe(2>0, "example assertion invocation via macro") ! true assertion
2225
call_assert_diagnose(1+1==2, "example with scalar diagnostic data", 1+1) ! true assertion
26+
#if ASSERTIONS
27+
print *
28+
print *,'Here comes the expected assertion failure:'
29+
print *
30+
#endif
2331
call_assert_diagnose(1+1>2, "example with array diagnostic data" , intrinsic_array_t([1,1,2])) ! false assertion
2432

2533
end program invoke_via_macro

include/assert_macros.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
#undef call_assert_describe
77
#undef call_assert_diagnose
88

9-
#ifdef DEBUG
9+
#ifndef ASSERTIONS
10+
! Assertions are off by default
11+
#define ASSERTIONS 0
12+
#endif
13+
14+
#if ASSERTIONS
1015
# define call_assert(assertion) call assert(assertion, "No description provided (see file " // __FILE__ // ", line " // string(__LINE__) // ")")
1116
# define call_assert_describe(assertion, description) call assert(assertion, description // " in file " // __FILE__ // ", line " // string(__LINE__) // ": " )
1217
# define call_assert_diagnose(assertion, description, diagnostic_data) call assert(assertion, "file " // __FILE__ // ", line " // string(__LINE__) // ": " // description, diagnostic_data)

src/assert/assert_subroutine_m.F90

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,48 @@
1+
! (c) 2024 UC Regents, see LICENSE file for detailed terms.
12
!
23
! (c) 2019-2020 Guide Star Engineering, LLC
34
! This Software was developed for the US Nuclear Regulatory Commission (US NRC) under contract
45
! "Multi-Dimensional Physics Implementation into Fuel Analysis under Steady-state and Transients (FAST)",
56
! contract # NRC-HQ-60-17-C-0007
67
!
8+
#include "assert_macros.h"
9+
710
module assert_subroutine_m
8-
!! summary: Utility for runtime checking of logical assertions.
11+
!! summary: Utility for runtime enforcement of logical assertions.
912
!! usage: error-terminate if the assertion fails:
1013
!!
1114
!! use assertions_m, only : assert
1215
!! call assert( 2 > 1, "2 > 1")
1316
!!
14-
!! Turn off assertions in production code by setting USE_ASSERTIONS to .false. via the preprocessor.
17+
!! Assertion enforcement is controlled via the `ASSERTIONS` preprocessor macro,
18+
!! which can be defined to non-zero or zero at compilation time to
19+
!! respectively enable or disable runtime assertion enforcement.
20+
!!
21+
!! When the `ASSERTIONS` preprocessor macro is not defined to any value,
22+
!! the default is that assertions are *disabled* and will not check the condition.
23+
!!
24+
!! Disabling assertion enforcement may eliminate any associated runtime
25+
!! overhead by enabling optimizing compilers to ignore the assertion procedure
26+
!! body during a dead-code-removal phase of optimization.
27+
!!
28+
!! To enable assertion enforcement (e.g., for a debug build), define the preprocessor ASSERTIONS to non-zero.
1529
!! This file's capitalized .F90 extension causes most Fortran compilers to preprocess this file so
16-
!! that building as follows turns off assertion enforcement:
30+
!! that building as follows enables assertion enforcement:
1731
!!
18-
!! fpm build --flag "-DUSE_ASSERTIONS=.false."
32+
!! fpm build --flag "-DASSERTIONS"
1933
!!
20-
!! Doing so may eliminate any associated runtime overhead by enabling optimizing compilers to ignore
21-
!! the assertion procedure body during a dead-code-removal phase of optimization.
2234
implicit none
2335
private
2436
public :: assert
2537

2638
#ifndef USE_ASSERTIONS
27-
# define USE_ASSERTIONS .true.
39+
# if ASSERTIONS
40+
# define USE_ASSERTIONS .true.
41+
# else
42+
# define USE_ASSERTIONS .false.
43+
# endif
2844
#endif
2945
logical, parameter :: enforce_assertions=USE_ASSERTIONS
30-
!! Turn off assertions as follows: fpm build --flag "-DUSE_ASSERTIONS=.false."
3146

3247
interface
3348

test/test-assert-macro.F90

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,60 @@ program test_assert_macros
55
print *
66
print *,"The call_assert macro"
77

8-
#define DEBUG
8+
#undef ASSERTIONS
9+
#define ASSERTIONS 1
910
#include "assert_macros.h"
1011
call_assert(1==1)
1112
print *," passes on not error-terminating when an assertion expression evaluating to .true. is the only argument"
1213

13-
#undef DEBUG
14+
#undef ASSERTIONS
1415
#include "assert_macros.h"
1516
call_assert(.false.)
16-
print *," passes on being removed by the preprocessor when DEBUG is undefined" // new_line('')
17+
print *," passes on being removed by the preprocessor when ASSERTIONS is undefined" // new_line('')
1718

1819
!------------------------------------------
1920

2021
print *,"The call_assert_describe macro"
2122

22-
#define DEBUG
23+
#undef ASSERTIONS
24+
#define ASSERTIONS 1
2325
#include "assert_macros.h"
2426
call_assert_describe(.true., ".true.")
2527
print *," passes on not error-terminating when assertion = .true. and a description is present"
2628

27-
#undef DEBUG
29+
#undef ASSERTIONS
2830
#include "assert_macros.h"
2931
call_assert_describe(.false., "")
30-
print *," passes on being removed by the preprocessor when DEBUG is undefined" // new_line('')
32+
print *," passes on being removed by the preprocessor when ASSERTIONS is undefined" // new_line('')
3133

3234
!------------------------------------------
3335

3436
print *,"The call_assert_diagnose macro"
3537

36-
#define DEBUG
38+
#undef ASSERTIONS
39+
#define ASSERTIONS 1
3740
#include "assert_macros.h"
3841
call_assert_diagnose(.true., ".true.", diagnostic_data=1)
3942
print *," passes on not error-terminating when assertion = .true. and description and diagnostic_data are present"
4043

4144
block
4245
integer :: computed_checksum = 37, expected_checksum = 37
4346

44-
call_assert_diagnose( computed_checksum == expected_checksum,
45-
"Checksum mismatch failure!",
46-
expected_checksum )
47+
call_assert_diagnose( computed_checksum == expected_checksum, \
48+
"Checksum mismatch failure!", \
49+
expected_checksum )
4750
print *," passes with macro-style line breaks"
4851

49-
call_assert_diagnose( computed_checksum == expected_checksum, /* ensured since version 3.14 */
50-
"Checksum mismatch failure!", /* TODO: write a better message here */
52+
call_assert_diagnose( computed_checksum == expected_checksum, /* ensured since version 3.14 */ \
53+
"Checksum mismatch failure!", /* TODO: write a better message here */ \
5154
computed_checksum )
5255
print *," passes with C block comments embedded in macro"
5356

5457
end block
5558

56-
#undef DEBUG
59+
#undef ASSERTIONS
5760
#include "assert_macros.h"
5861
call_assert_describe(.false., "")
59-
print *," passes on being removed by the preprocessor when DEBUG is undefined"
62+
print *," passes on being removed by the preprocessor when ASSERTIONS is undefined"
6063

6164
end program

0 commit comments

Comments
 (0)