forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(compiler-cli): create "full compile" compliance test rules (angu…
…lar#39617) This commit contains the basic runner logic and a couple of sample test cases for the "full compile" compliance tests, where source files are compiled to full definitions and checked against expectations. PR Close angular#39617
- Loading branch information
1 parent
8d445e0
commit 793d66a
Showing
22 changed files
with
1,117 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
# Compliance test-cases | ||
|
||
This directory contains rules, helpers and test-cases for the Angular compiler compliance tests. | ||
|
||
There are three different types of tests that are run based on file-based "test-cases". | ||
|
||
* **Full compile** - in this test the source files defined by the test-case are fully compiled by Angular. | ||
The generated files are compared to "expected files" via a matching algorithm that is tolerant to | ||
whitespace and variable name changes. | ||
* **Partial compile** - in this test the source files defined by the test-case are "partially" compiled by | ||
Angular to produce files that can be published. These partially compiled files are compared directly | ||
against "golden files" to ensure that we do not inadvertently break the public API of partial | ||
declarations. | ||
* **Linked** - in this test the golden files mentioned in the previous bullet point, are passed to the | ||
Angular linker, which generates files that are comparable to the fully compiled files. These linked | ||
files are compared against the "expected files" in the same way as in the "full compile" tests. | ||
|
||
This way the compliance tests are able to check each mode and stage of compilation is accurate and does | ||
not change unexpectedly. | ||
|
||
|
||
## Defining a test-case | ||
|
||
To define a test-case, create a new directory below `test_cases`. In this directory | ||
|
||
* add a new file called `TEST_CASES.json`. The format of this file is described below. | ||
* add an empty `GOLDEN_PARTIAL.js` file. This file will be updated by the tooling later. | ||
* add any `inputFiles` that will be compiled as part of the test-case. | ||
* add any `expected` files that will be compared to the files generated by compiling the source files. | ||
|
||
|
||
### TEST_CASES.json format | ||
|
||
The `TEST_CASES.json` defines an object with one or more test-case definitions in the `cases` property. | ||
|
||
Each test-case can specify: | ||
|
||
* A `description` of the test. | ||
* The `inputFiles` that will be compiled. | ||
* Additional `compilerOptions` and `angularCompilerOptions` that are passed to the compiler. | ||
* A collection of `expectations` definitions that will be checked against the generated files. | ||
|
||
Note that there is a JSON schema for the `TEST_CASES.json` file stored at `test_cases/test_case_schema.json`. | ||
You should add a link to this schema at the top of your `TEST_CASES.json` file to provide | ||
validation and intellisense for this file in your IDE. | ||
|
||
For example: | ||
|
||
```json | ||
{ | ||
"$schema": "../test_case_schema.json", | ||
"cases": [ | ||
{ | ||
"description": "description of the test - equivalent to an `it` clause message.", | ||
"inputFiles": ["abc.ts"], | ||
"expectations": [ | ||
{ | ||
"failureMessage": "message to display if this expectation fails", | ||
"files": [ | ||
{ "expected": "xyz.js", "generated": "abc.js" }, ... | ||
] | ||
}, ... | ||
], | ||
"compilerOptions": { ... }, | ||
"angularCompilerOptions": { ... } | ||
} | ||
] | ||
} | ||
``` | ||
|
||
### Input files | ||
|
||
The input files are the source file that will be compiled as part of this test-case. | ||
Input files should be stored in the directory next to the `TEST_CASES.json`. | ||
The paths to the input files should be listed in the `inputFiles` property of `TEST_CASES.json`. | ||
The paths are relative to the `TEST_CASES.json` file. | ||
|
||
If no `inputFiles` property is provided, the default is `["test.ts"]`. | ||
|
||
|
||
### Expectations | ||
|
||
An expectation consists of a collection of expected `files` pairs, and a `failureMessage`, which | ||
is displayed if the expectation check fails. | ||
|
||
Each file-pair consists of a path to a `generated` file (relative to the build output folder), | ||
and a path to an `expected` file (relative to the test case). | ||
|
||
The `generated` file is checked to see if it "matches" the `expected` file. The matching is | ||
resilient to whitespace and variable name changes. | ||
|
||
If no `failureMessage` property is provided, the default is `"Incorrect generated output."`. | ||
|
||
If no `files` property is provided, the default is a a collection of objects `{expected, generated}`, | ||
where `expected` and `generated` are computed by taking each path in the `inputFiles` collection | ||
and replacing the `.ts` extension with `.js`. | ||
|
||
|
||
## Running tests | ||
|
||
The simplest way to run all the compliance tests is: | ||
|
||
```sh | ||
yarn test-ivy-aot //packages/compiler-cli/test/compliance/... | ||
``` | ||
|
||
If you only want to run one of the three types of test you can be more specific: | ||
|
||
```sh | ||
yarn test-ivy-aot //packages/compiler-cli/test/compliance/full | ||
yarn test-ivy-aot //packages/compiler-cli/test/compliance/linked | ||
yarn test-ivy-aot //packages/compiler-cli/test/compliance/test_cases/... | ||
``` | ||
|
||
(The last command runs the partial compilation tests.) | ||
|
||
|
||
## Updating a golden partial file | ||
|
||
There is one golden partial file per `TEST_CASES.json` file. So even if this file defines multiple | ||
test-cases, which each contain multiple input files, there will only be one golden file. | ||
|
||
The golden file is generated by the tooling and should not be modified manually. | ||
|
||
When you first create a test-case, with an empty `PARTIAL_GOLDEN.js` file, or a change is made to | ||
the generated partial output, we must update the `PARTIAL_GOLDEN.js` file. | ||
|
||
This is done by running a specific bazel rule of the form: | ||
|
||
```sh | ||
bazel run //packages/compiler-cli/test/compliance/test_cases:<path/to/test_case>.golden.update | ||
``` | ||
|
||
where to replace `<path/to/test_case>` with the path (relative to `test_cases`) of the directory | ||
that contains the `PARTIAL_GOLDEN.js` to update. | ||
|
||
|
||
## Debugging test-cases | ||
|
||
Compliance tests are basically `jasmine_node_test` rules. As such, they can be debugged | ||
just like any other `jasmine_node_test`. The standard approach is to add `--config=debug` | ||
to the Bazel test command. | ||
|
||
It is useful when debugging to focus on a single test-case. | ||
|
||
|
||
### Focusing test-cases | ||
|
||
You can focus a test case by setting `"focusTest": true` in the `TEST_CASES.json` file. | ||
This is equivalent to using jasmine `fit()`. | ||
|
||
|
||
### Excluding test-cases | ||
|
||
You can exclude a test case by setting `"excludeTest": true` in the `TEST_CASES.json` file. | ||
This is equivalent to using jasmine `xit()`. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library") | ||
|
||
ts_library( | ||
name = "test_lib", | ||
testonly = True, | ||
srcs = ["full_compile_spec.ts"], | ||
deps = [ | ||
"//packages/compiler-cli/src/ngtsc/file_system", | ||
"//packages/compiler-cli/test/compliance/test_helpers", | ||
], | ||
) | ||
|
||
jasmine_node_test( | ||
name = "full", | ||
bootstrap = ["//tools/testing:node_no_angular_es5"], | ||
data = [ | ||
"//packages/compiler-cli/test/compliance/test_cases", | ||
"//packages/compiler-cli/test/ngtsc/fake_core:npm_package", | ||
], | ||
shard_count = 2, | ||
tags = [ | ||
"ivy-only", | ||
], | ||
deps = [ | ||
":test_lib", | ||
], | ||
) |
23 changes: 23 additions & 0 deletions
23
packages/compiler-cli/test/compliance/full/full_compile_spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* 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 | ||
*/ | ||
import {FileSystem} from '../../../src/ngtsc/file_system'; | ||
import {compileTest} from '../test_helpers/compile_test'; | ||
import {ComplianceTest} from '../test_helpers/get_compliance_tests'; | ||
import {runTests} from '../test_helpers/test_runner'; | ||
|
||
runTests('full compile', compileTests); | ||
|
||
/** | ||
* Fully compile all the input files in the given `test`. | ||
* | ||
* @param fs The mock file-system where the input files can be found. | ||
* @param test The compliance test whose input files should be compiled. | ||
*/ | ||
function compileTests(fs: FileSystem, test: ComplianceTest): void { | ||
compileTest(fs, test.inputFiles, test.compilerOptions, test.angularCompilerOptions); | ||
} |
11 changes: 11 additions & 0 deletions
11
packages/compiler-cli/test/compliance/test_cases/BUILD.bazel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") | ||
|
||
package(default_visibility = ["//packages/compiler-cli/test/compliance:__subpackages__"]) | ||
|
||
# Normally we would use `file_group` here but this doesn't work on Windows because there | ||
# appears to be a bug with making the `runfiles` folder available. | ||
# See https://github.com/bazelbuild/rules_nodejs/issues/1689 | ||
copy_to_bin( | ||
name = "test_cases", | ||
srcs = glob(["*/**"]), | ||
) |
47 changes: 47 additions & 0 deletions
47
...compiler-cli/test/compliance/test_cases/r3_view_compiler/interpolations/GOLDEN_PARTIAL.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/**************************************************************************************************** | ||
* PARTIAL FILE: test.js | ||
****************************************************************************************************/ | ||
import { Component, NgModule } from '@angular/core'; | ||
import * as i0 from "@angular/core"; | ||
export class MyApp { | ||
constructor() { | ||
this.list = []; | ||
} | ||
} | ||
MyApp.ɵfac = function MyApp_Factory(t) { return new (t || MyApp)(); }; | ||
MyApp.ɵcmp = i0.ɵɵdefineComponent({ type: MyApp, selectors: [["my-app"]], decls: 1, vars: 9, template: function MyApp_Template(rf, ctx) { if (rf & 1) { | ||
i0.ɵɵtext(0); | ||
} if (rf & 2) { | ||
i0.ɵɵtextInterpolateV([" ", ctx.list[0], " ", ctx.list[1], " ", ctx.list[2], " ", ctx.list[3], " ", ctx.list[4], " ", ctx.list[5], " ", ctx.list[6], " ", ctx.list[7], " ", ctx.list[8], " "]); | ||
} }, encapsulation: 2 }); | ||
/*@__PURE__*/ (function () { i0.ɵsetClassMetadata(MyApp, [{ | ||
type: Component, | ||
args: [{ | ||
selector: 'my-app', | ||
template: ' {{list[0]}} {{list[1]}} {{list[2]}} {{list[3]}} {{list[4]}} {{list[5]}} {{list[6]}} {{list[7]}} {{list[8]}} ' | ||
}] | ||
}], null, null); })(); | ||
export class MyModule { | ||
} | ||
MyModule.ɵmod = i0.ɵɵdefineNgModule({ type: MyModule }); | ||
MyModule.ɵinj = i0.ɵɵdefineInjector({ factory: function MyModule_Factory(t) { return new (t || MyModule)(); } }); | ||
(function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(MyModule, { declarations: [MyApp] }); })(); | ||
/*@__PURE__*/ (function () { i0.ɵsetClassMetadata(MyModule, [{ | ||
type: NgModule, | ||
args: [{ declarations: [MyApp] }] | ||
}], null, null); })(); | ||
|
||
/**************************************************************************************************** | ||
* PARTIAL FILE: test.d.ts | ||
****************************************************************************************************/ | ||
import * as i0 from "@angular/core"; | ||
export declare class MyApp { | ||
list: any[]; | ||
static ɵfac: i0.ɵɵFactoryDef<MyApp, never>; | ||
static ɵcmp: i0.ɵɵComponentDefWithMeta<MyApp, "my-app", never, {}, {}, never, never>; | ||
} | ||
export declare class MyModule { | ||
static ɵmod: i0.ɵɵNgModuleDefWithMeta<MyModule, [typeof MyApp], never, never>; | ||
static ɵinj: i0.ɵɵInjectorDef<MyModule>; | ||
} | ||
|
13 changes: 13 additions & 0 deletions
13
...s/compiler-cli/test/compliance/test_cases/r3_view_compiler/interpolations/TEST_CASES.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"$schema": "../test_case_schema.json", | ||
"cases": [ | ||
{ | ||
"description": "should generate a correct call to `ɵɵtextInterpolateV()` with more than 8 interpolations", | ||
"expectations": [ | ||
{ | ||
"failureMessage": "Incorrect `ɵɵtextInterpolateV()` call" | ||
} | ||
] | ||
} | ||
] | ||
} |
11 changes: 11 additions & 0 deletions
11
packages/compiler-cli/test/compliance/test_cases/r3_view_compiler/interpolations/test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
function MyApp_Template(rf, ctx) { | ||
if (rf & 1) { | ||
$i0$.ɵɵtext(0); | ||
} | ||
if (rf & 2) { | ||
$i0$.ɵɵtextInterpolateV([ | ||
" ", ctx.list[0], " ", ctx.list[1], " ", ctx.list[2], " ", ctx.list[3], " ", ctx.list[4], " ", | ||
ctx.list[5], " ", ctx.list[6], " ", ctx.list[7], " ", ctx.list[8], " " | ||
]); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
packages/compiler-cli/test/compliance/test_cases/r3_view_compiler/interpolations/test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import {Component, NgModule} from '@angular/core'; | ||
|
||
@Component({ | ||
selector: 'my-app', | ||
template: | ||
' {{list[0]}} {{list[1]}} {{list[2]}} {{list[3]}} {{list[4]}} {{list[5]}} {{list[6]}} {{list[7]}} {{list[8]}} ' | ||
}) | ||
export class MyApp { | ||
list: any[] = []; | ||
} | ||
|
||
@NgModule({declarations: [MyApp]}) | ||
export class MyModule { | ||
} |
50 changes: 50 additions & 0 deletions
50
...t/compliance/test_cases/r3_view_compiler_directives/directives/matching/GOLDEN_PARTIAL.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/**************************************************************************************************** | ||
* PARTIAL FILE: test.js | ||
****************************************************************************************************/ | ||
import { Component, Directive, NgModule } from '@angular/core'; | ||
import * as i0 from "@angular/core"; | ||
export class I18nDirective { | ||
} | ||
I18nDirective.ɵfac = function I18nDirective_Factory(t) { return new (t || I18nDirective)(); }; | ||
I18nDirective.ɵdir = i0.ɵɵngDeclareDirective({ version: 1, type: I18nDirective, selector: "[i18n]", ngImport: i0 }); | ||
/*@__PURE__*/ (function () { i0.ɵsetClassMetadata(I18nDirective, [{ | ||
type: Directive, | ||
args: [{ selector: '[i18n]' }] | ||
}], null, null); })(); | ||
export class MyComponent { | ||
} | ||
MyComponent.ɵfac = function MyComponent_Factory(t) { return new (t || MyComponent)(); }; | ||
MyComponent.ɵcmp = i0.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], decls: 1, vars: 0, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { | ||
i0.ɵɵelement(0, "div"); | ||
} }, encapsulation: 2 }); | ||
/*@__PURE__*/ (function () { i0.ɵsetClassMetadata(MyComponent, [{ | ||
type: Component, | ||
args: [{ selector: 'my-component', template: '<div i18n></div>' }] | ||
}], null, null); })(); | ||
export class MyModule { | ||
} | ||
MyModule.ɵmod = i0.ɵɵdefineNgModule({ type: MyModule }); | ||
MyModule.ɵinj = i0.ɵɵdefineInjector({ factory: function MyModule_Factory(t) { return new (t || MyModule)(); } }); | ||
(function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(MyModule, { declarations: [I18nDirective, MyComponent] }); })(); | ||
/*@__PURE__*/ (function () { i0.ɵsetClassMetadata(MyModule, [{ | ||
type: NgModule, | ||
args: [{ declarations: [I18nDirective, MyComponent] }] | ||
}], null, null); })(); | ||
|
||
/**************************************************************************************************** | ||
* PARTIAL FILE: test.d.ts | ||
****************************************************************************************************/ | ||
import * as i0 from "@angular/core"; | ||
export declare class I18nDirective { | ||
static ɵfac: i0.ɵɵFactoryDef<I18nDirective, never>; | ||
static ɵdir: i0.ɵɵDirectiveDefWithMeta<I18nDirective, "[i18n]", never, {}, {}, never>; | ||
} | ||
export declare class MyComponent { | ||
static ɵfac: i0.ɵɵFactoryDef<MyComponent, never>; | ||
static ɵcmp: i0.ɵɵComponentDefWithMeta<MyComponent, "my-component", never, {}, {}, never, never>; | ||
} | ||
export declare class MyModule { | ||
static ɵmod: i0.ɵɵNgModuleDefWithMeta<MyModule, [typeof I18nDirective, typeof MyComponent], never, never>; | ||
static ɵinj: i0.ɵɵInjectorDef<MyModule>; | ||
} | ||
|
Oops, something went wrong.