Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial pFUnit Integration #326

Open
wants to merge 30 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
65251c3
Initial unit test integration.
mwaxmonsky Nov 26, 2024
70f89a3
Changing workflow name.
mwaxmonsky Nov 26, 2024
5be4565
Changing action order.
mwaxmonsky Nov 26, 2024
aabf503
Fixing workflow file name.
mwaxmonsky Nov 26, 2024
3b00619
Updating pfunit path.
mwaxmonsky Nov 26, 2024
caffef0
Adding code coverage and test results artifact uploads.
mwaxmonsky Nov 26, 2024
235e10a
Adding test to source list and fixing compile errors.
mwaxmonsky Nov 27, 2024
1dfc844
Add additional demo tests.
mwaxmonsky Nov 29, 2024
275445c
Adding workflow dispatch to python tests.
mwaxmonsky Dec 4, 2024
b5252cd
Removing debug printing from workflow.
mwaxmonsky Dec 4, 2024
698b3bc
Adding warning about top level builds not supported.
mwaxmonsky Dec 4, 2024
a62cb23
Fixing whitespace.
mwaxmonsky Dec 4, 2024
4125fca
Updating fortran test paths from unit-fortran to unit_fortran.
mwaxmonsky Dec 4, 2024
24ddbfb
Move python tests to unit_python.
mwaxmonsky Dec 4, 2024
b600560
Merge branch 'development' into feature/pfunit-integration
mwaxmonsky Dec 5, 2024
fbd77ac
Moving fortran tests from unit_fortran to unit/fortran
mwaxmonsky Dec 5, 2024
c30bdf1
Moving python tests from unit_python to unit/python
mwaxmonsky Dec 5, 2024
d3d0d9d
Fixing build path.
mwaxmonsky Dec 5, 2024
c28c89f
Adding workflow_dispatch to if check for python unit tests.
mwaxmonsky Dec 5, 2024
2f9b484
Fixing python test path.
mwaxmonsky Dec 5, 2024
ff36e21
Fixing path to root of repository.
mwaxmonsky Dec 5, 2024
cd5b5e4
More python unit test path fixes.
mwaxmonsky Dec 5, 2024
4201e18
One more path fix.
mwaxmonsky Dec 5, 2024
58a8d2d
One more path fix.
mwaxmonsky Dec 5, 2024
d090ff3
One more path fix.
mwaxmonsky Dec 5, 2024
5c08bd8
Fixing tmp file path.
mwaxmonsky Dec 5, 2024
0fdebc8
Fixing tmp file path.
mwaxmonsky Dec 5, 2024
d882fb7
Adding top level comment to core lib cmake.
mwaxmonsky Dec 10, 2024
c5fe6cd
Renaming python test script.
mwaxmonsky Dec 10, 2024
f59b7d0
Adding missing newlines.
mwaxmonsky Dec 16, 2024
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
66 changes: 66 additions & 0 deletions .github/workflows/fortran_unit_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Fortran Unit Tests

on:
push:
branches:
- development
- main
pull_request:
workflow_dispatch:
nusbaume marked this conversation as resolved.
Show resolved Hide resolved

concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true

jobs:
gcc-toolchain:
runs-on: ubuntu-latest
steps:
- name: Checkout cam-sima
uses: actions/checkout@v4

- name: Build pFUnit
run: |
git clone --depth 1 --branch v4.10.0 https://github.com/Goddard-Fortran-Ecosystem/pFUnit.git
cd pFUnit
cmake -B./build -S.
cd build
make install

- name: Build cam-sima
run: |
cmake \
-DCMAKE_PREFIX_PATH=/home/runner/work/CAM-SIMA/CAM-SIMA/pFUnit/build/installed \
-DCAM_SIMA_ENABLE_CODE_COVERAGE=ON \
-B./build \
-S./test/unit/fortran
cd build
make

- name: Run fortran unit tests
run: |
cd build && ctest -V --output-on-failure --output-junit test_results.xml

- name: Upload unit test results
uses: actions/upload-artifact@v4
with:
name: unit-test-results
path: build/test_results.xml

- name: Setup GCov
run: |
python3 -m venv venv
source venv/bin/activate
pip3 install gcovr

- name: Run Gcov
run: |
source venv/bin/activate
cd build
gcovr -r .. --filter '\.\./src' --html cam_sima_code_coverage.html --txt

- name: Upload code coverage results
uses: actions/upload-artifact@v4
with:
name: code-coverage-results
path: build/cam_sima_code_coverage.html
5 changes: 3 additions & 2 deletions .github/workflows/python_unit_tests.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Python Unit Tests

on:
workflow_dispatch:
pull_request:
types: [opened, synchronize, reopened]
push:
Expand All @@ -16,7 +17,7 @@ jobs:
#a PR is either opened or synced (i.e. additional commits are pushed
#to branch involved in PR).
python_unit_tests:
if: github.event_name == 'pull_request' || github.repository == 'ESCOMP/CAM-SIMA'
if: github.event_name == 'pull_request' || github.repository == 'ESCOMP/CAM-SIMA' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
strategy:
matrix:
Expand Down Expand Up @@ -56,4 +57,4 @@ jobs:
pytest src/data --doctest-modules

# Run all python unit tests:
pytest test/unit
pytest test/unit/python
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildnmlc
# Ignore test output
test/include/*.mod
test/include/*.o
test/unit/tmp
test/unit/python/tmp
test/system/*.log
test/system/cime-tests.o*
test_driver_*.sh
Expand Down
2 changes: 1 addition & 1 deletion cime_config/cam_autogen.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def _find_scheme_source(source_dirs, metadata_file_name):
doctests:

1. Check that the function can correctly find source and namelist files:
>>> _find_scheme_source([os.path.join(_CAM_ROOT_DIR, "test", "unit", "sample_files", \
>>> _find_scheme_source([os.path.join(_CAM_ROOT_DIR, "test", "unit", "python", "sample_files", \
"autogen_files")], "two_scheme_banana") # doctest: +ELLIPSIS
('...two_scheme_banana.F90', '...two_scheme_banana_namelist.xml')

Expand Down
9 changes: 9 additions & 0 deletions src/core_utils/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
set(CORE_UTILS_SRC string_core_utils.F90)
nusbaume marked this conversation as resolved.
Show resolved Hide resolved

# core_utils is not integrated into the CMake build of any top level
# project yet and this CMake is for testing purposes only.
# Making a change to this project's CMake will not impact the build of
# a parent project at this time.
add_library(core_utils ${CORE_UTILS_SRC})
target_compile_options(core_utils PRIVATE -ffree-line-length-none)
target_include_directories(core_utils PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
163 changes: 163 additions & 0 deletions src/core_utils/string_core_utils.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
module string_core_utils

implicit none
private

public :: to_str ! Convert integer to left justified string
public :: int_date_to_yyyymmdd ! Convert encoded date integer to "yyyy-mm-dd" format
public :: int_seconds_to_hhmmss ! Convert integer seconds past midnight to "hh:mm:ss" format
public :: stringify ! Convert one or more values of any intrinsic data types to a character string for pretty printing

CONTAINS

character(len=10) pure function to_str(n)
! return default integer as a left justified string

! arguments
integer, intent(in) :: n
!----------------------------------------------------------------------------

write(to_str,'(i0)') n

end function to_str

character(len=10) pure function int_date_to_yyyymmdd (date)
! Undefined behavior if date <= 0

! Input arguments
integer, intent(in) :: date

! Local variables
integer :: year ! year of yyyy-mm-dd
integer :: month ! month of yyyy-mm-dd
integer :: day ! day of yyyy-mm-dd

year = date / 10000
month = (date - year*10000) / 100
day = date - year*10000 - month*100

write(int_date_to_yyyymmdd, '(i4.4,A,i2.2,A,i2.2)') &
year,'-',month,'-',day

end function int_date_to_yyyymmdd

character(len=8) pure function int_seconds_to_hhmmss (seconds)
! Undefined behavior if seconds outside [0, 86400]

! Input arguments
integer, intent(in) :: seconds

! Local variables
integer :: hours ! hours of hh:mm:ss
integer :: minutes ! minutes of hh:mm:ss
integer :: secs ! seconds of hh:mm:ss

hours = seconds / 3600
minutes = (seconds - hours*3600) / 60
secs = (seconds - hours*3600 - minutes*60)

write(int_seconds_to_hhmmss,'(i2.2,A,i2.2,A,i2.2)') &
hours,':',minutes,':',secs

end function int_seconds_to_hhmmss

!> Convert one or more values of any intrinsic data types to a character string for pretty printing.
!> If `value` contains more than one element, the elements will be stringified, delimited by `separator`, then concatenated.
!> If `value` contains exactly one element, the element will be stringified without using `separator`.
!> If `value` contains zero element or is of unsupported data types, an empty character string is produced.
!> If `separator` is not supplied, it defaults to `, ` (i.e., a comma and a space).
!> (KCW, 2024-02-04)
pure function stringify(value, separator)
use, intrinsic :: iso_fortran_env, only: int32, int64, real32, real64

class(*), intent(in) :: value(:)
character(*), optional, intent(in) :: separator
character(:), allocatable :: stringify

integer, parameter :: sizelimit = 1024

character(:), allocatable :: buffer, delimiter, format
integer :: i, n, offset

if (present(separator)) then
delimiter = separator
else
delimiter = ', '
end if

n = min(size(value), sizelimit)

if (n == 0) then
stringify = ''
return
end if

select type (value)
type is (character(*))
allocate(character(len(value) * n + len(delimiter) * (n - 1)) :: buffer)

buffer(:) = ''
offset = 0

do i = 1, n
if (len(delimiter) > 0 .and. i > 1) then
buffer(offset + 1:offset + len(delimiter)) = delimiter
offset = offset + len(delimiter)
end if

if (len_trim(adjustl(value(i))) > 0) then
buffer(offset + 1:offset + len_trim(adjustl(value(i)))) = trim(adjustl(value(i)))
offset = offset + len_trim(adjustl(value(i)))
end if
end do
type is (integer(int32))
allocate(character(11 * n + len(delimiter) * (n - 1)) :: buffer)
allocate(character(17 + len(delimiter) + floor(log10(real(n))) + 1) :: format)

write(format, '(a, i0, 3a)') '(ss, ', n, '(i0, :, "', delimiter, '"))'
write(buffer, format) value
type is (integer(int64))
allocate(character(20 * n + len(delimiter) * (n - 1)) :: buffer)
allocate(character(17 + len(delimiter) + floor(log10(real(n))) + 1) :: format)

write(format, '(a, i0, 3a)') '(ss, ', n, '(i0, :, "', delimiter, '"))'
write(buffer, format) value
type is (logical)
allocate(character(1 * n + len(delimiter) * (n - 1)) :: buffer)
allocate(character(13 + len(delimiter) + floor(log10(real(n))) + 1) :: format)

write(format, '(a, i0, 3a)') '(', n, '(l1, :, "', delimiter, '"))'
write(buffer, format) value
type is (real(real32))
allocate(character(13 * n + len(delimiter) * (n - 1)) :: buffer)

if (maxval(abs(value)) < 1.0e5_real32) then
allocate(character(20 + len(delimiter) + floor(log10(real(n))) + 1) :: format)
write(format, '(a, i0, 3a)') '(ss, ', n, '(f13.6, :, "', delimiter, '"))'
else
allocate(character(23 + len(delimiter) + floor(log10(real(n))) + 1) :: format)
write(format, '(a, i0, 3a)') '(ss, ', n, '(es13.6e2, :, "', delimiter, '"))'
end if

write(buffer, format) value
type is (real(real64))
allocate(character(13 * n + len(delimiter) * (n - 1)) :: buffer)

if (maxval(abs(value)) < 1.0e5_real64) then
allocate(character(20 + len(delimiter) + floor(log10(real(n))) + 1) :: format)
write(format, '(a, i0, 3a)') '(ss, ', n, '(f13.6, :, "', delimiter, '"))'
else
allocate(character(23 + len(delimiter) + floor(log10(real(n))) + 1) :: format)
write(format, '(a, i0, 3a)') '(ss, ', n, '(es13.6e2, :, "', delimiter, '"))'
end if

write(buffer, format) value
class default
stringify = ''
return
end select

stringify = trim(buffer)
end function stringify

end module string_core_utils
Loading