microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/pipeline-issue-debugging

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/npm/qsharp/test/basics.js

1341lines · 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: ["name error: `Foo` not found"],
806 },
807 {
808 messages: [],
809 },
810 ],
811 actualMessages,
812 );
813});
814
815async function testCompilerError(useWorker) {
816 const compiler = useWorker ? getCompilerWorker() : getCompiler();
817 if (useWorker) {
818 // @ts-expect-error onstatechange only exists on the worker
819 compiler.onstatechange = (state) => {
820 lastState = state;
821 };
822 }
823
824 const events = new QscEventTarget(true);
825 let promiseResult = undefined;
826 let lastState = undefined;
827 await compiler
828 .run(
829 { sources: [["test.qs", "invalid code"]], languageFeatures: [] },
830 "",
831 1,
832 events,
833 )
834 .then(() => {
835 promiseResult = "success";
836 })
837 .catch(() => {
838 promiseResult = "failure";
839 });
840
841 assert.equal(promiseResult, "failure");
842 const results = events.getResults();
843 assert.equal(results.length, 1);
844 assert.equal(results[0].success, false);
845 if (useWorker) {
846 // Only the worker has state change events
847 assert.equal(lastState, "idle");
848 // @ts-expect-error terminate() only exists on the worker
849 compiler.terminate();
850 }
851}
852
853test("compiler error on run", () => testCompilerError(false));
854test("compiler error on run - worker", () => testCompilerError(true));
855
856test("debug service loading source without entry point attr fails - web worker", async () => {
857 const debugService = getDebugServiceWorker();
858 try {
859 const result = await debugService.loadProgram(
860 {
861 sources: [
862 [
863 "test.qs",
864 `namespace Sample {
865 operation test() : Result[] {
866 use q1 = Qubit();
867 Y(q1);
868 let m1 = M(q1);
869 return [m1];
870 }
871}`,
872 ],
873 ],
874 languageFeatures: [],
875 profile: "base",
876 },
877 undefined,
878 );
879 assert.ok(typeof result === "string" && result.trim().length > 0);
880 } finally {
881 debugService.terminate();
882 }
883});
884
885test("debug service loading source with syntax error fails - web worker", async () => {
886 const debugService = getDebugServiceWorker();
887 try {
888 const result = await debugService.loadProgram(
889 {
890 sources: [
891 [
892 "test.qs",
893 `namespace Sample {
894 operation test() : Result[]
895 }
896}`,
897 ],
898 ],
899 languageFeatures: [],
900 profile: "base",
901 },
902 undefined,
903 );
904 assert.ok(typeof result === "string" && result.trim().length > 0);
905 } finally {
906 debugService.terminate();
907 }
908});
909
910test("debug service loading source with bad entry expr fails - web worker", async () => {
911 const debugService = getDebugServiceWorker();
912 try {
913 const result = await debugService.loadProgram(
914 {
915 sources: [
916 ["test.qs", `namespace Sample { operation main() : Unit { } }`],
917 ],
918 languageFeatures: [],
919 profile: "base",
920 },
921 "SomeBadExpr()",
922 );
923 assert.ok(typeof result === "string" && result.trim().length > 0);
924 } finally {
925 debugService.terminate();
926 }
927});
928
929test("debug service loading source that doesn't match profile fails - web worker", async () => {
930 const debugService = getDebugServiceWorker();
931 try {
932 const result = await debugService.loadProgram(
933 {
934 sources: [
935 [
936 "test.qs",
937 `namespace A { operation Test() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x } }`,
938 ],
939 ],
940 languageFeatures: [],
941 profile: "adaptive_ri",
942 },
943 "A.Test()",
944 );
945 assert.ok(typeof result === "string" && result.trim().length > 0);
946 } finally {
947 debugService.terminate();
948 }
949});
950
951test("debug service loading source with good entry expr succeeds - web worker", async () => {
952 const debugService = getDebugServiceWorker();
953 try {
954 const result = await debugService.loadProgram(
955 {
956 sources: [
957 ["test.qs", `namespace Sample { operation Main() : Unit { } }`],
958 ],
959 languageFeatures: [],
960 profile: "unrestricted",
961 },
962 "Sample.Main()",
963 );
964 assert.ok(typeof result === "string");
965 assert.equal(result.trim(), "");
966 } finally {
967 debugService.terminate();
968 }
969});
970
971test("debug service loading source with entry point attr succeeds - web worker", async () => {
972 const debugService = getDebugServiceWorker();
973 try {
974 const result = await debugService.loadProgram(
975 {
976 sources: [
977 [
978 "test.qs",
979 `namespace Sample {
980 @EntryPoint()
981 operation main() : Result[] {
982 use q1 = Qubit();
983 Y(q1);
984 let m1 = M(q1);
985 return [m1];
986 }
987}`,
988 ],
989 ],
990 languageFeatures: [],
991 profile: "base",
992 },
993 undefined,
994 );
995 assert.ok(typeof result === "string");
996 assert.equal(result.trim(), "");
997 } finally {
998 debugService.terminate();
999 }
1000});
1001
1002test("debug service getting breakpoints after loaded source succeeds when file names match - web worker", async () => {
1003 const debugService = getDebugServiceWorker();
1004 try {
1005 const result = await debugService.loadProgram(
1006 {
1007 sources: [
1008 [
1009 "test.qs",
1010 `namespace Sample {
1011 @EntryPoint()
1012 operation main() : Result[] {
1013 use q1 = Qubit();
1014 Y(q1);
1015 let m1 = M(q1);
1016 return [m1];
1017 }
1018}`,
1019 ],
1020 ],
1021 languageFeatures: [],
1022 profile: "base",
1023 },
1024 undefined,
1025 );
1026 assert.ok(typeof result === "string" && result.trim().length == 0);
1027 const bps = await debugService.getBreakpoints("test.qs");
1028 assert.equal(bps.length, 4);
1029 } finally {
1030 debugService.terminate();
1031 }
1032});
1033
1034test("debug service compiling multiple sources - web worker", async () => {
1035 const debugService = getDebugServiceWorker();
1036 try {
1037 const result = await debugService.loadProgram(
1038 {
1039 sources: [
1040 [
1041 "Foo.qs",
1042 `namespace Foo {
1043 open Bar;
1044 @EntryPoint()
1045 operation Main() : Int {
1046 Message("Hello");
1047 Message("Hello");
1048 return HelloFromBar();
1049 }
1050}`,
1051 ],
1052 [
1053 "Bar.qs",
1054 `namespace Bar {
1055 operation HelloFromBar() : Int {
1056 return 5;
1057 }
1058}`,
1059 ],
1060 ],
1061 languageFeatures: [],
1062 profile: "unrestricted",
1063 },
1064 undefined,
1065 );
1066 assert.equal(result.trim(), "");
1067 const fooBps = await debugService.getBreakpoints("Foo.qs");
1068 assert.equal(fooBps.length, 3);
1069
1070 const barBps = await debugService.getBreakpoints("Bar.qs");
1071 assert.equal(barBps.length, 1);
1072 } finally {
1073 debugService.terminate();
1074 }
1075});
1076
1077test("CreateIntegerTicks: invalid inputs", () => {
1078 runAndAssertIntegerTicks(2, 1, []);
1079 runAndAssertIntegerTicks(0, 2, []);
1080 runAndAssertIntegerTicks(-5, 100, []);
1081});
1082
1083test("CreateIntegerTicks: below 100", () => {
1084 runAndAssertIntegerTicks(1, 1, [1]);
1085 runAndAssertIntegerTicks(3, 3, [3]);
1086 runAndAssertIntegerTicks(4, 6, [4, 5, 6]);
1087 runAndAssertIntegerTicks(1, 100, [1, 10, 100]);
1088 runAndAssertIntegerTicks(1, 10, [1, 10]);
1089 runAndAssertIntegerTicks(1, 9, [1]);
1090 runAndAssertIntegerTicks(2, 10, [10]);
1091 runAndAssertIntegerTicks(2, 9, [2, 3, 4, 5, 6, 7, 8, 9]);
1092});
1093
1094test("CreateIntegerTicks: more than 100", () => {
1095 runAndAssertIntegerTicks(20, 59, [20, 30, 40, 50]);
1096 runAndAssertIntegerTicks(231, 365, [300]);
1097 runAndAssertIntegerTicks(331, 365, [340, 350, 360]);
1098 runAndAssertIntegerTicks(567, 569, [567, 568, 569]);
1099});
1100
1101test("CreateIntegerTicks: expected qubit numbers", () => {
1102 runAndAssertIntegerTicks(400, 8000000, [1000, 10000, 100000, 1000000]);
1103 runAndAssertIntegerTicks(12345, 67890, [20000, 30000, 40000, 50000, 60000]);
1104 runAndAssertIntegerTicks(23456, 27890, [24000, 25000, 26000, 27000]);
1105});
1106
1107test("CreateTimeTicks: invalid inputs", () => {
1108 runAndAssertTimeTicks(2, 1, []);
1109 runAndAssertTimeTicks(0, 2, []);
1110 runAndAssertTimeTicks(-5, 100, []);
1111});
1112
1113const second = 1e9;
1114const minute = 60 * second;
1115const hour = 60 * minute;
1116const day = 24 * hour;
1117const week = 7 * day;
1118const month = 30 * day;
1119const year = 365 * day;
1120const decade = 10 * year;
1121const century = 10 * decade;
1122
1123test("CreateTimeTicks: nanoseconds below 100", () => {
1124 runAndAssertTimeTicks(1, 1, ["1 nanosecond"]);
1125 runAndAssertTimeTicks(3, 3, ["3 nanoseconds"]);
1126 runAndAssertTimeTicks(4, 6, [
1127 "4 nanoseconds",
1128 "5 nanoseconds",
1129 "6 nanoseconds",
1130 ]);
1131 runAndAssertTimeTicks(1, 100, ["1 nanosecond"]);
1132 runAndAssertTimeTicks(1, 10, ["1 nanosecond"]);
1133 runAndAssertTimeTicks(1, 9, ["1 nanosecond"]);
1134 runAndAssertTimeTicks(2, 10, ["10 nanoseconds"]);
1135 runAndAssertTimeTicks(2, 9, [
1136 "2 nanoseconds",
1137 "3 nanoseconds",
1138 "4 nanoseconds",
1139 "5 nanoseconds",
1140 "6 nanoseconds",
1141 "7 nanoseconds",
1142 "8 nanoseconds",
1143 "9 nanoseconds",
1144 ]);
1145});
1146
1147test("CreateTimeTicks: microseconds", () => {
1148 runAndAssertTimeTicks(800, 1000, ["1 microsecond"]);
1149 runAndAssertTimeTicks(800, 2000, ["1 microsecond"]);
1150 runAndAssertTimeTicks(800, 11000, ["1 microsecond"]);
1151 runAndAssertTimeTicks(800, 21000, ["1 microsecond"]);
1152 runAndAssertTimeTicks(800, 111000, ["1 microsecond"]);
1153 runAndAssertTimeTicks(1001, 21000, ["10 microseconds"]);
1154 runAndAssertTimeTicks(10001, 21000, ["20 microseconds"]);
1155 runAndAssertTimeTicks(10001, 30000, ["20 microseconds", "30 microseconds"]);
1156});
1157
1158test("CreateTimeTicks: milliseconds", () => {
1159 runAndAssertTimeTicks(800, 999999, ["1 microsecond"]);
1160 runAndAssertTimeTicks(800, 1000000, ["1 microsecond", "1 millisecond"]);
1161 runAndAssertTimeTicks(800000, 2000000, ["1 millisecond"]);
1162 runAndAssertTimeTicks(800000, 11000000, ["1 millisecond"]);
1163 runAndAssertTimeTicks(800000, 21000000, ["1 millisecond"]);
1164 runAndAssertTimeTicks(800000, 111000000, ["1 millisecond"]);
1165 runAndAssertTimeTicks(1000001, 111000000, ["100 milliseconds"]);
1166});
1167
1168test("CreateTimeTicks: seconds", () => {
1169 runAndAssertTimeTicks(800000, second - 1, ["1 millisecond"]);
1170 runAndAssertTimeTicks(800000, second, ["1 millisecond", "1 second"]);
1171 runAndAssertTimeTicks(800000000, 2 * second, ["1 second"]);
1172 runAndAssertTimeTicks(800000000, 11 * second, ["1 second"]);
1173 runAndAssertTimeTicks(800000000, 21 * second, ["1 second"]);
1174 runAndAssertTimeTicks(800000000, 111 * second, ["1 second", "1 minute"]);
1175 runAndAssertTimeTicks(second + 1, 111 * second, ["1 minute"]);
1176});
1177
1178test("CreateTimeTicks: minutes", () => {
1179 runAndAssertTimeTicks(second - 1, minute, ["1 second", "1 minute"]);
1180 runAndAssertTimeTicks(minute - second, 2 * minute, ["1 minute"]);
1181 runAndAssertTimeTicks(minute, 11 * minute, ["1 minute"]);
1182 runAndAssertTimeTicks(minute + 1, 21 * minute, ["10 minutes"]);
1183 runAndAssertTimeTicks(second, 111 * minute, [
1184 "1 second",
1185 "1 minute",
1186 "1 hour",
1187 ]);
1188 runAndAssertTimeTicks(minute + 1, 111 * minute, ["1 hour"]);
1189});
1190
1191test("CreateTimeTicks: hours", () => {
1192 runAndAssertTimeTicks(minute - 1, hour, ["1 minute", "1 hour"]);
1193 runAndAssertTimeTicks(hour - minute, 2 * hour, ["1 hour"]);
1194 runAndAssertTimeTicks(hour, 11 * hour, ["1 hour"]);
1195 runAndAssertTimeTicks(hour + 1, 21 * hour, ["10 hours"]);
1196 runAndAssertTimeTicks(minute, 111 * hour, ["1 minute", "1 hour", "1 day"]);
1197 runAndAssertTimeTicks(hour + 1, 111 * hour, ["1 day"]);
1198});
1199
1200test("CreateTimeTicks: days", () => {
1201 runAndAssertTimeTicks(hour - 1, day, ["1 hour", "1 day"]);
1202 runAndAssertTimeTicks(day - hour, 2 * day, ["1 day"]);
1203 runAndAssertTimeTicks(day, 11 * day, ["1 day", "1 week"]);
1204 runAndAssertTimeTicks(day + 1, 21 * day, ["1 week"]);
1205 runAndAssertTimeTicks(hour, 111 * day, [
1206 "1 hour",
1207 "1 day",
1208 "1 week",
1209 "1 month",
1210 ]);
1211 runAndAssertTimeTicks(day + 1, 111 * day, ["1 week", "1 month"]);
1212});
1213
1214test("CreateTimeTicks: weeks", () => {
1215 runAndAssertTimeTicks(day, week, ["1 day", "1 week"]);
1216 runAndAssertTimeTicks(day + 1, week, ["1 week"]);
1217 runAndAssertTimeTicks(day * 8, day * 27, ["2 weeks", "3 weeks"]);
1218 runAndAssertTimeTicks(week - day, 2 * week, ["1 week"]);
1219 runAndAssertTimeTicks(week, 11 * week, ["1 week", "1 month"]);
1220 runAndAssertTimeTicks(week + 1, 35 * week, ["1 month"]);
1221 runAndAssertTimeTicks(day, 111 * week, [
1222 "1 day",
1223 "1 week",
1224 "1 month",
1225 "1 year",
1226 ]);
1227 runAndAssertTimeTicks(week + 1, 111 * week, ["1 month", "1 year"]);
1228});
1229
1230test("CreateTimeTicks: months", () => {
1231 runAndAssertTimeTicks(week - 1, month, ["1 week", "1 month"]);
1232 runAndAssertTimeTicks(month - 1, 2 * month, ["1 month"]);
1233 runAndAssertTimeTicks(month, 11 * month, ["1 month"]);
1234 runAndAssertTimeTicks(month, 12 * month, ["1 month"]);
1235 runAndAssertTimeTicks(month, 12 * month + 5 * day, ["1 month", "1 year"]);
1236 runAndAssertTimeTicks(month + 1, 12 * month, ["10 months"]);
1237 // due to precision issues month + 1 == month
1238 runAndAssertTimeTicks(month + hour, 10 * month - hour, [
1239 "2 months",
1240 "3 months",
1241 "4 months",
1242 "5 months",
1243 "6 months",
1244 "7 months",
1245 "8 months",
1246 "9 months",
1247 ]);
1248 runAndAssertTimeTicks(week, 111 * month, ["1 week", "1 month", "1 year"]);
1249 runAndAssertTimeTicks(month + 1, 111 * month, ["1 year"]);
1250});
1251
1252test("CreateTimeTicks: years", () => {
1253 runAndAssertTimeTicks(month - 1, year, ["1 month", "1 year"]);
1254 runAndAssertTimeTicks(year - month, 2 * year, ["1 year"]);
1255 // due to precision issues year + 1 == year and decade - 1 == decade
1256 runAndAssertTimeTicks(year + day, decade - day, [
1257 "2 years",
1258 "3 years",
1259 "4 years",
1260 "5 years",
1261 "6 years",
1262 "7 years",
1263 "8 years",
1264 "9 years",
1265 ]);
1266
1267 runAndAssertTimeTicks(month, 111 * year, [
1268 "1 month",
1269 "1 year",
1270 "1 decade",
1271 "1 century",
1272 ]);
1273});
1274
1275test("CreateTimeTicks: decades", () => {
1276 // due to precision issues year + 1 == year
1277 runAndAssertTimeTicks(year + day, 21 * year, ["1 decade"]);
1278 runAndAssertTimeTicks(year, decade, ["1 year", "1 decade"]);
1279 runAndAssertTimeTicks(decade - year, 2 * decade, ["1 decade"]);
1280 runAndAssertTimeTicks(year, 111 * decade, [
1281 "1 year",
1282 "1 decade",
1283 "1 century",
1284 ]);
1285 // due to precision issues decade + 1 == decade
1286 runAndAssertTimeTicks(decade + month, 111 * decade, ["1 century"]);
1287});
1288
1289test("CreateTimeTicks: centuries", () => {
1290 runAndAssertTimeTicks(decade - 1, century, ["1 decade", "1 century"]);
1291 runAndAssertTimeTicks(century - decade, 2 * century, ["1 century"]);
1292 runAndAssertTimeTicks(century, 11 * century, ["1 century"]);
1293 runAndAssertTimeTicks(century + 1, 21 * century, ["1 century"]);
1294 runAndAssertTimeTicks(decade, 111 * century, ["1 decade", "1 century"]);
1295 runAndAssertTimeTicks(century + 1, 111 * century, ["1 century"]);
1296});
1297
1298test("CreateTimeTicks: above centuries", () => {
1299 runAndAssertTimeTicks(century + 30 * year, 3 * century, [
1300 "2 centuries",
1301 "3 centuries",
1302 ]);
1303 runAndAssertTimeTicks(century + 30 * year, century + 55 * year, [
1304 "13 decades",
1305 "14 decades",
1306 "15 decades",
1307 ]);
1308 runAndAssertTimeTicks(2 * century + 32 * year, 2 * century + 36 * year, [
1309 "232 years",
1310 "233 years",
1311 "234 years",
1312 "235 years",
1313 "236 years",
1314 ]);
1315});
1316
1317function getValues(ticks) {
1318 return ticks.map((tick) => tick.value);
1319}
1320
1321function getLabels(ticks) {
1322 return ticks.map((tick) => tick.label);
1323}
1324
1325function runAndAssertIntegerTicks(min, max, expected) {
1326 const message = `min: ${min}, max: ${max}`;
1327 assert.deepStrictEqual(
1328 getValues(utils.CreateIntegerTicks(min, max)),
1329 expected,
1330 message,
1331 );
1332}
1333
1334function runAndAssertTimeTicks(min, max, expected) {
1335 const message = `min: ${min}, max: ${max}`;
1336 assert.deepStrictEqual(
1337 getLabels(utils.CreateTimeTicks(min, max)),
1338 expected,
1339 message,
1340 );
1341}
1342