Skip to content

Commit 515e549

Browse files
authored
Add additional unit tests (#226)
1 parent 06cda62 commit 515e549

13 files changed

Lines changed: 1199 additions & 543 deletions

.github/linters/.ruff.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
cache-dir = "/tmp/.ruff_cache"
22
line-length = 88
33
output-format = "grouped"
4+
exclude = ["*.F90"]
45

56
[lint]
67
# Allow unused variables when underscore-prefixed.

script_umdp3_checker/checker_dispatch_tables.py

Lines changed: 60 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,16 @@
99
Python translation of the original Perl module
1010
"""
1111

12-
from typing import Dict, Callable
12+
from typing import Callable
1313
from umdp3_checker_rules import UMDP3Checker
1414

1515
"""
16-
TODO : This list was checked to ensure it had something for each
17-
test in the original.
18-
TODO : This needs to be re-checked.
19-
TODO : And the tests need to be compared to the original Perl tests
20-
to ensure they are equivalent.
21-
"""
16+
TODO : This module has lost it's way. It uses a class to define methods which just
17+
return lists of functions. I don't think a class is required for this.
18+
As the functions themselves are shifted, renamed and hopefully improved, this
19+
class should eventually get emptied out and the file removed.
2220
23-
# Declare version
24-
VERSION = "13.5.0"
21+
"""
2522

2623

2724
class CheckerDispatchTables:
@@ -30,86 +27,66 @@ class CheckerDispatchTables:
3027
def __init__(self):
3128
self.umdp3_checker = UMDP3Checker()
3229

33-
def get_diff_dispatch_table_fortran(self) -> Dict[str, Callable]:
30+
def get_diff_dispatch_table_fortran(self) -> list[Callable]:
3431
"""Get dispatch table for Fortran diff tests"""
35-
return {
36-
# 'Captain Daves doomed test of destruction':
37-
# self.umdp3_checker.capitulated_keywords,
38-
"Lowercase Fortran keywords not permitted": self.umdp3_checker.capitalised_keywords,
39-
"OpenMP sentinels not in column one": self.umdp3_checker.openmp_sentinels_in_column_one,
40-
"Omitted optional space in keywords": self.umdp3_checker.unseparated_keywords,
41-
"GO TO other than 9999": self.umdp3_checker.go_to_other_than_9999,
42-
"WRITE without format": self.umdp3_checker.write_using_default_format,
43-
"Lowercase or CamelCase variable names only": self.umdp3_checker.lowercase_variable_names,
44-
"Use of dimension attribute": self.umdp3_checker.dimension_forbidden,
45-
"Continuation lines shouldn't start with &": self.umdp3_checker.ampersand_continuation,
46-
"Use of EQUIVALENCE or PAUSE": self.umdp3_checker.forbidden_keywords,
47-
"Use of older form of relational operator (.GT. etc.)": self.umdp3_checker.forbidden_operators,
48-
"Line longer than 80 characters": self.umdp3_checker.line_over_80chars,
49-
"Line includes tab character": self.umdp3_checker.tab_detection,
50-
"USEd printstatus_mod instead of umPrintMgr": self.umdp3_checker.printstatus_mod,
51-
"Used PRINT rather than umMessage and umPrint": self.umdp3_checker.printstar,
52-
"Used WRITE(6) rather than umMessage and umPrint": self.umdp3_checker.write6,
53-
"Used um_fort_flush rather than umPrintFlush": self.umdp3_checker.um_fort_flush,
54-
"Used Subversion keyword substitution which is prohibited": self.umdp3_checker.svn_keyword_subst,
55-
"Used !OMP instead of !$OMP": self.umdp3_checker.omp_missing_dollar,
56-
"Used #ifdef/#ifndef rather than #if defined() or #if !defined()": self.umdp3_checker.cpp_ifdef,
57-
"Presence of fortran comment in CPP directive": self.umdp3_checker.cpp_comment,
58-
"Used an archaic fortran intrinsic function": self.umdp3_checker.obsolescent_fortran_intrinsic,
59-
"EXIT statements should be labelled": self.umdp3_checker.exit_stmt_label,
60-
"Intrinsic modules must be USEd with an INTRINSIC "
61-
+ "keyword specifier": self.umdp3_checker.intrinsic_modules,
62-
"READ statements should have an explicit UNIT= as "
63-
+ "their first argument": self.umdp3_checker.read_unit_args,
64-
}
32+
return [
33+
self.umdp3_checker.openmp_sentinels_in_column_one,
34+
self.umdp3_checker.unseparated_keywords,
35+
self.umdp3_checker.go_to_other_than_9999,
36+
self.umdp3_checker.write_using_default_format,
37+
self.umdp3_checker.dimension_forbidden,
38+
self.umdp3_checker.ampersand_continuation,
39+
self.umdp3_checker.forbidden_keywords,
40+
self.umdp3_checker.forbidden_operators,
41+
self.umdp3_checker.tab_detection,
42+
self.umdp3_checker.printstatus_mod,
43+
self.umdp3_checker.printstar,
44+
self.umdp3_checker.write6,
45+
self.umdp3_checker.um_fort_flush,
46+
self.umdp3_checker.svn_keyword_subst,
47+
self.umdp3_checker.omp_missing_dollar,
48+
self.umdp3_checker.cpp_ifdef,
49+
self.umdp3_checker.cpp_comment,
50+
self.umdp3_checker.obsolescent_fortran_intrinsic,
51+
self.umdp3_checker.exit_stmt_label,
52+
self.umdp3_checker.intrinsic_modules,
53+
self.umdp3_checker.read_unit_args,
54+
]
6555

66-
def get_file_dispatch_table_fortran(
67-
self, filename: str = ""
68-
) -> Dict[str, Callable]:
56+
def get_file_dispatch_table_fortran(self, filename: str = "") -> list[Callable]:
6957
"""Get dispatch table for Fortran file tests"""
70-
return {
71-
"Warning - used an if-def due for retirement": self.umdp3_checker.retire_if_def,
72-
"File is missing at least one IMPLICIT NONE": self.umdp3_checker.implicit_none,
73-
"Never use STOP or CALL abort": self.umdp3_checker.forbidden_stop,
74-
"Use of Fortran function as a variable name": self.umdp3_checker.intrinsic_as_variable,
75-
"File missing crown copyright statement or agreement reference": self.umdp3_checker.check_crown_copyright,
76-
"File missing correct code owner comment": self.umdp3_checker.check_code_owner,
77-
"Used (/ 1,2,3 /) form of array initialisation, rather than "
78-
+ "[1,2,3] form": self.umdp3_checker.array_init_form,
79-
}
58+
return [
59+
self.umdp3_checker.retire_if_def,
60+
self.umdp3_checker.implicit_none,
61+
self.umdp3_checker.forbidden_stop,
62+
self.umdp3_checker.intrinsic_as_variable,
63+
self.umdp3_checker.check_code_owner,
64+
self.umdp3_checker.array_init_form,
65+
]
8066

81-
def get_diff_dispatch_table_c(self) -> Dict[str, Callable]:
67+
def get_diff_dispatch_table_c(self) -> list[Callable]:
8268
"""Get dispatch table for C diff tests"""
83-
return {
84-
"Line longer than 80 characters": self.umdp3_checker.line_over_80chars,
85-
"Line includes tab character": self.umdp3_checker.tab_detection,
86-
"Fixed-width Integer format specifiers must have a space "
87-
+ 'between themselves and the string delimiter (the " character)': self.umdp3_checker.c_integral_format_specifiers,
88-
}
69+
return [
70+
self.umdp3_checker.tab_detection,
71+
self.umdp3_checker.c_integral_format_specifiers,
72+
]
8973

90-
def get_file_dispatch_table_c(self) -> Dict[str, Callable]:
74+
def get_file_dispatch_table_c(self) -> list[Callable]:
9175
"""Get dispatch table for C file tests"""
92-
return {
93-
"Warning - used an if-def due for retirement": self.umdp3_checker.retire_if_def,
94-
"Used a deprecated C identifier": self.umdp3_checker.c_deprecated,
95-
"File missing crown copyright statement or agreement reference": self.umdp3_checker.check_crown_copyright,
96-
"File missing correct code owner comment": self.umdp3_checker.check_code_owner,
97-
"Used an _OPENMP if-def without also testing against "
98-
+ "SHUM_USE_C_OPENMP_VIA_THREAD_UTILS. (Or _OPENMP does "
99-
+ "not come first in the test.)": self.umdp3_checker.c_openmp_define_pair_thread_utils,
100-
"Used an _OPENMP && SHUM_USE_C_OPENMP_VIA_THREAD_UTILS if-def "
101-
+ "test in a logical combination with a third macro": self.umdp3_checker.c_openmp_define_no_combine,
102-
"Used !defined(_OPENMP) rather than defined(_OPENMP) "
103-
+ "with #else branch": self.umdp3_checker.c_openmp_define_not,
104-
"Used an omp #pragma (or #include <omp.h>) without "
105-
+ "protecting it with an _OPENMP if-def": self.umdp3_checker.c_protect_omp_pragma,
106-
"Used the #ifdef style of if-def, rather than the #if "
107-
+ "defined() style": self.umdp3_checker.c_ifdef_defines,
108-
"C Unit does not end with a final newline character": self.umdp3_checker.c_final_newline,
109-
}
76+
return [
77+
self.umdp3_checker.retire_if_def,
78+
self.umdp3_checker.c_deprecated,
79+
self.umdp3_checker.check_code_owner,
80+
self.umdp3_checker.c_openmp_define_pair_thread_utils,
81+
self.umdp3_checker.c_openmp_define_no_combine,
82+
self.umdp3_checker.c_openmp_define_not,
83+
self.umdp3_checker.c_protect_omp_pragma,
84+
self.umdp3_checker.c_ifdef_defines,
85+
self.umdp3_checker.c_final_newline,
86+
]
11087

111-
def get_file_dispatch_table_all(self) -> Dict[str, Callable]:
88+
def get_file_dispatch_table_all(self) -> list[Callable]:
11289
"""Get dispatch table for universal file tests"""
113-
return {
114-
"Line includes trailing whitespace character(s)": self.umdp3_checker.line_trail_whitespace,
115-
}
90+
return [
91+
self.umdp3_checker.line_trail_whitespace,
92+
]

script_umdp3_checker/fortran_keywords.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"""
1515
TODO: Current order may not be perfect, and could possibly be reviewed.
1616
However, it is probably 'good enough' for now.
17+
TODO: Document method used to decide on order. I think it was a grep and count for the
18+
frequency the keywords appear in the source code.
1719
"""
1820

1921
fortran_keywords = (

script_umdp3_checker/tests/__init__.py

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from pathlib import Path
2+
import pytest
3+
4+
5+
@pytest.fixture(scope="session")
6+
def example_fortran_lines() -> list[str]:
7+
"""Return the example Fortran source as a list of lines for tests."""
8+
test_dir = Path(__file__).resolve().parent
9+
return (test_dir / "example_fortran_code.F90").read_text().splitlines()
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
! fortls: ignore file
2+
! fortitude: ignore file
3+
! *****************************COPYRIGHT*******************************
4+
! (C) Crown copyright Met Office. All rights reserved.
5+
! For further details please refer to the file COPYRIGHT.txt
6+
! which you should have received as part of this distribution.
7+
! *****************************COPYRIGHT*******************************
8+
!
9+
! An example routine depicting how one should construct new code
10+
! to meet the UMDP3 coding standards.
11+
!
12+
MODULE example_mod
13+
IMPLICIT NONE
14+
! Description:
15+
! A noddy routine that illustrates the way to apply the UMDP3
16+
! coding standards to new code to help code developers
17+
! pass code reviews.
18+
!
19+
! Method:
20+
! In this routine we apply many of the UMDP3 features
21+
! to construct a simple routine. The references on the RHS take the reader
22+
! to the appropriate section of the UMDP3 guide with further details.
23+
!
24+
! Code Owner: Please refer to the UM file CodeOwners.txt
25+
! This file belongs in section: Control
26+
!
27+
! Code description:
28+
! Language: Fortran 2003.
29+
! This code is written to UMDP3 standards.
30+
CHARACTER(LEN=*), PARAMETER, PRIVATE :: ModuleName="EXAMPLE_MOD"
31+
CONTAINS
32+
! Subroutine Interface:
33+
SUBROUTINE example (xlen,ylen,l_unscale,input1,input2, &
34+
output, l_loud_opt)
35+
! Description:
36+
! Nothing further to add to module description.
37+
USE atmos_constants_mod, ONLY: r
38+
USE ereport_mod, ONLY: ereport
39+
USE parkind1, ONLY: jpim, jprb
40+
USE umprintMgr, ONLY: umprint,ummessage,PrNorm
41+
USE errormessagelength_mod, ONLY: errormessagelength
42+
USE yomhook, ONLY: lhook, dr_hook
43+
IMPLICIT NONE
44+
! Subroutine arguments
45+
INTEGER, INTENT(IN) :: xlen !Length of first dimension of the arrays.
46+
INTEGER, INTENT(IN) :: ylen !Length of second dimension of the arrays.
47+
LOGICAL, INTENT(IN) :: l_unscale ! switch scaling off.
48+
REAL, INTENT(IN) :: input1(xlen, ylen) !First input array
49+
REAL, INTENT(IN OUT) :: input2(xlen, ylen) !Second input array
50+
REAL, INTENT(OUT) :: output(xlen, ylen) !Contains the result
51+
LOGICAL, INTENT(IN), OPTIONAL :: l_loud_opt !optional debug flag
52+
! Local variables
53+
INTEGER(KIND=jpim), PARAMETER :: zhook_in = 0 ! DrHook tracing entry
54+
INTEGER(KIND=jpim), PARAMETER :: zhook_out = 1 ! DrHook tracing exit
55+
INTEGER :: i ! Loop counter
56+
INTEGER :: j ! Loop counter
57+
INTEGER :: icode ! error code for EReport
58+
LOGICAL :: l_loud ! debug flag (default false unless l_loud_opt is used)
59+
REAL, ALLOCATABLE :: field(:,:) ! Scaling array to fill.
60+
REAL, ALLOCATABLE :: field2(:,:) ! Scaling array to fill.
61+
REAL(KIND=jprb) :: zhook_handle ! DrHook tracing
62+
CHARACTER(LEN=*), PARAMETER :: RoutineName="EXAMPLE"
63+
CHARACTER(LEN=errormessagelength) :: Cmessage ! used for EReport
64+
CHARACTER(LEN=256) :: my_char ! string for output
65+
! End of header
66+
IF (lhook) CALL dr_hook(ModuleName//":"//RoutineName,zhook_in,zhook_handle)
67+
! Set debug flag if argument is present
68+
l_loud = .FALSE.
69+
IF (PRESENT(l_loud_opt)) THEN
70+
l_loud = l_loud_opt
71+
END IF
72+
my_char &
73+
= "This is a very very very very very very very " &
74+
// "long character assignment" ! A pointless long character example.
75+
icode=0
76+
! verbosity choice, output some numbers to aid with debugging
77+
! protected by printstatus>=PrNorm and pe=0
78+
WRITE(ummessage,"(A,I0)")"xlen=",xlen
79+
CALL umprint(ummessage,level=PrNorm,pe=0,src="example_mod")
80+
WRITE(ummessage,"(A,I0)")"ylen=",ylen
81+
CALL umprint(ummessage,level=PrNorm,pe=0,src="example_mod")
82+
IF (l_loud) CALL umprint(my_char,level=PrNormal,src="example_mod")
83+
! Allocate and initialise scaling array
84+
! Noddy code warns user when scalling is not employed.
85+
IF ( l_unscale ) THEN
86+
icode = -100 ! set up WARNING message
87+
ALLOCATE(field( 1,1 ) )
88+
ALLOCATE(field2( 1,1 ) )
89+
cmessage="Scaling is switched off in run!"
90+
CALL ereport(RoutineName,icode,cmessage)
91+
ELSE
92+
ALLOCATE(field( xlen, ylen ) )
93+
ALLOCATE(field2( xlen, ylen ) )
94+
DO j=1,ylen
95+
DO i=1,xlen
96+
field(i, j) = (1.0*i) + (2.0*j)
97+
input2(i, j) = input2(i, j) * field(i, j)
98+
field2(i, j) = (1.0*i) - (2.0*j) &
99+
+ (3.0*i*j) + (4.0*i**2) + field(i, j)*2
100+
END DO
101+
END DO
102+
END IF
103+
! The main calculation of the routine, using OpenMP.
104+
!$OMP PARALLEL DEFAULT(NONE) &
105+
!$OMP SHARED(xlen,ylen,input1,input2,field,output) &
106+
!$OMP PRIVATE(i, j )
107+
!$OMP DO SCHEDULE(STATIC)
108+
DO j = 1, ylen
109+
i_loop: DO i = 1, xlen
110+
! Calculate the Output value:
111+
output(i, j) = (input1(i, j) * input2(i, j))
112+
END DO i_loop
113+
END DO ! j loop
114+
!$OMP END DO
115+
!$OMP END PARALLEL
116+
DEALLOCATE (field)
117+
IF (lhook) CALL dr_hook(ModuleName//":"//RoutineName,zhook_out,zhook_handle)
118+
RETURN
119+
END SUBROUTINE example
120+
END MODULE example_mod

0 commit comments

Comments
 (0)