feat(dev-infra): add option to setup global types in API golden test (#42876)

Previously we disabled automatic type-resolution for the API
extractor because in non-sandbox environments this resulted in
different API reports. There are cases where global types are
still needed for analysis of an entry-point. To support this,
we add a new property called `types` which allows for explicit
type targets being specified.

Note that we do not want to determine types from the `data`
runfiles because API extractor itself also brings in types
which should not always be part of the API report analysis.

PR Close #42876
This commit is contained in:
Paul Gschwendtner 2021-07-16 20:18:18 +02:00 committed by Alex Rickabaugh
parent 7271ad15ba
commit 7eddd12bf0
4 changed files with 57 additions and 21 deletions

View File

@ -17,6 +17,27 @@ default_strip_export_pattern = "^ɵ(?!ɵdefineInjectable|ɵinject|ɵInjectableDe
def _escape_regex_for_arg(value):
return "\"%s\"" % value
"""
Extracts type names from a list of NPM type targets.
For example: Consider the `@npm//@types/node` target. This function extracts `node`
from the label. This is needed so that the Node types can be wired up within a
TypeScript program using the `types` tsconfig option.
"""
def extract_type_names_from_labels(type_targets):
type_names = []
for type_target in type_targets:
type_package = Label(type_target).package
if (type_package.startswith("@types/")):
type_names.append(type_package[len("@types/"):])
else:
fail("Expected type target to match the following format: " +
"`@<npm_workspace>//@types/<name>`, but got: %s" % type_target)
return type_names
"""
Builds an API report for the specified entry-point and compares it against the
specified golden
@ -28,6 +49,7 @@ def api_golden_test(
entry_point,
data = [],
strip_export_pattern = default_strip_export_pattern,
types = [],
**kwargs):
quoted_export_pattern = _escape_regex_for_arg(strip_export_pattern)
@ -46,13 +68,15 @@ def api_golden_test(
include_default_files = False,
)
test_data = ["//dev-infra/bazel/api-golden", "//:package.json", ":%s_data_typings" % name] + data
test_data = ["//dev-infra/bazel/api-golden", "//:package.json", ":%s_data_typings" % name] + \
data + types
nodejs_test(
name = name,
data = test_data,
entry_point = "//dev-infra/bazel/api-golden:index.ts",
templated_args = nodejs_test_args + [golden, entry_point, "false", quoted_export_pattern],
templated_args = nodejs_test_args + [golden, entry_point, "false", quoted_export_pattern] +
extract_type_names_from_labels(types),
**kwargs
)
@ -61,7 +85,8 @@ def api_golden_test(
testonly = True,
data = test_data,
entry_point = "//dev-infra/bazel/api-golden:index.ts",
templated_args = nodejs_test_args + [golden, entry_point, "true", quoted_export_pattern],
templated_args = nodejs_test_args + [golden, entry_point, "true", quoted_export_pattern] +
extract_type_names_from_labels(types),
**kwargs
)
@ -76,6 +101,7 @@ def api_golden_test_npm_package(
npm_package,
data = [],
strip_export_pattern = default_strip_export_pattern,
types = [],
**kwargs):
quoted_export_pattern = _escape_regex_for_arg(strip_export_pattern)
@ -83,17 +109,19 @@ def api_golden_test_npm_package(
nodejs_test(
name = name,
data = ["//dev-infra/bazel/api-golden"] + data,
data = ["//dev-infra/bazel/api-golden"] + data + types,
entry_point = "//dev-infra/bazel/api-golden:index_npm_packages.ts",
templated_args = nodejs_test_args + [golden_dir, npm_package, "false", quoted_export_pattern],
templated_args = nodejs_test_args + [golden_dir, npm_package, "false", quoted_export_pattern] +
extract_type_names_from_labels(types),
**kwargs
)
nodejs_binary(
name = name + ".accept",
testonly = True,
data = ["//dev-infra/bazel/api-golden"] + data,
data = ["//dev-infra/bazel/api-golden"] + data + types,
entry_point = "//dev-infra/bazel/api-golden:index_npm_packages.ts",
templated_args = nodejs_test_args + [golden_dir, npm_package, "true", quoted_export_pattern],
templated_args = nodejs_test_args + [golden_dir, npm_package, "true", quoted_export_pattern] +
extract_type_names_from_labels(types),
**kwargs
)

View File

@ -17,9 +17,9 @@ import {testApiGolden} from './test_api_report';
*/
async function main(
goldenFilePath: string, entryPointFilePath: string, approveGolden: boolean,
stripExportPattern: RegExp) {
const {succeeded, apiReportChanged} =
await testApiGolden(goldenFilePath, entryPointFilePath, approveGolden, stripExportPattern);
stripExportPattern: RegExp, typeNames: string[]) {
const {succeeded, apiReportChanged} = await testApiGolden(
goldenFilePath, entryPointFilePath, approveGolden, stripExportPattern, typeNames);
if (!succeeded && apiReportChanged) {
console.error(chalk.red(`The API signature has changed and the golden file is outdated.`));
@ -37,9 +37,11 @@ if (require.main === module) {
const entryPointFilePath = runfiles.resolve(args[1]);
const approveGolden = args[2] === 'true';
const stripExportPattern = new RegExp(args[3]);
const typeNames = args.slice(4);
main(goldenFilePath, entryPointFilePath, approveGolden, stripExportPattern).catch(e => {
console.error(e);
process.exit(1);
});
main(goldenFilePath, entryPointFilePath, approveGolden, stripExportPattern, typeNames)
.catch(e => {
console.error(e);
process.exit(1);
});
}

View File

@ -19,7 +19,8 @@ import {testApiGolden} from './test_api_report';
* against golden files within the given golden directory.
*/
async function main(
goldenDir: string, npmPackageDir: string, approveGolden: boolean, stripExportPattern: RegExp) {
goldenDir: string, npmPackageDir: string, approveGolden: boolean, stripExportPattern: RegExp,
typeNames: string[]) {
const entryPoints = findEntryPointsWithinNpmPackage(npmPackageDir);
const outdatedGoldens: string[] = [];
let allTestsSucceeding = true;
@ -35,7 +36,8 @@ async function main(
const goldenFilePath = join(goldenDir, goldenName);
const {succeeded, apiReportChanged} = await testApiGolden(
goldenFilePath, typesEntryPointPath, approveGolden, stripExportPattern, packageJsonPath);
goldenFilePath, typesEntryPointPath, approveGolden, stripExportPattern, typeNames,
packageJsonPath);
// Keep track of outdated goldens.
if (!succeeded && apiReportChanged) {
@ -63,8 +65,9 @@ if (require.main === module) {
const npmPackageDir = runfiles.resolve(args[1]);
const approveGolden = args[2] === 'true';
const stripExportPattern = new RegExp(args[3]);
const typeNames = args.slice(4);
main(goldenDir, npmPackageDir, approveGolden, stripExportPattern).catch(e => {
main(goldenDir, npmPackageDir, approveGolden, stripExportPattern, typeNames).catch(e => {
console.error(e);
process.exit(1);
});

View File

@ -28,13 +28,15 @@ const _origFetchAstModuleExportInfo = ExportAnalyzer.prototype.fetchAstModuleExp
* @param approveGolden Whether the golden file should be updated.
* @param stripExportPattern Regular Expression that can be used to filter out exports
* from the API report.
* @param typeNames Name of types which should be included for analysis of the entry-point.
* Types are expected to exist within the default `node_modules/@types/` folder.
* @param packageJsonPath Optional path to a `package.json` file that contains the entry
* point. Note that the `package.json` is currently only used by `api-extractor` to determine
* the package name displayed within the API golden.
*/
export async function testApiGolden(
goldenFilePath: string, indexFilePath: string, approveGolden: boolean,
stripExportPattern: RegExp,
stripExportPattern: RegExp, typeNames: string[] = [],
packageJsonPath = resolveWorkspacePackageJsonPath()): Promise<ExtractorResult> {
// If no `TEST_TMPDIR` is defined, then this script runs using `bazel run`. We use
// the runfile directory as temporary directory for API extractor.
@ -43,9 +45,10 @@ export async function testApiGolden(
const configObject: IConfigFile = {
compiler: {
overrideTsconfig:
// We disable inclusion of all `@types` installed as this throws-off API reports
// and causes different goldens when the API test is run outside sandbox.
{files: [indexFilePath], compilerOptions: {types: [], lib: ['esnext', 'dom']}}
// We disable automatic `@types` resolution as this throws-off API reports
// when the API test is run outside sandbox. Instead we expect a list of
// hard-coded types that should be included. This works in non-sandbox too.
{files: [indexFilePath], compilerOptions: {types: typeNames, lib: ['esnext', 'dom']}}
},
projectFolder: dirname(packageJsonPath),
mainEntryPointFilePath: indexFilePath,