
Specifically: 1) Adding a new //tools/typescript/definitions:build_ts ts_library() target which uses `skipLibCheck=false` and type checks all definition files once. 2) Making all other ts_library() targets (specifically the ones defined in build_webui() and webui_ts_library()) to depend on the new target. 3) Modifying ts_library() to automatically add `skipLibCheck=true` when `skipLibCheck` is not specified anywhere in the inheritance chain AND no definition files outside of tools/typescript/definitions exist. 4) Also fixed TsLibraryTest.testEnableSourceMaps which was erroneously passing due to a bug in _read_file() that was always reading from the same folder, ignoring its input parameter, resulting in comparing the expected file with itself. The actual file contains an absolute path embedded in the generated sourcemap and therefore the comparison needs to be relaxed to work across across machines. #1 and #2 mitigate any missed type coverage from #3, such that any TypeScript errors within tools/typescript/definitions/*.d.ts are still caught during the build. Essentially this change modifies the build such that some type checking work that was done by the TS compiler N times (where N=number of ts_library() targets) is only done once. Combined with the follow-up related CL this improves TS build times by ~21.5% (43.8s -> 34.4s) on a local Linux build with `use_remoteexec=false`. This CL alone accounts for the majority of that improvement (~20% out of 21.5%). Bug: 397737230 Change-Id: I14bc721e0e2a16ded0d71909d77a8e9b468f3519 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6292793 Auto-Submit: Demetrios Papadopoulos <dpapad@chromium.org> Reviewed-by: Rebekah Potter <rbpotter@chromium.org> Commit-Queue: Demetrios Papadopoulos <dpapad@chromium.org> Cr-Commit-Position: refs/heads/main@{#1425476}
517 lines
17 KiB
Python
Executable File
517 lines
17 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright 2021 The Chromium Authors
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import json
|
|
import ts_library
|
|
import ts_definitions
|
|
import path_mappings
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import unittest
|
|
|
|
_HERE_DIR = os.path.dirname(__file__)
|
|
_CWD = os.getcwd()
|
|
|
|
|
|
class TsLibraryTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self._out_folder = None
|
|
self._additional_flags = []
|
|
self.maxDiff = None
|
|
|
|
def tearDown(self):
|
|
if self._out_folder:
|
|
shutil.rmtree(self._out_folder)
|
|
|
|
def _read_file(self, parent_dir, file_name):
|
|
file_path = os.path.join(parent_dir, file_name)
|
|
with open(file_path, 'r', newline='') as f:
|
|
return f.read()
|
|
|
|
def _build_project1(self, enable_source_maps=False):
|
|
gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
|
|
'project1')
|
|
|
|
# Generate definition .d.ts file for legacy JS file.
|
|
ts_definitions.main([
|
|
'--root_dir',
|
|
os.path.join(_HERE_DIR, 'tests', 'project1'),
|
|
'--gen_dir',
|
|
gen_dir,
|
|
'--out_dir',
|
|
gen_dir,
|
|
'--js_files',
|
|
'legacy_file.js',
|
|
])
|
|
|
|
# Build project1, which includes a mix of TS and definition files.
|
|
args = [
|
|
'--output_suffix',
|
|
'build_ts',
|
|
'--root_gen_dir',
|
|
os.path.relpath(self._out_folder, gen_dir),
|
|
'--root_src_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
|
|
'--root_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project1'), _CWD),
|
|
'--gen_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--out_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--in_files',
|
|
'foo.ts',
|
|
'--definitions',
|
|
'legacy_file.d.ts',
|
|
'--composite',
|
|
]
|
|
|
|
if enable_source_maps:
|
|
args += ['--enable_source_maps']
|
|
|
|
ts_library.main(args)
|
|
return gen_dir
|
|
|
|
def _assert_project1_output(self, gen_dir):
|
|
files = [
|
|
'foo.d.ts',
|
|
'foo.js',
|
|
'legacy_file.d.ts',
|
|
'tsconfig_definitions.json',
|
|
'tsconfig_build_ts.json',
|
|
'build_ts_manifest.json',
|
|
]
|
|
for f in files:
|
|
self.assertTrue(os.path.exists(os.path.join(gen_dir, f)), f)
|
|
|
|
# Check that the generated .tsbuildinfo file is deleted.
|
|
tsbuildinfo = 'tsconfig_build_ts.tsbuildinfo'
|
|
self.assertFalse(os.path.exists(os.path.join(gen_dir, tsbuildinfo)),
|
|
tsbuildinfo)
|
|
|
|
# Builds project2 which depends on files from project1 and project3, project6,
|
|
# both via relative URLs, as well as via absolute chrome:// and
|
|
# chrome://resources/ URLs.
|
|
def _build_project2(self, project1_gen_dir, project3_gen_dir,
|
|
project6_gen_dir):
|
|
root_dir = os.path.join(_HERE_DIR, 'tests', 'project2')
|
|
gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
|
|
'project2')
|
|
project1_gen_dir = os.path.relpath(project1_gen_dir, gen_dir)
|
|
project3_gen_dir = os.path.relpath(project3_gen_dir, gen_dir)
|
|
project6_gen_dir = os.path.relpath(project6_gen_dir, gen_dir)
|
|
# Using path mappings to generate the path map file. path_mappings is also
|
|
# unit tested separately in path_mappings_test.py.
|
|
path_mappings.main([
|
|
'--root_gen_dir',
|
|
os.path.relpath(self._out_folder, gen_dir),
|
|
'--root_src_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
|
|
'--gen_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--raw_deps',
|
|
'//ui/webui/resources/js:build_ts',
|
|
'--output_suffix',
|
|
'project2',
|
|
])
|
|
|
|
ts_library.main([
|
|
'--output_suffix',
|
|
'build_ts',
|
|
'--root_gen_dir',
|
|
os.path.relpath(self._out_folder, gen_dir),
|
|
'--root_src_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
|
|
'--root_dir',
|
|
os.path.relpath(root_dir, _CWD),
|
|
'--gen_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--out_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--in_files',
|
|
'bar.ts',
|
|
'--deps',
|
|
os.path.join(project1_gen_dir, 'tsconfig_build_ts.json'),
|
|
os.path.join(project3_gen_dir, 'tsconfig_build_ts.json'),
|
|
os.path.join(project6_gen_dir, 'tsconfig_build_ts.json'),
|
|
'--path_mappings',
|
|
'chrome://some-other-source/*|' + os.path.join(project1_gen_dir, '*'),
|
|
'--path_mappings_file',
|
|
'path_mappings_project2.json',
|
|
'--tsconfig_base',
|
|
os.path.relpath(os.path.join(root_dir, 'tsconfig_base.json'), gen_dir),
|
|
])
|
|
return gen_dir
|
|
|
|
def _assert_project2_output(self, gen_dir):
|
|
files = [
|
|
'bar.js',
|
|
'tsconfig_build_ts.json',
|
|
'build_ts_manifest.json',
|
|
'path_mappings_project2.json',
|
|
]
|
|
for f in files:
|
|
self.assertTrue(os.path.exists(os.path.join(gen_dir, f)), f)
|
|
|
|
non_existing_files = [
|
|
'bar.d.ts',
|
|
'tsconfig_build_ts.tsbuildinfo',
|
|
]
|
|
for f in non_existing_files:
|
|
self.assertFalse(os.path.exists(os.path.join(gen_dir, f)), f)
|
|
|
|
# Builds project3, which includes only definition files.
|
|
def _build_project3(self):
|
|
gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
|
|
'project3')
|
|
|
|
ts_library.main([
|
|
'--output_suffix',
|
|
'build_ts',
|
|
'--root_gen_dir',
|
|
os.path.relpath(self._out_folder, gen_dir),
|
|
'--root_src_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
|
|
'--root_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project3'), _CWD),
|
|
'--gen_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--out_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--definitions',
|
|
os.path.relpath(
|
|
os.path.join(_HERE_DIR, 'tests', 'project3', 'baz.d.ts'), gen_dir),
|
|
'--composite',
|
|
])
|
|
return gen_dir
|
|
|
|
def _assert_project3_output(self, gen_dir):
|
|
self.assertTrue(
|
|
os.path.exists(os.path.join(gen_dir, 'tsconfig_build_ts.json')))
|
|
self.assertFalse(
|
|
os.path.exists(os.path.join(gen_dir, 'tsconfig_build_ts.tsbuildinfo')))
|
|
self.assertFalse(
|
|
os.path.exists(os.path.join(gen_dir, 'build_ts_manifest.json')))
|
|
|
|
# Check that 'skipLibCheck=true' was *not* automatically added.
|
|
tsconfig_contents = self._read_file(gen_dir, 'tsconfig_build_ts.json')
|
|
tsconfig = json.loads(tsconfig_contents)
|
|
self.assertFalse('skipLibCheck' in tsconfig['compilerOptions'])
|
|
|
|
def _build_project4(self):
|
|
gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
|
|
'project4')
|
|
|
|
# Build project4, which includes multiple TS files, only one of which should
|
|
# be included in the manifest.
|
|
ts_library.main([
|
|
'--output_suffix',
|
|
'build_ts',
|
|
'--root_gen_dir',
|
|
os.path.relpath(self._out_folder, gen_dir),
|
|
'--root_src_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
|
|
'--root_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project4'), _CWD),
|
|
'--gen_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--out_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--in_files',
|
|
'include.ts',
|
|
'exclude.ts',
|
|
'--manifest_excludes',
|
|
'exclude.ts',
|
|
])
|
|
return gen_dir
|
|
|
|
def _assert_project4_output(self, gen_dir):
|
|
files = [
|
|
'include.js',
|
|
'exclude.js',
|
|
'tsconfig_build_ts.json',
|
|
'build_ts_manifest.json',
|
|
]
|
|
for f in files:
|
|
self.assertTrue(os.path.exists(os.path.join(gen_dir, f)), f)
|
|
|
|
# Check that 'skipLibCheck=true' was automatically added.
|
|
tsconfig_contents = self._read_file(gen_dir, 'tsconfig_build_ts.json')
|
|
tsconfig = json.loads(tsconfig_contents)
|
|
self.assertTrue(tsconfig['compilerOptions']['skipLibCheck'])
|
|
|
|
# Check that the generated manifest file doesn't include exclude.js.
|
|
manifest = os.path.join(gen_dir, 'build_ts_manifest.json')
|
|
self._assert_manifest_files(manifest, ['include.js'])
|
|
|
|
def _assert_manifest_files(self, manifest_path, expected_files):
|
|
with open(manifest_path, 'r') as f:
|
|
data = json.load(f)
|
|
self.assertEqual(data['files'], expected_files)
|
|
|
|
def _build_project5(self):
|
|
gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
|
|
'project5')
|
|
out_dir_test = os.path.join(self._out_folder, 'project5_test')
|
|
|
|
# Build project5, which includes 2 TS projects one for prod and one for
|
|
# test, it should generate different manifest, tsconfig and tsbuildinfo.
|
|
# prod:
|
|
ts_library.main([
|
|
'--output_suffix',
|
|
'build_ts',
|
|
'--composite',
|
|
'--root_gen_dir',
|
|
os.path.relpath(self._out_folder, gen_dir),
|
|
'--root_src_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
|
|
'--root_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project5'), _CWD),
|
|
'--gen_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--out_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--in_files',
|
|
'bar.ts',
|
|
])
|
|
|
|
# test:
|
|
ts_library.main([
|
|
'--output_suffix',
|
|
'test_build_ts',
|
|
'--deps',
|
|
os.path.join(gen_dir, 'tsconfig_build_ts.json'),
|
|
'--root_gen_dir',
|
|
os.path.relpath(self._out_folder, gen_dir),
|
|
'--root_src_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
|
|
'--root_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project5'), _CWD),
|
|
'--gen_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--out_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--in_files',
|
|
'bar_test.ts',
|
|
])
|
|
|
|
return gen_dir
|
|
|
|
def _assert_project5_output(self, gen_dir):
|
|
# prod:
|
|
self.assertTrue(
|
|
os.path.exists(os.path.join(gen_dir, 'tsconfig_build_ts.json')))
|
|
manifest = os.path.join(gen_dir, 'build_ts_manifest.json')
|
|
self.assertTrue(os.path.exists(manifest))
|
|
self._assert_manifest_files(manifest, ['bar.js'])
|
|
|
|
# test:
|
|
self.assertTrue(
|
|
os.path.exists(os.path.join(gen_dir, 'tsconfig_test_build_ts.json')))
|
|
manifest_test = os.path.join(gen_dir, 'test_build_ts_manifest.json')
|
|
self.assertTrue(os.path.exists(manifest_test))
|
|
self._assert_manifest_files(manifest_test, ['bar_test.js'])
|
|
|
|
def _build_project6(self):
|
|
gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
|
|
'ui', 'webui', 'resources', 'js')
|
|
out_dir = os.path.join(self._out_folder, 'ui', 'webui', 'resources', 'tsc',
|
|
'js')
|
|
|
|
# Build project6, which simulates the build setup and location of shared
|
|
# ui/webui/resources/ projects, and is used to test the codepath that infers
|
|
# |path_mappings| from |raw_deps|.
|
|
ts_library.main([
|
|
'--output_suffix',
|
|
'build_ts',
|
|
'--root_gen_dir',
|
|
os.path.relpath(self._out_folder, gen_dir),
|
|
'--root_src_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
|
|
'--root_dir',
|
|
os.path.relpath(
|
|
os.path.join(_HERE_DIR, 'tests', 'ui', 'webui', 'resources', 'js'),
|
|
_CWD),
|
|
'--gen_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--out_dir',
|
|
os.path.relpath(out_dir, _CWD),
|
|
'--in_files',
|
|
'assert.ts',
|
|
'--composite',
|
|
])
|
|
|
|
return (gen_dir, out_dir)
|
|
|
|
def _assert_project6_output(self, gen_dir, out_dir):
|
|
gen_dir_files = [
|
|
'tsconfig_build_ts.json',
|
|
'build_ts_manifest.json',
|
|
]
|
|
for f in gen_dir_files:
|
|
self.assertTrue(os.path.exists(os.path.join(gen_dir, f)), f)
|
|
|
|
# Check that the generated .tsbuildinfo file is deleted.
|
|
tsbuildinfo = 'tsconfig_build_ts.tsbuildinfo'
|
|
self.assertFalse(os.path.exists(os.path.join(gen_dir, tsbuildinfo)),
|
|
tsbuildinfo)
|
|
|
|
out_dir_files = [
|
|
'assert.d.ts',
|
|
'assert.js',
|
|
]
|
|
for f in out_dir_files:
|
|
self.assertTrue(os.path.exists(os.path.join(out_dir, f)), f)
|
|
|
|
|
|
# Test success case where both project1 and project2 are compiled successfully
|
|
# and no errors are thrown.
|
|
def testSuccess(self):
|
|
self._out_folder = tempfile.mkdtemp(dir=_CWD)
|
|
project1_gen_dir = self._build_project1()
|
|
self._assert_project1_output(project1_gen_dir)
|
|
|
|
project3_gen_dir = self._build_project3()
|
|
self._assert_project3_output(project3_gen_dir)
|
|
|
|
(project6_gen_dir, project6_out_dir) = self._build_project6()
|
|
self._assert_project6_output(project6_gen_dir, project6_out_dir)
|
|
|
|
project2_gen_dir = self._build_project2(project1_gen_dir, project3_gen_dir,
|
|
project6_gen_dir)
|
|
self._assert_project2_output(project2_gen_dir)
|
|
|
|
project4_gen_dir = self._build_project4()
|
|
self._assert_project4_output(project4_gen_dir)
|
|
|
|
project5_gen_dir = self._build_project5()
|
|
self._assert_project5_output(project5_gen_dir)
|
|
|
|
# Test error case where a type violation exists, ensure that an error is
|
|
# thrown.
|
|
def testError(self):
|
|
self._out_folder = tempfile.mkdtemp(dir=_CWD)
|
|
gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
|
|
'project1')
|
|
try:
|
|
ts_library.main([
|
|
'--output_suffix',
|
|
'build_ts',
|
|
'--root_gen_dir',
|
|
os.path.relpath(self._out_folder, gen_dir),
|
|
'--root_src_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
|
|
'--root_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project1'), _CWD),
|
|
'--gen_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--out_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--in_files',
|
|
'errors.ts',
|
|
'--composite',
|
|
])
|
|
except RuntimeError as err:
|
|
self.assertTrue('Type \'number\' is not assignable to type \'string\'' \
|
|
in str(err))
|
|
self.assertFalse(
|
|
os.path.exists(os.path.join(gen_dir,
|
|
'tsconfig_build_ts.tsbuildinfo')))
|
|
else:
|
|
self.fail('Failed to detect type error')
|
|
|
|
# Test error case where the project's tsconfig file is failing validation.
|
|
def testTsConfigValidationError(self):
|
|
self._out_folder = tempfile.mkdtemp(dir=_CWD)
|
|
root_dir = os.path.join(_HERE_DIR, 'tests', 'project5')
|
|
gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
|
|
'project5')
|
|
try:
|
|
ts_library.main([
|
|
'--output_suffix',
|
|
'build_ts',
|
|
'--root_gen_dir',
|
|
os.path.relpath(self._out_folder, gen_dir),
|
|
'--root_src_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
|
|
'--root_dir',
|
|
os.path.relpath(root_dir, _CWD),
|
|
'--gen_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--out_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--in_files',
|
|
'bar.ts',
|
|
'--tsconfig_base',
|
|
os.path.relpath(os.path.join(root_dir, 'tsconfig_base.json'),
|
|
gen_dir),
|
|
])
|
|
except AssertionError as err:
|
|
self.assertTrue(
|
|
str(err).replace('\\', '/').startswith(
|
|
'Invalid |composite| flag detected in '
|
|
'tools/typescript/tests/project5/tsconfig_base.json.'
|
|
))
|
|
else:
|
|
self.fail('Failed to detect error')
|
|
|
|
# Test error case where the project's tsconfig file inherits from another
|
|
# tsconfig file that should fail validation.
|
|
def testTsConfigValidationErrorInParent(self):
|
|
self._out_folder = tempfile.mkdtemp(dir=_CWD)
|
|
root_dir = os.path.join(_HERE_DIR, 'tests', 'project5')
|
|
gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
|
|
'project5')
|
|
try:
|
|
ts_library.main([
|
|
'--output_suffix',
|
|
'build_ts',
|
|
'--root_gen_dir',
|
|
os.path.relpath(self._out_folder, gen_dir),
|
|
'--root_src_dir',
|
|
os.path.relpath(os.path.join(_HERE_DIR, 'tests'), gen_dir),
|
|
'--root_dir',
|
|
os.path.relpath(root_dir, _CWD),
|
|
'--gen_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--out_dir',
|
|
os.path.relpath(gen_dir, _CWD),
|
|
'--in_files',
|
|
'bar.ts',
|
|
'--tsconfig_base',
|
|
os.path.relpath(os.path.join(root_dir, 'tsconfig_base2.json'),
|
|
gen_dir),
|
|
])
|
|
except AssertionError as err:
|
|
self.assertTrue(
|
|
str(err).replace('\\', '/').startswith(
|
|
'Invalid |composite| flag detected in '
|
|
'tools/typescript/tests/project5/tsconfig_base.json.'
|
|
))
|
|
else:
|
|
self.fail('Failed to detect error')
|
|
|
|
# Test case where |enable_source_maps| is specified.
|
|
def testEnableSourceMaps(self):
|
|
self._out_folder = tempfile.mkdtemp(dir=_CWD)
|
|
|
|
expectations_dir = os.path.join(_HERE_DIR, 'tests', 'expected', 'project1')
|
|
|
|
# Build project1 which source maps enabled.
|
|
gen_dir = self._build_project1(enable_source_maps=True)
|
|
|
|
# Assert output contains a source map.
|
|
SOURCE_MAP_TOKEN = '//# sourceMappingURL=data:application/json;base64,'
|
|
|
|
def _assert_source_map_exists(file_path):
|
|
with open(file_path, 'r', newline='') as f:
|
|
lines = f.readlines()
|
|
self.assertTrue(lines[-1].startswith(SOURCE_MAP_TOKEN))
|
|
|
|
_assert_source_map_exists(os.path.join(gen_dir, 'foo.js'))
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|