Skip to content

Commit 9fd8749

Browse files
authored
Merge pull request #63 from BerkeleyLab/revise-example-test-suite
Revise example test suite
2 parents 67a9289 + 6539c4a commit 9fd8749

11 files changed

Lines changed: 261 additions & 269 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Julienne aims to be a more lightweight alternative that is more portable across
4242

4343
Getting Started
4444
---------------
45-
Please see the [example-test-suite README.md](./example/example-test-suite/README.md).
45+
Please see the demonstration test suite in [demo README.md](./demo/README.md).
4646

4747
Compiler Support
4848
----------------
Lines changed: 73 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,56 @@
1-
Example Test Suite Classes
2-
==========================
3-
41
Getting Started
5-
---------------
6-
Likely the fastest way to get started with Julienne is to copy the source code in this directory and modify it for your purposes:
7-
8-
1. If you build your project with the Fortran Package Manager ([`fpm`](https://github.com/fotran-lang/fpm)), then you might move the `main.F90` and `specimen_test_m.F90` files from this subdirectory to a `test/` subdirectory in the root of your project's source tree.
2+
===============
3+
To get started with Julienne, review and test the demonstration project in this directory.
4+
Then copy the `main.F90` and `specimen_test_m.F90` files to your project's test directory.
5+
Finally, modify the files as described below to adapt them to your project.
6+
7+
Testing the Demonstration Project
8+
--------------------------------
9+
This demonstration project defines a trivial library named "specimen" in the `src`
10+
subdirectory and a test suite the `test` subdirectory. The test suite includes five tests:
11+
12+
1. Two pass.
13+
2. One intentionally fails to demonstrate diagnostic output.
14+
3. One test is skipped to the reporting and tallying of skipped tests.
15+
4. One test passes with three compilers but is skipped with GCC due to a compiler bug.
16+
17+
Test Julienne by setting your present working directory to the `demo/` subdirectory in a
18+
terminal window and then building and running the demonstration project's test suite using
19+
the command corresponding to your compiler in the table below.
20+
21+
|Vendor | Version/Build | Example shell command |
22+
|---------|-------------------------|------------------------------------------------------|
23+
|LLVM | 20.1.4 (Homebrew) | `fpm test --compiler flang-new` |
24+
|GCC | 14.2.0_1 (Homebrew) | `fpm test --compiler gfortran --profile release` |
25+
|NAG | 7.2 Build 7227 | `fpm test --compiler nagfor --flag -fpp` |
26+
|Intel | 2025.1.0 Build 20250317 | `fpm test --compiler ifx --flag "-fpp -O3 -coarray"` |
27+
28+
Setting Up Your Project's Test Suite
29+
------------------------------------
30+
31+
1. If you build your project with the Fortran Package Manager ([`fpm`](https://github.com/fotran-lang/fpm)), then you might copy the `main.F90` and `specimen_test_m.F90` files from this subdirectory to a `test/` subdirectory in the root of your project's source tree.
932
2. Rename the `specimen_test_m.F90` file, the `specimen_test_m` module, and the `specimen_test_t` derived type and any references thereto, replacing `specimen` with the name of an entity that you intend to test -- most likely a module containing procedures or derived type with type-bound procedures.
1033
3. Similarly replace occurrences of `specimen` in the resulting`test/main.F90` file.
11-
4. Modify the `test_descriptions_t` array constructor in your new `*_test_m.F90` file, adding elements for each test to be performed:
12-
```fortran
13-
test_descriptions = [ &
14-
test_description_t("the type-bound function zero() producing a result of 0", check_zero) &
15-
]
16-
```
17-
5. Replace the above string (`"the type-bound..."`) with a description of your intended test. The test output will read most naturally if your description contains a gerund: a verb ending in "ing" and used as a noun, such as `producing` above.
18-
6. Replace the `check_zero` function name with the name of a function that will perform your test.
19-
7. Edit the correspondingly-renamed function to perform the test. The function must take no arguments and define a `test_diagnosis_t` result. An example result might be the following:
20-
```fortran
21-
test_diagnosis = test_diagnosis_t( &
22-
test_passed = actual_value == expected_value &
23-
,diagnostics_string = "expected value " // string_t(expected_value) //", actual value " // string_t(actual_value) &
24-
)
25-
```
26-
The above `test_diagnosis_t` constructor function invocation demonstrates the recommended pattern for writing tests with Julienne:
34+
4. In the `results()` function body of your new `*_test_m.F90` file, replace the `test_descriptions_t` array constructor elements with your own test descriptions. The test output will read most naturally if your description string (the first argument) contains a gerund: a verb ending in "ing" and used as a noun, such as `producing` above
35+
5. Replace the function name (the second argument) with the name of a function that will perform your test.
36+
7. Edit the correspondingly-renamed function to perform the test. The function must take no arguments and define a `test_diagnosis_t` result.
37+
38+
The functions in `specimen_test_m` demonstrate several common options for constructing a `test_diagnosis_t` as the diagnosis function result.
39+
The options include
2740

28-
* Define the `test_passed` keyword argument by writing an expression that will evaluate to `.true.` if and only if the test succeeds.
29-
* Define the `diagnostics_string` keyword argument from character literal values interspersed with`string_t` constructor, all strung together by instances of the string concatenation operator `//`.
41+
1. Writing an expression using Julienne's operators such as `.approximate.`, `.within`., and `.equalsExpected.`.
42+
2. Invoking the `test_diagnosis_t` constructor and using Julienne's `string_t` constructors to form a diagnostic string.
3043

3144
`String_t` is a generic interface to various specific functions, each of which takes an argument of a different data type, kind, and rank (TKR) and defines a `string_t` result containing a charater representation of the function argument.
32-
Please see Julienne's online [documentation](https:///berkeleylab.github.io/julienne/) for the currently supported TKR.
45+
Please see Julienne's online [documentation] for the currently supported TKR.
3346
Please submit an issue to request support for additional TKR or submit a pull request to contribute such support.
3447

3548
#### Forming diagnostic strings from array data
3649

37-
An especially useful pattern for forming diagnostic string involves invoking Julienne's `operator(.csv.)` to produce a string of comma-separated values (CSV) from a one-dimensional (1D) array.
50+
An especially useful pattern for forming diagnostic strings involves invoking Julienne's `operator(.csv.)` to produce a string of comma-separated values (CSV) from a one-dimensional (1D) array.
3851
For example, consider the following test description:
3952
```fortran
40-
test_description_t(" returning the counting numbers up to 3", check_counting_numbers)
53+
test_description_t("returning the counting numbers up to 3", check_counting_numbers)
4154
```
4255
and the following corresponding test:
4356
```fortran
@@ -59,13 +72,8 @@ FAILS on returning the counting numbers up to 3
5972
```
6073
To support a common array notation, Julienne also supports bracketing strings.
6174

62-
**Exercise 1:** Make `check_counting_numbers` more robust by testing the equivalence of `expected_array` and `actual_array` only if the array sizes match and by treating a size-mismatch as a test failure.
63-
64-
**Exercise 2:** Revise `check_counting_numbers` by defining CSV strings `expected_string` and `actual_string` _before_ invoking `test_diagnostics_t`.
65-
Bracket the CSV strings in the `diagnostics_string` keyword argument by invoking `bracket` type-bound procedure, e.g., `expected_string%bracket()`.
66-
67-
Scalar Diagnosis Function
68-
-------------------------
75+
Diagnosis Functions
76+
-------------------
6977
The Unified Modeling Language ([UML](https://wikipedia.org/Unified_modeling_langauge)) class diagram below depicts the class relationships involved in making the above example work:
7078

7179
```mermaid
@@ -104,33 +112,10 @@ class test_diagnosis_t{
104112
}
105113
```
106114

107-
Vector Diagnosis Function
108-
-------------------------
109-
The UML class diagram below depicts the class relationships involved when test function performs multiple checks and defines a result containing an array of corresponding `test_diagnosis_t` objects:
110-
```mermaid
111-
%%{init: { 'theme':'default', "class" : {"hideEmptyMembersBox": true} } }%%
112-
classDiagram
113-
114-
class vector_test_description_t{
115-
vector_test_description_t(description : string_t[1..*], vector_diagnosis_function : vector_diagnosis_function_i)
116-
run() test_result_t[1..*]
117-
}
118-
vector_test_description_t --> test_diagnosis_t : run() invokes vector_diagnosis_function to construct array of
119-
vector_test_description_t --> test_result_t : run() uses test_diagnostics_t array to construct array of
120-
121-
class test_result_t{
122-
test_result_t(test_passed : logical, diagnosis : test_diagnosis_t)
123-
}
124-
125-
class test_diagnosis_t{
126-
test_diagnosis_t(test_passed : logical, diagnostics_string : string_t)
127-
}
128-
```
129-
130115
Skipping Tests
131116
--------------
132117
When a test is known to cause a compile-time or runtime crash in a specific scenario, e.g., with a specific compiler or compiler version, including that test will prevent the test suite from building or running to completion.
133-
It can be useful to skip a test with the problematic compiler but to report the test as skipped and account for the skipped tests in the tally of test results..
118+
It can be useful to skip a test with the problematic compiler but to report the test as skipped and account for the skipped tests in the tally of test results.
134119
For this purpose, the `test_description_t` and `vector_test_description_t` constructor functions have optional second arguments `diagnosis_function` and `vector_diagnosis_function`, respectively.
135120
When these arguments are not `present`, the `test_t`'s `report` procedure will report the test as skipped but will terminate normally as long as the sum of the passing tests and skipped tests equals the total number of tests.
136121
One might accomplish this with the compiler's predefined preprocessor macro:
@@ -175,3 +160,32 @@ class string_t{
175160
base_name(string_t) string_t
176161
}
177162
```
163+
164+
Deprecated: Vector Diagnosis Function
165+
-------------------------------------
166+
Julienne's `vector_diagnosis_function_i` abstract interface and the corresponding `vector_test_description_t` type were developed before Julienne's `operator(.all.)` and `operator(.and.)`.
167+
Because the operators replace the interface and type with simpler functionality, it is likely that a future release will remove the `vector_*` entities.
168+
169+
The Unified Modeling Language ([UML]) class diagram below depicts the class relationships involved when test function performs multiple checks and defines a result containing an array of corresponding `test_diagnosis_t` objects:
170+
```mermaid
171+
%%{init: { 'theme':'default', "class" : {"hideEmptyMembersBox": true} } }%%
172+
classDiagram
173+
174+
class vector_test_description_t{
175+
vector_test_description_t(description : string_t[1..*], vector_diagnosis_function : vector_diagnosis_function_i)
176+
run() test_result_t[1..*]
177+
}
178+
vector_test_description_t --> test_diagnosis_t : run() invokes vector_diagnosis_function to construct array of
179+
vector_test_description_t --> test_result_t : run() uses test_diagnostics_t array to construct array of
180+
181+
class test_result_t{
182+
test_result_t(test_passed : logical, diagnosis : test_diagnosis_t)
183+
}
184+
185+
class test_diagnosis_t{
186+
test_diagnosis_t(test_passed : logical, diagnostics_string : string_t)
187+
}
188+
```
189+
190+
[documentation]: https://berkeleylab.github.io/julienne
191+
[UML]: https://wikipedia.org/Unified_modeling_language

demo/fpm.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name = "Example-Test-Suite"
2+
3+
[dependencies]
4+
julienne = {git = "https://github.com/berkeleylab/julienne", tag = "2.1.0-rc5"}

demo/include/language-support.F90

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
! Copyright (c) 2024-2025, The Regents of the University of California
2+
! Terms of use are as specified in LICENSE.txt
3+
4+
#ifndef _JULIENNE_LANGUAGE_SUPPORT_H
5+
#define _JULIENNE_LANGUAGE_SUPPORT_H
6+
7+
! If not already determined, make a compiler-dependent determination of whether Julienne may pass
8+
! procedure actual arguments to procedure pointer dummy arguments, a feature introduced in
9+
! Fortran 2008 and described in Fortran 2023 clause 15.5.2.10 paragraph 5.
10+
#ifndef HAVE_PROCEDURE_ACTUAL_FOR_POINTER_DUMMY
11+
# if defined(__GFORTRAN__)
12+
# define HAVE_PROCEDURE_ACTUAL_FOR_POINTER_DUMMY 0
13+
# else
14+
# define HAVE_PROCEDURE_ACTUAL_FOR_POINTER_DUMMY 1
15+
# endif
16+
#endif
17+
18+
! If not already determined, make a compiler-dependent determination of whether Julienne may use
19+
! multi-image features such as `this_image()` and `sync all`.
20+
#ifndef HAVE_MULTI_IMAGE_SUPPORT
21+
# if defined(__flang__)
22+
# define HAVE_MULTI_IMAGE_SUPPORT 0
23+
# else
24+
# define HAVE_MULTI_IMAGE_SUPPORT 1
25+
# endif
26+
#endif
27+
28+
! If not already determined, make a compiler-dependent determination of whether Julienne may use
29+
! kind type parameters for derived types.
30+
#ifndef HAVE_DERIVED_TYPE_KIND_PARAMETERS
31+
# if defined(__GFORTRAN__)
32+
# define HAVE_DERIVED_TYPE_KIND_PARAMETERS 0
33+
# else
34+
# define HAVE_DERIVED_TYPE_KIND_PARAMETERS 1
35+
# endif
36+
#endif
37+
38+
#endif
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@ module specimen_m
77
type specimen_t
88
contains
99
procedure, nopass :: zero
10+
procedure, nopass :: one
1011
end type
1112

1213
contains
1314

14-
pure function zero() result(incorrect_value)
15-
integer incorrect_value
16-
incorrect_value = 1
15+
pure function zero() result(correct_value)
16+
real correct_value
17+
correct_value = 0
18+
end function
19+
20+
pure function one() result(correct_value)
21+
integer correct_value
22+
correct_value = 1
1723
end function
1824

1925
end module

demo/test/specimen_test_m.F90

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
! Copyright (c) 2024-2025, The Regents of the University of California and Sourcery Institute
2+
! Terms of use are as specified in LICENSE.txt
3+
4+
#include "language-support.F90"
5+
6+
module specimen_test_m
7+
!! Example unit test for the specimen_t test subject
8+
use specimen_m, only : specimen_t
9+
use julienne_m, only : &
10+
string_t &
11+
,test_t &
12+
,test_result_t &
13+
,test_description_t &
14+
,test_description_substring &
15+
,test_diagnosis_t &
16+
,operator(.approximates.) &
17+
,operator(.within.) &
18+
,operator(.all.) &
19+
,operator(.equalsExpected.) &
20+
,operator(.greaterThan.) &
21+
,operator(.lessThan.)
22+
#if defined(__GFORTRAN__)
23+
use julienne_m, only : diagnosis_function_i ! work around gfortran's missing Fortran 2008 feature
24+
#endif
25+
26+
implicit none
27+
28+
private
29+
public :: specimen_test_t
30+
31+
type, extends(test_t) :: specimen_test_t
32+
contains
33+
procedure, nopass :: subject
34+
procedure, nopass :: results
35+
end type
36+
37+
contains
38+
39+
pure function subject() result(specimen_description)
40+
character(len=:), allocatable :: specimen_description
41+
specimen_description = "A specimen_t object"
42+
end function
43+
44+
#if ! defined(__GFORTRAN__)
45+
46+
function results() result(test_results)
47+
type(test_result_t), allocatable :: test_results(:)
48+
type(test_description_t), allocatable :: test_descriptions(:)
49+
50+
test_descriptions = [ &
51+
test_description_t("diagnosing the zero function using Julienne operators", check_zero_using_operators) &
52+
,test_description_t("diagnosing the zero function using a diagnosis constructor", check_zero_using_constructor) &
53+
,test_description_t("aggregating diagnoses of the zero and one functions using operator(.all.)", check_aggregate_diagnosis) &
54+
,test_description_t("(intentional failure to demonstrate diagnostic output)", check_print_diagnosis) &
55+
,test_description_t("skipping a test when no diagnosis function is specified") &
56+
]
57+
test_descriptions = pack( &
58+
array = test_descriptions &
59+
,mask = test_descriptions%contains_text(test_description_substring) .or. index(subject(), test_description_substring)/=0 &
60+
)
61+
test_results = test_descriptions%run()
62+
end function
63+
64+
#else
65+
66+
function results() result(test_results)
67+
!! work around missing Fortran 2008 feature in gfortran versions earlier than 15
68+
type(test_result_t), allocatable :: test_results(:)
69+
type(test_description_t), allocatable :: test_descriptions(:)
70+
procedure(diagnosis_function_i), pointer :: check_operators_ptr => check_zero_using_operators
71+
procedure(diagnosis_function_i), pointer :: check_constructor_ptr => check_zero_using_constructor
72+
procedure(diagnosis_function_i), pointer :: check_aggregate_ptr => check_aggregate_diagnosis
73+
procedure(diagnosis_function_i), pointer :: check_print_diagnosis_ptr => check_print_diagnosis
74+
75+
! Omitting the optional 2nd argument in the 3rd test_description_t constructor below skips the described
76+
! test. When the test suite runs, it reports the test as skipped and reports a tally of skippped tests.
77+
78+
test_descriptions = [ &
79+
test_description_t("diagnosing the zero function using Julienne operators", check_operators_ptr) &
80+
,test_description_t("diagnosing the zero function using a diagnosis constructor", check_constructor_ptr) &
81+
,test_description_t("aggregating diagnoses of the zero and one functions using operator(.all.)") &
82+
,test_description_t("(intentional failure to demonstrate diagnostic output)", check_print_diagnosis_ptr) &
83+
,test_description_t("skipping a test when no diagnosis function is specified") &
84+
]
85+
test_descriptions = pack( &
86+
array = test_descriptions &
87+
,mask = test_descriptions%contains_text(test_description_substring) .or. index(subject(), test_description_substring)/=0 &
88+
)
89+
test_results = test_descriptions%run()
90+
end function
91+
#endif
92+
93+
function check_zero_using_operators() result(test_diagnosis)
94+
!! Construct a test diagnosis using Julienne's operator(.approximates.) and operator(.within.)
95+
type(test_diagnosis_t) test_diagnosis
96+
type(specimen_t) specimen
97+
real, parameter :: expected_value = 0., tolerance = 1E-02
98+
test_diagnosis = specimen%zero() .approximates. expected_value .within. tolerance
99+
end function
100+
101+
function check_zero_using_constructor() result(test_diagnosis)
102+
!! Construct a test diagnosis using Julienne's string_t constructor to define a custom diagnostic string
103+
type(test_diagnosis_t) test_diagnosis
104+
type(specimen_t) specimen
105+
real, parameter :: expected_value = 0., tolerance = 1E-02
106+
associate(actual_value => specimen%zero())
107+
test_diagnosis = test_diagnosis_t( &
108+
test_passed = abs(actual_value - expected_value) < tolerance &
109+
,diagnostics_string = "expected " // string_t(expected_value) // "; actual " // string_t(actual_value) &
110+
)
111+
end associate
112+
end function
113+
114+
function check_aggregate_diagnosis() result(test_diagnosis)
115+
!! Aggregate two test diagnoses using Julienne's operator(.all.)
116+
type(test_diagnosis_t) test_diagnosis
117+
type(specimen_t) specimen
118+
real, parameter :: expected_ceiling = 10.
119+
integer, parameter :: expected_integer = 1
120+
associate(actual_real => specimen%zero(), actual_integer => specimen%one())
121+
test_diagnosis = .all. [actual_integer .equalsExpected. expected_integer, actual_real .lessThan. expected_ceiling]
122+
end associate
123+
end function
124+
125+
function check_print_diagnosis() result(test_diagnosis)
126+
type(test_diagnosis_t) test_diagnosis
127+
test_diagnosis = 2 .lessThan. 1 ! intentional test failure
128+
end function
129+
130+
end module

example/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Examples
2+
========
3+
This directory focuses on examples that demonstrate Julienne's string-handling capabilities.
4+
For a demonstration of how to use Julienne for unit testing, please see the [`demo`](../demo)
5+
subdirectory of this repository.

0 commit comments

Comments
 (0)