// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. //@ts-check import assert from "node:assert/strict"; import { test } from "node:test"; import { log } from "../dist/log.js"; import { getCompiler, getCompilerWorker, getLanguageService, getLanguageServiceWorker, getDebugServiceWorker, loadWasmModule, utils, } from "../dist/node.js"; import { QscEventTarget } from "../dist/compiler/events.js"; import { getAllKatas, getExerciseSources, getKata } from "../dist/katas.js"; import samples from "../dist/samples.generated.js"; import { readFileSync } from "node:fs"; const distDir = new URL("../dist/", import.meta.url); const compilerWorkerPath = new URL("compiler/worker.js", distDir).href; const languageServiceWorkerPath = new URL("language-service/worker.js", distDir) .href; const debugServiceWorkerPath = new URL("debug-service/worker.js", distDir).href; // Load the wasm module before running any tests const wasmPath = new URL("../lib/web/qsc_wasm_bg.wasm", import.meta.url); await loadWasmModule(readFileSync(wasmPath).buffer); /** @type {import("../dist/log.js").TelemetryEvent[]} */ const telemetryEvents = []; log.setLogLevel("warn"); log.setTelemetryCollector((event) => telemetryEvents.push(event)); /** * * @param {string} code * @param {string} expr * @param {boolean} useWorker * @returns {Promise} */ export async function runSingleShot(code, expr, useWorker) { const resultsHandler = new QscEventTarget(true); const compiler = useWorker ? getCompilerWorker(compilerWorkerPath) : await getCompiler(); try { await compiler.run( { sources: [["test.qs", code]], languageFeatures: [] }, expr, 1, resultsHandler, ); return resultsHandler.getResults()[0]; } catch (err) { console.error("Error during runSingleShot:", err); throw err; } finally { /* @ts-expect-error: ICompiler does not include 'terminate' */ if (useWorker) compiler.terminate(); } } test("autogenerated documentation", async () => { const compiler = await getCompiler(); const regex = new RegExp("^qsharp.namespace: (.+)$", "m"); const docFiles = await compiler.getDocumentation(); var numberOfGoodFiles = 0; for (const doc of docFiles) { assert(doc, "Each documentation file should be present."); if (doc.filename === "index.md") { continue; // Skip index.md - its contents are added later } assert( doc.contents && doc.contents.length > 10, "Content for each documentation file should be present.", ); const match = regex.exec(doc.metadata); // Parse namespace out of metadata if (match == null) { continue; // Skip items with non-parsable metadata } const namespace = match[1]; assert( namespace.startsWith("Std.") || namespace.startsWith("Microsoft.Quantum"), // old libraries like Unstable are still in M.Q, but newer ones are in Std. "Namespaces in the standard library should start with Std. or Microsoft.Quantum", ); numberOfGoodFiles++; } // Number of functions with comments may change in the standard library, // But it should be large enough. assert( numberOfGoodFiles > 100, "Number of good documentation files should be large enough.", ); }); test("library summaries slim docs", async () => { const compiler = await getCompiler(); const summaries = await compiler.getLibrarySummaries(); assert(typeof summaries === "string", "Summaries should be a string"); assert(summaries.length > 0, "Summaries should not be empty"); // Check that it contains namespace headers (markdown format) assert( summaries.includes("# Microsoft.Quantum"), "Should contain standard library namespaces", ); // Check that it contains function signatures in code blocks assert(summaries.includes("```qsharp"), "Should contain Q# code blocks"); assert(summaries.includes("## "), "Should contain function headers"); // Check that it's organized by namespace const lines = summaries.split("\n"); const namespaceHeaders = lines.filter((line) => line.startsWith("# ")); assert(namespaceHeaders.length > 0, "Should have namespace headers"); }); test("basic eval", async () => { const code = `namespace Test { function Answer() : Int { return 42; } }`; const expr = `Test.Answer()`; const result = await runSingleShot(code, expr, false); assert(result.success); assert.equal(result.result, "42"); }); test("EntryPoint only", async () => { const code = ` namespace Test { @EntryPoint() operation MyEntry() : Result { use q1 = Qubit(); return M(q1); } }`; const result = await runSingleShot(code, "", true); assert(result.success === true); assert(result.result === "Zero"); }); test("one syntax error", async () => { const compiler = await getCompiler(); const diags = await compiler.checkCode("namespace Foo []"); assert.equal(diags.length, 1); assert.deepEqual(diags[0].range.start, { line: 0, character: 14 }); assert.deepEqual(diags[0].range.end, { line: 0, character: 15 }); }); test("error with newlines", async () => { const compiler = await getCompiler(); const diags = await compiler.checkCode( "namespace input { operation Foo(a) : Unit {} }", ); assert.equal(diags.length, 2); assert.deepEqual(diags[0].range.start, { line: 0, character: 32 }); assert.deepEqual(diags[0].range.end, { line: 0, character: 33 }); assert.deepEqual(diags[1].range.start, { line: 0, character: 32 }); assert.deepEqual(diags[1].range.end, { line: 0, character: 33 }); assert.equal( diags[1].message, "type error: insufficient type information to infer type\n\nhelp: provide a type annotation", ); assert.equal( diags[0].message, "type error: missing type in item signature\n\nhelp: a type must be provided for this item", ); }); test("dump and message output", async () => { const code = `namespace Test { function Answer() : Int { Microsoft.Quantum.Diagnostics.DumpMachine(); Message("hello, qsharp"); return 42; } }`; const expr = `Test.Answer()`; const result = await runSingleShot(code, expr, true); assert(result.success); assert(result.events.length === 2); assert(result.events[0].type === "DumpMachine"); assert(result.events[0].state["|0⟩"].length === 2); assert(result.events[1].type === "Message"); assert(result.events[1].message === "hello, qsharp"); }); async function runExerciseSolutionCheck(exercise, solution) { const evtTarget = new QscEventTarget(true); const compiler = await getCompiler(); const sources = await getExerciseSources(exercise); const success = await compiler.checkExerciseSolution( solution, sources, evtTarget, ); const unsuccessful_events = evtTarget .getResults() .filter((evt) => !evt.success); let errorMsg = ""; for (const event of unsuccessful_events) { const error = event.result; if (typeof error === "string") { errorMsg += "Result = " + error + "\n"; } else { errorMsg += "Message = " + error.message + "\n"; } } return { success: success, errorCount: unsuccessful_events.length, errorMsg: errorMsg, }; } async function getAllKataExamples(kata) { let examples = []; // Get all the examples contained in solution explanations. const exerciseExamples = kata.sections .filter((section) => section.type === "exercise") .map((exercise) => exercise.explainedSolution.items.filter( (item) => item.type === "example", ), ) .flat(); examples = examples.concat(exerciseExamples); // Get all the examples in lessons. const lessonExamples = kata.sections .filter((section) => section.type === "lesson") .map((lesson) => lesson.items.filter((item) => item.type === "example")) .flat(); examples = examples.concat(lessonExamples); return examples; } async function validateExercise( exercise, validatePlaceholder, validateSolutions, ) { // Validate the correctness of the placeholder code. if (validatePlaceholder) { const placeholderResult = await runExerciseSolutionCheck( exercise, exercise.placeholderCode, ); // Check that there are no compilation or runtime errors. assert( placeholderResult.errorCount === 0, `Exercise "${exercise.id}" has compilation or runtime errors when using the placeholder as solution. ` + `Compilation and runtime errors:\n${placeholderResult.errorMsg}`, ); // Check that the placeholder is an incorrect solution. assert( !placeholderResult.success, `Placeholder for exercise "${exercise.id}" is a correct solution but it is expected to be an incorrect solution`, ); } // Validate the correctness of the solutions. if (validateSolutions) { const solutions = exercise.explainedSolution.items.filter( (item) => item.type === "solution", ); // Check that the exercise has at least one solution. assert( solutions.length > 0, `Exercise "${exercise.id}" does not have solutions`, ); // Check that the solutions are correct. for (const solution of solutions) { const solutionResult = await runExerciseSolutionCheck( exercise, solution.code, ); // Check that there are no compilation or runtime errors. assert( solutionResult.errorCount === 0, `Solution "${solution.id}" for exercise "${exercise.id}" has compilation or runtime errors` + `Compilation and runtime errors:\n${solutionResult.errorMsg}`, ); // Check that the solution is correct. assert( solutionResult.success, `Solution "${solution.id}" for exercise "${exercise.id}" is incorrect`, ); } } } async function validateKata( kata, validateExamples, validateExercisePlaceholder, validateExerciseSolutions, ) { // Validate the correctness of Q# code related to exercises. const exercises = kata.sections.filter( (section) => section.type === "exercise", ); for (const exercise of exercises) { await validateExercise( exercise, validateExercisePlaceholder, validateExerciseSolutions, ); } if (validateExamples) { const examples = await getAllKataExamples(kata); for (const example of examples) { try { const result = await runSingleShot(example.code, "", false); assert( result.success, `Example "${example.id}" in "${kata.id}" kata failed to run.`, ); } catch (error) { assert( false, `Example "${example.id}" in "${kata.id}" kata failed to build:\n${error}`, ); } } } } test("getAllKatas works", async () => { const katas = await getAllKatas({ includeUnpublished: true }); assert.ok(katas.length > 0, "katas should not be empty"); }); test("all katas", async (t) => { // Run tests for all katas, including unpublished const katasList = await getAllKatas({ includeUnpublished: true }); for (const kataDesc of katasList) { await t.test(`${kataDesc.id} kata is valid`, async () => { const kata = await getKata(kataDesc.id); await validateKata(kata, true, true, true); }); } }); test("worker 100 shots", async () => { let code = `namespace Test { function Answer() : Int { Microsoft.Quantum.Diagnostics.DumpMachine(); Message("hello, qsharp"); return 42; } }`; let expr = `Test.Answer()`; const resultsHandler = new QscEventTarget(true); const compiler = getCompilerWorker(compilerWorkerPath); await compiler.run( { sources: [["test.qs", code]], languageFeatures: [] }, expr, 100, resultsHandler, ); compiler.terminate(); const results = resultsHandler.getResults(); assert.equal(results.length, 100); results.forEach((result) => { assert(result.success); assert.equal(result.result, "42"); assert.equal(result.events.length, 2); }); }); test("Run samples", async () => { const compiler = getCompilerWorker(compilerWorkerPath); const resultsHandler = new QscEventTarget(true); const testCases = samples.filter((x) => !x.omitFromTests); for await (const sample of testCases) { await compiler.run( { sources: [["main.qs", sample.code]], languageFeatures: [] }, "", 1, resultsHandler, ); } compiler.terminate(); assert.equal(resultsHandler.resultCount(), testCases.length); resultsHandler.getResults().forEach((result) => { assert(result.success); }); }); test("state change", async () => { const compiler = getCompilerWorker(compilerWorkerPath); const resultsHandler = new QscEventTarget(false); const stateChanges = []; compiler.onstatechange = (state) => { stateChanges.push(state); }; const code = `namespace Test { @EntryPoint() operation MyEntry() : Result { use q1 = Qubit(); return M(q1); } }`; await compiler.run( { sources: [["test.qs", code]], languageFeatures: [] }, "", 10, resultsHandler, ); compiler.terminate(); // There SHOULDN'T be a race condition here between the 'run' promise completing and the // statechange events firing, as the run promise should 'resolve' in the next microtask, // whereas the idle event should fire synchronously when the queue is empty. // For more details, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#task_queues_vs._microtasks assert(stateChanges.length === 2); assert(stateChanges[0] === "busy"); assert(stateChanges[1] === "idle"); }); test("cancel worker", () => { return new Promise((resolve) => { const code = `namespace MyQuantumApp { import Std.Diagnostics.*; @EntryPoint(AdaptiveRI) operation Main() : Result[] { repeat {} until false; return []; } }`; const cancelledArray = []; const compiler = getCompilerWorker(compilerWorkerPath); const resultsHandler = new QscEventTarget(false); // Queue some tasks that will never complete compiler .run( { sources: [["test.qs", code]], languageFeatures: [], }, "", 10, resultsHandler, ) .catch((err) => { cancelledArray.push(err); }); compiler .getHir({ sources: [["test.qs", code]], languageFeatures: [] }) .catch((err) => { cancelledArray.push(err); }); // Ensure those tasks are running/queued before terminating. setTimeout(async () => { // Terminate the compiler, which should reject the queued promises compiler.terminate(); // Start a new compiler and ensure that works fine const compiler2 = getCompilerWorker(compilerWorkerPath); const result = await compiler2.getHir({ sources: [["test.qs", code]], languageFeatures: [], }); compiler2.terminate(); // getHir should have worked assert(typeof result === "string" && result.length > 0); // Old requests were cancelled assert(cancelledArray.length === 2); assert(cancelledArray[0] === "terminated"); assert(cancelledArray[1] === "terminated"); resolve(undefined); }, 4); }); }); test("check code", async () => { const compiler = await getCompiler(); const diags = await compiler.checkCode("namespace Foo []"); assert.equal(diags.length, 1); assert.deepEqual(diags[0].range.start, { line: 0, character: 14 }); assert.deepEqual(diags[0].range.end, { line: 0, character: 15 }); }); test("language service diagnostics", async () => { const languageService = await getLanguageService(); let gotDiagnostics = false; languageService.addEventListener("diagnostics", (event) => { gotDiagnostics = true; assert.equal(event.type, "diagnostics"); assert.equal(event.detail.diagnostics.length, 1); assert.equal( event.detail.diagnostics[0].message, "type error: expected (Double, Qubit), found Qubit", ); }); await languageService.updateDocument( "test.qs", 1, `namespace Sample { operation main() : Result[] { use q1 = Qubit(); Ry(q1); let m1 = M(q1); return [m1]; } }`, "qsharp", ); // dispose() will complete when the language service has processed all the updates. await languageService.dispose(); assert(gotDiagnostics); }); test("test callable discovery", async () => { const languageService = await getLanguageService(); let gotTests = false; languageService.addEventListener("testCallables", (event) => { gotTests = true; assert.equal(event.type, "testCallables"); assert.equal(event.detail.callables.length, 1); assert.equal(event.detail.callables[0].callableName, "Sample.main"); assert.deepStrictEqual(event.detail.callables[0].location, { source: "test.qs", span: { end: { character: 18, line: 2, }, start: { character: 14, line: 2, }, }, }); }); await languageService.updateDocument( "test.qs", 1, `namespace Sample { @Test() operation main() : Unit {} }`, "qsharp", ); // dispose() will complete when the language service has processed all the updates. await languageService.dispose(); assert(gotTests); }); test("multiple test callable discovery", async () => { const languageService = await getLanguageService(); let gotTests = false; languageService.addEventListener("testCallables", (event) => { gotTests = true; assert.equal(event.type, "testCallables"); assert.equal(event.detail.callables.length, 4); assert.equal(event.detail.callables[0].callableName, "Sample.test1"); assert.equal(event.detail.callables[1].callableName, "Sample.test2"); assert.equal(event.detail.callables[2].callableName, "Sample2.test1"); assert.equal(event.detail.callables[3].callableName, "Sample2.test2"); }); await languageService.updateDocument( "test.qs", 1, `namespace Sample { @Test() operation test1() : Unit {} @Test() function test2() : Unit {} } namespace Sample2 { @Test() operation test1() : Unit {} @Test() function test2() : Unit {} } } `, "qsharp", ); // dispose() will complete when the language service has processed all the updates. await languageService.dispose(); assert(gotTests); }); test("diagnostics with related spans", async () => { const languageService = await getLanguageService(); let gotDiagnostics = false; languageService.addEventListener("diagnostics", (event) => { gotDiagnostics = true; assert.equal(event.type, "diagnostics"); assert.deepEqual( { code: "Qsc.Resolve.Ambiguous", message: "name error: `DumpMachine` could refer to the item in `Std.Diagnostics` or `Other`", related: [ { message: "ambiguous name", range: { start: { character: 8, line: 6, }, end: { character: 19, line: 6, }, }, }, { message: "found in this namespace", range: { start: { character: 13, line: 2, }, end: { character: 28, line: 2, }, }, }, { message: "and also in this namespace", range: { start: { character: 11, line: 3, }, end: { character: 16, line: 3, }, }, }, ], }, { code: event.detail.diagnostics[0].code, message: event.detail.diagnostics[0].message, related: event.detail.diagnostics[0].related?.map((r) => ({ range: r.location.span, message: r.message, })), }, ); }); await languageService.updateDocument( "test.qs", 1, `namespace Other { operation DumpMachine() : Unit { } } namespace Test { import Std.Diagnostics.*; open Other; @EntryPoint() operation Main() : Unit { DumpMachine(); } }`, "qsharp", ); // dispose() will complete when the language service has processed all the updates. await languageService.dispose(); assert(gotDiagnostics); }); test("language service diagnostics - web worker", async () => { const languageService = getLanguageServiceWorker(languageServiceWorkerPath); let gotDiagnostics = false; languageService.addEventListener("diagnostics", (event) => { gotDiagnostics = true; assert.equal(event.type, "diagnostics"); assert.equal(event.detail.diagnostics.length, 1); assert.equal( event.detail.diagnostics[0].message, "type error: expected (Double, Qubit), found Qubit", ); }); await languageService.updateDocument( "test.qs", 1, `namespace Sample { operation main() : Result[] { use q1 = Qubit(); Ry(q1); let m1 = M(q1); return [m1]; } }`, "qsharp", ); // dispose() will complete when the language service has processed all the updates. await languageService.dispose(); languageService.terminate(); assert(gotDiagnostics); }); test("language service configuration update", async () => { const languageService = getLanguageServiceWorker(languageServiceWorkerPath); // Set the configuration to expect an entry point. await languageService.updateConfiguration({ packageType: "exe" }); let actualMessages = []; languageService.addEventListener("diagnostics", (event) => { actualMessages.push({ messages: event.detail.diagnostics.map((d) => d.message), }); }); await languageService.updateDocument( "test.qs", 1, `namespace Sample { operation Test() : Unit { } }`, "qsharp", ); // Above document should have generated a missing entrypoint error. // Now update the configuration. await languageService.updateConfiguration({ packageType: "lib" }); await languageService.dispose(); languageService.terminate(); // Updating the config should cause another diagnostics event clearing the errors. // All together, two events received: one with the error, one to clear it. assert.deepStrictEqual( [ { messages: [ "entry point not found\n" + "\n" + "help: a single callable with the `@EntryPoint()` attribute must be present if no entry expression is provided and no callable named `Main` is present", ], }, { messages: [], }, ], actualMessages, ); }); test("language service in notebook", async () => { const languageService = getLanguageServiceWorker(languageServiceWorkerPath); let actualMessages = []; languageService.addEventListener("diagnostics", (event) => { actualMessages.push({ messages: event.detail.diagnostics.map((d) => d.message), }); }); await languageService.updateNotebookDocument("notebook.ipynb", 1, {}, [ { uri: "cell1", version: 1, code: "operation Main() : Unit {}" }, { uri: "cell2", version: 1, code: "Foo()" }, ]); // Above document should have generated a resolve error. await languageService.updateNotebookDocument("notebook.ipynb", 2, {}, [ { uri: "cell1", version: 2, code: "operation Main() : Unit {}" }, { uri: "cell2", version: 2, code: "Main()" }, ]); // dispose() will complete when the language service has processed all the updates. await languageService.dispose(); languageService.terminate(); // Updating the notebook should cause another diagnostics event clearing the errors. // All together, two events received: one with the error, one to clear it. assert.deepStrictEqual( [ { messages: ["name error: `Foo` not found"], }, { messages: [], }, ], actualMessages, ); }); async function testCompilerError(useWorker) { const compiler = useWorker ? getCompilerWorker(compilerWorkerPath) : await getCompiler(); if (useWorker) { // @ts-expect-error onstatechange only exists on the worker compiler.onstatechange = (state) => { lastState = state; }; } const events = new QscEventTarget(true); let promiseResult = undefined; let lastState = undefined; await compiler .run( { sources: [["test.qs", "invalid code"]], languageFeatures: [] }, "", 1, events, ) .then(() => { promiseResult = "success"; }) .catch(() => { promiseResult = "failure"; }); assert.equal(promiseResult, "failure"); const results = events.getResults(); assert.equal(results.length, 1); assert.equal(results[0].success, false); if (useWorker) { // Only the worker has state change events assert.equal(lastState, "idle"); // @ts-expect-error terminate() only exists on the worker compiler.terminate(); } } test("compiler error on run", () => testCompilerError(false)); test("compiler error on run - worker", () => testCompilerError(true)); test("OpenQASM compile error on run", async () => { const compiler = await getCompiler(); const events = new QscEventTarget(true); let promiseResult = undefined; await compiler .run( { sources: [ // Missing stdgates.inc, so CX is undefined ["test.qasm", `OPENQASM 3.0;\nqubit[2] q;\nCX q[0], q[1];`], ], languageFeatures: [], projectType: "openqasm", }, "", 1, events, ) .then(() => { promiseResult = "success"; }) .catch(() => { promiseResult = "failure"; }); assert.equal(promiseResult, "failure"); const results = events.getResults(); assert.equal(results.length, 1); assert.equal(results[0].success, false); }); test("Q# dependency compile error on run", async () => { const compiler = await getCompiler(); const events = new QscEventTarget(true); let promiseResult = undefined; await compiler .run( { packageGraphSources: { root: { sources: [ [ "main.qs", `import BrokenDep.Broken; operation Main() : Unit { Broken(); }`, ], ], languageFeatures: [], dependencies: { BrokenDep: "broken-dep-key" }, }, packages: { "broken-dep-key": { sources: [["lib.qs", "invalid code"]], languageFeatures: [], dependencies: {}, packageType: "lib", }, }, hasManifest: true, }, }, "", 1, events, ) .then(() => { promiseResult = "success"; }) .catch(() => { promiseResult = "failure"; }); assert.equal(promiseResult, "failure"); const results = events.getResults(); assert.equal(results.length, 1); assert.equal(results[0].success, false); }); test("debug service loading source without entry point attr fails - web worker", async () => { const debugService = getDebugServiceWorker(debugServiceWorkerPath); try { const result = await debugService.loadProgram( { sources: [ [ "test.qs", `namespace Sample { operation test() : Result[] { use q1 = Qubit(); Y(q1); let m1 = M(q1); return [m1]; } }`, ], ], languageFeatures: [], profile: "base", }, undefined, ); assert.ok(typeof result === "string" && result.trim().length > 0); } finally { debugService.terminate(); } }); test("debug service loading source with syntax error fails - web worker", async () => { const debugService = getDebugServiceWorker(debugServiceWorkerPath); try { const result = await debugService.loadProgram( { sources: [ [ "test.qs", `namespace Sample { operation test() : Result[] } }`, ], ], languageFeatures: [], profile: "base", }, undefined, ); assert.ok(typeof result === "string" && result.trim().length > 0); } finally { debugService.terminate(); } }); test("debug service loading source with bad entry expr fails - web worker", async () => { const debugService = getDebugServiceWorker(debugServiceWorkerPath); try { const result = await debugService.loadProgram( { sources: [ ["test.qs", `namespace Sample { operation main() : Unit { } }`], ], languageFeatures: [], profile: "base", }, "SomeBadExpr()", ); assert.ok(typeof result === "string" && result.trim().length > 0); } finally { debugService.terminate(); } }); test("debug service loading source that doesn't match profile fails - web worker", async () => { const debugService = getDebugServiceWorker(debugServiceWorkerPath); try { const result = await debugService.loadProgram( { sources: [ [ "test.qs", `namespace A { operation Test() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x } }`, ], ], languageFeatures: [], profile: "adaptive_ri", }, "A.Test()", ); assert.ok(typeof result === "string" && result.trim().length > 0); } finally { debugService.terminate(); } }); test("debug service loading source with good entry expr succeeds - web worker", async () => { const debugService = getDebugServiceWorker(debugServiceWorkerPath); try { const result = await debugService.loadProgram( { sources: [ ["test.qs", `namespace Sample { operation Main() : Unit { } }`], ], languageFeatures: [], profile: "unrestricted", }, "Sample.Main()", ); assert.ok(typeof result === "string"); assert.equal(result.trim(), ""); } finally { debugService.terminate(); } }); test("debug service loading source with entry point attr succeeds - web worker", async () => { const debugService = getDebugServiceWorker(debugServiceWorkerPath); try { const result = await debugService.loadProgram( { sources: [ [ "test.qs", `namespace Sample { @EntryPoint() operation main() : Result[] { use q1 = Qubit(); Y(q1); let m1 = M(q1); return [m1]; } }`, ], ], languageFeatures: [], profile: "base", }, undefined, ); assert.ok(typeof result === "string"); assert.equal(result.trim(), ""); } finally { debugService.terminate(); } }); test("debug service getting breakpoints after loaded source succeeds when file names match - web worker", async () => { const debugService = getDebugServiceWorker(debugServiceWorkerPath); try { const result = await debugService.loadProgram( { sources: [ [ "test.qs", `namespace Sample { @EntryPoint() operation main() : Result[] { use q1 = Qubit(); Y(q1); let m1 = M(q1); return [m1]; } }`, ], ], languageFeatures: [], profile: "base", }, undefined, ); assert.ok(typeof result === "string" && result.trim().length == 0); const bps = await debugService.getBreakpoints("test.qs"); assert.equal(bps.length, 4); } finally { debugService.terminate(); } }); test("debug service compiling multiple sources - web worker", async () => { const debugService = getDebugServiceWorker(debugServiceWorkerPath); try { const result = await debugService.loadProgram( { sources: [ [ "Foo.qs", `namespace Foo { open Bar; @EntryPoint() operation Main() : Int { Message("Hello"); Message("Hello"); return HelloFromBar(); } }`, ], [ "Bar.qs", `namespace Bar { operation HelloFromBar() : Int { return 5; } }`, ], ], languageFeatures: [], profile: "unrestricted", }, undefined, ); assert.equal(result.trim(), ""); const fooBps = await debugService.getBreakpoints("Foo.qs"); assert.equal(fooBps.length, 3); const barBps = await debugService.getBreakpoints("Bar.qs"); assert.equal(barBps.length, 1); } finally { debugService.terminate(); } }); test("CreateIntegerTicks: invalid inputs", () => { runAndAssertIntegerTicks(2, 1, []); runAndAssertIntegerTicks(0, 2, []); runAndAssertIntegerTicks(-5, 100, []); }); test("CreateIntegerTicks: below 100", () => { runAndAssertIntegerTicks(1, 1, [1]); runAndAssertIntegerTicks(3, 3, [3]); runAndAssertIntegerTicks(4, 6, [4, 5, 6]); runAndAssertIntegerTicks(1, 100, [1, 10, 100]); runAndAssertIntegerTicks(1, 10, [1, 10]); runAndAssertIntegerTicks(1, 9, [1]); runAndAssertIntegerTicks(2, 10, [10]); runAndAssertIntegerTicks(2, 9, [2, 3, 4, 5, 6, 7, 8, 9]); }); test("CreateIntegerTicks: more than 100", () => { runAndAssertIntegerTicks(20, 59, [20, 30, 40, 50]); runAndAssertIntegerTicks(231, 365, [300]); runAndAssertIntegerTicks(331, 365, [340, 350, 360]); runAndAssertIntegerTicks(567, 569, [567, 568, 569]); }); test("CreateIntegerTicks: expected qubit numbers", () => { runAndAssertIntegerTicks(400, 8000000, [1000, 10000, 100000, 1000000]); runAndAssertIntegerTicks(12345, 67890, [20000, 30000, 40000, 50000, 60000]); runAndAssertIntegerTicks(23456, 27890, [24000, 25000, 26000, 27000]); }); test("CreateTimeTicks: invalid inputs", () => { runAndAssertTimeTicks(2, 1, []); runAndAssertTimeTicks(0, 2, []); runAndAssertTimeTicks(-5, 100, []); }); const second = 1e9; const minute = 60 * second; const hour = 60 * minute; const day = 24 * hour; const week = 7 * day; const month = 30 * day; const year = 365 * day; const decade = 10 * year; const century = 10 * decade; test("CreateTimeTicks: nanoseconds below 100", () => { runAndAssertTimeTicks(1, 1, ["1 nanosecond"]); runAndAssertTimeTicks(3, 3, ["3 nanoseconds"]); runAndAssertTimeTicks(4, 6, [ "4 nanoseconds", "5 nanoseconds", "6 nanoseconds", ]); runAndAssertTimeTicks(1, 100, ["1 nanosecond"]); runAndAssertTimeTicks(1, 10, ["1 nanosecond"]); runAndAssertTimeTicks(1, 9, ["1 nanosecond"]); runAndAssertTimeTicks(2, 10, ["10 nanoseconds"]); runAndAssertTimeTicks(2, 9, [ "2 nanoseconds", "3 nanoseconds", "4 nanoseconds", "5 nanoseconds", "6 nanoseconds", "7 nanoseconds", "8 nanoseconds", "9 nanoseconds", ]); }); test("CreateTimeTicks: microseconds", () => { runAndAssertTimeTicks(800, 1000, ["1 microsecond"]); runAndAssertTimeTicks(800, 2000, ["1 microsecond"]); runAndAssertTimeTicks(800, 11000, ["1 microsecond"]); runAndAssertTimeTicks(800, 21000, ["1 microsecond"]); runAndAssertTimeTicks(800, 111000, ["1 microsecond"]); runAndAssertTimeTicks(1001, 21000, ["10 microseconds"]); runAndAssertTimeTicks(10001, 21000, ["20 microseconds"]); runAndAssertTimeTicks(10001, 30000, ["20 microseconds", "30 microseconds"]); }); test("CreateTimeTicks: milliseconds", () => { runAndAssertTimeTicks(800, 999999, ["1 microsecond"]); runAndAssertTimeTicks(800, 1000000, ["1 microsecond", "1 millisecond"]); runAndAssertTimeTicks(800000, 2000000, ["1 millisecond"]); runAndAssertTimeTicks(800000, 11000000, ["1 millisecond"]); runAndAssertTimeTicks(800000, 21000000, ["1 millisecond"]); runAndAssertTimeTicks(800000, 111000000, ["1 millisecond"]); runAndAssertTimeTicks(1000001, 111000000, ["100 milliseconds"]); }); test("CreateTimeTicks: seconds", () => { runAndAssertTimeTicks(800000, second - 1, ["1 millisecond"]); runAndAssertTimeTicks(800000, second, ["1 millisecond", "1 second"]); runAndAssertTimeTicks(800000000, 2 * second, ["1 second"]); runAndAssertTimeTicks(800000000, 11 * second, ["1 second"]); runAndAssertTimeTicks(800000000, 21 * second, ["1 second"]); runAndAssertTimeTicks(800000000, 111 * second, ["1 second", "1 minute"]); runAndAssertTimeTicks(second + 1, 111 * second, ["1 minute"]); }); test("CreateTimeTicks: minutes", () => { runAndAssertTimeTicks(second - 1, minute, ["1 second", "1 minute"]); runAndAssertTimeTicks(minute - second, 2 * minute, ["1 minute"]); runAndAssertTimeTicks(minute, 11 * minute, ["1 minute"]); runAndAssertTimeTicks(minute + 1, 21 * minute, ["10 minutes"]); runAndAssertTimeTicks(second, 111 * minute, [ "1 second", "1 minute", "1 hour", ]); runAndAssertTimeTicks(minute + 1, 111 * minute, ["1 hour"]); }); test("CreateTimeTicks: hours", () => { runAndAssertTimeTicks(minute - 1, hour, ["1 minute", "1 hour"]); runAndAssertTimeTicks(hour - minute, 2 * hour, ["1 hour"]); runAndAssertTimeTicks(hour, 11 * hour, ["1 hour"]); runAndAssertTimeTicks(hour + 1, 21 * hour, ["10 hours"]); runAndAssertTimeTicks(minute, 111 * hour, ["1 minute", "1 hour", "1 day"]); runAndAssertTimeTicks(hour + 1, 111 * hour, ["1 day"]); }); test("CreateTimeTicks: days", () => { runAndAssertTimeTicks(hour - 1, day, ["1 hour", "1 day"]); runAndAssertTimeTicks(day - hour, 2 * day, ["1 day"]); runAndAssertTimeTicks(day, 11 * day, ["1 day", "1 week"]); runAndAssertTimeTicks(day + 1, 21 * day, ["1 week"]); runAndAssertTimeTicks(hour, 111 * day, [ "1 hour", "1 day", "1 week", "1 month", ]); runAndAssertTimeTicks(day + 1, 111 * day, ["1 week", "1 month"]); }); test("CreateTimeTicks: weeks", () => { runAndAssertTimeTicks(day, week, ["1 day", "1 week"]); runAndAssertTimeTicks(day + 1, week, ["1 week"]); runAndAssertTimeTicks(day * 8, day * 27, ["2 weeks", "3 weeks"]); runAndAssertTimeTicks(week - day, 2 * week, ["1 week"]); runAndAssertTimeTicks(week, 11 * week, ["1 week", "1 month"]); runAndAssertTimeTicks(week + 1, 35 * week, ["1 month"]); runAndAssertTimeTicks(day, 111 * week, [ "1 day", "1 week", "1 month", "1 year", ]); runAndAssertTimeTicks(week + 1, 111 * week, ["1 month", "1 year"]); }); test("CreateTimeTicks: months", () => { runAndAssertTimeTicks(week - 1, month, ["1 week", "1 month"]); runAndAssertTimeTicks(month - 1, 2 * month, ["1 month"]); runAndAssertTimeTicks(month, 11 * month, ["1 month"]); runAndAssertTimeTicks(month, 12 * month, ["1 month"]); runAndAssertTimeTicks(month, 12 * month + 5 * day, ["1 month", "1 year"]); runAndAssertTimeTicks(month + 1, 12 * month, ["10 months"]); // due to precision issues month + 1 == month runAndAssertTimeTicks(month + hour, 10 * month - hour, [ "2 months", "3 months", "4 months", "5 months", "6 months", "7 months", "8 months", "9 months", ]); runAndAssertTimeTicks(week, 111 * month, ["1 week", "1 month", "1 year"]); runAndAssertTimeTicks(month + 1, 111 * month, ["1 year"]); }); test("CreateTimeTicks: years", () => { runAndAssertTimeTicks(month - 1, year, ["1 month", "1 year"]); runAndAssertTimeTicks(year - month, 2 * year, ["1 year"]); // due to precision issues year + 1 == year and decade - 1 == decade runAndAssertTimeTicks(year + day, decade - day, [ "2 years", "3 years", "4 years", "5 years", "6 years", "7 years", "8 years", "9 years", ]); runAndAssertTimeTicks(month, 111 * year, [ "1 month", "1 year", "1 decade", "1 century", ]); }); test("CreateTimeTicks: decades", () => { // due to precision issues year + 1 == year runAndAssertTimeTicks(year + day, 21 * year, ["1 decade"]); runAndAssertTimeTicks(year, decade, ["1 year", "1 decade"]); runAndAssertTimeTicks(decade - year, 2 * decade, ["1 decade"]); runAndAssertTimeTicks(year, 111 * decade, [ "1 year", "1 decade", "1 century", ]); // due to precision issues decade + 1 == decade runAndAssertTimeTicks(decade + month, 111 * decade, ["1 century"]); }); test("CreateTimeTicks: centuries", () => { runAndAssertTimeTicks(decade - 1, century, ["1 decade", "1 century"]); runAndAssertTimeTicks(century - decade, 2 * century, ["1 century"]); runAndAssertTimeTicks(century, 11 * century, ["1 century"]); runAndAssertTimeTicks(century + 1, 21 * century, ["1 century"]); runAndAssertTimeTicks(decade, 111 * century, ["1 decade", "1 century"]); runAndAssertTimeTicks(century + 1, 111 * century, ["1 century"]); }); test("CreateTimeTicks: above centuries", () => { runAndAssertTimeTicks(century + 30 * year, 3 * century, [ "2 centuries", "3 centuries", ]); runAndAssertTimeTicks(century + 30 * year, century + 55 * year, [ "13 decades", "14 decades", "15 decades", ]); runAndAssertTimeTicks(2 * century + 32 * year, 2 * century + 36 * year, [ "232 years", "233 years", "234 years", "235 years", "236 years", ]); }); function getValues(ticks) { return ticks.map((tick) => tick.value); } function getLabels(ticks) { return ticks.map((tick) => tick.label); } function runAndAssertIntegerTicks(min, max, expected) { const message = `min: ${min}, max: ${max}`; assert.deepStrictEqual( getValues(utils.CreateIntegerTicks(min, max)), expected, message, ); } function runAndAssertTimeTicks(min, max, expected) { const message = `min: ${min}, max: ${max}`; assert.deepStrictEqual( getLabels(utils.CreateTimeTicks(min, max)), expected, message, ); }