Assert.f90 Source File


Source Code

!
!////////////////////////////////////////////////////////////////////////
!
! Assert.f90
! Created: February 21, 2013 2:34 PM 
! By: David Kopriva  
!
!> Assertions are functions that return true or false
!> that can be placed in a program to test whether
!> a predicate is true.
!>
!> To use the assertions module, it must be initialized,
!> usually in the main program. When it is no longer needed,
!> it is finalized. Assertions are posted to the module as they
!> are called, and can be summarized later at an appropriate time.
!>
!>### Initialization ###
!>
!>      CALL initializeSharedAssertionsManager
!>
!>### Finalization ###
!>
!>      CALL finalizeSharedAssertionsManager
!>
!>### Asserting ###
!>
!>      CALL FTAssertEqual(expectedValue,resultValue,message)
!>
!>### Summarizing Assertions ###
!>
!>      CALL SummarizeFTAssertions(title,unit)
!>
!>### Additional enquiry functions ###
!>
!>      INTEGER :: nf, nA
!>       nF = numberOfAssertionFailures()
!>       nA = numberOfAssertions()
!
!
!////////////////////////////////////////////////////////////////////////
!
      Module FTAssertions
      USE ComparisonsModule
      USE FTOLConstants
      USE ISO_FORTRAN_ENV
      IMPLICIT NONE
      PRIVATE
!
!     ------
!     Public
!     ------
!
      INTEGER, PARAMETER, PUBLIC :: FT_ASSERTION_STRING_LENGTH = 128
      
      TYPE FTAssertionsManager
         PRIVATE
          
         INTEGER                                 :: numberOfTests_
         INTEGER                                 :: numberOfAssertionFailures_
         TYPE(FTAssertionFailureRecord), POINTER :: failureListHead => NULL()
         TYPE(FTAssertionFailureRecord), POINTER :: failureListTail => NULL()
!
!        ========
         CONTAINS
!        ========
!         
         PROCEDURE, PUBLIC  :: init
         PROCEDURE, PUBLIC  :: finalize
         PROCEDURE, PUBLIC  :: numberOfAssertionFailures
         PROCEDURE, PUBLIC  :: numberOfAssertions
         PROCEDURE, PUBLIC  :: summarizeAssertions
         
      END TYPE FTAssertionsManager
      
      PUBLIC :: FTAssertionsManager
      
      INTERFACE FTAssertEqual
         MODULE PROCEDURE assertEqualTwoIntegers
         MODULE PROCEDURE assertEqualTwoIntegerArrays1D
         MODULE PROCEDURE assertEqualTwoIntegerArrays2D
         MODULE PROCEDURE assertWithinToleranceTwoReal
         MODULE PROCEDURE assertWithinToleranceTwoRealArrays1D
         MODULE PROCEDURE assertWithinToleranceTwoRealArrays2D
         MODULE PROCEDURE assertWithinToleranceTwoDouble
         MODULE PROCEDURE assertWithinToleranceTwoDoubleArrays1D
         MODULE PROCEDURE assertWithinToleranceTwoDoubleArrays2D
#ifdef _has_Quad
         MODULE PROCEDURE assertWithinToleranceTwoQuad
#endif
         MODULE PROCEDURE assertEqualTwoLogicals
         MODULE PROCEDURE assertEqualString
      END INTERFACE FTAssertEqual
      
      PUBLIC :: FTAssertEqual
#ifdef _has_Quad
      PUBLIC :: assertWithinToleranceTwoQuad
#endif
      PUBLIC :: initializeSharedAssertionsManager, finalizeSharedAssertionsManager
      PUBLIC :: FTAssert, sharedAssertionsManager, numberOfAssertionFailures, numberOfAssertions
      PUBLIC :: detachSharedAssertionsManager
!
!     -------
!     Private
!     -------
!
      TYPE FTAssertionFailureRecord
         CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: msg, expected, actual
         CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: assertionType
         TYPE(FTAssertionFailureRecord), POINTER   :: next
      END TYPE FTAssertionFailureRecord
!
!     -------------------------
!     Shared Assertions manager
!     -------------------------
!
      TYPE(FTAssertionsManager), POINTER, PRIVATE  :: sharedManager
!
!     ========      
      CONTAINS
!     ========
!@mark -
!
!//////////////////////////////////////////////////////////////////////// 
! 
      FUNCTION sharedAssertionsManager()
         IMPLICIT NONE  
         TYPE(FTAssertionsManager), POINTER :: sharedAssertionsManager
         sharedAssertionsManager => sharedManager 
      END FUNCTION sharedAssertionsManager
! 
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE detachSharedAssertionsManager 
         IMPLICIT NONE
!
!     --------------------------------------------------------------------------
!     To create a new sharedAssertionsManager, 
!     call this procedure after storing a pointer to the sharedAssertionsManager
!     and before initializing again.
!     --------------------------------------------------------------------------
!
         sharedManager => NULL()
      END SUBROUTINE detachSharedAssertionsManager
! 
!//////////////////////////////////////////////////////////////////////// 
! 
      INTEGER FUNCTION numberOfAssertions(self) 
         IMPLICIT NONE  
         CLASS(FTAssertionsManager) :: self
         numberOfAssertions = self % numberOfTests_
      END FUNCTION numberOfAssertions
! 
!//////////////////////////////////////////////////////////////////////// 
! 
      INTEGER FUNCTION numberOfAssertionFailures(self) 
         IMPLICIT NONE  
         CLASS(FTAssertionsManager) :: self
         numberOfAssertionFailures = self % numberOfAssertionFailures_
      END FUNCTION numberOfAssertionFailures
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE init(self)  
         IMPLICIT NONE
         CLASS(FTAssertionsManager) :: self
         
         self % numberOfTests_             = 0
         self % numberOfAssertionFailures_ = 0
         NULLIFY(self % failureListHead, self % failureListTail)
         
      END SUBROUTINE init
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE finalize(self)
         IMPLICIT NONE
         CLASS(FTAssertionsManager)              :: self
         TYPE(FTAssertionFailureRecord), POINTER :: tmp, current
         
         IF ( .NOT.ASSOCIATED(self % failureListHead) ) RETURN 
!
!        ------------------------------
!        Delete linked list of failures
!        ------------------------------
!
         current => self % failureListHead
         DO WHILE (ASSOCIATED(tmp))
            tmp => current % next
            DEALLOCATE(current)
            current => tmp
         END DO
         
         self % numberOfTests_    = 0
         self % numberOfAssertionFailures_ = 0
         NULLIFY(self % failureListHead, self % failureListTail)
        
      END SUBROUTINE finalize    
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE initializeSharedAssertionsManager  
         IMPLICIT NONE
!
!        --------------------------------------------------
!        The manager is allowed only once to be initialized
!        per run.
!        --------------------------------------------------
!
         IF ( ASSOCIATED(sharedManager) )     RETURN 
         
         ALLOCATE(sharedManager)
         CALL sharedManager % init()
         
      END SUBROUTINE initializeSharedAssertionsManager
! 
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE finalizeSharedAssertionsManager 
         IMPLICIT NONE
         
         IF(ASSOCIATED(sharedManager)) CALL sharedManager % finalize()
         
      END SUBROUTINE finalizeSharedAssertionsManager
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE addAssertionFailureForParameters(msg, expected, actual, assertionType)
         IMPLICIT NONE  
         CHARACTER(LEN=*)                        :: msg, expected, actual, assertionType
         TYPE(FTAssertionFailureRecord), POINTER :: newFailure
         
         ALLOCATE(newFailure)
         newFailure % msg           = TRIM(msg)
         newFailure % expected      = TRIM(ADJUSTL(expected))
         newFailure % actual        = TRIM(ADJUSTL(actual))
         newFailure % assertionType = assertionType
         newFailure % next          => NULL()
         
         IF ( ASSOCIATED(sharedManager % failureListTail) )     THEN
            sharedManager % failureListTail % next => newFailure
            sharedManager % failureListTail        => sharedManager % failureListTail % next
         ELSE
            sharedManager % failureListHead => newFailure
            sharedManager % failureListTail => newFailure
         END IF 
         
         sharedManager % numberOfAssertionFailures_ = sharedManager % numberOfAssertionFailures_ + 1
         
      END SUBROUTINE addAssertionFailureForParameters
!
!//////////////////////////////////////////////////////////////////////// 
! 
     SUBROUTINE summarizeAssertions(self,title,iUnit)  
        IMPLICIT NONE
        CLASS(FTAssertionsManager)              :: self
        CHARACTER(LEN=*)                        :: title
        INTEGER                                 :: iUnit
        TYPE(FTAssertionFailureRecord), POINTER :: current
        
        WRITE(iUnit,*)
        WRITE(iUnit,*) "   -------------------------------------------------------------"
        WRITE(iUnit,*) "   Summary of failed tests for test suite: ",TRIM(title)
        WRITE(iUnit,'(3x,i3,A,i5,A)')  self % numberOfAssertionFailures()," failures out of ", &
                              self % numberOfAssertions()," assertions." 
        WRITE(iUnit,*) "   -------------------------------------------------------------"
                  
         current => self % failureListHead
         DO WHILE (ASSOCIATED(current))
            WRITE(iUnit,*) "   ",TRIM(current % assertionType)
            WRITE(iUnit,*) "      ",TRIM(current % msg)
            WRITE(iUnit,*) "      ","Expected [",TRIM(current % expected),&
                                           "], Got [",TRIM(current % actual),"]"
            current => current % next
         END DO
         
         WRITE(iUnit,*)
         
     END SUBROUTINE summarizeAssertions    

!@mark -
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE FTAssert(test,msg)  
         IMPLICIT NONE
         CHARACTER(LEN=*), OPTIONAL :: msg
         LOGICAL                    :: test
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
        sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
        IF ( .NOT.test )     THEN
            IF ( PRESENT(msg) )     THEN
               CALL addAssertionFailureForParameters(msg,"True","False","Logical assertion failed: ")
            ELSE 
               CALL addAssertionFailureForParameters("","True","False","Logical assertion failed: ")
            END IF 
         END IF 
         
      END SUBROUTINE FTAssert      
!@mark -
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertEqualTwoIntegers(expectedValue,actualValue,msg)  
         IMPLICIT NONE  
         INTEGER, INTENT(in)        :: expectedValue,actualValue
         CHARACTER(LEN=*), OPTIONAL :: msg

         CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: expected,actual
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
        sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.isEqual(expectedValue,actualValue) )     THEN
            WRITE(expected,*) expectedValue
            WRITE(actual,*) actualValue
            IF ( PRESENT(msg) )     THEN
               CALL addAssertionFailureForParameters(msg,expected,actual,"Integer equality failed: ")
            ELSE 
               CALL addAssertionFailureForParameters("",expected,actual,"Integer equality failed: ")
            END IF 
         END IF 
         
      END SUBROUTINE assertEqualTwoIntegers    
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertEqualTwoIntegerArrays1D(expectedValue,actualValue)  
         IMPLICIT NONE  
         INTEGER, INTENT(in)    , DIMENSION(:)     :: expectedValue,actualValue
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
         sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.isEqual(expectedValue,actualValue) )     THEN
             
             PRINT *, "assertEqualTwoIntegerArrays1D not implemented"
         END IF 
         
      END SUBROUTINE assertEqualTwoIntegerArrays1D
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertEqualTwoIntegerArrays2D(expectedValue,actualValue)  
         IMPLICIT NONE  
         INTEGER, INTENT(in)    , DIMENSION(:,:)          :: expectedValue,actualValue
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
         sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.isEqual(expectedValue,actualValue) )     THEN
             PRINT *, "assertEqualTwoIntegerArrays2D not implemented"
         END IF 
         
      END SUBROUTINE assertEqualTwoIntegerArrays2D
!@mark -
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertWithinToleranceTwoReal(expectedValue,actualValue,tol,msg)  
         IMPLICIT NONE  
         REAL, INTENT(in)           :: expectedValue,actualValue,tol
         CHARACTER(LEN=*), OPTIONAL :: msg

         CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: expectedS,actualS
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
         sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.isEqual(expectedValue,actualValue,tol) )     THEN
            WRITE(expectedS,*) expectedValue
            WRITE(actualS,*) actualValue
            IF ( PRESENT(msg) )     THEN
               CALL addAssertionFailureForParameters(msg,expectedS,actualS,"Real equality failed: ")
            ELSE 
               CALL addAssertionFailureForParameters("",expectedS,actualS,"Real equality failed: ")
            END IF 
         END IF 
         
      END SUBROUTINE assertWithinToleranceTwoReal    
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertWithinToleranceTwoRealArrays1D(expectedValue,actualValue,tol,msg)  
         IMPLICIT NONE  
         REAL, INTENT(IN), DIMENSION(:) :: expectedValue,actualValue
         REAL, INTENT(IN)               :: tol
         CHARACTER(LEN=*), OPTIONAL     :: msg
         INTEGER                        :: k
         
         CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: expected,actual
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
         sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.isEqual(expectedValue,actualValue,tol) )     THEN
            DO k = 1, SIZE(expectedValue)
               WRITE(expected,*) expectedValue(k)
               WRITE(actual,*)   actualValue(k)
               IF ( PRESENT(msg) )     THEN
                  CALL addAssertionFailureForParameters(msg,expected,actual,"Real Array equality failed: ")
               ELSE 
                  CALL addAssertionFailureForParameters("",expected,actual,"Real Array equality failed: ")
               END IF 
            END DO  
         END IF 
         
      END SUBROUTINE assertWithinToleranceTwoRealArrays1D
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertWithinToleranceTwoRealArrays2D(expectedValue,actualValue,tol)  
         IMPLICIT NONE  
         REAL, INTENT(IN), DIMENSION(:,:) :: expectedValue,actualValue
         REAL, INTENT(IN)                 :: tol
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
         sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.isEqual(expectedValue,actualValue,tol) )     THEN
             PRINT *, "assertWithinToleranceTwoRealArrays2D not implemented"
         END IF 
         
      END SUBROUTINE assertWithinToleranceTwoRealArrays2D
!@mark -
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertWithinToleranceTwoDouble(expectedValue,actualValue,tol,msg)  
         IMPLICIT NONE  
         DOUBLE PRECISION, INTENT(in) :: expectedValue,actualValue,tol
         CHARACTER(LEN=*), OPTIONAL   :: msg

         CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: expected,actual
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
         sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.isEqual(expectedValue,actualValue,tol) )     THEN
            WRITE(expected,*) expectedValue
            WRITE(actual,*) actualValue
            IF ( PRESENT(msg) )     THEN
               CALL addAssertionFailureForParameters(msg,expected,actual, "Double Precision equality failed: ")
            ELSE 
               CALL addAssertionFailureForParameters("",expected,actual, "Double Precision equality failed: ")
            END IF 
         END IF 
         
      END SUBROUTINE assertWithinToleranceTwoDouble    
 
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertWithinToleranceTwoDoubleArrays1D(expectedValue,actualValue,tol,msg)  
         IMPLICIT NONE  
         DOUBLE PRECISION, INTENT(IN), DIMENSION(:) :: expectedValue,actualValue
         DOUBLE PRECISION, INTENT(IN)               :: tol
         CHARACTER(LEN=*), OPTIONAL                 :: msg
         INTEGER                                    :: code
         INTEGER                                    :: k
         
         CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: expected,actual,eMsg
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
         sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.isEqual(expectedValue,actualValue,tol,code) )     THEN
            IF ( PRESENT(msg) )     THEN
               eMsg = TRIM(msg) // "---" // TRIM(compareCodeStrings(code))
            ELSE 
               eMsg = "---" // TRIM(compareCodeStrings(code))
            END IF 
            
            DO k = 1, SIZE(expectedValue)
               WRITE(expected,*) expectedValue(k)
               WRITE(actual,*)   actualValue(k)
               CALL addAssertionFailureForParameters(eMsg,expected,actual,"Double Precision 1D Array equality failed: ")
            END DO  
         END IF 
         
      END SUBROUTINE assertWithinToleranceTwoDoubleArrays1D
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertWithinToleranceTwoDoubleArrays2D(expectedValue,actualValue,tol)  
         IMPLICIT NONE  
         DOUBLE PRECISION, INTENT(IN), DIMENSION(:,:) :: expectedValue,actualValue
         DOUBLE PRECISION, INTENT(IN)                 :: tol
         INTEGER                         :: code
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
         sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.isEqual(expectedValue,actualValue,tol,code) )     THEN
             PRINT *, "assertWithinToleranceTwoDoubleArrays2D not implemented"
        END IF 
         
      END SUBROUTINE assertWithinToleranceTwoDoubleArrays2D
!@mark -
#ifdef _has_Quad
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertWithinToleranceTwoQuad(expectedValue,actualValue,tol,msg)  
         IMPLICIT NONE  
         REAL(KIND=SELECTED_REAL_KIND(QUAD_DIGITS)), INTENT(in) :: expectedValue,actualValue,tol
         CHARACTER(LEN=*)  , OPTIONAL   :: msg

         CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: expectedS,actualS
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
         sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.isEqual(expectedValue,actualValue,tol) )     THEN
            WRITE(expectedS,*) expectedValue
            WRITE(actualS,*) actualValue
            IF ( PRESENT(msg) )     THEN
               CALL addAssertionFailureForParameters(msg,expectedS,actualS,"Quad equality failed: ")
            ELSE 
               CALL addAssertionFailureForParameters("",expectedS,actualS,"Quad equality failed: ")
            END IF 
         END IF 
         
      END SUBROUTINE assertWithinToleranceTwoQuad    
#endif
!@mark -
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertEqualString(expectedValue,actualValue,msg)
         IMPLICIT NONE
         CHARACTER(LEN=*)           :: expectedValue,actualValue
         CHARACTER(LEN=*), OPTIONAL :: msg
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
         sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.isEqual(expectedValue,actualValue) )     THEN
            IF ( PRESENT(msg) )     THEN
               CALL addAssertionFailureForParameters(msg,expectedValue,actualValue,"String equality failed: ")
            ELSE 
               CALL addAssertionFailureForParameters("",expectedValue,actualValue,"String equality failed: ")
            END IF 
         END IF 
         
      END SUBROUTINE assertEqualString
!@mark -
!
!//////////////////////////////////////////////////////////////////////// 
! 
      SUBROUTINE assertEqualTwoLogicals(expectedValue,actualValue,msg)  
         IMPLICIT NONE  
         LOGICAL, INTENT(in)        :: expectedValue,actualValue
         CHARACTER(LEN=*), OPTIONAL :: msg

         CHARACTER(LEN=FT_ASSERTION_STRING_LENGTH) :: expected,actual
         
         IF(.NOT.ASSOCIATED(sharedManager)) THEN
            CALL initializeSharedAssertionsManager
         END IF 
         
         sharedManager % numberOfTests_ = sharedManager % numberOfTests_ + 1
         IF ( .NOT.(expectedValue .EQV. actualValue) )     THEN
            WRITE(expected,*) expectedValue
            WRITE(actual,*)   actualValue
            IF ( PRESENT(msg) )     THEN
               CALL addAssertionFailureForParameters(msg,expected,actual,"Logical equality failed: ")
            ELSE 
               CALL addAssertionFailureForParameters(msg,expected,actual,"Logical equality failed: ")
            END IF 
         END IF 
         
      END SUBROUTINE assertEqualTwoLogicals    
       
      END Module FTAssertions