Skip to content

Commit 71fbc5b

Browse files
committed
Merge branch 'develop' into feature/phys2dyn_gjf
2 parents 6261eb6 + 9827d29 commit 71fbc5b

91 files changed

Lines changed: 3510 additions & 2754 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cicd/scripts/regression_test.sh

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,6 @@ function regression_test() {
5252
cd tests
5353
pwd
5454

55-
# shellcheck disable=SC2015
56-
[[ ${UFS_PLATFORM} =~ clusternoaa ]] && echo "export BL_DATE=20240426" > bl_date.conf || cat bl_date.conf
57-
58-
mkdir -p logs/
59-
BL_DATE=$(cut -d '=' -f2 bl_date.conf)
60-
export BL_DATE
61-
6255
if [[ ${machine} =~ "Hercules" ]]
6356
then
6457
echo "Running regression tests on ${machine}"

.github/pull_request_template.md

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,19 @@ Provide a concise commit message for the UFS WM and any subcomponents; delete un
3333
```
3434
* UFSWM -
3535
* AQM -
36+
* CATChem -
3637
* CDEPS -
38+
* CECE -
3739
* CICE -
3840
* CMEPS -
3941
* CMakeModules -
4042
* UFSATM -
41-
* ccpp-physics -
42-
* atmos_cubed_sphere -
43-
* GOCART -
43+
* ccpp-physics -
44+
* CCPP submodules (list) -
45+
* atmos_cubed_sphere -
46+
* MPAS
47+
* GOCART -
48+
* LM4 -
4449
* MOM6 -
4550
* NOAHMP -
4651
* WW3 -
@@ -72,20 +77,26 @@ Example:
7277
* WW3: NOAA-EMC/WW3#321
7378
Delete sections that are not needed.
7479
-->
75-
* AQM:
76-
* CDEPS:
77-
* CICE:
78-
* CMEPS:
79-
* CMakeModules:
80-
* UFSATM:
81-
* ccpp-physics:
82-
* atmos_cubed_sphere:
83-
* GOCART:
84-
* MOM6:
85-
* NOAHMP:
86-
* WW3:
87-
* fire_behavior:
88-
* stochastic_physics:
80+
* UFSWM -
81+
* AQM -
82+
* CATChem -
83+
* CDEPS -
84+
* CECE -
85+
* CICE -
86+
* CMEPS -
87+
* CMakeModules -
88+
* UFSATM -
89+
* ccpp-physics -
90+
* CCPP submodules (list) -
91+
* atmos_cubed_sphere -
92+
* MPAS
93+
* GOCART -
94+
* LM4 -
95+
* MOM6 -
96+
* NOAHMP -
97+
* WW3 -
98+
* fire_behavior
99+
* stochastic_physics -
89100
* None
90101

91102
### UFSWM Blocking Dependencies:
@@ -150,3 +161,8 @@ If there are changes to input data for a test, provide information here. Delete
150161
- [ ] Acorn
151162
- [ ] CI
152163
- [ ] opnReqTest (complete task if unnecessary)
164+
165+
## Testing Remarks:
166+
<!-- Lead CM: List (1) testing issues that we are bypassing (e.g., failing CI due to remarks or an early-merged component PR)
167+
(2) Issues that have been opened based on testing results (3) any other relevant info -->
168+
-

.github/scripts/check_log_warnings_remarks.py

Lines changed: 99 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ def call_API(self, endpoint):
3535
api_call = APICall(endpoint)
3636
response = requests.get(api_call.url, headers=api_call.header)
3737
if response.status_code != 200:
38-
logging.warning(response)
39-
print(response)
38+
logging.error(f"{response}: API call failed for {api_call.url}")
4039
sys.exit(1)
4140
response = json.loads(response.text)
4241

@@ -61,44 +60,37 @@ def _fetch_log_text(self, commit):
6160
r = requests.get(url, headers=api_call.header)
6261
return r.text
6362
except:
64-
logging.error("An appropriate commit(s) was not provided. Call _get_commits() first.")
65-
66-
def _get_test_data(self, log_instance):
67-
"""For each instance of a log at a given commit, extract runtime and memory data from the log text
68-
Args:
69-
log_instance: Log text for a given commit
70-
Returns:
71-
tests_for_log_instance: A dictionary of tests (keys) with a tuple of warnings and remarks as the value for each test
72-
"""
63+
logging.error(f"No commit found for the ref {commit}")
64+
sys.exit(1)
7365

74-
tests_for_log_instance = {}
66+
def _get_data(self, log_text, pattern, handler, to_clean=True):
67+
"""Extract data on warnings, remarks, and failed compiles/tests
68+
Args:
69+
log_text: Text from a log file at a given commit
70+
pattern: regex pattern to match
71+
handler: function indicating how to handle the incoming data
72+
to_clean: Flag indicating whether the data needs to be cleaned (True) or not (False)
73+
"""
7574

76-
# Use non-capturing groups in pattern to indicate warnings/remarks may or may not be present.
77-
pattern = r"COMPILE \'(.*)\' \[\d+:\d+, \d+:\d+\](?: \( (?:(\d+) warnings)?\s*(?:(\d+) remarks)? \))?"
75+
data = {}
7876

79-
log_instance = log_instance.splitlines()
77+
log_text = log_text.splitlines()
8078

81-
for line in log_instance:
79+
for line in log_text:
8280
test_match = re.search(pattern, line)
8381
if test_match:
84-
test_name, warnings, remarks = test_match.groups()
85-
tests_for_log_instance[test_name] = (warnings, remarks)
82+
handler(data, *test_match.groups())
83+
84+
if to_clean:
85+
data = self._clean_data(data)
86+
87+
return data
8688

87-
return tests_for_log_instance
89+
def handle_warn_rmk(self, data_dict, test_name, warnings, remarks):
90+
data_dict.update({test_name: (warnings, remarks)})
8891

89-
def _get_pr_data(self, commit):
90-
"""Extract warnings/remarks data for a particular commit.
91-
Returns:
92-
log_data: A dictionary of tests as the key with a tuple of (warnings, remarks) as the value
93-
"""
94-
try:
95-
log_text = self._fetch_log_text(commit)
96-
log_data = self._get_test_data(log_text)
97-
log_data = self._clean_data(log_data)
98-
return log_data
99-
except:
100-
logging.error(f"No commit found for the ref {commit}")
101-
sys.exit(1)
92+
def handle_failures(self, data_dict, reason, test):
93+
data_dict.setdefault(reason, []).append(test)
10294

10395
def _clean_data(self, test_data):
10496
"""Convert None values to zeros in the test_data dictionary"""
@@ -126,45 +118,99 @@ def compare_results(self, pr_log, base_log):
126118

127119
return increases
128120

129-
def print_html_results(dict):
130-
"""Print the comparison results in HTML."""
121+
def print_html_results(data_dict, title, file_name):
122+
"""Print results in HTML format.
123+
124+
Args:
125+
data_dict: Dictionary with structure {machine: {category: tests, ...}, ...}
126+
where values can be either:
127+
- A dict with {test: count, ...} (for warnings/remarks)
128+
- A list of tests (for failures)
129+
title: Title for the markdown file
130+
file_name: Name for the markdown file
131+
132+
Returns:
133+
Formatted markdown string
134+
"""
135+
mdFile = MdUtils(file_name=file_name, title=title)
136+
137+
for machine, machine_data in data_dict.items():
138+
# Skip if there is no data for the machine (cases: (1) where all RTs pass on the machine or (2) there is no increase in warnings & remarks)
139+
if not machine_data:
140+
continue
141+
142+
# Skip if all categories (warnings/remarks/failure reasons) are empty
143+
if all(not category for category in machine_data.values()):
144+
continue
145+
146+
mdFile.write(f"\n<h3>{machine.upper()}</h3>\n")
147+
148+
for category, tests in machine_data.items():
149+
# Skip printing info if there are no tests listed for a given category
150+
if not tests:
151+
continue
152+
153+
unordered_list = [f"**{category.title()}:**", []]
154+
155+
# Handle both dict (warnings/remarks) and list (failures)
156+
if isinstance(tests, dict):
157+
for test, count in tests.items():
158+
unordered_list[1].append(f"{test}: {count}")
159+
else: # list
160+
for test in tests:
161+
unordered_list[1].append(test)
162+
163+
mdFile.new_list(unordered_list, marked_with='*')
131164

132-
pr_num = os.environ.get('PR_NUM')
133-
mdFile = MdUtils(file_name='summary.md', title=f'Increased Warnings/Remarks for PR #{pr_num}')
134-
135-
for machine, results in dict.items():
136-
for category in results.keys():
137-
if results[category]:
138-
mdFile.write(f"\n<h3>{machine.upper()}</h3>\n")
139-
unordered_list = [f"**{category.title()}:**", []]
140-
for test, value in dict[machine][category].items():
141-
unordered_list[1].append(f"{test}: {value}")
142-
mdFile.new_list(unordered_list, marked_with='*')
143165
return mdFile.get_md_text()
144166

145167
def main():
146168
"""For each machine, create a log object, get current PR data, and determine
147169
which tests increase warnings and/or remarks on each machine."""
148170

149171
machines = os.environ.get('MACHINES').split()
172+
# Use non-capturing groups in pattern to indicate warnings/remarks may or may not be present.
173+
compile_pattern = r"COMPILE \'(.*)\' \[\d+:\d+, \d+:\d+\](?: \( (?:(\d+) warnings)?\s*(?:(\d+) remarks)? \))?"
174+
# failure_pattern = r"^(?:FAILED|SKIPPED): (?!UNABLE TO (?:COMPLETE COMPARISON|START TEST))(.+?) -- (?:TEST|COMPILE) '([^']+)"
175+
failure_pattern = r"^(?:FAILED|SKIPPED): (.+?) -- (?:TEST|COMPILE) '([^']+)"
176+
150177

151-
# For each machine, tests where warnings and/or remarks increase
178+
# For each machine, increased_warnings_remarks records where warnings and/or remarks increase
152179
increased_warnings_remarks = {}
180+
# For each machine, failures records which tests fail by reason
181+
failures = {}
153182

154183
for machine in machines:
155184
log = Log(machine)
156185
log._get_commits()
157-
log.pr_log_data = log._get_pr_data(log.pr_head_commit)
158-
log.base_log_data = log._get_pr_data(log.pr_base_commit)
159-
160-
increased_warnings_remarks[machine] = log.compare_results(log.pr_log_data, log.base_log_data)
186+
log.pr_log_text = log._fetch_log_text(log.pr_head_commit)
187+
log.pr_warn_rmk = log._get_data(log.pr_log_text, compile_pattern, log.handle_warn_rmk)
188+
log.pr_failures = log._get_data(log.pr_log_text, failure_pattern, log.handle_failures, to_clean=False)
161189

162-
results = print_html_results(increased_warnings_remarks)
190+
log.base_log_text = log._fetch_log_text(log.pr_base_commit)
191+
log.base_warn_rmk = log._get_data(log.base_log_text, compile_pattern, log.handle_warn_rmk)
192+
193+
increased_warnings_remarks[machine] = log.compare_results(log.pr_warn_rmk, log.base_warn_rmk)
194+
failures[machine] = log.pr_failures
163195

164-
if len(results) > 81: # Length of HTML header
165-
print(results)
196+
pr_num = os.environ.get('PR_NUM')
197+
warn_rmk_results = print_html_results(increased_warnings_remarks,
198+
f"Increased Warnings/Remarks for PR #{pr_num}",
199+
"warn_rmk.md")
200+
failure_results = print_html_results(failures,
201+
f"Compile and Test Failures for PR #{pr_num}",
202+
"failures.md")
203+
204+
if len(warn_rmk_results.splitlines()) > 3: # HTML header is 3 lines long
205+
print(warn_rmk_results)
206+
if len(failure_results.splitlines()) > 3:
207+
print(failure_results)
166208
sys.exit(1)
209+
elif len(failure_results.splitlines()) > 3:
210+
print(failure_results)
211+
sys.exit(0)
167212
else:
213+
print(f"No increase in warnings or remarks. All RTs passed.")
168214
sys.exit(0)
169215

170216
if __name__ == "__main__": # pragma: no coverage

.github/workflows/update_project_labels.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ jobs:
194194
- name: atmos_cubed_sphere
195195
if: contains(github.event.pull_request.labels.*.name, 'A3S')
196196
run: echo "SUBCOMPONENTS=${SUBCOMPONENTS} A3S" >> $GITHUB_ENV
197+
- name: MPAS
198+
if: contains(github.event.pull_request.labels.*.name, 'MPAS')
199+
run: echo "SUBCOMPONENTS=${SUBCOMPONENTS} MPAS" >> $GITHUB_ENV
197200
- name: UPP
198201
if: contains(github.event.pull_request.labels.*.name, 'UPP')
199202
run: echo "SUBCOMPONENTS=${SUBCOMPONENTS} UPP" >> $GITHUB_ENV
@@ -212,6 +215,12 @@ jobs:
212215
- name: CICE
213216
if: contains(github.event.pull_request.labels.*.name, 'CICE')
214217
run: echo "SUBCOMPONENTS=${SUBCOMPONENTS} CICE" >> $GITHUB_ENV
218+
- name: CECE
219+
if: contains(github.event.pull_request.labels.*.name, 'CECE')
220+
run: echo "SUBCOMPONENTS=${SUBCOMPONENTS} CECE" >> $GITHUB_ENV
221+
- name: CATChem
222+
if: contains(github.event.pull_request.labels.*.name, 'CATChem')
223+
run: echo "SUBCOMPONENTS=${SUBCOMPONENTS} CATChem" >> $GITHUB_ENV
215224
- name: GOCART
216225
if: contains(github.event.pull_request.labels.*.name, 'GOC')
217226
run: echo "SUBCOMPONENTS=${SUBCOMPONENTS} GOC" >> $GITHUB_ENV
@@ -230,6 +239,9 @@ jobs:
230239
- name: FB
231240
if: contains(github.event.pull_request.labels.*.name, 'FB')
232241
run: echo "SUBCOMPONENTS=${SUBCOMPONENTS} FB" >> $GITHUB_ENV
242+
- name: CCPP-sub
243+
if: contains(github.event.pull_request.labels.*.name, 'CCPP-sub')
244+
run: echo "SUBCOMPONENTS=${SUBCOMPONENTS} CCPP-sub" >> $GITHUB_ENV
233245
- name: Update subcomponents text
234246
if: ${{ env.SUBCOMPONENTS != '' }}
235247
uses: nipe0324/update-project-v2-item-field@v2.0.2

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,7 @@ tests/logs/log_*
8282
compile_0_time.log
8383
tests/modules.ufs_model.lua
8484
tests/ufs_common.lua
85+
86+
# Backup logs for local regression tests
87+
tests/logs/RegressionTests_*.log.bak
88+

CDEPS-interface/ufs/cdeps_share/shr_is_restart_fh_mod.F90

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module shr_is_restart_fh_mod
2424
contains
2525

2626
!-----------------------------------------------------------------------
27-
subroutine init_is_restart_fh(currentTime, dtime, lLog, restartfh_info)
27+
subroutine init_is_restart_fh(currentTime, dtime, lLog, restartfh_info, key)
2828
!
2929
! !DESCRIPTION:
3030
! Process restart_fh attribute from model_configure in UFS
@@ -36,30 +36,54 @@ subroutine init_is_restart_fh(currentTime, dtime, lLog, restartfh_info)
3636
integer, intent(in) :: dtime ! time step (s)
3737
logical, intent(in) :: lLog ! If true, this task logs restart_fh info
3838
type(is_restart_fh_type), intent(out) :: restartfh_info !restart_fh info for each task
39+
character(len=*),intent(in),optional :: key
3940
!
4041
! !LOCAL VARIABLES:
4142
character(len=256) :: timestr
42-
integer :: n, nfh, fh_s, rc
43+
integer :: n, nfh, fh_s, rc, nhours_fcst, ntimeout, fhi, ifh
4344
logical :: isPresent
4445
real(kind=ESMF_KIND_R8), allocatable :: restart_fh(:)
4546
type(ESMF_TimeInterval) :: fhInterval
4647
type(ESMF_Config) :: CF_mc
48+
character(len=64) :: key_name
4749
!-----------------------------------------------------------------------
4850

51+
! Default is to create restart times, but other "keys" could use this routine.
52+
if (present(key)) then
53+
key_name = trim(key)//':'
54+
else
55+
key_name = 'restart_fh:'
56+
endif
57+
4958
! set up Times to write non-interval restarts
5059
inquire(FILE='model_configure', EXIST=isPresent)
5160
if (isPresent) then !model_configure exists. this is ufs run
5261
CF_mc = ESMF_ConfigCreate(rc=rc)
5362
call ESMF_ConfigLoadFile(config=CF_mc,filename='model_configure' ,rc=rc)
5463
if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) return
64+
nfh = ESMF_ConfigGetLen(config=CF_mc, label=trim(key_name),rc=rc)
5565

56-
nfh = ESMF_ConfigGetLen(config=CF_mc, label ='restart_fh:',rc=rc)
5766
if (nfh .gt. 0) then
5867
allocate(restart_fh(1:nfh))
5968
allocate(restartfh_info%restartFhTimes(1:nfh)) !not deallocated here
60-
61-
call ESMF_ConfigGetAttribute(CF_mc,valueList=restart_fh,label='restart_fh:', rc=rc)
69+
call ESMF_ConfigGetAttribute(CF_mc,valueList=restart_fh,label=trim(key_name), rc=rc)
6270
if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) return
71+
72+
! Support case where restart_fh: "fh_interval -1"
73+
if (nfh == 2 .and. int(restart_fh(2)) == -1) then
74+
call ESMF_ConfigGetAttribute(CF_mc,value=nhours_fcst,label='nhours_fcst:', rc=rc)
75+
fhi = int(restart_fh(1))
76+
ntimeout = nhours_fcst/fhi
77+
nfh = ntimeout
78+
deallocate(restart_fh)
79+
deallocate(restartfh_info%restartFhTimes)
80+
allocate(restart_fh(1:nfh))
81+
allocate(restartfh_info%restartFhTimes(1:nfh))
82+
do ifh = 1,nfh
83+
restart_fh(ifh) = ifh*fhi
84+
end do
85+
endif
86+
6387
! create a list of times at each restart_fh
6488
do n = 1,nfh
6589
fh_s = NINT(3600*restart_fh(n))

0 commit comments

Comments
 (0)