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 {
267 if !old_ast.body.iter().zip(new_ast.body.iter()).all(|(old, new)| {
268 let old_node: WalkNode = old.into();
269 let new_node: WalkNode = new.into();
270 old_node.digest() == new_node.digest()
271 }) {
272 return CacheResult::ReExecute {
278 clear_scene: true,
279 reapply_settings,
280 program: new_ast,
281 };
282 }
283
284 match new_ast.body.len().cmp(&old_ast.body.len()) {
288 std::cmp::Ordering::Less => {
289 CacheResult::ReExecute {
298 clear_scene: true,
299 reapply_settings,
300 program: new_ast,
301 }
302 }
303 std::cmp::Ordering::Greater => {
304 new_ast.body = new_ast.body[old_ast.body.len()..].to_owned();
312
313 CacheResult::ReExecute {
314 clear_scene: false,
315 reapply_settings,
316 program: new_ast,
317 }
318 }
319 std::cmp::Ordering::Equal => {
320 CacheResult::NoAction(reapply_settings)
330 }
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use pretty_assertions::assert_eq;
337
338 use super::*;
339 use crate::execution::{ExecTestResults, parse_execute, parse_execute_with_project_dir};
340
341 #[tokio::test(flavor = "multi_thread")]
342 async fn test_get_changed_program_same_code() {
343 let new = r#"// Remove the end face for the extrusion.
344firstSketch = startSketchOn(XY)
345 |> startProfile(at = [-12, 12])
346 |> line(end = [24, 0])
347 |> line(end = [0, -24])
348 |> line(end = [-24, 0])
349 |> close()
350 |> extrude(length = 6)
351
352// Remove the end face for the extrusion.
353shell(firstSketch, faces = [END], thickness = 0.25)"#;
354
355 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(new).await.unwrap();
356
357 let result = get_changed_program(
358 CacheInformation {
359 ast: &program.ast,
360 settings: &exec_ctxt.settings,
361 },
362 CacheInformation {
363 ast: &program.ast,
364 settings: &exec_ctxt.settings,
365 },
366 )
367 .await;
368
369 assert_eq!(result, CacheResult::NoAction(false));
370 }
371
372 #[tokio::test(flavor = "multi_thread")]
373 async fn test_get_changed_program_same_code_changed_whitespace() {
374 let old = r#" // Remove the end face for the extrusion.
375firstSketch = startSketchOn(XY)
376 |> startProfile(at = [-12, 12])
377 |> line(end = [24, 0])
378 |> line(end = [0, -24])
379 |> line(end = [-24, 0])
380 |> close()
381 |> extrude(length = 6)
382
383// Remove the end face for the extrusion.
384shell(firstSketch, faces = [END], thickness = 0.25) "#;
385
386 let new = r#"// Remove the end face for the extrusion.
387firstSketch = startSketchOn(XY)
388 |> startProfile(at = [-12, 12])
389 |> line(end = [24, 0])
390 |> line(end = [0, -24])
391 |> line(end = [-24, 0])
392 |> close()
393 |> extrude(length = 6)
394
395// Remove the end face for the extrusion.
396shell(firstSketch, faces = [END], thickness = 0.25)"#;
397
398 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
399
400 let program_new = crate::Program::parse_no_errs(new).unwrap();
401
402 let result = get_changed_program(
403 CacheInformation {
404 ast: &program.ast,
405 settings: &exec_ctxt.settings,
406 },
407 CacheInformation {
408 ast: &program_new.ast,
409 settings: &exec_ctxt.settings,
410 },
411 )
412 .await;
413
414 assert_eq!(result, CacheResult::NoAction(false));
415 }
416
417 #[tokio::test(flavor = "multi_thread")]
418 async fn test_get_changed_program_same_code_changed_code_comment_start_of_program() {
419 let old = r#" // Removed the end face for the extrusion.
420firstSketch = startSketchOn(XY)
421 |> startProfile(at = [-12, 12])
422 |> line(end = [24, 0])
423 |> line(end = [0, -24])
424 |> line(end = [-24, 0])
425 |> close()
426 |> extrude(length = 6)
427
428// Remove the end face for the extrusion.
429shell(firstSketch, faces = [END], thickness = 0.25) "#;
430
431 let new = r#"// Remove the end face for the extrusion.
432firstSketch = startSketchOn(XY)
433 |> startProfile(at = [-12, 12])
434 |> line(end = [24, 0])
435 |> line(end = [0, -24])
436 |> line(end = [-24, 0])
437 |> close()
438 |> extrude(length = 6)
439
440// Remove the end face for the extrusion.
441shell(firstSketch, faces = [END], thickness = 0.25)"#;
442
443 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
444
445 let program_new = crate::Program::parse_no_errs(new).unwrap();
446
447 let result = get_changed_program(
448 CacheInformation {
449 ast: &program.ast,
450 settings: &exec_ctxt.settings,
451 },
452 CacheInformation {
453 ast: &program_new.ast,
454 settings: &exec_ctxt.settings,
455 },
456 )
457 .await;
458
459 assert_eq!(result, CacheResult::NoAction(false));
460 }
461
462 #[tokio::test(flavor = "multi_thread")]
463 async fn test_get_changed_program_same_code_changed_code_comments_attrs() {
464 let old = r#"@foo(whatever = whatever)
465@bar
466// Removed the end face for the extrusion.
467firstSketch = startSketchOn(XY)
468 |> startProfile(at = [-12, 12])
469 |> line(end = [24, 0])
470 |> line(end = [0, -24])
471 |> line(end = [-24, 0]) // my thing
472 |> close()
473 |> extrude(length = 6)
474
475// Remove the end face for the extrusion.
476shell(firstSketch, faces = [END], thickness = 0.25) "#;
477
478 let new = r#"@foo(whatever = 42)
479@baz
480// Remove the end face for the extrusion.
481firstSketch = startSketchOn(XY)
482 |> startProfile(at = [-12, 12])
483 |> line(end = [24, 0])
484 |> line(end = [0, -24])
485 |> line(end = [-24, 0])
486 |> close()
487 |> extrude(length = 6)
488
489// Remove the end face for the extrusion.
490shell(firstSketch, faces = [END], thickness = 0.25)"#;
491
492 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old).await.unwrap();
493
494 let program_new = crate::Program::parse_no_errs(new).unwrap();
495
496 let result = get_changed_program(
497 CacheInformation {
498 ast: &program.ast,
499 settings: &exec_ctxt.settings,
500 },
501 CacheInformation {
502 ast: &program_new.ast,
503 settings: &exec_ctxt.settings,
504 },
505 )
506 .await;
507
508 assert_eq!(result, CacheResult::NoAction(false));
509 }
510
511 #[tokio::test(flavor = "multi_thread")]
513 async fn test_get_changed_program_same_code_but_different_grid_setting() {
514 let new = r#"// Remove the end face for the extrusion.
515firstSketch = startSketchOn(XY)
516 |> startProfile(at = [-12, 12])
517 |> line(end = [24, 0])
518 |> line(end = [0, -24])
519 |> line(end = [-24, 0])
520 |> close()
521 |> extrude(length = 6)
522
523// Remove the end face for the extrusion.
524shell(firstSketch, faces = [END], thickness = 0.25)"#;
525
526 let ExecTestResults {
527 program, mut exec_ctxt, ..
528 } = parse_execute(new).await.unwrap();
529
530 exec_ctxt.settings.show_grid = !exec_ctxt.settings.show_grid;
532
533 let result = get_changed_program(
534 CacheInformation {
535 ast: &program.ast,
536 settings: &Default::default(),
537 },
538 CacheInformation {
539 ast: &program.ast,
540 settings: &exec_ctxt.settings,
541 },
542 )
543 .await;
544
545 assert_eq!(result, CacheResult::NoAction(true));
546 }
547
548 #[tokio::test(flavor = "multi_thread")]
550 async fn test_get_changed_program_same_code_but_different_edge_visibility_setting() {
551 let new = r#"// Remove the end face for the extrusion.
552firstSketch = startSketchOn(XY)
553 |> startProfile(at = [-12, 12])
554 |> line(end = [24, 0])
555 |> line(end = [0, -24])
556 |> line(end = [-24, 0])
557 |> close()
558 |> extrude(length = 6)
559
560// Remove the end face for the extrusion.
561shell(firstSketch, faces = [END], thickness = 0.25)"#;
562
563 let ExecTestResults {
564 program, mut exec_ctxt, ..
565 } = parse_execute(new).await.unwrap();
566
567 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
569
570 let result = get_changed_program(
571 CacheInformation {
572 ast: &program.ast,
573 settings: &Default::default(),
574 },
575 CacheInformation {
576 ast: &program.ast,
577 settings: &exec_ctxt.settings,
578 },
579 )
580 .await;
581
582 assert_eq!(result, CacheResult::NoAction(true));
583
584 let old_settings = exec_ctxt.settings.clone();
586 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
587
588 let result = get_changed_program(
589 CacheInformation {
590 ast: &program.ast,
591 settings: &old_settings,
592 },
593 CacheInformation {
594 ast: &program.ast,
595 settings: &exec_ctxt.settings,
596 },
597 )
598 .await;
599
600 assert_eq!(result, CacheResult::NoAction(true));
601
602 let old_settings = exec_ctxt.settings.clone();
604 exec_ctxt.settings.highlight_edges = !exec_ctxt.settings.highlight_edges;
605
606 let result = get_changed_program(
607 CacheInformation {
608 ast: &program.ast,
609 settings: &old_settings,
610 },
611 CacheInformation {
612 ast: &program.ast,
613 settings: &exec_ctxt.settings,
614 },
615 )
616 .await;
617
618 assert_eq!(result, CacheResult::NoAction(true));
619 }
620
621 #[tokio::test(flavor = "multi_thread")]
624 async fn test_get_changed_program_same_code_but_different_unit_setting_using_annotation() {
625 let old_code = r#"@settings(defaultLengthUnit = in)
626startSketchOn(XY)
627"#;
628 let new_code = r#"@settings(defaultLengthUnit = mm)
629startSketchOn(XY)
630"#;
631
632 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
633
634 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
635 new_program.compute_digest();
636
637 let result = get_changed_program(
638 CacheInformation {
639 ast: &program.ast,
640 settings: &exec_ctxt.settings,
641 },
642 CacheInformation {
643 ast: &new_program.ast,
644 settings: &exec_ctxt.settings,
645 },
646 )
647 .await;
648
649 assert_eq!(
650 result,
651 CacheResult::ReExecute {
652 clear_scene: true,
653 reapply_settings: true,
654 program: new_program.ast,
655 }
656 );
657 }
658
659 #[tokio::test(flavor = "multi_thread")]
662 async fn test_get_changed_program_same_code_but_removed_unit_setting_using_annotation() {
663 let old_code = r#"@settings(defaultLengthUnit = in)
664startSketchOn(XY)
665"#;
666 let new_code = r#"
667startSketchOn(XY)
668"#;
669
670 let ExecTestResults { program, exec_ctxt, .. } = parse_execute(old_code).await.unwrap();
671
672 let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
673 new_program.compute_digest();
674
675 let result = get_changed_program(
676 CacheInformation {
677 ast: &program.ast,
678 settings: &exec_ctxt.settings,
679 },
680 CacheInformation {
681 ast: &new_program.ast,
682 settings: &exec_ctxt.settings,
683 },
684 )
685 .await;
686
687 assert_eq!(
688 result,
689 CacheResult::ReExecute {
690 clear_scene: true,
691 reapply_settings: true,
692 program: new_program.ast,
693 }
694 );
695 }
696
697 #[tokio::test(flavor = "multi_thread")]
698 async fn test_multi_file_no_changes_does_not_reexecute() {
699 let code = r#"import "toBeImported.kcl" as importedCube
700
701importedCube
702
703sketch001 = startSketchOn(XZ)
704profile001 = startProfile(sketch001, at = [-134.53, -56.17])
705 |> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
706 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
707 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
708 |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
709 |> close()
710extrude001 = extrude(profile001, length = 100)
711sketch003 = startSketchOn(extrude001, face = seg02)
712sketch002 = startSketchOn(extrude001, face = seg01)
713"#;
714
715 let other_file = (
716 std::path::PathBuf::from("toBeImported.kcl"),
717 r#"sketch001 = startSketchOn(XZ)
718profile001 = startProfile(sketch001, at = [281.54, 305.81])
719 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
720 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
721 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
722 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
723 |> close()
724extrude(profile001, length = 100)"#
725 .to_string(),
726 );
727
728 let tmp_dir = std::env::temp_dir();
729 let tmp_dir = tmp_dir.join(uuid::Uuid::new_v4().to_string());
730
731 let tmp_file = tmp_dir.join(other_file.0);
733 std::fs::create_dir_all(tmp_file.parent().unwrap()).unwrap();
734 std::fs::write(tmp_file, other_file.1).unwrap();
735
736 let ExecTestResults { program, exec_ctxt, .. } =
737 parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
738 .await
739 .unwrap();
740
741 let mut new_program = crate::Program::parse_no_errs(code).unwrap();
742 new_program.compute_digest();
743
744 let result = get_changed_program(
745 CacheInformation {
746 ast: &program.ast,
747 settings: &exec_ctxt.settings,
748 },
749 CacheInformation {
750 ast: &new_program.ast,
751 settings: &exec_ctxt.settings,
752 },
753 )
754 .await;
755
756 let CacheResult::CheckImportsOnly { reapply_settings, .. } = result else {
757 panic!("Expected CheckImportsOnly, got {result:?}");
758 };
759
760 assert_eq!(reapply_settings, false);
761 }
762
763 #[tokio::test(flavor = "multi_thread")]
764 async fn test_cache_multi_file_only_other_file_changes_should_reexecute() {
765 let code = r#"import "toBeImported.kcl" as importedCube
766
767importedCube
768
769sketch001 = startSketchOn(XZ)
770profile001 = startProfile(sketch001, at = [-134.53, -56.17])
771 |> angledLine(angle = 0, length = 79.05, tag = $rectangleSegmentA001)
772 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 76.28)
773 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg01)
774 |> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
775 |> close()
776extrude001 = extrude(profile001, length = 100)
777sketch003 = startSketchOn(extrude001, face = seg02)
778sketch002 = startSketchOn(extrude001, face = seg01)
779"#;
780
781 let other_file = (
782 std::path::PathBuf::from("toBeImported.kcl"),
783 r#"sketch001 = startSketchOn(XZ)
784profile001 = startProfile(sketch001, at = [281.54, 305.81])
785 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
786 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
787 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
788 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
789 |> close()
790extrude(profile001, length = 100)"#
791 .to_string(),
792 );
793
794 let other_file2 = (
795 std::path::PathBuf::from("toBeImported.kcl"),
796 r#"sketch001 = startSketchOn(XZ)
797profile001 = startProfile(sketch001, at = [281.54, 305.81])
798 |> angledLine(angle = 0, length = 123.43, tag = $rectangleSegmentA001)
799 |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 85.99)
800 |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
801 |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
802 |> close()
803extrude(profile001, length = 100)
804|> translate(z=100)
805"#
806 .to_string(),
807 );
808
809 let tmp_dir = std::env::temp_dir();
810 let tmp_dir = tmp_dir.join(uuid::Uuid::new_v4().to_string());
811
812 let tmp_file = tmp_dir.join(other_file.0);
814 std::fs::create_dir_all(tmp_file.parent().unwrap()).unwrap();
815 std::fs::write(&tmp_file, other_file.1).unwrap();
816
817 let ExecTestResults { program, exec_ctxt, .. } =
818 parse_execute_with_project_dir(code, Some(crate::TypedPath(tmp_dir)))
819 .await
820 .unwrap();
821
822 std::fs::write(tmp_file, other_file2.1).unwrap();
824
825 let mut new_program = crate::Program::parse_no_errs(code).unwrap();
826 new_program.compute_digest();
827
828 let result = get_changed_program(
829 CacheInformation {
830 ast: &program.ast,
831 settings: &exec_ctxt.settings,
832 },
833 CacheInformation {
834 ast: &new_program.ast,
835 settings: &exec_ctxt.settings,
836 },
837 )
838 .await;
839
840 let CacheResult::CheckImportsOnly { reapply_settings, .. } = result else {
841 panic!("Expected CheckImportsOnly, got {result:?}");
842 };
843
844 assert_eq!(reapply_settings, false);
845 }
846}