Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4c04333
Add capability to parse nested suites using a new version of the XML …
climbfuji Oct 7, 2025
bb5d19e
Add nested_suite_test
climbfuji Oct 7, 2025
26bba86
Add doctests to scripts/parse_tools/xml_tools.py, clean up scripts/cc…
climbfuji Oct 7, 2025
7821cef
More cleanups
climbfuji Oct 7, 2025
559c5e7
Merge branch 'develop' of https://github.com/ncar/ccpp-framework into…
climbfuji Oct 7, 2025
648612a
Remove unused test/nested_suite_test/nested_suite_test_reports.py
climbfuji Oct 7, 2025
219f41b
Merge branch 'develop' into feature/nested_suites
climbfuji Oct 23, 2025
a500b4e
Merge branch 'develop' of https://github.com/ncar/ccpp-framework into…
climbfuji Nov 4, 2025
0d144ae
Nested suites: prevent infinite recursion, allow file attribute to be…
climbfuji Nov 4, 2025
57acb17
Merge branch 'feature/nested_suites' of https://github.com/climbfuji/…
climbfuji Nov 4, 2025
21623ce
Merge branch 'develop' into feature/nested_suites
climbfuji Nov 5, 2025
f336c17
Add capability to insert only a specific group of a nested suite at t…
climbfuji Nov 12, 2025
e0d375b
Revert back to only one suite per SDF; fix check for recursive expans…
climbfuji Nov 13, 2025
2877943
In xml_tools.py: collect names of expanded suite and provide to user …
climbfuji Nov 20, 2025
4ac80e4
Remove unused optional attribute lib from schema/suite_v2_0.xsd
climbfuji Nov 20, 2025
1b80a4b
Added tracking and trapping of circular dependencies, partially reverted
Nov 30, 2025
afbf356
Remove no-longer-needed logic to inject file attribute for nested sui…
climbfuji Dec 2, 2025
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
16 changes: 10 additions & 6 deletions .github/workflows/capgen_unit_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ jobs:
python3 \
git \
libxml2-utils
pip install --user pytest
python -m pip install --upgrade pip
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this consistent with the install commands in python.yaml

pip install pytest
which xmllint
xmllint --version
which pytest

- name: Build the framework
run: |
Expand All @@ -42,11 +46,11 @@ jobs:
run: |
BUILD_DIR=./build \
PYTHONPATH=test/:scripts/ \
pytest \
test/capgen_test/capgen_test_reports.py \
test/advection_test/advection_test_reports.py \
test/ddthost_test/ddthost_test_reports.py \
test/var_compatibility_test/var_compatibility_test_reports.py
pytest \
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aligning the indentation to match the rest of the file

test/capgen_test/capgen_test_reports.py \
test/advection_test/advection_test_reports.py \
test/ddthost_test/ddthost_test_reports.py \
test/var_compatibility_test/var_compatibility_test_reports.py

- name: Run Fortran to metadata test
run: cd test && ./test_fortran_to_metadata.sh
7 changes: 7 additions & 0 deletions .github/workflows/python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libxml2-utils
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now require xmllint to run the pytests - we should have done so much earlier imo.

python -m pip install --upgrade pip
pip install pytest
which xmllint
xmllint --version
which pytest

- name: Test with pytest
run: |
export PYTHONPATH=$(pwd)/scripts:$(pwd)/scripts/parse_tools
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ option(CCPP_RUN_ADVECTION_TEST "Enable advection regression test" OFF)
option(CCPP_RUN_CAPGEN_TEST "Enable capgen regression test" OFF)
option(CCPP_RUN_DDT_HOST_TEST "Enable ddt host regression test" OFF)
option(CCPP_RUN_VAR_COMPATIBILITY_TEST "Enable variable compatibility regression test" OFF)
option(CCPP_RUN_NESTED_SUITE_TEST "Enable nested suite regression test" OFF)

message("")
message("OPENMP .............................. ${OPENMP}")
Expand All @@ -31,6 +32,7 @@ message("CCPP_RUN_ADVECTION_TEST ............. ${CCPP_RUN_ADVECTION_TEST}")
message("CCPP_RUN_CAPGEN_TEST ................ ${CCPP_RUN_CAPGEN_TEST}")
message("CCPP_RUN_DDT_HOST_TEST .............. ${CCPP_RUN_DDT_HOST_TEST}")
message("CCPP_RUN_VAR_COMPATIBILITY_TEST ..... ${CCPP_RUN_VAR_COMPATIBILITY_TEST}")
message("CCPP_RUN_NESTED_SUITE_TEST .......... ${CCPP_RUN_NESTED_SUITE_TEST}")
message("")

set(CCPP_VERBOSITY "0" CACHE STRING "Verbosity level of output (default: 0)")
Expand Down
2 changes: 1 addition & 1 deletion schema/suite_v1_0.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

<!-- attributes -->

<xs:attribute name="version" type="version_type"/>
<xs:attribute name="version" type="version_type"/>

<!-- definition of complex types -->

Expand Down
150 changes: 150 additions & 0 deletions schema/suite_v2_0.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>

<xs:schema elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">

<!-- identifier types -->

<xs:simpleType name="version_type">
<xs:restriction base="xs:string">
<xs:pattern value="[1-9][0-9]*[.][0-9]+"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="fortran_id_type">
<xs:restriction base="xs:string">
<xs:pattern value="[A-Za-z][A-Za-z0-9_]{0,63}"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="subcycle_type">
<xs:restriction base="xs:string">
<xs:pattern value="[a-z][a-z0-9_]*"/>
<xs:pattern value="[1-9][0-9]*"/>
</xs:restriction>
</xs:simpleType>

<!-- attributes -->

<xs:attribute name="version" type="version_type"/>

<!-- definition of complex types -->

<xs:complexType name="scheme_type">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="version" type="xs:string" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>

<xs:complexType name="nested_suite_group_type">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="group" type="xs:string" use="required"/>
<xs:attribute name="file" type="xs:string" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>

<xs:complexType name="nested_suite_type">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="name" type="xs:string" use="required"/>
<xs:attribute name="group" type="xs:string" use="optional"/>
<xs:attribute name="file" type="xs:string" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>

<!-- definition of suite elements -->

<xs:element name="time_split">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="time_split"/>
<xs:element ref="process_split"/>
<xs:element ref="subcycle"/>
<xs:element ref="subcol"/>
<xs:element name="scheme" type="scheme_type"/>
</xs:choice>
</xs:complexType>
</xs:element>

<xs:element name="process_split">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="time_split"/>
<xs:element ref="process_split"/>
<xs:element ref="subcycle"/>
<xs:element ref="subcol"/>
<xs:element name="scheme" type="scheme_type"/>
</xs:choice>
</xs:complexType>
</xs:element>

<xs:element name="subcycle">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="time_split"/>
<xs:element ref="process_split"/>
<xs:element ref="subcycle"/>
<xs:element ref="subcol"/>
<xs:element name="scheme" type="scheme_type"/>
</xs:choice>
<xs:attribute name="loop" type="subcycle_type" use="optional"/>
<xs:attribute name="name" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>

<xs:element name="subcol">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="time_split"/>
<xs:element ref="process_split"/>
<xs:element ref="subcycle"/>
<xs:element ref="subcol"/>
<xs:element name="scheme" type="scheme_type"/>
</xs:choice>
<xs:attribute name="gen" type="fortran_id_type" use="required"/>
<xs:attribute name="avg" type="fortran_id_type" use="required"/>
</xs:complexType>
</xs:element>

<xs:element name="group">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="time_split"/>
<xs:element ref="process_split"/>
<xs:element ref="subcycle"/>
<xs:element ref="subcol"/>
<xs:element name="scheme" type="scheme_type"/>
<xs:element name="nested_suite" type="nested_suite_group_type"/>
</xs:choice>
<xs:attribute name="name" type="xs:ID" use="required"/>
</xs:complexType>
</xs:element>

<xs:element name="suite">
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="1">
<xs:element name="init" type="scheme_type"/>
<xs:element name="initalize" type="scheme_type"/>
</xs:choice>
<xs:element ref="group" minOccurs="0" maxOccurs="unbounded"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="nested_suite" type="nested_suite_type"/>
</xs:choice>
<xs:choice minOccurs="0" maxOccurs="1">
<xs:element name="final" type="scheme_type"/>
<xs:element name="finalize" type="scheme_type"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="name" type="xs:ID" use="required"/>
<xs:attribute name="version" type="version_type" use="required"/>
</xs:complexType>
</xs:element>

</xs:schema>
5 changes: 2 additions & 3 deletions scripts/ccpp_datafile.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from framework_env import CCPPFrameworkEnv
from metadata_table import UNKNOWN_PROCESS_TYPE
from metavar import Var
from parse_tools import read_xml_file, PrettyElementTree
from parse_tools import read_xml_file, write_xml_file
from parse_tools import ParseContext, ParseSource
from suite_objects import VerticalLoop, Subcycle

Expand Down Expand Up @@ -1182,8 +1182,7 @@ def generate_ccpp_datatable(run_env, host_model, api, scheme_headers,
# end for
_add_dependencies(datatable, scheme_depends, host_depends)
# Write tree
datatable_tree = PrettyElementTree(datatable)
datatable_tree.write(run_env.datatable_file)
write_xml_file(datatable, run_env.datatable_file)

###############################################################################

Expand Down
64 changes: 42 additions & 22 deletions scripts/ccpp_suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from metavar import Var, VarDictionary, ccpp_standard_var
from parse_tools import ParseContext, ParseSource
from parse_tools import ParseInternalError, CCPPError
from parse_tools import read_xml_file, validate_xml_file, find_schema_version
from parse_tools import read_xml_file, validate_xml_file, write_xml_file
from parse_tools import find_schema_version, expand_nested_suites
from parse_tools import init_log, set_log_to_null
from suite_objects import CallList, Group, Scheme
from metavar import CCPP_LOOP_VAR_STDNAMES
Expand Down Expand Up @@ -82,7 +83,7 @@ class Suite(VarDictionary):

__scheme_template = '<scheme>{}</scheme>'

def __init__(self, filename, api, run_env):
def __init__(self, filename, suite_xml, api, run_env):
"""Initialize this Suite object from the SDF, <filename>.
<api> serves as the Suite's parent."""
self.__run_env = run_env
Expand Down Expand Up @@ -114,7 +115,7 @@ def __init__(self, filename, api, run_env):
raise CCPPError(emsg.format(self.__sdf_name))
# end if
# Parse the SDF
self.parse(run_env)
self.parse(suite_xml, run_env)

@property
def name(self):
Expand Down Expand Up @@ -186,27 +187,13 @@ def new_group_from_name(self, group_name, run_env):
group_xml = '<group name="{}"></group>'.format(group_name)
return self.new_group(group_xml, group_name, run_env)

def parse(self, run_env):
def parse(self, suite_xml, run_env):
"""Parse the suite definition file."""
success = True

_, suite_xml = read_xml_file(self.__sdf_name, run_env.logger)
# We do not have line number information for the XML file
self.__context = ParseContext(filename=self.__sdf_name)
# Validate the XML file
version = find_schema_version(suite_xml)
res = validate_xml_file(self.__sdf_name, 'suite', version,
run_env.logger)
if not res:
emsg = "Invalid suite definition file, '{}'"
raise CCPPError(emsg.format(self.__sdf_name))
# end if
self.__name = suite_xml.get('name')
self.__module = 'ccpp_{}_cap'.format(self.name)
lmsg = "Reading suite definition file for '{}'"
if run_env.logger and run_env.logger.isEnabledFor(logging.INFO):
run_env.logger.info(lmsg.format(self.name))
# end if
gname = Suite.__register_group_name
self.__suite_reg_group = self.new_group_from_name(gname, run_env)
gname = Suite.__initial_group_name
Expand Down Expand Up @@ -681,13 +668,46 @@ def __init__(self, sdfs, host_model, scheme_headers, run_env):
raise CCPPError(errmsg.format(header.title))
# end if
# end for

# Turn the SDF files into Suites
for sdf in sdfs:
suite = Suite(sdf, self, run_env)
suite.analyze(self.host_model, scheme_library,
self.__ddt_lib, run_env)
self.__suites.append(suite)
# Load the suite definition file to determine the schema version,
# validate the file, and expand nested suites if applicable
_, xml_root = read_xml_file(sdf, run_env.logger)
# We do not have line number information for the XML file
self.__context = ParseContext(filename=sdf)
# Validate the XML file
schema_version = find_schema_version(xml_root)
_ = validate_xml_file(sdf, 'suite', schema_version, run_env.logger)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment on xmllint further below (test_sdf.py)


# Write the expanded sdf to the capgen output directory.
# This file isn't used by capgen (everything is in memory
# from here onwards), but it is useful for developers/users
# (although the output can also be found in the datatable).
(sdf_path, sdf_name) = os.path.split(sdf)
sdf_expanded = os.path.join(run_env.output_dir,
sdf_name.replace(".xml", "_expanded.xml"))
if schema_version[0] in [1, 2]:
# Preprocess the sdf to expand nested suites
if schema_version[0] == 2:
expand_nested_suites(xml_root, sdf_path, logger=run_env.logger)
# For both versions 1 and 2, write the SDF (expanded for
# version 2, original for version 1) to the current directory
write_xml_file(xml_root, sdf_expanded, run_env.logger)
# Validate the expanded SDF for version 2
if schema_version[0] == 2:
_ = validate_xml_file(sdf, 'suite', schema_version, run_env.logger)
suite = Suite(sdf, xml_root, self, run_env)
suite.analyze(self.host_model, scheme_library,
self.__ddt_lib, run_env)
self.__suites.append(suite)
else:
errmsg = f"Suite XML schema not supported: " + \
"root={xml_root.tag}, version={schema_version}"
raise CCPPError(errmsg)
# end if
# end for

# We will need the correct names for errmsg and errcode
evar = self.host_model.find_variable(standard_name='ccpp_error_message')
if evar is not None:
Expand Down
5 changes: 3 additions & 2 deletions scripts/parse_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from preprocess import PreprocStack
from xml_tools import find_schema_file, find_schema_version
from xml_tools import read_xml_file, validate_xml_file
from xml_tools import PrettyElementTree
from xml_tools import expand_nested_suites, write_xml_file
from fortran_conditional import FORTRAN_CONDITIONAL_REGEX_WORDS, FORTRAN_CONDITIONAL_REGEX
# pylint: enable=wrong-import-position

Expand All @@ -51,6 +51,7 @@
'check_valid_values',
'check_molar_mass',
'context_string',
'expand_nested_suites',
'find_schema_file',
'find_schema_version',
'flush_log',
Expand All @@ -65,7 +66,6 @@
'ParseSyntaxError',
'ParseObject',
'PreprocStack',
'PrettyElementTree',
'read_xml_file',
'register_fortran_ddt_name',
'registered_fortran_ddt_name',
Expand All @@ -78,6 +78,7 @@
'type_name',
'unique_standard_name',
'validate_xml_file',
'write_xml_file',
'FORTRAN_CONDITIONAL_REGEX_WORDS',
'FORTRAN_CONDITIONAL_REGEX'
]
Loading