microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
4fa10c30a716d7fc2f67a0a385f3da565daa1237

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/language_service/src/state/tests.rs

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