Skip to content

Commit c5da31f

Browse files
authored
Merge pull request #92 from BerkeleyLab/auto-generate-driver
Automatically generate demo test suite from JSON file
2 parents 10a8a80 + 56320fc commit c5da31f

26 files changed

+712
-268
lines changed

README.md

Lines changed: 17 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,21 @@ Example expressions | Operand types
3131
`s .isAfter. t` | `character`
3232
`.expect. command_line%argument_present("--help")`| `logical`
3333

34+
where
35+
* `.isAtLeast.` and `.isAtMost.` can alternatively be spelled `.greaterThanOrEqualTo.` and `.lessThanOrEqualTo.`, respectively;
36+
* `.equalsExpected.` uses `==`, which implies that trailing blank spaces are ignored in character operands;
37+
* `.isBefore.` and `.isAfter.` verify alphabetical and reverse-alphabetical order, respectively;
38+
* `.all.` aggregates arrays of expression results, reports a consensus result, passes, and shows diagnostics only for failing tests, if any; and
39+
* `.equalsExpected.` generates asymmetric diagnostic output for failures, denoting the left- and right-hand sides as the actual value and expected values, respectively.
3440

35-
where `.isAtLeast.` and `.isAtMost.` can alternatively be spelled
36-
`.greaterThanOrEqualTo.` and `.lessThanOrEqualTo.`, respectively;
37-
`.equalsExpected.` uses `==`, which implies that trailing blank spaces
38-
are ignored in character operands; and `.isBefore.` and `.isAfter.`
39-
verify alphabetical and reverse-alphabetical order, respectively.
40-
The new `.all.` operator is synonymous with the `.and.` defined operation
41-
but avoids confusion with the intrinsic `.and.` operator.
4241

4342
Expressive idioms
4443
-----------------
4544
### Assertions
46-
Any of the above tabulated expressions can be the actual argument in an
47-
invocation of Julienne's `call_assert` function-line preprocessor macro:
48-
```
49-
call_assert(x .lessThan. y)
45+
Any of the above expressions can be the actual argument in an invocation
46+
of Julienne's `call_julienne_assert` function-line preprocessor macro:
47+
```fortran
48+
call_julienne_assert(x .lessThan. y)
5049
```
5150
which a preprocessor will replace with a call to Julienne's assertion subroutine
5251
when compiling with `-DASSERTIONS`. Otherwise, the preprocessor will remove the
@@ -103,7 +102,7 @@ Example expression | Result
103102

104103
One can use such expressions to craft a diagnostic message when constructing
105104
a custom test function result:
106-
```
105+
```fortran
107106
type(test_diagnosis_t) test_diagnosis
108107
test_diagnosis = test_diagnosis_t( &
109108
test_passed = i==j, &
@@ -122,7 +121,7 @@ or two procedures. The resulting `file_t` object can be manipulated elsewhere
122121
without incurring the costs associated with file I/O. For example, the following
123122
line reads a file named `data.txt` into a `file_t` object and associates the name
124123
`file` with the resulting object.
125-
```
124+
```fortran
126125
type(file_t) file
127126
associate(file => file_t("data.txt"))
128127
end associate
@@ -155,6 +154,10 @@ diagnostic messages during error termination.
155154

156155
Getting Started
157156
---------------
157+
### Writing Unit Tests
158+
Please see [demo/README.md](./demo/README.md) for a detailed demonstration of
159+
test setup.
160+
158161
### Writing Assertions
159162
To write a Julienne assertion, insert a function-like preprocessor macro
160163
`call_julienne_assert` on a single line as in each of the two macro
@@ -185,46 +188,6 @@ also inserts the file and line number into the stop code using via the `__FILE__
185188
and `__LINE__` macros, respectively. Most compilers write the resulting stop code
186189
to `error_unit`.
187190

188-
### Writing Unit Tests
189-
Writing tests using Julienne involves constructing a test-description array,
190-
in which each element is a `test_description_t` constructor function invocation
191-
with two arguments: a `character` string describing what the test does and the
192-
name of a function that performs the test. An example follows:
193-
```fortran
194-
type(test_description_t), allocatable ::test_descriptions(:)
195-
test_descriptions = [ &
196-
test_description_t("checking something", check_something) &
197-
,test_description_t("checking something else", check_something_else) &
198-
]
199-
```
200-
Execute the tests by calling the `test_description_t` type-bound `run` procedure
201-
on the `test_descriptions` array.
202-
203-
Define each test function to produce a `test_diagnosis_t` object result
204-
encapsulating the two components enumerated in the [Assertions] section.
205-
Use one of Julienne's expression idioms to construct the function result
206-
automatically:
207-
```fortran
208-
function check_something() result(test_diagnosis)
209-
type(test_diagnosis_t) test_diagnosis
210-
integer, parameter :: i=1, j=1
211-
test_diagnosis = i .equalsExpected. j
212-
end function
213-
```
214-
where the `.equalsExpected.` operator is named to imply an asymmetry with
215-
respect to the arguments `i` and `j`. If the condition `i==j` evaluates to
216-
`.false.`, Julienne constructs a diagnostic message reflecting the implied
217-
asymmetry, i.e., indicating that `i` is the actual value, whereas `j` is the
218-
expected value.
219-
220-
### A demonstration test suite
221-
Please see the demonstration test suite in [demo README.md](./demo/README.md)
222-
for detailed instructions on setting up a new test suite. The demonstration
223-
test suite's main program also shows how to use Julienne's `command_line_t` type
224-
to access arguments that users pass to program via a command line or shell
225-
script. Julienne also offers useful input/output format strings and
226-
format-generating functions in the `julienne_formats_m` module.
227-
228191
An Origin Story
229192
---------------
230193
Julienne's name derives from the term for vegetables sliced into thin strings:
@@ -269,7 +232,7 @@ fpm test --compiler flang-new
269232
##### `flang-new` version 19
270233
Add the following command before the `fpm` command recommended above for
271234
LLVM 20 or later:
272-
```
235+
```bash
273236
export FPM_FFLAGS="-mmlir -allow-assumed-rank"
274237
```
275238
where this `FPM_FFLAGS` setting turns on the support for Fortran's assumed-rank

app/scaffold.F90

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
program scaffold
5+
use julienne_m, only : command_line_t, file_t, test_suite_t
6+
implicit none
7+
type(command_line_t) command_line
8+
integer i
9+
10+
if (help_requested()) call print_usage_info_and_stop
11+
12+
#ifndef __GNUC__
13+
associate(subjects_file_name => command_line%flag_value("--json-file"))
14+
if (len(subjects_file_name) == 0) call print_usage_info_and_stop
15+
print '(*(a))', "Reading test subject information from " // subjects_file_name
16+
associate(test_suite => test_suite_t(file_t(subjects_file_name)))
17+
associate(path => command_line%flag_value("--suite-path"))
18+
print '(*(a))', "Writing test-suite scaffolding in " // path
19+
if (len(path) == 0) call print_usage_info_and_stop
20+
associate(driver => test_suite%driver_file())
21+
call driver%write_lines(path // "/driver.f90")
22+
end associate
23+
associate(subjects => test_suite%test_subjects(), modules => test_suite%test_modules())
24+
do i = 1, size(subjects)
25+
associate(stub => test_suite%stub_file(subjects(i)))
26+
call stub%write_lines(path // "/" // modules(i) // ".f90")
27+
end associate
28+
end do
29+
end associate
30+
end associate
31+
end associate
32+
end associate
33+
#else
34+
block
35+
character(len=:), allocatable :: path, subjects_file_name
36+
type(file_t) driver
37+
subjects_file_name = command_line%flag_value("--json-file")
38+
if (len(subjects_file_name) == 0) call print_usage_info_and_stop
39+
print '(*(a))', "Reading test subject information from " // subjects_file_name
40+
associate(test_suite => test_suite_t(file_t(subjects_file_name)))
41+
path = command_line%flag_value("--suite-path")
42+
if (len(path) == 0) call print_usage_info_and_stop
43+
print '(*(a))', "Writing test-suite scaffolding in " // path
44+
call test_suite%write_driver(path // "/driver.f90")
45+
end associate
46+
end block
47+
#endif
48+
49+
contains
50+
51+
logical function help_requested()
52+
character(len=:), allocatable :: file_name
53+
type(command_line_t) command_line
54+
help_requested = command_line%argument_present([character(len=len("--help"))::"--help","-h"])
55+
end function
56+
57+
subroutine print_usage_info_and_stop
58+
character(len=*), parameter :: usage = &
59+
new_line('') // new_line('') // &
60+
'Usage: fpm run scaffold -- [--json-file <string> --suite-path <string>] | [--help] | -h]' // &
61+
new_line('') // new_line('') // &
62+
'where square brackets ([]) denote optional arguments, a pipe (|) separates alternative arguments,' // new_line('') // &
63+
'angular brackets (<>) denote a user-provided value, the --subjects string names a JSON file,' // new_line('') // &
64+
'and the --path string names a directory for the new test-suite scaffold.' // new_line('')
65+
stop usage
66+
end subroutine
67+
68+
end program scaffold

app/test-suite.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"test suite": {
3+
"modules" : ["assert_test_m","bin_test_m","command_line_test_m","formats_test_m","string_test_m","test_description_test_m","test_diagnosis_test_m","test_result_test_m"],
4+
"types" : ["assert_test_t","bin_test_t","command_line_test_t","formats_test_t","string_test_t","test_description_test_t","test_diagnosis_test_t","test_result_test_t"]
5+
}
6+
}

0 commit comments

Comments
 (0)