2014-11-04 08:31:38 +01:00
const enum CompilerTestType {
2014-07-13 01:04:16 +02:00
Conformance ,
Regressions ,
Test262
}
2018-05-08 22:18:38 +02:00
interface CompilerFileBasedTest extends Harness . FileBasedTest {
2019-04-25 22:26:19 +02:00
readonly content? : string ;
2018-05-08 22:18:38 +02:00
}
2014-07-13 01:04:16 +02:00
class CompilerBaselineRunner extends RunnerBase {
2015-07-24 02:30:31 +02:00
private basePath = "tests/cases" ;
2016-05-24 01:00:56 +02:00
private testSuiteName : TestRunnerKind ;
2014-07-13 01:04:16 +02:00
private emit : boolean ;
2018-11-17 01:02:23 +01:00
public options : string | undefined ;
2014-07-13 01:04:16 +02:00
2015-04-30 02:54:15 +02:00
constructor ( public testType : CompilerTestType ) {
2014-07-13 01:04:16 +02:00
super ( ) ;
this . emit = true ;
if ( testType === CompilerTestType . Conformance ) {
2015-07-24 02:30:31 +02:00
this . testSuiteName = "conformance" ;
2014-07-13 01:04:16 +02:00
}
else if ( testType === CompilerTestType . Regressions ) {
2015-07-24 02:30:31 +02:00
this . testSuiteName = "compiler" ;
2014-07-13 01:04:16 +02:00
}
else if ( testType === CompilerTestType . Test262 ) {
2015-07-24 02:30:31 +02:00
this . testSuiteName = "test262" ;
2015-10-01 00:45:23 +02:00
}
else {
2015-07-24 02:30:31 +02:00
this . testSuiteName = "compiler" ; // default to this for historical reasons
2014-07-13 01:04:16 +02:00
}
2015-07-24 02:30:31 +02:00
this . basePath += "/" + this . testSuiteName ;
2014-07-13 01:04:16 +02:00
}
2016-05-24 01:00:56 +02:00
public kind() {
return this . testSuiteName ;
}
public enumerateTestFiles() {
2018-05-08 22:18:38 +02:00
// see also: `enumerateTestFiles` in tests/webTestServer.ts
return this . enumerateFiles ( this . basePath , /\.tsx?$/ , { recursive : true } ) . map ( CompilerTest . getConfigurations ) ;
2016-05-24 01:00:56 +02:00
}
2017-11-10 03:35:24 +01:00
public initializeTests() {
describe ( this . testSuiteName + " tests" , ( ) = > {
describe ( "Setup compiler for compiler baselines" , ( ) = > {
this . parseOptions ( ) ;
} ) ;
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
2018-05-08 22:18:38 +02:00
const files = this . tests . length > 0 ? this . tests : Harness.IO.enumerateTestFiles ( this ) ;
files . forEach ( test = > {
const file = typeof test === "string" ? test : test.file ;
this . checkTestCodeOutput ( vpath . normalizeSeparators ( file ) , typeof test === "string" ? CompilerTest . getConfigurations ( test ) : test ) ;
} ) ;
2017-11-10 03:35:24 +01:00
} ) ;
2017-04-05 07:34:51 +02:00
}
2015-11-11 19:57:32 +01:00
2018-05-08 22:18:38 +02:00
public checkTestCodeOutput ( fileName : string , test? : CompilerFileBasedTest ) {
2019-08-06 01:53:21 +02:00
if ( test && ts . some ( test . configurations ) ) {
2018-05-08 22:18:38 +02:00
test . configurations . forEach ( configuration = > {
2018-05-09 21:57:48 +02:00
describe ( ` ${ this . testSuiteName } tests for ${ fileName } ${ configuration ? ` ( ${ Harness . getFileBasedTestConfigurationDescription ( configuration ) } ) ` : ` ` } ` , ( ) = > {
2018-05-08 22:18:38 +02:00
this . runSuite ( fileName , test , configuration ) ;
} ) ;
2017-11-10 03:35:24 +01:00
} ) ;
}
2019-08-06 01:53:21 +02:00
else {
describe ( ` ${ this . testSuiteName } tests for ${ fileName } ` , ( ) = > {
this . runSuite ( fileName , test ) ;
} ) ;
}
2017-11-10 03:35:24 +01:00
}
2015-11-19 06:46:45 +01:00
2018-05-08 22:18:38 +02:00
private runSuite ( fileName : string , test? : CompilerFileBasedTest , configuration? : Harness.FileBasedTestConfiguration ) {
2017-11-10 03:35:24 +01:00
// Mocha holds onto the closure environment of the describe callback even after the test is done.
// Everything declared here should be cleared out in the "after" callback.
2018-05-22 23:46:57 +02:00
let compilerTest ! : CompilerTest ;
2019-04-25 22:26:19 +02:00
before ( ( ) = > {
let payload ;
if ( test && test . content ) {
const rootDir = test . file . indexOf ( "conformance" ) === - 1 ? "tests/cases/compiler/" : ts . getDirectoryPath ( test . file ) + "/" ;
payload = Harness . TestCaseParser . makeUnitsFromTest ( test . content , test . file , rootDir ) ;
}
compilerTest = new CompilerTest ( fileName , payload , configuration ) ;
} ) ;
2017-11-10 03:35:24 +01:00
it ( ` Correct errors for ${ fileName } ` , ( ) = > { compilerTest . verifyDiagnostics ( ) ; } ) ;
it ( ` Correct module resolution tracing for ${ fileName } ` , ( ) = > { compilerTest . verifyModuleResolution ( ) ; } ) ;
it ( ` Correct sourcemap content for ${ fileName } ` , ( ) = > { compilerTest . verifySourceMapRecord ( ) ; } ) ;
it ( ` Correct JS output for ${ fileName } ` , ( ) = > { if ( this . emit ) compilerTest . verifyJavaScriptOutput ( ) ; } ) ;
it ( ` Correct Sourcemap output for ${ fileName } ` , ( ) = > { compilerTest . verifySourceMapOutput ( ) ; } ) ;
it ( ` Correct type/symbol baselines for ${ fileName } ` , ( ) = > { compilerTest . verifyTypesAndSymbols ( ) ; } ) ;
2018-05-22 23:46:57 +02:00
after ( ( ) = > { compilerTest = undefined ! ; } ) ;
2017-11-10 03:35:24 +01:00
}
2014-07-13 01:04:16 +02:00
2017-11-10 03:35:24 +01:00
private parseOptions() {
if ( this . options && this . options . length > 0 ) {
this . emit = false ;
const opts = this . options . split ( "," ) ;
for ( const opt of opts ) {
switch ( opt ) {
case "emit" :
this . emit = true ;
break ;
default :
throw new Error ( "unsupported flag" ) ;
2016-02-23 21:48:31 +01:00
}
2017-11-10 03:35:24 +01:00
}
}
}
}
2016-02-23 21:48:31 +01:00
2017-11-10 03:35:24 +01:00
class CompilerTest {
private fileName : string ;
private justName : string ;
2019-08-06 01:53:21 +02:00
private configuredName : string ;
2017-11-10 03:35:24 +01:00
private lastUnit : Harness.TestCaseParser.TestUnitData ;
private harnessSettings : Harness.TestCaseParser.CompilerSettings ;
private hasNonDtsFiles : boolean ;
2017-11-10 22:52:12 +01:00
private result : compiler.CompilationResult ;
2017-11-10 03:35:24 +01:00
private options : ts.CompilerOptions ;
private tsConfigFiles : Harness.Compiler.TestFile [ ] ;
// equivalent to the files that will be passed on the command line
private toBeCompiled : Harness.Compiler.TestFile [ ] ;
// equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files)
private otherFiles : Harness.Compiler.TestFile [ ] ;
2014-07-13 01:04:16 +02:00
2018-05-08 22:18:38 +02:00
constructor ( fileName : string , testCaseContent? : Harness.TestCaseParser.TestCaseContent , configurationOverrides? : Harness.TestCaseParser.CompilerSettings ) {
2017-11-10 03:35:24 +01:00
this . fileName = fileName ;
this . justName = vpath . basename ( fileName ) ;
2019-08-06 01:53:21 +02:00
this . configuredName = this . justName ;
if ( configurationOverrides ) {
let configuredName = "" ;
const keys = Object
. keys ( configurationOverrides )
. map ( k = > k . toLowerCase ( ) )
. sort ( ) ;
for ( const key of keys ) {
if ( configuredName ) {
configuredName += "," ;
}
configuredName += ` ${ key } = ${ configurationOverrides [ key ] . toLowerCase ( ) } ` ;
}
if ( configuredName ) {
const extname = vpath . extname ( this . justName ) ;
const basename = vpath . basename ( this . justName , extname , /*ignoreCase*/ true ) ;
this . configuredName = ` ${ basename } ( ${ configuredName } ) ${ extname } ` ;
}
}
2018-05-08 22:18:38 +02:00
2017-11-10 03:35:24 +01:00
const rootDir = fileName . indexOf ( "conformance" ) === - 1 ? "tests/cases/compiler/" : ts . getDirectoryPath ( fileName ) + "/" ;
2018-05-08 22:18:38 +02:00
if ( testCaseContent === undefined ) {
2018-05-22 23:46:57 +02:00
testCaseContent = Harness . TestCaseParser . makeUnitsFromTest ( Harness . IO . readFile ( fileName ) ! , fileName , rootDir ) ;
2018-05-08 22:18:38 +02:00
}
if ( configurationOverrides ) {
testCaseContent = { . . . testCaseContent , settings : { . . . testCaseContent . settings , . . . configurationOverrides } } ;
}
2017-11-10 03:35:24 +01:00
const units = testCaseContent . testUnitData ;
this . harnessSettings = testCaseContent . settings ;
2018-05-22 23:46:57 +02:00
let tsConfigOptions : ts.CompilerOptions | undefined ;
2017-11-10 03:35:24 +01:00
this . tsConfigFiles = [ ] ;
if ( testCaseContent . tsConfig ) {
assert . equal ( testCaseContent . tsConfig . fileNames . length , 0 , ` list of files in tsconfig is not currently supported ` ) ;
2019-06-25 00:29:02 +02:00
assert . equal ( testCaseContent . tsConfig . raw . exclude , undefined , ` exclude in tsconfig is not currently supported ` ) ;
2014-08-18 23:29:55 +02:00
2017-11-10 03:35:24 +01:00
tsConfigOptions = ts . cloneCompilerOptions ( testCaseContent . tsConfig . options ) ;
2019-06-19 17:45:02 +02:00
this . tsConfigFiles . push ( this . createHarnessTestFile ( testCaseContent . tsConfigFileUnitData ! , rootDir , ts . combinePaths ( rootDir , tsConfigOptions . configFilePath ) ) ) ;
2017-11-10 03:35:24 +01:00
}
else {
const baseUrl = this . harnessSettings . baseUrl ;
if ( baseUrl !== undefined && ! ts . isRootedDiskPath ( baseUrl ) ) {
this . harnessSettings . baseUrl = ts . getNormalizedAbsolutePath ( baseUrl , rootDir ) ;
}
}
2014-07-13 01:04:16 +02:00
2017-11-10 03:35:24 +01:00
this . lastUnit = units [ units . length - 1 ] ;
2018-05-22 23:46:57 +02:00
this . hasNonDtsFiles = units . some ( unit = > ! ts . fileExtensionIs ( unit . name , ts . Extension . Dts ) ) ;
2017-11-10 03:35:24 +01:00
// We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test)
// If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references,
// otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another.
this . toBeCompiled = [ ] ;
this . otherFiles = [ ] ;
2016-01-25 20:49:26 +01:00
2017-11-10 03:35:24 +01:00
if ( testCaseContent . settings . noImplicitReferences || /require\(/ . test ( this . lastUnit . content ) || /reference\spath/ . test ( this . lastUnit . content ) ) {
this . toBeCompiled . push ( this . createHarnessTestFile ( this . lastUnit , rootDir ) ) ;
units . forEach ( unit = > {
if ( unit . name !== this . lastUnit . name ) {
this . otherFiles . push ( this . createHarnessTestFile ( unit , rootDir ) ) ;
2014-07-13 01:04:16 +02:00
}
} ) ;
2017-11-10 03:35:24 +01:00
}
else {
this . toBeCompiled = units . map ( unit = > {
return this . createHarnessTestFile ( unit , rootDir ) ;
2014-07-13 01:04:16 +02:00
} ) ;
2017-11-10 03:35:24 +01:00
}
2014-07-13 01:04:16 +02:00
2017-11-10 03:35:24 +01:00
if ( tsConfigOptions && tsConfigOptions . configFilePath !== undefined ) {
tsConfigOptions . configFilePath = ts . combinePaths ( rootDir , tsConfigOptions . configFilePath ) ;
2018-05-22 23:46:57 +02:00
tsConfigOptions . configFile ! . fileName = tsConfigOptions . configFilePath ;
2017-11-10 03:35:24 +01:00
}
2017-12-19 23:00:17 +01:00
this . result = Harness . Compiler . compileFiles (
2017-11-10 03:35:24 +01:00
this . toBeCompiled ,
this . otherFiles ,
this . harnessSettings ,
/*options*/ tsConfigOptions ,
2018-06-12 21:52:44 +02:00
/*currentDirectory*/ this . harnessSettings . currentDirectory ,
testCaseContent . symlinks
) ;
2014-07-13 01:04:16 +02:00
2017-12-19 23:00:17 +01:00
this . options = this . result . options ;
2017-11-10 03:35:24 +01:00
}
2018-05-08 22:18:38 +02:00
public static getConfigurations ( file : string ) : CompilerFileBasedTest {
// also see `parseCompilerTestConfigurations` in tests/webTestServer.ts
2018-05-22 23:46:57 +02:00
const content = Harness . IO . readFile ( file ) ! ;
2018-05-08 22:18:38 +02:00
const settings = Harness . TestCaseParser . extractCompilerSettings ( content ) ;
2018-05-09 21:57:48 +02:00
const configurations = Harness . getFileBasedTestConfigurations ( settings , /*varyBy*/ [ "module" , "target" ] ) ;
2019-04-25 22:26:19 +02:00
return { file , configurations , content } ;
2014-07-13 01:04:16 +02:00
}
2017-11-10 03:35:24 +01:00
public verifyDiagnostics() {
// check errors
Harness . Compiler . doErrorBaseline (
2019-08-06 01:53:21 +02:00
this . configuredName ,
2017-11-10 03:35:24 +01:00
this . tsConfigFiles . concat ( this . toBeCompiled , this . otherFiles ) ,
2017-11-10 23:55:04 +01:00
this . result . diagnostics ,
2017-11-10 03:35:24 +01:00
! ! this . options . pretty ) ;
2016-11-29 21:42:17 +01:00
}
2017-11-10 03:35:24 +01:00
public verifyModuleResolution() {
if ( this . options . traceResolution ) {
2019-08-06 01:53:21 +02:00
Harness . Baseline . runBaseline ( this . configuredName . replace ( /\.tsx?$/ , ".trace.json" ) ,
2018-09-11 23:19:28 +02:00
JSON . stringify ( this . result . traces . map ( utils . sanitizeTraceResolutionLogEntry ) , undefined , 4 ) ) ;
2017-11-10 03:35:24 +01:00
}
}
2014-07-13 01:04:16 +02:00
2017-11-10 03:35:24 +01:00
public verifySourceMapRecord() {
2018-04-17 23:39:20 +02:00
if ( this . options . sourceMap || this . options . inlineSourceMap || this . options . declarationMap ) {
2018-08-17 21:51:30 +02:00
const record = utils . removeTestPathPrefixes ( this . result . getSourceMapRecord ( ) ! ) ;
const baseline = ( this . options . noEmitOnError && this . result . diagnostics . length !== 0 ) || record === undefined
// Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required.
2019-07-18 09:50:38 +02:00
? null // eslint-disable-line no-null/no-null
2018-08-17 21:51:30 +02:00
: record ;
2019-08-06 01:53:21 +02:00
Harness . Baseline . runBaseline ( this . configuredName . replace ( /\.tsx?$/ , ".sourcemap.txt" ) , baseline ) ;
2017-11-10 03:35:24 +01:00
}
2014-07-13 01:04:16 +02:00
}
2017-11-10 03:35:24 +01:00
public verifyJavaScriptOutput() {
if ( this . hasNonDtsFiles ) {
Harness . Compiler . doJsEmitBaseline (
2019-08-06 01:53:21 +02:00
this . configuredName ,
2017-11-10 03:35:24 +01:00
this . fileName ,
this . options ,
this . result ,
this . tsConfigFiles ,
this . toBeCompiled ,
this . otherFiles ,
this . harnessSettings ) ;
}
}
2014-07-13 01:04:16 +02:00
2017-11-10 03:35:24 +01:00
public verifySourceMapOutput() {
Harness . Compiler . doSourcemapBaseline (
2019-08-06 01:53:21 +02:00
this . configuredName ,
2017-11-10 03:35:24 +01:00
this . options ,
this . result ,
this . harnessSettings ) ;
}
public verifyTypesAndSymbols() {
if ( this . fileName . indexOf ( "APISample" ) >= 0 ) {
return ;
2014-07-13 01:04:16 +02:00
}
2017-11-10 03:35:24 +01:00
2019-08-06 01:53:21 +02:00
const noTypesAndSymbols =
this . harnessSettings . noTypesAndSymbols &&
this . harnessSettings . noTypesAndSymbols . toLowerCase ( ) === "true" ;
if ( noTypesAndSymbols ) {
return ;
}
2017-11-10 03:35:24 +01:00
Harness . Compiler . doTypeAndSymbolBaseline (
2019-08-06 01:53:21 +02:00
this . configuredName ,
2018-05-22 23:46:57 +02:00
this . result . program ! ,
2018-08-18 00:45:14 +02:00
this . toBeCompiled . concat ( this . otherFiles ) . filter ( file = > ! ! this . result . program ! . getSourceFile ( file . unitName ) ) ,
/*opts*/ undefined ,
/*multifile*/ undefined ,
/*skipTypeBaselines*/ undefined ,
/*skipSymbolBaselines*/ undefined ,
! ! ts . length ( this . result . diagnostics )
) ;
2017-11-10 03:35:24 +01:00
}
private makeUnitName ( name : string , root : string ) {
2018-04-24 19:48:55 +02:00
const path = ts . toPath ( name , root , ts . identity ) ;
const pathStart = ts . toPath ( Harness . IO . getCurrentDirectory ( ) , "" , ts . identity ) ;
2017-11-10 03:35:24 +01:00
return pathStart ? path . replace ( pathStart , "/" ) : path ;
}
private createHarnessTestFile ( lastUnit : Harness.TestCaseParser.TestUnitData , rootDir : string , unitName? : string ) : Harness . Compiler . TestFile {
return { unitName : unitName || this . makeUnitName ( lastUnit . name , rootDir ) , content : lastUnit.content , fileOptions : lastUnit.fileOptions } ;
2014-07-13 01:04:16 +02:00
}
2019-06-19 17:45:02 +02:00
}