mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(schematics): do not rerun migrations for each test-case (#13256)
Instead of running the whole migration transformations for *each* test-case, we should just run the schematic once per target version. This speeds up the case testing of the schematics, and makes it easier to develop more.
This commit is contained in:
committed by
Jeremy Elbourn
parent
884762f872
commit
f418f78978
@@ -1,8 +1,11 @@
|
||||
import {getSystemPath, normalize} from '@angular-devkit/core';
|
||||
import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing';
|
||||
import * as virtualFs from '@angular-devkit/core/src/virtual-fs/host';
|
||||
import {readFileSync} from 'fs';
|
||||
import {createTestApp} from '../../test-setup/test-app';
|
||||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
|
||||
import {runPostScheduledTasks} from '../../test-setup/post-scheduled-tasks';
|
||||
import {readFileSync, writeFileSync, mkdirpSync} from 'fs-extra';
|
||||
import {join, dirname} from 'path';
|
||||
import {createTestApp, migrationCollection} from '../../test-setup/test-app';
|
||||
|
||||
/** Module name suffix for data files of the `jasmine_node_test` Bazel rule. */
|
||||
const bazelModuleSuffix = 'angular_material/src/lib/schematics/update/test-cases';
|
||||
@@ -25,15 +28,13 @@ export function resolveBazelDataFile(filePath: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test app schematic tree that includes the specified test case as TypeScript
|
||||
* entry point file. Also writes the app tree to a real file system location in order to be
|
||||
* able to test the tslint fix rules.
|
||||
* Creates a test app schematic tree that will be copied over to a real filesystem location.
|
||||
* This is necessary because TSLint is not able to read from the virtual filesystem tree.
|
||||
*/
|
||||
export function createTestAppWithTestCase(testCaseInputPath: string) {
|
||||
export function createFileSystemTestApp() {
|
||||
const tempFileSystemHost = new TempScopedNodeJsSyncHost();
|
||||
const appTree = createTestApp();
|
||||
|
||||
appTree.overwrite('/projects/material/src/main.ts', readFileContent(testCaseInputPath));
|
||||
const tempPath = getSystemPath(tempFileSystemHost.root);
|
||||
|
||||
// Since the TSLint fix task expects all files to be present on the real file system, we
|
||||
// map every file in the app tree to a temporary location on the file system.
|
||||
@@ -41,8 +42,40 @@ export function createTestAppWithTestCase(testCaseInputPath: string) {
|
||||
tempFileSystemHost.sync.write(f, virtualFs.stringToFileBuffer(appTree.readContent(f)));
|
||||
});
|
||||
|
||||
// Switch to the new temporary directory because otherwise TSLint cannot read the files.
|
||||
process.chdir(getSystemPath(tempFileSystemHost.root));
|
||||
|
||||
return appTree;
|
||||
return {appTree, tempPath};
|
||||
}
|
||||
|
||||
export async function runTestCases(migrationName: string, inputs: {[name: string]: string}) {
|
||||
const runner = new SchematicTestRunner('schematics', migrationCollection);
|
||||
const inputNames = Object.keys(inputs);
|
||||
const initialWorkingDir = process.cwd();
|
||||
|
||||
let logOutput = '';
|
||||
runner.logger.subscribe(entry => logOutput += entry.message);
|
||||
|
||||
const {appTree, tempPath} = createFileSystemTestApp();
|
||||
|
||||
// Write each test-case input to the file-system. This is necessary because otherwise
|
||||
// TSLint won't be able to pick up the test cases.
|
||||
inputNames.forEach(inputName => {
|
||||
const tempInputPath = join(tempPath, `projects/material/src/test-cases/${inputName}.ts`);
|
||||
|
||||
mkdirpSync(dirname(tempInputPath));
|
||||
writeFileSync(tempInputPath, readFileContent(inputs[inputName]));
|
||||
});
|
||||
|
||||
runner.runSchematic(migrationName, {}, appTree);
|
||||
|
||||
// Switch to the new temporary directory because otherwise TSLint cannot read the files.
|
||||
process.chdir(tempPath);
|
||||
|
||||
// Run the scheduled TSLint fix task from the update schematic. This task is responsible for
|
||||
// identifying outdated code parts and performs the fixes. Since tasks won't run automatically
|
||||
// within a `SchematicTestRunner`, we manually need to run the scheduled task.
|
||||
await runPostScheduledTasks(runner, 'tslint-fix').toPromise();
|
||||
|
||||
// Switch back to the initial working directory.
|
||||
process.chdir(initialWorkingDir);
|
||||
|
||||
return {tempPath, logOutput};
|
||||
}
|
||||
|
||||
@@ -1,51 +1,42 @@
|
||||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
|
||||
import {runPostScheduledTasks} from '../../../test-setup/post-scheduled-tasks';
|
||||
import {migrationCollection} from '../../../test-setup/test-app';
|
||||
import {createTestAppWithTestCase, resolveBazelDataFile} from '../index.spec';
|
||||
import {resolveBazelDataFile, runTestCases} from '../index.spec';
|
||||
|
||||
describe('constructor checks', () => {
|
||||
|
||||
it('should properly report invalid constructor expression signatures', async () => {
|
||||
const inputPath = resolveBazelDataFile(`misc/constructor-checks_input.ts`);
|
||||
const runner = new SchematicTestRunner('schematics', migrationCollection);
|
||||
const {logOutput} = await runTestCases('migration-01', {
|
||||
'constructor-checks': resolveBazelDataFile(`misc/constructor-checks_input.ts`)
|
||||
});
|
||||
|
||||
runner.runSchematic('migration-01', {}, createTestAppWithTestCase(inputPath));
|
||||
|
||||
let output = '';
|
||||
runner.logger.subscribe(entry => output += entry.message);
|
||||
|
||||
await runPostScheduledTasks(runner, 'tslint-fix').toPromise();
|
||||
|
||||
expect(output).toMatch(/\[22.*Found "NativeDateAdapter"/,
|
||||
expect(logOutput).toMatch(/\[22.*Found "NativeDateAdapter"/,
|
||||
'Expected the constructor checks to report if an argument is not assignable.');
|
||||
expect(output).not.toMatch(/\[26.*Found "NativeDateAdapter".*/,
|
||||
expect(logOutput).not.toMatch(/\[26.*Found "NativeDateAdapter".*/,
|
||||
'Expected the constructor to not report if an argument is assignable.');
|
||||
|
||||
expect(output).not.toMatch(/Found "NonMaterialClass".*: new NonMaterialClass\(\)/);
|
||||
expect(logOutput).not.toMatch(/Found "NonMaterialClass".*: new NonMaterialClass\(\)/);
|
||||
|
||||
expect(output).toMatch(/Found "NativeDateAdapter".*super.*: super\(string, Platform\)/);
|
||||
expect(output).toMatch(/Found "NativeDateAdapter".*: new \w+\(string, Platform\)/);
|
||||
expect(logOutput).toMatch(/Found "NativeDateAdapter".*super.*: super\(string, Platform\)/);
|
||||
expect(logOutput).toMatch(/Found "NativeDateAdapter".*: new \w+\(string, Platform\)/);
|
||||
|
||||
expect(output).toMatch(/Found "MatAutocomplete".*super.*: super\(any, any, string\[]\)/);
|
||||
expect(output).toMatch(/Found "MatAutocomplete".*: new \w+\(any, any, string\[]\)/);
|
||||
expect(logOutput).toMatch(/Found "MatAutocomplete".*super.*: super\(any, any, string\[]\)/);
|
||||
expect(logOutput).toMatch(/Found "MatAutocomplete".*: new \w+\(any, any, string\[]\)/);
|
||||
|
||||
expect(output).toMatch(/Found "MatTooltip".*super.*: super\((any, ){10}{ opt1: string; }\)/);
|
||||
expect(output).toMatch(/Found "MatTooltip".*: new \w+\((any, ){10}{ opt1: string; }\)/);
|
||||
expect(logOutput).toMatch(/Found "MatTooltip".*super.*: super\((any, ){10}{ opt1: string; }\)/);
|
||||
expect(logOutput).toMatch(/Found "MatTooltip".*: new \w+\((any, ){10}{ opt1: string; }\)/);
|
||||
|
||||
expect(output).toMatch(/Found "MatIconRegistry".*super.*: super\(any, any, Document\)/);
|
||||
expect(output).toMatch(/Found "MatIconRegistry".*: new \w+\(any, any, Document\)/);
|
||||
expect(logOutput).toMatch(/Found "MatIconRegistry".*super.*: super\(any, any, Document\)/);
|
||||
expect(logOutput).toMatch(/Found "MatIconRegistry".*: new \w+\(any, any, Document\)/);
|
||||
|
||||
expect(output).toMatch(/Found "MatCalendar".*super.*: super\(any, any, any, any\)/);
|
||||
expect(output).toMatch(/Found "MatCalendar".*: new \w+\(any, any, any, any\)/);
|
||||
expect(logOutput).toMatch(/Found "MatCalendar".*super.*: super\(any, any, any, any\)/);
|
||||
expect(logOutput).toMatch(/Found "MatCalendar".*: new \w+\(any, any, any, any\)/);
|
||||
|
||||
expect(output).toMatch(/Found "MatDrawerContent".*super.*: super\((any, ){4}any\)/);
|
||||
expect(output).toMatch(/Found "MatDrawerContent".*: new \w+\((any, ){4}any\)/);
|
||||
expect(logOutput).toMatch(/Found "MatDrawerContent".*super.*: super\((any, ){4}any\)/);
|
||||
expect(logOutput).toMatch(/Found "MatDrawerContent".*: new \w+\((any, ){4}any\)/);
|
||||
|
||||
expect(output).toMatch(/Found "MatSidenavContent".*super.*: super\((any, ){4}any\)/);
|
||||
expect(output).toMatch(/Found "MatSidenavContent".*: new \w+\((any, ){4}any\)/);
|
||||
expect(logOutput).toMatch(/Found "MatSidenavContent".*super.*: super\((any, ){4}any\)/);
|
||||
expect(logOutput).toMatch(/Found "MatSidenavContent".*: new \w+\((any, ){4}any\)/);
|
||||
|
||||
expect(output).toMatch(/Found "ExtendedDateAdapter".*super.*: super\(string, Platform\)/);
|
||||
expect(output).toMatch(/Found "ExtendedDateAdapter".*: new \w+\(string, Platform\)/);
|
||||
expect(logOutput).toMatch(/Found "ExtendedDateAdapter".*super.*: super\(string, Platform\)/);
|
||||
expect(logOutput).toMatch(/Found "ExtendedDateAdapter".*: new \w+\(string, Platform\)/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
|
||||
import {runPostScheduledTasks} from '../../../test-setup/post-scheduled-tasks';
|
||||
import {migrationCollection} from '../../../test-setup/test-app';
|
||||
import {createTestAppWithTestCase, resolveBazelDataFile} from '../index.spec';
|
||||
import {resolveBazelDataFile, runTestCases} from '../index.spec';
|
||||
|
||||
describe('v6 import misc checks', () => {
|
||||
|
||||
it('should report imports for deleted animation constants', async () => {
|
||||
const inputPath = resolveBazelDataFile(`misc/import-checks_input.ts`);
|
||||
const runner = new SchematicTestRunner('schematics', migrationCollection);
|
||||
const {logOutput} = await runTestCases('migration-01', {
|
||||
'import-checks': resolveBazelDataFile(`misc/import-checks_input.ts`)
|
||||
});
|
||||
|
||||
runner.runSchematic('migration-01', {}, createTestAppWithTestCase(inputPath));
|
||||
|
||||
let output = '';
|
||||
runner.logger.subscribe(entry => output += entry.message);
|
||||
|
||||
await runPostScheduledTasks(runner, 'tslint-fix').toPromise();
|
||||
|
||||
expect(output).toMatch(/Found deprecated symbol "SHOW_ANIMATION"/);
|
||||
expect(output).toMatch(/Found deprecated symbol "HIDE_ANIMATION"/);
|
||||
expect(logOutput).toMatch(/Found deprecated symbol "SHOW_ANIMATION"/);
|
||||
expect(logOutput).toMatch(/Found deprecated symbol "HIDE_ANIMATION"/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
|
||||
import {runPostScheduledTasks} from '../../../test-setup/post-scheduled-tasks';
|
||||
import {migrationCollection} from '../../../test-setup/test-app';
|
||||
import {createTestAppWithTestCase, resolveBazelDataFile} from '../index.spec';
|
||||
import {resolveBazelDataFile, runTestCases} from '../index.spec';
|
||||
|
||||
describe('v6 method call checks', () => {
|
||||
|
||||
it('should properly report invalid method calls', async () => {
|
||||
const inputPath = resolveBazelDataFile(`misc/method-call-checks_input.ts`);
|
||||
const runner = new SchematicTestRunner('schematics', migrationCollection);
|
||||
const {logOutput} = await runTestCases('migration-01', {
|
||||
'method-call-checks': resolveBazelDataFile(`misc/method-call-checks_input.ts`)
|
||||
});
|
||||
|
||||
runner.runSchematic('migration-01', {}, createTestAppWithTestCase(inputPath));
|
||||
|
||||
let output = '';
|
||||
runner.logger.subscribe(entry => output += entry.message);
|
||||
|
||||
await runPostScheduledTasks(runner, 'tslint-fix').toPromise();
|
||||
|
||||
expect(output)
|
||||
expect(logOutput)
|
||||
.toMatch(/\[15,.*Found call to "FocusMonitor\.monitor".*renderer.*has been removed/);
|
||||
expect(output)
|
||||
expect(logOutput)
|
||||
.toMatch(/\[16,.*Found call to "FocusMonitor\.monitor".*renderer.*has been removed/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
|
||||
import {runPostScheduledTasks} from '../../test-setup/post-scheduled-tasks';
|
||||
import {migrationCollection} from '../../test-setup/test-app';
|
||||
import {createTestAppWithTestCase, readFileContent, resolveBazelDataFile} from './index.spec';
|
||||
import {join} from 'path';
|
||||
import {readFileContent, resolveBazelDataFile, runTestCases} from './index.spec';
|
||||
|
||||
describe('v6 upgrade test cases', () => {
|
||||
|
||||
@@ -19,24 +17,27 @@ describe('v6 upgrade test cases', () => {
|
||||
'v6/property-names',
|
||||
];
|
||||
|
||||
let testCasesOutputPath: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const testCaseInputs = testCases.reduce((inputs, testCaseName) => {
|
||||
inputs[testCaseName] = resolveBazelDataFile(`${testCaseName}_input.ts`);
|
||||
return inputs;
|
||||
}, {});
|
||||
|
||||
const {tempPath} = await runTestCases('migration-01', testCaseInputs);
|
||||
|
||||
testCasesOutputPath = join(tempPath, 'projects/material/src/test-cases/');
|
||||
});
|
||||
|
||||
// Iterates through every test case directory and generates a jasmine test block that will
|
||||
// verify that the update schematics properly update the test input to the expected output.
|
||||
// verify that the update schematics properly updated the test input to the expected output.
|
||||
testCases.forEach(testCaseName => {
|
||||
const inputPath = resolveBazelDataFile(`${testCaseName}_input.ts`);
|
||||
const expectedOutputPath = resolveBazelDataFile(`${testCaseName}_expected_output.ts`);
|
||||
|
||||
it(`should apply update schematics to test case: ${testCaseName}`, () => {
|
||||
const runner = new SchematicTestRunner('schematics', migrationCollection);
|
||||
|
||||
runner.runSchematic('migration-01', {}, createTestAppWithTestCase(inputPath));
|
||||
|
||||
// Run the scheduled TSLint fix task from the update schematic. This task is responsible for
|
||||
// identifying outdated code parts and performs the fixes. Since tasks won't run automatically
|
||||
// within a `SchematicTestRunner`, we manually need to run the scheduled task.
|
||||
return runPostScheduledTasks(runner, 'tslint-fix').toPromise().then(() => {
|
||||
expect(readFileContent('projects/material/src/main.ts'))
|
||||
.toBe(readFileContent(expectedOutputPath));
|
||||
});
|
||||
expect(readFileContent(join(testCasesOutputPath, `${testCaseName}.ts`)))
|
||||
.toBe(readFileContent(expectedOutputPath));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
|
||||
import {runPostScheduledTasks} from '../../test-setup/post-scheduled-tasks';
|
||||
import {migrationCollection} from '../../test-setup/test-app';
|
||||
import {createTestAppWithTestCase, readFileContent, resolveBazelDataFile} from './index.spec';
|
||||
import {join} from 'path';
|
||||
import {readFileContent, resolveBazelDataFile, runTestCases} from './index.spec';
|
||||
|
||||
describe('v7 upgrade test cases', () => {
|
||||
|
||||
@@ -14,26 +12,28 @@ describe('v7 upgrade test cases', () => {
|
||||
'v7/ripple-speed-factor',
|
||||
];
|
||||
|
||||
let testCasesOutputPath: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const testCaseInputs = testCases.reduce((inputs, testCaseName) => {
|
||||
inputs[testCaseName] = resolveBazelDataFile(`${testCaseName}_input.ts`);
|
||||
return inputs;
|
||||
}, {});
|
||||
|
||||
const {tempPath} = await runTestCases('migration-02', testCaseInputs);
|
||||
|
||||
testCasesOutputPath = join(tempPath, 'projects/material/src/test-cases/');
|
||||
});
|
||||
|
||||
// Iterates through every test case directory and generates a jasmine test block that will
|
||||
// verify that the update schematics properly update the test input to the expected output.
|
||||
// verify that the update schematics properly updated the test input to the expected output.
|
||||
testCases.forEach(testCaseName => {
|
||||
const inputPath = resolveBazelDataFile(`${testCaseName}_input.ts`);
|
||||
const expectedOutputPath = resolveBazelDataFile(`${testCaseName}_expected_output.ts`);
|
||||
|
||||
it(`should apply update schematics to test case: ${testCaseName}`, () => {
|
||||
const runner = new SchematicTestRunner('schematics', migrationCollection);
|
||||
|
||||
runner.runSchematic('migration-02', {}, createTestAppWithTestCase(inputPath));
|
||||
|
||||
// Run the scheduled TSLint fix task from the update schematic. This task is responsible for
|
||||
// identifying outdated code parts and performs the fixes. Since tasks won't run automatically
|
||||
// within a `SchematicTestRunner`, we manually need to run the scheduled task.
|
||||
return runPostScheduledTasks(runner, 'tslint-fix').toPromise().then(() => {
|
||||
expect(readFileContent('projects/material/src/main.ts'))
|
||||
.toBe(readFileContent(expectedOutputPath));
|
||||
});
|
||||
expect(readFileContent(join(testCasesOutputPath, `${testCaseName}.ts`)))
|
||||
.toBe(readFileContent(expectedOutputPath));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user