! fortplot_validation.f90 - Functional validation utilities for output verification module fortplot_validation use, intrinsic :: iso_fortran_env, only: wp => real64 implicit none public :: validate_file_exists public :: validate_file_size public :: validate_png_format public :: validate_pdf_format public :: validate_ascii_format public :: compare_with_baseline public :: validation_result_t ! Validation result type for structured reporting type :: validation_result_t logical :: passed character(len=256) :: message real(wp) :: metric_value end type ! Minimum file size thresholds (bytes) integer, parameter :: MIN_PNG_SIZE = 100 integer, parameter :: MIN_PDF_SIZE = 200 integer, parameter :: MIN_ASCII_SIZE = 50 ! Baseline comparison tolerance (10% size difference) real(wp), parameter :: BASELINE_TOLERANCE = 0.1_wp contains ! Given: A file path to validate ! When: Checking if the file exists ! Then: Return validation result with existence status function validate_file_exists(file_path) result(validation) character(len=*), intent(in) :: file_path type(validation_result_t) :: validation logical :: file_exists inquire(file=file_path, exist=file_exists) validation%passed = file_exists if (file_exists) then validation%message = "File exists: " // trim(file_path) else validation%message = "File missing: " // trim(file_path) end if validation%metric_value = 0.0_wp end function ! Given: A file path to validate ! When: Checking file size against minimum thresholds ! Then: Return validation result with size information function validate_file_size(file_path, min_size) result(validation) character(len=*), intent(in) :: file_path integer, intent(in) :: min_size type(validation_result_t) :: validation integer :: file_size, ios, file_unit open(newunit=file_unit, file=file_path, access='stream', iostat=ios) if (ios /= 0) then validation%passed = .false. validation%message = "Cannot open file for size check: " // trim(file_path) validation%metric_value = 0.0_wp return end if inquire(unit=file_unit, size=file_size) close(file_unit) validation%passed = file_size >= min_size validation%metric_value = real(file_size, wp) if (validation%passed) then write(validation%message, '(a,i0,a,i0,a)') & "File size valid: ", file_size, " bytes (min: ", min_size, ")" else write(validation%message, '(a,i0,a,i0,a)') & "File too small: ", file_size, " bytes (min: ", min_size, ")" end if end function ! Given: A PNG file path ! When: Validating PNG format signature ! Then: Return validation result for PNG format compliance function validate_png_format(file_path) result(validation) character(len=*), intent(in) :: file_path type(validation_result_t) :: validation integer, parameter :: PNG_SIGNATURE_SIZE = 8 character(len=1), dimension(PNG_SIGNATURE_SIZE) :: signature character(len=1), dimension(PNG_SIGNATURE_SIZE), parameter :: & PNG_SIGNATURE = [achar(137), 'P', 'N', 'G', achar(13), achar(10), achar(26), achar(10)] integer :: i, ios, file_unit open(newunit=file_unit, file=file_path, access='stream', form='unformatted', iostat=ios) if (ios /= 0) then validation%passed = .false. validation%message = "Cannot open PNG file: " // trim(file_path) validation%metric_value = 0.0_wp return end if read(file_unit, iostat=ios) signature close(file_unit) if (ios /= 0) then validation%passed = .false. validation%message = "Cannot read PNG signature" validation%metric_value = 0.0_wp return end if validation%passed = .true. do i = 1, PNG_SIGNATURE_SIZE if (signature(i) /= PNG_SIGNATURE(i)) then validation%passed = .false. exit end if end do if (validation%passed) then validation%message = "Valid PNG format signature" else validation%message = "Invalid PNG format signature" end if validation%metric_value = 1.0_wp end function ! Given: A PDF file path ! When: Validating PDF format signature ! Then: Return validation result for PDF format compliance function validate_pdf_format(file_path) result(validation) character(len=*), intent(in) :: file_path type(validation_result_t) :: validation character(len=5) :: pdf_header integer :: ios, file_unit open(newunit=file_unit, file=file_path, iostat=ios) if (ios /= 0) then validation%passed = .false. validation%message = "Cannot open PDF file: " // trim(file_path) validation%metric_value = 0.0_wp return end if read(file_unit, '(a5)', iostat=ios) pdf_header close(file_unit) if (ios /= 0) then validation%passed = .false. validation%message = "Cannot read PDF header" validation%metric_value = 0.0_wp return end if validation%passed = (pdf_header == '%PDF-') if (validation%passed) then validation%message = "Valid PDF format header" else validation%message = "Invalid PDF format header: " // pdf_header end if validation%metric_value = 1.0_wp end function ! Given: An ASCII file path ! When: Validating ASCII format content ! Then: Return validation result for ASCII format compliance function validate_ascii_format(file_path) result(validation) character(len=*), intent(in) :: file_path type(validation_result_t) :: validation character(len=256) :: line integer :: ios, line_count, file_unit open(newunit=file_unit, file=file_path, iostat=ios) if (ios /= 0) then validation%passed = .false. validation%message = "Cannot open ASCII file: " // trim(file_path) validation%metric_value = 0.0_wp return end if line_count = 0 do read(file_unit, '(a)', iostat=ios) line if (ios /= 0) exit line_count = line_count + 1 end do close(file_unit) validation%passed = line_count > 0 validation%metric_value = real(line_count, wp) if (validation%passed) then write(validation%message, '(a,i0,a)') "Valid ASCII content (", line_count, " lines)" else validation%message = "Empty or invalid ASCII content" end if end function ! Given: Current and baseline file paths ! When: Comparing files for regression detection ! Then: Return validation result for comparison function compare_with_baseline(current_file, baseline_file) result(validation) character(len=*), intent(in) :: current_file, baseline_file type(validation_result_t) :: validation integer :: current_size, baseline_size, ios ! Check if baseline exists inquire(file=baseline_file, exist=validation%passed, size=baseline_size) if (.not. validation%passed) then validation%message = "Baseline file missing: " // trim(baseline_file) validation%metric_value = 0.0_wp return end if ! Get current file size inquire(file=current_file, exist=validation%passed, size=current_size) if (.not. validation%passed) then validation%message = "Current file missing: " // trim(current_file) validation%metric_value = 0.0_wp return end if ! Compare sizes (simple regression check) validation%metric_value = abs(real(current_size - baseline_size, wp) / real(baseline_size, wp)) validation%passed = validation%metric_value < BASELINE_TOLERANCE if (validation%passed) then write(validation%message, '(a,f6.2,a)') "Size difference within tolerance (", & validation%metric_value * 100.0_wp, "%)" else write(validation%message, '(a,f6.2,a)') "Size difference exceeds tolerance (", & validation%metric_value * 100.0_wp, "%)" end if end function end module fortplot_validation