RegressionTests__VMSpawningTestCase.st
author Jan Vrany <jan.vrany@labware.com>
Mon, 08 Mar 2021 11:25:35 +0000
branchjv
changeset 2594 e5f39c0a5bd6
parent 2082 05e1892aadee
permissions -rw-r--r--
Improve UTF8 read/write tests in `ChangeSetTests`

"
 COPYRIGHT (c) Claus Gittinger / eXept Software AG
 COPYRIGHT (c) 2017-2018 Jan Vrany
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
"{ Package: 'stx:goodies/regression' }"

"{ NameSpace: RegressionTests }"

TestCase subclass:#VMSpawningTestCase
	instanceVariableNames:''
	classVariableNames:'EXIT_CODE_SUCCESS EXIT_CODE_FAILURE EXIT_CODE_ERROR
		EXIT_CODE_SKIPPED'
	poolDictionaries:''
	category:'tests-Regression-Abstract'
!

!VMSpawningTestCase class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) Claus Gittinger / eXept Software AG
 COPYRIGHT (c) 2017-2018 Jan Vrany
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
! !

!VMSpawningTestCase class methodsFor:'initialization'!

initialize
    "Invoked at system start or when the class is dynamically loaded."

    "/ please change as required (and remove this comment)

    EXIT_CODE_SUCCESS := 0.
    EXIT_CODE_FAILURE := 1.
    EXIT_CODE_ERROR := 2.
    "/ Never define EXIT_CODE_SKIPPED as 3. On Windows,
    "/ 3 is used by abort() so then we'd not be able to
    "/ tell between skip and crash!! Sigh.
    EXIT_CODE_SKIPPED := 97.

    "Modified: / 03-09-2016 / 08:23:11 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 08-09-2016 / 12:44:05 / jv"
! !

!VMSpawningTestCase class methodsFor:'queries'!

isAbstract
    "Return if this class is an abstract class.
     True is returned here for myself only; false for subclasses.
     Abstract subclasses must redefine this again."

    ^ self == RegressionTests::VMSpawningTestCase.
! !

!VMSpawningTestCase methodsFor:'private'!

spawnSelector:selector 
    "Perform selector in freshly spawned Smalltalk."
    
    | tempDir |

    [
        tempDir := Filename newTemporary.
        tempDir makeDirectory.
        self spawnSelector:selector inDirectory:tempDir.
    ] ensure:[
        (tempDir notNil and:[ tempDir exists ]) ifTrue:[
            [
                tempDir recursiveRemove.
            ] on:Error
                    do:[:ex | 
                OperatingSystem isMSWINDOWSlike ifFalse:[
                    ex reject.
                ].
            ]
        ].
    ].

    "Created: / 05-01-2017 / 23:08:38 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 06-01-2017 / 22:07:27 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

spawnSelector:selector inDirectory:directory 
    "Perform `selector` in new smalltalk process. Set new process's working directory to `directory`"
    
    | testcaseFile script |

    directory makeDirectory.
    testcaseFile := directory 
            / ((Smalltalk fileNameForClass:self class) , '.st').
    self class fileOutAs:testcaseFile.
    script := 'NoHandlerError emergencyHandler:[:ex |
                    ex suspendedContext fullPrintAllOn: Stdout.
                    Stdout nextPutAll: ''ERROR (unhandled) '', ex printString.
                    Smalltalk exit: %7
               ].
               Smalltalk packagePath: %1.
               Smalltalk loadPackage:%2.
               Smalltalk fileIn: %3.
               STCCompilerInterface verbose: true.
               Smalltalk addStartBlock:[
                   ParserFlags stcModulePath: Filename currentDirectory.
                   ParserFlags stcKeepCIntermediate: true.
                   ParserFlags stcKeepSTIntermediate: true.  
                   Processor exitWhenNoMoreUserProcesses: false.                   
                   [
                       (%4 selector: %5) spawnSelectorInternal: %6.                   
                   ] ensure:[
                       Smalltalk exit: %7
                   ]
               ].
               ' 
            bindWith:Smalltalk packagePath asArray storeString
            with:self class package storeString
            with:testcaseFile pathName storeString
            with:self class name
            with:testSelector storeString
            with:selector storeString
            with:EXIT_CODE_ERROR storeString.
    (directory / 'run.st') writingFileDo:[:f | f nextPutAll:script. ].
    self spawnSmalltalk: { '--abortOnSEGV'. '-I'. '--quick'. '--load'. (directory / 'run.st') pathName } inDirectory: directory

    "Created: / 06-01-2017 / 22:06:37 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 04-02-2019 / 17:57:53 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

spawnSelectorInternal:selector 
    [
        [
            self perform:selector.
            Stdout
                cr;
                nextPutAll:'PASSED';
                cr.
            Smalltalk exit:EXIT_CODE_SUCCESS
        ] on:TestResult skipped
                do:[:skip | 
            Stdout
                cr;
                nextPutAll:'SKIPPED';
                cr.
            Smalltalk exit:EXIT_CODE_SKIPPED.
        ]
    ] on:TestResult failure
            do:[:failure | 
        Stdout
            cr;
            nextPutAll:'FAILURE: ';
            nextPutAll:failure description;
            cr.
        Smalltalk exit:EXIT_CODE_FAILURE.
    ]
            on:TestResult exError
            do:[:error | 
        Stdout
            cr;
            nextPutAll:'ERROR: ';
            nextPutAll:error description;
            cr.
        Smalltalk exit:EXIT_CODE_ERROR.
    ].

    "Created: / 05-01-2017 / 23:02:29 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

spawnSmalltalk:argv
    | tempDir |
    [
        tempDir := Filename newTemporary.
        tempDir makeDirectory.
        self spawnSmalltalk:argv inDirectory:tempDir.
    ] ensure:[
        (tempDir notNil and:[ tempDir exists ]) ifTrue:[
            [
                tempDir recursiveRemove.
            ] on:Error
                    do:[:ex | 
                OperatingSystem isMSWINDOWSlike ifFalse:[
                    ex reject.
                ].
            ]
        ].
    ].

    "Created: / 17-04-2018 / 15:56:06 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

spawnSmalltalk:argv inDirectory:directory 
    "
     A helper method to spawn a new smalltalk process using current executable and
     given arguments (in `argv`). Set initial working copy of freskly spawned process
     to `directory`. Wait until spawned smalltalk finishes and then if process exit status

      * is EXIT_CODE_SUCCESS do nothing and return
      * is EXIT_CODE_SKIPPED then signal skipped test by means of #skipIf:description:
      * is EXIT_CODE_FAILURE then signal test failure by means of failed #assert:
      * is anything else then signal test error by means of #error:"
    
    | exe  args  environment  outputFile  output  pid  blocker  status |

    exe := OperatingSystem pathOfSTXExecutable.
    args := { exe } , #('--abortOnSEGV') , argv , #('--no-preferences').
    OperatingSystem isMSWINDOWSlike ifTrue:[
        args := String 
                streamContents:[:s | 
                    args 
                        do:[:each | 
                            s
                                nextPut:$";
                                nextPutAll:each;
                                nextPut:$"
                        ]
                        separatedBy:[ s space ]
                ]
    ].
    outputFile := directory / 'output.txt'.
    output := outputFile writeStream.
    environment := OperatingSystem isUNIXlike ifTrue:[
            OperatingSystem getEnvironment copy
        ] ifFalse:[
            environment := Dictionary new
        ].
    blocker := Semaphore new.

    "/ Write a little shell / .cmd script to re-execute the test.
    "/ This script is not used here, but provided for convenience
    "/ when debugging tests (using GDB or alike)
    OperatingSystem isMSWINDOWSlike  ifTrue:[
        (directory / 'run.cmd') writingFileDo:[ :s |
            s nextPutAll: args
        ]
    ] ifFalse:[ 
        (directory / 'run.sh') writingFileDo:[ :s |
            args do:[:e | s nextPut: $"; nextPutAll: e; nextPut: $" ]
                separatedBy: [ s space ]
        ].
    ].

    Processor 
        monitor:[
            pid := OperatingSystem 
                    exec:exe
                    withArguments:args
                    environment:environment
                    fileDescriptors:{
                            0.
                            output fileDescriptor.
                            output fileDescriptor
                        }
                    fork:true
                    newPgrp:false
                    inDirectory:directory pathName
                    showWindow:true
        ]
        action:[:s | 
            status := s.
            blocker signal.
        ].
    output close.
    pid isNil ifTrue:[
        self error:'Failed to spawn test'.
        ^ self.
    ].
    [
        blocker wait.
        status code == EXIT_CODE_SUCCESS ifFalse:[
            status code == EXIT_CODE_SKIPPED ifTrue:[
                self skipIf:true description:'Skipped'.
            ] ifFalse:[
                status code == EXIT_CODE_FAILURE ifTrue:[
                    "
                    (directory / 'output.txt') contents asString
                    "
                    self writeFile: outputFile to: Stdout labeled: 'TEST FAILED'.
                    self assert:false description:'Assertion failed, see log'.
                ] ifFalse:[
                    "
                    directory inspect
                    "
                    self writeFile: outputFile to: Stdout labeled: 'TEST ERROR'.                 
                    self error:'Error occured'.
                ].
            ].
        ].
    ] on: TerminateProcessRequest do:[:ex|
        pid notNil ifTrue:[ OperatingSystem killProcess: pid ].
        self writeFile: outputFile to: Stdout labeled: 'TEST TERMINATED'.
        ex pass.
    ].

    "Created: / 06-01-2017 / 11:25:04 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified: / 02-01-2018 / 16:26:09 / jv"
    "Modified: / 04-02-2019 / 20:17:06 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

writeFile: aFilename to: aStream labeled: aString
    "
    Write contents of given `aFilename` to given `aStream` with given label (`aString`).
    Utility method for spawnSmalltalk:inDirectory: to ease the debugging.
    "
    aStream
            nextPutAll: '== ';
            nextPutAll: aString;
            nextPutAll: ' (testcase ';
            nextPutAll: self printString;
            nextPutLine:') =='.
    aFilename isNil ifTrue:[
        aStream nextPutLine: 'No file given!!'
    ] ifFalse:[ aFilename exists ifFalse:[
        aStream nextPutAll: 'File does not exist: '; nextPutLine: aFilename pathName
    ] ifTrue:[
        aFilename readingFileDo:[:s | 
            [ s atEnd ] whileFalse:[ aStream nextPutLine:s nextLine.].
        ].
    ]].

    "Created: / 28-07-2017 / 10:48:53 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!VMSpawningTestCase class methodsFor:'documentation'!

version_HG

    ^ '$Changeset: <not expanded> $'
! !


VMSpawningTestCase initialize!