Squeak 4.6: SUnitGUI-mt.62.mcz

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

Squeak 4.6: SUnitGUI-mt.62.mcz

commits-2
Chris Muller uploaded a new version of SUnitGUI to project Squeak 4.6:
http://source.squeak.org/squeak46/SUnitGUI-mt.62.mcz

==================== Summary ====================

Name: SUnitGUI-mt.62
Author: mt
Time: 10 May 2015, 12:18:33.708 pm
UUID: 23278a70-3219-2542-bdb0-1e42b35989a6
Ancestors: SUnitGUI-topa.61

Upper spacing of button bar in test runner fixed by removing some magic numbers.

==================== Snapshot ====================

SystemOrganization addCategory: #SUnitGUI!

ProtoObject subclass: #TestCoverage
        instanceVariableNames: 'hasRun reference method'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'SUnitGUI'!

----- Method: TestCoverage class>>on: (in category 'instance creation') -----
on: aMethodReference
        ^ self new initializeOn: aMethodReference!

----- Method: TestCoverage>>doesNotUnderstand: (in category 'private') -----
doesNotUnderstand: aMessage
        ^ method perform: aMessage selector withArguments: aMessage arguments!

----- Method: TestCoverage>>flushCache (in category 'private') -----
flushCache!

----- Method: TestCoverage>>hasRun (in category 'testing') -----
hasRun
        ^ hasRun!

----- Method: TestCoverage>>initializeOn: (in category 'initialization') -----
initializeOn: aMethodReference
        hasRun := false.
        reference := aMethodReference.
        method := reference compiledMethod!

----- Method: TestCoverage>>install (in category 'actions') -----
install
        reference actualClass methodDictionary
                at: reference methodSymbol
                put: self!

----- Method: TestCoverage>>mark (in category 'private') -----
mark
        hasRun := true!

----- Method: TestCoverage>>reference (in category 'private') -----
reference
        ^ reference!

----- Method: TestCoverage>>run:with:in: (in category 'evaluation') -----
run: aSelector with: anArray in: aReceiver
        self mark; uninstall.
        ^ aReceiver withArgs: anArray executeMethod: method!

----- Method: TestCoverage>>uninstall (in category 'actions') -----
uninstall
        reference actualClass methodDictionary
                at: reference methodSymbol
                put: method!

----- Method: TestCase class>>packageNamesUnderTest (in category '*sunitgui') -----
packageNamesUnderTest
        "Answer a collection of package names under test. This is used by the test runner to automatically instrument the code in these packages when checking for test coverage."
       
        ^ #()!

Object subclass: #TestRunner
        instanceVariableNames: 'categories categoriesSelected classes classIndex classesSelected failedList failedSelected errorList errorSelected lastUpdate result previousRun categoryPattern classPattern'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'SUnitGUI'!

!TestRunner commentStamp: '<historical>' prior: 0!
<lint: #ignore rule: #classNotReferenced rational: 'this view is only accessed from menus'>

!

----- Method: TestRunner class>>build (in category 'instance-creation') -----
build
        ^ ToolBuilder build: self new.!

----- Method: TestRunner class>>initialize (in category 'initialization') -----
initialize
        self registerInWorldMenu; registerInToolsFlap.!

----- Method: TestRunner class>>open (in category 'instance-creation') -----
open
        ^ ToolBuilder open: self new.!

----- Method: TestRunner class>>registerInToolsFlap (in category 'initialization') -----
registerInToolsFlap
        self environment at: #Flaps ifPresent: [ :class |
                class
                        registerQuad: #( TestRunner build 'SUnit Runner' 'A production scale test-runner.' ) forFlapNamed: 'Tools';
                        replaceToolsFlap ].!

----- Method: TestRunner class>>registerInWorldMenu (in category 'initialization') -----
registerInWorldMenu
        self environment at: #TheWorldMenu ifPresent: [ :class |
                class registerOpenCommand: (Array
                        with: 'Test Runner'
                        with: (Array
                                with: self
                                with: #open)) ].!

----- Method: TestRunner class>>windowColorSpecification (in category 'window color') -----
windowColorSpecification

        ^ WindowColorSpec
                classSymbol: self name
                wording: 'Test Runner'
                brightColor:  Color orange
                pastelColor: (Color r: 0.65 g: 0.753 b: 0.976)
                helpMessage: 'The Camp Smalltalk TestRunner tool for SUnit'!

----- Method: TestRunner>>addDeclaredPackagesUnderTestTo: (in category 'actions') -----
addDeclaredPackagesUnderTestTo: packages
        classesSelected do:
                [ :class |
                (class class includesSelector: #packageNamesUnderTest) ifTrue:
                        [ class packageNamesUnderTest do: [ :name | packages add: (PackageInfo named: name) ] ] ]!

----- Method: TestRunner>>addMethodsUnderTestIn:to: (in category 'actions') -----
addMethodsUnderTestIn: packages to: methods
        packages
                do: [:package | package isNil
                                ifFalse: [package methods
                                                do: [:method | ((#(#packageNamesUnderTest #classNamesNotUnderTest ) includes: method methodSymbol)
                                                                        or: [method compiledMethod isAbstract
                                                                                        or: [method compiledMethod refersToLiteral: #ignoreForCoverage]])
                                                                ifFalse: [methods add: method]]]]!

----- Method: TestRunner>>baseClass (in category 'accessing') -----
baseClass
        ^ TestCase!

----- Method: TestRunner>>basicRunSuite:do: (in category 'processing') -----
basicRunSuite: aTestSuite do: aBlock

        self basicSetUpSuite: aTestSuite.
        [
                | prefix |
                prefix := aTestSuite name isEmptyOrNil
                        ifTrue: [ '' ]
                        ifFalse: [ aTestSuite name, ' - ' ].
                aTestSuite tests
                        do: aBlock
                        displayingProgress: [ :test | prefix, test printString ]
                        every: 0 "Update the label for all tests" ]
                ensure: [ self basicTearDownSuite: aTestSuite ].
        !

----- Method: TestRunner>>basicSetUpSuite: (in category 'processing') -----
basicSetUpSuite: aTestSuite
        aTestSuite resources do: [ :each |
                each isAvailable
                        ifFalse: [ each signalInitializationError ] ].!

----- Method: TestRunner>>basicTearDownSuite: (in category 'processing') -----
basicTearDownSuite: aTestSuite
        aTestSuite resources do: [ :each | each reset ].!

----- Method: TestRunner>>browseClass (in category 'accessing-classes') -----
browseClass
        (classes at: classIndex ifAbsent: [ ^ self ]) browse!

----- Method: TestRunner>>browserEnvironment (in category 'private') -----
browserEnvironment
        ^ Smalltalk classNamed: #BrowserEnvironment.!

----- Method: TestRunner>>buildButtonsWith: (in category 'building') -----
buildButtonsWith: aBuilder
        ^ aBuilder pluggablePanelSpec new
                model: self;
                layout: #horizontal;
                children: (self buttons collect: [ :each |
                        aBuilder pluggableButtonSpec new
                                model: self;
                                label: each first;
                                action: each second;
                                enabled: each third;
                                yourself ]);
                yourself.!

----- Method: TestRunner>>buildCategoriesWith: (in category 'building') -----
buildCategoriesWith: aBuilder
        ^ aBuilder pluggableMultiSelectionListSpec new
                model: self;
                list: #categoryList;
                menu: #categoryMenu:;
                getIndex: #categorySelected;
                setIndex: #categorySelected:;
                getSelectionList: #categoryAt:;
                setSelectionList: #categoryAt:put:;
                yourself.!

----- Method: TestRunner>>buildClassesWith: (in category 'building') -----
buildClassesWith: aBuilder
        ^ aBuilder pluggableMultiSelectionListSpec new
                model: self;
                list: #classList;
                menu: #classMenu:;
                getIndex: #classSelected;
                setIndex: #classSelected:;
                getSelectionList: #classAt:;
                setSelectionList: #classAt:put:;
                yourself.!

----- Method: TestRunner>>buildErrorListWith: (in category 'building') -----
buildErrorListWith: aBuilder
        ^ aBuilder pluggableListSpec new
                model: self;
                name: 'Error List';
                list: #errorList;
                menu: #errorMenu:;
                getIndex: #errorSelected;
                setIndex: #errorSelected:;
                yourself.!

----- Method: TestRunner>>buildFailureListWith: (in category 'building') -----
buildFailureListWith: aBuilder
        ^ aBuilder pluggableListSpec new
                model: self;
                name: 'Failure List';
                list: #failedList;
                menu: #failureMenu:;
                getIndex: #failedSelected;
                setIndex: #failedSelected:;
                yourself.!

----- Method: TestRunner>>buildStatusWith: (in category 'building') -----
buildStatusWith: aBuilder
        ^ aBuilder pluggableTextSpec new
                model: self;
                menu: #statusMenu:;
                color: #statusColor;
                getText: #statusText;
                yourself.!

----- Method: TestRunner>>buildWith: (in category 'building') -----
buildWith: aBuilder
        | window |
        window := aBuilder pluggableWindowSpec new
                model: self; label: self label; extent: self extent;
                children: (OrderedCollection new
                        add: ((self buildCategoriesWith: aBuilder)
                                frame: self categoriesFrame;
                                yourself);
                        add: ((self buildClassesWith: aBuilder)
                                frame: self classesFrame;
                                yourself);
                        add: ((self buildStatusWith: aBuilder)
                                frame: self statusFrame;
                                yourself);
                        add: ((self buildFailureListWith: aBuilder)
                                frame: self failureListFrame;
                                yourself);
                        add: ((self buildErrorListWith: aBuilder)
                                frame: self errorListFrame;
                                yourself);
                        add: ((self buildButtonsWith: aBuilder)
                                frame: self buttonsFrame;
                                yourself);
                        yourself);
                yourself.
        ^ aBuilder build: window.!

----- Method: TestRunner>>buttonHeight (in category 'building') -----
buttonHeight
        ^ Preferences standardButtonFont height * 3!

----- Method: TestRunner>>buttons (in category 'accessing-ui') -----
buttons
        ^ #(( 'Run Selected' #runAll #hasRunnable )
                ( 'Run Profiled' #runProfiled #hasRunnable )
                ( 'Run Coverage' #runCoverage #hasRunnable )
                ( 'Run Failures' #runFailures #hasFailures )
                ( 'Run Errors' #runErrors #hasErrors ))!

----- Method: TestRunner>>buttonsFrame (in category 'building') -----
buttonsFrame
        ^LayoutFrame new
                leftFraction: 0 offset: 0;
                topFraction: 1 offset: self buttonHeight negated;
                rightFraction: 1 offset: 0;
                bottomFraction: 1 offset: 0!

----- Method: TestRunner>>categoriesFrame (in category 'building') -----
categoriesFrame
        ^LayoutFrame new
                leftFraction: 0 offset: 0;
                topFraction: 0 offset: 0;
                rightFraction: 0.25 offset: 0;
                bottomFraction: 1 offset: self buttonHeight negated!

----- Method: TestRunner>>categoryAt: (in category 'accessing-categories') -----
categoryAt: anIndex
        ^ categoriesSelected includes: (categories at: anIndex ifAbsent: [ ^ false ]).!

----- Method: TestRunner>>categoryAt:put: (in category 'accessing-categories') -----
categoryAt: anInteger put: aBoolean
        categoriesSelected := categoriesSelected
                perform: (aBoolean ifTrue: [ #copyWith: ] ifFalse: [ #copyWithout: ])
                with: (categories at: anInteger ifAbsent: [ ^ self ]).
        self changed: #categorySelected; updateClasses.!

----- Method: TestRunner>>categoryList (in category 'accessing-categories') -----
categoryList
        ^ categories!

----- Method: TestRunner>>categoryMenu: (in category 'accessing-categories') -----
categoryMenu: aMenu
        ^ aMenu
                title: 'Categories';
                add: 'Select all' action: #selectAllCategories;
                add: 'Select inversion' action: #selectInverseCategories;
                add: 'Select none' action: #selectNoCategories;
                addLine;
                add: 'Filter...' action: #filterCategories;
                addLine;
                add: 'Refresh' action: #updateCategories;
                yourself.!

----- Method: TestRunner>>categorySelected (in category 'accessing-categories') -----
categorySelected
        ^ 0!

----- Method: TestRunner>>categorySelected: (in category 'accessing-categories') -----
categorySelected: anInteger
        self changed: #categorySelected.!

----- Method: TestRunner>>classAt: (in category 'accessing-classes') -----
classAt: anInteger
        ^ classesSelected includes: (classes at: anInteger ifAbsent: [ ^ false ]).!

----- Method: TestRunner>>classAt:put: (in category 'accessing-classes') -----
classAt: anInteger put: aBoolean
        classesSelected := classesSelected
                perform: (aBoolean ifTrue: [ #copyWith: ] ifFalse: [ #copyWithout: ])
                with: (classes at: anInteger ifAbsent: [ ^ self ]).
        self changed: #classSelected; changed: #hasRunnable.!

----- Method: TestRunner>>classList (in category 'accessing-classes') -----
classList
        | offset |
        classes isEmpty ifTrue: [ ^ classes ].
        offset := classes first allSuperclasses size.
        ^ classes collect: [ :each | | ident |
                ident := String
                        new: 2 * (0 max: each allSuperclasses size - offset)
                        withAll: $ .
                each isAbstract
                        ifFalse: [ ident , each name ]
                        ifTrue: [
                                ident asText , each name asText
                                        addAttribute: TextEmphasis italic;
                                        yourself ] ].!

----- Method: TestRunner>>classMenu: (in category 'accessing-classes') -----
classMenu: aMenu
        ^ aMenu
                title: 'Classes';
                add: 'Browse' action: #browseClass;
                addLine;
                add: 'Select all' action: #selectAllClasses;
                add: 'Select subclasses' action: #selectSubclasses;
                add: 'Select inversion' action: #selectInverseClasses;
                add: 'Select none' action: #selectNoClasses;
                addLine;
                add: 'Filter...' action: #filterClasses;
                addLine;
                add: 'Refresh' action: #updateClasses;
                yourself.!

----- Method: TestRunner>>classSelected (in category 'accessing-classes') -----
classSelected
        ^ classIndex!

----- Method: TestRunner>>classSelected: (in category 'accessing-classes') -----
classSelected: anInteger
        classIndex := anInteger.
        self changed: #classSelected!

----- Method: TestRunner>>classesFrame (in category 'building') -----
classesFrame
        ^LayoutFrame new
                leftFraction: 0.25 offset: 0;
                topFraction: 0 offset: 0;
                rightFraction: 0.5 offset: 0;
                bottomFraction: 1 offset: self buttonHeight negated!

----- Method: TestRunner>>classesSelected (in category 'accessing') -----
classesSelected
        ^ classesSelected!

----- Method: TestRunner>>collectCoverageFor: (in category 'actions') -----
collectCoverageFor: methods
        | wrappers suite |
        wrappers := methods collect: [ :each | TestCoverage on: each ].
        suite := self
                reset;
                suiteAll.
       
        [ wrappers do: [ :each | each install ].
        [ self runSuite: suite ] ensure: [ wrappers do: [ :each | each uninstall ] ] ] valueUnpreemptively.
        wrappers := wrappers reject: [ :each | each hasRun ].
        wrappers isEmpty
                ifTrue:
                        [ UIManager default inform: 'Congratulations. Your tests cover all code under analysis.' ]
                ifFalse:
                        [ ToolSet
                                browseMessageSet: (wrappers collect: [ :each | each reference ])
                                name: 'Not Covered Code (' , (100 - (100 * wrappers size // methods size)) printString , '% Code Coverage)'
                                autoSelect: nil ].
        self saveResultInHistory!

----- Method: TestRunner>>debug: (in category 'actions') -----
debug: aTestCase
        self debugSuite: (TestSuite new
                addTest: aTestCase;
                yourself).!

----- Method: TestRunner>>debugSuite: (in category 'actions') -----
debugSuite: aTestSuite
        self basicRunSuite: aTestSuite do: [ :each | each debug ].!

----- Method: TestRunner>>defaultBackgroundColor (in category 'private') -----
defaultBackgroundColor
        "<lint: #expect rule: #overridesSuper rational: 'we want a different color than the parent'>"

        ^ Preferences testRunnerWindowColor!

----- Method: TestRunner>>errorList (in category 'accessing-testing') -----
errorList
        ^ errorList collect: [ :each | each printString ].!

----- Method: TestRunner>>errorListFrame (in category 'building') -----
errorListFrame
        ^LayoutFrame new
                leftFraction: 0.5 offset: 0;
                topFraction: 0.5 offset: 0;
                rightFraction: 1 offset: 0;
                bottomFraction: 1 offset: self buttonHeight negated!

----- Method: TestRunner>>errorMenu: (in category 'accessing-menu') -----
errorMenu: aMenu
        ^ self statusMenu: aMenu!

----- Method: TestRunner>>errorSelected (in category 'accessing-testing') -----
errorSelected
        ^ errorList indexOf: errorSelected.!

----- Method: TestRunner>>errorSelected: (in category 'accessing-testing') -----
errorSelected: anInteger
        errorSelected := errorList at: anInteger ifAbsent: nil.
        self changed: #errorSelected.
        errorSelected ifNotNil: [ self debug: errorSelected ].!

----- Method: TestRunner>>excludeClassesNotUnderTestFrom: (in category 'actions') -----
excludeClassesNotUnderTestFrom: methods
       
        classesSelected do:
                [ :class |
                (class class includesSelector: #classNamesNotUnderTest) ifTrue:
                        [ class classNamesNotUnderTest do:
                                [ :className | | theClass |
                                theClass := Smalltalk classNamed: className.
                                theClass ifNotNil:[
                                theClass methods do:
                                        [ :each |
                                        methods
                                                remove: each methodReference
                                                ifAbsent: [  ] ].
                                theClass class methods do:
                                        [ :each |
                                        methods
                                                remove: each methodReference
                                                ifAbsent: [  ] ]] ] ] ]!

----- Method: TestRunner>>extent (in category 'accessing-ui') -----
extent
        ^ 640 @ 480!

----- Method: TestRunner>>failedList (in category 'accessing-testing') -----
failedList
        ^ failedList collect: [ :each | each printString ].!

----- Method: TestRunner>>failedSelected (in category 'accessing-testing') -----
failedSelected
        ^ failedList indexOf: failedSelected.!

----- Method: TestRunner>>failedSelected: (in category 'accessing-testing') -----
failedSelected: anInteger
        failedSelected := failedList at: anInteger ifAbsent: nil.
        self changed: #failedSelected.
        failedSelected ifNotNil: [ self debug: failedSelected ].!

----- Method: TestRunner>>failureListFrame (in category 'building') -----
failureListFrame
        ^LayoutFrame new
                leftFraction: 0.5 offset: 0;
                topFraction: 0 offset: self statusHeight;
                rightFraction: 1 offset: 0;
                bottomFraction: 0.5 offset: 0!

----- Method: TestRunner>>failureMenu: (in category 'accessing-menu') -----
failureMenu: aMenu
        ^ aMenu!

----- Method: TestRunner>>filterCategories (in category 'accessing-categories') -----
filterCategories
        | pattern |
        pattern := UIManager default
                                        request: 'Pattern(s) to select categories:\    (separate patterns with '';'')' withCRs
                                        initialAnswer: (categoryPattern ifNil: ['*']).
        (pattern isNil or: [pattern isEmpty]) ifTrue:
                [^self].
        categoriesSelected := ((categoryPattern := pattern) subStrings: ';')
                                                                inject: Set new
                                                                into: [:matches :subPattern|
                                                                        matches
                                                                                addAll: (categories select: [ :each | subPattern match: each]);
                                                                                yourself].
        self changed: #allSelections; changed: #categorySelected; updateClasses!

----- Method: TestRunner>>filterClasses (in category 'accessing-classes') -----
filterClasses
        | pattern |
        pattern := UIManager default
                                        request: 'Pattern(s) to select tests:\  (separate patterns with '';'')' withCRs
                                        initialAnswer: (classPattern ifNil: '*').
        (pattern isNil or: [pattern isEmpty]) ifTrue:
                [^self].
        classesSelected := ((classPattern := pattern) subStrings: ';')
                                                        inject: Set new
                                                        into: [:matches :subPattern|
                                                                matches
                                                                        addAll: (classes select: [ :each | subPattern match: each name]);
                                                                        yourself].
        self
                changed: #allSelections;
                changed: #classSelected;
                changed: #hasRunnable!

----- Method: TestRunner>>findCategories (in category 'utilities') -----
findCategories
        | visible |
        visible := Set new.
        self baseClass withAllSubclassesDo: [ :each |
                each category ifNotNil: [ :category |
                        visible add: category ] ].
        ^ Array streamContents: [ :stream |
                Smalltalk organization categories do: [ :each |
                        (visible includes: each)
                                ifTrue: [ stream nextPut: each ] ] ].!

----- Method: TestRunner>>findClassesForCategories: (in category 'utilities') -----
findClassesForCategories: aCollection

        | items |
        aCollection isEmpty
                ifTrue: [ ^ self baseClass withAllSubclasses asSet ].
        items := aCollection gather: [ :category |
                ((Smalltalk organization listAtCategoryNamed: category)
                        collect: [ :each | Smalltalk at: each ])
                        select: [ :each | each includesBehavior: self baseClass ] ].
        ^ items asSet.!

----- Method: TestRunner>>hasErrors (in category 'testing') -----
hasErrors
        ^ result hasErrors.!

----- Method: TestRunner>>hasFailures (in category 'testing') -----
hasFailures
        ^ result hasFailures.!

----- Method: TestRunner>>hasHistory (in category 'history saving') -----
hasHistory

        self flag: #Useless. "No Senders?"
        ^ true!

----- Method: TestRunner>>hasProgress (in category 'history saving') -----
hasProgress

        result classesTested do: [:cls |
                (cls class methodDictionary includesKey: #lastStoredRun)
                        ifTrue: [^ true]].
        ^ false!

----- Method: TestRunner>>hasResults (in category 'history saving') -----
hasResults

        ^ result notNil!

----- Method: TestRunner>>hasRunnable (in category 'testing') -----
hasRunnable
        ^ classesSelected notEmpty.!

----- Method: TestRunner>>historyMenuList (in category 'history saving') -----
historyMenuList
        ^ {'** save current result **'}, (self previousRun collect: [:ts | ts printString])!

----- Method: TestRunner>>initialize (in category 'initialization') -----
initialize
        super initialize.
        failedList := errorList := Array new.
        SystemChangeNotifier uniqueInstance
                notify: self ofSystemChangesOfItem: #class change: #Added using: #update;
                notify: self ofSystemChangesOfItem: #category change: #Added using: #update;
                notify: self ofSystemChangesOfItem: #class change: #Removed using: #update;
                notify: self ofSystemChangesOfItem: #category change: #Removed using: #update;
                notify: self ofSystemChangesOfItem: #class change: #Renamed using: #update;
                notify: self ofSystemChangesOfItem: #category change: #Renamed using: #update;
                notify: self ofSystemChangesOfItem: #class change: #Recategorized using: #update;
                notify: self ofSystemChangesOfItem: #category change: #Recategorized using: #update.
        self update; reset!

----- Method: TestRunner>>label (in category 'accessing-ui') -----
label
        ^ 'Test Runner' !

----- Method: TestRunner>>label:forSuite: (in category 'private') -----
label: aString forSuite: aTestSuite
        ^ String streamContents: [ :stream |
                stream nextPutAll: 'Running '; print: aTestSuite tests size; space; nextPutAll: aString.
                aTestSuite tests size > 1 ifTrue: [ stream nextPut: $s ] ]. !

----- Method: TestRunner>>perform:orSendTo: (in category 'private') -----
perform: selector orSendTo: otherTarget
        "<lint: #expect rule: #badMessage rational: 'this is a common morphic pattern'>"
       
        ^ (self respondsTo: selector)
                ifTrue: [ self perform: selector ]
                ifFalse: [ super perform: selector orSendTo: otherTarget ].!

----- Method: TestRunner>>postAcceptBrowseFor: (in category 'accessing-ui') -----
postAcceptBrowseFor: aModel
        "Nothing to do."!

----- Method: TestRunner>>previousRun (in category 'history saving') -----
previousRun

        ^ previousRun ifNil: [ previousRun := OrderedCollection new ]!

----- Method: TestRunner>>promptForPackages (in category 'actions') -----
promptForPackages
        | packages |
        packages := (PackageOrganizer default packages
                                reject: [:package | (package packageName beginsWith: 'Kernel')
                                                or: [(package packageName beginsWith: 'Collections')
                                                                or: [(package packageName beginsWith: 'Exceptions')
                                                                                or: [(package packageName beginsWith: 'SUnit')
                                                                                                or: [(package packageName beginsWith: 'System')
                                                                                                                or: [package packageName includesSubstring: 'Test' caseSensitive: false]]]]]])
                                sort: [:a :b | a packageName < b packageName].
        packages := Array
                                with: (UIManager default
                                                chooseFrom: (packages
                                                                collect: [:package | package packageName])
                                                values: packages
                                                title: 'Select Package').
        ^ packages!

----- Method: TestRunner>>representsSameBrowseeAs: (in category 'accessing-ui') -----
representsSameBrowseeAs: anotherModel
        ^ self class = anotherModel class
        and: [ classesSelected = anotherModel classesSelected ]!

----- Method: TestRunner>>reset (in category 'actions') -----
reset
        self result: TestResult new; updateResults.!

----- Method: TestRunner>>result (in category 'accessing-testing') -----
result
        ^ result!

----- Method: TestRunner>>result: (in category 'accessing-testing') -----
result: aResult
        result := aResult!

----- Method: TestRunner>>runAll (in category 'actions') -----
runAll
        self reset; runSuite: self suiteAll.
        self saveResultInHistory!

----- Method: TestRunner>>runCoverage (in category 'actions') -----
runCoverage
        | packages methods |
        packages := Set new.
        self addDeclaredPackagesUnderTestTo: packages.
        packages isEmpty ifTrue:
                [ packages := self promptForPackages ].
        methods := OrderedCollection new.
        self
                addMethodsUnderTestIn: packages
                to: methods.
        self excludeClassesNotUnderTestFrom: methods.
        methods isEmpty ifTrue:
                [ ^ UIManager default inform: 'No methods found for coverage analysis.' ].
        self collectCoverageFor: methods
!

----- Method: TestRunner>>runErrors (in category 'actions') -----
runErrors
        self result instVarNamed: 'errors' put: OrderedCollection new.
        self runSuite: self suiteErrors.!

----- Method: TestRunner>>runFailures (in category 'actions') -----
runFailures
        self result instVarNamed: 'failures' put: Set new.
        self runSuite: self suiteFailures.!

----- Method: TestRunner>>runProfiled (in category 'actions') -----
runProfiled
        MessageTally spyOn: [ self runAll ].!

----- Method: TestRunner>>runSuite: (in category 'actions') -----
runSuite: aTestSuite
        self basicRunSuite: aTestSuite do: [ :each | self runTest: each ].
        self updateResults

!

----- Method: TestRunner>>runTest: (in category 'actions') -----
runTest: aTestCase
        aTestCase run: result.
        self updateStatus: true.!

----- Method: TestRunner>>saveResultInHistory (in category 'history saving') -----
saveResultInHistory
        result dispatchResultsIntoHistory!

----- Method: TestRunner>>selectAllCategories (in category 'accessing-categories') -----
selectAllCategories
        categoriesSelected := categories asSet.
        self changed: #allSelections; changed: #categorySelected; updateClasses!

----- Method: TestRunner>>selectAllClasses (in category 'accessing-classes') -----
selectAllClasses
        "Fixed to update all selections now that the
        selection invalidation has been optimised."
       
        classesSelected := classes asSet.
        self
                changed: #allSelections;
                changed: #classSelected;
                changed: #hasRunnable!

----- Method: TestRunner>>selectInverseCategories (in category 'accessing-categories') -----
selectInverseCategories
        categoriesSelected := categories asSet
                removeAll: categoriesSelected;
                yourself.
        self changed: #allSelections; changed: #categorySelected; updateClasses!

----- Method: TestRunner>>selectInverseClasses (in category 'accessing-classes') -----
selectInverseClasses
        "Fixed to update all selections now that the
        selection invalidation has been optimised."
       
        classesSelected := classes asSet
                removeAll: classesSelected;
                yourself.
        self
                changed: #allSelections;
                changed: #classSelected;
                changed: #hasRunnable!

----- Method: TestRunner>>selectNoCategories (in category 'accessing-categories') -----
selectNoCategories
        categoriesSelected := Set new.
        self changed: #allSelections; changed: #categorySelected; updateClasses!

----- Method: TestRunner>>selectNoClasses (in category 'accessing-classes') -----
selectNoClasses
        "Fixed to update all selections now that the
        selection invalidation has been optimised."
       
        classesSelected := Set new.
        self
                changed: #allSelections;
                changed: #classSelected;
                changed: #hasRunnable!

----- Method: TestRunner>>selectSubclasses (in category 'accessing-classes') -----
selectSubclasses
        "Fixed to update all selections now that the
        selection invalidation has been optimised."
       
        | classesForPackages |
        classesForPackages := self findClassesForCategories: categoriesSelected.
        classesSelected := (classesSelected gather: [ :class |
                class withAllSubclasses select: [ :each |
                        classesForPackages includes: each ] ])
                asSet.
        self
                changed: #allSelections;
                changed: #classSelected;
                changed: #hasRunnable!

----- Method: TestRunner>>showDiffWith: (in category 'history saving') -----
showDiffWith: aTestResult
        | string diff |

        diff := result diff: aTestResult.
        string := String streamContents: [:str|
                str nextPutAll: '----------------'; cr.
                str nextPutAll: 'Diff between current result with: ', aTestResult asString; cr.
                str nextPutAll: 'New passed: '.
                diff first do: [:s| str nextPutAll: s printString, ' '].
                str cr.
                str nextPutAll: 'New failures: '.
                diff second do: [:s| str nextPutAll: s printString, ' '].
                str cr.
               
                str nextPutAll: 'New errors: '.
                diff third do: [:s| str nextPutAll: s printString, ' '].
                str cr].
       
        Workspace new contents: string; openLabel: 'SUnit Progress'
        !

----- Method: TestRunner>>showHistoryMenu (in category 'history saving') -----
showHistoryMenu
        | selectionIndex selectedPreviousResult actionIndex |
        selectionIndex := UIManager default chooseFrom: self historyMenuList title: 'History:'.

        "We pressed outside the menu"
        selectionIndex isZero ifTrue: [ ^ self ].

        "save current result is selected"
        selectionIndex = 1 ifTrue: [ self previousRun addFirst: result. ^ self ].

        selectedPreviousResult := self previousRun at: (selectionIndex - 1).
  actionIndex := (UIManager default chooseFrom: #('delete' 'show diff')  title:  'Action:').
        actionIndex = 1 ifTrue: [ self previousRun remove: selectedPreviousResult. ^ self ].
        actionIndex = 2 ifTrue: [ self showDiffWith: selectedPreviousResult]. !

----- Method: TestRunner>>showProgress (in category 'history saving') -----
showProgress
        | testCaseClasses d string |
        testCaseClasses := (self suiteAll tests collect: [:testCase | testCase class]) asSet.
       
        "At the end of the algorithm, d will contains all the diff between what was saved and the current result"
        d := Dictionary new.
        d at: #passed put: OrderedCollection new.
        d at: #failures put: OrderedCollection new.
        d at: #errors put: OrderedCollection new.

        testCaseClasses do: [ :cls | | t |
                (cls class methodDict includesKey: #lastStoredRun)
                        ifTrue: [t := cls lastStoredRun.
                                        (t at: #passed) do: [:s |
                                                                                        (result isErrorFor: cls selector: s)
                                                                                                ifTrue: [(d at: #errors) add: {cls . s}].
                                                                                        (result isFailureFor: cls selector: s)
                                                                                                ifTrue: [(d at: #failures) add: {cls . s}]  ].
                                                                                       
                                        (t at: #failures) do: [:s | (result isPassedFor: cls selector: s)
                                                                                                ifTrue: [(d at: #passed) add: {cls . s}].
                                                                                        (result isErrorFor: cls selector: s)
                                                                                                ifTrue: [(d at: #errors) add: {cls . s}]].
                                                                                       
                                        (t at: #errors) do: [:s | (result isPassedFor: cls selector: s)
                                                                                                ifTrue: [(d at: #passed) add: {cls . s}].
                                                                                        (result isFailureFor: cls selector: s)
                                                                                                ifTrue: [(d at: #failures) add: {cls . s}]]]].
               
                       
        string := String streamContents: [:str|
                str nextPutAll: '----------------'; cr.
                str nextPutAll: 'Diff between current result and saved result'; cr.
                str nextPutAll: 'New passed: '.
                (d at: #passed) do: [:s| str nextPutAll: s printString, ' '].
                str cr.
                str nextPutAll: 'New failures: '.
                (d at: #failures) do: [:s| str nextPutAll: s printString, ' '].
                str cr.
               
                str nextPutAll: 'New errors: '.
                (d at: #errors) do: [:s| str nextPutAll: s printString, ' '].
                str cr].
       
        Workspace new contents: string; openLabel: 'SUnit Progress' string.

        !

----- Method: TestRunner>>sortClass:before: (in category 'utilities') -----
sortClass: aFirstClass before: aSecondClass
        | first second |
        first := aFirstClass withAllSuperclasses reversed.
        second := aSecondClass withAllSuperclasses reversed.
        1 to: (first size min: second size) do: [ :index |
                (first at: index) == (second at: index)
                        ifFalse: [ ^ (first at: index) name <= (second at: index) name ] ].
        ^ second includes: aFirstClass.!

----- Method: TestRunner>>statusColor (in category 'accessing-testing') -----
statusColor
        result hasErrors
                ifTrue: [ ^ Color red ].
        result hasFailures
                ifTrue:[ ^ Color yellow ].
        ^ Color green!

----- Method: TestRunner>>statusFrame (in category 'building') -----
statusFrame
        ^LayoutFrame new
                leftFraction: 0.5 offset: 0;
                topFraction: 0 offset: 0;
                rightFraction: 1 offset: 0;
                bottomFraction: 0 offset: self statusHeight!

----- Method: TestRunner>>statusHeight (in category 'building') -----
statusHeight
        ^Preferences standardCodeFont height * 2 + 12!

----- Method: TestRunner>>statusMenu: (in category 'accessing-menu') -----
statusMenu: aMenu
        ^ aMenu
                add: 'History' action: #showHistoryMenu;
                add: 'Store result as progress reference' action: #storeResultIntoTestCases;
                add: 'Show progress' action: #showProgress;
                yourself!

----- Method: TestRunner>>statusText (in category 'accessing-testing') -----
statusText
        ^ result printString.!

----- Method: TestRunner>>storeResultIntoTestCases (in category 'history saving') -----
storeResultIntoTestCases

        result classesTested do: [:testCaseCls | testCaseCls generateLastStoredRunMethod ]
!

----- Method: TestRunner>>suiteAll (in category 'accessing') -----
suiteAll
        ^ TestSuite new in: [ :suite |
                classesSelected do: [ :each |
                        each isAbstract
                                ifFalse: [ each addToSuiteFromSelectors: suite ] ].
                suite name: (self label: 'Test' forSuite: suite) ].!

----- Method: TestRunner>>suiteErrors (in category 'accessing') -----
suiteErrors
        ^ TestSuite new in: [ :suite |
                suite
                        addTests: errorList;
                        name: (self label: 'Error' forSuite: suite) ].!

----- Method: TestRunner>>suiteFailures (in category 'accessing') -----
suiteFailures
        ^ TestSuite new in: [ :suite |
                suite
                        addTests: failedList;
                        name: (self label: 'Failure' forSuite: suite) ].!

----- Method: TestRunner>>update (in category 'updating') -----
update
        self updateCategories; updateClasses!

----- Method: TestRunner>>updateCategories (in category 'updating') -----
updateCategories
        categories := self findCategories.
        categoriesSelected := categoriesSelected isNil
                ifTrue: [ Set new ]
                ifFalse: [
                        categoriesSelected
                                select: [ :each | categories includes: each ] ].
        self changed: #categoryList; changed: #categorySelected.!

----- Method: TestRunner>>updateClasses (in category 'updating') -----
updateClasses
        | classesForCategories |
        classesForCategories := self findClassesForCategories: categoriesSelected.
        classes := classesForCategories asArray
                sort: [ :a :b | self sortClass: a before: b ].
        classIndex := 0.
        classesSelected := classesSelected isNil
                ifTrue: [ classesForCategories ]
                ifFalse: [
                        classesSelected
                                select: [ :each | classesForCategories includes: each ] ].
        self changed: #classList; changed: #classSelected; changed: #hasRunnable.!

----- Method: TestRunner>>updateResults (in category 'updating') -----
updateResults
        "<lint: #expect rule: #guardingClause>"
        "<lint: #expect rule: #longMethods>"

        self updateStatus: false.
        failedList size = result failures size ifFalse: [
                failedList := result failures asArray
                        sort: [ :a :b | a printString <= b printString ].
                failedSelected := nil.
                self
                        changed: #failedList;
                        changed: #failedSelected;
                        changed: #hasFailures;
                        changed: #hasProgress  ].
        errorList size = result errors size ifFalse: [
                errorList := result errors asArray
                        sort: [ :a :b | a printString <= b printString ].
                errorSelected := nil.
                self
                        changed: #errorList;
                        changed: #errorSelected;
                        changed: #hasErrors;
                        changed: #hasProgress ].!

----- Method: TestRunner>>updateStatus: (in category 'updating') -----
updateStatus: aBoolean
        "Update the status display, at most once a second if aBoolean is true."

        (aBoolean and: [ lastUpdate = Time totalSeconds ])
                ifTrue: [ ^ self ].
        self changed: #statusText; changed: #statusColor.
        lastUpdate := Time totalSeconds.!

----- Method: TestRunner>>windowIsClosing (in category 'private') -----
windowIsClosing
        SystemChangeNotifier uniqueInstance noMoreNotificationsFor: self!