git-ents.gitmain
⌘K
foforge
binding_roundtrip.rs169 lines · 6.6 KB · rusthistorycomment on this file
1//! Round-trip fixture for [`Binding::deserialize`]/[`Binding::serialize_into`]
2//! against the *existing* stored anchor format, captured from the current
3//! code before this phase's changes: a byte-for-byte guarantee that
4//! `Binding::Position` decodes, and re-encodes, the exact tree
5//! `facet_git_tree::serialize_into(&anchor, ...)` has always produced.
6//!
7//! Every oid below is content-addressed from the bytes reconstructed in
8//! this file, so a single corrupted byte in any hex constant — or in the
9//! reconstructed content itself — fails an `assert_eq!` here rather than
10//! silently drifting.
11
12#![allow(
13 clippy::unwrap_used,
14 clippy::expect_used,
15 clippy::panic,
16 reason = "integration test"
17)]
18
19use ents_anchor::{Binding, LineRange};
20use facet_git_tree::ObjectStore;
21use gix_object::tree::{Entry, EntryKind, EntryMode};
22use gix_object::{Kind, Tree, Write as _};
23
24/// The root tree's oid, exactly as the current code produces it for an
25/// anchor at `file.txt`, lines 3..=4, in a 10-line numbered file.
26const ROOT: &str = "002b45e6824a3a9723ebc245104426c43ccf91be";
27
28/// `blob` entry: [`ents_anchor::Anchor::blob`]'s 20 raw bytes, embedded —
29/// equal to `CONTENT_OID`'s own bytes, by content addressing
30/// (`anchor.retention`).
31const BLOB_ENTRY_OID: &str = "4a3354a7c472ad13ffd9fb0e30d9a8fd66efd0b5";
32const BLOB_ENTRY_RAW: &str = "fa2da6e55caa540725b55c04d13f1e42b4c725ce";
33
34/// `commit` entry: [`ents_anchor::Anchor::commit`]'s 20 raw bytes, embedded
35/// (an arbitrary, best-effort commit id — it need not resolve to a real
36/// object in this fixture).
37const COMMIT_ENTRY_OID: &str = "a662e760fcd5534f59d7c7d72e401a646ac1a88f";
38const COMMIT_ENTRY_RAW: &str = "92cf309c4efcf8698a5bd8f82d56f68fd38cc963";
39
40/// `content` entry: the anchored blob's own bytes, `"line 1\n"` through
41/// `"line 10\n"`.
42const CONTENT_OID: &str = "fa2da6e55caa540725b55c04d13f1e42b4c725ce";
43/// `context` entry: a three-line margin around lines 3..=4, `"line 1\n"`
44/// through `"line 7\n"`.
45const CONTEXT_OID: &str = "734156dc73cccb9703067e6366f3d09266e090dd";
46
47const LINES_OID: &str = "b76e73cdb409fa346566f18e8f054dbdf04a7304";
48const LINES_SOME_OID: &str = "3433ad944c71f4b15c4de9e87568ae4cf03feb50";
49const LINES_END_OID: &str = "bf0d87ab1b2b0ec1a11a3973d2845b42413d9767";
50const LINES_START_OID: &str = "e440e5c842586965a7fb77deda2eca68612b1f53";
51
52const PATH_OID: &str = "4c330738cc959751fb6760a91a50d9e58cfe5cb9";
53
54fn oid(hex: &str) -> gix::ObjectId {
55 gix::ObjectId::from_hex(hex.as_bytes()).expect("valid hex oid")
56}
57
58fn numbered(range: std::ops::RangeInclusive<u32>) -> String {
59 range.map(|n| format!("line {n}\n")).collect()
60}
61
62fn write_blob(store: &ObjectStore, bytes: &[u8]) -> gix::ObjectId {
63 store.write_buf(Kind::Blob, bytes).expect("write blob")
64}
65
66fn write_tree(store: &ObjectStore, mut entries: Vec<Entry>) -> gix::ObjectId {
67 entries.sort();
68 store.write(&Tree { entries }).expect("write tree")
69}
70
71fn entry(name: &str, kind: EntryKind, id: gix::ObjectId) -> Entry {
72 Entry {
73 mode: EntryMode::from(kind),
74 filename: name.into(),
75 oid: id,
76 }
77}
78
79/// Reconstruct the fixture's object set with `gix_object::Tree` +
80/// `gix_object::Write`, asserting every intermediate oid along the way
81/// (item 1 of the fixture contract) before returning the finished store.
82fn build_fixture() -> (gix::ObjectId, ObjectStore) {
83 let store = ObjectStore::default();
84
85 let blob_entry = write_blob(&store, oid(BLOB_ENTRY_RAW).as_slice());
86 assert_eq!(blob_entry.to_string(), BLOB_ENTRY_OID);
87
88 let commit_entry = write_blob(&store, oid(COMMIT_ENTRY_RAW).as_slice());
89 assert_eq!(commit_entry.to_string(), COMMIT_ENTRY_OID);
90
91 let content = write_blob(&store, numbered(1..=10).as_bytes());
92 assert_eq!(content.to_string(), CONTENT_OID);
93
94 let context = write_blob(&store, numbered(1..=7).as_bytes());
95 assert_eq!(context.to_string(), CONTEXT_OID);
96
97 let end = write_blob(&store, b"4");
98 assert_eq!(end.to_string(), LINES_END_OID);
99 let start = write_blob(&store, b"3");
100 assert_eq!(start.to_string(), LINES_START_OID);
101 let some = write_tree(
102 &store,
103 vec![
104 entry("end", EntryKind::Blob, end),
105 entry("start", EntryKind::Blob, start),
106 ],
107 );
108 assert_eq!(some.to_string(), LINES_SOME_OID);
109 let lines = write_tree(&store, vec![entry("some", EntryKind::Tree, some)]);
110 assert_eq!(lines.to_string(), LINES_OID);
111
112 let path = write_blob(&store, b"file.txt");
113 assert_eq!(path.to_string(), PATH_OID);
114
115 let root = write_tree(
116 &store,
117 vec![
118 entry("blob", EntryKind::Blob, blob_entry),
119 entry("commit", EntryKind::Blob, commit_entry),
120 entry("content", EntryKind::Blob, content),
121 entry("context", EntryKind::Blob, context),
122 entry("lines", EntryKind::Tree, lines),
123 entry("path", EntryKind::Blob, path),
124 ],
125 );
126 (root, store)
127}
128
129/// Item 1 + 2 of the fixture contract: every reconstructed object's oid —
130/// including the root's — matches the value the current code produces.
131/// Content addressing means a single corrupted byte anywhere above fails
132/// this assertion (or one of `build_fixture`'s own, reached first).
133#[test]
134fn reconstructing_the_fixture_reproduces_every_recorded_oid() {
135 let (root, _store) = build_fixture();
136 assert_eq!(root.to_string(), ROOT);
137}
138
139/// Item 3: `Binding::deserialize` decodes the fixture as
140/// `Binding::Position`, recovering exactly the `Anchor` the current stored
141/// format has always encoded.
142#[test]
143fn the_fixture_deserializes_as_a_position_binding() {
144 let (root, store) = build_fixture();
145
146 let binding = Binding::deserialize(&root, &store).expect("deserialize");
147 let Binding::Position(anchor) = binding else {
148 panic!("the fixture must decode as Binding::Position");
149 };
150 assert_eq!(anchor.path, "file.txt");
151 assert_eq!(anchor.lines, Some(LineRange { start: 3, end: 4 }));
152 assert_eq!(anchor.content, numbered(1..=10).into_bytes());
153 assert_eq!(anchor.context, numbered(1..=7).into_bytes());
154 assert_eq!(anchor.blob().to_string(), CONTENT_OID);
155 assert_eq!(anchor.commit().to_string(), COMMIT_ENTRY_RAW);
156}
157
158/// Item 4: re-encoding the decoded binding into a fresh store reproduces
159/// the fixture's root oid exactly — the existing anchor storage format is
160/// unchanged, byte for byte, now that it decodes through `Binding`.
161#[test]
162fn re_encoding_reproduces_the_fixture_root_byte_for_byte() {
163 let (root, store) = build_fixture();
164 let binding = Binding::deserialize(&root, &store).expect("deserialize");
165
166 let fresh = ObjectStore::default();
167 let re_root = binding.serialize_into(&fresh).expect("serialize");
168 assert_eq!(re_root.to_string(), ROOT);
169}