subroutine summary(this, required_score, verbose, stop_fail)
use face, only: colorize
use iso_fortran_env, only: compiler_version, compiler_options
class(unit_tests), intent(inout) :: this
real, intent(in) :: required_score
integer, optional, intent(in) :: verbose
logical, optional, intent(in) :: stop_fail
integer :: i, j
integer :: n_passed, n_failed, n_groups, found_idx, bars
integer :: name_w, group_w, msg_w, passed_w, total_w, bar_col, status_w, id_w
logical :: do_test, do_group, do_stop
real :: score, rate
character(len=:), allocatable :: test_name, test_msg, comp_version, comp_options
character(len=64), allocatable :: groups(:)
character(len=64) :: grp
character(len=8) :: date_str
character(len=10) :: time_str
character(len=5) :: zone_str
character(len=6) :: status_color
character(len=100) :: fmt_string, hdr_line
character(len=5) :: success_label
character(len=9) :: bar_label
integer, allocatable :: grp_total(:), grp_pass(:)
character(len=*), parameter :: full_block = '█', light_block = '░'
! Determine verbosity
do_test = .false.
do_group = .true.
if (present(verbose)) then
select case (verbose)
case (0); do_test = .false.; do_group = .false.
case (1); do_test = .false.; do_group = .true.
case (2); do_test = .true.; do_group = .false.
case (3); do_test = .true.; do_group = .true.
end select
end if
do_stop = .false.; if (present(stop_fail)) do_stop = stop_fail
n_passed = count(this%test(:)%result)
n_failed = this%n - n_passed
score = merge(100.0 * real(n_passed) / real(max(1, this%n)), 0.0, this%n > 0)
call date_and_time(date=date_str, time=time_str, zone=zone_str)
comp_version = compiler_version()
comp_options = compiler_options()
! per-test summary
if (do_test) then
print '(A)', colorize("Per-test result summary:", color_fg='cyan')
id_w = 3
name_w = 4; group_w = 5; msg_w = 7; status_w = 6
do i = 1, this%n
name_w = max(name_w, len_trim(this%test(i)%name))
group_w = max(group_w, len_trim(this%test(i)%group))
msg_w = max(msg_w, len_trim(this%test(i)%msg))
end do
! Print header
print '(A)', 'ID' // repeat(' ', id_w - 2 + 2) // &
'NAME' // repeat(' ', name_w - 4 + 2) // &
'GROUP' // repeat(' ', group_w - 5 + 2) // &
'MESSAGE' // repeat(' ', msg_w - 7 + 2) // 'STATUS'
! Print separator line
print '(A)', repeat('-', id_w + name_w + group_w + msg_w + status_w + 8)
! Print each test line
do i = 1, this%n
write(fmt_string, '(A,A,I0,A,I0,A,I0,A,I0,A,I0,A)') '(', 'I', id_w, ',2X,A', name_w, ',2X,A', group_w, ',2X,A', msg_w, ',2X,A)'
grp = trim(this%test(i)%group); if (grp == '') grp = 'none'
test_name = adjustl(this%test(i)%name)
test_msg = adjustl(this%test(i)%msg)
status_color = merge('green', 'red ', this%test(i)%result)
print fmt_string, i, test_name, adjustl(grp), test_msg, &
colorize(merge('passed', 'failed', this%test(i)%result), color_fg=status_color)
end do
end if
! per-group summary
if (do_group) then
print '(A)', ''
print '(A)', colorize("Per-group test results:", color_fg='cyan')
allocate(groups(this%n), grp_total(this%n), grp_pass(this%n))
groups = ''; grp_total = 0; grp_pass = 0; n_groups = 0
do i = 1, this%n
grp = trim(this%test(i)%group); if (grp == '') grp = 'none'
found_idx = 0
do j = 1, n_groups
if (trim(grp) == trim(groups(j))) then
found_idx = j; exit
end if
end do
if (found_idx == 0) then
n_groups = n_groups + 1
groups(n_groups) = grp
grp_total(n_groups) = 1
if (this%test(i)%result) grp_pass(n_groups) = 1
else
grp_total(found_idx) = grp_total(found_idx) + 1
if (this%test(i)%result) grp_pass(found_idx) = grp_pass(found_idx) + 1
end if
end do
group_w = 5; passed_w = 6; total_w = 5
do i = 1, n_groups
group_w = max(group_w, len_trim(groups(i)))
passed_w = max(passed_w, len_trim(adjustl(itoa(grp_pass(i)))))
total_w = max(total_w, len_trim(adjustl(itoa(grp_total(i)))))
end do
write(fmt_string, '(A,I0,A,I0,A,I0,A)') &
'(A', group_w, ',2X,I', passed_w, ',2X,I', total_w, ',2X,F6.1," % ",A)'
success_label = 'SCORE'
bar_label = 'SCORE BAR'
hdr_line = 'GROUP' // repeat(' ', group_w - 5 + 2) // &
'PASSED' // repeat(' ', passed_w - 6 + 2) // &
'TOTAL' // repeat(' ', total_w - 5 + 3) // &
success_label
bar_col = group_w + 2 + passed_w + 2 + total_w + 2 + 11
hdr_line = trim(hdr_line) // repeat(' ', max(1, bar_col - len_trim(hdr_line))) // bar_label
print '(A)', hdr_line
print '(A)', repeat('-', len_trim(hdr_line)+1)
do i = 1, n_groups
rate = merge(100.0 * real(grp_pass(i)) / grp_total(i), 0.0, grp_total(i) > 0)
bars = min(10, int(rate / 10.0))
print fmt_string, adjustl(groups(i)), grp_pass(i), grp_total(i), rate, &
repeat(full_block, bars) // repeat(light_block, 10 - bars)
end do
deallocate(groups, grp_total, grp_pass)
end if
! Final summary
print '(A)', ''
print '(A)', colorize("================== Tests Summary ==================", color_fg='cyan')
print '(A,A)', 'Date: ', date_str
print '(A,A)', 'Time: ', time_str(1:2)//':'//time_str(3:4)//':'//time_str(5:6)
print '(A,A)', 'Compiler: ', trim(comp_version)
print '(A)', ''
print '(A,I5)', 'Total tests: ', this%n
print '(A,I5)', 'Passed tests: ', n_passed
print '(A,I5)', 'Failed tests: ', n_failed
print '(A)', ''
print '(A,F6.1,A)', 'Score: ', score, ' %'
print '(A,F6.1,A)', 'Required score: ', required_score, ' %'
bars = min(10, int(score / 10.0))
print '(A,A)', 'Score bar: ', repeat(full_block, bars) // repeat(light_block, 10 - bars)
if (score < required_score) then
print '(A)', colorize("FAILED: Required score not met.", color_fg='red')
print '(A)', ''
print '(A)', colorize("ForUnitTest - https://github.com/gha3mi/forunittest", color_fg='cyan')
print '(A)', colorize("===================================================", color_fg='cyan ')
if (do_stop) stop 1
else
print '(A)', colorize("PASSED: Required score met.", color_fg='green')
print '(A)', colorize("ForUnitTest - https://github.com/gha3mi/forunittest", color_fg='cyan')
print '(A)', colorize("===================================================", color_fg='cyan ')
end if
contains
pure function itoa(ii) result(str)
integer, intent(in) :: ii
character(len=32) :: str
write(str, '(I0)') ii
str = adjustl(str)
end function itoa
end subroutine summary