microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.23.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/npm/qsharp/test/basics.js

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