microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
billti/activitybar-icon

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/npm/qsharp/test/basics.js

1360lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//@ts-check
5
6import assert from "node:assert/strict";
7import { test } from "node:test";
8import { log } from "../dist/log.js";
9import {
10 getCompiler,
11 getCompilerWorker,
12 getLanguageService,
13 getLanguageServiceWorker,
14 getDebugServiceWorker,
15 loadWasmModule,
16 utils,
17} from "../dist/node.js";
18
19import { QscEventTarget } from "../dist/compiler/events.js";
20import { getAllKatas, getExerciseSources, getKata } from "../dist/katas.js";
21import samples from "../dist/samples.generated.js";
22
23import { readFileSync } from "node:fs";
24
25const distDir = new URL("../dist/", import.meta.url);
26const compilerWorkerPath = new URL("compiler/worker.js", distDir).href;
27const languageServiceWorkerPath = new URL("language-service/worker.js", distDir)
28 .href;
29const debugServiceWorkerPath = new URL("debug-service/worker.js", distDir).href;
30
31// Load the wasm module before running any tests
32const wasmPath = new URL("../lib/web/qsc_wasm_bg.wasm", import.meta.url);
33await loadWasmModule(readFileSync(wasmPath).buffer);
34
35/** @type {import("../dist/log.js").TelemetryEvent[]} */
36const telemetryEvents = [];
37log.setLogLevel("warn");
38log.setTelemetryCollector((event) => telemetryEvents.push(event));
39
40/**
41 *
42 * @param {string} code
43 * @param {string} expr
44 * @param {boolean} useWorker
45 * @returns {Promise<import("../dist/compiler/common.js").ShotResult>}
46 */
47export async function runSingleShot(code, expr, useWorker) {
48 const resultsHandler = new QscEventTarget(true);
49 const compiler = useWorker
50 ? getCompilerWorker(compilerWorkerPath)
51 : await getCompiler();
52
53 try {
54 await compiler.run(
55 { sources: [["test.qs", code]], languageFeatures: [] },
56 expr,
57 1,
58 resultsHandler,
59 );
60 return resultsHandler.getResults()[0];
61 } catch (err) {
62 console.error("Error during runSingleShot:", err);
63 throw err;
64 } finally {
65 /* @ts-expect-error: ICompiler does not include 'terminate' */
66 if (useWorker) compiler.terminate();
67 }
68}
69
70test("autogenerated documentation", async () => {
71 const compiler = await getCompiler();
72 const regex = new RegExp("^qsharp.namespace: (.+)$", "m");
73 const docFiles = await compiler.getDocumentation();
74 var numberOfGoodFiles = 0;
75 for (const doc of docFiles) {
76 assert(doc, "Each documentation file should be present.");
77 if (doc.filename === "index.md") {
78 continue; // Skip index.md - its contents are added later
79 }
80 assert(
81 doc.contents && doc.contents.length > 10,
82 "Content for each documentation file should be present.",
83 );
84 const match = regex.exec(doc.metadata); // Parse namespace out of metadata
85 if (match == null) {
86 continue; // Skip items with non-parsable metadata
87 }
88 const namespace = match[1];
89 assert(
90 namespace.startsWith("Std.") || namespace.startsWith("Microsoft.Quantum"),
91 // old libraries like Unstable are still in M.Q, but newer ones are in Std.
92 "Namespaces in the standard library should start with Std. or Microsoft.Quantum",
93 );
94 numberOfGoodFiles++;
95 }
96 // Number of functions with comments may change in the standard library,
97 // But it should be large enough.
98 assert(
99 numberOfGoodFiles > 100,
100 "Number of good documentation files should be large enough.",
101 );
102});
103
104test("library summaries slim docs", async () => {
105 const compiler = await getCompiler();
106 const summaries = await compiler.getLibrarySummaries();
107 assert(typeof summaries === "string", "Summaries should be a string");
108 assert(summaries.length > 0, "Summaries should not be empty");
109
110 // Check that it contains namespace headers (markdown format)
111 assert(
112 summaries.includes("# Microsoft.Quantum"),
113 "Should contain standard library namespaces",
114 );
115
116 // Check that it contains function signatures in code blocks
117 assert(summaries.includes("```qsharp"), "Should contain Q# code blocks");
118 assert(summaries.includes("## "), "Should contain function headers");
119
120 // Check that it's organized by namespace
121 const lines = summaries.split("\n");
122 const namespaceHeaders = lines.filter((line) => line.startsWith("# "));
123 assert(namespaceHeaders.length > 0, "Should have namespace headers");
124});
125
126test("basic eval", async () => {
127 const code = `namespace Test {
128 function Answer() : Int {
129 return 42;
130 }
131 }`;
132 const expr = `Test.Answer()`;
133
134 const result = await runSingleShot(code, expr, false);
135 assert(result.success);
136 assert.equal(result.result, "42");
137});
138
139test("EntryPoint only", async () => {
140 const code = `
141namespace Test {
142 @EntryPoint()
143 operation MyEntry() : Result {
144 use q1 = Qubit();
145 return M(q1);
146 }
147}`;
148 const result = await runSingleShot(code, "", true);
149 assert(result.success === true);
150 assert(result.result === "Zero");
151});
152
153test("one syntax error", async () => {
154 const compiler = await getCompiler();
155
156 const diags = await compiler.checkCode("namespace Foo []");
157 assert.equal(diags.length, 1);
158 assert.deepEqual(diags[0].range.start, { line: 0, character: 14 });
159 assert.deepEqual(diags[0].range.end, { line: 0, character: 15 });
160});
161
162test("error with newlines", async () => {
163 const compiler = await getCompiler();
164
165 const diags = await compiler.checkCode(
166 "namespace input { operation Foo(a) : Unit {} }",
167 );
168 assert.equal(diags.length, 2);
169 assert.deepEqual(diags[0].range.start, { line: 0, character: 32 });
170 assert.deepEqual(diags[0].range.end, { line: 0, character: 33 });
171 assert.deepEqual(diags[1].range.start, { line: 0, character: 32 });
172 assert.deepEqual(diags[1].range.end, { line: 0, character: 33 });
173 assert.equal(
174 diags[1].message,
175 "type error: insufficient type information to infer type\n\nhelp: provide a type annotation",
176 );
177 assert.equal(
178 diags[0].message,
179 "type error: missing type in item signature\n\nhelp: a type must be provided for this item",
180 );
181});
182
183test("dump and message output", async () => {
184 const code = `namespace Test {
185 function Answer() : Int {
186 Microsoft.Quantum.Diagnostics.DumpMachine();
187 Message("hello, qsharp");
188 return 42;
189 }
190 }`;
191 const expr = `Test.Answer()`;
192
193 const result = await runSingleShot(code, expr, true);
194 assert(result.success);
195 assert(result.events.length === 2);
196 assert(result.events[0].type === "DumpMachine");
197 assert(result.events[0].state["|0⟩"].length === 2);
198 assert(result.events[1].type === "Message");
199 assert(result.events[1].message === "hello, qsharp");
200});
201
202async function runExerciseSolutionCheck(exercise, solution) {
203 const evtTarget = new QscEventTarget(true);
204 const compiler = await getCompiler();
205 const sources = await getExerciseSources(exercise);
206 const success = await compiler.checkExerciseSolution(
207 solution,
208 sources,
209 evtTarget,
210 );
211
212 const unsuccessful_events = evtTarget
213 .getResults()
214 .filter((evt) => !evt.success);
215 let errorMsg = "";
216 for (const event of unsuccessful_events) {
217 const error = event.result;
218 if (typeof error === "string") {
219 errorMsg += "Result = " + error + "\n";
220 } else {
221 errorMsg += "Message = " + error.message + "\n";
222 }
223 }
224
225 return {
226 success: success,
227 errorCount: unsuccessful_events.length,
228 errorMsg: errorMsg,
229 };
230}
231
232async function getAllKataExamples(kata) {
233 let examples = [];
234
235 // Get all the examples contained in solution explanations.
236 const exerciseExamples = kata.sections
237 .filter((section) => section.type === "exercise")
238 .map((exercise) =>
239 exercise.explainedSolution.items.filter(
240 (item) => item.type === "example",
241 ),
242 )
243 .flat();
244 examples = examples.concat(exerciseExamples);
245
246 // Get all the examples in lessons.
247 const lessonExamples = kata.sections
248 .filter((section) => section.type === "lesson")
249 .map((lesson) => lesson.items.filter((item) => item.type === "example"))
250 .flat();
251 examples = examples.concat(lessonExamples);
252
253 return examples;
254}
255
256async function validateExercise(
257 exercise,
258 validatePlaceholder,
259 validateSolutions,
260) {
261 // Validate the correctness of the placeholder code.
262 if (validatePlaceholder) {
263 const placeholderResult = await runExerciseSolutionCheck(
264 exercise,
265 exercise.placeholderCode,
266 );
267
268 // Check that there are no compilation or runtime errors.
269 assert(
270 placeholderResult.errorCount === 0,
271 `Exercise "${exercise.id}" has compilation or runtime errors when using the placeholder as solution. ` +
272 `Compilation and runtime errors:\n${placeholderResult.errorMsg}`,
273 );
274
275 // Check that the placeholder is an incorrect solution.
276 assert(
277 !placeholderResult.success,
278 `Placeholder for exercise "${exercise.id}" is a correct solution but it is expected to be an incorrect solution`,
279 );
280 }
281
282 // Validate the correctness of the solutions.
283 if (validateSolutions) {
284 const solutions = exercise.explainedSolution.items.filter(
285 (item) => item.type === "solution",
286 );
287
288 // Check that the exercise has at least one solution.
289 assert(
290 solutions.length > 0,
291 `Exercise "${exercise.id}" does not have solutions`,
292 );
293
294 // Check that the solutions are correct.
295 for (const solution of solutions) {
296 const solutionResult = await runExerciseSolutionCheck(
297 exercise,
298 solution.code,
299 );
300
301 // Check that there are no compilation or runtime errors.
302 assert(
303 solutionResult.errorCount === 0,
304 `Solution "${solution.id}" for exercise "${exercise.id}" has compilation or runtime errors` +
305 `Compilation and runtime errors:\n${solutionResult.errorMsg}`,
306 );
307
308 // Check that the solution is correct.
309 assert(
310 solutionResult.success,
311 `Solution "${solution.id}" for exercise "${exercise.id}" is incorrect`,
312 );
313 }
314 }
315}
316
317async function validateKata(
318 kata,
319 validateExamples,
320 validateExercisePlaceholder,
321 validateExerciseSolutions,
322) {
323 // Validate the correctness of Q# code related to exercises.
324 const exercises = kata.sections.filter(
325 (section) => section.type === "exercise",
326 );
327 for (const exercise of exercises) {
328 await validateExercise(
329 exercise,
330 validateExercisePlaceholder,
331 validateExerciseSolutions,
332 );
333 }
334
335 if (validateExamples) {
336 const examples = await getAllKataExamples(kata);
337 for (const example of examples) {
338 try {
339 const result = await runSingleShot(example.code, "", false);
340 assert(
341 result.success,
342 `Example "${example.id}" in "${kata.id}" kata failed to run.`,
343 );
344 } catch (error) {
345 assert(
346 false,
347 `Example "${example.id}" in "${kata.id}" kata failed to build:\n${error}`,
348 );
349 }
350 }
351 }
352}
353
354test("getAllKatas works", async () => {
355 const katas = await getAllKatas({ includeUnpublished: true });
356 assert.ok(katas.length > 0, "katas should not be empty");
357});
358
359test("all katas", async (t) => {
360 // Run tests for all katas, including unpublished
361 const katasList = await getAllKatas({ includeUnpublished: true });
362
363 for (const kataDesc of katasList) {
364 await t.test(`${kataDesc.id} kata is valid`, async () => {
365 const kata = await getKata(kataDesc.id);
366 await validateKata(kata, true, true, true);
367 });
368 }
369});
370
371test("worker 100 shots", async () => {
372 let code = `namespace Test {
373 function Answer() : Int {
374 Microsoft.Quantum.Diagnostics.DumpMachine();
375 Message("hello, qsharp");
376 return 42;
377 }
378 }`;
379 let expr = `Test.Answer()`;
380
381 const resultsHandler = new QscEventTarget(true);
382 const compiler = getCompilerWorker(compilerWorkerPath);
383 await compiler.run(
384 { sources: [["test.qs", code]], languageFeatures: [] },
385 expr,
386 100,
387 resultsHandler,
388 );
389 compiler.terminate();
390
391 const results = resultsHandler.getResults();
392
393 assert.equal(results.length, 100);
394 results.forEach((result) => {
395 assert(result.success);
396 assert.equal(result.result, "42");
397 assert.equal(result.events.length, 2);
398 });
399});
400
401test("Run samples", async () => {
402 const compiler = getCompilerWorker(compilerWorkerPath);
403 const resultsHandler = new QscEventTarget(true);
404 const testCases = samples.filter((x) => !x.omitFromTests);
405
406 for await (const sample of testCases) {
407 await compiler.run(
408 { sources: [["main.qs", sample.code]], languageFeatures: [] },
409 "",
410 1,
411 resultsHandler,
412 );
413 }
414
415 compiler.terminate();
416 assert.equal(resultsHandler.resultCount(), testCases.length);
417 resultsHandler.getResults().forEach((result) => {
418 assert(result.success);
419 });
420});
421
422test("state change", async () => {
423 const compiler = getCompilerWorker(compilerWorkerPath);
424 const resultsHandler = new QscEventTarget(false);
425 const stateChanges = [];
426
427 compiler.onstatechange = (state) => {
428 stateChanges.push(state);
429 };
430 const code = `namespace Test {
431 @EntryPoint()
432 operation MyEntry() : Result {
433 use q1 = Qubit();
434 return M(q1);
435 }
436 }`;
437 await compiler.run(
438 { sources: [["test.qs", code]], languageFeatures: [] },
439 "",
440 10,
441 resultsHandler,
442 );
443 compiler.terminate();
444 // There SHOULDN'T be a race condition here between the 'run' promise completing and the
445 // statechange events firing, as the run promise should 'resolve' in the next microtask,
446 // whereas the idle event should fire synchronously when the queue is empty.
447 // For more details, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#task_queues_vs._microtasks
448 assert(stateChanges.length === 2);
449 assert(stateChanges[0] === "busy");
450 assert(stateChanges[1] === "idle");
451});
452
453test("cancel worker", () => {
454 return new Promise((resolve) => {
455 const code = `namespace MyQuantumApp {
456 import Std.Diagnostics.*;
457
458 @EntryPoint(AdaptiveRI)
459 operation Main() : Result[] {
460 repeat {} until false;
461 return [];
462 }
463 }`;
464
465 const cancelledArray = [];
466 const compiler = getCompilerWorker(compilerWorkerPath);
467 const resultsHandler = new QscEventTarget(false);
468
469 // Queue some tasks that will never complete
470 compiler
471 .run(
472 {
473 sources: [["test.qs", code]],
474 languageFeatures: [],
475 },
476 "",
477 10,
478 resultsHandler,
479 )
480 .catch((err) => {
481 cancelledArray.push(err);
482 });
483 compiler.getHir(code, []).catch((err) => {
484 cancelledArray.push(err);
485 });
486
487 // Ensure those tasks are running/queued before terminating.
488 setTimeout(async () => {
489 // Terminate the compiler, which should reject the queued promises
490 compiler.terminate();
491
492 // Start a new compiler and ensure that works fine
493 const compiler2 = getCompilerWorker(compilerWorkerPath);
494 const result = await compiler2.getHir(code, []);
495 compiler2.terminate();
496
497 // getHir should have worked
498 assert(typeof result === "string" && result.length > 0);
499
500 // Old requests were cancelled
501 assert(cancelledArray.length === 2);
502 assert(cancelledArray[0] === "terminated");
503 assert(cancelledArray[1] === "terminated");
504 resolve(undefined);
505 }, 4);
506 });
507});
508
509test("check code", async () => {
510 const compiler = await getCompiler();
511
512 const diags = await compiler.checkCode("namespace Foo []");
513 assert.equal(diags.length, 1);
514 assert.deepEqual(diags[0].range.start, { line: 0, character: 14 });
515 assert.deepEqual(diags[0].range.end, { line: 0, character: 15 });
516});
517
518test("language service diagnostics", async () => {
519 const languageService = await getLanguageService();
520 let gotDiagnostics = false;
521 languageService.addEventListener("diagnostics", (event) => {
522 gotDiagnostics = true;
523 assert.equal(event.type, "diagnostics");
524 assert.equal(event.detail.diagnostics.length, 1);
525 assert.equal(
526 event.detail.diagnostics[0].message,
527 "type error: expected (Double, Qubit), found Qubit",
528 );
529 });
530 await languageService.updateDocument(
531 "test.qs",
532 1,
533 `namespace Sample {
534 operation main() : Result[] {
535 use q1 = Qubit();
536 Ry(q1);
537 let m1 = M(q1);
538 return [m1];
539 }
540}`,
541 "qsharp",
542 );
543
544 // dispose() will complete when the language service has processed all the updates.
545 await languageService.dispose();
546 assert(gotDiagnostics);
547});
548
549test("test callable discovery", async () => {
550 const languageService = await getLanguageService();
551 let gotTests = false;
552 languageService.addEventListener("testCallables", (event) => {
553 gotTests = true;
554 assert.equal(event.type, "testCallables");
555 assert.equal(event.detail.callables.length, 1);
556 assert.equal(event.detail.callables[0].callableName, "Sample.main");
557 assert.deepStrictEqual(event.detail.callables[0].location, {
558 source: "test.qs",
559 span: {
560 end: {
561 character: 18,
562 line: 2,
563 },
564 start: {
565 character: 14,
566 line: 2,
567 },
568 },
569 });
570 });
571 await languageService.updateDocument(
572 "test.qs",
573 1,
574 `namespace Sample {
575 @Test()
576 operation main() : Unit {}
577}`,
578 "qsharp",
579 );
580
581 // dispose() will complete when the language service has processed all the updates.
582 await languageService.dispose();
583 assert(gotTests);
584});
585
586test("multiple test callable discovery", async () => {
587 const languageService = await getLanguageService();
588 let gotTests = false;
589 languageService.addEventListener("testCallables", (event) => {
590 gotTests = true;
591 assert.equal(event.type, "testCallables");
592 assert.equal(event.detail.callables.length, 4);
593 assert.equal(event.detail.callables[0].callableName, "Sample.test1");
594 assert.equal(event.detail.callables[1].callableName, "Sample.test2");
595 assert.equal(event.detail.callables[2].callableName, "Sample2.test1");
596 assert.equal(event.detail.callables[3].callableName, "Sample2.test2");
597 });
598 await languageService.updateDocument(
599 "test.qs",
600 1,
601 `namespace Sample {
602 @Test()
603 operation test1() : Unit {}
604
605 @Test()
606 function test2() : Unit {}
607}
608namespace Sample2 {
609 @Test()
610 operation test1() : Unit {}
611
612 @Test()
613 function test2() : Unit {}
614 }
615}
616`,
617 "qsharp",
618 );
619
620 // dispose() will complete when the language service has processed all the updates.
621 await languageService.dispose();
622 assert(gotTests);
623});
624
625test("diagnostics with related spans", async () => {
626 const languageService = await getLanguageService();
627 let gotDiagnostics = false;
628 languageService.addEventListener("diagnostics", (event) => {
629 gotDiagnostics = true;
630 assert.equal(event.type, "diagnostics");
631 assert.deepEqual(
632 {
633 code: "Qsc.Resolve.Ambiguous",
634 message:
635 "name error: `DumpMachine` could refer to the item in `Std.Diagnostics` or `Other`",
636 related: [
637 {
638 message: "ambiguous name",
639 range: {
640 start: {
641 character: 8,
642 line: 6,
643 },
644 end: {
645 character: 19,
646 line: 6,
647 },
648 },
649 },
650 {
651 message: "found in this namespace",
652 range: {
653 start: {
654 character: 13,
655 line: 2,
656 },
657 end: {
658 character: 28,
659 line: 2,
660 },
661 },
662 },
663 {
664 message: "and also in this namespace",
665 range: {
666 start: {
667 character: 11,
668 line: 3,
669 },
670 end: {
671 character: 16,
672 line: 3,
673 },
674 },
675 },
676 ],
677 },
678 {
679 code: event.detail.diagnostics[0].code,
680 message: event.detail.diagnostics[0].message,
681 related: event.detail.diagnostics[0].related?.map((r) => ({
682 range: r.location.span,
683 message: r.message,
684 })),
685 },
686 );
687 });
688
689 await languageService.updateDocument(
690 "test.qs",
691 1,
692 `namespace Other { operation DumpMachine() : Unit { } }
693 namespace Test {
694 import Std.Diagnostics.*;
695 open Other;
696 @EntryPoint()
697 operation Main() : Unit {
698 DumpMachine();
699 }
700 }`,
701 "qsharp",
702 );
703
704 // dispose() will complete when the language service has processed all the updates.
705 await languageService.dispose();
706 assert(gotDiagnostics);
707});
708
709test("language service diagnostics - web worker", async () => {
710 const languageService = getLanguageServiceWorker(languageServiceWorkerPath);
711 let gotDiagnostics = false;
712 languageService.addEventListener("diagnostics", (event) => {
713 gotDiagnostics = true;
714 assert.equal(event.type, "diagnostics");
715 assert.equal(event.detail.diagnostics.length, 1);
716 assert.equal(
717 event.detail.diagnostics[0].message,
718 "type error: expected (Double, Qubit), found Qubit",
719 );
720 });
721 await languageService.updateDocument(
722 "test.qs",
723 1,
724 `namespace Sample {
725 operation main() : Result[] {
726 use q1 = Qubit();
727 Ry(q1);
728 let m1 = M(q1);
729 return [m1];
730 }
731}`,
732 "qsharp",
733 );
734
735 // dispose() will complete when the language service has processed all the updates.
736 await languageService.dispose();
737 languageService.terminate();
738 assert(gotDiagnostics);
739});
740
741test("language service configuration update", async () => {
742 const languageService = getLanguageServiceWorker(languageServiceWorkerPath);
743
744 // Set the configuration to expect an entry point.
745 await languageService.updateConfiguration({ packageType: "exe" });
746
747 let actualMessages = [];
748 languageService.addEventListener("diagnostics", (event) => {
749 actualMessages.push({
750 messages: event.detail.diagnostics.map((d) => d.message),
751 });
752 });
753 await languageService.updateDocument(
754 "test.qs",
755 1,
756 `namespace Sample {
757 operation Test() : Unit {
758 }
759}`,
760 "qsharp",
761 );
762
763 // Above document should have generated a missing entrypoint error.
764
765 // Now update the configuration.
766 await languageService.updateConfiguration({ packageType: "lib" });
767
768 await languageService.dispose();
769 languageService.terminate();
770
771 // Updating the config should cause another diagnostics event clearing the errors.
772
773 // All together, two events received: one with the error, one to clear it.
774 assert.deepStrictEqual(
775 [
776 {
777 messages: [
778 "entry point not found\n" +
779 "\n" +
780 "help: a single callable with the `@EntryPoint()` attribute must be present if no entry expression is provided and no callable named `Main` is present",
781 ],
782 },
783 {
784 messages: [],
785 },
786 ],
787 actualMessages,
788 );
789});
790
791test("language service in notebook", async () => {
792 const languageService = getLanguageServiceWorker(languageServiceWorkerPath);
793 let actualMessages = [];
794 languageService.addEventListener("diagnostics", (event) => {
795 actualMessages.push({
796 messages: event.detail.diagnostics.map((d) => d.message),
797 });
798 });
799
800 await languageService.updateNotebookDocument("notebook.ipynb", 1, {}, [
801 { uri: "cell1", version: 1, code: "operation Main() : Unit {}" },
802 { uri: "cell2", version: 1, code: "Foo()" },
803 ]);
804
805 // Above document should have generated a resolve error.
806
807 await languageService.updateNotebookDocument("notebook.ipynb", 2, {}, [
808 { uri: "cell1", version: 2, code: "operation Main() : Unit {}" },
809 { uri: "cell2", version: 2, code: "Main()" },
810 ]);
811
812 // dispose() will complete when the language service has processed all the updates.
813 await languageService.dispose();
814 languageService.terminate();
815
816 // Updating the notebook should cause another diagnostics event clearing the errors.
817
818 // All together, two events received: one with the error, one to clear it.
819 assert.deepStrictEqual(
820 [
821 {
822 messages: ["name error: `Foo` not found"],
823 },
824 {
825 messages: [],
826 },
827 ],
828 actualMessages,
829 );
830});
831
832async function testCompilerError(useWorker) {
833 const compiler = useWorker
834 ? getCompilerWorker(compilerWorkerPath)
835 : await getCompiler();
836 if (useWorker) {
837 // @ts-expect-error onstatechange only exists on the worker
838 compiler.onstatechange = (state) => {
839 lastState = state;
840 };
841 }
842
843 const events = new QscEventTarget(true);
844 let promiseResult = undefined;
845 let lastState = undefined;
846 await compiler
847 .run(
848 { sources: [["test.qs", "invalid code"]], languageFeatures: [] },
849 "",
850 1,
851 events,
852 )
853 .then(() => {
854 promiseResult = "success";
855 })
856 .catch(() => {
857 promiseResult = "failure";
858 });
859
860 assert.equal(promiseResult, "failure");
861 const results = events.getResults();
862 assert.equal(results.length, 1);
863 assert.equal(results[0].success, false);
864 if (useWorker) {
865 // Only the worker has state change events
866 assert.equal(lastState, "idle");
867 // @ts-expect-error terminate() only exists on the worker
868 compiler.terminate();
869 }
870}
871
872test("compiler error on run", () => testCompilerError(false));
873test("compiler error on run - worker", () => testCompilerError(true));
874
875test("debug service loading source without entry point attr fails - web worker", async () => {
876 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
877 try {
878 const result = await debugService.loadProgram(
879 {
880 sources: [
881 [
882 "test.qs",
883 `namespace Sample {
884 operation test() : Result[] {
885 use q1 = Qubit();
886 Y(q1);
887 let m1 = M(q1);
888 return [m1];
889 }
890}`,
891 ],
892 ],
893 languageFeatures: [],
894 profile: "base",
895 },
896 undefined,
897 );
898 assert.ok(typeof result === "string" && result.trim().length > 0);
899 } finally {
900 debugService.terminate();
901 }
902});
903
904test("debug service loading source with syntax error fails - web worker", async () => {
905 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
906 try {
907 const result = await debugService.loadProgram(
908 {
909 sources: [
910 [
911 "test.qs",
912 `namespace Sample {
913 operation test() : Result[]
914 }
915}`,
916 ],
917 ],
918 languageFeatures: [],
919 profile: "base",
920 },
921 undefined,
922 );
923 assert.ok(typeof result === "string" && result.trim().length > 0);
924 } finally {
925 debugService.terminate();
926 }
927});
928
929test("debug service loading source with bad entry expr fails - web worker", async () => {
930 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
931 try {
932 const result = await debugService.loadProgram(
933 {
934 sources: [
935 ["test.qs", `namespace Sample { operation main() : Unit { } }`],
936 ],
937 languageFeatures: [],
938 profile: "base",
939 },
940 "SomeBadExpr()",
941 );
942 assert.ok(typeof result === "string" && result.trim().length > 0);
943 } finally {
944 debugService.terminate();
945 }
946});
947
948test("debug service loading source that doesn't match profile fails - web worker", async () => {
949 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
950 try {
951 const result = await debugService.loadProgram(
952 {
953 sources: [
954 [
955 "test.qs",
956 `namespace A { operation Test() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x } }`,
957 ],
958 ],
959 languageFeatures: [],
960 profile: "adaptive_ri",
961 },
962 "A.Test()",
963 );
964 assert.ok(typeof result === "string" && result.trim().length > 0);
965 } finally {
966 debugService.terminate();
967 }
968});
969
970test("debug service loading source with good entry expr succeeds - web worker", async () => {
971 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
972 try {
973 const result = await debugService.loadProgram(
974 {
975 sources: [
976 ["test.qs", `namespace Sample { operation Main() : Unit { } }`],
977 ],
978 languageFeatures: [],
979 profile: "unrestricted",
980 },
981 "Sample.Main()",
982 );
983 assert.ok(typeof result === "string");
984 assert.equal(result.trim(), "");
985 } finally {
986 debugService.terminate();
987 }
988});
989
990test("debug service loading source with entry point attr succeeds - web worker", async () => {
991 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
992 try {
993 const result = await debugService.loadProgram(
994 {
995 sources: [
996 [
997 "test.qs",
998 `namespace Sample {
999 @EntryPoint()
1000 operation main() : Result[] {
1001 use q1 = Qubit();
1002 Y(q1);
1003 let m1 = M(q1);
1004 return [m1];
1005 }
1006}`,
1007 ],
1008 ],
1009 languageFeatures: [],
1010 profile: "base",
1011 },
1012 undefined,
1013 );
1014 assert.ok(typeof result === "string");
1015 assert.equal(result.trim(), "");
1016 } finally {
1017 debugService.terminate();
1018 }
1019});
1020
1021test("debug service getting breakpoints after loaded source succeeds when file names match - web worker", async () => {
1022 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
1023 try {
1024 const result = await debugService.loadProgram(
1025 {
1026 sources: [
1027 [
1028 "test.qs",
1029 `namespace Sample {
1030 @EntryPoint()
1031 operation main() : Result[] {
1032 use q1 = Qubit();
1033 Y(q1);
1034 let m1 = M(q1);
1035 return [m1];
1036 }
1037}`,
1038 ],
1039 ],
1040 languageFeatures: [],
1041 profile: "base",
1042 },
1043 undefined,
1044 );
1045 assert.ok(typeof result === "string" && result.trim().length == 0);
1046 const bps = await debugService.getBreakpoints("test.qs");
1047 assert.equal(bps.length, 4);
1048 } finally {
1049 debugService.terminate();
1050 }
1051});
1052
1053test("debug service compiling multiple sources - web worker", async () => {
1054 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
1055 try {
1056 const result = await debugService.loadProgram(
1057 {
1058 sources: [
1059 [
1060 "Foo.qs",
1061 `namespace Foo {
1062 open Bar;
1063 @EntryPoint()
1064 operation Main() : Int {
1065 Message("Hello");
1066 Message("Hello");
1067 return HelloFromBar();
1068 }
1069}`,
1070 ],
1071 [
1072 "Bar.qs",
1073 `namespace Bar {
1074 operation HelloFromBar() : Int {
1075 return 5;
1076 }
1077}`,
1078 ],
1079 ],
1080 languageFeatures: [],
1081 profile: "unrestricted",
1082 },
1083 undefined,
1084 );
1085 assert.equal(result.trim(), "");
1086 const fooBps = await debugService.getBreakpoints("Foo.qs");
1087 assert.equal(fooBps.length, 3);
1088
1089 const barBps = await debugService.getBreakpoints("Bar.qs");
1090 assert.equal(barBps.length, 1);
1091 } finally {
1092 debugService.terminate();
1093 }
1094});
1095
1096test("CreateIntegerTicks: invalid inputs", () => {
1097 runAndAssertIntegerTicks(2, 1, []);
1098 runAndAssertIntegerTicks(0, 2, []);
1099 runAndAssertIntegerTicks(-5, 100, []);
1100});
1101
1102test("CreateIntegerTicks: below 100", () => {
1103 runAndAssertIntegerTicks(1, 1, [1]);
1104 runAndAssertIntegerTicks(3, 3, [3]);
1105 runAndAssertIntegerTicks(4, 6, [4, 5, 6]);
1106 runAndAssertIntegerTicks(1, 100, [1, 10, 100]);
1107 runAndAssertIntegerTicks(1, 10, [1, 10]);
1108 runAndAssertIntegerTicks(1, 9, [1]);
1109 runAndAssertIntegerTicks(2, 10, [10]);
1110 runAndAssertIntegerTicks(2, 9, [2, 3, 4, 5, 6, 7, 8, 9]);
1111});
1112
1113test("CreateIntegerTicks: more than 100", () => {
1114 runAndAssertIntegerTicks(20, 59, [20, 30, 40, 50]);
1115 runAndAssertIntegerTicks(231, 365, [300]);
1116 runAndAssertIntegerTicks(331, 365, [340, 350, 360]);
1117 runAndAssertIntegerTicks(567, 569, [567, 568, 569]);
1118});
1119
1120test("CreateIntegerTicks: expected qubit numbers", () => {
1121 runAndAssertIntegerTicks(400, 8000000, [1000, 10000, 100000, 1000000]);
1122 runAndAssertIntegerTicks(12345, 67890, [20000, 30000, 40000, 50000, 60000]);
1123 runAndAssertIntegerTicks(23456, 27890, [24000, 25000, 26000, 27000]);
1124});
1125
1126test("CreateTimeTicks: invalid inputs", () => {
1127 runAndAssertTimeTicks(2, 1, []);
1128 runAndAssertTimeTicks(0, 2, []);
1129 runAndAssertTimeTicks(-5, 100, []);
1130});
1131
1132const second = 1e9;
1133const minute = 60 * second;
1134const hour = 60 * minute;
1135const day = 24 * hour;
1136const week = 7 * day;
1137const month = 30 * day;
1138const year = 365 * day;
1139const decade = 10 * year;
1140const century = 10 * decade;
1141
1142test("CreateTimeTicks: nanoseconds below 100", () => {
1143 runAndAssertTimeTicks(1, 1, ["1 nanosecond"]);
1144 runAndAssertTimeTicks(3, 3, ["3 nanoseconds"]);
1145 runAndAssertTimeTicks(4, 6, [
1146 "4 nanoseconds",
1147 "5 nanoseconds",
1148 "6 nanoseconds",
1149 ]);
1150 runAndAssertTimeTicks(1, 100, ["1 nanosecond"]);
1151 runAndAssertTimeTicks(1, 10, ["1 nanosecond"]);
1152 runAndAssertTimeTicks(1, 9, ["1 nanosecond"]);
1153 runAndAssertTimeTicks(2, 10, ["10 nanoseconds"]);
1154 runAndAssertTimeTicks(2, 9, [
1155 "2 nanoseconds",
1156 "3 nanoseconds",
1157 "4 nanoseconds",
1158 "5 nanoseconds",
1159 "6 nanoseconds",
1160 "7 nanoseconds",
1161 "8 nanoseconds",
1162 "9 nanoseconds",
1163 ]);
1164});
1165
1166test("CreateTimeTicks: microseconds", () => {
1167 runAndAssertTimeTicks(800, 1000, ["1 microsecond"]);
1168 runAndAssertTimeTicks(800, 2000, ["1 microsecond"]);
1169 runAndAssertTimeTicks(800, 11000, ["1 microsecond"]);
1170 runAndAssertTimeTicks(800, 21000, ["1 microsecond"]);
1171 runAndAssertTimeTicks(800, 111000, ["1 microsecond"]);
1172 runAndAssertTimeTicks(1001, 21000, ["10 microseconds"]);
1173 runAndAssertTimeTicks(10001, 21000, ["20 microseconds"]);
1174 runAndAssertTimeTicks(10001, 30000, ["20 microseconds", "30 microseconds"]);
1175});
1176
1177test("CreateTimeTicks: milliseconds", () => {
1178 runAndAssertTimeTicks(800, 999999, ["1 microsecond"]);
1179 runAndAssertTimeTicks(800, 1000000, ["1 microsecond", "1 millisecond"]);
1180 runAndAssertTimeTicks(800000, 2000000, ["1 millisecond"]);
1181 runAndAssertTimeTicks(800000, 11000000, ["1 millisecond"]);
1182 runAndAssertTimeTicks(800000, 21000000, ["1 millisecond"]);
1183 runAndAssertTimeTicks(800000, 111000000, ["1 millisecond"]);
1184 runAndAssertTimeTicks(1000001, 111000000, ["100 milliseconds"]);
1185});
1186
1187test("CreateTimeTicks: seconds", () => {
1188 runAndAssertTimeTicks(800000, second - 1, ["1 millisecond"]);
1189 runAndAssertTimeTicks(800000, second, ["1 millisecond", "1 second"]);
1190 runAndAssertTimeTicks(800000000, 2 * second, ["1 second"]);
1191 runAndAssertTimeTicks(800000000, 11 * second, ["1 second"]);
1192 runAndAssertTimeTicks(800000000, 21 * second, ["1 second"]);
1193 runAndAssertTimeTicks(800000000, 111 * second, ["1 second", "1 minute"]);
1194 runAndAssertTimeTicks(second + 1, 111 * second, ["1 minute"]);
1195});
1196
1197test("CreateTimeTicks: minutes", () => {
1198 runAndAssertTimeTicks(second - 1, minute, ["1 second", "1 minute"]);
1199 runAndAssertTimeTicks(minute - second, 2 * minute, ["1 minute"]);
1200 runAndAssertTimeTicks(minute, 11 * minute, ["1 minute"]);
1201 runAndAssertTimeTicks(minute + 1, 21 * minute, ["10 minutes"]);
1202 runAndAssertTimeTicks(second, 111 * minute, [
1203 "1 second",
1204 "1 minute",
1205 "1 hour",
1206 ]);
1207 runAndAssertTimeTicks(minute + 1, 111 * minute, ["1 hour"]);
1208});
1209
1210test("CreateTimeTicks: hours", () => {
1211 runAndAssertTimeTicks(minute - 1, hour, ["1 minute", "1 hour"]);
1212 runAndAssertTimeTicks(hour - minute, 2 * hour, ["1 hour"]);
1213 runAndAssertTimeTicks(hour, 11 * hour, ["1 hour"]);
1214 runAndAssertTimeTicks(hour + 1, 21 * hour, ["10 hours"]);
1215 runAndAssertTimeTicks(minute, 111 * hour, ["1 minute", "1 hour", "1 day"]);
1216 runAndAssertTimeTicks(hour + 1, 111 * hour, ["1 day"]);
1217});
1218
1219test("CreateTimeTicks: days", () => {
1220 runAndAssertTimeTicks(hour - 1, day, ["1 hour", "1 day"]);
1221 runAndAssertTimeTicks(day - hour, 2 * day, ["1 day"]);
1222 runAndAssertTimeTicks(day, 11 * day, ["1 day", "1 week"]);
1223 runAndAssertTimeTicks(day + 1, 21 * day, ["1 week"]);
1224 runAndAssertTimeTicks(hour, 111 * day, [
1225 "1 hour",
1226 "1 day",
1227 "1 week",
1228 "1 month",
1229 ]);
1230 runAndAssertTimeTicks(day + 1, 111 * day, ["1 week", "1 month"]);
1231});
1232
1233test("CreateTimeTicks: weeks", () => {
1234 runAndAssertTimeTicks(day, week, ["1 day", "1 week"]);
1235 runAndAssertTimeTicks(day + 1, week, ["1 week"]);
1236 runAndAssertTimeTicks(day * 8, day * 27, ["2 weeks", "3 weeks"]);
1237 runAndAssertTimeTicks(week - day, 2 * week, ["1 week"]);
1238 runAndAssertTimeTicks(week, 11 * week, ["1 week", "1 month"]);
1239 runAndAssertTimeTicks(week + 1, 35 * week, ["1 month"]);
1240 runAndAssertTimeTicks(day, 111 * week, [
1241 "1 day",
1242 "1 week",
1243 "1 month",
1244 "1 year",
1245 ]);
1246 runAndAssertTimeTicks(week + 1, 111 * week, ["1 month", "1 year"]);
1247});
1248
1249test("CreateTimeTicks: months", () => {
1250 runAndAssertTimeTicks(week - 1, month, ["1 week", "1 month"]);
1251 runAndAssertTimeTicks(month - 1, 2 * month, ["1 month"]);
1252 runAndAssertTimeTicks(month, 11 * month, ["1 month"]);
1253 runAndAssertTimeTicks(month, 12 * month, ["1 month"]);
1254 runAndAssertTimeTicks(month, 12 * month + 5 * day, ["1 month", "1 year"]);
1255 runAndAssertTimeTicks(month + 1, 12 * month, ["10 months"]);
1256 // due to precision issues month + 1 == month
1257 runAndAssertTimeTicks(month + hour, 10 * month - hour, [
1258 "2 months",
1259 "3 months",
1260 "4 months",
1261 "5 months",
1262 "6 months",
1263 "7 months",
1264 "8 months",
1265 "9 months",
1266 ]);
1267 runAndAssertTimeTicks(week, 111 * month, ["1 week", "1 month", "1 year"]);
1268 runAndAssertTimeTicks(month + 1, 111 * month, ["1 year"]);
1269});
1270
1271test("CreateTimeTicks: years", () => {
1272 runAndAssertTimeTicks(month - 1, year, ["1 month", "1 year"]);
1273 runAndAssertTimeTicks(year - month, 2 * year, ["1 year"]);
1274 // due to precision issues year + 1 == year and decade - 1 == decade
1275 runAndAssertTimeTicks(year + day, decade - day, [
1276 "2 years",
1277 "3 years",
1278 "4 years",
1279 "5 years",
1280 "6 years",
1281 "7 years",
1282 "8 years",
1283 "9 years",
1284 ]);
1285
1286 runAndAssertTimeTicks(month, 111 * year, [
1287 "1 month",
1288 "1 year",
1289 "1 decade",
1290 "1 century",
1291 ]);
1292});
1293
1294test("CreateTimeTicks: decades", () => {
1295 // due to precision issues year + 1 == year
1296 runAndAssertTimeTicks(year + day, 21 * year, ["1 decade"]);
1297 runAndAssertTimeTicks(year, decade, ["1 year", "1 decade"]);
1298 runAndAssertTimeTicks(decade - year, 2 * decade, ["1 decade"]);
1299 runAndAssertTimeTicks(year, 111 * decade, [
1300 "1 year",
1301 "1 decade",
1302 "1 century",
1303 ]);
1304 // due to precision issues decade + 1 == decade
1305 runAndAssertTimeTicks(decade + month, 111 * decade, ["1 century"]);
1306});
1307
1308test("CreateTimeTicks: centuries", () => {
1309 runAndAssertTimeTicks(decade - 1, century, ["1 decade", "1 century"]);
1310 runAndAssertTimeTicks(century - decade, 2 * century, ["1 century"]);
1311 runAndAssertTimeTicks(century, 11 * century, ["1 century"]);
1312 runAndAssertTimeTicks(century + 1, 21 * century, ["1 century"]);
1313 runAndAssertTimeTicks(decade, 111 * century, ["1 decade", "1 century"]);
1314 runAndAssertTimeTicks(century + 1, 111 * century, ["1 century"]);
1315});
1316
1317test("CreateTimeTicks: above centuries", () => {
1318 runAndAssertTimeTicks(century + 30 * year, 3 * century, [
1319 "2 centuries",
1320 "3 centuries",
1321 ]);
1322 runAndAssertTimeTicks(century + 30 * year, century + 55 * year, [
1323 "13 decades",
1324 "14 decades",
1325 "15 decades",
1326 ]);
1327 runAndAssertTimeTicks(2 * century + 32 * year, 2 * century + 36 * year, [
1328 "232 years",
1329 "233 years",
1330 "234 years",
1331 "235 years",
1332 "236 years",
1333 ]);
1334});
1335
1336function getValues(ticks) {
1337 return ticks.map((tick) => tick.value);
1338}
1339
1340function getLabels(ticks) {
1341 return ticks.map((tick) => tick.label);
1342}
1343
1344function runAndAssertIntegerTicks(min, max, expected) {
1345 const message = `min: ${min}, max: ${max}`;
1346 assert.deepStrictEqual(
1347 getValues(utils.CreateIntegerTicks(min, max)),
1348 expected,
1349 message,
1350 );
1351}
1352
1353function runAndAssertTimeTicks(min, max, expected) {
1354 const message = `min: ${min}, max: ${max}`;
1355 assert.deepStrictEqual(
1356 getLabels(utils.CreateTimeTicks(min, max)),
1357 expected,
1358 message,
1359 );
1360}
1361