microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
joaoboechat/test-get-azdo-userid

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/npm/qsharp/test/basics.js

1437lines · 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("OpenQASM compile error on run", async () => {
876 const compiler = await getCompiler();
877 const events = new QscEventTarget(true);
878 let promiseResult = undefined;
879 await compiler
880 .run(
881 {
882 sources: [
883 // Missing stdgates.inc, so CX is undefined
884 ["test.qasm", `OPENQASM 3.0;\nqubit[2] q;\nCX q[0], q[1];`],
885 ],
886 languageFeatures: [],
887 projectType: "openqasm",
888 },
889 "",
890 1,
891 events,
892 )
893 .then(() => {
894 promiseResult = "success";
895 })
896 .catch(() => {
897 promiseResult = "failure";
898 });
899
900 assert.equal(promiseResult, "failure");
901 const results = events.getResults();
902 assert.equal(results.length, 1);
903 assert.equal(results[0].success, false);
904});
905
906test("Q# dependency compile error on run", async () => {
907 const compiler = await getCompiler();
908 const events = new QscEventTarget(true);
909 let promiseResult = undefined;
910 await compiler
911 .run(
912 {
913 packageGraphSources: {
914 root: {
915 sources: [
916 [
917 "main.qs",
918 `import BrokenDep.Broken; operation Main() : Unit { Broken(); }`,
919 ],
920 ],
921 languageFeatures: [],
922 dependencies: { BrokenDep: "broken-dep-key" },
923 },
924 packages: {
925 "broken-dep-key": {
926 sources: [["lib.qs", "invalid code"]],
927 languageFeatures: [],
928 dependencies: {},
929 packageType: "lib",
930 },
931 },
932 hasManifest: true,
933 },
934 },
935 "",
936 1,
937 events,
938 )
939 .then(() => {
940 promiseResult = "success";
941 })
942 .catch(() => {
943 promiseResult = "failure";
944 });
945
946 assert.equal(promiseResult, "failure");
947 const results = events.getResults();
948 assert.equal(results.length, 1);
949 assert.equal(results[0].success, false);
950});
951
952test("debug service loading source without entry point attr fails - web worker", async () => {
953 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
954 try {
955 const result = await debugService.loadProgram(
956 {
957 sources: [
958 [
959 "test.qs",
960 `namespace Sample {
961 operation test() : Result[] {
962 use q1 = Qubit();
963 Y(q1);
964 let m1 = M(q1);
965 return [m1];
966 }
967}`,
968 ],
969 ],
970 languageFeatures: [],
971 profile: "base",
972 },
973 undefined,
974 );
975 assert.ok(typeof result === "string" && result.trim().length > 0);
976 } finally {
977 debugService.terminate();
978 }
979});
980
981test("debug service loading source with syntax error fails - web worker", async () => {
982 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
983 try {
984 const result = await debugService.loadProgram(
985 {
986 sources: [
987 [
988 "test.qs",
989 `namespace Sample {
990 operation test() : Result[]
991 }
992}`,
993 ],
994 ],
995 languageFeatures: [],
996 profile: "base",
997 },
998 undefined,
999 );
1000 assert.ok(typeof result === "string" && result.trim().length > 0);
1001 } finally {
1002 debugService.terminate();
1003 }
1004});
1005
1006test("debug service loading source with bad entry expr fails - web worker", async () => {
1007 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
1008 try {
1009 const result = await debugService.loadProgram(
1010 {
1011 sources: [
1012 ["test.qs", `namespace Sample { operation main() : Unit { } }`],
1013 ],
1014 languageFeatures: [],
1015 profile: "base",
1016 },
1017 "SomeBadExpr()",
1018 );
1019 assert.ok(typeof result === "string" && result.trim().length > 0);
1020 } finally {
1021 debugService.terminate();
1022 }
1023});
1024
1025test("debug service loading source that doesn't match profile fails - web worker", async () => {
1026 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
1027 try {
1028 const result = await debugService.loadProgram(
1029 {
1030 sources: [
1031 [
1032 "test.qs",
1033 `namespace A { operation Test() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x } }`,
1034 ],
1035 ],
1036 languageFeatures: [],
1037 profile: "adaptive_ri",
1038 },
1039 "A.Test()",
1040 );
1041 assert.ok(typeof result === "string" && result.trim().length > 0);
1042 } finally {
1043 debugService.terminate();
1044 }
1045});
1046
1047test("debug service loading source with good entry expr succeeds - web worker", async () => {
1048 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
1049 try {
1050 const result = await debugService.loadProgram(
1051 {
1052 sources: [
1053 ["test.qs", `namespace Sample { operation Main() : Unit { } }`],
1054 ],
1055 languageFeatures: [],
1056 profile: "unrestricted",
1057 },
1058 "Sample.Main()",
1059 );
1060 assert.ok(typeof result === "string");
1061 assert.equal(result.trim(), "");
1062 } finally {
1063 debugService.terminate();
1064 }
1065});
1066
1067test("debug service loading source with entry point attr succeeds - web worker", async () => {
1068 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
1069 try {
1070 const result = await debugService.loadProgram(
1071 {
1072 sources: [
1073 [
1074 "test.qs",
1075 `namespace Sample {
1076 @EntryPoint()
1077 operation main() : Result[] {
1078 use q1 = Qubit();
1079 Y(q1);
1080 let m1 = M(q1);
1081 return [m1];
1082 }
1083}`,
1084 ],
1085 ],
1086 languageFeatures: [],
1087 profile: "base",
1088 },
1089 undefined,
1090 );
1091 assert.ok(typeof result === "string");
1092 assert.equal(result.trim(), "");
1093 } finally {
1094 debugService.terminate();
1095 }
1096});
1097
1098test("debug service getting breakpoints after loaded source succeeds when file names match - web worker", async () => {
1099 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
1100 try {
1101 const result = await debugService.loadProgram(
1102 {
1103 sources: [
1104 [
1105 "test.qs",
1106 `namespace Sample {
1107 @EntryPoint()
1108 operation main() : Result[] {
1109 use q1 = Qubit();
1110 Y(q1);
1111 let m1 = M(q1);
1112 return [m1];
1113 }
1114}`,
1115 ],
1116 ],
1117 languageFeatures: [],
1118 profile: "base",
1119 },
1120 undefined,
1121 );
1122 assert.ok(typeof result === "string" && result.trim().length == 0);
1123 const bps = await debugService.getBreakpoints("test.qs");
1124 assert.equal(bps.length, 4);
1125 } finally {
1126 debugService.terminate();
1127 }
1128});
1129
1130test("debug service compiling multiple sources - web worker", async () => {
1131 const debugService = getDebugServiceWorker(debugServiceWorkerPath);
1132 try {
1133 const result = await debugService.loadProgram(
1134 {
1135 sources: [
1136 [
1137 "Foo.qs",
1138 `namespace Foo {
1139 open Bar;
1140 @EntryPoint()
1141 operation Main() : Int {
1142 Message("Hello");
1143 Message("Hello");
1144 return HelloFromBar();
1145 }
1146}`,
1147 ],
1148 [
1149 "Bar.qs",
1150 `namespace Bar {
1151 operation HelloFromBar() : Int {
1152 return 5;
1153 }
1154}`,
1155 ],
1156 ],
1157 languageFeatures: [],
1158 profile: "unrestricted",
1159 },
1160 undefined,
1161 );
1162 assert.equal(result.trim(), "");
1163 const fooBps = await debugService.getBreakpoints("Foo.qs");
1164 assert.equal(fooBps.length, 3);
1165
1166 const barBps = await debugService.getBreakpoints("Bar.qs");
1167 assert.equal(barBps.length, 1);
1168 } finally {
1169 debugService.terminate();
1170 }
1171});
1172
1173test("CreateIntegerTicks: invalid inputs", () => {
1174 runAndAssertIntegerTicks(2, 1, []);
1175 runAndAssertIntegerTicks(0, 2, []);
1176 runAndAssertIntegerTicks(-5, 100, []);
1177});
1178
1179test("CreateIntegerTicks: below 100", () => {
1180 runAndAssertIntegerTicks(1, 1, [1]);
1181 runAndAssertIntegerTicks(3, 3, [3]);
1182 runAndAssertIntegerTicks(4, 6, [4, 5, 6]);
1183 runAndAssertIntegerTicks(1, 100, [1, 10, 100]);
1184 runAndAssertIntegerTicks(1, 10, [1, 10]);
1185 runAndAssertIntegerTicks(1, 9, [1]);
1186 runAndAssertIntegerTicks(2, 10, [10]);
1187 runAndAssertIntegerTicks(2, 9, [2, 3, 4, 5, 6, 7, 8, 9]);
1188});
1189
1190test("CreateIntegerTicks: more than 100", () => {
1191 runAndAssertIntegerTicks(20, 59, [20, 30, 40, 50]);
1192 runAndAssertIntegerTicks(231, 365, [300]);
1193 runAndAssertIntegerTicks(331, 365, [340, 350, 360]);
1194 runAndAssertIntegerTicks(567, 569, [567, 568, 569]);
1195});
1196
1197test("CreateIntegerTicks: expected qubit numbers", () => {
1198 runAndAssertIntegerTicks(400, 8000000, [1000, 10000, 100000, 1000000]);
1199 runAndAssertIntegerTicks(12345, 67890, [20000, 30000, 40000, 50000, 60000]);
1200 runAndAssertIntegerTicks(23456, 27890, [24000, 25000, 26000, 27000]);
1201});
1202
1203test("CreateTimeTicks: invalid inputs", () => {
1204 runAndAssertTimeTicks(2, 1, []);
1205 runAndAssertTimeTicks(0, 2, []);
1206 runAndAssertTimeTicks(-5, 100, []);
1207});
1208
1209const second = 1e9;
1210const minute = 60 * second;
1211const hour = 60 * minute;
1212const day = 24 * hour;
1213const week = 7 * day;
1214const month = 30 * day;
1215const year = 365 * day;
1216const decade = 10 * year;
1217const century = 10 * decade;
1218
1219test("CreateTimeTicks: nanoseconds below 100", () => {
1220 runAndAssertTimeTicks(1, 1, ["1 nanosecond"]);
1221 runAndAssertTimeTicks(3, 3, ["3 nanoseconds"]);
1222 runAndAssertTimeTicks(4, 6, [
1223 "4 nanoseconds",
1224 "5 nanoseconds",
1225 "6 nanoseconds",
1226 ]);
1227 runAndAssertTimeTicks(1, 100, ["1 nanosecond"]);
1228 runAndAssertTimeTicks(1, 10, ["1 nanosecond"]);
1229 runAndAssertTimeTicks(1, 9, ["1 nanosecond"]);
1230 runAndAssertTimeTicks(2, 10, ["10 nanoseconds"]);
1231 runAndAssertTimeTicks(2, 9, [
1232 "2 nanoseconds",
1233 "3 nanoseconds",
1234 "4 nanoseconds",
1235 "5 nanoseconds",
1236 "6 nanoseconds",
1237 "7 nanoseconds",
1238 "8 nanoseconds",
1239 "9 nanoseconds",
1240 ]);
1241});
1242
1243test("CreateTimeTicks: microseconds", () => {
1244 runAndAssertTimeTicks(800, 1000, ["1 microsecond"]);
1245 runAndAssertTimeTicks(800, 2000, ["1 microsecond"]);
1246 runAndAssertTimeTicks(800, 11000, ["1 microsecond"]);
1247 runAndAssertTimeTicks(800, 21000, ["1 microsecond"]);
1248 runAndAssertTimeTicks(800, 111000, ["1 microsecond"]);
1249 runAndAssertTimeTicks(1001, 21000, ["10 microseconds"]);
1250 runAndAssertTimeTicks(10001, 21000, ["20 microseconds"]);
1251 runAndAssertTimeTicks(10001, 30000, ["20 microseconds", "30 microseconds"]);
1252});
1253
1254test("CreateTimeTicks: milliseconds", () => {
1255 runAndAssertTimeTicks(800, 999999, ["1 microsecond"]);
1256 runAndAssertTimeTicks(800, 1000000, ["1 microsecond", "1 millisecond"]);
1257 runAndAssertTimeTicks(800000, 2000000, ["1 millisecond"]);
1258 runAndAssertTimeTicks(800000, 11000000, ["1 millisecond"]);
1259 runAndAssertTimeTicks(800000, 21000000, ["1 millisecond"]);
1260 runAndAssertTimeTicks(800000, 111000000, ["1 millisecond"]);
1261 runAndAssertTimeTicks(1000001, 111000000, ["100 milliseconds"]);
1262});
1263
1264test("CreateTimeTicks: seconds", () => {
1265 runAndAssertTimeTicks(800000, second - 1, ["1 millisecond"]);
1266 runAndAssertTimeTicks(800000, second, ["1 millisecond", "1 second"]);
1267 runAndAssertTimeTicks(800000000, 2 * second, ["1 second"]);
1268 runAndAssertTimeTicks(800000000, 11 * second, ["1 second"]);
1269 runAndAssertTimeTicks(800000000, 21 * second, ["1 second"]);
1270 runAndAssertTimeTicks(800000000, 111 * second, ["1 second", "1 minute"]);
1271 runAndAssertTimeTicks(second + 1, 111 * second, ["1 minute"]);
1272});
1273
1274test("CreateTimeTicks: minutes", () => {
1275 runAndAssertTimeTicks(second - 1, minute, ["1 second", "1 minute"]);
1276 runAndAssertTimeTicks(minute - second, 2 * minute, ["1 minute"]);
1277 runAndAssertTimeTicks(minute, 11 * minute, ["1 minute"]);
1278 runAndAssertTimeTicks(minute + 1, 21 * minute, ["10 minutes"]);
1279 runAndAssertTimeTicks(second, 111 * minute, [
1280 "1 second",
1281 "1 minute",
1282 "1 hour",
1283 ]);
1284 runAndAssertTimeTicks(minute + 1, 111 * minute, ["1 hour"]);
1285});
1286
1287test("CreateTimeTicks: hours", () => {
1288 runAndAssertTimeTicks(minute - 1, hour, ["1 minute", "1 hour"]);
1289 runAndAssertTimeTicks(hour - minute, 2 * hour, ["1 hour"]);
1290 runAndAssertTimeTicks(hour, 11 * hour, ["1 hour"]);
1291 runAndAssertTimeTicks(hour + 1, 21 * hour, ["10 hours"]);
1292 runAndAssertTimeTicks(minute, 111 * hour, ["1 minute", "1 hour", "1 day"]);
1293 runAndAssertTimeTicks(hour + 1, 111 * hour, ["1 day"]);
1294});
1295
1296test("CreateTimeTicks: days", () => {
1297 runAndAssertTimeTicks(hour - 1, day, ["1 hour", "1 day"]);
1298 runAndAssertTimeTicks(day - hour, 2 * day, ["1 day"]);
1299 runAndAssertTimeTicks(day, 11 * day, ["1 day", "1 week"]);
1300 runAndAssertTimeTicks(day + 1, 21 * day, ["1 week"]);
1301 runAndAssertTimeTicks(hour, 111 * day, [
1302 "1 hour",
1303 "1 day",
1304 "1 week",
1305 "1 month",
1306 ]);
1307 runAndAssertTimeTicks(day + 1, 111 * day, ["1 week", "1 month"]);
1308});
1309
1310test("CreateTimeTicks: weeks", () => {
1311 runAndAssertTimeTicks(day, week, ["1 day", "1 week"]);
1312 runAndAssertTimeTicks(day + 1, week, ["1 week"]);
1313 runAndAssertTimeTicks(day * 8, day * 27, ["2 weeks", "3 weeks"]);
1314 runAndAssertTimeTicks(week - day, 2 * week, ["1 week"]);
1315 runAndAssertTimeTicks(week, 11 * week, ["1 week", "1 month"]);
1316 runAndAssertTimeTicks(week + 1, 35 * week, ["1 month"]);
1317 runAndAssertTimeTicks(day, 111 * week, [
1318 "1 day",
1319 "1 week",
1320 "1 month",
1321 "1 year",
1322 ]);
1323 runAndAssertTimeTicks(week + 1, 111 * week, ["1 month", "1 year"]);
1324});
1325
1326test("CreateTimeTicks: months", () => {
1327 runAndAssertTimeTicks(week - 1, month, ["1 week", "1 month"]);
1328 runAndAssertTimeTicks(month - 1, 2 * month, ["1 month"]);
1329 runAndAssertTimeTicks(month, 11 * month, ["1 month"]);
1330 runAndAssertTimeTicks(month, 12 * month, ["1 month"]);
1331 runAndAssertTimeTicks(month, 12 * month + 5 * day, ["1 month", "1 year"]);
1332 runAndAssertTimeTicks(month + 1, 12 * month, ["10 months"]);
1333 // due to precision issues month + 1 == month
1334 runAndAssertTimeTicks(month + hour, 10 * month - hour, [
1335 "2 months",
1336 "3 months",
1337 "4 months",
1338 "5 months",
1339 "6 months",
1340 "7 months",
1341 "8 months",
1342 "9 months",
1343 ]);
1344 runAndAssertTimeTicks(week, 111 * month, ["1 week", "1 month", "1 year"]);
1345 runAndAssertTimeTicks(month + 1, 111 * month, ["1 year"]);
1346});
1347
1348test("CreateTimeTicks: years", () => {
1349 runAndAssertTimeTicks(month - 1, year, ["1 month", "1 year"]);
1350 runAndAssertTimeTicks(year - month, 2 * year, ["1 year"]);
1351 // due to precision issues year + 1 == year and decade - 1 == decade
1352 runAndAssertTimeTicks(year + day, decade - day, [
1353 "2 years",
1354 "3 years",
1355 "4 years",
1356 "5 years",
1357 "6 years",
1358 "7 years",
1359 "8 years",
1360 "9 years",
1361 ]);
1362
1363 runAndAssertTimeTicks(month, 111 * year, [
1364 "1 month",
1365 "1 year",
1366 "1 decade",
1367 "1 century",
1368 ]);
1369});
1370
1371test("CreateTimeTicks: decades", () => {
1372 // due to precision issues year + 1 == year
1373 runAndAssertTimeTicks(year + day, 21 * year, ["1 decade"]);
1374 runAndAssertTimeTicks(year, decade, ["1 year", "1 decade"]);
1375 runAndAssertTimeTicks(decade - year, 2 * decade, ["1 decade"]);
1376 runAndAssertTimeTicks(year, 111 * decade, [
1377 "1 year",
1378 "1 decade",
1379 "1 century",
1380 ]);
1381 // due to precision issues decade + 1 == decade
1382 runAndAssertTimeTicks(decade + month, 111 * decade, ["1 century"]);
1383});
1384
1385test("CreateTimeTicks: centuries", () => {
1386 runAndAssertTimeTicks(decade - 1, century, ["1 decade", "1 century"]);
1387 runAndAssertTimeTicks(century - decade, 2 * century, ["1 century"]);
1388 runAndAssertTimeTicks(century, 11 * century, ["1 century"]);
1389 runAndAssertTimeTicks(century + 1, 21 * century, ["1 century"]);
1390 runAndAssertTimeTicks(decade, 111 * century, ["1 decade", "1 century"]);
1391 runAndAssertTimeTicks(century + 1, 111 * century, ["1 century"]);
1392});
1393
1394test("CreateTimeTicks: above centuries", () => {
1395 runAndAssertTimeTicks(century + 30 * year, 3 * century, [
1396 "2 centuries",
1397 "3 centuries",
1398 ]);
1399 runAndAssertTimeTicks(century + 30 * year, century + 55 * year, [
1400 "13 decades",
1401 "14 decades",
1402 "15 decades",
1403 ]);
1404 runAndAssertTimeTicks(2 * century + 32 * year, 2 * century + 36 * year, [
1405 "232 years",
1406 "233 years",
1407 "234 years",
1408 "235 years",
1409 "236 years",
1410 ]);
1411});
1412
1413function getValues(ticks) {
1414 return ticks.map((tick) => tick.value);
1415}
1416
1417function getLabels(ticks) {
1418 return ticks.map((tick) => tick.label);
1419}
1420
1421function runAndAssertIntegerTicks(min, max, expected) {
1422 const message = `min: ${min}, max: ${max}`;
1423 assert.deepStrictEqual(
1424 getValues(utils.CreateIntegerTicks(min, max)),
1425 expected,
1426 message,
1427 );
1428}
1429
1430function runAndAssertTimeTicks(min, max, expected) {
1431 const message = `min: ${min}, max: ${max}`;
1432 assert.deepStrictEqual(
1433 getLabels(utils.CreateTimeTicks(min, max)),
1434 expected,
1435 message,
1436 );
1437}
1438