Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
09510e0
Some initial drafts
r-sharp Mar 24, 2026
7784b5f
some working, but very messy examples
r-sharp Mar 25, 2026
e626a32
Some tidying, and some questions for tomorrow.
r-sharp Mar 25, 2026
f71e30d
Twiddling things, it works, but is "useless"
r-sharp Mar 27, 2026
7134e81
Adding copyright checker.
r-sharp Mar 30, 2026
4ba0e8d
improve error messages
r-sharp Apr 1, 2026
89db21a
Minor fixes for missing config owner (#218)
james-bruten-mo Mar 31, 2026
7bbecff
Aaaarrrrrrrrgle!!!!!!!
r-sharp Apr 7, 2026
f16a1fa
Merge branch 'MetOffice:main' into add-additional-unit-tests
r-sharp Apr 15, 2026
de71e6c
testing built in helper functions and trying to concattenate lines
r-sharp Apr 15, 2026
1c7c589
Adding line length check
r-sharp Apr 16, 2026
cd2b864
Adding re-worked tests to checker so they get run.
r-sharp Apr 16, 2026
5e01490
converting dictionaries of callable tests to lists
r-sharp Apr 16, 2026
d2de828
Bit of a TODO and redundant code tidy
r-sharp Apr 17, 2026
1713ac1
missed a bit...
r-sharp Apr 17, 2026
2a33be3
Stop ruff from trying to correct the demo Fortran file
r-sharp Apr 22, 2026
e8f9b5c
Adding single programming module tests and adjusting TODOs a bit more.
r-sharp Apr 22, 2026
ae4e58a
Quick Tidy of an output failure message.
r-sharp Apr 23, 2026
5f1d9c0
Updating the Fortran file editing routine to add or replace with mult…
r-sharp Apr 24, 2026
02c663b
skinning the Latte.
r-sharp Apr 24, 2026
6c543ea
Tidying up redundant tests
r-sharp Apr 24, 2026
db52136
Merge branch 'MetOffice:main' into add-additional-unit-tests
r-sharp Apr 27, 2026
f323196
A mild tidy, and a deliberate test for l_some_logical = .FALSE. as hi…
r-sharp Apr 27, 2026
5afa827
It is well documented that I hate ruff, but when I find out who keeps…
r-sharp Apr 27, 2026
6a708aa
Linter loonacy.
r-sharp Apr 27, 2026
ed1a09d
to requote : "I hate ruff"
r-sharp Apr 27, 2026
6db154e
minor tweaks to CamelCase var checker, plus extra tests
r-sharp Apr 28, 2026
d14935c
Change order of assertions to improve error diagnosis when the tests …
r-sharp Apr 28, 2026
edcdd20
Improving checks on unseparated keywords
r-sharp Apr 29, 2026
f5364e9
preventing false failures on cpp directives, and adding those caases …
r-sharp Apr 29, 2026
e85890d
ruff sucks
r-sharp Apr 29, 2026
e4dcf56
Bump timeout (#227)
james-bruten-mo Apr 29, 2026
3ccde20
Merge branch 'MetOffice:main' into Improve_umdp3_checks_I
r-sharp Apr 30, 2026
f948e9e
Apply suggestions from code review
r-sharp Apr 30, 2026
9325482
Trying to pass the unit tests....
r-sharp Apr 30, 2026
827a3a3
Fungle Worzle Aardvark Frakkin' Apostorphes
r-sharp Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/linters/.ruff.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
cache-dir = "/tmp/.ruff_cache"
line-length = 88
output-format = "grouped"
exclude = ["*.F90"]

[lint]
# Allow unused variables when underscore-prefixed.
Expand Down
143 changes: 60 additions & 83 deletions script_umdp3_checker/checker_dispatch_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,16 @@
Python translation of the original Perl module
"""

from typing import Dict, Callable
from typing import Callable
from umdp3_checker_rules import UMDP3Checker

"""
TODO : This list was checked to ensure it had something for each
test in the original.
TODO : This needs to be re-checked.
TODO : And the tests need to be compared to the original Perl tests
to ensure they are equivalent.
"""
TODO : This module has lost it's way. It uses a class to define methods which just
return lists of functions. I don't think a class is required for this.
As the functions themselves are shifted, renamed and hopefully improved, this
class should eventually get emptied out and the file removed.

# Declare version
VERSION = "13.5.0"
"""


class CheckerDispatchTables:
Expand All @@ -30,86 +27,66 @@ class CheckerDispatchTables:
def __init__(self):
self.umdp3_checker = UMDP3Checker()

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

def get_file_dispatch_table_fortran(
self, filename: str = ""
) -> Dict[str, Callable]:
def get_file_dispatch_table_fortran(self, filename: str = "") -> list[Callable]:
"""Get dispatch table for Fortran file tests"""
return {
"Warning - used an if-def due for retirement": self.umdp3_checker.retire_if_def,
"File is missing at least one IMPLICIT NONE": self.umdp3_checker.implicit_none,
"Never use STOP or CALL abort": self.umdp3_checker.forbidden_stop,
"Use of Fortran function as a variable name": self.umdp3_checker.intrinsic_as_variable,
"File missing crown copyright statement or agreement reference": self.umdp3_checker.check_crown_copyright,
"File missing correct code owner comment": self.umdp3_checker.check_code_owner,
"Used (/ 1,2,3 /) form of array initialisation, rather than "
+ "[1,2,3] form": self.umdp3_checker.array_init_form,
}
return [
self.umdp3_checker.retire_if_def,
self.umdp3_checker.implicit_none,
self.umdp3_checker.forbidden_stop,
self.umdp3_checker.intrinsic_as_variable,
self.umdp3_checker.check_code_owner,
self.umdp3_checker.array_init_form,
]

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

def get_file_dispatch_table_c(self) -> Dict[str, Callable]:
def get_file_dispatch_table_c(self) -> list[Callable]:
"""Get dispatch table for C file tests"""
return {
"Warning - used an if-def due for retirement": self.umdp3_checker.retire_if_def,
"Used a deprecated C identifier": self.umdp3_checker.c_deprecated,
"File missing crown copyright statement or agreement reference": self.umdp3_checker.check_crown_copyright,
"File missing correct code owner comment": self.umdp3_checker.check_code_owner,
"Used an _OPENMP if-def without also testing against "
+ "SHUM_USE_C_OPENMP_VIA_THREAD_UTILS. (Or _OPENMP does "
+ "not come first in the test.)": self.umdp3_checker.c_openmp_define_pair_thread_utils,
"Used an _OPENMP && SHUM_USE_C_OPENMP_VIA_THREAD_UTILS if-def "
+ "test in a logical combination with a third macro": self.umdp3_checker.c_openmp_define_no_combine,
"Used !defined(_OPENMP) rather than defined(_OPENMP) "
+ "with #else branch": self.umdp3_checker.c_openmp_define_not,
"Used an omp #pragma (or #include <omp.h>) without "
+ "protecting it with an _OPENMP if-def": self.umdp3_checker.c_protect_omp_pragma,
"Used the #ifdef style of if-def, rather than the #if "
+ "defined() style": self.umdp3_checker.c_ifdef_defines,
"C Unit does not end with a final newline character": self.umdp3_checker.c_final_newline,
}
return [
self.umdp3_checker.retire_if_def,
self.umdp3_checker.c_deprecated,
self.umdp3_checker.check_code_owner,
self.umdp3_checker.c_openmp_define_pair_thread_utils,
self.umdp3_checker.c_openmp_define_no_combine,
self.umdp3_checker.c_openmp_define_not,
self.umdp3_checker.c_protect_omp_pragma,
self.umdp3_checker.c_ifdef_defines,
self.umdp3_checker.c_final_newline,
]

def get_file_dispatch_table_all(self) -> Dict[str, Callable]:
def get_file_dispatch_table_all(self) -> list[Callable]:
"""Get dispatch table for universal file tests"""
return {
"Line includes trailing whitespace character(s)": self.umdp3_checker.line_trail_whitespace,
}
return [
self.umdp3_checker.line_trail_whitespace,
]
2 changes: 2 additions & 0 deletions script_umdp3_checker/fortran_keywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"""
TODO: Current order may not be perfect, and could possibly be reviewed.
However, it is probably 'good enough' for now.
TODO: Document method used to decide on order. I think it was a grep and count for the
frequency the keywords appear in the source code.
"""

fortran_keywords = (
Expand Down
Empty file.
9 changes: 9 additions & 0 deletions script_umdp3_checker/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pathlib import Path
import pytest


@pytest.fixture(scope="session")
def example_fortran_lines() -> list[str]:
"""Return the example Fortran source as a list of lines for tests."""
test_dir = Path(__file__).resolve().parent
return (test_dir / "example_fortran_code.F90").read_text().splitlines()
121 changes: 121 additions & 0 deletions script_umdp3_checker/tests/example_fortran_code.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
! fortls: ignore file
! fortitude: ignore file
! *****************************COPYRIGHT*******************************
! (C) Crown copyright Met Office. All rights reserved.
! For further details please refer to the file COPYRIGHT.txt
! which you should have received as part of this distribution.
! *****************************COPYRIGHT*******************************
!
! An example routine depicting how one should construct new code
! to meet the UMDP3 coding standards.
!
MODULE example_mod
IMPLICIT NONE
! Description:
! A noddy routine that illustrates the way to apply the UMDP3
! coding standards to new code to help code developers
! pass code reviews.
!
! Method:
! In this routine we apply many of the UMDP3 features
! to construct a simple routine. The references on the RHS take the reader
! to the appropriate section of the UMDP3 guide with further details.
!
! Code Owner: Please refer to the UM file CodeOwners.txt
! This file belongs in section: Control
!
! Code description:
! Language: Fortran 2003.
! This code is written to UMDP3 standards.
CHARACTER(LEN=*), PARAMETER, PRIVATE :: ModuleName="EXAMPLE_MOD"
CONTAINS
! Subroutine Interface:
SUBROUTINE example (xlen,ylen,l_unscale,input1,input2, &
output, l_loud_opt)
! Description:
! Nothing further to add to module description.
USE atmos_constants_mod, ONLY: r
USE ereport_mod, ONLY: ereport
USE parkind1, ONLY: jpim, jprb
USE umprintMgr, ONLY: umprint,ummessage,PrNorm
USE errormessagelength_mod, ONLY: errormessagelength
USE yomhook, ONLY: lhook, dr_hook
IMPLICIT NONE
! Subroutine arguments
INTEGER, INTENT(IN) :: xlen !Length of first dimension of the arrays.
INTEGER, INTENT(IN) :: ylen !Length of second dimension of the arrays.
LOGICAL, INTENT(IN) :: l_unscale ! switch scaling off.
REAL, INTENT(IN) :: input1(xlen, ylen) !First input array
REAL, INTENT(IN OUT) :: input2(xlen, ylen) ! INOUT Second input array
REAL, INTENT(OUT) :: output(xlen, ylen) !Contains the result
REAL, PARAMETER :: b_pollonator(4) = [ 0.0, 11.2, 6.6, 3.6]
LOGICAL, INTENT(IN), OPTIONAL :: l_loud_opt !optional debug flag
! Local variables
INTEGER(KIND=jpim), PARAMETER :: zhook_in = 0 ! DrHook tracing entry
INTEGER(KIND=jpim), PARAMETER :: zhook_out = 1 ! DrHook tracing exit
INTEGER :: i ! Loop counter
INTEGER :: j ! Loop counter
INTEGER :: icode ! error code for EReport
LOGICAL :: l_loud ! debug flag (default false unless l_loud_opt is used)
REAL, ALLOCATABLE :: field(:,:) ! Scaling array to fill.
REAL, ALLOCATABLE :: field2(:,:) ! Scaling array to fill.
REAL(KIND=jprb) :: zhook_handle ! DrHook tracing
CHARACTER(LEN=*), PARAMETER :: RoutineName="EXAMPLE"
CHARACTER(LEN=errormessagelength) :: Cmessage ! used for EReport
CHARACTER(LEN=256) :: my_char ! string for output
! End of header
IF (lhook) CALL dr_hook(ModuleName//":"//RoutineName,zhook_in,zhook_handle)
! Set debug flag if argument is present
l_loud = .FALSE.
IF (PRESENT(l_loud_opt)) THEN
l_loud = l_loud_opt
END IF
my_char &
= "This is a very very very very very very very " &
// "long character assignment" ! A pointless long character example.
icode=0
! verbosity choice, output some numbers to aid with debugging
! protected by printstatus>=PrNorm and pe=0
WRITE(ummessage,"(A,I0)")"xlen=",xlen
CALL umprint(ummessage,level=PrNorm,pe=0,src="example_mod")
WRITE(ummessage,"(A,I0)")"ylen=",ylen
CALL umprint(ummessage,level=PrNorm,pe=0,src="example_mod")
IF (l_loud) CALL umprint(my_char,level=PrNormal,src="example_mod")
! Allocate and initialise scaling array
! Noddy code warns user when scalling is not employed.
IF ( l_unscale ) THEN
icode = -100 ! set up WARNING message
ALLOCATE(field( 1,1 ) )
ALLOCATE(field2( 1,1 ) )
cmessage="Scaling is switched off in run!"
CALL ereport(RoutineName,icode,cmessage)
ELSE
ALLOCATE(field( xlen, ylen ) )
ALLOCATE(field2( xlen, ylen ) )
DO j=1,ylen
DO i=1,xlen
field(i, j) = (1.0*i) + (2.0*j)
input2(i, j) = input2(i, j) * field(i, j)
field2(i, j) = (1.0*i) - (2.0*j) &
+ (3.0*i*j) + (4.0*i**2) + field(i, j)*2
END DO
END DO
END IF
! The main calculation of the routine, using OpenMP.
!$OMP PARALLEL DEFAULT(NONE) &
!$OMP SHARED(xlen,ylen,input1,input2,field,output) &
!$OMP PRIVATE(i, j )
!$OMP DO SCHEDULE(STATIC)
DO j = 1, ylen
i_loop: DO i = 1, xlen
! Calculate the Output value:
output(i, j) = (input1(i, j) * input2(i, j))
END DO i_loop
END DO ! j loop
!$OMP END DO
!$OMP END PARALLEL
DEALLOCATE (field)
IF (lhook) CALL dr_hook(ModuleName//":"//RoutineName,zhook_out,zhook_handle)
RETURN
END SUBROUTINE example
END MODULE example_mod
Loading
Loading