diff_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. package diff_test
  2. import (
  3. "bytes"
  4. "context"
  5. "io"
  6. "os"
  7. "strings"
  8. "testing"
  9. "time"
  10. "github.com/stretchr/testify/require"
  11. "github.com/zeebo/blake3"
  12. "github.com/kopia/kopia/fs"
  13. "github.com/kopia/kopia/internal/diff"
  14. "github.com/kopia/kopia/internal/repotesting"
  15. "github.com/kopia/kopia/internal/testlogging"
  16. "github.com/kopia/kopia/repo"
  17. "github.com/kopia/kopia/repo/content"
  18. "github.com/kopia/kopia/repo/manifest"
  19. "github.com/kopia/kopia/repo/object"
  20. "github.com/kopia/kopia/snapshot"
  21. )
  22. const statsOnly = false
  23. var (
  24. _ fs.Entry = (*testFile)(nil)
  25. _ fs.Directory = (*testDirectory)(nil)
  26. )
  27. type testBaseEntry struct {
  28. modtime time.Time
  29. mode os.FileMode
  30. name string
  31. owner fs.OwnerInfo
  32. oid object.ID
  33. }
  34. func (f *testBaseEntry) IsDir() bool { return false }
  35. func (f *testBaseEntry) LocalFilesystemPath() string { return f.name }
  36. func (f *testBaseEntry) Close() {}
  37. func (f *testBaseEntry) Name() string { return f.name }
  38. func (f *testBaseEntry) ModTime() time.Time { return f.modtime }
  39. func (f *testBaseEntry) Sys() any { return nil }
  40. func (f *testBaseEntry) Owner() fs.OwnerInfo { return f.owner }
  41. func (f *testBaseEntry) Device() fs.DeviceInfo { return fs.DeviceInfo{Dev: 1} }
  42. func (f *testBaseEntry) ObjectID() object.ID { return f.oid }
  43. func (f *testBaseEntry) Mode() os.FileMode {
  44. if f.mode == 0 {
  45. return 0o644
  46. }
  47. return f.mode & ^os.ModeDir
  48. }
  49. type testFile struct {
  50. testBaseEntry
  51. content string
  52. }
  53. func (f *testFile) Open(ctx context.Context) (io.Reader, error) {
  54. return strings.NewReader(f.content), nil
  55. }
  56. func (f *testFile) Size() int64 { return int64(len(f.content)) }
  57. type testDirectory struct {
  58. testBaseEntry
  59. files []fs.Entry
  60. }
  61. func (d *testDirectory) Iterate(ctx context.Context) (fs.DirectoryIterator, error) {
  62. return fs.StaticIterator(d.files, nil), nil
  63. }
  64. func (d *testDirectory) SupportsMultipleIterations() bool { return false }
  65. func (d *testDirectory) IsDir() bool { return true }
  66. func (d *testDirectory) LocalFilesystemPath() string { return d.name }
  67. func (d *testDirectory) Size() int64 { return 0 }
  68. func (d *testDirectory) Readdir(ctx context.Context) ([]fs.Entry, error) { return d.files, nil }
  69. func (d *testDirectory) Mode() os.FileMode {
  70. if d.mode == 0 {
  71. return os.ModeDir | 0o755
  72. }
  73. return os.ModeDir | d.mode
  74. }
  75. func (d *testDirectory) Child(ctx context.Context, name string) (fs.Entry, error) {
  76. for _, f := range d.files {
  77. if f.Name() == name {
  78. return f, nil
  79. }
  80. }
  81. return nil, fs.ErrEntryNotFound
  82. }
  83. func TestCompareEmptyDirectories(t *testing.T) {
  84. var buf bytes.Buffer
  85. ctx := context.Background()
  86. dirModTime := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  87. dirOwnerInfo := fs.OwnerInfo{UserID: 1000, GroupID: 1000}
  88. dirMode := os.FileMode(0o777)
  89. oid1 := oidForString(t, "k", "sdkjfn")
  90. oid2 := oidForString(t, "k", "dfjlgn")
  91. dir1 := createTestDirectory("testDir1", dirModTime, dirOwnerInfo, dirMode, oid1)
  92. dir2 := createTestDirectory("testDir2", dirModTime, dirOwnerInfo, dirMode, oid2)
  93. c, err := diff.NewComparer(&buf, statsOnly)
  94. require.NoError(t, err)
  95. t.Cleanup(func() {
  96. _ = c.Close()
  97. })
  98. expectedStats := diff.Stats{}
  99. actualStats, err := c.Compare(ctx, dir1, dir2)
  100. require.NoError(t, err)
  101. require.Empty(t, buf.String())
  102. require.Equal(t, expectedStats, actualStats)
  103. }
  104. func TestCompareIdenticalDirectories(t *testing.T) {
  105. var buf bytes.Buffer
  106. ctx := context.Background()
  107. dirModTime := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  108. dirOwnerInfo := fs.OwnerInfo{UserID: 1000, GroupID: 1000}
  109. dirMode := os.FileMode(0o777)
  110. fileModTime := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  111. oid1 := oidForString(t, "k", "sdkjfn")
  112. oid2 := oidForString(t, "k", "dfjlgn")
  113. file1 := &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime, name: "file1.txt"}, content: "abcdefghij"}
  114. file2 := &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime, name: "file2.txt"}, content: "klmnopqrstuvwxyz"}
  115. dir1 := createTestDirectory(
  116. "testDir1",
  117. dirModTime,
  118. dirOwnerInfo,
  119. dirMode,
  120. oid1,
  121. file1,
  122. file2,
  123. )
  124. dir2 := createTestDirectory(
  125. "testDir2",
  126. dirModTime,
  127. dirOwnerInfo,
  128. dirMode,
  129. oid2,
  130. file1,
  131. file2,
  132. )
  133. expectedStats := diff.Stats{}
  134. c, err := diff.NewComparer(&buf, statsOnly)
  135. require.NoError(t, err)
  136. t.Cleanup(func() {
  137. _ = c.Close()
  138. })
  139. actualStats, err := c.Compare(ctx, dir1, dir2)
  140. require.NoError(t, err)
  141. require.Empty(t, buf.String())
  142. require.Equal(t, expectedStats, actualStats)
  143. }
  144. func TestCompareDifferentDirectories(t *testing.T) {
  145. var buf bytes.Buffer
  146. ctx := context.Background()
  147. dirModTime := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  148. fileModTime := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  149. dirOwnerInfo := fs.OwnerInfo{UserID: 1000, GroupID: 1000}
  150. dirMode := os.FileMode(0o777)
  151. oid1 := oidForString(t, "k", "sdkjfn")
  152. oid2 := oidForString(t, "k", "dfjlgn")
  153. dir1 := createTestDirectory(
  154. "testDir1",
  155. dirModTime,
  156. dirOwnerInfo,
  157. dirMode,
  158. oid1,
  159. &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime, name: "file1.txt"}, content: "abcdefghij"},
  160. &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime, name: "file2.txt"}, content: "klmnopqrstuvwxyz"},
  161. )
  162. dir2 := createTestDirectory(
  163. "testDir2",
  164. dirModTime,
  165. dirOwnerInfo,
  166. dirMode,
  167. oid2,
  168. &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime, name: "file3.txt"}, content: "abcdefghij1"},
  169. &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime, name: "file4.txt"}, content: "klmnopqrstuvwxyz2"},
  170. )
  171. c, err := diff.NewComparer(&buf, statsOnly)
  172. require.NoError(t, err)
  173. t.Cleanup(func() {
  174. _ = c.Close()
  175. })
  176. expectedStats := diff.Stats{}
  177. expectedStats.FileEntries.Added = 2
  178. expectedStats.FileEntries.Removed = 2
  179. expectedOutput := "added file ./file3.txt (11 bytes)\nadded file ./file4.txt (17 bytes)\n" +
  180. "removed file ./file1.txt (10 bytes)\n" +
  181. "removed file ./file2.txt (16 bytes)\n"
  182. actualStats, err := c.Compare(ctx, dir1, dir2)
  183. require.NoError(t, err)
  184. require.Equal(t, expectedStats, actualStats)
  185. require.Equal(t, expectedOutput, buf.String())
  186. }
  187. func TestCompareDifferentDirectories_DirTimeDiff(t *testing.T) {
  188. var buf bytes.Buffer
  189. ctx := context.Background()
  190. fileModTime := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  191. dirModTime1 := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  192. dirModTime2 := time.Date(2022, time.April, 12, 10, 30, 0, 0, time.UTC)
  193. dirOwnerInfo := fs.OwnerInfo{UserID: 1000, GroupID: 1000}
  194. dirMode := os.FileMode(0o777)
  195. oid1 := oidForString(t, "k", "sdkjfn")
  196. oid2 := oidForString(t, "k", "dfjlgn")
  197. dir1 := createTestDirectory(
  198. "testDir1",
  199. dirModTime1,
  200. dirOwnerInfo,
  201. dirMode,
  202. oid1,
  203. &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime, name: "file1.txt"}, content: "abcdefghij"},
  204. &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime, name: "file2.txt"}, content: "klmnopqrstuvwxyz"},
  205. )
  206. dir2 := createTestDirectory(
  207. "testDir2",
  208. dirModTime2,
  209. dirOwnerInfo,
  210. dirMode,
  211. oid2,
  212. &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime, name: "file1.txt"}, content: "abcdefghij"},
  213. &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime, name: "file2.txt"}, content: "klmnopqrstuvwxyz"},
  214. )
  215. expectedStats := diff.Stats{}
  216. expectedStats.DirectoryEntries.Modified = 1
  217. c, err := diff.NewComparer(&buf, statsOnly)
  218. require.NoError(t, err)
  219. t.Cleanup(func() {
  220. _ = c.Close()
  221. })
  222. expectedOutput := ". modification times differ: 2023-04-12 10:30:00 +0000 UTC 2022-04-12 10:30:00 +0000 UTC\n"
  223. actualStats, err := c.Compare(ctx, dir1, dir2)
  224. require.NoError(t, err)
  225. require.Equal(t, expectedOutput, buf.String())
  226. require.Equal(t, expectedStats, actualStats)
  227. }
  228. func TestCompareDifferentDirectories_FileTimeDiff(t *testing.T) {
  229. var buf bytes.Buffer
  230. ctx := context.Background()
  231. fileModTime1 := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  232. fileModTime2 := time.Date(2022, time.April, 12, 10, 30, 0, 0, time.UTC)
  233. dirModTime := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  234. dirOwnerInfo := fs.OwnerInfo{UserID: 1000, GroupID: 1000}
  235. dirMode := os.FileMode(0o700)
  236. oid1 := oidForString(t, "k", "sdkjfn")
  237. oid2 := oidForString(t, "k", "hvhjb")
  238. dir1 := createTestDirectory(
  239. "testDir1",
  240. dirModTime,
  241. dirOwnerInfo,
  242. dirMode,
  243. oid1,
  244. &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime1, name: "file1.txt", oid: oid1}, content: "abcdefghij"},
  245. )
  246. dir2 := createTestDirectory(
  247. "testDir2",
  248. dirModTime,
  249. dirOwnerInfo,
  250. dirMode,
  251. oid2,
  252. &testFile{testBaseEntry: testBaseEntry{modtime: fileModTime2, name: "file1.txt", oid: oid2}, content: "abcdefghij"},
  253. )
  254. c, err := diff.NewComparer(&buf, statsOnly)
  255. require.NoError(t, err)
  256. t.Cleanup(func() {
  257. _ = c.Close()
  258. })
  259. expectedStats := diff.Stats{}
  260. expectedStats.FileEntries.Modified = 1
  261. expectedOutput := "./file1.txt modification times differ: 2023-04-12 10:30:00 +0000 UTC 2022-04-12 10:30:00 +0000 UTC\n"
  262. actualStats, err := c.Compare(ctx, dir1, dir2)
  263. require.NoError(t, err)
  264. require.Equal(t, expectedOutput, buf.String())
  265. require.Equal(t, expectedStats, actualStats)
  266. }
  267. func TestCompareFileWithIdenticalContentsButDiffFileMetadata(t *testing.T) {
  268. var buf bytes.Buffer
  269. ctx := context.Background()
  270. fileModTime1 := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  271. fileModTime2 := time.Date(2022, time.April, 12, 10, 30, 0, 0, time.UTC)
  272. fileOwnerinfo1 := fs.OwnerInfo{UserID: 1000, GroupID: 1000}
  273. fileOwnerinfo2 := fs.OwnerInfo{UserID: 1001, GroupID: 1002}
  274. dirOwnerInfo := fs.OwnerInfo{UserID: 1000, GroupID: 1000}
  275. dirMode := os.FileMode(0o777)
  276. dirModTime := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  277. oid1 := oidForString(t, "k", "sdkjfn")
  278. oid2 := oidForString(t, "k", "dfjlgn")
  279. dir1 := createTestDirectory(
  280. "testDir1",
  281. dirModTime,
  282. dirOwnerInfo,
  283. dirMode,
  284. oid1,
  285. &testFile{testBaseEntry: testBaseEntry{name: "file1.txt", modtime: fileModTime1, oid: object.ID{}, owner: fileOwnerinfo1, mode: 0o700}, content: "abcdefghij"},
  286. )
  287. dir2 := createTestDirectory(
  288. "testDir2",
  289. dirModTime,
  290. dirOwnerInfo,
  291. dirMode,
  292. oid2,
  293. &testFile{testBaseEntry: testBaseEntry{name: "file1.txt", modtime: fileModTime2, oid: object.ID{}, owner: fileOwnerinfo2, mode: 0o777}, content: "abcdefghij"},
  294. )
  295. c, err := diff.NewComparer(&buf, statsOnly)
  296. require.NoError(t, err)
  297. t.Cleanup(func() {
  298. _ = c.Close()
  299. })
  300. expectedStats := diff.Stats{
  301. FileEntries: diff.EntryTypeStats{
  302. SameContentButDifferentMetadata: 1,
  303. SameContentButDifferentModificationTime: 1,
  304. SameContentButDifferentMode: 1,
  305. SameContentButDifferentUserOwner: 1,
  306. SameContentButDifferentGroupOwner: 1,
  307. },
  308. }
  309. actualStats, err := c.Compare(ctx, dir1, dir2)
  310. require.NoError(t, err)
  311. require.Empty(t, buf.String())
  312. require.Equal(t, expectedStats, actualStats)
  313. }
  314. func TestCompareIdenticalDirectoriesWithDiffDirectoryMetadata(t *testing.T) {
  315. var buf bytes.Buffer
  316. ctx := context.Background()
  317. dirModTime1 := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  318. dirModTime2 := time.Date(2022, time.April, 12, 10, 30, 0, 0, time.UTC)
  319. dirOwnerInfo1 := fs.OwnerInfo{UserID: 1000, GroupID: 1000}
  320. dirOwnerInfo2 := fs.OwnerInfo{UserID: 1001, GroupID: 1002}
  321. dirMode1 := os.FileMode(0o644)
  322. dirMode2 := os.FileMode(0o777)
  323. fileModTime := time.Date(2023, time.April, 12, 10, 30, 0, 0, time.UTC)
  324. oid := oidForString(t, "k", "sdkjfn")
  325. dir1 := createTestDirectory(
  326. "testDir1",
  327. dirModTime1,
  328. dirOwnerInfo1,
  329. dirMode1,
  330. oid,
  331. &testFile{testBaseEntry: testBaseEntry{name: "file1.txt", modtime: fileModTime}, content: "abcdefghij"},
  332. )
  333. dir2 := createTestDirectory(
  334. "testDir2",
  335. dirModTime2,
  336. dirOwnerInfo2,
  337. dirMode2,
  338. oid,
  339. &testFile{testBaseEntry: testBaseEntry{name: "file1.txt", modtime: fileModTime}, content: "abcdefghij"},
  340. )
  341. c, err := diff.NewComparer(&buf, statsOnly)
  342. require.NoError(t, err)
  343. t.Cleanup(func() {
  344. _ = c.Close()
  345. })
  346. expectedStats := diff.Stats{
  347. DirectoryEntries: diff.EntryTypeStats{
  348. SameContentButDifferentMetadata: 1,
  349. SameContentButDifferentModificationTime: 1,
  350. SameContentButDifferentMode: 1,
  351. SameContentButDifferentUserOwner: 1,
  352. SameContentButDifferentGroupOwner: 1,
  353. },
  354. }
  355. actualStats, err := c.Compare(ctx, dir1, dir2)
  356. require.NoError(t, err)
  357. require.Empty(t, buf.String())
  358. require.Equal(t, expectedStats, actualStats)
  359. }
  360. func createTestDirectory(name string, modtime time.Time, owner fs.OwnerInfo, mode os.FileMode, oid object.ID, files ...fs.Entry) *testDirectory {
  361. return &testDirectory{testBaseEntry: testBaseEntry{modtime: modtime, name: name, owner: owner, mode: mode, oid: oid}, files: files}
  362. }
  363. func getManifests(t *testing.T) map[string]*snapshot.Manifest {
  364. t.Helper()
  365. // manifests store snapshot manifests based on start-time
  366. manifests := make(map[string]*snapshot.Manifest, 3)
  367. src := getSnapshotSource()
  368. snapshotTime := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
  369. rootEntry1 := snapshot.DirEntry{
  370. ObjectID: oidForString(t, "", "indexID1"),
  371. }
  372. rootEntry2 := snapshot.DirEntry{
  373. ObjectID: oidForString(t, "", "indexID2"),
  374. }
  375. manifests["initial_snapshot"] = &snapshot.Manifest{
  376. ID: "manifest_1_id",
  377. Source: src,
  378. StartTime: fs.UTCTimestamp(snapshotTime.Add((-24) * time.Hour).UnixNano()),
  379. Description: "snapshot captured a day ago",
  380. RootEntry: &rootEntry2,
  381. }
  382. manifests["intermediate_snapshot"] = &snapshot.Manifest{
  383. ID: "manifest_2_id",
  384. Source: src,
  385. StartTime: fs.UTCTimestamp(snapshotTime.Add(-time.Hour).UnixNano()),
  386. Description: "snapshot taken an hour ago",
  387. RootEntry: &rootEntry2,
  388. }
  389. manifests["latest_snapshot"] = &snapshot.Manifest{
  390. ID: "manifest_3_id",
  391. Source: src,
  392. StartTime: fs.UTCTimestamp(snapshotTime.UnixNano()),
  393. Description: "latest snapshot",
  394. RootEntry: &rootEntry1,
  395. }
  396. return manifests
  397. }
  398. // Tests GetPrecedingSnapshot function
  399. // - GetPrecedingSnapshot with an invalid snapshot id and expect an error;
  400. // - Add a snapshot, expect an error from GetPrecedingSnapshot since there is
  401. // only a single snapshot in the repo;
  402. // - Subsequently add more snapshots and GetPrecedingSnapshot the immediately
  403. // preceding with no error.
  404. func TestGetPrecedingSnapshot(t *testing.T) {
  405. ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant)
  406. manifests := getManifests(t)
  407. _, err := diff.GetPrecedingSnapshot(ctx, env.RepositoryWriter, "non_existent_snapshot_ID")
  408. require.Error(t, err, "expect error when calling GetPrecedingSnapshot with a wrong snapshotID")
  409. initialSnapshotManifestID := mustSaveSnapshot(t, env.RepositoryWriter, manifests["initial_snapshot"])
  410. _, err = diff.GetPrecedingSnapshot(ctx, env.RepositoryWriter, string(initialSnapshotManifestID))
  411. require.Error(t, err, "expect error when there is a single snapshot in the repo")
  412. intermediateSnapshotManifestID := mustSaveSnapshot(t, env.RepositoryWriter, manifests["intermediate_snapshot"])
  413. gotManID, err := diff.GetPrecedingSnapshot(ctx, env.RepositoryWriter, string(intermediateSnapshotManifestID))
  414. require.NoError(t, err)
  415. require.Equal(t, initialSnapshotManifestID, gotManID.ID)
  416. latestSnapshotManifestID := mustSaveSnapshot(t, env.RepositoryWriter, manifests["latest_snapshot"])
  417. gotManID2, err := diff.GetPrecedingSnapshot(ctx, env.RepositoryWriter, string(latestSnapshotManifestID))
  418. require.NoError(t, err)
  419. require.Equal(t, intermediateSnapshotManifestID, gotManID2.ID)
  420. }
  421. // First call GetTwoLatestSnapshots with insufficient snapshots in the repo and
  422. // expect an error;
  423. // As snapshots are added, GetTwoLatestSnapshots is expected to return the
  424. // manifests for the two most recent snapshots for a the given source.
  425. func TestGetTwoLatestSnapshots(t *testing.T) {
  426. ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant)
  427. snapshotSrc := getSnapshotSource()
  428. manifests := getManifests(t)
  429. _, _, err := diff.GetTwoLatestSnapshotsForASource(ctx, env.RepositoryWriter, snapshotSrc)
  430. require.Error(t, err, "expected error as there aren't enough snapshots to get the two most recent snapshots")
  431. initialSnapshotManifestID := mustSaveSnapshot(t, env.RepositoryWriter, manifests["initial_snapshot"])
  432. _, _, err = diff.GetTwoLatestSnapshotsForASource(ctx, env.RepositoryWriter, snapshotSrc)
  433. require.Error(t, err, "expected error as there aren't enough snapshots to get the two most recent snapshots")
  434. intermediateSnapshotManifestID := mustSaveSnapshot(t, env.RepositoryWriter, manifests["intermediate_snapshot"])
  435. var expectedManifestIDs []manifest.ID
  436. expectedManifestIDs = append(expectedManifestIDs, initialSnapshotManifestID, intermediateSnapshotManifestID)
  437. secondLastSnapshot, lastSnapshot, err := diff.GetTwoLatestSnapshotsForASource(ctx, env.RepositoryWriter, snapshotSrc)
  438. var gotManifestIDs []manifest.ID
  439. gotManifestIDs = append(gotManifestIDs, secondLastSnapshot.ID, lastSnapshot.ID)
  440. require.NoError(t, err)
  441. require.Equal(t, expectedManifestIDs, gotManifestIDs)
  442. latestSnapshotManifestID := mustSaveSnapshot(t, env.RepositoryWriter, manifests["latest_snapshot"])
  443. expectedManifestIDs = nil
  444. expectedManifestIDs = append(expectedManifestIDs, intermediateSnapshotManifestID, latestSnapshotManifestID)
  445. gotManifestIDs = nil
  446. secondLastSnapshot, lastSnapshot, err = diff.GetTwoLatestSnapshotsForASource(ctx, env.RepositoryWriter, snapshotSrc)
  447. gotManifestIDs = append(gotManifestIDs, secondLastSnapshot.ID, lastSnapshot.ID)
  448. require.NoError(t, err)
  449. require.Equal(t, expectedManifestIDs, gotManifestIDs)
  450. }
  451. func mustSaveSnapshot(t *testing.T, rep repo.RepositoryWriter, man *snapshot.Manifest) manifest.ID {
  452. t.Helper()
  453. id, err := snapshot.SaveSnapshot(testlogging.Context(t), rep, man)
  454. require.NoError(t, err, "saving snapshot")
  455. return id
  456. }
  457. func getSnapshotSource() snapshot.SourceInfo {
  458. src := snapshot.SourceInfo{
  459. Host: "host-1",
  460. UserName: "user-1",
  461. Path: "/some/path",
  462. }
  463. return src
  464. }
  465. func oidForString(t *testing.T, prefix content.IDPrefix, s string) object.ID {
  466. t.Helper()
  467. return oidForContent(t, prefix, []byte(s))
  468. }
  469. func oidForContent(t *testing.T, prefix content.IDPrefix, c []byte) object.ID {
  470. t.Helper()
  471. h := blake3.New()
  472. _, err := h.Write(c)
  473. require.NoError(t, err)
  474. cid, err := content.IDFromHash(prefix, h.Sum(nil))
  475. require.NoError(t, err)
  476. return object.DirectObjectID(cid)
  477. }