| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
| Key Features | Things You Need To Know |
|---|---|
| Idioms for unit testing & assertions | Getting started |
| A file abstraction | Building and Testing |
| Printing in Pure Procedures |
The Julienne framework offers unified approaches to unit testing, assertion enforcement, and formatted error-output inside pure procedures. Julienne defines idioms for specifying correctness conditions in a common way in tests that wrap the tested procedures or assertions that conditionally execute inside procedures. Julienne idioms center around expressions built from defined operations: a uniquely flexible Fortran capability allowing developers to define new operators or to overloading Fortran's intrinsic operators.
| Example expressions | Supported operand types |
|---|---|
| x .approximates. y .within. tolerance | real, double precision for x, y, tolerance |
| x .approximates. y .withinFraction. tolerance | real, double precision for x, y, tolerance |
| x .approximates. y .withinPercentage. tolerance | real, double precision for x, y, tolerance |
| .all. ([i,j] .lessThan. k) | test_diagnosis_t for .all. operator's operand |
| .all. ([i,j] .lessThan. [k,m]) | test_diagnosis_t for .all. operator's operand |
| .all. (i .lessThan. [k,m]) | test_diagnosis_t for .all. operator's operand |
| (i .lessThan. j) .also. (k .equalsExpected. m)) | test_diagnosis_t for .also. operator's operands |
| x .lessThan. y | integer, real, double precision for x, y |
| x .greaterThan. y | integer, real, double precision for x, y |
| i .equalsExpected. j | integer, character, type(c_ptr) for i, j |
| i .isAtLeast. j | integer, real, double precision for i, j |
| i .isAtMost. j | integer, real, double precision for i, j |
| s .isBefore. t | character for s, t |
| s .isAfter. t | character for s, t |
| .expect. allocated(A) // " (expected allocated A)" | logical for .expect. operator's operand |
where
Any of the above expressions can be the actual argument in an invocation of Julienne's call_julienne_assert function-line preprocessor macro:
call_julienne_assert(x .lessThan. y)which a preprocessor will replace with a call to Julienne's assertion subroutine when compiling with -DASSERTIONS. Otherwise, the preprocessor will remove the above line entirely.
The above tabulated expressions can also serve as results in unit-test functions.
All operands in an expression must be compatible in type and kind as well as conformable in rank. Conformability implies that the operands must be all scalars or all arrays with the same shape or a combination of scalars and arrays with the same shape. This constraint follows from each of the binary operators being elemental. The unary .all. operator applies to operands of any rank.
Each expression tabulated above produces a test_diagnosis_t object with two components:
For cases in which the defined operations do not support a desired correctness condition, Julienne provides string-handling utilities for use in crafting custom diagnostic messages. The string utilities center around a string_t derived type, which offers elemental constructor functions, i.e., functions that one invokes via the same name as the derived type: string_t(). The string_t() constructor functions convert data of numeric type to character type, storing the resulting character representation in a private component of the constructor function result. The actual argument provided to the constructor function can be of any one of several types, kinds, and ranks.
Julienne provides defined operations for concatenating string_t objects (//), forming a concatenated string_t object from an array of string_t objects (.cat.), forming a separated-value list (.separatedBy. or equivalently .sv.), including a comma-separated value list (.csv.).
| Example expression | Result |
|---|---|
| s%bracket(), where s=string_t("abc"), | string_t("[abc]") |
| s%bracket("_"), where s=string_t("abc") | string_t("_abc_") |
| s%bracket("{","}"), where s=string_t("abc") | string_t("{abc}") |
| string_t(["a", "b", "c"]) | [string_t("a"), string_t("b"), string_t("c")] |
| .cat. string_t([9,8,7]) | string_t("987") |
| .csv. string_t([1.5,2.0,3.25]) | string_t("1.50000000,2.00000000,3.25000000") |
| string_t([1,2,4]) .separatedBy. "-" | string_t("1-2-4") |
| string_t("ab") // string_t("cd") | string_t("abcd") |
| "ab" // string_t("cd") | string_t("abcd") |
| string_t("ab") // "cd" | string_t("abcd") |
One can use such expressions to craft a diagnostic message:
type(test_diagnosis_t) test_diagnosis
test_diagnosis = test_diagnosis_t( &
test_passed = i==j, &
diagnostics_string = "expected " // string_t(i) // "; actual " //string_t(j) &
)Arrays of string_t objects provide a convenient way to store a ragged-length array of character data. Julienne's file_t derived type has a private component that is a string_t array, wherein each element is one line of a text file. By storing a file in a file_t object using the file_t derived type's constructor function one can confine a program's file input/output (I/O) to one or two procedures. The resulting file_t object can be manipulated elsewhere without incurring the costs associated with file I/O. For example, the following lines read a file named data.txt into a file_t object and associates the name file with the resulting object.
associate(file => file_t("data.txt"))
end associateThis style supports functional programming patterns in two ways. First, the rest of the program can be comprised of pure procedures, which are precluded from performing I/O. Second, an associate name is immutable when associated with an expression, including an expression that is simply a function reference. Functional programming revolves around creating and using immutable state. (By contrast, when associating a name with a variable or array instead of with an expression, only certain attributes, such as the entity's allocation status, are immutable. The value of such a variable or array can be redefined.)
Functional programming patterns centered around pure procedures enhance code clarity, ease refactoring, and encourage optimization. For example, the constraints on pure procedures make it easier for a developer or a compiler to safely reorder program statements. Moreover, Fortran allows invoking only pure procedures inside do concurrent, a construct that compilers can automatically offload to a graphics processing unit (GPU).
Julienne lowers a widely stated barrier to writing pure procedures (including simple procedures): the difficulty in printing values while debugging code. The Julienne philosophy is that printing a value for debugging purposes implies an expectation about the value. Assert such expectations by writing Julienne expressions inspired by natural language. A program will proceed quietly past a correct assertion. An incorrect assertion produces either automated or custom diagnostic messages during error termination.
Please see demo/README.md for a detailed demonstration of test setup.
To write a Julienne assertion, insert a function-like preprocessor macro call_julienne_assert on a single line as in each of the two macro invocations below:
#include "julienne-assertion-macros.h"
program main
use julienne_m, only : call_julienne_assert_ ! also required
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 programwhere inserting -DASSERTIONS in a compile command will expand the macros to
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. 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. 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__ and __LINE__ macros, respectively. Most compilers write the resulting stop code to error_unit.
With the Fortran Package Manager (fpm) installed and in your PATH, the commands in the table below will build and run the Julienne test suite. With fpm versions older than 0.13.0, replace flang with flang-new below.
| Compiler/Runtime | Tested Versions | Run Type | Example build/test commands (parallel examples use 2 images) |
|---|---|---|---|
| LLVM/Caffeine | 22 | parallel | fpm test --compiler flang --flag "-O3 -DHAVE_MULTI_IMAGE_SUPPORT -fcoarray" --link-flag "-lcaffeine -lgasnet-smp-seq -L<caffeine-prefix>/lib" |
| LLVM | 20-22 | serial | fpm test --compiler flang --flag -O3 |
| LLVM | 19 (+) | serial | fpm test --compiler flang --flag "-O3 -mmlir -allow-assumed-rank" |
| GCC/OpenCoarrays | 14-15 | parallel | fpm test --compiler caf --runner "cafrun -n 2" --profile release |
| GCC/OpenCoarrays | 13 | parallel | fpm test --compiler caf --runner "cafrun -n 2" --profile release --flag -ffree-line-length-none |
| GCC | 14-16 | serial | fpm test --compiler gfortran --profile release |
| GCC | 13 | serial | fpm test --compiler gfortran --profile release --flag -ffree-line-length-none |
| Intel | 2025.2-2026.0 | parallel | FOR_COARRAY_NUM_IMAGES=2 fpm test --compiler ifx --flag "-fpp -O3 -coarray" --profile release |
| LFortran | 0.61-0.63 | serial | fpm test --compiler lfortran --flag "--cpp --realloc-lhs-arrays --separate-compilation" |
| NAG | 7.2 (-) | parallel | NAGFORTRAN_NUM_IMAGES=2 fpm test --compiler nagfor --flag "-O4 -fpp -coarray" |
(+) Support for LLVM flang version 19 is deprecated, and may be removed in a future release. (-) Using NAG on macOS requires an fpm installation containing PR 1312
The test output reports a test as skipped if there is a known issue that blocks the tested feature with the chosen compiler version or platform. Due to a GitHub continuous-integration (CI) issue, the default behavior is to skip the tests for Julienne's command-line parsing utility: command_line_t. To test command_line_t, add -- --flag --test command_line_t --type at the end of an fpm command.
To define the following macros or to override the values defined in Julienne's include directory, add --flag -D<macro-name>=<value> to an fpm command:
Julienne provides the pure stop_and_print subroutine to facilite the automatic or user-defined formatting and printing of various types and ranks of data as stop codes inside pure procedures. The stop_and_print subroutine accepts an unlimited-polymorphic, assumed-rank dummy dummy argument and optional character header and footer arguments for printing above and below the contents of data, respectively.
Julienne automatically formats and prints numeric scalars or arrays up to rank 3. Alternatively, users can customize the output format by passing a data actual argument constructed from one of the following Julienne types:
Julienne's name derives from the term for vegetables sliced into thin strings: julienned vegetables. The Veggies and Garden unit-testing frameworks inspired the structure of Julienne's tests and output. Initially developed in the Sourcery repository as lightweight alternative with greater portability across compilers, Julienne's chief innovation now lies in the expressive idioms the framework supports.
See our online documentation or build the documentation locally by installing FORD and executing ford ford.md.
Citing Julienne? Please use the following publication:
Damian Rouson, Dan Bonachea, and Katherine Rasmussen, "Idiomatic Correctness-Checking via Julienne in Fortran 2023",
Proceedings of the US Research Software Engineering Conference, October 2025. DOI: 10.25344/S4BG65
| Back | FazBrowse Home | New Git URL |