import * as vscode from 'vscode' ; import { LabelsClass } from '../labels/labels' ; import { Settings , SettingsParameters } from '../settings' ; import { readFileSync } from 'fs' ; import { Utility } from '../misc/utility' ; import * as path from 'path' ; import { FileWatcher } from '../misc/filewatcher' ; /**
 * An item that represents a unit test and connect the vscode TestItem,
 * the unit test and the file watcher.
 * There are 4 different types of test items:
 * - A test suite representing a launch.json file
 * - A test suite representing a sld/list file
 * - A test suite collecting several unit test (this base class here)
 * - A unit test itself (the UT label)
 * plus the rootSuite which is a suite without parent and testItem references.
 */ export class UnitTestCaseBase { public testItem : vscode . TestItem ; public static tcMap = new Map < vscode . TestItem , UnitTestCaseBase > ( ) ; param id param label param filePath constructor ( id , label , filePath ? ) { if ( id ) { let uri ; if ( filePath ) uri = vscode . Uri . file ( filePath ) ; this . testItem = RootTestSuite . testController . createTestItem ( id , label , uri ) ; UnitTestCaseBase . tcMap . set ( this . testItem , this ) ; } } /**
     * Returns the "real" UnitTestCase for a vscode test item.
     */ public static getUnitTestCase ( item ) { const ut = UnitTestCaseBase . tcMap . get ( item ) ; Utility . assert ( ut ) ; return ut ! ; } returns public getConfigParent ( ) { let testItem = this . testItem . parent ; let testConfig ; while ( testItem ) { testConfig = UnitTestCaseBase . getUnitTestCase ( testItem ) as UnitTestSuiteConfig ; Utility . assert ( testConfig ) ; if ( testConfig instanceof UnitTestSuiteConfig ) { return testConfig ; } testItem = testItem . parent ; } return undefined ; } } /**
 * A test case.
 * Additionally contains information for executing the test case, e.g. the label.
 */ export class UnitTestCase extends UnitTestCaseBase { public utLabel : string ; param id param label param utLabel param filePath constructor ( id , label , utLabel , filePath ) { super ( id , label , filePath ) ; this . utLabel = utLabel ; } } /**
 * A test suite containing other test suites or test cases.
 */ export class UnitTestSuite extends UnitTestCase { protected children : Array < UnitTestSuite | UnitTestCaseBase > ; param id param label param utLabel param filePath constructor ( id , label , utLabel , filePath ) { super ( id , label , utLabel , filePath ) ; this . children = [ ] ; } /**
     * Adds a child. If necessary removes the child from its old parent.
     */ public addChild ( child ) { this . children . push ( child ) ; this . testItem . children . add ( child . testItem ) ; } /**
     * Removes a child from the list.
     */ public removeChild ( child ) { const reducedList = this . children . filter ( item => item != child ) ; this . children = reducedList ; this . testItem . children . delete ( child . testItem . id ) ; } /**
     * Delete a test suite and it's children.
     */ public delete ( ) { this . deleteChildren ( ) ; } /**
     * Deletes all children.
     * Calls delete on each child.
     */ public deleteChildren ( ) { for ( const child of this . children ) { this . testItem . children . delete ( child . testItem . id ) ; } this . children = [ ] ; } } /**
 * The root test suite. Used to hold all other test suites.
 * Is associated with a test controller but not with a test item.
 */ export class RootTestSuite extends UnitTestSuite { public static testController : vscode . TestController ; protected wsTsMap : Map < string , UnitTestSuiteLaunchJson > ; protected wsFwMap : Map < string , FileWatcher > ; /**
     * Constructor.
     */ constructor ( testController ) { super ( undefined as any , undefined as any , undefined as any , undefined as any ) ; UnitTestCaseBase . tcMap . clear ( ) ; this . wsTsMap = new Map < string , UnitTestSuiteLaunchJson > ( ) ; this . wsFwMap = new Map < string , FileWatcher > ( ) ; RootTestSuite . testController = testController ; testController . resolveHandler = ( testItem ) => { this . resolveTests ( testItem ) ; } ; } param testItem protected resolveTests ( testItem ) { if ( testItem ) return ; if ( ! vscode . workspace . workspaceFolders ) return ; this . addWorkspaces ( vscode . workspace . workspaceFolders ) ; vscode . workspace . onDidChangeWorkspaceFolders ( e => { this . addWorkspaces ( e . added ) ; for ( const ws of e . removed ) { const wsFolder = ws . uri . fsPath ; const tsSuite = this . wsTsMap . get ( wsFolder ) ! ; tsSuite . delete ( ) ; this . wsTsMap . delete ( wsFolder ) ; const fw = this . wsFwMap . get ( wsFolder ) ! ; fw . dispose ( ) ; this . wsFwMap . delete ( wsFolder ) ; } } ) ; } param workspaces protected addWorkspaces ( workspaces ) { for ( const ws of workspaces ) { const wsFolder = ws . uri . fsPath ; const filePath = Utility . getlaunchJsonPath ( wsFolder ) ; const fileWatcher = new FileWatcher ( filePath ) ; this . wsFwMap . set ( wsFolder , fileWatcher ) ! ; let wsSuite ; fileWatcher . onDidCreate ( ( ) => { wsSuite = new UnitTestSuiteLaunchJson ( wsFolder , path . basename ( wsFolder ) ) ; this . addChild ( wsSuite ) ; this . wsTsMap . set ( wsFolder , wsSuite ) ; } ) ; fileWatcher . onDidChange ( ( ) => { wsSuite . fileChanged ( ) ; } ) ; fileWatcher . onDidDelete ( ( ) => { this . removeChild ( wsSuite ) ; this . wsTsMap . delete ( wsFolder ) ; } ) ; } } /**
     * Adds a child. If necessary removes the child from its old parent.
     */ public addChild ( child ) { this . children . push ( child ) ; } /**
     * Removes a child from the list.
     */ public removeChild ( child ) { const reducedList = this . children . filter ( item => item != child ) ; this . children = reducedList ; RootTestSuite . testController . items . delete ( child . testItem . id ) ; } } /**
 * Extends the base class with functionality for handling files (file watcher)
 * and especially the launch.json file.
 */ class UnitTestSuiteLaunchJson extends UnitTestSuite { protected wsFolder : string ; param wsFolder constructor ( wsFolder , label ) { super ( Utility . getlaunchJsonPath ( wsFolder ) , label , undefined as any , Utility . getlaunchJsonPath ( wsFolder ) ) ; this . testItem . description = 'workspace' ; this . wsFolder = wsFolder ; this . fileChanged ( ) ; } /**
     * Call if launch.json file has been changed.
     */ public fileChanged ( ) { this . deleteChildren ( ) ; try { const launchJsonPath = this . testItem . id ; const configs = this . getUnitTestsLaunchConfigs ( launchJsonPath ) ; RootTestSuite . testController . items . delete ( this . testItem . id ) ; for ( const config of configs ) { const testConfig = new UnitTestSuiteConfig ( this . wsFolder , config ) ; this . addChild ( testConfig ) ; const lineNr = config . __lineNr ; if ( lineNr != undefined ) { const vsTest = testConfig . testItem ; vsTest . range = new vscode . Range ( lineNr , 0 , lineNr , 0 ) ; } } if ( this . children . length > 0 ) { RootTestSuite . testController . items . add ( this . testItem ) ; } } catch ( e ) { } } param launchJsonPath returns protected getUnitTestsLaunchConfigs ( launchJsonPath ) { const launchData = readFileSync ( launchJsonPath , 'utf8' ) ; const launch = Utility . readLaunchJson ( launchJsonPath , launchData ) ; const configurations = launch . configurations . filter ( config => ( config . type == 'dezog' ) && config . unitTests ) ; for ( const config of configurations ) { const regStr = '"name"\\s*:\\s*"' + config . name + '"' ; const regex = new RegExp ( regStr ) ; const lineNr = Utility . getLineNumberInText ( regex , launchData ) ; config . __lineNr = lineNr ; } return configurations ; } } /**
 * Extends the base class with functionality for handling launch.json configs.
 */ export class UnitTestSuiteConfig extends UnitTestSuite { public wsFolder : string ; public config : SettingsParameters ; protected fileWatchers : FileWatcher [ ] ; protected timerId : NodeJS . Timeout ; param wsFolder param config constructor ( wsFolder , config ) { super ( wsFolder + '#' + config . name , config . name , undefined as any , Utility . getlaunchJsonPath ( wsFolder ) ) ; this . testItem . description = 'config' ; this . wsFolder = wsFolder ; this . config = Settings . Init ( config ) ; this . fileWatchers = [ ] ; try { const listFiles = Settings . GetAllAssemblerListFiles ( this . config ) ; for ( const listFile of listFiles ) { const fw = new FileWatcher ( listFile . path ) ; this . fileWatchers . push ( fw ) ; fw . onDidCreate ( ( ) => { this . fileChanged ( ) ; } ) ; fw . onDidChange ( ( ) => { this . fileChanged ( ) ; } ) ; fw . onDidDelete ( ( ) => { this . fileChanged ( ) ; } ) ; } } catch ( e ) { } } /**
     * Delete a test item and it's children.
     * Removes the file watcher.
     */ public delete ( ) { super . delete ( ) ; this . fileWatchers . forEach ( element => element . dispose ( ) ) ; } /**
     * Called if a sld/list file changes.
     * Start a timer to wait for other file changes (changes of other list files).
     */ protected fileChanged ( ) { clearTimeout ( this . timerId ) ; this . timerId = setTimeout ( ( ) => { try { this . delayedFileChanged ( ) ; } catch ( e ) { } } , 200 ) ; } /**
     * Called if a sld/list file changed and no change happened for 1 second.
     * Creates labels from the list files.
     * From the UT-labels test suites and test cases are created.
     */ public delayedFileChanged ( ) { this . deleteChildren ( ) ; const labels = new LabelsClass ( ) ; Utility . setRootPath ( this . wsFolder ) ; try { labels . readListFiles ( this . config ) ; } catch ( e ) { console . log ( e ) ; throw e ; } const utLabels = this . getAllUtLabels ( labels ) ; const map = this . convertLabelsToMap ( utLabels ) ; this . createTestSuite ( labels , map , '' ) ; } param map protected createTestSuite ( labels , map , name , parent ? ) { let testItem ; if ( parent ) { const fullId = parent . testItem . id + '.' + name ; let fullUtLabel = '' ; if ( parent . utLabel ) fullUtLabel = parent . utLabel + '.' ; fullUtLabel += name ; const location = labels . getLocationOfLabel ( fullUtLabel ) ! ; let file ; if ( location ) { file = Utility . getAbsFilePath ( location . file ) ; } if ( map . size == 0 ) { Utility . assert ( file ) ; testItem = new UnitTestCase ( fullId , name , fullUtLabel , file ) ; } else { testItem = new UnitTestSuite ( fullId , name , fullUtLabel , file ) ; } parent . addChild ( testItem ) ; if ( location ) { const vsTest = testItem . testItem ; vsTest . range = new vscode . Range ( location . lineNr , 0 , location . lineNr , 0 ) ; } } else { testItem = this ; } for ( const [ key , childMap ] of map ) { this . createTestSuite ( labels , childMap , key , testItem ) ; } } returns protected getAllUtLabels ( labels ) { const utLabels = labels . getLabelsForRegEx ( '.*\\bUT_\\w*$' , '' ) ; const labelFilesLines = utLabels . map ( label => { const location = labels . getLocationOfLabel ( label ) ! ; Utility . assert ( location , "'getAllUtLabels'" ) ; return { label , file : Utility . getAbsFilePath ( location . file ) , line : location . lineNr } ; } ) ; return labelFilesLines ; } param lblLocations protected convertLabelsToMap ( lblLocations ) { const labels = lblLocations . map ( lblLoc => lblLoc . label ) ; const labelMap = new Map < string , any > ( ) ; for ( const label of labels ) { const parts = label . split ( '.' ) ; let map = labelMap ; for ( const part of parts ) { let nextMap = map . get ( part ) ; if ( ! nextMap ) { nextMap = new Map < string , any > ( ) ; map . set ( part , nextMap ) ; } map = nextMap ; } } return labelMap ; } } /**
 * This structure is returned by getAllUnitTests.
 */ interface UtLabelFileLine { label : string ; file : string ; line : number ; }