db_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package db
  7. import (
  8. "bytes"
  9. "testing"
  10. "github.com/syncthing/syncthing/lib/db/backend"
  11. "github.com/syncthing/syncthing/lib/fs"
  12. "github.com/syncthing/syncthing/lib/protocol"
  13. )
  14. func genBlocks(n int) []protocol.BlockInfo {
  15. b := make([]protocol.BlockInfo, n)
  16. for i := range b {
  17. h := make([]byte, 32)
  18. for j := range h {
  19. h[j] = byte(i + j)
  20. }
  21. b[i].Size = int32(i)
  22. b[i].Hash = h
  23. }
  24. return b
  25. }
  26. func TestIgnoredFiles(t *testing.T) {
  27. ldb, err := openJSONS("testdata/v0.14.48-ignoredfiles.db.jsons")
  28. if err != nil {
  29. t.Fatal(err)
  30. }
  31. db := NewLowlevel(ldb)
  32. defer db.Close()
  33. if err := UpdateSchema(db); err != nil {
  34. t.Fatal(err)
  35. }
  36. fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
  37. // The contents of the database are like this:
  38. //
  39. // fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
  40. // fs.Update(protocol.LocalDeviceID, []protocol.FileInfo{
  41. // { // invalid (ignored) file
  42. // Name: "foo",
  43. // Type: protocol.FileInfoTypeFile,
  44. // Invalid: true,
  45. // Version: protocol.Vector{Counters: []protocol.Counter{{ID: 1, Value: 1000}}},
  46. // },
  47. // { // regular file
  48. // Name: "bar",
  49. // Type: protocol.FileInfoTypeFile,
  50. // Version: protocol.Vector{Counters: []protocol.Counter{{ID: 1, Value: 1001}}},
  51. // },
  52. // })
  53. // fs.Update(protocol.DeviceID{42}, []protocol.FileInfo{
  54. // { // invalid file
  55. // Name: "baz",
  56. // Type: protocol.FileInfoTypeFile,
  57. // Invalid: true,
  58. // Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1000}}},
  59. // },
  60. // { // regular file
  61. // Name: "quux",
  62. // Type: protocol.FileInfoTypeFile,
  63. // Version: protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 1002}}},
  64. // },
  65. // })
  66. // Local files should have the "ignored" bit in addition to just being
  67. // generally invalid if we want to look at the simulation of that bit.
  68. snap := fs.Snapshot()
  69. defer snap.Release()
  70. fi, ok := snap.Get(protocol.LocalDeviceID, "foo")
  71. if !ok {
  72. t.Fatal("foo should exist")
  73. }
  74. if !fi.IsInvalid() {
  75. t.Error("foo should be invalid")
  76. }
  77. if !fi.IsIgnored() {
  78. t.Error("foo should be ignored")
  79. }
  80. fi, ok = snap.Get(protocol.LocalDeviceID, "bar")
  81. if !ok {
  82. t.Fatal("bar should exist")
  83. }
  84. if fi.IsInvalid() {
  85. t.Error("bar should not be invalid")
  86. }
  87. if fi.IsIgnored() {
  88. t.Error("bar should not be ignored")
  89. }
  90. // Remote files have the invalid bit as usual, and the IsInvalid() method
  91. // should pick this up too.
  92. fi, ok = snap.Get(protocol.DeviceID{42}, "baz")
  93. if !ok {
  94. t.Fatal("baz should exist")
  95. }
  96. if !fi.IsInvalid() {
  97. t.Error("baz should be invalid")
  98. }
  99. if !fi.IsInvalid() {
  100. t.Error("baz should be invalid")
  101. }
  102. fi, ok = snap.Get(protocol.DeviceID{42}, "quux")
  103. if !ok {
  104. t.Fatal("quux should exist")
  105. }
  106. if fi.IsInvalid() {
  107. t.Error("quux should not be invalid")
  108. }
  109. if fi.IsInvalid() {
  110. t.Error("quux should not be invalid")
  111. }
  112. }
  113. const myID = 1
  114. var (
  115. remoteDevice0, remoteDevice1 protocol.DeviceID
  116. update0to3Folder = "UpdateSchema0to3"
  117. invalid = "invalid"
  118. slashPrefixed = "/notgood"
  119. haveUpdate0to3 map[protocol.DeviceID]fileList
  120. )
  121. func init() {
  122. remoteDevice0, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
  123. remoteDevice1, _ = protocol.DeviceIDFromString("I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU")
  124. haveUpdate0to3 = map[protocol.DeviceID]fileList{
  125. protocol.LocalDeviceID: {
  126. protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)},
  127. protocol.FileInfo{Name: slashPrefixed, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)},
  128. },
  129. remoteDevice0: {
  130. protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)},
  131. protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(5), RawInvalid: true},
  132. protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, Blocks: genBlocks(7)},
  133. },
  134. remoteDevice1: {
  135. protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(7)},
  136. protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, Blocks: genBlocks(5), RawInvalid: true},
  137. protocol.FileInfo{Name: invalid, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1004}}}, Blocks: genBlocks(5), RawInvalid: true},
  138. },
  139. }
  140. }
  141. func TestUpdate0to3(t *testing.T) {
  142. ldb, err := openJSONS("testdata/v0.14.45-update0to3.db.jsons")
  143. if err != nil {
  144. t.Fatal(err)
  145. }
  146. db := NewLowlevel(ldb)
  147. defer db.Close()
  148. updater := schemaUpdater{db}
  149. folder := []byte(update0to3Folder)
  150. if err := updater.updateSchema0to1(0); err != nil {
  151. t.Fatal(err)
  152. }
  153. trans, err := db.newReadOnlyTransaction()
  154. if err != nil {
  155. t.Fatal(err)
  156. }
  157. defer trans.Release()
  158. if _, ok, err := trans.getFile(folder, protocol.LocalDeviceID[:], []byte(slashPrefixed)); err != nil {
  159. t.Fatal(err)
  160. } else if ok {
  161. t.Error("File prefixed by '/' was not removed during transition to schema 1")
  162. }
  163. var key []byte
  164. key, err = db.keyer.GenerateGlobalVersionKey(nil, folder, []byte(invalid))
  165. if err != nil {
  166. t.Fatal(err)
  167. }
  168. if _, err := db.Get(key); err != nil {
  169. t.Error("Invalid file wasn't added to global list")
  170. }
  171. if err := updater.updateSchema1to2(1); err != nil {
  172. t.Fatal(err)
  173. }
  174. found := false
  175. trans, err = db.newReadOnlyTransaction()
  176. if err != nil {
  177. t.Fatal(err)
  178. }
  179. defer trans.Release()
  180. _ = trans.withHaveSequence(folder, 0, func(fi protocol.FileIntf) bool {
  181. f := fi.(protocol.FileInfo)
  182. l.Infoln(f)
  183. if found {
  184. t.Error("Unexpected additional file via sequence", f.FileName())
  185. return true
  186. }
  187. if e := haveUpdate0to3[protocol.LocalDeviceID][0]; f.IsEquivalentOptional(e, 0, true, true, 0) {
  188. found = true
  189. } else {
  190. t.Errorf("Wrong file via sequence, got %v, expected %v", f, e)
  191. }
  192. return true
  193. })
  194. if !found {
  195. t.Error("Local file wasn't added to sequence bucket", err)
  196. }
  197. if err := updater.updateSchema2to3(2); err != nil {
  198. t.Fatal(err)
  199. }
  200. need := map[string]protocol.FileInfo{
  201. haveUpdate0to3[remoteDevice0][0].Name: haveUpdate0to3[remoteDevice0][0],
  202. haveUpdate0to3[remoteDevice1][0].Name: haveUpdate0to3[remoteDevice1][0],
  203. haveUpdate0to3[remoteDevice0][2].Name: haveUpdate0to3[remoteDevice0][2],
  204. }
  205. trans, err = db.newReadOnlyTransaction()
  206. if err != nil {
  207. t.Fatal(err)
  208. }
  209. defer trans.Release()
  210. key, err = trans.keyer.GenerateNeedFileKey(nil, folder, nil)
  211. if err != nil {
  212. t.Fatal(err)
  213. }
  214. dbi, err := trans.NewPrefixIterator(key)
  215. if err != nil {
  216. t.Fatal(err)
  217. }
  218. defer dbi.Release()
  219. for dbi.Next() {
  220. name := trans.keyer.NameFromGlobalVersionKey(dbi.Key())
  221. key, err = trans.keyer.GenerateGlobalVersionKey(key, folder, name)
  222. bs, err := trans.Get(key)
  223. if err != nil {
  224. t.Fatal(err)
  225. }
  226. var vl VersionListDeprecated
  227. if err := vl.Unmarshal(bs); err != nil {
  228. t.Fatal(err)
  229. }
  230. key, err = trans.keyer.GenerateDeviceFileKey(key, folder, vl.Versions[0].Device, name)
  231. fi, ok, err := trans.getFileTrunc(key, false)
  232. if err != nil {
  233. t.Fatal(err)
  234. }
  235. if !ok {
  236. device := "<invalid>"
  237. if dev, err := protocol.DeviceIDFromBytes(vl.Versions[0].Device); err != nil {
  238. device = dev.String()
  239. }
  240. t.Fatal("surprise missing global file", string(name), device)
  241. }
  242. e, ok := need[fi.FileName()]
  243. if !ok {
  244. t.Error("Got unexpected needed file:", fi.FileName())
  245. }
  246. f := fi.(protocol.FileInfo)
  247. delete(need, f.Name)
  248. if !f.IsEquivalentOptional(e, 0, true, true, 0) {
  249. t.Errorf("Wrong needed file, got %v, expected %v", f, e)
  250. }
  251. }
  252. if dbi.Error() != nil {
  253. t.Fatal(err)
  254. }
  255. for n := range need {
  256. t.Errorf(`Missing needed file "%v"`, n)
  257. }
  258. }
  259. // TestRepairSequence checks that a few hand-crafted messed-up sequence entries get fixed.
  260. func TestRepairSequence(t *testing.T) {
  261. db := NewLowlevel(backend.OpenMemory())
  262. defer db.Close()
  263. folderStr := "test"
  264. folder := []byte(folderStr)
  265. id := protocol.LocalDeviceID
  266. short := protocol.LocalDeviceID.Short()
  267. files := []protocol.FileInfo{
  268. {Name: "fine", Blocks: genBlocks(1)},
  269. {Name: "duplicate", Blocks: genBlocks(2)},
  270. {Name: "missing", Blocks: genBlocks(3)},
  271. {Name: "overwriting", Blocks: genBlocks(4)},
  272. {Name: "inconsistent", Blocks: genBlocks(5)},
  273. }
  274. for i, f := range files {
  275. files[i].Version = f.Version.Update(short)
  276. }
  277. trans, err := db.newReadWriteTransaction()
  278. if err != nil {
  279. t.Fatal(err)
  280. }
  281. defer trans.close()
  282. addFile := func(f protocol.FileInfo, seq int64) {
  283. dk, err := trans.keyer.GenerateDeviceFileKey(nil, folder, id[:], []byte(f.Name))
  284. if err != nil {
  285. t.Fatal(err)
  286. }
  287. if err := trans.putFile(dk, f, false); err != nil {
  288. t.Fatal(err)
  289. }
  290. sk, err := trans.keyer.GenerateSequenceKey(nil, folder, seq)
  291. if err != nil {
  292. t.Fatal(err)
  293. }
  294. if err := trans.Put(sk, dk); err != nil {
  295. t.Fatal(err)
  296. }
  297. }
  298. // Plain normal entry
  299. var seq int64 = 1
  300. files[0].Sequence = 1
  301. addFile(files[0], seq)
  302. // Second entry once updated with original sequence still in place
  303. f := files[1]
  304. f.Sequence = int64(len(files) + 1)
  305. addFile(f, f.Sequence)
  306. // Original sequence entry
  307. seq++
  308. sk, err := trans.keyer.GenerateSequenceKey(nil, folder, seq)
  309. if err != nil {
  310. t.Fatal(err)
  311. }
  312. dk, err := trans.keyer.GenerateDeviceFileKey(nil, folder, id[:], []byte(f.Name))
  313. if err != nil {
  314. t.Fatal(err)
  315. }
  316. if err := trans.Put(sk, dk); err != nil {
  317. t.Fatal(err)
  318. }
  319. // File later overwritten thus missing sequence entry
  320. seq++
  321. files[2].Sequence = seq
  322. addFile(files[2], seq)
  323. // File overwriting previous sequence entry (no seq bump)
  324. seq++
  325. files[3].Sequence = seq
  326. addFile(files[3], seq)
  327. // Inconistent file
  328. seq++
  329. files[4].Sequence = 101
  330. addFile(files[4], seq)
  331. // And a sequence entry pointing at nothing because why not
  332. sk, err = trans.keyer.GenerateSequenceKey(nil, folder, 100001)
  333. if err != nil {
  334. t.Fatal(err)
  335. }
  336. dk, err = trans.keyer.GenerateDeviceFileKey(nil, folder, id[:], []byte("nonexisting"))
  337. if err != nil {
  338. t.Fatal(err)
  339. }
  340. if err := trans.Put(sk, dk); err != nil {
  341. t.Fatal(err)
  342. }
  343. if err := trans.Commit(); err != nil {
  344. t.Fatal(err)
  345. }
  346. // Loading the metadata for the first time means a "re"calculation happens,
  347. // along which the sequences get repaired too.
  348. db.gcMut.RLock()
  349. _ = db.loadMetadataTracker(folderStr)
  350. db.gcMut.RUnlock()
  351. if err != nil {
  352. t.Fatal(err)
  353. }
  354. // Check the db
  355. ro, err := db.newReadOnlyTransaction()
  356. if err != nil {
  357. t.Fatal(err)
  358. }
  359. defer ro.close()
  360. it, err := ro.NewPrefixIterator([]byte{KeyTypeDevice})
  361. if err != nil {
  362. t.Fatal(err)
  363. }
  364. defer it.Release()
  365. for it.Next() {
  366. fi, err := ro.unmarshalTrunc(it.Value(), true)
  367. if err != nil {
  368. t.Fatal(err)
  369. }
  370. if sk, err = ro.keyer.GenerateSequenceKey(sk, folder, fi.SequenceNo()); err != nil {
  371. t.Fatal(err)
  372. }
  373. dk, err := ro.Get(sk)
  374. if backend.IsNotFound(err) {
  375. t.Error("Missing sequence entry for", fi.FileName())
  376. } else if err != nil {
  377. t.Fatal(err)
  378. }
  379. if !bytes.Equal(it.Key(), dk) {
  380. t.Errorf("Wrong key for %v, expected %s, got %s", f.FileName(), it.Key(), dk)
  381. }
  382. }
  383. if err := it.Error(); err != nil {
  384. t.Fatal(err)
  385. }
  386. it.Release()
  387. it, err = ro.NewPrefixIterator([]byte{KeyTypeSequence})
  388. if err != nil {
  389. t.Fatal(err)
  390. }
  391. defer it.Release()
  392. for it.Next() {
  393. intf, ok, err := ro.getFileTrunc(it.Value(), false)
  394. if err != nil {
  395. t.Fatal(err)
  396. }
  397. fi := intf.(protocol.FileInfo)
  398. seq := ro.keyer.SequenceFromSequenceKey(it.Key())
  399. if !ok {
  400. t.Errorf("Sequence entry %v points at nothing", seq)
  401. } else if fi.SequenceNo() != seq {
  402. t.Errorf("Inconsistent sequence entry for %v: %v != %v", fi.FileName(), fi.SequenceNo(), seq)
  403. }
  404. if len(fi.Blocks) == 0 {
  405. t.Error("Missing blocks in", fi.FileName())
  406. }
  407. }
  408. if err := it.Error(); err != nil {
  409. t.Fatal(err)
  410. }
  411. it.Release()
  412. }
  413. func TestDowngrade(t *testing.T) {
  414. db := NewLowlevel(backend.OpenMemory())
  415. defer db.Close()
  416. // sets the min version etc
  417. if err := UpdateSchema(db); err != nil {
  418. t.Fatal(err)
  419. }
  420. // Bump the database version to something newer than we actually support
  421. miscDB := NewMiscDataNamespace(db)
  422. if err := miscDB.PutInt64("dbVersion", dbVersion+1); err != nil {
  423. t.Fatal(err)
  424. }
  425. l.Infoln(dbVersion)
  426. // Pretend we just opened the DB and attempt to update it again
  427. err := UpdateSchema(db)
  428. if err, ok := err.(databaseDowngradeError); !ok {
  429. t.Fatal("Expected error due to database downgrade, got", err)
  430. } else if err.minSyncthingVersion != dbMinSyncthingVersion {
  431. t.Fatalf("Error has %v as min Syncthing version, expected %v", err.minSyncthingVersion, dbMinSyncthingVersion)
  432. }
  433. }
  434. func TestCheckGlobals(t *testing.T) {
  435. db := NewLowlevel(backend.OpenMemory())
  436. defer db.Close()
  437. fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), db)
  438. // Add any file
  439. name := "foo"
  440. fs.Update(protocol.LocalDeviceID, []protocol.FileInfo{
  441. {
  442. Name: name,
  443. Type: protocol.FileInfoTypeFile,
  444. Version: protocol.Vector{Counters: []protocol.Counter{{ID: 1, Value: 1001}}},
  445. },
  446. })
  447. // Remove just the file entry
  448. if err := db.dropPrefix([]byte{KeyTypeDevice}); err != nil {
  449. t.Fatal(err)
  450. }
  451. // Clean up global entry of the now missing file
  452. if err := db.checkGlobals([]byte(fs.folder)); err != nil {
  453. t.Fatal(err)
  454. }
  455. // Check that the global entry is gone
  456. gk, err := db.keyer.GenerateGlobalVersionKey(nil, []byte(fs.folder), []byte(name))
  457. if err != nil {
  458. t.Fatal(err)
  459. }
  460. _, err = db.Get(gk)
  461. if !backend.IsNotFound(err) {
  462. t.Error("Expected key missing error, got", err)
  463. }
  464. }
  465. func TestUpdateTo10(t *testing.T) {
  466. ldb, err := openJSONS("./testdata/v1.4.0-updateTo10.json")
  467. if err != nil {
  468. t.Fatal(err)
  469. }
  470. db := NewLowlevel(ldb)
  471. defer db.Close()
  472. UpdateSchema(db)
  473. folder := "test"
  474. meta := db.getMetaAndCheck(folder)
  475. empty := Counts{}
  476. c := meta.Counts(protocol.LocalDeviceID, needFlag)
  477. if c.Files != 1 {
  478. t.Error("Expected 1 needed file locally, got", c.Files)
  479. }
  480. c.Files = 0
  481. if c.Deleted != 1 {
  482. t.Error("Expected 1 needed deletion locally, got", c.Deleted)
  483. }
  484. c.Deleted = 0
  485. if !c.Equal(empty) {
  486. t.Error("Expected all counts to be zero, got", c)
  487. }
  488. c = meta.Counts(remoteDevice0, needFlag)
  489. if !c.Equal(empty) {
  490. t.Error("Expected all counts to be zero, got", c)
  491. }
  492. trans, err := db.newReadOnlyTransaction()
  493. if err != nil {
  494. t.Fatal(err)
  495. }
  496. defer trans.Release()
  497. // a
  498. vl, err := trans.getGlobalVersions(nil, []byte(folder), []byte("a"))
  499. if err != nil {
  500. t.Fatal(err)
  501. }
  502. for _, v := range vl.RawVersions {
  503. if !v.Deleted {
  504. t.Error("Unexpected undeleted global version for a")
  505. }
  506. }
  507. // b
  508. vl, err = trans.getGlobalVersions(nil, []byte(folder), []byte("b"))
  509. if err != nil {
  510. t.Fatal(err)
  511. }
  512. if !vl.RawVersions[0].Deleted {
  513. t.Error("vl.Versions[0] not deleted for b")
  514. }
  515. if vl.RawVersions[1].Deleted {
  516. t.Error("vl.Versions[1] deleted for b")
  517. }
  518. // c
  519. vl, err = trans.getGlobalVersions(nil, []byte(folder), []byte("c"))
  520. if err != nil {
  521. t.Fatal(err)
  522. }
  523. if vl.RawVersions[0].Deleted {
  524. t.Error("vl.Versions[0] deleted for c")
  525. }
  526. if !vl.RawVersions[1].Deleted {
  527. t.Error("vl.Versions[1] not deleted for c")
  528. }
  529. }
  530. func TestDropDuplicates(t *testing.T) {
  531. names := []string{
  532. "foo",
  533. "bar",
  534. "dcxvoijnds",
  535. "3d/dsfase/4/ss2",
  536. }
  537. tcs := []struct{ in, out []int }{
  538. {[]int{0}, []int{0}},
  539. {[]int{0, 1}, []int{0, 1}},
  540. {[]int{0, 1, 0, 1}, []int{0, 1}},
  541. {[]int{0, 1, 1, 1, 1}, []int{0, 1}},
  542. {[]int{0, 0, 0, 1}, []int{0, 1}},
  543. {[]int{0, 1, 2, 3}, []int{0, 1, 2, 3}},
  544. {[]int{3, 2, 1, 0, 0, 1, 2, 3}, []int{0, 1, 2, 3}},
  545. {[]int{0, 1, 1, 3, 0, 1, 0, 1, 2, 3}, []int{0, 1, 2, 3}},
  546. }
  547. for tci, tc := range tcs {
  548. inp := make([]protocol.FileInfo, len(tc.in))
  549. expSeq := make(map[string]int)
  550. for i, j := range tc.in {
  551. inp[i] = protocol.FileInfo{Name: names[j], Sequence: int64(i)}
  552. expSeq[names[j]] = i
  553. }
  554. outp := normalizeFilenamesAndDropDuplicates(inp)
  555. if len(outp) != len(tc.out) {
  556. t.Errorf("tc %v: Expected %v entries, got %v", tci, len(tc.out), len(outp))
  557. continue
  558. }
  559. for i, f := range outp {
  560. if exp := names[tc.out[i]]; exp != f.Name {
  561. t.Errorf("tc %v: Got file %v at pos %v, expected %v", tci, f.Name, i, exp)
  562. }
  563. if exp := int64(expSeq[outp[i].Name]); exp != f.Sequence {
  564. t.Errorf("tc %v: Got sequence %v at pos %v, expected %v", tci, f.Sequence, i, exp)
  565. }
  566. }
  567. }
  568. }