Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
195 commits
Select commit Hold shift + click to select a range
3183d80
add Jong's updated warnings files
gspetro-NOAA Sep 18, 2025
cb71806
experiment w/token
gspetro-NOAA Sep 19, 2025
20aaa17
Merge branch 'ufs-community:develop' into feature/log-warning
gspetro-NOAA Sep 19, 2025
4ad6b14
Replace icplocn2atm (integer) with use_oceanuv (logical); Update clou…
DeniseWorthen Sep 18, 2025
cbb8abd
add historical data
gspetro-NOAA Sep 20, 2025
11c61cd
Merge branch 'feature/log-warning' of github.com:gspetro-NOAA/ufs-wea…
gspetro-NOAA Sep 20, 2025
f20665e
generate historical data - functional but not pretty
gspetro-NOAA Sep 20, 2025
ac3c053
add machines - gaeac6 not working
gspetro-NOAA Sep 20, 2025
a1c2278
handle microseconds
gspetro-NOAA Sep 22, 2025
30ecbae
Merge branch 'ufs-community:develop' into feature/log-warning
gspetro-NOAA Sep 22, 2025
f8e779b
partially refactor parse_historical_data.py
gspetro-NOAA Sep 22, 2025
1e11c49
add action for retrieving and storigin historical mem/runtime data
gspetro-NOAA Sep 22, 2025
91d1351
Merge branch 'feature/log-warning' of github.com:gspetro-NOAA/ufs-wea…
gspetro-NOAA Sep 22, 2025
b5a2fa1
fix header formatting
gspetro-NOAA Sep 22, 2025
53e736c
fix typo
gspetro-NOAA Sep 22, 2025
611bf58
cancel in-progress jobs w/another push
gspetro-NOAA Sep 22, 2025
583e46c
fix env array syntax
gspetro-NOAA Sep 22, 2025
a59295b
fix api ref syntax
gspetro-NOAA Sep 23, 2025
544c989
fix api ref syntax
gspetro-NOAA Sep 23, 2025
4f2368c
upgrade to upload-artifact v4
gspetro-NOAA Sep 23, 2025
ed3bd04
rm json install
gspetro-NOAA Sep 23, 2025
51cc5ec
update path
gspetro-NOAA Sep 23, 2025
5494109
update file permissions
gspetro-NOAA Sep 23, 2025
deaab2a
update header
gspetro-NOAA Sep 23, 2025
a8d8574
add https://
gspetro-NOAA Sep 23, 2025
431f904
update header info
gspetro-NOAA Sep 23, 2025
6dabc32
fix function call
gspetro-NOAA Sep 23, 2025
fd90c39
fix machine names
gspetro-NOAA Sep 23, 2025
70a18ee
add github api base url
gspetro-NOAA Sep 23, 2025
5818878
fix api call?
gspetro-NOAA Sep 23, 2025
61644e2
fix api call?
gspetro-NOAA Sep 23, 2025
89ceb0c
update sha retrieval
gspetro-NOAA Sep 23, 2025
7b99485
troubleshooting
gspetro-NOAA Sep 23, 2025
ba89f18
troubleshooting
gspetro-NOAA Sep 23, 2025
78decae
fix authentication
gspetro-NOAA Sep 23, 2025
66e2aae
update token header
gspetro-NOAA Sep 23, 2025
8e9f6ed
comment out extra accept in header
gspetro-NOAA Sep 23, 2025
b09d2bc
add pr_target and update token info
gspetro-NOAA Sep 29, 2025
6b57f7b
fix token in ghd.py
gspetro-NOAA Sep 29, 2025
817232d
add file for diff
gspetro-NOAA Sep 29, 2025
2753fe5
fix string formatting
gspetro-NOAA Sep 29, 2025
b24557a
update artifact path
gspetro-NOAA Sep 29, 2025
1f86048
try with GITHUB_TOKEN
gspetro-NOAA Sep 29, 2025
af083f6
print stats dict, upload stats.json
gspetro-NOAA Sep 29, 2025
7df566d
fix path to json and round values
gspetro-NOAA Sep 29, 2025
d8b6443
add comparison logic for test results
gspetro-NOAA Sep 30, 2025
7f18dd0
add prerequisite jobs
gspetro-NOAA Sep 30, 2025
43d6a9f
update path
gspetro-NOAA Sep 30, 2025
8bd56e2
install dependencies
gspetro-NOAA Sep 30, 2025
522b869
update path
gspetro-NOAA Sep 30, 2025
0002ab0
add pass/fail emojis
gspetro-NOAA Oct 1, 2025
7514c8e
add back requests
gspetro-NOAA Oct 1, 2025
14a2a24
update jobs & create write_test_summary.py
gspetro-NOAA Oct 1, 2025
ea9f265
rm typo
gspetro-NOAA Oct 1, 2025
2999d8e
install dependencies
gspetro-NOAA Oct 1, 2025
4915674
fix json decoding; rm extraneous code
gspetro-NOAA Oct 1, 2025
ea24607
mv env var to correct step
gspetro-NOAA Oct 1, 2025
4f50beb
rm excess print statements
gspetro-NOAA Oct 1, 2025
015ff81
capitalize machine name
gspetro-NOAA Oct 1, 2025
a78e33b
print md text
gspetro-NOAA Oct 1, 2025
b953309
try to add details sections
gspetro-NOAA Oct 1, 2025
a4c13a2
fix section headings
gspetro-NOAA Oct 1, 2025
9335d11
reformat tables in each section
gspetro-NOAA Oct 1, 2025
43e13ca
only process 4 machines
gspetro-NOAA Oct 1, 2025
689bd80
print output to GHA
gspetro-NOAA Oct 1, 2025
1484fc0
generate markdown tables for all machines
gspetro-NOAA Oct 1, 2025
74d2743
attempt caching
gspetro-NOAA Oct 1, 2025
2b0bac9
fix caching
gspetro-NOAA Oct 1, 2025
f13f4a2
add quotes to file name
gspetro-NOAA Oct 1, 2025
d365855
update so anomolously fast results are passes
gspetro-NOAA Oct 1, 2025
a3d4fc4
output fails only
gspetro-NOAA Oct 2, 2025
e6b33dd
add back dictionary
gspetro-NOAA Oct 2, 2025
db10ba3
refactor rt and mem into separate functions
gspetro-NOAA Oct 2, 2025
1911578
use list instead of table for reporting
gspetro-NOAA Oct 2, 2025
939c4eb
add formatting
gspetro-NOAA Oct 2, 2025
435b652
fix headers?
gspetro-NOAA Oct 2, 2025
ca3436f
format headers
gspetro-NOAA Oct 2, 2025
aa2e12e
refactor hist data retrieval/processing
gspetro-NOAA Oct 2, 2025
5d47bf8
Merge branch 'ufs-community:develop' into feature/log-warning
gspetro-NOAA Oct 3, 2025
a1f70d9
rm parse_historical_data.py
gspetro-NOAA Oct 3, 2025
fa03589
rm date logic
gspetro-NOAA Oct 3, 2025
3b003b5
combine compare.py functionality into get_historical_data.py
gspetro-NOAA Oct 3, 2025
0582db0
rm date logic
gspetro-NOAA Oct 3, 2025
773dcbc
rm compare_logs.py
gspetro-NOAA Oct 3, 2025
bb11f1d
update GHA yaml
gspetro-NOAA Oct 6, 2025
3538374
fix dependency
gspetro-NOAA Oct 6, 2025
8df433f
fix file path
gspetro-NOAA Oct 6, 2025
f45c1be
refactor write-to-file functionality
gspetro-NOAA Oct 6, 2025
bb8896c
generalize file paths
gspetro-NOAA Oct 6, 2025
10084fe
rename file
gspetro-NOAA Oct 6, 2025
06f1d7d
chg mem to memory
gspetro-NOAA Oct 6, 2025
77872e0
fix formatting
gspetro-NOAA Oct 6, 2025
3b35b2a
fix formatting
gspetro-NOAA Oct 6, 2025
0b6d5e8
add key
gspetro-NOAA Oct 6, 2025
a2f6241
add warning category for one-time failures
gspetro-NOAA Oct 6, 2025
c8c1835
fix comparison
gspetro-NOAA Oct 6, 2025
211eaac
add key styling
gspetro-NOAA Oct 7, 2025
7c8c75e
fix formating for key
gspetro-NOAA Oct 7, 2025
4cc9cfa
fix formatting
gspetro-NOAA Oct 8, 2025
e20e1bf
rm accidental commit of in-progress get_data work
gspetro-NOAA Oct 8, 2025
4994e39
Merge branch 'ufs-community:develop' into feature/log-warning
gspetro-NOAA Oct 8, 2025
9f6a0b4
change comparison to HEAD of PR instead of HEAD of develop
gspetro-NOAA Oct 8, 2025
9c8896d
Merge branch 'feature/log-warning' of github.com:gspetro-NOAA/ufs-wea…
gspetro-NOAA Oct 8, 2025
77879b6
refactoring, caching
gspetro-NOAA Oct 10, 2025
abf01da
print only rows with warn/fail status
gspetro-NOAA Oct 10, 2025
e459489
print only failing rows
gspetro-NOAA Oct 10, 2025
8576ed4
add pass rates for each test
gspetro-NOAA Oct 13, 2025
ca0c3ba
add pass rates per machine
gspetro-NOAA Oct 13, 2025
f5ce83f
add machine name to column total
gspetro-NOAA Oct 13, 2025
c89c55f
format bottom total
gspetro-NOAA Oct 13, 2025
080e581
minor formatting updates
gspetro-NOAA Oct 13, 2025
9b3932a
add 'passing' to end of results
gspetro-NOAA Oct 13, 2025
6a12359
Merge branch 'feature/log-warning' into feature/tests-caching
gspetro-NOAA Oct 13, 2025
f04ddec
revise docstrings
gspetro-NOAA Oct 13, 2025
18d3b69
rm extraneous files and print statements
gspetro-NOAA Oct 13, 2025
213aa80
rm extraneous files
gspetro-NOAA Oct 13, 2025
9bc6019
attempt caching
gspetro-NOAA Oct 13, 2025
a4c9b87
add all machines
gspetro-NOAA Oct 13, 2025
7b43e2b
rm mkdir
gspetro-NOAA Oct 13, 2025
c565394
print for testing
gspetro-NOAA Oct 13, 2025
26e891b
minor refactoring/docs for get_data.py
gspetro-NOAA Oct 13, 2025
75952ec
attempt caching
gspetro-NOAA Oct 13, 2025
47846ee
fix mkdir cmd
gspetro-NOAA Oct 13, 2025
e561352
test caching
gspetro-NOAA Oct 13, 2025
7db1c16
change file mode from append to write
gspetro-NOAA Oct 13, 2025
7e3bbce
fix stat read from cache
gspetro-NOAA Oct 14, 2025
efab05b
test stats.json path
gspetro-NOAA Oct 14, 2025
10fc76c
test stats.json path
gspetro-NOAA Oct 14, 2025
09616ae
check if stats.json exists in path
gspetro-NOAA Oct 14, 2025
27a8fe5
reorder steps
gspetro-NOAA Oct 14, 2025
c7f74a1
debug path issue
gspetro-NOAA Oct 14, 2025
2ad5731
try new cache
gspetro-NOAA Oct 14, 2025
c1207b3
rm data dir
gspetro-NOAA Oct 14, 2025
8b99e62
rm data dir
gspetro-NOAA Oct 14, 2025
07b73c6
mkdir data
gspetro-NOAA Oct 14, 2025
cf9e42b
add back data dir
gspetro-NOAA Oct 14, 2025
d729af8
add ls
gspetro-NOAA Oct 14, 2025
0eed44f
adjust path
gspetro-NOAA Oct 14, 2025
eeae385
Merge branch 'develop' into feature/log-warning
gspetro-NOAA Oct 14, 2025
20ff28f
change hash to latest develop sha
gspetro-NOAA Oct 15, 2025
9e6871f
Merge branch 'feature/log-warning' of github.com:gspetro-NOAA/ufs-wea…
gspetro-NOAA Oct 15, 2025
0b82dbf
Merge branch 'develop' into feature/log-warning
gspetro-NOAA Oct 22, 2025
21d2d5a
Merge branch 'ufs-community:develop' into feature/tests-caching
gspetro-NOAA Oct 22, 2025
44a2332
update caching
gspetro-NOAA Oct 22, 2025
18cb7df
initial test format/outline
gspetro-NOAA Oct 22, 2025
cba2643
minor docstring and print updates
gspetro-NOAA Oct 22, 2025
3ead7d9
fix machine capitalization location
gspetro-NOAA Oct 27, 2025
c689833
add outline of test file and w/test data fixtures in conftest
gspetro-NOAA Oct 27, 2025
330be4f
add test_compile_historical_log_data and associated sample data
gspetro-NOAA Oct 27, 2025
31ce432
add check for commits
gspetro-NOAA Oct 28, 2025
419704a
fix mem compare bug, add logging
gspetro-NOAA Oct 29, 2025
49fa0e4
add hercules_mean_std fixture
gspetro-NOAA Oct 29, 2025
761b3b3
add passing file I/O tests
gspetro-NOAA Oct 30, 2025
b5ce1e1
fix mem_results bug, privatize methods, consolidate file I/O
gspetro-NOAA Oct 30, 2025
f358925
fix mem_results bug, privatize methods, consolidate file I/O
gspetro-NOAA Oct 30, 2025
fd1a067
Merge branch 'feature/log-warning' of github.com:gspetro-NOAA/ufs-wea…
gspetro-NOAA Oct 30, 2025
d38210e
add tests for write_test_summary.py
gspetro-NOAA Oct 31, 2025
a14b194
add test_build_content
gspetro-NOAA Nov 4, 2025
a97e6e8
Merge branch 'develop' of github.com:gspetro-NOAA/ufs-weather-model i…
gspetro-NOAA Nov 4, 2025
34a1f8f
full set of write_test_summary tests
gspetro-NOAA Nov 5, 2025
3876140
full set of initial tests
gspetro-NOAA Nov 6, 2025
b1e9953
add test stats.json file
gspetro-NOAA Nov 6, 2025
e71a5a2
clean up extraneous statements
gspetro-NOAA Nov 6, 2025
aa26df9
add test workflow
gspetro-NOAA Nov 6, 2025
7b1b98f
fix syntax
gspetro-NOAA Nov 6, 2025
f805122
add on: push option temporarily
gspetro-NOAA Nov 6, 2025
7dec7c6
on: workflow_dispatch only
gspetro-NOAA Nov 6, 2025
e97c92f
add checkout action
gspetro-NOAA Nov 6, 2025
95cbc0e
on: workflow_dispatch only
gspetro-NOAA Nov 6, 2025
2ec359d
add pytest dependency
gspetro-NOAA Nov 6, 2025
d3649d7
add scripts __init__.py file
gspetro-NOAA Nov 6, 2025
a22dd7f
add tests __init__.py
gspetro-NOAA Nov 6, 2025
3dfb100
publish outputs?
gspetro-NOAA Nov 6, 2025
8d8305f
add test runtime_results.json file
gspetro-NOAA Nov 6, 2025
f57143f
Merge branch 'ufs-community:develop' into feature/tests-caching
gspetro-NOAA Nov 7, 2025
109f2ec
add runtime_results.json in expected location
gspetro-NOAA Nov 7, 2025
b132532
rm data/runtime_results file
gspetro-NOAA Nov 7, 2025
d7069fa
publish output
gspetro-NOAA Nov 7, 2025
6aa829b
Merge branch 'ufs-community:develop' into feature/log-warning
gspetro-NOAA Nov 7, 2025
30d545b
add tests to GHA log-warning; sync develop
gspetro-NOAA Nov 7, 2025
acea05c
Merge branch 'feature/log-warning' of github.com:gspetro-NOAA/ufs-wea…
gspetro-NOAA Nov 7, 2025
c404b45
minor changes
gspetro-NOAA Nov 7, 2025
0a867e2
fix stdev calculation
gspetro-NOAA Nov 7, 2025
0fb1fd1
fix minor formatting
gspetro-NOAA Nov 7, 2025
2934812
uncomment assert; add cancel on concurrency
gspetro-NOAA Nov 7, 2025
450abf9
minor update, privatize some functions
gspetro-NOAA Nov 10, 2025
49910c2
format test output
gspetro-NOAA Nov 10, 2025
960c490
write paragraphs in HTML
gspetro-NOAA Nov 10, 2025
ff66117
fix formatting
gspetro-NOAA Nov 10, 2025
f519278
fix formatting
gspetro-NOAA Nov 10, 2025
5c0f95f
fix formatting
gspetro-NOAA Nov 10, 2025
5057be7
fix formatting
gspetro-NOAA Nov 10, 2025
8607082
fix formatting
gspetro-NOAA Nov 10, 2025
7a59533
add ursa logs and test_changes.list
gspetro-NOAA Nov 10, 2025
12c366e
Merge branch 'develop' into feature/log-warning
gspetro-NOAA Nov 12, 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
Empty file added .github/scripts/__init__.py
Empty file.
259 changes: 259 additions & 0 deletions .github/scripts/get_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import requests
import os
import json
from datetime import datetime
import re
import numpy as np
import logging

class APICall():
"""A GitHub API call"""

def __init__(self, endpoint='', num_commits=1):
self.token = os.environ.get('GITHUB_TOKEN')
self.base_url = os.environ.get('BASE_URL')
self.endpoint = endpoint
self.url = f"{self.base_url}/{self.endpoint}" #Could use a path join?
self.num_commits = num_commits
self.header = {
"Accept": "application/vnd.github.v3+json",
"Authorization": f"Bearer {self.token}",
"X-GitHub-Api-Version": "2022-11-28",
"Accept": "application/vnd.github.raw"
}

class Log():
"""A Regression Test log file."""

def __init__(self, machine):
"""Create the log file object for a specific machine."""
self.machine = machine.lower()
self.text_per_log = []

def call_API(self, endpoint):
"""Call the GitHub API to get information about the log file."""

api_call = APICall(endpoint)
response = requests.get(api_call.url, headers=api_call.header)
response = json.loads(response.text)

return response

def _get_pr_head(self):
"""Get SHA for the HEAD of the PR. Structure of response:
response = [{"head": {"sha": "a1b2c3d..."}}]
See GitHub documentation for https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits
"""
response = self.call_API(f"pulls/{os.environ.get('PR_NUM')}")
self.pr_head_commit = [response['head']['sha']]

def _fetch_repo_commits(self, num_commits=1):
"""Get a list of commits for the log file from the authoritative repository, with a maximum of 100 and a default of 1.
Structure of response: response = [{'sha': '3jl26ka...'}, {'sha': '6ag43sb...'}, ...]
See GitHub documentation for https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits
"""
response = self.call_API(f"commits?path=tests/logs/RegressionTests_{self.machine}.log&per_page={num_commits}")

self.repo_commits = []
for num in range(len(response)):
try:
self.repo_commits.append(response[num]['sha'])
except:
logging.error(f"API Call failed. The sha does not exist!")

def _fetch_log_text(self, commits):
"""For each commit of a log, extract the log text."""

try:
api_call = APICall(f"contents/tests/logs/RegressionTests_{self.machine}.log")

for num in range(len(commits)):
url = api_call.url + (f"?ref={commits[num]}") #Could use a path join?
r = requests.get(url, headers=api_call.header)
if commits == self.pr_head_commit:
# Ensure that the pr log text comes first
self.text_per_log.insert(0,r.text)
else:
self.text_per_log.append(r.text)
except:
logging.error("An appropriate commit(s) was not provided. Call _get_pr_head() or _fetch_repo_commits() first.")

def _get_instance_test_data(self, log_instance):
"""For each instance of a log at a given commit, extract runtime and memory data from the log text
Args:
log_instance: Log text for a given commit
Returns:
tests_for_log_instance: A dictionary of tests (keys) with an array of total runtime and memory use as the value for each test
"""

tests_for_log_instance = {}

pattern = r"TEST \'(.*)\' \[\d+:\d+, (\d+):(\d+)\]\((\d+) MB\)"
log_instance = log_instance.splitlines()

for line in log_instance:
test_match = re.search(pattern, line)
if test_match:
test_name, hh, mm, mem = test_match.groups()
total_minutes = int(hh) * 60 + int(mm)
tests_for_log_instance[test_name] = [total_minutes, int(mem)]

return tests_for_log_instance

def _compile_historical_log_data(self): # Could split for runtime, mem to make more maintainable
"""Create a dictionary of data with runtime and memory usage for each test over time. Structure:
historical_test_data = {
test: {runtime: [], memory: []}
}
"""

self.historical_rt_mem_data = {}

# Skip self.text_per_log[0] because it is the log from the PR
for log_instance in self.text_per_log[1:]:

data = self._get_instance_test_data(log_instance)
for test in data:
try:
self.historical_rt_mem_data[test]["runtime"].append(data[test][0])
self.historical_rt_mem_data[test]["memory"].append(data[test][1])
except KeyError:
logging.info("Test key doesn't exist yet. Creating test key.")
self.historical_rt_mem_data[test] = {"runtime": [data[test][0]], "memory": [data[test][1]]}

def calculate_stats(self):
"""For each test, calculate the mean and standard deviation of memory and runtime.
"""
self.test_stats = {}
for test in self.historical_rt_mem_data:
runtime_mean = round(np.mean(self.historical_rt_mem_data[test]["runtime"]), 5)
runtime_stdev = round(np.std(self.historical_rt_mem_data[test]["runtime"]), 5)
memory_mean = round(np.mean(self.historical_rt_mem_data[test]["memory"]), 5)
memory_stdev = round(np.std(self.historical_rt_mem_data[test]["memory"]), 5)
self.test_stats[test] = [runtime_mean, runtime_stdev, memory_mean, memory_stdev]

def _compare_runtime(self, current_log, previous_logs):
"""Determine whether the test runtime is within normal bounds."""

self.runtime_results = {}

for test in current_log:
try:
hi_rt = self.test_stats[test][0] + self.test_stats[test][1]
if current_log[test][0] > hi_rt and previous_logs['last'][test][0] > hi_rt and previous_logs['second_to_last'][test][0] > hi_rt:
self.runtime_results[test] = '❌'
elif current_log[test][0] > hi_rt:
self.runtime_results[test] = '⚠️'
else:
self.runtime_results[test] = '✅'
except KeyError:
logging.info(f"{test} is new. No comparison data.")
self.runtime_results[test] = 'New'

def _compare_memory(self, current_log, previous_logs):
"""Determine whether the test memory usage is within normal bounds."""

self.memory_results = {}

for test in current_log:
try:
hi_mem = self.test_stats[test][2] + self.test_stats[test][3]
if current_log[test][1] > hi_mem and previous_logs['last'][test][1] > hi_mem and previous_logs['second_to_last'][test][1] > hi_mem:
self.memory_results[test] = '❌'
elif current_log[test][1] > hi_mem:
self.memory_results[test] = '⚠️'
else:
self.memory_results[test] = '✅'
except KeyError:
logging.info(f"{test} is new. No comparison data.")
self.memory_results[test] = 'New'

def compare_results(self):
"""Check results from previous two commits to determine whether the test runtime/memory usage is within normal bounds."""

current_log = self._get_instance_test_data(self.text_per_log[0])
previous_logs = {"last" : {}, "second_to_last" : {}}

for index, item in enumerate(previous_logs):
previous_logs[item] = self._get_instance_test_data(self.text_per_log[index + 1])

self._compare_runtime(current_log, previous_logs)
self._compare_memory(current_log, previous_logs)

def get_current_pr_data(self):
"""Extract runtime/memory data for the PR's most recent commit."""

self._get_pr_head()
self._fetch_log_text(self.pr_head_commit)
pr_log_data = self._get_instance_test_data(self.text_per_log[0])

return pr_log_data

def gather_historical_data(self, num_commits=2):
"""Extract runtime/memory data for the authoritative repository's last two commits."""
self._fetch_repo_commits(num_commits) #increase for statistical significance
self._fetch_log_text(self.repo_commits)
self._compile_historical_log_data()

"""Utilities for file I/O"""

def create_json(dictionary, file_name):
"""Create a json file with statistics for each test on each machine"""

with open(f"data/{file_name}.json", 'w') as fh:
json.dump(dictionary, fh, indent=4)

def load_json(file_path):
"""Convert JSON file to python dictionary."""
with open(file_path, 'r', encoding='utf-8') as file:
data = json.load(file)

return data

def main():
"""For each machine, create a log object, get current PR data, gather historical runtime/memory data,
and compare results to determine which test/machine combinations fall more than 2 standard deviations
above the historical mean for each test."""

machines = os.environ.get('MACHINES').split()

# Contains mean and standard deviation for each test on each machine
stats_by_machine = {}
# Contains information on whether test runtime was more than 2 standard deviations above the mean.
runtime_results_by_machine = {}
# Contains information on whether test memory was more than 2 standard deviations above the mean.
mem_results_by_machine = {}

for machine in machines:
print(machine.upper())
log = Log(machine)
log.get_current_pr_data()
# Case where test stats have been calculated and cached:
if os.environ.get('TEST_STATS'):
log.gather_historical_data(2) # past two commits only
log.test_stats = load_json(os.environ.get('TEST_STATS'))[machine]

# Case where test stats have NOT been calculated and cached:
else:
log.gather_historical_data(50) # past 50 commits
log.calculate_stats()
stats_by_machine[machine] = log.test_stats # Add stats to save/cache later

# Compare and save results
log.compare_results()
runtime_results_by_machine[machine] = log.runtime_results
mem_results_by_machine[machine] = log.memory_results

# If the statistics on mean/standard deviation have NOT already been cached, create file to cache.
if not os.environ.get('TEST_STATS'):
create_json(stats_by_machine, "stats")

# Create resource summaries to use in write_test_summary.py
create_json(runtime_results_by_machine, "runtime_results")
create_json(mem_results_by_machine, "memory_results")

return 0

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

main()
136 changes: 136 additions & 0 deletions .github/scripts/write_test_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import os
import json
import re
from mdutils.mdutils import MdUtils
import pandas as pd

def load_json(file_path):
"""Convert JSON file to python dictionary."""
with open(file_path, 'r', encoding='utf-8') as file:
data = json.load(file)

return data

def create_mdFile():
"""Create a markdown file named summary.md with the PR# in the title."""
pr_num = os.environ.get('PR_NUM')
mdFile = MdUtils(file_name='summary.md', title=f'Test Summary for PR #{pr_num}')

return mdFile

def build_content(category):
"""Load the runtime or memory results dictionary, convert to dataframe, and return the results
Args:
category (str): "runtime" or "memory"
Returns:
results: DataFrame containing the runtime/memory testing results. Rows are tests and columns are machines.
"""

contents = load_json(os.environ.get(f"{category.upper()}_RESULTS"))
results = pd.DataFrame()

for machine in contents:

machine_results = pd.DataFrame.from_dict(contents[machine], orient='index', columns=[machine])
results = pd.merge(results, machine_results, left_index=True, right_index=True, how='outer').fillna("N/A")

results = _count_passes_per_test(results)
results = pd.concat([results, _count_passes_per_machine(results)])

return results

def write_content(data, mdFile):

machines = os.environ.get('MACHINES').split()

# Create contents list starting with header row
contents = ["Test"] + machines + ["Passing"]

# Create table starting with one row (header)
rows = 1
for index, row in data.iterrows():
warn = '⚠️'
fail = '❌'
# If there is a warn or fail in the row, add the row to contents to be printed; also add summary row
if (data.loc[index] == warn).any() or (data.loc[index] == fail).any() or (index == 'Platform Total (Passing):'):
rows += 1
contents.append(str(index))
for item in row:
contents.append(item)

mdFile.new_table(columns=(len(machines) + 2), rows=rows, text_align='center', text=contents)
mdFile.new_paragraph('\n')
mdFile.write('</details>')

return mdFile

def _count_passes_per_machine(data):
"""Counts number of passing tests on each machine and procudes a row with the totals.
Args:
data(DataFrame): Table of tests and pass/warn/fail status by machine
Returns:
machine_total(DataFrame): Number of tests passing per machine
"""

# Counts for passing tests
passing_tests_by_machine = data.eq('✅').sum(axis=0).astype(str) + '/' + data.ne('N/A').sum(axis=0).astype(str)
for machine in passing_tests_by_machine.index:
passing_tests_by_machine[machine] = f"**{machine.upper()}:** " + passing_tests_by_machine[machine] + " passing"
passing_tests_by_machine.name = 'Platform Total (Passing):'
# Set bottom right corner to empty string
passing_tests_by_machine.loc['Passing'] = ''
machine_total = pd.DataFrame(passing_tests_by_machine).T

return machine_total

def _count_passes_per_test(data):
"""Counts number of platforms on which a given test passes and adds a column to the table.
Args:
data (DataFrame): DataFrame containing pass/warn/fail status for each test on each machine
Returns:
data: with an extra column listing pass rates for each test
"""

passing_tests = data.eq('✅').sum(axis=1).astype(str) + "/" + data.ne('N/A').sum(axis=1).astype(str)
passing_tests.name = 'Passing'
data = pd.merge(data, pd.DataFrame(passing_tests), left_index=True, right_index=True, how='inner')

return data

def create_summary(categories):
"""Append a runtime or memory header and key and call write_contents() to write the runtime/memory table to the file.
Args:
categories (list): Test categories. Currently 'runtime' and 'memory'.
Returns:
mdFile: A markdown file
"""

mdFile = create_mdFile()

for category in categories:
# Create <details> section
mdFile.write(f"<details><summary><h3>{category.upper()} Results Summary</h3></summary>")
mdFile.new_paragraph('\n')
# Add key to section
mdFile.new_paragraph("<h4>Key:</h4>")
mdFile.new_paragraph(f"&nbsp;&nbsp;&nbsp;&nbsp;✅ = NORMAL {category}: {category.title()} falls within two standard deviations of the mean.")
mdFile.new_paragraph(f"&nbsp;&nbsp;&nbsp;&nbsp;⚠️ = {category.title()} WARNING: {category.title()} is greater than two standard deviations above the mean.")
mdFile.new_paragraph(f"&nbsp;&nbsp;&nbsp;&nbsp;❌ = {category.title()} FAIL: For the past 2+ PRs, {category} has been greater than two standard deviations above the mean.")
mdFile.new_paragraph(f"&nbsp;&nbsp;&nbsp;&nbsp;N/A = Test does not run on this machine.")
mdFile.new_paragraph('\n')
# Create a DataFrame w/the runtime/memory results content
data = build_content(category)

# Write the content to a file
mdFile = write_content(data, mdFile)

return mdFile

def main(): # pragma: no cover

summary = create_summary(['runtime', 'memory'])
print(summary.get_md_text())

if __name__ == "__main__": # pragma: no cover

main()
Empty file added .github/tests/__init__.py
Empty file.
Loading