From 76d2ba64df3e520a58cb58925d418c3e760793dc Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 21 May 2018 21:24:29 -0700 Subject: [PATCH] Testing WIP --- src/compiler/tsbuild.ts | 22 +++--- src/harness/unittests/tsbuild.ts | 88 ++++++++++++++++++---- tests/projects/sample1/core/index.ts | 1 + tests/projects/sample1/core/tsconfig.json | 5 +- tests/projects/sample1/logic/index.ts | 2 +- tests/projects/sample1/logic/tsconfig.json | 4 + tests/projects/sample1/tests/index.ts | 4 +- tests/projects/sample1/tests/tsconfig.json | 3 +- tests/projects/sample1/ui/tsconfig.json | 2 +- 9 files changed, 101 insertions(+), 30 deletions(-) diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index d9c3eb4212d..34f013fa017 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -317,16 +317,12 @@ namespace ts { return date2 > date1 ? date2 : date1; } - function older(date1: Date, date2: Date): Date { - return date2 < date1 ? date2 : date1; - } - function isDeclarationFile(fileName: string) { return fileExtensionIs(fileName, ".d.ts"); } - export function createBuildContext(options: BuildOptions): BuildContext { - const verboseDiag = options.verbose && createDiagnosticReporter(sys, /*pretty*/ false); + export function createBuildContext(options: BuildOptions, reportDiagnostic: DiagnosticReporter): BuildContext { + const verboseDiag = options.verbose && reportDiagnostic; return { options, projectStatus: createFileMap(), @@ -371,8 +367,7 @@ namespace ts { addProject("."); } - const context = createBuildContext({ verbose, dry, force }); - const builder = createSolutionBuilder(host, reportDiagnostic, context); + const builder = createSolutionBuilder(host, reportDiagnostic, { verbose, dry, force }); if (clean) { builder.cleanProjects(projects); } @@ -391,8 +386,9 @@ namespace ts { } } - export function createSolutionBuilder(host: CompilerHost, reportDiagnostic: DiagnosticReporter, context: BuildContext) { + export function createSolutionBuilder(host: CompilerHost, reportDiagnostic: DiagnosticReporter, options: BuildOptions) { const configFileCache = createConfigFileCache(host); + let context: BuildContext = undefined!; return { getUpToDateStatus, @@ -473,7 +469,7 @@ namespace ts { oldestOutputFileTime = outputTime; oldestOutputFileName = output; } - newestOutputFileTime = older(newestOutputFileTime, outputTime); + newestOutputFileTime = newer(newestOutputFileTime, outputTime); // Keep track of when the most recent time a .d.ts file was changed. // In addition to file timestamps, we also keep track of when a .d.ts file @@ -709,6 +705,8 @@ namespace ts { } function cleanProjects(configFileNames: string[]) { + context = createBuildContext(options, reportDiagnostic); + // Get the same graph for cleaning we'd use for building const graph = createDependencyGraph(configFileNames); @@ -736,6 +734,8 @@ namespace ts { } function buildProjects(configFileNames: string[]) { + context = createBuildContext(options, reportDiagnostic); + const resolvedNames: string[] = []; for (const name of configFileNames) { let fullPath = resolvePath(host.getCurrentDirectory(), name); @@ -823,7 +823,7 @@ namespace ts { context.verbose(Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, configFileName, status.missingOutputFileName); return; case UpToDateStatusType.UpToDate: - context.verbose(Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, configFileName, status.newestDeclarationFileContentChangedTime as any, status.newestOutputFileTime); + context.verbose(Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, configFileName, status.newestInputFileTime, status.newestOutputFileTime); return; case UpToDateStatusType.UpToDateWithUpstreamTypes: context.verbose(Diagnostics.Project_0_is_up_to_date_with_its_upstream_types, configFileName); diff --git a/src/harness/unittests/tsbuild.ts b/src/harness/unittests/tsbuild.ts index 5bd713f377f..809d7bb1b0b 100644 --- a/src/harness/unittests/tsbuild.ts +++ b/src/harness/unittests/tsbuild.ts @@ -3,7 +3,7 @@ namespace ts { let currentTime = 100; const bfs = new vfs.FileSystem(/*ignoreCase*/ false, { time }); - const lastDiagnostics: Diagnostic[] = []; + let lastDiagnostics: Diagnostic[] = []; const reportDiagnostic: DiagnosticReporter = diagnostic => lastDiagnostics.push(diagnostic); const sampleRoot = resolvePath(__dirname, "../../tests/projects/sample1"); @@ -12,47 +12,109 @@ namespace ts { bfs.writeFileSync("/lib/lib.d.ts", Harness.IO.readFile(combinePaths(Harness.libFolder, "lib.d.ts"))); bfs.meta.set("defaultLibLocation", "/lib"); bfs.makeReadonly(); + tick(); describe("tsbuild tests", () => { - it("builds the referenced project", () => { + it("can build the sample project 'tests' without error", () => { const fs = bfs.shadow(); const host = new fakes.CompilerHost(fs); - const builder = createSolutionBuilder(host, reportDiagnostic, createBuildContext({ dry: false, force: false, verbose: false })); + const builder = createSolutionBuilder(host, reportDiagnostic, { dry: false, force: false, verbose: false }); fs.chdir("/src/tests"); - fs.debugPrint(); builder.buildProjects(["."]); - printDiagnostics(); - fs.debugPrint(); - assertDiagnosticMessages(Diagnostics.File_0_does_not_exist); + assertDiagnosticMessages(/*empty*/); + }); + it("can detect when and what to rebuild", () => { + const fs = bfs.shadow(); + const host = new fakes.CompilerHost(fs); + const builder = createSolutionBuilder(host, reportDiagnostic, { dry: false, force: false, verbose: true }); + + fs.chdir("/src/tests"); + builder.buildProjects(["."]); + assertDiagnosticMessages(Diagnostics.Sorted_list_of_input_projects_Colon_0, + Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, + Diagnostics.Building_project_0, + Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, + Diagnostics.Building_project_0, + Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, + Diagnostics.Building_project_0); tick(); + + // All three projects are up to date + clearDiagnostics(); + builder.buildProjects(["."]); + assertDiagnosticMessages(Diagnostics.Sorted_list_of_input_projects_Colon_0, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2); + tick(); + + // Update a file in the leaf node (tests), only it should rebuild the last one + clearDiagnostics(); + fs.writeFileSync("/src/tests/index.ts", "const m = 10;"); + builder.buildProjects(["."]); + + assertDiagnosticMessages(Diagnostics.Sorted_list_of_input_projects_Colon_0, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, + Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, + Diagnostics.Building_project_0); + tick(); + + // Update a file in the parent (without affecting types), should get fast downstream builds + clearDiagnostics(); + replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET"); + builder.buildProjects(["."]); + + assertDiagnosticMessages(Diagnostics.Sorted_list_of_input_projects_Colon_0, + Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, + Diagnostics.Building_project_0, + Diagnostics.Project_0_is_up_to_date_with_its_upstream_types, + Diagnostics.Updating_output_timestamps_of_project_0, + Diagnostics.Project_0_is_up_to_date_with_its_upstream_types, + Diagnostics.Updating_output_timestamps_of_project_0); }); }); + function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) { + if (!fs.statSync(path).isFile()) { + throw new Error(`File ${path} does not exist`); + } + const old = fs.readFileSync(path, 'utf-8'); + if (old.indexOf(oldText) < 0) { + throw new Error(`Text "${oldText}" does not exist in file ${path}`); + } + const newContent = old.replace(oldText, newText); + fs.writeFileSync(path, newContent, 'utf-8'); + } + function assertDiagnosticMessages(...expected: DiagnosticMessage[]) { const actual = lastDiagnostics.slice(); - actual.sort((a, b) => b.code - a.code); - expected.sort((a, b) => b.code - a.code); if (actual.length !== expected.length) { - assert.fail(actual, expected, `Diagnostic arrays did not match - expected ${actual.join(",")}, got ${expected.join(",")}`); + assert.fail(actual, expected, `Diagnostic arrays did not match - expected\r\n${actual.map(a => " " + a.messageText).join("\r\n")}\r\ngot\r\n${expected.map(e => " " + e.message).join("\r\n")}`); } for (let i = 0; i < actual.length; i++) { if (actual[i].code !== expected[i].code) { - assert.fail(actual[i].messageText, expected[i].message, "Mismatched error code"); + assert.fail(actual[i].messageText, expected[i].message, `Mismatched error code - expected diagnostic ${i} "${actual[i].messageText}" to match ${expected[i].message}`); } } } - export function printDiagnostics() { + function clearDiagnostics() { + lastDiagnostics = []; + } + + export function printDiagnostics(header = "== Diagnostics ==") { const out = createDiagnosticReporter(sys); + sys.write(header + "\r\n"); for (const d of lastDiagnostics) { out(d); } } function tick() { - currentTime += 10; + currentTime += 100000; } function time() { return currentTime; diff --git a/tests/projects/sample1/core/index.ts b/tests/projects/sample1/core/index.ts index 9ade19f5e2e..529a7f549ec 100644 --- a/tests/projects/sample1/core/index.ts +++ b/tests/projects/sample1/core/index.ts @@ -1,2 +1,3 @@ +export const someString: string = "HELLO WORLD"; export function leftPad(s: string, n: number) { return s + n; } export function multiply(a: number, b: number) { return a * b; } diff --git a/tests/projects/sample1/core/tsconfig.json b/tests/projects/sample1/core/tsconfig.json index a514dfe8a03..b8332f5c476 100644 --- a/tests/projects/sample1/core/tsconfig.json +++ b/tests/projects/sample1/core/tsconfig.json @@ -1,3 +1,6 @@ { - + "compilerOptions": { + "composite": true, + "declaration": true + } } \ No newline at end of file diff --git a/tests/projects/sample1/logic/index.ts b/tests/projects/sample1/logic/index.ts index cccadd1718b..fd6b2106bb8 100644 --- a/tests/projects/sample1/logic/index.ts +++ b/tests/projects/sample1/logic/index.ts @@ -1,4 +1,4 @@ -import * as c from '../core'; +import * as c from '../core/index'; export function getSecondsInDay() { return c.multiply(10, 15); } diff --git a/tests/projects/sample1/logic/tsconfig.json b/tests/projects/sample1/logic/tsconfig.json index 3c2056eec88..a58b3a9f48e 100644 --- a/tests/projects/sample1/logic/tsconfig.json +++ b/tests/projects/sample1/logic/tsconfig.json @@ -1,4 +1,8 @@ { + "compilerOptions": { + "composite": true, + "declaration": true + }, "references": [ { "path": "../core" } ] diff --git a/tests/projects/sample1/tests/index.ts b/tests/projects/sample1/tests/index.ts index e9a1ebde3e1..f89dcd08a82 100644 --- a/tests/projects/sample1/tests/index.ts +++ b/tests/projects/sample1/tests/index.ts @@ -1,5 +1,5 @@ -import * as c from '../core'; -import * as logic from '../logic'; +import * as c from '../core/index'; +import * as logic from '../logic/index'; c.leftPad("", 10); logic.getSecondsInDay(); diff --git a/tests/projects/sample1/tests/tsconfig.json b/tests/projects/sample1/tests/tsconfig.json index dd1bf3bae6b..437d8ca6fb3 100644 --- a/tests/projects/sample1/tests/tsconfig.json +++ b/tests/projects/sample1/tests/tsconfig.json @@ -2,5 +2,6 @@ "references": [ { "path": "../core" }, { "path": "../logic" } - ] + ], + "files": ["index.ts"] } \ No newline at end of file diff --git a/tests/projects/sample1/ui/tsconfig.json b/tests/projects/sample1/ui/tsconfig.json index 45eff16d4d9..d843e35c549 100644 --- a/tests/projects/sample1/ui/tsconfig.json +++ b/tests/projects/sample1/ui/tsconfig.json @@ -1,5 +1,5 @@ { "references": [ - { "path": "../logic" } + { "path": "../logic/index" } ] }