microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/qdk_openqasm_parser

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/language_service/src/state/tests.rs

2916lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4// expect-test updates these strings automatically
5#![allow(clippy::needless_raw_string_hashes, clippy::too_many_lines)]
6
7use super::{CompilationState, CompilationStateUpdater};
8use crate::{
9 protocol::{DiagnosticUpdate, NotebookMetadata, TestCallables, WorkspaceConfigurationUpdate},
10 tests::test_fs::{FsNode, TestProjectHost, dir, file},
11};
12use expect_test::{Expect, expect};
13use miette::Diagnostic;
14use qsc::{LanguageFeatures, PackageType, line_column::Encoding};
15use qsc_linter::{AstLint, LintConfig, LintKind, LintLevel, LintOrGroupConfig};
16use serde_json::Value;
17use std::{
18 cell::RefCell,
19 fmt::{Display, Write},
20 rc::Rc,
21 str::from_utf8,
22};
23
24#[tokio::test]
25async fn no_error() {
26 let errors = RefCell::new(Vec::new());
27 let test_cases = RefCell::new(Vec::new());
28 let mut updater = new_updater(&errors, &test_cases);
29
30 updater
31 .update_document(
32 "single/foo.qs",
33 1,
34 "namespace Foo { @EntryPoint() operation Main() : Unit {} }",
35 "qsharp",
36 )
37 .await;
38
39 expect_errors(&errors, &expect!["[]"]);
40}
41
42#[tokio::test]
43async fn clear_error() {
44 let errors = RefCell::new(Vec::new());
45 let test_cases = RefCell::new(Vec::new());
46 let mut updater = new_updater(&errors, &test_cases);
47
48 updater
49 .update_document("single/foo.qs", 1, "namespace {", "qsharp")
50 .await;
51
52 expect_errors(
53 &errors,
54 &expect![[r#"
55 [
56 uri: "single/foo.qs" version: Some(1) errors: [
57 syntax error
58 [single/foo.qs] [{]
59 ],
60 ]"#]],
61 );
62
63 updater
64 .update_document(
65 "single/foo.qs",
66 2,
67 "namespace Foo { @EntryPoint() operation Main() : Unit {} }",
68 "qsharp",
69 )
70 .await;
71
72 expect_errors(
73 &errors,
74 &expect![[r#"
75 [
76 uri: "single/foo.qs" version: Some(2) errors: [],
77 ]"#]],
78 );
79}
80
81#[tokio::test]
82async fn conditionally_excluded_code_reported_as_unnecessary() {
83 let errors = RefCell::new(Vec::new());
84 let test_cases = RefCell::new(Vec::new());
85 let mut updater = new_updater(&errors, &test_cases);
86
87 // The default target profile is Unrestricted, so the `@Config(Base)` item
88 // is excluded from the compilation and should be reported as unnecessary.
89 updater
90 .update_document(
91 "single/foo.qs",
92 1,
93 "namespace Foo { operation Main() : Unit {} @Config(Base) operation Excluded() : Unit {} }",
94 "qsharp",
95 )
96 .await;
97
98 expect_errors(
99 &errors,
100 &expect![[r#"
101 [
102 uri: "single/foo.qs" version: Some(1) errors: [
103 this code is not included in the current compilation because it does not apply to the current target profile
104 ],
105 ]"#]],
106 );
107}
108
109#[tokio::test]
110async fn close_last_doc_in_project() {
111 let received_errors = RefCell::new(Vec::new());
112 let test_cases = RefCell::new(Vec::new());
113 let mut updater = new_updater(&received_errors, &test_cases);
114
115 updater
116 .update_document(
117 "project/src/other_file.qs",
118 1,
119 "namespace Foo { @EntryPoint() operation Main() : Unit {} }",
120 "qsharp",
121 )
122 .await;
123 updater
124 .update_document(
125 "project/src/this_file.qs",
126 1,
127 "/* this should not show up in the final state */ we should not see compile errors",
128 "qsharp",
129 )
130 .await;
131
132 updater
133 .close_document("project/src/this_file.qs", "qsharp")
134 .await;
135 // now there should be one compilation and one open document
136
137 check_state_and_errors(
138 &updater,
139 &received_errors,
140 &expect![[r#"
141 {
142 "project/src/other_file.qs": OpenDocument {
143 version: 1,
144 compilation: "project/qsharp.json",
145 latest_str_content: "namespace Foo { @EntryPoint() operation Main() : Unit {} }",
146 },
147 }
148 "#]],
149 &expect![[r#"
150 project/qsharp.json: [
151 "project/src/other_file.qs": "namespace Foo { @EntryPoint() operation Main() : Unit {} }",
152 "project/src/this_file.qs": "// DISK CONTENTS\n namespace Foo { }",
153 ],
154 "#]],
155 &expect![[r#"
156 [
157 uri: "project/src/this_file.qs" version: Some(1) errors: [
158 syntax error
159 [project/src/this_file.qs] [/]
160 ],
161
162 uri: "project/src/this_file.qs" version: None errors: [],
163 ]"#]],
164 );
165 updater
166 .close_document("project/src/other_file.qs", "qsharp")
167 .await;
168
169 // now there should be no file and no compilation
170 check_state_and_errors(
171 &updater,
172 &received_errors,
173 &expect![[r#"
174 {}
175 "#]],
176 &expect![""],
177 &expect!["[]"],
178 );
179}
180
181#[tokio::test]
182async fn close_last_doc_in_openqasm_project() {
183 let received_errors = RefCell::new(Vec::new());
184 let test_cases = RefCell::new(Vec::new());
185 let mut updater = new_updater(&received_errors, &test_cases);
186
187 updater
188 .update_document(
189 "openqasm_files/self-contained.qasm",
190 1,
191 "include \"stdgates.inc\";\nqubit q;\nreset q;\nx q;\nh q;\nbit c = measure q;\n",
192 "openqasm",
193 )
194 .await;
195
196 check_state_and_errors(
197 &updater,
198 &received_errors,
199 &expect![[r#"
200 {
201 "openqasm_files/self-contained.qasm": OpenDocument {
202 version: 1,
203 compilation: "openqasm_files/self-contained.qasm",
204 latest_str_content: "include \"stdgates.inc\";\nqubit q;\nreset q;\nx q;\nh q;\nbit c = measure q;\n",
205 },
206 }
207 "#]],
208 &expect![[r#"
209 openqasm_files/self-contained.qasm: [
210 "openqasm_files/self-contained.qasm": "include \"stdgates.inc\";\nqubit q;\nreset q;\nx q;\nh q;\nbit c = measure q;\n",
211 ],
212 "#]],
213 &expect!["[]"],
214 );
215
216 updater
217 .close_document("openqasm_files/self-contained.qasm", "openqasm")
218 .await;
219
220 // now there should be no file and no compilation
221 check_state_and_errors(
222 &updater,
223 &received_errors,
224 &expect![[r#"
225 {}
226 "#]],
227 &expect![""],
228 &expect!["[]"],
229 );
230}
231
232#[tokio::test]
233async fn clear_on_document_close() {
234 let errors = RefCell::new(Vec::new());
235 let test_cases = RefCell::new(Vec::new());
236
237 let mut updater = new_updater(&errors, &test_cases);
238
239 updater
240 .update_document("single/foo.qs", 1, "namespace {", "qsharp")
241 .await;
242
243 expect_errors(
244 &errors,
245 &expect![[r#"
246 [
247 uri: "single/foo.qs" version: Some(1) errors: [
248 syntax error
249 [single/foo.qs] [{]
250 ],
251 ]"#]],
252 );
253
254 updater.close_document("single/foo.qs", "qsharp").await;
255
256 expect_errors(
257 &errors,
258 &expect![[r#"
259 [
260 uri: "single/foo.qs" version: None errors: [],
261 ]"#]],
262 );
263}
264
265#[tokio::test]
266async fn compile_error() {
267 let errors = RefCell::new(Vec::new());
268 let test_cases = RefCell::new(Vec::new());
269 let mut updater = new_updater(&errors, &test_cases);
270
271 updater
272 .update_document("single/foo.qs", 1, "badsyntax", "qsharp")
273 .await;
274
275 expect_errors(
276 &errors,
277 &expect![[r#"
278 [
279 uri: "single/foo.qs" version: Some(1) errors: [
280 syntax error
281 [single/foo.qs] [badsyntax]
282 ],
283 ]"#]],
284 );
285}
286
287#[tokio::test]
288async fn rca_errors_are_reported_when_compilation_succeeds() {
289 let fs = FsNode::Dir(
290 [dir(
291 "parent",
292 [
293 file("qsharp.json", r#"{ "targetProfile": "adaptive_ri" }"#),
294 dir(
295 "src",
296 [file(
297 "main.qs",
298 r#"namespace Test { operation RcaCheck() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x } }"#,
299 )],
300 ),
301 ],
302 )]
303 .into_iter()
304 .collect(),
305 );
306
307 let fs = std::rc::Rc::new(std::cell::RefCell::new(fs));
308 let errors = std::cell::RefCell::new(Vec::new());
309 let test_cases = std::cell::RefCell::new(Vec::new());
310 let mut updater = new_updater_with_file_system(&errors, &test_cases, &fs);
311
312 // Trigger a document update to read the file
313 updater
314 .update_document(
315 "parent/src/main.qs",
316 1,
317 r#"namespace Test { operation RcaCheck() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x } }"#,
318 "qsharp",
319 )
320 .await;
321
322 // we expect two errors, one for `set x = 2.0` and one for `x`
323 expect_errors(
324 &errors,
325 &expect![[r#"
326 [
327 uri: "parent/src/main.qs" version: Some(1) errors: [
328 cannot use a dynamic double value
329 [parent/src/main.qs] [set x = 2.0]
330 cannot use a dynamic double value
331 [parent/src/main.qs] [x]
332 ],
333 ]"#]],
334 );
335}
336
337#[tokio::test]
338async fn base_profile_rca_errors_are_reported_when_compilation_succeeds() {
339 let fs = FsNode::Dir(
340 [dir(
341 "parent",
342 [
343 file("qsharp.json", r#"{ "targetProfile": "base" }"#),
344 dir(
345 "src",
346 [file(
347 "main.qs",
348 r#"namespace Test { operation RcaCheck() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x } }"#,
349 )],
350 ),
351 ],
352 )]
353 .into_iter()
354 .collect(),
355 );
356 let fs = std::rc::Rc::new(std::cell::RefCell::new(fs));
357 let errors = std::cell::RefCell::new(Vec::new());
358 let test_cases = std::cell::RefCell::new(Vec::new());
359 let mut updater = new_updater_with_file_system(&errors, &test_cases, &fs);
360
361 // Trigger a document update to re-read the manifest
362 updater
363 .update_document(
364 "parent/src/main.qs",
365 1,
366 r#"namespace Test { operation RcaCheck() : Double { use q = Qubit(); mutable x = 1.0; if MResetZ(q) == One { set x = 2.0; } x } }"#,
367 "qsharp",
368 )
369 .await;
370
371 // we expect three errors: one for `MResetZ(q) == One`, one for `set x = 2.0`, and one for `x`
372 expect_errors(
373 &errors,
374 &expect![[r#"
375 [
376 uri: "parent/src/main.qs" version: Some(1) errors: [
377 cannot use a dynamic bool value
378 [parent/src/main.qs] [MResetZ(q) == One]
379 cannot use a dynamic double value
380 [parent/src/main.qs] [set x = 2.0]
381 cannot use a dynamic double value
382 [parent/src/main.qs] [x]
383 ],
384 ]"#]],
385 );
386}
387
388#[tokio::test]
389async fn package_type_update_causes_error() {
390 let errors = RefCell::new(Vec::new());
391 let test_cases = RefCell::new(Vec::new());
392 let mut updater = new_updater(&errors, &test_cases);
393
394 updater.update_configuration(WorkspaceConfigurationUpdate {
395 package_type: Some(PackageType::Lib),
396 ..WorkspaceConfigurationUpdate::default()
397 });
398
399 updater
400 .update_document(
401 "single/foo.qs",
402 1,
403 "namespace Foo { operation Test() : Unit {} }",
404 "qsharp",
405 )
406 .await;
407
408 expect_errors(&errors, &expect!["[]"]);
409
410 updater.update_configuration(WorkspaceConfigurationUpdate {
411 package_type: Some(PackageType::Exe),
412 ..WorkspaceConfigurationUpdate::default()
413 });
414
415 expect_errors(
416 &errors,
417 &expect![[r#"
418 [
419 uri: "single/foo.qs" version: Some(1) errors: [
420 entry point not found
421 ],
422 ]"#]],
423 );
424}
425
426#[tokio::test]
427async fn target_profile_update_fixes_error() {
428 let fs = FsNode::Dir(
429 [dir(
430 "parent",
431 [
432 file("qsharp.json", r#"{}"#),
433 dir(
434 "src",
435 [file(
436 "main.qs",
437 r#"namespace Foo { operation Main() : Unit { use q = Qubit(); if M(q) == Zero { Message("hi") } } }"#,
438 )],
439 ),
440 ],
441 )]
442 .into_iter()
443 .collect(),
444 );
445 let fs = Rc::new(RefCell::new(fs));
446 let errors = RefCell::new(Vec::new());
447 let test_cases = RefCell::new(Vec::new());
448 let mut updater = new_updater_with_file_system(&errors, &test_cases, &fs);
449
450 let manifest_path = "parent/qsharp.json";
451 let success = update_manifest_field(
452 &fs,
453 manifest_path,
454 "targetProfile",
455 Value::String("base".to_string()),
456 );
457 assert!(success, "Failed to update manifest profile");
458
459 // Trigger a document update to re-read the manifest
460 updater
461 .update_document(
462 "parent/src/main.qs",
463 1,
464 r#"namespace Foo { operation Main() : Unit { use q = Qubit(); if M(q) == Zero { Message("hi") } } }"#,
465 "qsharp",
466 )
467 .await;
468
469 expect_errors(
470 &errors,
471 &expect![[r#"
472 [
473 uri: "parent/src/main.qs" version: Some(1) errors: [
474 cannot use a dynamic bool value
475 [parent/src/main.qs] [M(q) == Zero]
476 ],
477 ]"#]],
478 );
479
480 let success = update_manifest_field(
481 &fs,
482 manifest_path,
483 "targetProfile",
484 Value::String("unrestricted".to_string()),
485 );
486 assert!(success, "Failed to update manifest profile");
487
488 // Trigger a document update to re-read the manifest
489 updater
490 .update_document(
491 "parent/src/main.qs",
492 2,
493 r#"namespace Foo { operation Main() : Unit { use q = Qubit(); if M(q) == Zero { Message("hi") } } }"#,
494 "qsharp",
495 )
496 .await;
497
498 expect_errors(
499 &errors,
500 &expect![[r#"
501 [
502 uri: "parent/src/main.qs" version: Some(2) errors: [],
503 ]"#]],
504 );
505}
506
507#[tokio::test]
508async fn target_profile_update_updates_test_cases() {
509 let fs = FsNode::Dir(
510 [dir(
511 "parent",
512 [
513 file("qsharp.json", r#"{}"#),
514 dir(
515 "src",
516 [file(
517 "main.qs",
518 r#"@Config(Base) @Test() operation BaseTest() : Unit {}"#,
519 )],
520 ),
521 ],
522 )]
523 .into_iter()
524 .collect(),
525 );
526 let fs = Rc::new(RefCell::new(fs));
527 let errors = RefCell::new(Vec::new());
528 let test_cases = RefCell::new(Vec::new());
529 let mut updater = new_updater_with_file_system(&errors, &test_cases, &fs);
530
531 // Set profile to unrestricted, expect test case to NOT appear
532 assert!(update_manifest_field(
533 &fs,
534 "parent/qsharp.json",
535 "targetProfile",
536 Value::String("unrestricted".to_string())
537 ));
538
539 updater
540 .update_document(
541 "parent/src/main.qs",
542 1,
543 r#"@Config(Base) @Test() operation BaseTest() : Unit {}"#,
544 "qsharp",
545 )
546 .await;
547
548 expect![[r#"
549 [
550 TestCallables {
551 callables: [],
552 },
553 ]
554 "#]]
555 .assert_debug_eq(&test_cases.borrow());
556
557 // reset accumulated test cases after each check
558 test_cases.borrow_mut().clear();
559
560 // Set profile to base, expect test case to appear
561 assert!(update_manifest_field(
562 &fs,
563 "parent/qsharp.json",
564 "targetProfile",
565 Value::String("base".to_string())
566 ));
567
568 // Trigger a document update to re-read the manifest
569 updater
570 .update_document(
571 "parent/src/main.qs",
572 2,
573 r#"@Config(Base) @Test() operation BaseTest() : Unit {}"#,
574 "qsharp",
575 )
576 .await;
577
578 expect![[r#"
579 [
580 TestCallables {
581 callables: [
582 TestCallable {
583 callable_name: "main.BaseTest",
584 compilation_uri: "parent/qsharp.json",
585 location: Location {
586 source: "parent/src/main.qs",
587 range: Range {
588 start: Position {
589 line: 0,
590 column: 32,
591 },
592 end: Position {
593 line: 0,
594 column: 40,
595 },
596 },
597 },
598 friendly_name: "parent",
599 },
600 ],
601 },
602 ]
603 "#]]
604 .assert_debug_eq(&test_cases.borrow());
605}
606
607#[tokio::test]
608async fn target_profile_update_causes_error_in_stdlib() {
609 let fs = FsNode::Dir(
610 [dir(
611 "parent",
612 [
613 file("qsharp.json", r#"{}"#),
614 dir(
615 "src",
616 [file(
617 "main.qs",
618 r#"namespace Foo { @EntryPoint() operation Main() : Unit { use q = Qubit(); let r = M(q); let b = Microsoft.Quantum.Convert.ResultAsBool(r); } }"#,
619 )],
620 ),
621 ],
622 )]
623 .into_iter()
624 .collect(),
625 );
626 let fs = Rc::new(RefCell::new(fs));
627 let errors = RefCell::new(Vec::new());
628 let test_cases = RefCell::new(Vec::new());
629 let mut updater = new_updater_with_file_system(&errors, &test_cases, &fs);
630
631 updater.update_document(
632 "parent/src/main.qs",
633 1,
634 r#"namespace Foo { @EntryPoint() operation Main() : Unit { use q = Qubit(); let r = M(q); let b = Microsoft.Quantum.Convert.ResultAsBool(r); } }"#,
635 "qsharp",
636 ).await;
637
638 expect_errors(&errors, &expect!["[]"]);
639
640 let manifest_path = "parent/qsharp.json";
641 let success = update_manifest_field(
642 &fs,
643 manifest_path,
644 "targetProfile",
645 Value::String("base".to_string()),
646 );
647 assert!(success, "Failed to update manifest profile");
648
649 // Trigger a document update to re-read the manifest
650 updater
651 .update_document(
652 "parent/src/main.qs",
653 2,
654 r#"namespace Foo { @EntryPoint() operation Main() : Unit { use q = Qubit(); let r = M(q); let b = Microsoft.Quantum.Convert.ResultAsBool(r); } }"#,
655 "qsharp",
656 )
657 .await;
658
659 expect_errors(
660 &errors,
661 &expect![[r#"
662 [
663 uri: "parent/src/main.qs" version: Some(2) errors: [
664 cannot use a dynamic bool value
665 [parent/src/main.qs] [Microsoft.Quantum.Convert.ResultAsBool(r)]
666 ],
667 ]"#]],
668 );
669}
670
671#[tokio::test]
672async fn notebook_document_no_errors() {
673 let errors = RefCell::new(Vec::new());
674 let test_cases = RefCell::new(Vec::new());
675 let mut updater = new_updater(&errors, &test_cases);
676
677 updater
678 .update_notebook_document(
679 "notebook.ipynb",
680 &NotebookMetadata::default(),
681 [
682 ("cell1", 1, "operation Main() : Unit {}"),
683 ("cell2", 1, "Main()"),
684 ]
685 .into_iter(),
686 )
687 .await;
688
689 expect_errors(&errors, &expect!["[]"]);
690}
691
692#[tokio::test]
693async fn notebook_document_errors() {
694 let errors = RefCell::new(Vec::new());
695 let test_cases = RefCell::new(Vec::new());
696 let mut updater = new_updater(&errors, &test_cases);
697
698 updater
699 .update_notebook_document(
700 "notebook.ipynb",
701 &NotebookMetadata::default(),
702 [
703 ("cell1", 1, "operation Main() : Unit {}"),
704 ("cell2", 1, "Foo()"),
705 ]
706 .into_iter(),
707 )
708 .await;
709
710 expect_errors(
711 &errors,
712 &expect![[r#"
713 [
714 uri: "cell2" version: Some(1) errors: [
715 name error
716 [cell2] [Foo]
717 ],
718 ]"#]],
719 );
720}
721
722#[tokio::test]
723async fn notebook_document_lints() {
724 let errors = RefCell::new(Vec::new());
725 let test_cases = RefCell::new(Vec::new());
726 let mut updater = new_updater(&errors, &test_cases);
727
728 updater
729 .update_notebook_document(
730 "notebook.ipynb",
731 &NotebookMetadata::default(),
732 [
733 ("cell1", 1, "function Foo() : Unit { let x = 4;;;; }"),
734 ("cell2", 1, "function Bar() : Unit { let y = 5 / 0; }"),
735 ]
736 .into_iter(),
737 )
738 .await;
739
740 expect_errors(
741 &errors,
742 &expect![[r#"
743 [
744 uri: "cell1" version: Some(1) errors: [
745 redundant semicolons
746 [cell1] [;;;]
747 ],
748
749 uri: "cell2" version: Some(1) errors: [
750 attempt to divide by zero
751 [cell2] [5 / 0]
752 ],
753 ]"#]],
754 );
755}
756
757#[tokio::test]
758async fn notebook_update_remove_cell_clears_errors() {
759 let errors = RefCell::new(Vec::new());
760 let test_cases = RefCell::new(Vec::new());
761 let mut updater = new_updater(&errors, &test_cases);
762
763 updater
764 .update_notebook_document(
765 "notebook.ipynb",
766 &NotebookMetadata::default(),
767 [
768 ("cell1", 1, "operation Main() : Unit {}"),
769 ("cell2", 1, "Foo()"),
770 ]
771 .into_iter(),
772 )
773 .await;
774
775 expect_errors(
776 &errors,
777 &expect![[r#"
778 [
779 uri: "cell2" version: Some(1) errors: [
780 name error
781 [cell2] [Foo]
782 ],
783 ]"#]],
784 );
785
786 updater
787 .update_notebook_document(
788 "notebook.ipynb",
789 &NotebookMetadata::default(),
790 [("cell1", 1, "operation Main() : Unit {}")].into_iter(),
791 )
792 .await;
793
794 expect_errors(
795 &errors,
796 &expect![[r#"
797 [
798 uri: "cell2" version: None errors: [],
799 ]"#]],
800 );
801}
802
803#[tokio::test]
804async fn close_notebook_clears_errors() {
805 let errors = RefCell::new(Vec::new());
806 let test_cases = RefCell::new(Vec::new());
807 let mut updater = new_updater(&errors, &test_cases);
808
809 updater
810 .update_notebook_document(
811 "notebook.ipynb",
812 &NotebookMetadata::default(),
813 [
814 ("cell1", 1, "operation Main() : Unit {}"),
815 ("cell2", 1, "Foo()"),
816 ]
817 .into_iter(),
818 )
819 .await;
820
821 expect_errors(
822 &errors,
823 &expect![[r#"
824 [
825 uri: "cell2" version: Some(1) errors: [
826 name error
827 [cell2] [Foo]
828 ],
829 ]"#]],
830 );
831
832 updater.close_notebook_document("notebook.ipynb");
833
834 expect_errors(
835 &errors,
836 &expect![[r#"
837 [
838 uri: "cell2" version: None errors: [],
839 ]"#]],
840 );
841}
842
843#[tokio::test]
844async fn update_notebook_with_valid_dependencies() {
845 let fs = FsNode::Dir(
846 [dir(
847 "project",
848 [
849 file("qsharp.json", r#"{ }"#),
850 dir(
851 "src",
852 [file(
853 "file.qs",
854 r#"namespace Foo { function Bar() : Unit { } }"#,
855 )],
856 ),
857 ],
858 )]
859 .into_iter()
860 .collect(),
861 );
862
863 let fs = Rc::new(RefCell::new(fs));
864 let errors = RefCell::new(Vec::new());
865 let test_cases = RefCell::new(Vec::new());
866
867 let mut updater = new_updater_with_file_system(&errors, &test_cases, &fs);
868
869 updater
870 .update_notebook_document(
871 "notebook.ipynb",
872 &NotebookMetadata {
873 target_profile: None,
874 language_features: LanguageFeatures::default(),
875 manifest: None,
876 project_root: Some("project".to_string()),
877 },
878 [("cell1", 1, "open Foo;Bar();")].into_iter(),
879 )
880 .await;
881
882 expect_errors(&errors, &expect!["[]"]);
883}
884
885#[tokio::test]
886async fn update_notebook_reports_errors_from_dependencies() {
887 let fs = FsNode::Dir(
888 [dir(
889 "project",
890 [
891 file("qsharp.json", r#"{ }"#),
892 dir(
893 "src",
894 [file(
895 "file.qs",
896 r#"namespace Foo { function Bar() : Int { } }"#,
897 )],
898 ),
899 ],
900 )]
901 .into_iter()
902 .collect(),
903 );
904
905 let fs = Rc::new(RefCell::new(fs));
906 let errors = RefCell::new(Vec::new());
907 let test_cases = RefCell::new(Vec::new());
908
909 let mut updater = new_updater_with_file_system(&errors, &test_cases, &fs);
910
911 updater
912 .update_notebook_document(
913 "notebook.ipynb",
914 &NotebookMetadata {
915 target_profile: None,
916 language_features: LanguageFeatures::default(),
917 manifest: None,
918 project_root: Some("project".to_string()),
919 },
920 [("cell1", 1, "open Foo;Bar();")].into_iter(),
921 )
922 .await;
923
924 expect_errors(
925 &errors,
926 &expect![[r#"
927 [
928 uri: "cell1" version: Some(1) errors: [
929 name error
930 [cell1] [Foo]
931 name error
932 [cell1] [Bar]
933 ],
934
935 uri: "project/src/file.qs" version: None errors: [
936 type error
937 [project/src/file.qs] [Int]
938 ],
939 ]"#]],
940 );
941}
942
943#[tokio::test]
944async fn update_notebook_reports_errors_from_dependency_of_dependencies() {
945 let fs = FsNode::Dir(
946 [
947 dir(
948 "project",
949 [
950 file(
951 "qsharp.json",
952 r#"{ "dependencies" : { "MyDep" : { "path" : "../project2" } } }"#,
953 ),
954 dir(
955 "src",
956 [file(
957 "file.qs",
958 r#"namespace Foo { function Bar() : Unit { } }"#,
959 )],
960 ),
961 ],
962 ),
963 dir(
964 "project2",
965 [
966 file("qsharp.json", r#"{ }"#),
967 dir(
968 "src",
969 [file(
970 "file.qs",
971 r#"namespace Foo { function Baz() : Int { } }"#,
972 )],
973 ),
974 ],
975 ),
976 ]
977 .into_iter()
978 .collect(),
979 );
980
981 let fs = Rc::new(RefCell::new(fs));
982 let errors = RefCell::new(Vec::new());
983 let test_cases = RefCell::new(Vec::new());
984
985 let mut updater = new_updater_with_file_system(&errors, &test_cases, &fs);
986
987 updater
988 .update_notebook_document(
989 "notebook.ipynb",
990 &NotebookMetadata {
991 target_profile: None,
992 language_features: LanguageFeatures::default(),
993 manifest: None,
994 project_root: Some("project".to_string()),
995 },
996 [("cell1", 1, "open Foo;Bar();")].into_iter(),
997 )
998 .await;
999
1000 expect_errors(
1001 &errors,
1002 &expect![[r#"
1003 [
1004 uri: "project2/src/file.qs" version: None errors: [
1005 type error
1006 [project2/src/file.qs] [Int]
1007 ],
1008 ]"#]],
1009 );
1010}
1011
1012#[tokio::test]
1013async fn update_doc_updates_project() {
1014 let received_errors = RefCell::new(Vec::new());
1015 let test_cases = RefCell::new(Vec::new());
1016 let mut updater = new_updater(&received_errors, &test_cases);
1017
1018 updater
1019 .update_document(
1020 "project/src/other_file.qs",
1021 1,
1022 "namespace Foo { @EntryPoint() operation Main() : Unit {} }",
1023 "qsharp",
1024 )
1025 .await;
1026 updater
1027 .update_document(
1028 "project/src/this_file.qs",
1029 1,
1030 "namespace Foo { we should see this in the source }",
1031 "qsharp",
1032 )
1033 .await;
1034
1035 check_state_and_errors(
1036 &updater,
1037 &received_errors,
1038 &expect![[r#"
1039 {
1040 "project/src/this_file.qs": OpenDocument {
1041 version: 1,
1042 compilation: "project/qsharp.json",
1043 latest_str_content: "namespace Foo { we should see this in the source }",
1044 },
1045 "project/src/other_file.qs": OpenDocument {
1046 version: 1,
1047 compilation: "project/qsharp.json",
1048 latest_str_content: "namespace Foo { @EntryPoint() operation Main() : Unit {} }",
1049 },
1050 }
1051 "#]],
1052 &expect![[r#"
1053 project/qsharp.json: [
1054 "project/src/other_file.qs": "namespace Foo { @EntryPoint() operation Main() : Unit {} }",
1055 "project/src/this_file.qs": "namespace Foo { we should see this in the source }",
1056 ],
1057 "#]],
1058 &expect![[r#"
1059 [
1060 uri: "project/src/this_file.qs" version: Some(1) errors: [
1061 syntax error
1062 [project/src/this_file.qs] [we]
1063 ],
1064 ]"#]],
1065 );
1066}
1067
1068#[tokio::test]
1069async fn file_not_in_files_list() {
1070 let received_errors = RefCell::new(Vec::new());
1071 let test_cases = RefCell::new(Vec::new());
1072
1073 // Manifest has a "files" field.
1074 // One file is listed in it, the other is not.
1075 // This shouldn't block project load, but should generate an error.
1076 let fs = FsNode::Dir(
1077 [dir(
1078 "project",
1079 [
1080 file(
1081 "qsharp.json",
1082 r#"{
1083 "files" : [
1084 "src/explicitly_listed.qs"
1085 ]
1086 }"#,
1087 ),
1088 dir(
1089 "src",
1090 [
1091 file("explicitly_listed.qs", "// CONTENTS"),
1092 file("unlisted.qs", "// CONTENTS"),
1093 ],
1094 ),
1095 ],
1096 )]
1097 .into_iter()
1098 .collect(),
1099 );
1100
1101 let fs = Rc::new(RefCell::new(fs));
1102 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
1103
1104 // Open the file that is listed in the files list
1105 updater
1106 .update_document(
1107 "project/src/explicitly_listed.qs",
1108 1,
1109 "// CONTENTS",
1110 "qsharp",
1111 )
1112 .await;
1113
1114 // The whole project should be loaded, which should generate
1115 // an error about the other file that's unlisted.
1116 // They are both in the compilation.
1117 check_state_and_errors(
1118 &updater,
1119 &received_errors,
1120 &expect![[r#"
1121 {
1122 "project/src/explicitly_listed.qs": OpenDocument {
1123 version: 1,
1124 compilation: "project/qsharp.json",
1125 latest_str_content: "// CONTENTS",
1126 },
1127 }
1128 "#]],
1129 &expect![[r#"
1130 project/qsharp.json: [
1131 "project/src/explicitly_listed.qs": "// CONTENTS",
1132 "project/src/unlisted.qs": "// CONTENTS",
1133 ],
1134 "#]],
1135 &expect![[r#"
1136 [
1137 uri: "project/src/unlisted.qs" version: None errors: [
1138 File src/unlisted.qs is not listed in the `files` field of the manifest
1139 ],
1140 ]"#]],
1141 );
1142
1143 // Open the unlisted file as well.
1144 updater
1145 .update_document("project/src/unlisted.qs", 1, "// CONTENTS", "qsharp")
1146 .await;
1147
1148 // Documents are both open and correctly associated with the project.
1149 // The error about the unlisted file persists.
1150 check_state_and_errors(
1151 &updater,
1152 &received_errors,
1153 &expect![[r#"
1154 {
1155 "project/src/explicitly_listed.qs": OpenDocument {
1156 version: 1,
1157 compilation: "project/qsharp.json",
1158 latest_str_content: "// CONTENTS",
1159 },
1160 "project/src/unlisted.qs": OpenDocument {
1161 version: 1,
1162 compilation: "project/qsharp.json",
1163 latest_str_content: "// CONTENTS",
1164 },
1165 }
1166 "#]],
1167 &expect![[r#"
1168 project/qsharp.json: [
1169 "project/src/explicitly_listed.qs": "// CONTENTS",
1170 "project/src/unlisted.qs": "// CONTENTS",
1171 ],
1172 "#]],
1173 &expect![[r#"
1174 [
1175 uri: "project/src/unlisted.qs" version: Some(1) errors: [
1176 File src/unlisted.qs is not listed in the `files` field of the manifest
1177 ],
1178 ]"#]],
1179 );
1180}
1181
1182#[tokio::test]
1183async fn file_not_under_src() {
1184 let received_errors = RefCell::new(Vec::new());
1185 let test_cases = RefCell::new(Vec::new());
1186
1187 // One file lives under the 'src' directory, the other does not.
1188 // The one that isn't under 'src' should not be associated with the project.
1189 let fs = FsNode::Dir(
1190 [dir(
1191 "project",
1192 [
1193 file(
1194 "qsharp.json",
1195 r#"{
1196 "files" : [
1197 "src/under_src.qs"
1198 ]
1199 }"#,
1200 ),
1201 file("not_under_src.qs", "// CONTENTS"),
1202 dir("src", [file("under_src.qs", "// CONTENTS")]),
1203 ],
1204 )]
1205 .into_iter()
1206 .collect(),
1207 );
1208
1209 let fs = Rc::new(RefCell::new(fs));
1210 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
1211
1212 // Open the file that is not under src.
1213 updater
1214 .update_document("project/not_under_src.qs", 1, "// CONTENTS", "qsharp")
1215 .await;
1216
1217 // This document is not associated with the manifest,
1218 // didn't cause the manifest to be loaded,
1219 // and lives in its own project by itself.
1220 check_state_and_errors(
1221 &updater,
1222 &received_errors,
1223 &expect![[r#"
1224 {
1225 "project/not_under_src.qs": OpenDocument {
1226 version: 1,
1227 compilation: "project/not_under_src.qs",
1228 latest_str_content: "// CONTENTS",
1229 },
1230 }
1231 "#]],
1232 &expect![[r#"
1233 project/not_under_src.qs: [
1234 "project/not_under_src.qs": "// CONTENTS",
1235 ],
1236 "#]],
1237 &expect!["[]"],
1238 );
1239
1240 // Open the file that's properly under the "src" directory.
1241 updater
1242 .update_document("project/src/under_src.qs", 1, "// CONTENTS", "qsharp")
1243 .await;
1244
1245 // The manifest is loaded, `not_under_src.qs` is still not associated with it.
1246 check_state_and_errors(
1247 &updater,
1248 &received_errors,
1249 &expect![[r#"
1250 {
1251 "project/not_under_src.qs": OpenDocument {
1252 version: 1,
1253 compilation: "project/not_under_src.qs",
1254 latest_str_content: "// CONTENTS",
1255 },
1256 "project/src/under_src.qs": OpenDocument {
1257 version: 1,
1258 compilation: "project/qsharp.json",
1259 latest_str_content: "// CONTENTS",
1260 },
1261 }
1262 "#]],
1263 &expect![[r#"
1264 project/not_under_src.qs: [
1265 "project/not_under_src.qs": "// CONTENTS",
1266 ],
1267 project/qsharp.json: [
1268 "project/src/under_src.qs": "// CONTENTS",
1269 ],
1270 "#]],
1271 &expect!["[]"],
1272 );
1273}
1274
1275/// In this test, we:
1276/// open a project
1277/// update a buffer in the LS
1278/// close that buffer
1279/// assert that the LS no longer prioritizes that open buffer
1280/// over the FS
1281#[tokio::test]
1282async fn close_doc_prioritizes_fs() {
1283 let received_errors = RefCell::new(Vec::new());
1284 let test_cases = RefCell::new(Vec::new());
1285 let mut updater = new_updater(&received_errors, &test_cases);
1286
1287 updater
1288 .update_document(
1289 "project/src/other_file.qs",
1290 1,
1291 "namespace Foo { @EntryPoint() operation Main() : Unit {} }",
1292 "qsharp",
1293 )
1294 .await;
1295 updater
1296 .update_document(
1297 "project/src/this_file.qs",
1298 1,
1299 "/* this should not show up in the final state */ we should not see compile errors",
1300 "qsharp",
1301 )
1302 .await;
1303
1304 updater
1305 .close_document("project/src/this_file.qs", "qsharp")
1306 .await;
1307
1308 check_state_and_errors(
1309 &updater,
1310 &received_errors,
1311 &expect![[r#"
1312 {
1313 "project/src/other_file.qs": OpenDocument {
1314 version: 1,
1315 compilation: "project/qsharp.json",
1316 latest_str_content: "namespace Foo { @EntryPoint() operation Main() : Unit {} }",
1317 },
1318 }
1319 "#]],
1320 &expect![[r#"
1321 project/qsharp.json: [
1322 "project/src/other_file.qs": "namespace Foo { @EntryPoint() operation Main() : Unit {} }",
1323 "project/src/this_file.qs": "// DISK CONTENTS\n namespace Foo { }",
1324 ],
1325 "#]],
1326 &expect![[r#"
1327 [
1328 uri: "project/src/this_file.qs" version: Some(1) errors: [
1329 syntax error
1330 [project/src/this_file.qs] [/]
1331 ],
1332
1333 uri: "project/src/this_file.qs" version: None errors: [],
1334 ]"#]],
1335 );
1336}
1337
1338#[tokio::test]
1339async fn delete_manifest() {
1340 let received_errors = RefCell::new(Vec::new());
1341 let test_cases = RefCell::new(Vec::new());
1342 let mut updater = new_updater(&received_errors, &test_cases);
1343
1344 updater
1345 .update_document(
1346 "project/src/this_file.qs",
1347 1,
1348 "// DISK CONTENTS\n namespace Foo { }",
1349 "qsharp",
1350 )
1351 .await;
1352
1353 check_state(
1354 &updater,
1355 &expect![[r#"
1356 {
1357 "project/src/this_file.qs": OpenDocument {
1358 version: 1,
1359 compilation: "project/qsharp.json",
1360 latest_str_content: "// DISK CONTENTS\n namespace Foo { }",
1361 },
1362 }
1363 "#]],
1364 &expect![[r#"
1365 project/qsharp.json: [
1366 "project/src/other_file.qs": "// DISK CONTENTS\n namespace OtherFile { operation Other() : Unit { } }",
1367 "project/src/this_file.qs": "// DISK CONTENTS\n namespace Foo { }",
1368 ],
1369 "#]],
1370 );
1371
1372 TEST_FS.with(|fs| fs.borrow_mut().remove("project/qsharp.json"));
1373
1374 updater
1375 .update_document(
1376 "project/src/this_file.qs",
1377 2,
1378 "// DISK CONTENTS\n namespace Foo { }",
1379 "qsharp",
1380 )
1381 .await;
1382
1383 check_state(
1384 &updater,
1385 &expect![[r#"
1386 {
1387 "project/src/this_file.qs": OpenDocument {
1388 version: 2,
1389 compilation: "project/src/this_file.qs",
1390 latest_str_content: "// DISK CONTENTS\n namespace Foo { }",
1391 },
1392 }
1393 "#]],
1394 &expect![[r#"
1395 project/src/this_file.qs: [
1396 "project/src/this_file.qs": "// DISK CONTENTS\n namespace Foo { }",
1397 ],
1398 "#]],
1399 );
1400}
1401
1402#[tokio::test]
1403async fn delete_manifest_then_close() {
1404 let received_errors = RefCell::new(Vec::new());
1405 let test_cases = RefCell::new(Vec::new());
1406 let mut updater = new_updater(&received_errors, &test_cases);
1407
1408 updater
1409 .update_document(
1410 "project/src/this_file.qs",
1411 1,
1412 "// DISK CONTENTS\n namespace Foo { }",
1413 "qsharp",
1414 )
1415 .await;
1416
1417 check_state(
1418 &updater,
1419 &expect![[r#"
1420 {
1421 "project/src/this_file.qs": OpenDocument {
1422 version: 1,
1423 compilation: "project/qsharp.json",
1424 latest_str_content: "// DISK CONTENTS\n namespace Foo { }",
1425 },
1426 }
1427 "#]],
1428 &expect![[r#"
1429 project/qsharp.json: [
1430 "project/src/other_file.qs": "// DISK CONTENTS\n namespace OtherFile { operation Other() : Unit { } }",
1431 "project/src/this_file.qs": "// DISK CONTENTS\n namespace Foo { }",
1432 ],
1433 "#]],
1434 );
1435
1436 TEST_FS.with(|fs| fs.borrow_mut().remove("project/qsharp.json"));
1437
1438 updater
1439 .close_document("project/src/this_file.qs", "qsharp")
1440 .await;
1441
1442 check_state(
1443 &updater,
1444 &expect![[r#"
1445 {}
1446 "#]],
1447 &expect![""],
1448 );
1449}
1450
1451#[tokio::test]
1452async fn doc_switches_project() {
1453 let received_errors = RefCell::new(Vec::new());
1454 let test_cases = RefCell::new(Vec::new());
1455 let mut updater = new_updater(&received_errors, &test_cases);
1456
1457 updater
1458 .update_document(
1459 "nested_projects/src/subdir/src/a.qs",
1460 1,
1461 "namespace A {}",
1462 "qsharp",
1463 )
1464 .await;
1465
1466 updater
1467 .update_document(
1468 "nested_projects/src/subdir/src/b.qs",
1469 1,
1470 "namespace B {}",
1471 "qsharp",
1472 )
1473 .await;
1474
1475 check_state(
1476 &updater,
1477 &expect![[r#"
1478 {
1479 "nested_projects/src/subdir/src/a.qs": OpenDocument {
1480 version: 1,
1481 compilation: "nested_projects/src/subdir/qsharp.json",
1482 latest_str_content: "namespace A {}",
1483 },
1484 "nested_projects/src/subdir/src/b.qs": OpenDocument {
1485 version: 1,
1486 compilation: "nested_projects/src/subdir/qsharp.json",
1487 latest_str_content: "namespace B {}",
1488 },
1489 }
1490 "#]],
1491 &expect![[r#"
1492 nested_projects/src/subdir/qsharp.json: [
1493 "nested_projects/src/subdir/src/a.qs": "namespace A {}",
1494 "nested_projects/src/subdir/src/b.qs": "namespace B {}",
1495 ],
1496 "#]],
1497 );
1498
1499 // This is just a trick to cause the file to move between projects.
1500 // Deleting subdir/qsharp.json will cause subdir/a.qs to be picked up
1501 // by the parent directory's qsharp.json
1502 TEST_FS.with(|fs| {
1503 fs.borrow_mut()
1504 .remove("nested_projects/src/subdir/qsharp.json");
1505 });
1506
1507 updater
1508 .update_document(
1509 "nested_projects/src/subdir/src/a.qs",
1510 2,
1511 "namespace A {}",
1512 "qsharp",
1513 )
1514 .await;
1515
1516 updater
1517 .update_document(
1518 "nested_projects/src/subdir/src/b.qs",
1519 2,
1520 "namespace B {}",
1521 "qsharp",
1522 )
1523 .await;
1524
1525 // the error should now be coming from the parent qsharp.json? But the document
1526 // is closed........
1527 check_state(
1528 &updater,
1529 &expect![[r#"
1530 {
1531 "nested_projects/src/subdir/src/a.qs": OpenDocument {
1532 version: 2,
1533 compilation: "nested_projects/qsharp.json",
1534 latest_str_content: "namespace A {}",
1535 },
1536 "nested_projects/src/subdir/src/b.qs": OpenDocument {
1537 version: 2,
1538 compilation: "nested_projects/qsharp.json",
1539 latest_str_content: "namespace B {}",
1540 },
1541 }
1542 "#]],
1543 &expect![[r#"
1544 nested_projects/qsharp.json: [
1545 "nested_projects/src/subdir/src/a.qs": "namespace A {}",
1546 "nested_projects/src/subdir/src/b.qs": "namespace B {}",
1547 ],
1548 "#]],
1549 );
1550}
1551
1552#[tokio::test]
1553async fn doc_switches_project_on_close() {
1554 let received_errors = RefCell::new(Vec::new());
1555 let test_cases = RefCell::new(Vec::new());
1556 let mut updater = new_updater(&received_errors, &test_cases);
1557
1558 updater
1559 .update_document(
1560 "nested_projects/src/subdir/src/a.qs",
1561 1,
1562 "namespace A {}",
1563 "qsharp",
1564 )
1565 .await;
1566
1567 updater
1568 .update_document(
1569 "nested_projects/src/subdir/src/b.qs",
1570 1,
1571 "namespace B {}",
1572 "qsharp",
1573 )
1574 .await;
1575
1576 check_state(
1577 &updater,
1578 &expect![[r#"
1579 {
1580 "nested_projects/src/subdir/src/a.qs": OpenDocument {
1581 version: 1,
1582 compilation: "nested_projects/src/subdir/qsharp.json",
1583 latest_str_content: "namespace A {}",
1584 },
1585 "nested_projects/src/subdir/src/b.qs": OpenDocument {
1586 version: 1,
1587 compilation: "nested_projects/src/subdir/qsharp.json",
1588 latest_str_content: "namespace B {}",
1589 },
1590 }
1591 "#]],
1592 &expect![[r#"
1593 nested_projects/src/subdir/qsharp.json: [
1594 "nested_projects/src/subdir/src/a.qs": "namespace A {}",
1595 "nested_projects/src/subdir/src/b.qs": "namespace B {}",
1596 ],
1597 "#]],
1598 );
1599
1600 // This is just a trick to cause the file to move between projects.
1601 // Deleting subdir/qsharp.json will cause subdir/src/a.qs to be picked up
1602 // by the parent directory's qsharp.json
1603 TEST_FS.with(|fs| {
1604 fs.borrow_mut()
1605 .remove("nested_projects/src/subdir/qsharp.json");
1606 });
1607
1608 updater
1609 .close_document("nested_projects/src/subdir/src/a.qs", "qsharp")
1610 .await;
1611
1612 updater
1613 .update_document(
1614 "nested_projects/src/subdir/src/b.qs",
1615 2,
1616 "namespace B {}",
1617 "qsharp",
1618 )
1619 .await;
1620
1621 check_state(
1622 &updater,
1623 &expect![[r#"
1624 {
1625 "nested_projects/src/subdir/src/b.qs": OpenDocument {
1626 version: 2,
1627 compilation: "nested_projects/qsharp.json",
1628 latest_str_content: "namespace B {}",
1629 },
1630 }
1631 "#]],
1632 &expect![[r#"
1633 nested_projects/qsharp.json: [
1634 "nested_projects/src/subdir/src/a.qs": "namespace A {}",
1635 "nested_projects/src/subdir/src/b.qs": "namespace B {}",
1636 ],
1637 "#]],
1638 );
1639}
1640
1641#[tokio::test]
1642async fn loading_lints_config_from_manifest() {
1643 let this_file_qs = "namespace Foo { operation Main() : Unit { let x = 5 / 0 + (2 ^ 4); } }";
1644 let fs = FsNode::Dir(
1645 [dir(
1646 "project",
1647 [
1648 file(
1649 "qsharp.json",
1650 r#"{ "lints": [{ "lint": "divisionByZero", "level": "error" }, { "lint": "needlessParens", "level": "error" }] }"#,
1651 ),
1652 dir(
1653 "src",
1654 [file(
1655 "this_file.qs",
1656 this_file_qs,
1657 )],
1658 ),
1659 ],
1660 )]
1661 .into_iter()
1662 .collect(),
1663 );
1664
1665 let fs = Rc::new(RefCell::new(fs));
1666 let received_errors = RefCell::new(Vec::new());
1667 let test_cases = RefCell::new(Vec::new());
1668
1669 let updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
1670
1671 // Check the LintConfig.
1672 check_lints_config(
1673 &updater,
1674 &expect![[r#"
1675 [
1676 Lint(
1677 LintConfig {
1678 kind: Ast(
1679 DivisionByZero,
1680 ),
1681 level: Error,
1682 },
1683 ),
1684 Lint(
1685 LintConfig {
1686 kind: Ast(
1687 NeedlessParens,
1688 ),
1689 level: Error,
1690 },
1691 ),
1692 ]"#]],
1693 )
1694 .await;
1695}
1696
1697#[allow(clippy::too_many_lines)]
1698#[tokio::test]
1699async fn lints_update_after_manifest_change() {
1700 let this_file_qs =
1701 "namespace Foo { @EntryPoint() function Main() : Unit { let x = 5 / 0 + (2 ^ 4); } }";
1702 let fs = FsNode::Dir(
1703 [dir(
1704 "project",
1705 [
1706 file(
1707 "qsharp.json",
1708 r#"{ "lints": [{ "lint": "divisionByZero", "level": "error" }, { "lint": "needlessParens", "level": "error" }] }"#,
1709 ),
1710 dir(
1711 "src",
1712 [file(
1713 "this_file.qs",
1714 this_file_qs,
1715 )],
1716 ),
1717 ],
1718 )]
1719 .into_iter()
1720 .collect(),
1721 );
1722
1723 let fs = Rc::new(RefCell::new(fs));
1724 let received_errors = RefCell::new(Vec::new());
1725 let test_cases = RefCell::new(Vec::new());
1726
1727 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
1728
1729 // Trigger a document update.
1730 updater
1731 .update_document("project/src/this_file.qs", 1, this_file_qs, "qsharp")
1732 .await;
1733
1734 // Check generated lints.
1735 expect_errors(
1736 &received_errors,
1737 &expect![[r#"
1738 [
1739 uri: "project/src/this_file.qs" version: Some(1) errors: [
1740 unnecessary parentheses
1741 [project/src/this_file.qs] [(2 ^ 4)]
1742 attempt to divide by zero
1743 [project/src/this_file.qs] [5 / 0]
1744 ],
1745 ]"#]],
1746 );
1747
1748 // Modify the manifest.
1749 fs
1750 .borrow_mut()
1751 .write_file("project/qsharp.json", r#"{ "lints": [{ "lint": "divisionByZero", "level": "warn" }, { "lint": "needlessParens", "level": "warn" }] }"#)
1752 .expect("qsharp.json should exist");
1753
1754 // Trigger a document update
1755 updater
1756 .update_document("project/src/this_file.qs", 1, this_file_qs, "qsharp")
1757 .await;
1758
1759 // Check lints again
1760 expect_errors(
1761 &received_errors,
1762 &expect![[r#"
1763 [
1764 uri: "project/src/this_file.qs" version: Some(1) errors: [
1765 unnecessary parentheses
1766 [project/src/this_file.qs] [(2 ^ 4)]
1767 attempt to divide by zero
1768 [project/src/this_file.qs] [5 / 0]
1769 ],
1770 ]"#]],
1771 );
1772}
1773
1774#[tokio::test]
1775async fn lints_prefer_workspace_over_defaults() {
1776 let this_file_qs =
1777 "namespace Foo { @EntryPoint() function Main() : Unit { let x = 5 / 0 + (2 ^ 4); } }";
1778
1779 let received_errors = RefCell::new(Vec::new());
1780 let test_cases = RefCell::new(Vec::new());
1781 let mut updater = new_updater(&received_errors, &test_cases);
1782 updater.update_configuration(WorkspaceConfigurationUpdate {
1783 lints_config: Some(vec![LintOrGroupConfig::Lint(LintConfig {
1784 kind: LintKind::Ast(AstLint::DivisionByZero),
1785 level: LintLevel::Warn,
1786 })]),
1787 ..WorkspaceConfigurationUpdate::default()
1788 });
1789
1790 // Trigger a document update.
1791 updater
1792 .update_document("project/src/this_file.qs", 1, this_file_qs, "qsharp")
1793 .await;
1794
1795 // Check generated lints.
1796 expect_errors(
1797 &received_errors,
1798 &expect![[r#"
1799 [
1800 uri: "project/src/this_file.qs" version: Some(1) errors: [
1801 attempt to divide by zero
1802 [project/src/this_file.qs] [5 / 0]
1803 ],
1804 ]"#]],
1805 );
1806}
1807
1808#[tokio::test]
1809async fn lints_prefer_manifest_over_workspace() {
1810 let this_file_qs =
1811 "namespace Foo { @EntryPoint() function Main() : Unit { let x = 5 / 0 + (2 ^ 4); } }";
1812 let fs = FsNode::Dir(
1813 [dir(
1814 "project",
1815 [
1816 file(
1817 "qsharp.json",
1818 r#"{ "lints": [{ "lint": "divisionByZero", "level": "allow" }] }"#,
1819 ),
1820 dir("src", [file("this_file.qs", this_file_qs)]),
1821 ],
1822 )]
1823 .into_iter()
1824 .collect(),
1825 );
1826
1827 let fs = Rc::new(RefCell::new(fs));
1828 let received_errors = RefCell::new(Vec::new());
1829 let test_cases = RefCell::new(Vec::new());
1830 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
1831 updater.update_configuration(WorkspaceConfigurationUpdate {
1832 lints_config: Some(vec![LintOrGroupConfig::Lint(LintConfig {
1833 kind: LintKind::Ast(AstLint::DivisionByZero),
1834 level: LintLevel::Warn,
1835 })]),
1836 ..WorkspaceConfigurationUpdate::default()
1837 });
1838
1839 // Trigger a document update.
1840 updater
1841 .update_document("project/src/this_file.qs", 1, this_file_qs, "qsharp")
1842 .await;
1843
1844 // No lints expected ("allow" wins over "warn")
1845 assert_eq!(received_errors.borrow().len(), 0);
1846}
1847
1848#[tokio::test]
1849async fn missing_dependency_reported() {
1850 let fs = FsNode::Dir(
1851 [dir(
1852 "parent",
1853 [
1854 file(
1855 "qsharp.json",
1856 r#"{ "dependencies" : { "MyDep" : { "path" : "../child" } } }"#,
1857 ),
1858 dir("src", [file("main.qs", "function Main() : Unit {}")]),
1859 ],
1860 )]
1861 .into_iter()
1862 .collect(),
1863 );
1864
1865 let fs = Rc::new(RefCell::new(fs));
1866 let received_errors = RefCell::new(Vec::new());
1867 let test_cases = RefCell::new(Vec::new());
1868 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
1869
1870 // Trigger a document update.
1871 updater
1872 .update_document(
1873 "parent/src/main.qs",
1874 1,
1875 "function Main() : Unit {}",
1876 "qsharp",
1877 )
1878 .await;
1879
1880 expect_errors(
1881 &received_errors,
1882 &expect![[r#"
1883 [
1884 uri: "parent/qsharp.json" version: None errors: [
1885 File system error: child/qsharp.json: file not found
1886 ],
1887 ]"#]],
1888 );
1889}
1890
1891#[tokio::test]
1892async fn error_from_dependency_reported() {
1893 let fs = FsNode::Dir(
1894 [
1895 dir(
1896 "parent",
1897 [
1898 file(
1899 "qsharp.json",
1900 r#"{ "dependencies" : { "MyDep" : { "path" : "../child" } } }"#,
1901 ),
1902 dir("src", [file("main.qs", "function Main() : Unit {}")]),
1903 ],
1904 ),
1905 dir(
1906 "child",
1907 [
1908 file("qsharp.json", "{}"),
1909 dir("src", [file("main.qs", "broken_syntax")]),
1910 ],
1911 ),
1912 ]
1913 .into_iter()
1914 .collect(),
1915 );
1916
1917 let fs = Rc::new(RefCell::new(fs));
1918 let received_errors = RefCell::new(Vec::new());
1919 let test_cases = RefCell::new(Vec::new());
1920 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
1921
1922 // Trigger a document update.
1923 updater
1924 .update_document(
1925 "parent/src/main.qs",
1926 1,
1927 "function Main() : Unit {}",
1928 "qsharp",
1929 )
1930 .await;
1931
1932 expect_errors(
1933 &received_errors,
1934 &expect![[r#"
1935 [
1936 uri: "child/src/main.qs" version: None errors: [
1937 syntax error
1938 [child/src/main.qs] [broken_syntax]
1939 ],
1940 ]"#]],
1941 );
1942}
1943
1944#[tokio::test]
1945async fn single_github_source_no_errors() {
1946 let received_errors = RefCell::new(Vec::new());
1947 let test_cases = RefCell::new(Vec::new());
1948 let mut updater = new_updater(&received_errors, &test_cases);
1949
1950 updater
1951 .update_document(
1952 "qsharp-github-source:foo/bar/Main.qs",
1953 1,
1954 "badsyntax",
1955 "qsharp",
1956 )
1957 .await;
1958
1959 updater
1960 .update_document("/foo/bar/Main.qs", 1, "badsyntax", "qsharp")
1961 .await;
1962
1963 // Same error exists in both files, but the github one should not be reported
1964
1965 check_state_and_errors(
1966 &updater,
1967 &received_errors,
1968 &expect![[r#"
1969 {
1970 "qsharp-github-source:foo/bar/Main.qs": OpenDocument {
1971 version: 1,
1972 compilation: "qsharp-github-source:foo/bar/Main.qs",
1973 latest_str_content: "badsyntax",
1974 },
1975 "/foo/bar/Main.qs": OpenDocument {
1976 version: 1,
1977 compilation: "/foo/bar/Main.qs",
1978 latest_str_content: "badsyntax",
1979 },
1980 }
1981 "#]],
1982 &expect![[r#"
1983 qsharp-github-source:foo/bar/Main.qs: [
1984 "qsharp-github-source:foo/bar/Main.qs": "badsyntax",
1985 ],
1986 /foo/bar/Main.qs: [
1987 "/foo/bar/Main.qs": "badsyntax",
1988 ],
1989 "#]],
1990 &expect![[r#"
1991 [
1992 uri: "/foo/bar/Main.qs" version: Some(1) errors: [
1993 syntax error
1994 [/foo/bar/Main.qs] [badsyntax]
1995 ],
1996 ]"#]],
1997 );
1998}
1999
2000#[tokio::test]
2001async fn test_case_detected() {
2002 let fs = FsNode::Dir(
2003 [dir(
2004 "parent",
2005 [
2006 file("qsharp.json", r#"{}"#),
2007 dir("src", [file("main.qs", "function MyTestCase() : Unit {}")]),
2008 ],
2009 )]
2010 .into_iter()
2011 .collect(),
2012 );
2013
2014 let fs = Rc::new(RefCell::new(fs));
2015 let received_errors = RefCell::new(Vec::new());
2016 let test_cases = RefCell::new(Vec::new());
2017 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
2018
2019 // Trigger a document update.
2020 updater
2021 .update_document(
2022 "parent/src/main.qs",
2023 1,
2024 "@Test() function MyTestCase() : Unit {}",
2025 "qsharp",
2026 )
2027 .await;
2028
2029 expect![[r#"
2030 [
2031 TestCallables {
2032 callables: [
2033 TestCallable {
2034 callable_name: "main.MyTestCase",
2035 compilation_uri: "parent/qsharp.json",
2036 location: Location {
2037 source: "parent/src/main.qs",
2038 range: Range {
2039 start: Position {
2040 line: 0,
2041 column: 17,
2042 },
2043 end: Position {
2044 line: 0,
2045 column: 27,
2046 },
2047 },
2048 },
2049 friendly_name: "parent",
2050 },
2051 ],
2052 },
2053 ]
2054 "#]]
2055 .assert_debug_eq(&test_cases.borrow());
2056}
2057
2058#[tokio::test]
2059async fn test_case_removed() {
2060 let fs = FsNode::Dir(
2061 [dir(
2062 "parent",
2063 [
2064 file("qsharp.json", r#"{}"#),
2065 dir(
2066 "src",
2067 [file("main.qs", "@Test() function MyTestCase() : Unit {}")],
2068 ),
2069 ],
2070 )]
2071 .into_iter()
2072 .collect(),
2073 );
2074
2075 let fs = Rc::new(RefCell::new(fs));
2076 let received_errors = RefCell::new(Vec::new());
2077 let test_cases = RefCell::new(Vec::new());
2078 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
2079
2080 // Trigger a document update.
2081 updater
2082 .update_document(
2083 "parent/src/main.qs",
2084 1,
2085 "function MyTestCase() : Unit {}",
2086 "qsharp",
2087 )
2088 .await;
2089
2090 expect![[r#"
2091 [
2092 TestCallables {
2093 callables: [],
2094 },
2095 ]
2096 "#]]
2097 .assert_debug_eq(&test_cases.borrow());
2098}
2099
2100#[tokio::test]
2101async fn test_case_modified() {
2102 let fs = FsNode::Dir(
2103 [dir(
2104 "parent",
2105 [
2106 file("qsharp.json", r#"{}"#),
2107 dir(
2108 "src",
2109 [file("main.qs", "@Test() function MyTestCase() : Unit {}")],
2110 ),
2111 ],
2112 )]
2113 .into_iter()
2114 .collect(),
2115 );
2116
2117 let fs = Rc::new(RefCell::new(fs));
2118 let received_errors = RefCell::new(Vec::new());
2119 let test_cases = RefCell::new(Vec::new());
2120 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
2121
2122 // Trigger a document update.
2123 updater
2124 .update_document(
2125 "parent/src/main.qs",
2126 1,
2127 "@Test() function MyTestCase() : Unit {}",
2128 "qsharp",
2129 )
2130 .await;
2131
2132 updater
2133 .update_document(
2134 "parent/src/main.qs",
2135 2,
2136 "@Test() function MyTestCase2() : Unit { }",
2137 "qsharp",
2138 )
2139 .await;
2140
2141 expect![[r#"
2142 [
2143 TestCallables {
2144 callables: [
2145 TestCallable {
2146 callable_name: "main.MyTestCase",
2147 compilation_uri: "parent/qsharp.json",
2148 location: Location {
2149 source: "parent/src/main.qs",
2150 range: Range {
2151 start: Position {
2152 line: 0,
2153 column: 17,
2154 },
2155 end: Position {
2156 line: 0,
2157 column: 27,
2158 },
2159 },
2160 },
2161 friendly_name: "parent",
2162 },
2163 ],
2164 },
2165 TestCallables {
2166 callables: [
2167 TestCallable {
2168 callable_name: "main.MyTestCase2",
2169 compilation_uri: "parent/qsharp.json",
2170 location: Location {
2171 source: "parent/src/main.qs",
2172 range: Range {
2173 start: Position {
2174 line: 0,
2175 column: 17,
2176 },
2177 end: Position {
2178 line: 0,
2179 column: 28,
2180 },
2181 },
2182 },
2183 friendly_name: "parent",
2184 },
2185 ],
2186 },
2187 ]
2188 "#]]
2189 .assert_debug_eq(&test_cases.borrow());
2190}
2191
2192#[tokio::test]
2193async fn test_annotation_removed() {
2194 let fs = FsNode::Dir(
2195 [dir(
2196 "parent",
2197 [
2198 file("qsharp.json", r#"{}"#),
2199 dir(
2200 "src",
2201 [file("main.qs", "@Test() function MyTestCase() : Unit {}")],
2202 ),
2203 ],
2204 )]
2205 .into_iter()
2206 .collect(),
2207 );
2208
2209 let fs = Rc::new(RefCell::new(fs));
2210 let received_errors = RefCell::new(Vec::new());
2211 let test_cases = RefCell::new(Vec::new());
2212 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
2213
2214 // Trigger a document update.
2215 updater
2216 .update_document(
2217 "parent/src/main.qs",
2218 1,
2219 "@Test() function MyTestCase() : Unit {}",
2220 "qsharp",
2221 )
2222 .await;
2223
2224 updater
2225 .update_document(
2226 "parent/src/main.qs",
2227 2,
2228 "function MyTestCase() : Unit {}",
2229 "qsharp",
2230 )
2231 .await;
2232
2233 expect![[r#"
2234 [
2235 TestCallables {
2236 callables: [
2237 TestCallable {
2238 callable_name: "main.MyTestCase",
2239 compilation_uri: "parent/qsharp.json",
2240 location: Location {
2241 source: "parent/src/main.qs",
2242 range: Range {
2243 start: Position {
2244 line: 0,
2245 column: 17,
2246 },
2247 end: Position {
2248 line: 0,
2249 column: 27,
2250 },
2251 },
2252 },
2253 friendly_name: "parent",
2254 },
2255 ],
2256 },
2257 TestCallables {
2258 callables: [],
2259 },
2260 ]
2261 "#]]
2262 .assert_debug_eq(&test_cases.borrow());
2263}
2264
2265#[tokio::test]
2266async fn multiple_tests() {
2267 let fs = FsNode::Dir(
2268 [dir(
2269 "parent",
2270 [
2271 file("qsharp.json", r#"{}"#),
2272 dir(
2273 "src",
2274 [file(
2275 "main.qs",
2276 "@Test() function Test1() : Unit {} @Test() function Test2() : Unit {}",
2277 )],
2278 ),
2279 ],
2280 )]
2281 .into_iter()
2282 .collect(),
2283 );
2284
2285 let fs = Rc::new(RefCell::new(fs));
2286 let received_errors = RefCell::new(Vec::new());
2287 let test_cases = RefCell::new(Vec::new());
2288 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
2289
2290 // Trigger a document update.
2291 updater
2292 .update_document(
2293 "parent/src/main.qs",
2294 1,
2295 "@Test() function Test1() : Unit {} @Test() function Test2() : Unit {}",
2296 "qsharp",
2297 )
2298 .await;
2299
2300 expect![[r#"
2301 [
2302 TestCallables {
2303 callables: [
2304 TestCallable {
2305 callable_name: "main.Test1",
2306 compilation_uri: "parent/qsharp.json",
2307 location: Location {
2308 source: "parent/src/main.qs",
2309 range: Range {
2310 start: Position {
2311 line: 0,
2312 column: 17,
2313 },
2314 end: Position {
2315 line: 0,
2316 column: 22,
2317 },
2318 },
2319 },
2320 friendly_name: "parent",
2321 },
2322 TestCallable {
2323 callable_name: "main.Test2",
2324 compilation_uri: "parent/qsharp.json",
2325 location: Location {
2326 source: "parent/src/main.qs",
2327 range: Range {
2328 start: Position {
2329 line: 0,
2330 column: 52,
2331 },
2332 end: Position {
2333 line: 0,
2334 column: 57,
2335 },
2336 },
2337 },
2338 friendly_name: "parent",
2339 },
2340 ],
2341 },
2342 ]
2343 "#]]
2344 .assert_debug_eq(&test_cases.borrow());
2345}
2346
2347#[tokio::test]
2348async fn test_case_in_different_files() {
2349 let fs = FsNode::Dir(
2350 [dir(
2351 "parent",
2352 [
2353 file("qsharp.json", r#"{}"#),
2354 dir(
2355 "src",
2356 [
2357 file("test1.qs", "@Test() function Test1() : Unit {}"),
2358 file("test2.qs", "@Test() function Test2() : Unit {}"),
2359 ],
2360 ),
2361 ],
2362 )]
2363 .into_iter()
2364 .collect(),
2365 );
2366
2367 let fs = Rc::new(RefCell::new(fs));
2368 let received_errors = RefCell::new(Vec::new());
2369 let test_cases = RefCell::new(Vec::new());
2370 let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs);
2371
2372 // Trigger a document update for the first test file.
2373 updater
2374 .update_document(
2375 "parent/src/test1.qs",
2376 1,
2377 "@Test() function Test1() : Unit {}",
2378 "qsharp",
2379 )
2380 .await;
2381
2382 expect![[r#"
2383 [
2384 TestCallables {
2385 callables: [
2386 TestCallable {
2387 callable_name: "test1.Test1",
2388 compilation_uri: "parent/qsharp.json",
2389 location: Location {
2390 source: "parent/src/test1.qs",
2391 range: Range {
2392 start: Position {
2393 line: 0,
2394 column: 17,
2395 },
2396 end: Position {
2397 line: 0,
2398 column: 22,
2399 },
2400 },
2401 },
2402 friendly_name: "parent",
2403 },
2404 TestCallable {
2405 callable_name: "test2.Test2",
2406 compilation_uri: "parent/qsharp.json",
2407 location: Location {
2408 source: "parent/src/test2.qs",
2409 range: Range {
2410 start: Position {
2411 line: 0,
2412 column: 17,
2413 },
2414 end: Position {
2415 line: 0,
2416 column: 22,
2417 },
2418 },
2419 },
2420 friendly_name: "parent",
2421 },
2422 ],
2423 },
2424 ]
2425 "#]]
2426 .assert_debug_eq(&test_cases.borrow());
2427}
2428
2429#[tokio::test]
2430async fn test_dev_diagnostics_configuration() {
2431 let errors = RefCell::new(Vec::new());
2432 let test_cases = RefCell::new(Vec::new());
2433 let mut updater = new_updater(&errors, &test_cases);
2434
2435 // First, enable test diagnostics before updating any document
2436 updater.update_configuration(WorkspaceConfigurationUpdate {
2437 dev_diagnostics: Some(true),
2438 ..WorkspaceConfigurationUpdate::default()
2439 });
2440
2441 // Now update a document with test diagnostics enabled
2442 updater
2443 .update_document(
2444 "test/sample.qs",
2445 1,
2446 "namespace Test { @EntryPoint() operation Main() : Unit {} }",
2447 "qsharp",
2448 )
2449 .await;
2450
2451 // Should have test diagnostic
2452 expect_errors(
2453 &errors,
2454 &expect![[r#"
2455 [
2456 uri: "test/sample.qs" version: Some(1) errors: [
2457 [qdk-status] compilation=test/sample.qs, version=1
2458 ],
2459 ]"#]],
2460 );
2461
2462 // Clear errors and disable test diagnostics
2463 updater.update_configuration(WorkspaceConfigurationUpdate {
2464 dev_diagnostics: Some(false),
2465 ..WorkspaceConfigurationUpdate::default()
2466 });
2467
2468 // Should have no diagnostics after disabling
2469 expect_errors(
2470 &errors,
2471 &expect![[r#"
2472 [
2473 uri: "test/sample.qs" version: Some(1) errors: [],
2474 ]"#]],
2475 );
2476}
2477
2478#[tokio::test]
2479async fn test_show_test_diagnostics_with_real_errors() {
2480 let errors = RefCell::new(Vec::new());
2481 let test_cases = RefCell::new(Vec::new());
2482 let mut updater = new_updater(&errors, &test_cases);
2483
2484 // Enable test diagnostics
2485 updater.update_configuration(WorkspaceConfigurationUpdate {
2486 dev_diagnostics: Some(true),
2487 ..WorkspaceConfigurationUpdate::default()
2488 });
2489
2490 // Update document with syntax error
2491 updater
2492 .update_document(
2493 "test/error.qs",
2494 1,
2495 "namespace Test { operation Main() : Unit { invalidcode } }",
2496 "qsharp",
2497 )
2498 .await;
2499
2500 // Should have diagnostics with test diagnostic first, followed by actual errors
2501 expect_errors(
2502 &errors,
2503 &expect![[r#"
2504 [
2505 uri: "test/error.qs" version: Some(1) errors: [
2506 name error
2507 [test/error.qs] [invalidcode]
2508 [qdk-status] compilation=test/error.qs, version=1
2509 ],
2510 ]"#]],
2511 );
2512}
2513
2514#[tokio::test]
2515async fn test_show_test_diagnostics_multi_file_project() {
2516 let errors = RefCell::new(Vec::new());
2517 let test_cases = RefCell::new(Vec::new());
2518
2519 // Create a multi-file project structure
2520 let fs = FsNode::Dir(
2521 [dir(
2522 "project",
2523 [
2524 file("qsharp.json", r#"{ }"#),
2525 dir(
2526 "src",
2527 [
2528 file(
2529 "main.qs",
2530 "namespace Main { @EntryPoint() operation Main() : Unit {} }",
2531 ),
2532 file(
2533 "helper.qs",
2534 "namespace Helper { operation HelperOp() : Unit {} }",
2535 ),
2536 ],
2537 ),
2538 ],
2539 )]
2540 .into_iter()
2541 .collect(),
2542 );
2543
2544 let fs = Rc::new(RefCell::new(fs));
2545 let mut updater = new_updater_with_file_system(&errors, &test_cases, &fs);
2546
2547 // Enable test diagnostics
2548 updater.update_configuration(WorkspaceConfigurationUpdate {
2549 dev_diagnostics: Some(true),
2550 ..WorkspaceConfigurationUpdate::default()
2551 });
2552
2553 // Open both files in the project
2554 updater
2555 .update_document(
2556 "project/src/main.qs",
2557 1,
2558 "namespace Main { @EntryPoint() operation Main() : Unit {} }",
2559 "qsharp",
2560 )
2561 .await;
2562
2563 updater
2564 .update_document(
2565 "project/src/helper.qs",
2566 1,
2567 "namespace Helper { operation HelperOp() : Unit {} }",
2568 "qsharp",
2569 )
2570 .await;
2571
2572 // Both files should have test diagnostics with the same compilation name
2573 expect_errors(
2574 &errors,
2575 &expect![[r#"
2576 [
2577 uri: "project/src/main.qs" version: Some(1) errors: [
2578 [qdk-status] compilation=project/qsharp.json, version=1
2579 ],
2580
2581 uri: "project/src/main.qs" version: Some(1) errors: [
2582 [qdk-status] compilation=project/qsharp.json, version=1
2583 ],
2584
2585 uri: "project/src/helper.qs" version: Some(1) errors: [
2586 [qdk-status] compilation=project/qsharp.json, version=1
2587 ],
2588 ]"#]],
2589 );
2590
2591 // Close one of the files
2592 updater
2593 .close_document("project/src/main.qs", "qsharp")
2594 .await;
2595
2596 // The remaining file should still have the test diagnostic
2597 expect_errors(
2598 &errors,
2599 &expect![[r#"
2600 [
2601 uri: "project/src/helper.qs" version: Some(1) errors: [
2602 [qdk-status] compilation=project/qsharp.json, version=1
2603 ],
2604
2605 uri: "project/src/main.qs" version: None errors: [],
2606 ]"#]],
2607 );
2608}
2609
2610#[tokio::test]
2611async fn test_show_test_diagnostics_in_notebook() {
2612 let errors = RefCell::new(Vec::new());
2613 let test_cases = RefCell::new(Vec::new());
2614 let mut updater = new_updater(&errors, &test_cases);
2615
2616 // First, enable test diagnostics before updating any notebook document
2617 updater.update_configuration(WorkspaceConfigurationUpdate {
2618 dev_diagnostics: Some(true),
2619 ..WorkspaceConfigurationUpdate::default()
2620 });
2621
2622 // Now update a notebook document with test diagnostics enabled
2623 updater
2624 .update_notebook_document(
2625 "notebook.ipynb",
2626 &NotebookMetadata::default(),
2627 [
2628 ("cell1", 1, "operation Main() : Unit {}"),
2629 ("cell2", 1, "Main()"),
2630 ]
2631 .into_iter(),
2632 )
2633 .await;
2634
2635 // Should have test diagnostics for both cells
2636 expect_errors(
2637 &errors,
2638 &expect![[r#"
2639 [
2640 uri: "cell1" version: Some(1) errors: [
2641 [qdk-status] compilation=notebook.ipynb, version=1
2642 ],
2643
2644 uri: "cell2" version: Some(1) errors: [
2645 [qdk-status] compilation=notebook.ipynb, version=1
2646 ],
2647 ]"#]],
2648 );
2649}
2650
2651/// Standalone helper to update a field in a manifest JSON file in the virtual file system.
2652/// `fs` is the root `FsNode`, `manifest_path` is the path to the manifest (e.g., "project/qsharp.json").
2653/// `field` is the key to update, and `value` is the new value (as a `serde_json::Value`).
2654/// Returns true if the update was successful.
2655fn update_manifest_field(
2656 fs: &Rc<RefCell<FsNode>>,
2657 manifest_path: &str,
2658 field: &str,
2659 value: serde_json::Value,
2660) -> bool {
2661 let mut fs = fs.borrow_mut();
2662 let components: Vec<&str> = manifest_path.split('/').collect();
2663 let mut node = &mut *fs;
2664 for (i, comp) in components.iter().enumerate() {
2665 match node {
2666 crate::tests::test_fs::FsNode::Dir(entries) => {
2667 if let Some(next) = entries.get_mut(*comp) {
2668 node = next;
2669 } else {
2670 return false;
2671 }
2672 }
2673 crate::tests::test_fs::FsNode::File(_) => {
2674 if i == components.len() - 1 {
2675 break;
2676 }
2677 return false;
2678 }
2679 }
2680 }
2681 if let crate::tests::test_fs::FsNode::File(contents) = node {
2682 let mut json: serde_json::Value = match serde_json::from_str(&*contents) {
2683 Ok(j) => j,
2684 Err(_) => return false,
2685 };
2686 if let Some(obj) = json.as_object_mut() {
2687 obj.insert(field.to_string(), value);
2688 } else {
2689 return false;
2690 }
2691 let Ok(new_contents) = serde_json::to_string_pretty(&json) else {
2692 return false;
2693 };
2694 *contents = new_contents.into();
2695 true
2696 } else {
2697 false
2698 }
2699}
2700
2701impl Display for DiagnosticUpdate {
2702 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2703 let DiagnosticUpdate {
2704 uri,
2705 version,
2706 errors,
2707 } = self;
2708
2709 write!(f, "uri: {uri:?} version: {version:?} errors: [")?;
2710 // Formatting loosely taken from compiler/qsc/src/interpret/tests.rs
2711 for error in errors {
2712 write!(f, "\n {error}")?;
2713 for label in error.labels().into_iter().flatten() {
2714 let span = error
2715 .source_code()
2716 .expect("expected valid source code")
2717 .read_span(label.inner(), 0, 0)
2718 .expect("expected to be able to read span");
2719
2720 write!(
2721 f,
2722 "\n {} [{}] [{}]",
2723 label.label().unwrap_or(""),
2724 span.name().expect("expected source file name"),
2725 from_utf8(span.data()).expect("expected valid utf-8 string"),
2726 )?;
2727 }
2728 }
2729 if !errors.is_empty() {
2730 write!(f, "\n ")?;
2731 }
2732 writeln!(f, "],")?;
2733
2734 Ok(())
2735 }
2736}
2737
2738fn new_updater<'a>(
2739 received_errors: &'a RefCell<Vec<DiagnosticUpdate>>,
2740 received_test_cases: &'a RefCell<Vec<TestCallables>>,
2741) -> CompilationStateUpdater<'a> {
2742 let diagnostic_receiver = move |update: DiagnosticUpdate| {
2743 let mut v = received_errors.borrow_mut();
2744 v.push(update);
2745 };
2746
2747 let test_callable_receiver = move |update: TestCallables| {
2748 let mut v = received_test_cases.borrow_mut();
2749 v.push(update);
2750 };
2751
2752 CompilationStateUpdater::new(
2753 Rc::new(RefCell::new(CompilationState::default())),
2754 diagnostic_receiver,
2755 test_callable_receiver,
2756 TestProjectHost {
2757 fs: TEST_FS.with(Clone::clone),
2758 },
2759 Encoding::Utf8,
2760 )
2761}
2762
2763fn new_updater_with_file_system<'a>(
2764 received_errors: &'a RefCell<Vec<DiagnosticUpdate>>,
2765 received_test_cases: &'a RefCell<Vec<TestCallables>>,
2766 fs: &Rc<RefCell<FsNode>>,
2767) -> CompilationStateUpdater<'a> {
2768 let diagnostic_receiver = move |update: DiagnosticUpdate| {
2769 let mut v = received_errors.borrow_mut();
2770 v.push(update);
2771 };
2772
2773 let test_callable_receiver = move |update: TestCallables| {
2774 let mut v = received_test_cases.borrow_mut();
2775 v.push(update);
2776 };
2777
2778 CompilationStateUpdater::new(
2779 Rc::new(RefCell::new(CompilationState::default())),
2780 diagnostic_receiver,
2781 test_callable_receiver,
2782 TestProjectHost { fs: fs.clone() },
2783 Encoding::Utf8,
2784 )
2785}
2786
2787fn expect_errors(updates: &RefCell<Vec<DiagnosticUpdate>>, expected: &Expect) {
2788 let mut buf = String::new();
2789 let _ = buf.write_str("[");
2790 for update in updates.borrow().iter() {
2791 let _ = write!(buf, "\n {update}");
2792 }
2793 let _ = buf.write_str("]");
2794
2795 expected.assert_eq(&buf);
2796
2797 // reset accumulated errors after each check
2798 updates.borrow_mut().clear();
2799}
2800
2801fn assert_compilation_sources(updater: &CompilationStateUpdater<'_>, expected: &Expect) {
2802 let state = updater.state.try_borrow().expect("borrow should succeed");
2803
2804 let compilation_sources =
2805 state
2806 .compilations
2807 .iter()
2808 .fold(String::new(), |mut output, (name, compilation)| {
2809 let _ = writeln!(output, "{name}: [");
2810 for source in compilation.0.user_unit().sources.iter() {
2811 let _ = writeln!(output, " {:?}: {:?},", source.name, source.contents);
2812 }
2813 let _ = writeln!(output, "],");
2814 output
2815 });
2816 expected.assert_eq(&compilation_sources);
2817}
2818
2819fn assert_open_documents(updater: &CompilationStateUpdater<'_>, expected: &Expect) {
2820 let state = updater.state.try_borrow().expect("borrow should succeed");
2821 expected.assert_debug_eq(&state.open_documents);
2822}
2823
2824fn check_state_and_errors(
2825 updater: &CompilationStateUpdater<'_>,
2826 received_diag_updates: &RefCell<Vec<DiagnosticUpdate>>,
2827 expected_open_documents: &Expect,
2828 expected_compilation_sources: &Expect,
2829 expected_errors: &Expect,
2830) {
2831 assert_open_documents(updater, expected_open_documents);
2832 assert_compilation_sources(updater, expected_compilation_sources);
2833 expect_errors(received_diag_updates, expected_errors);
2834}
2835
2836fn check_state(
2837 updater: &CompilationStateUpdater<'_>,
2838 expected_open_documents: &Expect,
2839 expected_compilation_sources: &Expect,
2840) {
2841 assert_open_documents(updater, expected_open_documents);
2842 assert_compilation_sources(updater, expected_compilation_sources);
2843}
2844
2845/// Checks that the lints config is being loaded from the qsharp.json manifest
2846async fn check_lints_config(updater: &CompilationStateUpdater<'_>, expected_config: &Expect) {
2847 let manifest = updater
2848 .load_manifest(&"project/src/this_file.qs".into())
2849 .await
2850 .expect("manifest should load successfully")
2851 .expect("manifest should exist");
2852
2853 let lints_config = manifest.lints;
2854
2855 expected_config.assert_eq(&format!("{lints_config:#?}"));
2856}
2857
2858thread_local! { static TEST_FS: Rc<RefCell<FsNode>> = Rc::new(RefCell::new(test_fs()))}
2859
2860fn test_fs() -> FsNode {
2861 FsNode::Dir(
2862 [
2863 dir(
2864 "project",
2865 [
2866 file("qsharp.json", "{}"),
2867 dir(
2868 "src",
2869 [
2870 file(
2871 "other_file.qs",
2872 "// DISK CONTENTS\n namespace OtherFile { operation Other() : Unit { } }",
2873 ),
2874 file("this_file.qs", "// DISK CONTENTS\n namespace Foo { }"),
2875 ],
2876 ),
2877 ],
2878 ),
2879 dir(
2880 "nested_projects",
2881 [
2882 file("qsharp.json", "{}"),
2883 dir(
2884 "src",
2885 [dir(
2886 "subdir",
2887 [
2888 file("qsharp.json", "{}"),
2889 dir(
2890 "src",
2891 [
2892 file("a.qs", "namespace A {}"),
2893 file("b.qs", "namespace B {}"),
2894 ],
2895 ),
2896 ],
2897 )],
2898 ),
2899 ],
2900 ),
2901 dir(
2902 "openqasm_files",
2903 [
2904 file(
2905 "self-contained.qasm",
2906 "include \"stdgates.inc\";\nqubit q;\nreset q;\nx q;\nh q;\nbit c = measure q;\n",
2907 ),
2908 file("multifile.qasm", "include \"stdgates.inc\";\ninclude \"imports.inc\";\nBar();\nBar();\nqubit q;\nh q;\nreset q;\n"),
2909 file("imports.inc", "\ndef Bar() {\n\nint c = 42;\n}\n"),
2910 ],
2911 ),
2912 ]
2913 .into_iter()
2914 .collect(),
2915 )
2916}
2917