Skip to content

Commit 6ba9bbb

Browse files
authored
Merge branch 'main' into prep-release
2 parents 486a856 + cc83cbe commit 6ba9bbb

27 files changed

+893
-324
lines changed

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Sourcery Library
22
================
33

4-
A grab bag of useful tricks in Fortran 2018.
4+
A grab bag of useful tricks in Fortran 2023.
55

66
```fortran
77
-:/+-
@@ -52,7 +52,7 @@ Contents
5252

5353
### Classes
5454
* Parallel data partitioning and gathering,
55-
* A minimalistic unit testing framework comprised of two types: `test_t` and `test_result_t`
55+
* A minimalistic unit testing framework based around three types: `test_t`, `test_description_t`, and `test_result_t`
5656
* (Co-)[Object pattern] abstract parent,
5757
* Runtime units tracking,
5858
* A test oracle using the [Template Method pattern], and
@@ -82,6 +82,16 @@ git clone [email protected]:sourceryinstitute/sourcery
8282

8383
Building and Testing
8484
--------------------
85+
### Test-Suite Usage
86+
Executing `fpm test -- --help` prints the following message:
87+
```
88+
Usage: fpm test -- [--help] | [--contains <substring>]
89+
90+
where square brackets ([]) denote optional arguments, a pipe (|) separates alternative arguments,
91+
angular brackets (<>) denote a user-provided value, and passing a substring limits execution to
92+
the tests with test subjects or test descriptions containing the user-specified substring.
93+
```
94+
8595
### GNU Fortran (`gfortran`) compiler
8696
#### Single-image (serial) testing
8797
With recent versions of [GNU Fortran] (gfortran) and [OpenCoarrays] installed,
@@ -105,7 +115,7 @@ fpm test --compiler ifx --flag "-coarray"
105115

106116
### Numerical Algorithms Group (`nagfor`) compiler
107117
```zsh
108-
fpm test --compiler nagfor --flag "-fpp -coarray=cosmp"
118+
fpm test --compiler nagfor --flag -fpp
109119
```
110120

111121
### Building and testing with the Cray Compiler Environment (CCE)
@@ -153,4 +163,3 @@ documentation.
153163
[Building the documentation]: #building-the-documentation
154164
[Sourcery GitHub Pages site]: http://sourceryinstitute.github.io/sourcery/
155165
[`ford`]: https://github.com/Fortran-FOSS-Programmers/ford
156-
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
program check_command_line_argument
2+
!! This program serves the dual purposes of
3+
!! 1. Showing how to use the command_line_t derived type to check whether a
4+
!! command-line argument is present and
5+
!! 2. Supporting the test suite verification of this same behavior.
6+
!!
7+
!! Running this program as follows with the command
8+
!!
9+
!! fpm run --example check-command-line-argument -- --some-argument
10+
!!
11+
!! should result in normal termination.
12+
use assert_m, only : assert
13+
use sourcery_m, only : command_line_t
14+
implicit none
15+
16+
type(command_line_t) command_line
17+
logical argument_passed
18+
19+
argument_passed = command_line%argument_present(["--some-argument"])
20+
21+
call assert(argument_passed, "check_command_line_argument: argument present")
22+
end program

example/handle-missing-flag.f90

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
program handle_missing_flag
2+
!! This program serves the dual purposes of
3+
!!
4+
!! 1. Showing an example of a command-line with an expected flag missing an
5+
!! 2. Supporting the test suite check that the returned value has zero length.
6+
!!
7+
!! Running this program as follows with the command
8+
!!
9+
!! fpm run --example handle-missing-flag -- --empty-flag
10+
!!
11+
!! should result in normal termination.
12+
use assert_m, only : assert
13+
use sourcery_m, only : command_line_t
14+
implicit none
15+
16+
type(command_line_t) command_line
17+
character(len=:), allocatable :: flag_value
18+
character(len=*), parameter :: expected_name=""
19+
20+
flag_value = command_line%flag_value("--empty-flag")
21+
22+
call assert(flag_value==expected_name,"handle_missing_flag: expected empty flag value", flag_value)
23+
end program

src/sourcery/sourcery_command_line_s.f90

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,28 @@
3939
integer argnum, arglen, flag_value_length
4040
character(len=:), allocatable :: arg
4141

42-
43-
flag_search: &
44-
do argnum = 1,command_argument_count()
45-
46-
if (allocated(arg)) deallocate(arg)
47-
48-
call get_command_argument(argnum, length=arglen)
49-
allocate(character(len=arglen) :: arg)
50-
call get_command_argument(argnum, arg)
51-
52-
if (arg==flag) then
53-
call get_command_argument(argnum+1, length=flag_value_length)
54-
allocate(character(len=flag_value_length) :: flag_value)
55-
call get_command_argument(argnum+1, flag_value)
56-
exit flag_search
42+
associate(argcount => command_argument_count())
43+
if (argcount==0) then
44+
flag_value=""
45+
else
46+
flag_search: &
47+
do argnum = 1,argcount
48+
49+
if (allocated(arg)) deallocate(arg)
50+
51+
call get_command_argument(argnum, length=arglen)
52+
allocate(character(len=arglen) :: arg)
53+
call get_command_argument(argnum, arg)
54+
55+
if (arg==flag) then
56+
call get_command_argument(argnum+1, length=flag_value_length)
57+
allocate(character(len=flag_value_length) :: flag_value)
58+
call get_command_argument(argnum+1, flag_value)
59+
exit flag_search
60+
end if
61+
end do flag_search
5762
end if
58-
end do flag_search
63+
end associate
5964

6065
end procedure
6166

src/sourcery/sourcery_string_m.f90

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ module sourcery_string_m
2121
generic :: operator(/=) => string_t_ne_string_t, string_t_ne_character, character_ne_string_t
2222
generic :: operator(==) => string_t_eq_string_t, string_t_eq_character, character_eq_string_t
2323
generic :: assignment(= ) => assign_string_t_to_character, assign_character_to_string_t
24-
generic :: get_json_value => get_json_integer_array, get_json_logical, get_json_integer, get_json_string, get_json_real
25-
procedure, private :: get_json_integer_array, get_json_logical, get_json_integer, get_json_string, get_json_real
24+
generic :: get_json_value => get_json_integer_array, get_json_logical, get_json_integer, get_json_string, get_json_real, &
25+
get_json_real_array
26+
procedure, private :: get_json_integer_array, get_json_logical, get_json_integer, get_json_string, get_json_real, &
27+
get_json_real_array
2628
procedure, private :: string_t_ne_string_t, string_t_ne_character
2729
procedure, private :: string_t_eq_string_t, string_t_eq_character
2830
procedure, private :: assign_character_to_string_t
@@ -102,7 +104,7 @@ pure module function base_name(self) result(base)
102104
type(string_t) base
103105
end function
104106

105-
elemental module function get_json_real(self, key, mold) result(value_)
107+
pure module function get_json_real(self, key, mold) result(value_)
106108
implicit none
107109
class(string_t), intent(in) :: self, key
108110
real, intent(in) :: mold
@@ -136,6 +138,13 @@ pure module function get_json_integer_array(self, key, mold) result(value_)
136138
integer, allocatable :: value_(:)
137139
end function
138140

141+
pure module function get_json_real_array(self, key, mold) result(value_)
142+
implicit none
143+
class(string_t), intent(in) :: self, key
144+
real, intent(in) :: mold(:)
145+
real, allocatable :: value_(:)
146+
end function
147+
139148
elemental module function string_t_eq_string_t(lhs, rhs) result(lhs_eq_rhs)
140149
implicit none
141150
class(string_t), intent(in) :: lhs, rhs

src/sourcery/sourcery_string_s.f90

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,15 @@
182182
end procedure
183183

184184
module procedure get_json_integer_array
185+
value_ = int(self%get_json_real_array(key,mold=[0.]))
186+
end procedure
187+
188+
module procedure get_json_real_array
185189
character(len=:), allocatable :: raw_line
186190
real, allocatable :: real_array(:)
187191
integer i
188192

189-
call assert(key==self%get_json_key(), "string_s(get_json_integer_array): key==self%get_json_key()", key)
193+
call assert(key==self%get_json_key(), "string_s(get_json_{real,integer}_array): key==self%get_json_key()", key)
190194

191195
raw_line = self%string()
192196
associate(colon => index(raw_line, ":"))
@@ -196,7 +200,7 @@
196200
associate(num_inputs => commas + 1)
197201
allocate(real_array(num_inputs))
198202
read(raw_line(opening_bracket+1:closing_bracket-1), fmt=*) real_array
199-
value_ = int(real_array)
203+
value_ = real_array
200204
end associate
201205
end associate
202206
end associate
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
module sourcery_test_description_m
2+
!! Define an abstraction for describing test intentions and test functions
3+
use sourcery_string_m, only : string_t
4+
use sourcery_test_result_m, only : test_result_t
5+
implicit none
6+
7+
private
8+
public :: test_description_t
9+
#ifdef __GFORTRAN__
10+
public :: test_function_i
11+
#endif
12+
13+
abstract interface
14+
function test_function_i() result(passes)
15+
implicit none
16+
logical passes
17+
end function
18+
end interface
19+
20+
type test_description_t
21+
!! Encapsulate test descriptions and test-functions
22+
private
23+
type(string_t) description_
24+
procedure(test_function_i), pointer, nopass :: test_function_ => null()
25+
contains
26+
procedure run
27+
procedure contains_text
28+
generic :: operator(==) => equals
29+
procedure, private :: equals
30+
end type
31+
32+
interface test_description_t
33+
34+
module function construct_from_string_t(description, test_function) result(test_description)
35+
!! The result is a test_description_t object with the components defined by the dummy arguments
36+
implicit none
37+
type(string_t), intent(in) :: description
38+
procedure(test_function_i), intent(in), pointer :: test_function
39+
type(test_description_t) test_description
40+
end function
41+
42+
module function construct_from_character(description, test_function) result(test_description)
43+
!! The result is a test_description_t object with the components defined by the dummy arguments
44+
implicit none
45+
character(len=*), intent(in) :: description
46+
procedure(test_function_i), intent(in), pointer :: test_function
47+
type(test_description_t) test_description
48+
end function
49+
50+
end interface
51+
52+
interface
53+
54+
impure elemental module function run(self) result(test_result)
55+
!! The result encapsulates the test description and test outcome
56+
implicit none
57+
class(test_description_t), intent(in) :: self
58+
type(test_result_t) test_result
59+
end function
60+
61+
impure elemental module function contains_text(self, substring) result(match)
62+
!! The result is .true. if the test description includes the value of substring
63+
implicit none
64+
class(test_description_t), intent(in) :: self
65+
type(string_t), intent(in) :: substring
66+
logical match
67+
end function
68+
69+
elemental module function equals(lhs, rhs) result(lhs_eq_rhs)
70+
!! The result is .true. if the components of the lhs & rhs are equal
71+
implicit none
72+
class(test_description_t), intent(in) :: lhs, rhs
73+
logical lhs_eq_rhs
74+
end function
75+
76+
end interface
77+
78+
end module sourcery_test_description_m
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
submodule(sourcery_test_description_m) sourcery_test_description_s
2+
implicit none
3+
contains
4+
module procedure construct_from_character
5+
test_description%description_ = description
6+
test_description%test_function_ => test_function
7+
end procedure
8+
9+
module procedure construct_from_string_t
10+
test_description%description_ = description
11+
test_description%test_function_ => test_function
12+
end procedure
13+
14+
module procedure run
15+
test_result = test_result_t(self%description_, self%test_function_())
16+
end procedure
17+
18+
module procedure contains_text
19+
match = index(self%description_%string(), substring%string()) /= 0
20+
end procedure
21+
22+
module procedure equals
23+
lhs_eq_rhs = (lhs%description_ == rhs%description_) .and. associated(lhs%test_function_, rhs%test_function_)
24+
end procedure
25+
end submodule sourcery_test_description_s

src/sourcery/sourcery_test_m.f90

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ module sourcery_test_m
66
implicit none
77

88
private
9-
public :: test_t, test_result_t
9+
public :: test_t, test_description_substring
10+
11+
character(len=:), allocatable, protected :: test_description_substring
1012

1113
type, abstract :: test_t
1214
!! Facilitate testing and test reporting

src/sourcery/sourcery_test_result_m.f90

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
module sourcery_test_result_m
22
!! Define an abstraction for describing test intentions and results
3+
use sourcery_string_m, only : string_t
34
implicit none
45

56
private
67
public :: test_result_t
78

89
type test_result_t
9-
!! Encapsulate test descriptions and outcomes and reporting
10+
!! Encapsulate test descriptions and outcomes
1011
private
11-
character(len=:), allocatable :: description_
12+
type(string_t) description_
1213
logical passed_
1314
contains
1415
procedure :: characterize
@@ -17,14 +18,22 @@ module sourcery_test_result_m
1718

1819
interface test_result_t
1920

20-
elemental module function construct(description, passed) result(test_result)
21+
elemental module function construct_from_character(description, passed) result(test_result)
2122
!! The result is a test_result_t object with the components defined by the dummy arguments
2223
implicit none
2324
character(len=*), intent(in) :: description
2425
logical, intent(in) :: passed
2526
type(test_result_t) test_result
2627
end function
2728

29+
module function construct_from_string(description, passed) result(test_result)
30+
!! The result is a test_result_t object with the components defined by the dummy arguments
31+
implicit none
32+
type(string_t), intent(in) :: description
33+
logical, intent(in) :: passed
34+
type(test_result_t) test_result
35+
end function
36+
2837
end interface
2938

3039
interface

0 commit comments

Comments
 (0)