@@ -1291,11 +1291,25 @@ namespace Harness {
1291
1291
}
1292
1292
1293
1293
export function getErrorBaseline ( inputFiles : ReadonlyArray < TestFile > , diagnostics : ReadonlyArray < ts . Diagnostic > , pretty ?: boolean ) {
1294
+ let outputLines = "" ;
1295
+ const gen = iterateErrorBaseline ( inputFiles , diagnostics , pretty ) ;
1296
+ for ( let { done, value} = gen . next ( ) ; ! done ; { done, value } = gen . next ( ) ) {
1297
+ const [ , content ] = value ;
1298
+ outputLines += content ;
1299
+ }
1300
+ return outputLines ;
1301
+ }
1302
+
1303
+ export const diagnosticSummaryMarker = "__diagnosticSummary" ;
1304
+ export const globalErrorsMarker = "__globalErrors" ;
1305
+ export function * iterateErrorBaseline ( inputFiles : ReadonlyArray < TestFile > , diagnostics : ReadonlyArray < ts . Diagnostic > , pretty ?: boolean ) : IterableIterator < [ string , string , number ] > {
1294
1306
diagnostics = diagnostics . slice ( ) . sort ( ts . compareDiagnostics ) ;
1295
1307
let outputLines = "" ;
1296
1308
// Count up all errors that were found in files other than lib.d.ts so we don't miss any
1297
1309
let totalErrorsReportedInNonLibraryFiles = 0 ;
1298
1310
1311
+ let errorsReported = 0 ;
1312
+
1299
1313
let firstLine = true ;
1300
1314
function newLine ( ) {
1301
1315
if ( firstLine ) {
@@ -1314,6 +1328,7 @@ namespace Harness {
1314
1328
. filter ( s => s . length > 0 )
1315
1329
. map ( s => "!!! " + ts . DiagnosticCategory [ error . category ] . toLowerCase ( ) + " TS" + error . code + ": " + s ) ;
1316
1330
errLines . forEach ( e => outputLines += ( newLine ( ) + e ) ) ;
1331
+ errorsReported ++ ;
1317
1332
1318
1333
// do not count errors from lib.d.ts here, they are computed separately as numLibraryDiagnostics
1319
1334
// if lib.d.ts is explicitly included in input files and there are some errors in it (i.e. because of duplicate identifiers)
@@ -1325,12 +1340,18 @@ namespace Harness {
1325
1340
}
1326
1341
}
1327
1342
1343
+ yield [ diagnosticSummaryMarker , minimalDiagnosticsToString ( diagnostics , pretty ) + Harness . IO . newLine ( ) + Harness . IO . newLine ( ) , diagnostics . length ] ;
1344
+
1328
1345
// Report global errors
1329
1346
const globalErrors = diagnostics . filter ( err => ! err . file ) ;
1330
1347
globalErrors . forEach ( outputErrorText ) ;
1348
+ yield [ globalErrorsMarker , outputLines , errorsReported ] ;
1349
+ outputLines = "" ;
1350
+ errorsReported = 0 ;
1331
1351
1332
1352
// 'merge' the lines of each input file with any errors associated with it
1333
- inputFiles . filter ( f => f . content !== undefined ) . forEach ( inputFile => {
1353
+ const dupeCase = ts . createMap < number > ( ) ;
1354
+ for ( const inputFile of inputFiles . filter ( f => f . content !== undefined ) ) {
1334
1355
// Filter down to the errors in the file
1335
1356
const fileErrors = diagnostics . filter ( e => {
1336
1357
const errFn = e . file ;
@@ -1396,7 +1417,10 @@ namespace Harness {
1396
1417
1397
1418
// Verify we didn't miss any errors in this file
1398
1419
assert . equal ( markedErrorCount , fileErrors . length , "count of errors in " + inputFile . unitName ) ;
1399
- } ) ;
1420
+ yield [ checkDuplicatedFileName ( inputFile . unitName , dupeCase ) , outputLines , errorsReported ] ;
1421
+ outputLines = "" ;
1422
+ errorsReported = 0 ;
1423
+ }
1400
1424
1401
1425
const numLibraryDiagnostics = ts . countWhere ( diagnostics , diagnostic => {
1402
1426
return diagnostic . file && ( isDefaultLibraryFile ( diagnostic . file . fileName ) || isBuiltFile ( diagnostic . file . fileName ) ) ;
@@ -1409,9 +1433,6 @@ namespace Harness {
1409
1433
1410
1434
// Verify we didn't miss any errors in total
1411
1435
assert . equal ( totalErrorsReportedInNonLibraryFiles + numLibraryDiagnostics + numTest262HarnessDiagnostics , diagnostics . length , "total number of errors" ) ;
1412
-
1413
- return minimalDiagnosticsToString ( diagnostics , pretty ) +
1414
- Harness . IO . newLine ( ) + Harness . IO . newLine ( ) + outputLines ;
1415
1436
}
1416
1437
1417
1438
export function doErrorBaseline ( baselinePath : string , inputFiles : TestFile [ ] , errors : ts . Diagnostic [ ] , pretty ?: boolean ) {
@@ -1425,7 +1446,7 @@ namespace Harness {
1425
1446
} ) ;
1426
1447
}
1427
1448
1428
- export function doTypeAndSymbolBaseline ( baselinePath : string , result : CompilerResult , allFiles : { unitName : string , content : string } [ ] , opts ?: Harness . Baseline . BaselineOptions ) {
1449
+ export function doTypeAndSymbolBaseline ( baselinePath : string , result : CompilerResult , allFiles : { unitName : string , content : string } [ ] , opts ?: Harness . Baseline . BaselineOptions , multifile ?: boolean ) {
1429
1450
if ( result . errors . length !== 0 ) {
1430
1451
return ;
1431
1452
}
@@ -1486,67 +1507,85 @@ namespace Harness {
1486
1507
return ;
1487
1508
1488
1509
function checkBaseLines ( isSymbolBaseLine : boolean ) {
1489
- const fullBaseLine = generateBaseLine ( fullResults , isSymbolBaseLine ) ;
1490
-
1491
1510
const fullExtension = isSymbolBaseLine ? ".symbols" : ".types" ;
1492
-
1493
1511
// When calling this function from rwc-runner, the baselinePath will have no extension.
1494
1512
// As rwc test- file is stored in json which ".json" will get stripped off.
1495
1513
// When calling this function from compiler-runner, the baselinePath will then has either ".ts" or ".tsx" extension
1496
1514
const outputFileName = ts . endsWith ( baselinePath , ts . Extension . Ts ) || ts . endsWith ( baselinePath , ts . Extension . Tsx ) ?
1497
- baselinePath . replace ( / \. t s x ? / , fullExtension ) : baselinePath . concat ( fullExtension ) ;
1498
- Harness . Baseline . runBaseline ( outputFileName , ( ) => fullBaseLine , opts ) ;
1515
+ baselinePath . replace ( / \. t s x ? / , "" ) : baselinePath ;
1516
+
1517
+ if ( ! multifile ) {
1518
+ const fullBaseLine = generateBaseLine ( fullResults , isSymbolBaseLine ) ;
1519
+ Harness . Baseline . runBaseline ( outputFileName + fullExtension , ( ) => fullBaseLine , opts ) ;
1520
+ }
1521
+ else {
1522
+ Harness . Baseline . runMultifileBaseline ( outputFileName , fullExtension , ( ) => {
1523
+ return iterateBaseLine ( fullResults , isSymbolBaseLine ) ;
1524
+ } , opts ) ;
1525
+ }
1499
1526
}
1500
1527
1501
1528
function generateBaseLine ( typeWriterResults : ts . Map < TypeWriterResult [ ] > , isSymbolBaseline : boolean ) : string {
1502
- const typeLines : string [ ] = [ ] ;
1529
+ let result = "" ;
1530
+ const gen = iterateBaseLine ( typeWriterResults , isSymbolBaseline ) ;
1531
+ for ( let { done, value} = gen . next ( ) ; ! done ; { done, value } = gen . next ( ) ) {
1532
+ const [ , content ] = value ;
1533
+ result += content ;
1534
+ }
1535
+ return result ;
1536
+ }
1537
+
1538
+ function * iterateBaseLine ( typeWriterResults : ts . Map < TypeWriterResult [ ] > , isSymbolBaseline : boolean ) : IterableIterator < [ string , string ] > {
1539
+ let typeLines = "" ;
1503
1540
const typeMap : { [ fileName : string ] : { [ lineNum : number ] : string [ ] ; } } = { } ;
1541
+ const dupeCase = ts . createMap < number > ( ) ;
1504
1542
1505
- allFiles . forEach ( file => {
1543
+ for ( const file of allFiles ) {
1506
1544
const codeLines = file . content . split ( "\n" ) ;
1545
+ const key = file . unitName ;
1507
1546
typeWriterResults . get ( file . unitName ) . forEach ( result => {
1508
1547
if ( isSymbolBaseline && ! result . symbol ) {
1509
1548
return ;
1510
1549
}
1511
1550
1512
1551
const typeOrSymbolString = isSymbolBaseline ? result . symbol : result . type ;
1513
1552
const formattedLine = result . sourceText . replace ( / \r ? \n / g, "" ) + " : " + typeOrSymbolString ;
1514
- if ( ! typeMap [ file . unitName ] ) {
1515
- typeMap [ file . unitName ] = { } ;
1553
+ if ( ! typeMap [ key ] ) {
1554
+ typeMap [ key ] = { } ;
1516
1555
}
1517
1556
1518
1557
let typeInfo = [ formattedLine ] ;
1519
- const existingTypeInfo = typeMap [ file . unitName ] [ result . line ] ;
1558
+ const existingTypeInfo = typeMap [ key ] [ result . line ] ;
1520
1559
if ( existingTypeInfo ) {
1521
1560
typeInfo = existingTypeInfo . concat ( typeInfo ) ;
1522
1561
}
1523
- typeMap [ file . unitName ] [ result . line ] = typeInfo ;
1562
+ typeMap [ key ] [ result . line ] = typeInfo ;
1524
1563
} ) ;
1525
1564
1526
- typeLines . push ( "=== " + file . unitName + " ===\r\n" ) ;
1565
+ typeLines += "=== " + file . unitName + " ===\r\n" ;
1527
1566
for ( let i = 0 ; i < codeLines . length ; i ++ ) {
1528
1567
const currentCodeLine = codeLines [ i ] ;
1529
- typeLines . push ( currentCodeLine + "\r\n" ) ;
1530
- if ( typeMap [ file . unitName ] ) {
1531
- const typeInfo = typeMap [ file . unitName ] [ i ] ;
1568
+ typeLines += currentCodeLine + "\r\n" ;
1569
+ if ( typeMap [ key ] ) {
1570
+ const typeInfo = typeMap [ key ] [ i ] ;
1532
1571
if ( typeInfo ) {
1533
1572
typeInfo . forEach ( ty => {
1534
- typeLines . push ( ">" + ty + "\r\n" ) ;
1573
+ typeLines += ">" + ty + "\r\n" ;
1535
1574
} ) ;
1536
1575
if ( i + 1 < codeLines . length && ( codeLines [ i + 1 ] . match ( / ^ \s * [ { | } ] \s * $ / ) || codeLines [ i + 1 ] . trim ( ) === "" ) ) {
1537
1576
}
1538
1577
else {
1539
- typeLines . push ( "\r\n" ) ;
1578
+ typeLines += "\r\n" ;
1540
1579
}
1541
1580
}
1542
1581
}
1543
1582
else {
1544
- typeLines . push ( "No type information for this code." ) ;
1583
+ typeLines += "No type information for this code." ;
1545
1584
}
1546
1585
}
1547
- } ) ;
1548
-
1549
- return typeLines . join ( "" ) ;
1586
+ yield [ checkDuplicatedFileName ( file . unitName , dupeCase ) , typeLines ] ;
1587
+ typeLines = "" ;
1588
+ }
1550
1589
}
1551
1590
}
1552
1591
@@ -1642,31 +1681,54 @@ namespace Harness {
1642
1681
}
1643
1682
1644
1683
export function collateOutputs ( outputFiles : Harness . Compiler . GeneratedFile [ ] ) : string {
1645
- // Collect, test, and sort the fileNames
1646
- outputFiles . sort ( ( a , b ) => ts . compareStrings ( cleanName ( a . fileName ) , cleanName ( b . fileName ) ) ) ;
1647
-
1684
+ const gen = iterateOutputs ( outputFiles ) ;
1648
1685
// Emit them
1649
1686
let result = "" ;
1650
- for ( const outputFile of outputFiles ) {
1687
+ for ( let { done , value } = gen . next ( ) ; ! done ; { done , value } = gen . next ( ) ) {
1651
1688
// Some extra spacing if this isn't the first file
1652
1689
if ( result . length ) {
1653
1690
result += "\r\n\r\n" ;
1654
1691
}
1655
-
1656
1692
// FileName header + content
1657
- result += "/*====== " + outputFile . fileName + " ======*/\r\n" ;
1658
-
1659
- result += outputFile . code ;
1693
+ const [ , content ] = value ;
1694
+ result += content ;
1660
1695
}
1661
-
1662
1696
return result ;
1697
+ }
1698
+
1699
+ export function * iterateOutputs ( outputFiles : Harness . Compiler . GeneratedFile [ ] ) : IterableIterator < [ string , string ] > {
1700
+ // Collect, test, and sort the fileNames
1701
+ outputFiles . sort ( ( a , b ) => ts . compareStrings ( cleanName ( a . fileName ) , cleanName ( b . fileName ) ) ) ;
1702
+ const dupeCase = ts . createMap < number > ( ) ;
1703
+ // Yield them
1704
+ for ( const outputFile of outputFiles ) {
1705
+ yield [ checkDuplicatedFileName ( outputFile . fileName , dupeCase ) , "/*====== " + outputFile . fileName + " ======*/\r\n" + outputFile . code ] ;
1706
+ }
1663
1707
1664
1708
function cleanName ( fn : string ) {
1665
1709
const lastSlash = ts . normalizeSlashes ( fn ) . lastIndexOf ( "/" ) ;
1666
1710
return fn . substr ( lastSlash + 1 ) . toLowerCase ( ) ;
1667
1711
}
1668
1712
}
1669
1713
1714
+ function checkDuplicatedFileName ( resultName : string , dupeCase : ts . Map < number > ) : string {
1715
+ resultName = sanitizeTestFilePath ( resultName ) ;
1716
+ if ( dupeCase . has ( resultName ) ) {
1717
+ // A different baseline filename should be manufactured if the names differ only in case, for windows compat
1718
+ const count = 1 + dupeCase . get ( resultName ) ;
1719
+ dupeCase . set ( resultName , count ) ;
1720
+ resultName = `${ resultName } .dupe${ count } ` ;
1721
+ }
1722
+ else {
1723
+ dupeCase . set ( resultName , 0 ) ;
1724
+ }
1725
+ return resultName ;
1726
+ }
1727
+
1728
+ function sanitizeTestFilePath ( name : string ) {
1729
+ return ts . normalizeSlashes ( name . replace ( / [ \^ < > : " | ? * % ] / g, "_" ) ) . replace ( / \. \. \/ / g, "__dotdot/" ) . toLowerCase ( ) ;
1730
+ }
1731
+
1670
1732
// This does not need to exist strictly speaking, but many tests will need to be updated if it's removed
1671
1733
export function compileString ( _code : string , _unitName : string , _callback : ( result : CompilerResult ) => void ) {
1672
1734
// NEWTODO: Re-implement 'compileString'
@@ -2004,6 +2066,66 @@ namespace Harness {
2004
2066
const comparison = compareToBaseline ( actual , relativeFileName , opts ) ;
2005
2067
writeComparison ( comparison . expected , comparison . actual , relativeFileName , actualFileName ) ;
2006
2068
}
2069
+
2070
+ export function runMultifileBaseline ( relativeFileBase : string , extension : string , generateContent : ( ) => IterableIterator < [ string , string , number ] > | IterableIterator < [ string , string ] > , opts ?: BaselineOptions , referencedExtensions ?: string [ ] ) : void {
2071
+ const gen = generateContent ( ) ;
2072
+ const writtenFiles = ts . createMap < true > ( ) ;
2073
+ const canonicalize = ts . createGetCanonicalFileName ( /*caseSensitive*/ false ) ; // This is done so tests work on windows _and_ linux
2074
+ /* tslint:disable-next-line:no-null-keyword */
2075
+ const errors : Error [ ] = [ ] ;
2076
+ if ( gen !== null ) {
2077
+ for ( let { done, value} = gen . next ( ) ; ! done ; { done, value } = gen . next ( ) ) {
2078
+ const [ name , content , count ] = value as [ string , string , number | undefined ] ;
2079
+ if ( count === 0 ) continue ; // Allow error reporter to skip writing files without errors
2080
+ const relativeFileName = ts . combinePaths ( relativeFileBase , name ) + extension ;
2081
+ const actualFileName = localPath ( relativeFileName , opts && opts . Baselinefolder , opts && opts . Subfolder ) ;
2082
+ const actual = content ;
2083
+ const comparison = compareToBaseline ( actual , relativeFileName , opts ) ;
2084
+ try {
2085
+ writeComparison ( comparison . expected , comparison . actual , relativeFileName , actualFileName ) ;
2086
+ }
2087
+ catch ( e ) {
2088
+ errors . push ( e ) ;
2089
+ }
2090
+ const path = ts . toPath ( relativeFileName , "" , canonicalize ) ;
2091
+ writtenFiles . set ( path , true ) ;
2092
+ }
2093
+ }
2094
+
2095
+ const referenceDir = referencePath ( relativeFileBase , opts && opts . Baselinefolder , opts && opts . Subfolder ) ;
2096
+ let existing = Harness . IO . readDirectory ( referenceDir , referencedExtensions || [ extension ] ) ;
2097
+ if ( extension === ".ts" || referencedExtensions && referencedExtensions . indexOf ( ".ts" ) > - 1 && referencedExtensions . indexOf ( ".d.ts" ) === - 1 ) {
2098
+ // special-case and filter .d.ts out of .ts results
2099
+ existing = existing . filter ( f => ! ts . endsWith ( f , ".d.ts" ) ) ;
2100
+ }
2101
+ const missing : string [ ] = [ ] ;
2102
+ for ( const name of existing ) {
2103
+ const localCopy = name . substring ( referenceDir . length - relativeFileBase . length ) ;
2104
+ const path = ts . toPath ( localCopy , "" , canonicalize ) ;
2105
+ if ( ! writtenFiles . has ( path ) ) {
2106
+ missing . push ( localCopy ) ;
2107
+ }
2108
+ }
2109
+ if ( missing . length ) {
2110
+ for ( const file of missing ) {
2111
+ IO . writeFile ( localPath ( file + ".delete" , opts && opts . Baselinefolder , opts && opts . Subfolder ) , "" ) ;
2112
+ }
2113
+ }
2114
+
2115
+ if ( errors . length || missing . length ) {
2116
+ let errorMsg = "" ;
2117
+ if ( errors . length ) {
2118
+ errorMsg += `The baseline for ${ relativeFileBase } has changed:${ "\n " + errors . map ( e => e . message ) . join ( "\n " ) } ` ;
2119
+ }
2120
+ if ( errors . length && missing . length ) {
2121
+ errorMsg += "\n" ;
2122
+ }
2123
+ if ( missing . length ) {
2124
+ errorMsg += `Baseline missing files:${ "\n " + missing . join ( "\n " ) + "\n" } Written:${ "\n " + ts . arrayFrom ( writtenFiles . keys ( ) ) . join ( "\n " ) } ` ;
2125
+ }
2126
+ throw new Error ( errorMsg ) ;
2127
+ }
2128
+ }
2007
2129
}
2008
2130
2009
2131
export function isDefaultLibraryFile ( filePath : string ) : boolean {
0 commit comments