microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
billti/num2-sim

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/npm/qsharp/test/basics.js

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