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:
Paul Gschwendtner
2018-09-24 19:58:32 +02:00
committed by Jeremy Elbourn
parent 884762f872
commit f418f78978
6 changed files with 116 additions and 109 deletions
@@ -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));
});
});
});