1use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5use zccache_core::NormalizedPath;
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub enum Request {
10 Ping,
12 Shutdown,
14 Status,
16 Lookup {
18 cache_key: String,
20 },
21 Store {
23 cache_key: String,
25 artifact: ArtifactData,
27 },
28 SessionStart {
30 client_pid: u32,
32 working_dir: NormalizedPath,
34 log_file: Option<NormalizedPath>,
36 track_stats: bool,
38 journal_path: Option<NormalizedPath>,
40 profile: bool,
46 },
47 Compile {
49 session_id: String,
51 args: Vec<String>,
53 cwd: NormalizedPath,
55 compiler: NormalizedPath,
57 env: Option<Vec<(String, String)>>,
61 stdin: Vec<u8>,
67 },
68 SessionEnd {
70 session_id: String,
72 },
73 Clear,
75 CompileEphemeral {
79 client_pid: u32,
81 working_dir: NormalizedPath,
83 compiler: NormalizedPath,
85 args: Vec<String>,
87 cwd: NormalizedPath,
89 env: Option<Vec<(String, String)>>,
91 stdin: Vec<u8>,
95 },
96 LinkEphemeral {
99 client_pid: u32,
101 tool: NormalizedPath,
103 args: Vec<String>,
105 cwd: NormalizedPath,
107 env: Option<Vec<(String, String)>>,
109 },
110 SessionStats {
113 session_id: String,
115 },
116 FingerprintCheck {
119 cache_file: NormalizedPath,
121 cache_type: String,
123 root: NormalizedPath,
125 extensions: Vec<String>,
128 include_globs: Vec<String>,
131 exclude: Vec<String>,
133 },
134 FingerprintMarkSuccess {
136 cache_file: NormalizedPath,
138 },
139 FingerprintMarkFailure {
141 cache_file: NormalizedPath,
143 },
144 FingerprintInvalidate {
146 cache_file: NormalizedPath,
148 },
149 ListRustArtifacts,
152}
153
154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156pub enum Response {
157 Pong,
159 ShuttingDown,
161 Status(DaemonStatus),
163 LookupResult(LookupResult),
165 StoreResult(StoreResult),
167 SessionStarted {
169 session_id: String,
171 journal_path: Option<NormalizedPath>,
173 },
174 CompileResult {
176 exit_code: i32,
178 stdout: Arc<Vec<u8>>,
180 stderr: Arc<Vec<u8>>,
182 cached: bool,
184 },
185 SessionEnded {
187 stats: Option<SessionStats>,
189 },
190 LinkResult {
192 exit_code: i32,
194 stdout: Arc<Vec<u8>>,
196 stderr: Arc<Vec<u8>>,
198 cached: bool,
200 warning: Option<String>,
202 },
203 Error {
205 message: String,
207 },
208 Cleared {
210 artifacts_removed: u64,
212 metadata_cleared: u64,
214 dep_graph_contexts_cleared: u64,
216 on_disk_bytes_freed: u64,
218 },
219 SessionStatsResult {
222 stats: Option<SessionStats>,
224 },
225 FingerprintCheckResult {
228 decision: String,
230 reason: Option<String>,
232 changed_files: Vec<String>,
234 },
235 FingerprintAck,
237 RustArtifactList { artifacts: Vec<RustArtifactInfo> },
240}
241
242#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
244pub struct DaemonStatus {
245 pub version: String,
247 pub artifact_count: u64,
249 pub cache_size_bytes: u64,
251 pub metadata_entries: u64,
253 pub uptime_secs: u64,
255 pub cache_hits: u64,
257 pub cache_misses: u64,
259 pub total_compilations: u64,
261 pub non_cacheable: u64,
263 pub compile_errors: u64,
265 pub time_saved_ms: u64,
267 pub total_links: u64,
269 pub link_hits: u64,
271 pub link_misses: u64,
273 pub link_non_cacheable: u64,
275 pub dep_graph_contexts: u64,
277 pub dep_graph_files: u64,
279 pub sessions_total: u64,
281 pub sessions_active: u64,
283 pub cache_dir: NormalizedPath,
285 pub dep_graph_version: u32,
287 pub dep_graph_disk_size: u64,
289 pub dep_graph_persisted: bool,
295}
296
297#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
299pub enum LookupResult {
300 Hit {
302 artifact: ArtifactData,
304 },
305 Miss,
307}
308
309#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311pub enum StoreResult {
312 Stored,
314 AlreadyExists,
316}
317
318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
320pub struct ArtifactData {
321 pub outputs: Vec<ArtifactOutput>,
323 pub stdout: Arc<Vec<u8>>,
325 pub stderr: Arc<Vec<u8>>,
327 pub exit_code: i32,
329}
330
331#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
333pub struct SessionStats {
334 pub duration_ms: u64,
336 pub compilations: u64,
338 pub hits: u64,
340 pub misses: u64,
342 pub non_cacheable: u64,
344 pub errors: u64,
346 pub time_saved_ms: u64,
348 pub unique_sources: u64,
350 pub bytes_read: u64,
352 pub bytes_written: u64,
354 pub phase_profile: Option<PhaseProfileSummary>,
364}
365
366#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
376pub struct PhaseProfileSummary {
377 pub hit_count: u64,
379 pub miss_count: u64,
381 pub parse_args_ns: u64,
384 pub build_context_ns: u64,
386 pub hash_source_ns: u64,
388 pub hash_headers_ns: u64,
390 pub depgraph_check_ns: u64,
392 pub request_cache_lookup_ns: u64,
394 pub cross_root_validate_ns: u64,
396 pub artifact_lookup_ns: u64,
398 pub write_output_ns: u64,
400 pub bookkeeping_ns: u64,
402 pub total_hit_ns: u64,
404 pub compiler_exec_ns: u64,
407 pub include_scan_ns: u64,
409 pub hash_all_ns: u64,
411 pub artifact_store_ns: u64,
413 pub total_miss_ns: u64,
415}
416
417#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
430pub enum ArtifactPayload {
431 Bytes(Arc<Vec<u8>>),
434 Path(NormalizedPath),
439}
440
441impl ArtifactPayload {
442 #[must_use]
446 pub fn size_bytes(&self) -> u64 {
447 match self {
448 Self::Bytes(b) => b.len() as u64,
449 Self::Path(p) => std::fs::metadata(p.as_path()).map(|m| m.len()).unwrap_or(0),
450 }
451 }
452
453 #[must_use]
458 pub fn as_bytes(&self) -> Option<&Arc<Vec<u8>>> {
459 match self {
460 Self::Bytes(b) => Some(b),
461 Self::Path(_) => None,
462 }
463 }
464}
465
466#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
468pub struct ArtifactOutput {
469 pub name: String,
471 pub payload: ArtifactPayload,
474}
475
476#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
478pub struct RustArtifactInfo {
479 pub cache_key: String,
481 pub output_names: Vec<String>,
483 pub payload_count: usize,
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490
491 fn roundtrip<T: Serialize + serde::de::DeserializeOwned + PartialEq + std::fmt::Debug>(
493 val: &T,
494 ) {
495 let bytes = bincode::serialize(val).unwrap();
496 let decoded: T = bincode::deserialize(&bytes).unwrap();
497 assert_eq!(*val, decoded);
498 }
499
500 #[test]
501 fn session_stats_roundtrip() {
502 let stats = SessionStats {
503 duration_ms: 12345,
504 compilations: 100,
505 hits: 80,
506 misses: 15,
507 non_cacheable: 5,
508 errors: 2,
509 time_saved_ms: 8000,
510 unique_sources: 42,
511 bytes_read: 1024 * 1024,
512 bytes_written: 512 * 1024,
513 phase_profile: None,
514 };
515 roundtrip(&stats);
516 }
517
518 #[test]
519 fn session_stats_default_zeros() {
520 let stats = SessionStats {
521 duration_ms: 0,
522 compilations: 0,
523 hits: 0,
524 misses: 0,
525 non_cacheable: 0,
526 errors: 0,
527 time_saved_ms: 0,
528 unique_sources: 0,
529 bytes_read: 0,
530 bytes_written: 0,
531 phase_profile: None,
532 };
533 roundtrip(&stats);
534 }
535
536 #[test]
537 fn session_stats_with_phase_profile_roundtrip() {
538 let stats = SessionStats {
542 duration_ms: 12345,
543 compilations: 146,
544 hits: 103,
545 misses: 12,
546 non_cacheable: 31,
547 errors: 3,
548 time_saved_ms: 223,
549 unique_sources: 115,
550 bytes_read: 143_812_577,
551 bytes_written: 62_500_000,
552 phase_profile: Some(PhaseProfileSummary {
553 hit_count: 103,
554 miss_count: 12,
555 parse_args_ns: 4_000_000,
556 build_context_ns: 19_000_000,
557 hash_source_ns: 6_000_000,
558 hash_headers_ns: 11_000_000,
559 depgraph_check_ns: 28_000_000,
560 request_cache_lookup_ns: 2_500_000,
561 cross_root_validate_ns: 1_200_000,
562 artifact_lookup_ns: 8_700_000,
563 write_output_ns: 540_000_000,
564 bookkeeping_ns: 3_300_000,
565 total_hit_ns: 623_700_000,
566 compiler_exec_ns: 11_400_000_000,
567 include_scan_ns: 270_000_000,
568 hash_all_ns: 95_000_000,
569 artifact_store_ns: 120_000_000,
570 total_miss_ns: 11_885_000_000,
571 }),
572 };
573 roundtrip(&stats);
574
575 let json = serde_json::to_string(&stats).expect("serialize");
579 let decoded: SessionStats = serde_json::from_str(&json).expect("deserialize");
580 assert_eq!(stats, decoded);
581
582 let legacy = r#"{
586 "duration_ms": 0, "compilations": 0, "hits": 0, "misses": 0,
587 "non_cacheable": 0, "errors": 0, "time_saved_ms": 0,
588 "unique_sources": 0, "bytes_read": 0, "bytes_written": 0
589 }"#;
590 let decoded: SessionStats = serde_json::from_str(legacy).expect("legacy decode");
591 assert!(decoded.phase_profile.is_none());
592 }
593
594 #[test]
595 fn daemon_status_expanded_roundtrip() {
596 let status = DaemonStatus {
597 version: env!("CARGO_PKG_VERSION").to_string(),
598 artifact_count: 892,
599 cache_size_bytes: 147_000_000,
600 metadata_entries: 5430,
601 uptime_secs: 8040,
602 cache_hits: 1089,
603 cache_misses: 143,
604 total_compilations: 1247,
605 non_cacheable: 15,
606 compile_errors: 3,
607 time_saved_ms: 750_000,
608 total_links: 50,
609 link_hits: 38,
610 link_misses: 10,
611 link_non_cacheable: 2,
612 dep_graph_contexts: 892,
613 dep_graph_files: 4201,
614 sessions_total: 41,
615 sessions_active: 3,
616 cache_dir: "/home/user/.zccache".into(),
617 dep_graph_version: 1,
618 dep_graph_disk_size: 2_500_000,
619 dep_graph_persisted: true,
620 };
621 roundtrip(&status);
622 }
623
624 #[test]
625 fn session_start_with_track_stats_roundtrip() {
626 let req = Request::SessionStart {
627 client_pid: 1234,
628 working_dir: "/home/user/project".into(),
629 log_file: None,
630 track_stats: true,
631 journal_path: None,
632 profile: false,
633 };
634 roundtrip(&req);
635
636 let req_no_stats = Request::SessionStart {
637 client_pid: 1234,
638 working_dir: "/home/user/project".into(),
639 log_file: None,
640 track_stats: false,
641 journal_path: None,
642 profile: false,
643 };
644 roundtrip(&req_no_stats);
645 }
646
647 #[test]
648 fn session_start_with_journal_path_roundtrip() {
649 let req = Request::SessionStart {
650 client_pid: 5678,
651 working_dir: "/home/user/project".into(),
652 log_file: None,
653 track_stats: false,
654 journal_path: Some("/tmp/build.jsonl".into()),
655 profile: false,
656 };
657 roundtrip(&req);
658
659 let req_no_journal = Request::SessionStart {
660 client_pid: 5678,
661 working_dir: "/home/user/project".into(),
662 log_file: None,
663 track_stats: false,
664 journal_path: None,
665 profile: false,
666 };
667 roundtrip(&req_no_journal);
668 }
669
670 #[test]
671 fn session_started_with_journal_path_roundtrip() {
672 let resp = Response::SessionStarted {
673 session_id: "550e8400-e29b-41d4-a716-446655440000".into(),
674 journal_path: Some("/home/user/.zccache/logs/sessions/test.jsonl".into()),
675 };
676 roundtrip(&resp);
677
678 let resp_no_journal = Response::SessionStarted {
679 session_id: "550e8400-e29b-41d4-a716-446655440000".into(),
680 journal_path: None,
681 };
682 roundtrip(&resp_no_journal);
683 }
684
685 #[test]
686 fn session_ended_with_stats_roundtrip() {
687 let stats = SessionStats {
688 duration_ms: 34000,
689 compilations: 32,
690 hits: 28,
691 misses: 3,
692 non_cacheable: 1,
693 errors: 0,
694 time_saved_ms: 8200,
695 unique_sources: 30,
696 bytes_read: 2_000_000,
697 bytes_written: 500_000,
698 phase_profile: None,
699 };
700 let resp = Response::SessionEnded { stats: Some(stats) };
701 roundtrip(&resp);
702
703 let resp_no_stats = Response::SessionEnded { stats: None };
704 roundtrip(&resp_no_stats);
705 }
706
707 #[test]
708 fn clear_request_roundtrip() {
709 roundtrip(&Request::Clear);
710 }
711
712 #[test]
713 fn cleared_response_roundtrip() {
714 roundtrip(&Response::Cleared {
715 artifacts_removed: 42,
716 metadata_cleared: 100,
717 dep_graph_contexts_cleared: 25,
718 on_disk_bytes_freed: 1024 * 1024,
719 });
720 }
721
722 #[test]
723 fn compile_ephemeral_roundtrip() {
724 roundtrip(&Request::CompileEphemeral {
725 client_pid: 9876,
726 working_dir: "/home/user/project".into(),
727 compiler: "/usr/bin/clang++".into(),
728 args: vec!["-c".into(), "main.cpp".into(), "-o".into(), "main.o".into()],
729 cwd: "/home/user/project/build".into(),
730 env: Some(vec![("PATH".into(), "/usr/bin".into())]),
731 stdin: Vec::new(),
732 });
733 roundtrip(&Request::CompileEphemeral {
737 client_pid: 1,
738 working_dir: ".".into(),
739 compiler: "gcc".into(),
740 args: vec![],
741 cwd: ".".into(),
742 env: None,
743 stdin: b"hello\x00world\nbinary\xff\xfe".to_vec(),
744 });
745 }
746
747 #[test]
748 fn link_ephemeral_roundtrip() {
749 roundtrip(&Request::LinkEphemeral {
750 client_pid: 5555,
751 tool: "/usr/bin/ar".into(),
752 args: vec!["rcs".into(), "libfoo.a".into(), "a.o".into(), "b.o".into()],
753 cwd: "/home/user/project/build".into(),
754 env: Some(vec![("PATH".into(), "/usr/bin".into())]),
755 });
756 roundtrip(&Request::LinkEphemeral {
757 client_pid: 1,
758 tool: "lib.exe".into(),
759 args: vec!["/OUT:foo.lib".into(), "a.obj".into()],
760 cwd: ".".into(),
761 env: None,
762 });
763 }
764
765 #[test]
766 fn link_result_roundtrip() {
767 roundtrip(&Response::LinkResult {
768 exit_code: 0,
769 stdout: Arc::new(vec![]),
770 stderr: Arc::new(vec![]),
771 cached: true,
772 warning: None,
773 });
774 roundtrip(&Response::LinkResult {
775 exit_code: 0,
776 stdout: Arc::new(vec![]),
777 stderr: Arc::new(b"some warning".to_vec()),
778 cached: false,
779 warning: Some("non-deterministic: missing D flag".into()),
780 });
781 }
782
783 #[test]
784 fn session_stats_request_roundtrip() {
785 roundtrip(&Request::SessionStats {
786 session_id: "550e8400-e29b-41d4-a716-446655440000".into(),
787 });
788 }
789
790 #[test]
791 fn session_stats_result_roundtrip() {
792 let stats = SessionStats {
793 duration_ms: 5000,
794 compilations: 10,
795 hits: 7,
796 misses: 2,
797 non_cacheable: 1,
798 errors: 0,
799 time_saved_ms: 3000,
800 unique_sources: 9,
801 bytes_read: 50_000,
802 bytes_written: 20_000,
803 phase_profile: None,
804 };
805 roundtrip(&Response::SessionStatsResult { stats: Some(stats) });
806 roundtrip(&Response::SessionStatsResult { stats: None });
807 }
808
809 #[test]
810 fn existing_request_variants_still_work() {
811 roundtrip(&Request::Ping);
812 roundtrip(&Request::Shutdown);
813 roundtrip(&Request::Status);
814 roundtrip(&Request::SessionEnd {
815 session_id: "550e8400-e29b-41d4-a716-446655440000".into(),
816 });
817 roundtrip(&Request::Compile {
818 session_id: "550e8400-e29b-41d4-a716-446655440000".into(),
819 args: vec!["-c".into(), "foo.c".into()],
820 cwd: "/tmp".into(),
821 compiler: "/usr/bin/gcc".into(),
822 env: None,
823 stdin: Vec::new(),
824 });
825 }
826
827 #[test]
828 fn existing_response_variants_still_work() {
829 roundtrip(&Response::Pong);
830 roundtrip(&Response::ShuttingDown);
831 roundtrip(&Response::CompileResult {
832 exit_code: 0,
833 stdout: Arc::new(vec![]),
834 stderr: Arc::new(vec![]),
835 cached: true,
836 });
837 roundtrip(&Response::Error {
838 message: "test".into(),
839 });
840 }
841
842 #[test]
843 fn daemon_status_version_field_roundtrips() {
844 let with_version = DaemonStatus {
845 version: "1.2.3".to_string(),
846 artifact_count: 0,
847 cache_size_bytes: 0,
848 metadata_entries: 0,
849 uptime_secs: 0,
850 cache_hits: 0,
851 cache_misses: 0,
852 total_compilations: 0,
853 non_cacheable: 0,
854 compile_errors: 0,
855 time_saved_ms: 0,
856 total_links: 0,
857 link_hits: 0,
858 link_misses: 0,
859 link_non_cacheable: 0,
860 dep_graph_contexts: 0,
861 dep_graph_files: 0,
862 sessions_total: 0,
863 sessions_active: 0,
864 cache_dir: "".into(),
865 dep_graph_version: 0,
866 dep_graph_disk_size: 0,
867 dep_graph_persisted: false,
868 };
869 roundtrip(&with_version);
870 }
871
872 const _: () = assert!(crate::PROTOCOL_VERSION > 0);
874 const _FINGERPRINT_VERSION: () = assert!(crate::PROTOCOL_VERSION == 9);
880
881 #[test]
882 fn fingerprint_check_roundtrip() {
883 roundtrip(&Request::FingerprintCheck {
884 cache_file: "/tmp/lint.json".into(),
885 cache_type: "two-layer".into(),
886 root: "/home/user/project/src".into(),
887 extensions: vec!["rs".into(), "toml".into()],
888 include_globs: vec![],
889 exclude: vec![".git".into(), "target".into()],
890 });
891 roundtrip(&Request::FingerprintCheck {
892 cache_file: "cache.json".into(),
893 cache_type: "hash".into(),
894 root: ".".into(),
895 extensions: vec![],
896 include_globs: vec!["**/*.cpp".into(), "**/*.h".into()],
897 exclude: vec![],
898 });
899 }
900
901 #[test]
902 fn fingerprint_mark_success_roundtrip() {
903 roundtrip(&Request::FingerprintMarkSuccess {
904 cache_file: "/tmp/lint.json".into(),
905 });
906 }
907
908 #[test]
909 fn fingerprint_mark_failure_roundtrip() {
910 roundtrip(&Request::FingerprintMarkFailure {
911 cache_file: "/tmp/lint.json".into(),
912 });
913 }
914
915 #[test]
916 fn fingerprint_invalidate_roundtrip() {
917 roundtrip(&Request::FingerprintInvalidate {
918 cache_file: "/tmp/lint.json".into(),
919 });
920 }
921
922 #[test]
923 fn fingerprint_check_result_roundtrip() {
924 roundtrip(&Response::FingerprintCheckResult {
925 decision: "skip".into(),
926 reason: None,
927 changed_files: vec![],
928 });
929 roundtrip(&Response::FingerprintCheckResult {
930 decision: "run".into(),
931 reason: Some("content changed".into()),
932 changed_files: vec!["src/main.rs".into(), "src/lib.rs".into()],
933 });
934 roundtrip(&Response::FingerprintCheckResult {
935 decision: "run".into(),
936 reason: Some("no cache file".into()),
937 changed_files: vec![],
938 });
939 }
940
941 #[test]
942 fn fingerprint_ack_roundtrip() {
943 roundtrip(&Response::FingerprintAck);
944 }
945
946 #[test]
947 fn list_rust_artifacts_request_roundtrip() {
948 roundtrip(&Request::ListRustArtifacts);
949 }
950
951 #[test]
952 fn rust_artifact_list_response_roundtrip() {
953 roundtrip(&Response::RustArtifactList {
954 artifacts: vec![
955 RustArtifactInfo {
956 cache_key: "abc123def456".into(),
957 output_names: vec![
958 "libfoo-abc123.rlib".into(),
959 "libfoo-abc123.rmeta".into(),
960 "foo-abc123.d".into(),
961 ],
962 payload_count: 3,
963 },
964 RustArtifactInfo {
965 cache_key: "deadbeef".into(),
966 output_names: vec!["libbar-deadbeef.rlib".into()],
967 payload_count: 1,
968 },
969 ],
970 });
971 roundtrip(&Response::RustArtifactList { artifacts: vec![] });
973 }
974
975 #[test]
976 fn rust_artifact_info_roundtrip() {
977 roundtrip(&RustArtifactInfo {
978 cache_key: "0123456789abcdef".into(),
979 output_names: vec!["test.o".into()],
980 payload_count: 1,
981 });
982 }
983
984 #[test]
985 fn artifact_clone_shares_payload_via_arc() {
986 let bytes = Arc::new(vec![1u8, 2, 3, 4]);
987 let artifact = ArtifactData {
988 outputs: vec![ArtifactOutput {
989 name: "test.o".into(),
990 payload: ArtifactPayload::Bytes(Arc::clone(&bytes)),
991 }],
992 stdout: Arc::new(vec![5, 6]),
993 stderr: Arc::new(vec![7, 8]),
994 exit_code: 0,
995 };
996
997 let cloned = artifact.clone();
998
999 let orig_inner = artifact.outputs[0].payload.as_bytes().unwrap();
1001 let cloned_inner = cloned.outputs[0].payload.as_bytes().unwrap();
1002 assert!(Arc::ptr_eq(orig_inner, cloned_inner));
1003 assert!(Arc::ptr_eq(orig_inner, &bytes));
1004 assert!(Arc::ptr_eq(&artifact.stdout, &cloned.stdout));
1005 assert!(Arc::ptr_eq(&artifact.stderr, &cloned.stderr));
1006 }
1007
1008 #[test]
1009 fn artifact_payload_size_bytes_for_bytes_variant() {
1010 let p = ArtifactPayload::Bytes(Arc::new(vec![0u8; 1234]));
1011 assert_eq!(p.size_bytes(), 1234);
1012 }
1013
1014 #[test]
1015 fn artifact_payload_size_bytes_for_path_variant() {
1016 let tmp = tempfile::NamedTempFile::new().expect("tempfile");
1017 std::fs::write(tmp.path(), vec![0u8; 4321]).expect("write");
1018 let p = ArtifactPayload::Path(NormalizedPath::from(tmp.path()));
1019 assert_eq!(p.size_bytes(), 4321);
1020 }
1021
1022 #[test]
1023 fn artifact_payload_size_bytes_for_missing_path_is_zero() {
1024 let p = ArtifactPayload::Path(NormalizedPath::from(std::path::Path::new(
1025 "/this/path/does/not/exist/zccache",
1026 )));
1027 assert_eq!(p.size_bytes(), 0);
1028 }
1029
1030 #[test]
1031 fn artifact_payload_round_trips_through_bincode() {
1032 let bytes_variant = ArtifactPayload::Bytes(Arc::new(b"hello".to_vec()));
1033 let encoded = bincode::serialize(&bytes_variant).expect("serialize bytes");
1034 let decoded: ArtifactPayload = bincode::deserialize(&encoded).expect("deserialize bytes");
1035 assert_eq!(decoded, bytes_variant);
1036
1037 let path_variant = ArtifactPayload::Path(NormalizedPath::from(std::path::Path::new(
1038 "/tmp/some/place.rlib",
1039 )));
1040 let encoded = bincode::serialize(&path_variant).expect("serialize path");
1041 let decoded: ArtifactPayload = bincode::deserialize(&encoded).expect("deserialize path");
1042 assert_eq!(decoded, path_variant);
1043 }
1044
1045 #[test]
1046 fn arc_vec_u8_roundtrip_matches_plain_vec() {
1047 let plain: Vec<u8> = vec![0xDE, 0xAD, 0xBE, 0xEF];
1049 let arc_wrapped: Arc<Vec<u8>> = Arc::new(plain.clone());
1050
1051 let plain_bytes = bincode::serialize(&plain).unwrap();
1052 let arc_bytes = bincode::serialize(&arc_wrapped).unwrap();
1053 assert_eq!(
1054 plain_bytes, arc_bytes,
1055 "Arc<Vec<u8>> must serialize identically to Vec<u8>"
1056 );
1057
1058 let decoded_plain: Vec<u8> = bincode::deserialize(&arc_bytes).unwrap();
1060 let decoded_arc: Arc<Vec<u8>> = bincode::deserialize(&plain_bytes).unwrap();
1061 assert_eq!(decoded_plain, plain);
1062 assert_eq!(*decoded_arc, plain);
1063 }
1064}