microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.23.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/language_service/src/state/tests.rs

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