1use std::sync::Arc;
4
5use itertools::{EitherOrBoth, Itertools};
6use tokio::sync::RwLock;
7
8use crate::{
9 ExecOutcome, ExecutorContext,
10 execution::{
11 EnvironmentRef, ExecutorSettings, annotations,
12 memory::Stack,
13 state::{self as exec_state, ModuleInfoMap},
14 },
15 parsing::ast::types::{Annotation, Node, Program},
16 walk::Node as WalkNode,
17};
18
19lazy_static::lazy_static! {
20 static ref OLD_AST: Arc<RwLock<Option<GlobalState>>> = Default::default();
22 static ref PREV_MEMORY: Arc<RwLock<Option<(Stack, ModuleInfoMap)>>> = Default::default();
24}
25
26pub(super) async fn read_old_ast() -> Option<GlobalState> {
28 let old_ast = OLD_AST.read().await;
29 old_ast.clone()
30}
31
32pub(super) async fn write_old_ast(old_state: GlobalState) {
33 let mut old_ast = OLD_AST.write().await;
34 *old_ast = Some(old_state);
35}
36
37pub(crate) async fn read_old_memory() -> Option<(Stack, ModuleInfoMap)> {
38 let old_mem = PREV_MEMORY.read().await;
39 old_mem.clone()
40}
41
42pub(crate) async fn write_old_memory(mem: (Stack, ModuleInfoMap)) {
43 let mut old_mem = PREV_MEMORY.write().await;
44 *old_mem = Some(mem);
45}
46
47pub async fn bust_cache() {
48 let mut old_ast = OLD_AST.write().await;
49 *old_ast = None;
50}
51
52pub async fn clear_mem_cache() {
53 let mut old_mem = PREV_MEMORY.write().await;
54 *old_mem = None;
55}
56
57#[derive(Debug, Clone)]
59pub struct CacheInformation<'a> {
60 pub ast: &'a Node<Program>,
61 pub settings: &'a ExecutorSettings,
62}
63
64#[derive(Debug, Clone)]
66pub(super) struct GlobalState {
67 pub(super) main: ModuleState,
68 pub(super) exec_state: exec_state::GlobalState,
70 pub(super) settings: ExecutorSettings,
72}
73
74impl GlobalState {
75 pub fn new(
76 state: exec_state::ExecState,
77 settings: ExecutorSettings,
78 ast: Node<Program>,
79 result_env: EnvironmentRef,
80 ) -> Self {
81 Self {
82 main: ModuleState {
83 ast,
84 exec_state: state.mod_local,
85 result_env,
86 },
87 exec_state: state.global,
88 settings,
89 }
90 }
91
92 pub fn with_settings(mut self, settings: ExecutorSettings) -> GlobalState {
93 self.settings = settings;
94 self
95 }
96
97 pub fn reconstitute_exec_state(&self) -> exec_state::ExecState {
98 exec_state::ExecState {
99 global: self.exec_state.clone(),
100 mod_local: self.main.exec_state.clone(),
101 }
102 }
103
104 pub async fn into_exec_outcome(self, ctx: &ExecutorContext) -> ExecOutcome {
105 ExecOutcome {
108 variables: self.main.exec_state.variables(self.main.result_env),
109 filenames: self.exec_state.filenames(),
110 #[cfg(feature = "artifact-graph")]
111 operations: self.exec_state.root_module_artifacts.operations,
112 #[cfg(feature = "artifact-graph")]
113 artifact_graph: self.exec_state.artifacts.graph,
114 errors: self.exec_state.errors,
115 default_planes: ctx.engine.get_default_planes().read().await.clone(),
116 }
117 }
118}
119
120#[derive(Debug, Clone)]
122pub(super) struct ModuleState {
123 pub(super) ast: Node<Program>,
125 pub(super) exec_state: exec_state::ModuleState,
127 pub(super) result_env: EnvironmentRef,
129}
130
131#[derive(Debug, Clone, PartialEq)]
133#[allow(clippy::large_enum_variant)]
134pub(super) enum CacheResult {
135 ReExecute {
136 clear_scene: bool,
138 reapply_settings: bool,
140 program: Node<Program>,
142 },
143 CheckImportsOnly {
147 reapply_settings: bool,
149 ast: Node<Program>,
151 },
152 NoAction(bool),
154}
155
156pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInformation<'_>) -> CacheResult {
164 let mut reapply_settings = false;
165
166 if old.settings != new.settings {
169 reapply_settings = true;
172 }
173
174 if old.ast == new.ast {
177 if !old.ast.has_import_statements() {
181 return CacheResult::NoAction(reapply_settings);
182 }
183
184 return CacheResult::CheckImportsOnly {
186 reapply_settings,
187 ast: old.ast.clone(),
188 };
189 }
190
191 let mut old_ast = old.ast.clone();
193 let mut new_ast = new.ast.clone();
194
195 old_ast.compute_digest();
198 new_ast.compute_digest();
199
200 if old_ast.digest == new_ast.digest {
202 if !old.ast.has_import_statements() {
206 return CacheResult::NoAction(reapply_settings);
207 }
208
209 return CacheResult::CheckImportsOnly {
211 reapply_settings,
212 ast: old.ast.clone(),
213 };
214 }
215
216 if !old_ast
218 .inner_attrs
219 .iter()
220 .filter(annotations::is_significant)
221 .zip_longest(new_ast.inner_attrs.iter().filter(annotations::is_significant))
222 .all(|pair| {
223 match pair {
224 EitherOrBoth::Both(old, new) => {
225 let Annotation { name, properties, .. } = &old.inner;
228 let Annotation {
229 name: new_name,
230 properties: new_properties,
231 ..
232 } = &new.inner;
233
234 name.as_ref().map(|n| n.digest) == new_name.as_ref().map(|n| n.digest)
235 && properties
236 .as_ref()
237 .map(|props| props.iter().map(|p| p.digest).collect::<Vec<_>>())
238 == new_properties
239 .as_ref()
240 .map(|props| props.iter().map(|p| p.digest).collect::<Vec<_>>())
241 }
242 _ => false,
243 }
244 })
245 {
246 return CacheResult::ReExecute {
250 clear_scene: true,
251 reapply_settings: true,
252 program: new.ast.clone(),
253 };
254 }
255
256 generate_changed_program(old_ast, new_ast, reapply_settings)
258}
259
260fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>, reapply_settings: bool) -> CacheResult {
272 if !old_ast.body.iter().zip(new_ast.body.iter()).all(|(old, new)| {
273 let old_node: WalkNode = old.into();
274 let new_node: WalkNode = new.into();
275 old_node.digest() == new_node.digest()
276 }) {
277 return CacheResult::ReExecute {
283 clear_scene: true,
284 reapply_settings,
285 program: new_ast,
286 };
287 }
288
289 match new_ast.body.len().cmp(&old_ast.body.len()) {
293 std::cmp::Ordering::Less => {
294 CacheResult::ReExecute {
303 clear_scene: true,
304 reapply_settings,
305 program: new_ast,
306 }
307 }
308 std::cmp::Ordering::Greater => {
309 new_ast.body = new_ast.body[old_ast.body.len()..].to_owned();
317
318 CacheResult::ReExecute {
319 clear_scene: false,
320 reapply_settings,
321 program: new_ast,
322 }
323 }
324 std::cmp::Ordering::Equal => {
325 CacheResult::NoAction(reapply_settings)
335 }
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use pretty_assertions::assert_eq;
342
343 use super::*;
344 use crate::execution::{ExecTestResults, parse_execute, parse_execute_with_project_dir};
345
346 #[tokio::test(flavor = "multi_thread")]
347 async fn test_get_changed_program_same_code() {
348 let new = r#"// Remove the end face for the extrusion.
349firstSketch = startSketchOn(XY)
350 |> startProfile(at = [-12, 12])
351 |> line(end = [24, 0])
352 |> line(end = [0, -24])
353 |> line(end = [-24, 0])
354 |> close()
355 |> extrude(length = 6)
356
357// Remove the end face for the extrusion.
358shell(firstSketch, faces = [END], thickness = 0.25)"#;
359
360 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(new).await.unwrap();
361
362 let result = get_changed_program(
363 CacheInformation {
364 ast: &program.ast,
365 settings: &exec_ctxt.settings,
366 },
367 CacheInformation {
368 ast: &program.ast,
369 settings: &exec_ctxt.settings,
370 },
371 )
372 .await;
373
374 assert_eq!(result, CacheResult::NoAction(false));
375 }
376
377 #[tokio::test(flavor = "multi_thread")]
378 async fn test_get_changed_program_same_code_changed_whitespace() {
379 let old = r#" // Remove the end face for the extrusion.
380firstSketch = startSketchOn(XY)
381 |> startProfile(at = [-12, 12])
382 |> line(end = [24, 0])
383 |> line(end = [0, -24])
384 |> line(end = [-24, 0])
385 |> close()
386 |> extrude(length = 6)
387
388// Remove the end face for the extrusion.
389shell(firstSketch, faces = [END], thickness = 0.25) "#;
390
391 let new = r#"// Remove the end face for the extrusion.
392firstSketch = startSketchOn(XY)
393 |> startProfile(at = [-12, 12])
394 |> line(end = [24, 0])
395 |> line(end = [0, -24])
396 |> line(end = [-24, 0])
397 |> close()
398 |> extrude(length = 6)
399
400// Remove the end face for the extrusion.
401shell(firstSketch, faces = [END], thickness = 0.25)"#;
402
403 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
404
405 let program_new = crate::Program::parse_no_errs(new).unwrap();
406
407 let result = get_changed_program(
408 CacheInformation {
409 ast: &program.ast,
410 settings: &exec_ctxt.settings,
411 },
412 CacheInformation {
413 ast: &program_new.ast,
414 settings: &exec_ctxt.settings,
415 },
416 )
417 .await;
418
419 assert_eq!(result, CacheResult::NoAction(false));
420 }
421
422 #[tokio::test(flavor = "multi_thread")]
423 async fn test_get_changed_program_same_code_changed_code_comment_start_of_program() {
424 let old = r#" // Removed the end face for the extrusion.
425firstSketch = startSketchOn(XY)
426 |> startProfile(at = [-12, 12])
427 |> line(end = [24, 0])
428 |> line(end = [0, -24])
429 |> line(end = [-24, 0])
430 |> close()
431 |> extrude(length = 6)
432
433// Remove the end face for the extrusion.
434shell(firstSketch, faces = [END], thickness = 0.25) "#;
435
436 let new = r#"// Remove the end face for the extrusion.
437firstSketch = startSketchOn(XY)
438 |> startProfile(at = [-12, 12])
439 |> line(end = [24, 0])
440 |> line(end = [0, -24])
441 |> line(end = [-24, 0])
442 |> close()
443 |> extrude(length = 6)
444
445// Remove the end face for the extrusion.
446shell(firstSketch, faces = [END], thickness = 0.25)"#;
447
448 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
449
450 let program_new = crate::Program::parse_no_errs(new).unwrap();
451
452 let result = get_changed_program(
453 CacheInformation {
454 ast: &program.ast,
455 settings: &exec_ctxt.settings,
456 },
457 CacheInformation {
458 ast: &program_new.ast,
459 settings: &exec_ctxt.settings,
460 },
461 )
462 .await;
463
464 assert_eq!(result, CacheResult::NoAction(false));
465 }
466
467 #[tokio::test(flavor = "multi_thread")]
468 async fn test_get_changed_program_same_code_changed_code_comments_attrs() {
469 let old = r#"@foo(whatever = whatever)
470@bar
471// Removed the end face for the extrusion.
472firstSketch = startSketchOn(XY)
473 |> startProfile(at = [-12, 12])
474 |> line(end = [24, 0])
475 |> line(end = [0, -24])
476 |> line(end = [-24, 0]) // my thing
477 |> close()
478 |> extrude(length = 6)
479
480// Remove the end face for the extrusion.
481shell(firstSketch, faces = [END], thickness = 0.25) "#;
482
483 let new = r#"@foo(whatever = 42)
484@baz
485// Remove the end face for the extrusion.
486firstSketch = startSketchOn(XY)
487 |> startProfile(at = [-12, 12])
488 |> line(end = [24, 0])
489 |> line(end = [0, -24])
490 |> line(end = [-24, 0])
491 |> close()
492 |> extrude(length = 6)
493
494// Remove the end face for the extrusion.
495shell(firstSketch, faces = [END], thickness = 0.25)"#;
496
497 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
498
499 let program_new = crate::Program::parse_no_errs(new).unwrap();
500
501 let result = get_changed_program(
502 CacheInformation {
503 ast: &program.ast,
504 settings: &exec_ctxt.settings,
505 },
506 CacheInformation {
507 ast: &program_new.ast,
508 settings: &exec_ctxt.settings,
509 },
510 )
511 .await;
512
513 assert_eq!(result, CacheResult::NoAction(false));
514 }
515
516 #[tokio::test(flavor = "multi_thread")]
518 async fn test_get_changed_program_same_code_but_different_grid_setting() {
519 let new = r#"// Remove the end face for the extrusion.
520firstSketch = startSketchOn(XY)
521 |> startProfile(at = [-12, 12])
522 |> line(end = [24, 0])
523 |> line(end = [0, -24])
524 |> line(end = [-24, 0])
525 |> close()
526 |> extrude(length = 6)
527
528// Remove the end face for the extrusion.
529shell(firstSketch, faces = [END], thickness = 0.25)"#;
530
531 let ExecTestResults {
532 program, mut exec_ctxt, ..
533 } = parse_execute(new).await.unwrap();
534
535 exec_ctxt.settings.show_grid = !exec_ctxt.settings.show_grid;
537
538 let result = get_changed_program(
539 CacheInformation {
540 ast: &program.ast,
541 settings: &Default::default(),
542 },
543 CacheInformation {
544 ast: &program.ast,
545 settings: &exec_ctxt.settings,
546 },
547 )
548 .await;
549
550 assert_eq!(result, CacheResult::NoAction(true));
551 }
552
553 #[tokio::test(flavor = "multi_thread")]
555 async fn test_get_changed_program_same_code_but_different_edge_visibility_setting() {
556 let new = r#"// Remove the end face for the extrusion.
557firstSketch = startSketchOn(XY)
558 |> startProfile(at = [-12, 12])
559 |> line(end = [24, 0])
560 |> line(end = [0, -24])
561 |> line(end = [-24, 0])
562 |> close()
563 |> extrude(length = 6)
564
565// Remove the end face for the extrusion.
566shell(firstSketch, faces = [END], thickness = 0.25)"#;
567
568 let ExecTestResults {
569 program, mut exec_ctxt, ..
570 } = parse_execute(new).await.unwrap();
571
572 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
574
575 let result = get_changed_program(
576 CacheInformation {
577 ast: &program.ast,
578 settings: &Default::default(),
579 },
580 CacheInformation {
581 ast: &program.ast,
582 settings: &exec_ctxt.settings,
583 },
584 )
585 .await;
586
587 assert_eq!(result, CacheResult::NoAction(true));
588
589 let old_settings = exec_ctxt.settings.clone();
591 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
592
593 let result = get_changed_program(
594 CacheInformation {
595 ast: &program.ast,
596 settings: &old_settings,
597 },
598 CacheInformation {
599 ast: &program.ast,
600 settings: &exec_ctxt.settings,
601 },
602 )
603 .await;
604
605 assert_eq!(result, CacheResult::NoAction(true));
606
607 let old_settings = exec_ctxt.settings.clone();
609 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
610
611 let result = get_changed_program(
612 CacheInformation {
613 ast: &program.ast,
614 settings: &old_settings,
615 },
616 CacheInformation {
617 ast: &program.ast,
618 settings: &exec_ctxt.settings,
619 },
620 )
621 .await;
622
623 assert_eq!(result, CacheResult::NoAction(true));
624 }
625
626 #[tokio::test(flavor = "multi_thread")]
629 async fn test_get_changed_program_same_code_but_different_unit_setting_using_annotation() {
630 let old_code = r#"@settings(defaultLengthUnit = in)
631startSketchOn(XY)
632"#;
633 let new_code = r#"@settings(defaultLengthUnit = mm)
634startSketchOn(XY)
635"#;
636
637 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
638
639 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
640 new_program.compute_digest();
641
642 let result = get_changed_program(
643 CacheInformation {
644 ast: &program.ast,
645 settings: &exec_ctxt.settings,
646 },
647 CacheInformation {
648 ast: &new_program.ast,
649 settings: &exec_ctxt.settings,
650 },
651 )
652 .await;
653
654 assert_eq!(
655 result,
656 CacheResult::ReExecute {
657 clear_scene: true,
658 reapply_settings: true,
659 program: new_program.ast,
660 }
661 );
662 }
663
664 #[tokio::test(flavor = "multi_thread")]
667 async fn test_get_changed_program_same_code_but_removed_unit_setting_using_annotation() {
668 let old_code = r#"@settings(defaultLengthUnit = in)
669startSketchOn(XY)
670"#;
671 let new_code = r#"
672startSketchOn(XY)
673"#;
674
675 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
676
677 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
678 new_program.compute_digest();
679
680 let result = get_changed_program(
681 CacheInformation {
682 ast: &program.ast,
683 settings: &exec_ctxt.settings,
684 },
685 CacheInformation {
686 ast: &new_program.ast,
687 settings: &exec_ctxt.settings,
688 },
689 )
690 .await;
691
692 assert_eq!(
693 result,
694 CacheResult::ReExecute {
695 clear_scene: true,
696 reapply_settings: true,
697 program: new_program.ast,
698 }
699 );
700 }
701
702 #[tokio::test(flavor = "multi_thread")]
703 async fn test_multi_file_no_changes_does_not_reexecute() {
704 let code = r#"import "toBeImported.kcl" as importedCube
705
706importedCube
707
708sketch001 = startSketchOn(XZ)
709profile001 = startProfile(sketch001, at = [-134.53, -56.17])
710 |> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
711 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
712 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
713 |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
714 |> close()
715extrude001 = extrude(profile001, length = 100)
716sketch003 = startSketchOn(extrude001, face = seg02)
717sketch002 = startSketchOn(extrude001, face = seg01)
718"#;
719
720 let other_file = (
721 std::path::PathBuf::from("toBeImported.kcl"),
722 r#"sketch001 = startSketchOn(XZ)
723profile001 = startProfile(sketch001, at = [281.54, 305.81])
724 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
725 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
726 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
727 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
728 |> close()
729extrude(profile001, length = 100)"#
730 .to_string(),
731 );
732
733 let tmp_dir = std::env::temp_dir();
734 let tmp_dir = tmp_dir.join(uuid::Uuid::new_v4().to_string());
735
736 let tmp_file = tmp_dir.join(other_file.0);
738 std::fs::create_dir_all(tmp_file.parent().unwrap()).unwrap();
739 std::fs::write(tmp_file, other_file.1).unwrap();
740
741 let ExecTestResults { program, exec_ctxt, .. } =
742 parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
743 .await
744 .unwrap();
745
746 let mut new_program = crate::Program::parse_no_errs(code).unwrap();
747 new_program.compute_digest();
748
749 let result = get_changed_program(
750 CacheInformation {
751 ast: &program.ast,
752 settings: &exec_ctxt.settings,
753 },
754 CacheInformation {
755 ast: &new_program.ast,
756 settings: &exec_ctxt.settings,
757 },
758 )
759 .await;
760
761 let CacheResult::CheckImportsOnly { reapply_settings, .. } = result else {
762 panic!("Expected CheckImportsOnly, got {result:?}");
763 };
764
765 assert_eq!(reapply_settings, false);
766 }
767
768 #[tokio::test(flavor = "multi_thread")]
769 async fn test_cache_multi_file_only_other_file_changes_should_reexecute() {
770 let code = r#"import "toBeImported.kcl" as importedCube
771
772importedCube
773
774sketch001 = startSketchOn(XZ)
775profile001 = startProfile(sketch001, at = [-134.53, -56.17])
776 |> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
777 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
778 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
779 |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
780 |> close()
781extrude001 = extrude(profile001, length = 100)
782sketch003 = startSketchOn(extrude001, face = seg02)
783sketch002 = startSketchOn(extrude001, face = seg01)
784"#;
785
786 let other_file = (
787 std::path::PathBuf::from("toBeImported.kcl"),
788 r#"sketch001 = startSketchOn(XZ)
789profile001 = startProfile(sketch001, at = [281.54, 305.81])
790 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
791 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
792 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
793 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
794 |> close()
795extrude(profile001, length = 100)"#
796 .to_string(),
797 );
798
799 let other_file2 = (
800 std::path::PathBuf::from("toBeImported.kcl"),
801 r#"sketch001 = startSketchOn(XZ)
802profile001 = startProfile(sketch001, at = [281.54, 305.81])
803 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
804 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
805 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
806 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
807 |> close()
808extrude(profile001, length = 100)
809|> translate(z=100)
810"#
811 .to_string(),
812 );
813
814 let tmp_dir = std::env::temp_dir();
815 let tmp_dir = tmp_dir.join(uuid::Uuid::new_v4().to_string());
816
817 let tmp_file = tmp_dir.join(other_file.0);
819 std::fs::create_dir_all(tmp_file.parent().unwrap()).unwrap();
820 std::fs::write(&tmp_file, other_file.1).unwrap();
821
822 let ExecTestResults { program, exec_ctxt, .. } =
823 parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
824 .await
825 .unwrap();
826
827 std::fs::write(tmp_file, other_file2.1).unwrap();
829
830 let mut new_program = crate::Program::parse_no_errs(code).unwrap();
831 new_program.compute_digest();
832
833 let result = get_changed_program(
834 CacheInformation {
835 ast: &program.ast,
836 settings: &exec_ctxt.settings,
837 },
838 CacheInformation {
839 ast: &new_program.ast,
840 settings: &exec_ctxt.settings,
841 },
842 )
843 .await;
844
845 let CacheResult::CheckImportsOnly { reapply_settings, .. } = result else {
846 panic!("Expected CheckImportsOnly, got {result:?}");
847 };
848
849 assert_eq!(reapply_settings, false);
850 }
851}