microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
billti/qdk_package

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/npm/qsharp/test/basics.js

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