Skip to content
Merged
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,22 +156,33 @@ Getting Started
---------------
### Writing Assertions
To write a Julienne assertion, insert a function-like preprocessor macro
`call_julienne_assert` on a single line as in the following program:
`call_julienne_assert` on a single line as in each of the two macro
invocations below:
```fortran
#include "julienne-assertion-macros.h"
program main
use, julienne_m, only : call_julienne_assert_
implicit none
real, parameter :: x=1., y=2., tolerance=3.
call_julienne_assert(x .approximates. y .within. tolerance)
call_julienne_assert(abs(x-y) < tolerance)
end program
```
where inserting `-DASSERTIONS` in a compile command will expand the macro to
where inserting `-DASSERTIONS` in a compile command will expand the macros to
```fortran
call call_julienne_assert_(x .approximates. y .within. tolerance)
call call_julienne_assert_(x .approximates. y .within. tolerance, __FILE__, __LINE__)
call call_julienne_assert_(allocated(a), __FILE__, __LINE__)
```
and where dots (`.`) delimit Julienne operators and the parenthetical expression
evaluates to a Julienne `test_diagnosis_t` object.
and where dots (`.`) delimit Julienne operators. The above expression containing
Julienne operators evaluates to a Julienne `test_diagnosis_t` object, whereas
expression `allocated(a)` on the subsequent line evaluates to a `logical` value.
Comment thread
rouson marked this conversation as resolved.
If an assertion containing a Julienne expression fails, Julienne inserts diagnostic
information into the stop code in an ultimate `error stop`. If an expression
evaluates to a `logical` value of `false.`, the error stop code will contain a
literal copy of the expression (e.g., `allocated(a)`). In either case, Julienne
also inserts the file and line number into the stop code using via the `__FILE__`
Comment thread
rouson marked this conversation as resolved.
and `__LINE__` macros, respectively. Most compilers write the resulting stop code
to `error_unit`.

### Writing Unit Tests
Writing tests using Julienne involves constructing a test-description array,
Expand Down
2 changes: 1 addition & 1 deletion fpm.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "julienne"

[dependencies]
assert = {git = "https://github.com/berkeleylab/assert", tag = "3.0.0"}
assert = {git = "https://github.com/berkeleylab/assert", tag = "3.0.2"}

[install]
library = true
53 changes: 53 additions & 0 deletions src/julienne/julienne_assert_m.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

module julienne_assert_m
!! Define interfaces for writing assertions
use julienne_test_diagnosis_m, only : test_diagnosis_t
implicit none

private
public :: call_julienne_assert_
public :: julienne_assert

interface call_julienne_assert_

pure module subroutine julienne_assert(test_diagnosis, file, line)
!! This subroutine wraps the Assert library's `assert` subroutine.
!!
!! Use Cases
!! ---------
!! 1. Invoke julienne_assert via the generic interface `call_julienne_assert` to
!! facilitate complete removal when compiling without the flag `-DASSERTIONS`.
!! 2. Invoke julienne_assert via direct procedure call to guarantee execution.
!!
!! Usage
!! -----
!! Make the only actual argument an expression containing `test_diagnosis_t` defined
!! operations, such as `x .approximates. y .within. tolerance`. The expression
!! result will be a `test_diagnosis_t` object on which `julienne_assert` will invoke
!! the `diagnostics_string()` type-bound procedure, the result of which julienne_assert
!! will include in the stop code of an `error stop` if the expresssion is untrue.
!! The resulting stop code will contain such information as the operand values and
!! roles (expected value, actual value, tolerance value). In use case 1, compiling
!! with `-DASSERTIONS` will cause the preprocessor to insert the corresponding
!! invocations's line number and the encompassing file's name as the `file` and `line`
!! arguments, respectively, which `julienne_assert` will include in the stop code.
!! Most compilers will write the stop code to `error_unit`.
!!
!! If a literal reproduction of the test expression suffices, such as when the
!! expression is `allocated(a)`, then instead invoke the Assert library's `assert`
!! subroutine by that library's `call_assert` macro or by direct call.
!! When invoking via the macro, make the only actual argument, `assertion`, a
!! `logical` expression. Then if compiling with `-DASSERTIONS` and if the assertion
!! evaluates to `.false.`, the stop code will include the text of the expression
!! argument, the file name, and the line number of the `call_assert` macro invocation.
implicit none
type(test_diagnosis_t), intent(in) :: test_diagnosis
character(len=*), intent(in), optional :: file
integer, intent(in), optional :: line
end subroutine

end interface

end module julienne_assert_m
19 changes: 19 additions & 0 deletions src/julienne/julienne_assert_s.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

submodule(julienne_assert_m) julienne_assert_s
use assert_m, only : assert_always
use julienne_m, only : string_t
implicit none

contains

module procedure julienne_assert
character(len=:), allocatable :: diagnostics_string
diagnostics_string = test_diagnosis%diagnostics_string()
if (present(file)) diagnostics_string = diagnostics_string // " in file " // file
if (present(line)) diagnostics_string = diagnostics_string // " at line " // string_t(line)
call assert_always(test_diagnosis%test_passed(), diagnostics_string)
end procedure
Comment on lines +11 to +17

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because you are using Assert 3+, this procedure could more simply and efficiently be written as:

module procedure julienne_assert
   call assert_always(test_diagnosis%test_passed(), test_diagnosis%diagnostics_string(), file, line)
end procedure

In addition to simplicity, this also has the significant advantage to skipping most of the string-handling overhead for the common case when the assertion is .true. (and the description string is therefore "dead").

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bonachea nice! I'll try this solution.


end submodule julienne_assert_s
6 changes: 3 additions & 3 deletions src/julienne/julienne_bin_s.F90
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

#include "assert_macros.h"
#include "julienne-assert-macros.h"

submodule(julienne_bin_m) julienne_bin_s
use assert_m
use julienne_m, only : call_julienne_assert_, operator(.isAtLeast.)
implicit none

contains

module procedure construct

call_assert(num_items>=num_bins)
call_julienne_assert(num_items .isAtLeast. num_bins)

associate( remainder => mod(num_items, num_bins), items_per_bin => num_items/num_bins)

Expand Down
1 change: 1 addition & 0 deletions src/julienne/julienne_command_line_m.f90
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

module julienne_command_line_m
!! return command line argument information
implicit none
Expand Down
1 change: 1 addition & 0 deletions src/julienne/julienne_command_line_s.f90
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

submodule(julienne_command_line_m) julienne_command_line_s
implicit none

Expand Down
1 change: 1 addition & 0 deletions src/julienne/julienne_file_m.f90
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

module julienne_file_m
!! A representation of a file as an object
use julienne_string_m, only : string_t
Expand Down
1 change: 1 addition & 0 deletions src/julienne/julienne_formats_m.F90
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

module julienne_formats_m
!! Useful strings for formatting `print` and `write` statements
implicit none
Expand Down
1 change: 1 addition & 0 deletions src/julienne/julienne_formats_s.F90
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

submodule(julienne_formats_m) julienne_formats_s
!! Construct separated-value formats
implicit none
Expand Down
1 change: 1 addition & 0 deletions src/julienne/julienne_github_ci_m.f90
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

module julienne_github_ci_m
!! Detect whether a program is running in GitHub Continuous Integration (CI)
implicit none
Expand Down
1 change: 1 addition & 0 deletions src/julienne/julienne_github_ci_s.f90
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

submodule(julienne_github_ci_m) julienne_github_ci_s
implicit none

Expand Down
1 change: 1 addition & 0 deletions src/julienne/julienne_string_m.f90
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

module julienne_string_m
use iso_c_binding, only : c_bool
implicit none
Expand Down
21 changes: 12 additions & 9 deletions src/julienne/julienne_string_s.F90
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

#include "julienne-assert-macros.h"
#include "assert_macros.h"

submodule(julienne_string_m) julienne_string_s
use assert_m
use julienne_m, only : call_julienne_assert_, operator(.equalsExpected.)
implicit none

integer, parameter :: integer_width_supremum = 11, default_real_width_supremum = 20, double_precision_width_supremum = 25
Expand Down Expand Up @@ -185,7 +188,7 @@
module procedure get_real
character(len=:), allocatable :: raw_line, string_value

call_assert(key==self%get_json_key())
call_julienne_assert(self%get_json_key() .equalsExpected. key)

raw_line = self%string()
associate(text_after_colon => raw_line(index(raw_line, ':')+1:))
Expand All @@ -204,7 +207,7 @@
module procedure get_double_precision
character(len=:), allocatable :: raw_line, string_value

call_assert(key==self%get_json_key())
call_julienne_assert(self%get_json_key() .equalsExpected. key)

raw_line = self%string()
associate(text_after_colon => raw_line(index(raw_line, ':')+1:))
Expand Down Expand Up @@ -247,7 +250,7 @@
character(len=:), allocatable :: raw_line
integer i, comma, opening_quotes, closing_quotes

call_assert(key==self%get_json_key())
call_julienne_assert(self%get_json_key() .equalsExpected. key)

raw_line = self%string()

Expand Down Expand Up @@ -275,7 +278,7 @@

character(len=:), allocatable :: raw_line

call_assert(key==self%get_json_key())
call_julienne_assert(self%get_json_key() .equalsExpected. key)

raw_line = self%string()
associate(text_after_colon => raw_line(index(raw_line, ':')+1:))
Expand All @@ -299,7 +302,7 @@
module procedure get_logical
character(len=:), allocatable :: raw_line, string_value

call_assert(key==self%get_json_key())
call_julienne_assert(self%get_json_key() .equalsExpected. key)

raw_line = self%string()
associate(text_after_colon => raw_line(index(raw_line, ':')+1:))
Expand All @@ -309,7 +312,7 @@
else
string_value = trim(adjustl((text_after_colon(:trailing_comma-1))))
end if
call_assert(string_value=="true" .or. string_value=="false")
call_assert(any(string_value==['true ', 'false']))
value_ = string_value == "true"
end associate
end associate
Expand All @@ -319,7 +322,7 @@
module procedure get_integer
character(len=:), allocatable :: raw_line, string_value

call_assert(key==self%get_json_key())
call_julienne_assert(self%get_json_key() .equalsExpected. key)

raw_line = self%string()
associate(text_after_colon => raw_line(index(raw_line, ':')+1:))
Expand Down Expand Up @@ -360,7 +363,7 @@
real, allocatable :: real_array(:)
integer i

call_assert(key==self%get_json_key())
call_julienne_assert(self%get_json_key() .equalsExpected. key)

raw_line = self%string()
associate(colon => index(raw_line, ":"))
Expand All @@ -384,7 +387,7 @@
double precision, allocatable :: double_precision_array(:)
integer i

call_assert(key==self%get_json_key())
call_julienne_assert(self%get_json_key() .equalsExpected. key)

raw_line = self%string()
associate(colon => index(raw_line, ":"))
Expand Down
1 change: 1 addition & 0 deletions src/julienne/julienne_test_description_m.f90
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

module julienne_test_description_m
!! Define an abstraction for describing test intentions and test functions
use julienne_string_m, only : string_t
Expand Down
2 changes: 2 additions & 0 deletions src/julienne/julienne_test_description_s.F90
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
! Copyright (c) 20242-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

#include "julienne-assert-macros.h"
#include "assert_macros.h"

submodule(julienne_test_description_m) julienne_test_description_s
use assert_m
use julienne_m, only : call_julienne_assert_
implicit none
contains

Expand Down
21 changes: 2 additions & 19 deletions src/julienne/julienne_test_diagnosis_m.F90
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@
#include "language-support.F90"

module julienne_test_diagnosis_m
!! Define abstractions, defined operations, and procedures for writing correctness checks in
!! the form of assertions and tests.
!! Define abstractions, defined operations, and procedures for writing correctness checks
use julienne_string_m, only : string_t
implicit none

private
public :: test_diagnosis_t
public :: call_julienne_assert_
public :: julienne_assert
public :: operator(.all.)
public :: operator(.and.)
public :: operator(.also.)
Expand All @@ -33,7 +30,7 @@ module julienne_test_diagnosis_m
type test_diagnosis_t
!! Encapsulate test outcome and diagnostic information
private
logical test_passed_
logical :: test_passed_ = .false.
character(len=:), allocatable :: diagnostics_string_
contains
procedure test_passed
Expand All @@ -57,20 +54,6 @@ module julienne_test_diagnosis_m
end type
#endif

interface call_julienne_assert_

pure module subroutine julienne_assert(test_diagnosis, file, line)
!! Use cases:
!! 1. When invoked via the generic interface, the preprocessor passes the 'file' and 'line' dummy arguments automatically.
!! 2. When invoked directly, there is 1 argument: an expression containing defined operations such as 1 .equalsExpected. 1
implicit none
type(test_diagnosis_t), intent(in) :: test_diagnosis
character(len=*), intent(in), optional :: file
integer, intent(in), optional :: line
end subroutine

end interface

interface operator(.all.)

#ifndef __GFORTRAN__
Expand Down
10 changes: 1 addition & 9 deletions src/julienne/julienne_test_diagnosis_s.F90
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

submodule(julienne_test_diagnosis_m) julienne_test_diagnosis_s
use assert_m
use julienne_string_m, only : operator(.csv.), operator(.cat.)
use julienne_m, only : operator(.cat.)
implicit none
contains

Expand Down Expand Up @@ -541,12 +541,4 @@ pure function aggregate_vector_diagnosis(diagnoses) result(diagnosis)
string_ = string_t(self%diagnostics_string_)
end procedure

module procedure julienne_assert
character(len=:), allocatable :: diagnostics_string
diagnostics_string = test_diagnosis%diagnostics_string_
if (present(file)) diagnostics_string = diagnostics_string // " in file " // file
if (present(line)) diagnostics_string = diagnostics_string // " at line " // string_t(line)
call assert_always(test_diagnosis%test_passed_, diagnostics_string)
end procedure

end submodule julienne_test_diagnosis_s
1 change: 1 addition & 0 deletions src/julienne/julienne_test_result_m.f90
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

module julienne_test_result_m
!! Define an abstraction for describing test intentions and results
use julienne_string_m, only : string_t
Expand Down
1 change: 1 addition & 0 deletions src/julienne/julienne_test_result_s.f90
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
! Terms of use are as specified in LICENSE.txt

submodule(julienne_test_result_m) julienne_test_result_s
use julienne_user_defined_collectives_m, only : co_all
implicit none
Expand Down
Loading