| 
									
										
										
										
											2018-02-28 06:51:40 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2018-02-28 06:51:40 -08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-17 17:30:27 +01:00
										 |  |  | import * as crypto from 'crypto'; | 
					
						
							| 
									
										
										
										
											2018-03-22 10:37:43 -07:00
										 |  |  | import {createPatch} from 'diff'; | 
					
						
							| 
									
										
										
										
											2018-02-28 06:51:40 -08:00
										 |  |  | import * as fs from 'fs'; | 
					
						
							|  |  |  | import * as path from 'path'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-22 14:27:30 -08:00
										 |  |  | /** Runfiles helper from bazel to resolve file name paths.  */ | 
					
						
							|  |  |  | const runfiles = require(process.env['BAZEL_NODE_RUNFILES_HELPER']!); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  | type TestPackage = { | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |   displayName: string; packagePath: string; goldenFilePath: string; | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  | const packagesToTest: TestPackage[] = [ | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |   { | 
					
						
							|  |  |  |     displayName: 'Example NPM package', | 
					
						
							|  |  |  |     // Resolve the "npm_package" directory by using the runfile resolution. Note that we need to
 | 
					
						
							|  |  |  |     // resolve the "package.json" of the package since otherwise NodeJS would resolve the "main"
 | 
					
						
							|  |  |  |     // file, which is not necessarily at the root of the "npm_package".
 | 
					
						
							| 
									
										
										
										
											2021-02-22 14:27:30 -08:00
										 |  |  |     packagePath: path.dirname(runfiles.resolve( | 
					
						
							|  |  |  |         'angular/packages/bazel/test/ng_package/example/npm_package/package.json')), | 
					
						
							|  |  |  |     goldenFilePath: runfiles.resolvePackageRelative('./example_package.golden') | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |   }, | 
					
						
							| 
									
										
										
										
											2019-09-11 22:21:34 +02:00
										 |  |  |   { | 
					
						
							|  |  |  |     displayName: 'Example with ts_library NPM package', | 
					
						
							|  |  |  |     // Resolve the "npm_package" directory by using the runfile resolution. Note that we need to
 | 
					
						
							|  |  |  |     // resolve the "package.json" of the package since otherwise NodeJS would resolve the "main"
 | 
					
						
							|  |  |  |     // file, which is not necessarily at the root of the "npm_package".
 | 
					
						
							| 
									
										
										
										
											2021-02-22 14:27:30 -08:00
										 |  |  |     packagePath: path.dirname(runfiles.resolve( | 
					
						
							| 
									
										
										
										
											2019-09-11 22:21:34 +02:00
										 |  |  |         'angular/packages/bazel/test/ng_package/example-with-ts-library/npm_package/package.json')), | 
					
						
							| 
									
										
										
										
											2021-02-22 14:27:30 -08:00
										 |  |  |     goldenFilePath: runfiles.resolvePackageRelative('./example_with_ts_library_package.golden') | 
					
						
							| 
									
										
										
										
											2019-09-11 22:21:34 +02:00
										 |  |  |   }, | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  | ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Gets all entries in a given directory (files and directories) recursively, | 
					
						
							|  |  |  |  * indented based on each entry's depth. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param directoryPath Path of the directory for which to get entries. | 
					
						
							|  |  |  |  * @param depth The depth of this directory (used for indentation). | 
					
						
							|  |  |  |  * @returns Array of all indented entries (files and directories). | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function getIndentedDirectoryStructure(directoryPath: string, depth = 0): string[] { | 
					
						
							| 
									
										
										
										
											2018-03-15 18:04:34 -07:00
										 |  |  |   const result: string[] = []; | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  |   if (fs.statSync(directoryPath).isDirectory()) { | 
					
						
							| 
									
										
										
										
											2018-12-25 23:16:56 +01:00
										 |  |  |     // We need to sort the directories because on Windows "readdirsync" is not sorted. Since we
 | 
					
						
							|  |  |  |     // compare these in a golden file, the order needs to be consistent across different platforms.
 | 
					
						
							|  |  |  |     fs.readdirSync(directoryPath).sort().forEach(f => { | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |       const filePath = path.posix.join(directoryPath, f); | 
					
						
							| 
									
										
										
										
											2018-03-15 18:04:34 -07:00
										 |  |  |       result.push( | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |           '  '.repeat(depth) + filePath, ...getIndentedDirectoryStructure(filePath, depth + 1)); | 
					
						
							| 
									
										
										
										
											2018-03-15 18:04:34 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return result; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-02-28 06:51:40 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Gets all file contents in a given directory recursively. Each file's content will be | 
					
						
							|  |  |  |  * prefixed with a one-line header with the file name. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param directoryPath Path of the directory for which file contents are collected. | 
					
						
							|  |  |  |  * @returns Array of all files' contents. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function getDescendantFilesContents(directoryPath: string): string[] { | 
					
						
							| 
									
										
										
										
											2018-03-15 18:04:34 -07:00
										 |  |  |   const result: string[] = []; | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  |   if (fs.statSync(directoryPath).isDirectory()) { | 
					
						
							| 
									
										
										
										
											2018-12-25 23:16:56 +01:00
										 |  |  |     // We need to sort the directories because on Windows "readdirsync" is not sorted. Since we
 | 
					
						
							|  |  |  |     // compare these in a golden file, the order needs to be consistent across different platforms.
 | 
					
						
							|  |  |  |     fs.readdirSync(directoryPath).sort().forEach(dir => { | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |       result.push(...getDescendantFilesContents(path.posix.join(directoryPath, dir))); | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2019-09-17 17:30:27 +01:00
										 |  |  |   // Binary files should equal the same as in the srcdir.
 | 
					
						
							|  |  |  |   else if (path.extname(directoryPath) === '.png') { | 
					
						
							|  |  |  |     result.push(`--- ${directoryPath} ---`, '', hashFileContents(directoryPath), ''); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |   // Note that we don't want to include ".map" files in the golden file since these are not
 | 
					
						
							|  |  |  |   // consistent across different environments (e.g. path delimiters)
 | 
					
						
							|  |  |  |   else if (path.extname(directoryPath) !== '.map') { | 
					
						
							| 
									
										
										
										
											2018-12-25 23:16:56 +01:00
										 |  |  |     result.push(`--- ${directoryPath} ---`, '', readFileContents(directoryPath), ''); | 
					
						
							| 
									
										
										
										
											2018-03-15 18:04:34 -07:00
										 |  |  |   } | 
					
						
							|  |  |  |   return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  | /** Accepts the current package output by overwriting the gold file in source control. */ | 
					
						
							|  |  |  | function acceptNewPackageGold(testPackage: TestPackage) { | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |   process.chdir(testPackage.packagePath); | 
					
						
							|  |  |  |   fs.writeFileSync(testPackage.goldenFilePath, getCurrentPackageContent(), 'utf-8'); | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Gets the content of the current package. Depends on the current working directory. */ | 
					
						
							|  |  |  | function getCurrentPackageContent() { | 
					
						
							|  |  |  |   return [...getIndentedDirectoryStructure('.'), ...getDescendantFilesContents('.')] | 
					
						
							|  |  |  |       .join('\n') | 
					
						
							|  |  |  |       .replace(/bazel-out\/.*\/bin/g, 'bazel-bin'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Compares the current package output to the gold file in source control in a jasmine test. */ | 
					
						
							|  |  |  | function runPackageGoldTest(testPackage: TestPackage) { | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |   const {displayName, packagePath, goldenFilePath} = testPackage; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   process.chdir(packagePath); | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // Gold file content from source control. We expect that the output of the package matches this.
 | 
					
						
							| 
									
										
										
										
											2019-07-25 15:38:13 +01:00
										 |  |  |   const expected = readFileContents(goldenFilePath); | 
					
						
							| 
									
										
										
										
											2018-03-22 10:37:43 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  |   // Actual file content generated from the rule.
 | 
					
						
							|  |  |  |   const actual = getCurrentPackageContent(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Without the `--accept` flag, compare the actual to the expected in a jasmine test.
 | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |   it(`Package "${displayName}"`, () => { | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  |     if (actual !== expected) { | 
					
						
							| 
									
										
										
										
											2018-03-22 10:37:43 -07:00
										 |  |  |       // Compute the patch and strip the header
 | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |       let patch = createPatch( | 
					
						
							|  |  |  |           goldenFilePath, expected, actual, 'Golden file', 'Generated file', {context: 5}); | 
					
						
							| 
									
										
										
										
											2018-03-22 10:37:43 -07:00
										 |  |  |       const endOfHeader = patch.indexOf('\n', patch.indexOf('\n') + 1) + 1; | 
					
						
							|  |  |  |       patch = patch.substring(endOfHeader); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  |       // Use string concatentation instead of whitespace inside a single template string
 | 
					
						
							|  |  |  |       // to make the structure message explicit.
 | 
					
						
							|  |  |  |       const failureMessage = `example ng_package differs from golden file\n` + | 
					
						
							|  |  |  |           `    Diff:\n` + | 
					
						
							|  |  |  |           `    ${patch}\n\n` + | 
					
						
							|  |  |  |           `    To accept the new golden file, run:\n` + | 
					
						
							| 
									
										
										
										
											2018-10-27 09:25:45 +02:00
										 |  |  |           `      yarn bazel run ${process.env['BAZEL_TARGET']}.accept\n`; | 
					
						
							| 
									
										
										
										
											2018-03-22 10:37:43 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  |       fail(failureMessage); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-02-28 06:51:40 -08:00
										 |  |  |   }); | 
					
						
							| 
									
										
										
										
											2018-03-15 18:04:34 -07:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-25 23:16:56 +01:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Reads the contents of the specified file. Additionally it strips all carriage return (CR) | 
					
						
							|  |  |  |  * characters from the given content. We do this since the content that will be pulled into the | 
					
						
							|  |  |  |  * golden file needs to be consistent across all platforms. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function readFileContents(filePath: string): string { | 
					
						
							|  |  |  |   return fs.readFileSync(filePath, 'utf8').replace(/\r/g, ''); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-17 17:30:27 +01:00
										 |  |  | function hashFileContents(filePath: string): string { | 
					
						
							|  |  |  |   return crypto.createHash('md5').update(fs.readFileSync(filePath)).digest('hex'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  | if (require.main === module) { | 
					
						
							|  |  |  |   const args = process.argv.slice(2); | 
					
						
							|  |  |  |   const acceptingNewGold = (args[0] === '--accept'); | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  |   if (acceptingNewGold) { | 
					
						
							|  |  |  |     for (let p of packagesToTest) { | 
					
						
							|  |  |  |       acceptNewPackageGold(p); | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-12-25 20:50:54 +01:00
										 |  |  | } else { | 
					
						
							|  |  |  |   describe('Comparing test packages to golds', () => { | 
					
						
							|  |  |  |     for (let p of packagesToTest) { | 
					
						
							|  |  |  |       runPackageGoldTest(p); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2018-04-02 16:52:13 -07:00
										 |  |  | } |