model_test.go 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334
  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 http://mozilla.org/MPL/2.0/.
  6. package model
  7. import (
  8. "bytes"
  9. "encoding/json"
  10. "fmt"
  11. "io/ioutil"
  12. "math/rand"
  13. "net"
  14. "os"
  15. "path/filepath"
  16. "runtime"
  17. "strconv"
  18. "testing"
  19. "time"
  20. "github.com/d4l3k/messagediff"
  21. "github.com/syncthing/syncthing/lib/config"
  22. "github.com/syncthing/syncthing/lib/db"
  23. "github.com/syncthing/syncthing/lib/protocol"
  24. )
  25. var device1, device2 protocol.DeviceID
  26. var defaultConfig *config.Wrapper
  27. var defaultFolderConfig config.FolderConfiguration
  28. func init() {
  29. device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
  30. device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
  31. defaultFolderConfig = config.NewFolderConfiguration("default", "testdata")
  32. defaultFolderConfig.Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}}
  33. _defaultConfig := config.Configuration{
  34. Folders: []config.FolderConfiguration{defaultFolderConfig},
  35. Devices: []config.DeviceConfiguration{config.NewDeviceConfiguration(device1, "device1")},
  36. Options: config.OptionsConfiguration{
  37. // Don't remove temporaries directly on startup
  38. KeepTemporariesH: 1,
  39. },
  40. }
  41. defaultConfig = config.Wrap("/tmp/test", _defaultConfig)
  42. }
  43. var testDataExpected = map[string]protocol.FileInfo{
  44. "foo": {
  45. Name: "foo",
  46. Flags: 0,
  47. Modified: 0,
  48. Blocks: []protocol.BlockInfo{{Offset: 0x0, Size: 0x7, Hash: []uint8{0xae, 0xc0, 0x70, 0x64, 0x5f, 0xe5, 0x3e, 0xe3, 0xb3, 0x76, 0x30, 0x59, 0x37, 0x61, 0x34, 0xf0, 0x58, 0xcc, 0x33, 0x72, 0x47, 0xc9, 0x78, 0xad, 0xd1, 0x78, 0xb6, 0xcc, 0xdf, 0xb0, 0x1, 0x9f}}},
  49. },
  50. "empty": {
  51. Name: "empty",
  52. Flags: 0,
  53. Modified: 0,
  54. Blocks: []protocol.BlockInfo{{Offset: 0x0, Size: 0x0, Hash: []uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}}},
  55. },
  56. "bar": {
  57. Name: "bar",
  58. Flags: 0,
  59. Modified: 0,
  60. Blocks: []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}},
  61. },
  62. }
  63. func init() {
  64. // Fix expected test data to match reality
  65. for n, f := range testDataExpected {
  66. fi, _ := os.Stat("testdata/" + n)
  67. f.Flags = uint32(fi.Mode())
  68. f.Modified = fi.ModTime().Unix()
  69. testDataExpected[n] = f
  70. }
  71. }
  72. func TestRequest(t *testing.T) {
  73. db := db.OpenMemory()
  74. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  75. // device1 shares default, but device2 doesn't
  76. m.AddFolder(defaultFolderConfig)
  77. m.StartFolderRO("default")
  78. m.ServeBackground()
  79. m.ScanFolder("default")
  80. bs := make([]byte, protocol.BlockSize)
  81. // Existing, shared file
  82. bs = bs[:6]
  83. err := m.Request(device1, "default", "foo", 0, nil, 0, nil, bs)
  84. if err != nil {
  85. t.Error(err)
  86. }
  87. if !bytes.Equal(bs, []byte("foobar")) {
  88. t.Errorf("Incorrect data from request: %q", string(bs))
  89. }
  90. // Existing, nonshared file
  91. err = m.Request(device2, "default", "foo", 0, nil, 0, nil, bs)
  92. if err == nil {
  93. t.Error("Unexpected nil error on insecure file read")
  94. }
  95. // Nonexistent file
  96. err = m.Request(device1, "default", "nonexistent", 0, nil, 0, nil, bs)
  97. if err == nil {
  98. t.Error("Unexpected nil error on insecure file read")
  99. }
  100. // Shared folder, but disallowed file name
  101. err = m.Request(device1, "default", "../walk.go", 0, nil, 0, nil, bs)
  102. if err == nil {
  103. t.Error("Unexpected nil error on insecure file read")
  104. }
  105. // Negative offset
  106. err = m.Request(device1, "default", "foo", -4, nil, 0, nil, bs[:0])
  107. if err == nil {
  108. t.Error("Unexpected nil error on insecure file read")
  109. }
  110. // Larger block than available
  111. bs = bs[:42]
  112. err = m.Request(device1, "default", "foo", 0, nil, 0, nil, bs)
  113. if err == nil {
  114. t.Error("Unexpected nil error on insecure file read")
  115. }
  116. }
  117. func genFiles(n int) []protocol.FileInfo {
  118. files := make([]protocol.FileInfo, n)
  119. t := time.Now().Unix()
  120. for i := 0; i < n; i++ {
  121. files[i] = protocol.FileInfo{
  122. Name: fmt.Sprintf("file%d", i),
  123. Modified: t,
  124. Blocks: []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}},
  125. }
  126. }
  127. return files
  128. }
  129. func BenchmarkIndex_10000(b *testing.B) {
  130. benchmarkIndex(b, 10000)
  131. }
  132. func BenchmarkIndex_100(b *testing.B) {
  133. benchmarkIndex(b, 100)
  134. }
  135. func benchmarkIndex(b *testing.B, nfiles int) {
  136. db := db.OpenMemory()
  137. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  138. m.AddFolder(defaultFolderConfig)
  139. m.StartFolderRO("default")
  140. m.ServeBackground()
  141. files := genFiles(nfiles)
  142. m.Index(device1, "default", files, 0, nil)
  143. b.ResetTimer()
  144. for i := 0; i < b.N; i++ {
  145. m.Index(device1, "default", files, 0, nil)
  146. }
  147. b.ReportAllocs()
  148. }
  149. func BenchmarkIndexUpdate_10000_10000(b *testing.B) {
  150. benchmarkIndexUpdate(b, 10000, 10000)
  151. }
  152. func BenchmarkIndexUpdate_10000_100(b *testing.B) {
  153. benchmarkIndexUpdate(b, 10000, 100)
  154. }
  155. func BenchmarkIndexUpdate_10000_1(b *testing.B) {
  156. benchmarkIndexUpdate(b, 10000, 1)
  157. }
  158. func benchmarkIndexUpdate(b *testing.B, nfiles, nufiles int) {
  159. db := db.OpenMemory()
  160. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  161. m.AddFolder(defaultFolderConfig)
  162. m.StartFolderRO("default")
  163. m.ServeBackground()
  164. files := genFiles(nfiles)
  165. ufiles := genFiles(nufiles)
  166. m.Index(device1, "default", files, 0, nil)
  167. b.ResetTimer()
  168. for i := 0; i < b.N; i++ {
  169. m.IndexUpdate(device1, "default", ufiles, 0, nil)
  170. }
  171. b.ReportAllocs()
  172. }
  173. type FakeConnection struct {
  174. id protocol.DeviceID
  175. requestData []byte
  176. }
  177. func (FakeConnection) Close() error {
  178. return nil
  179. }
  180. func (f FakeConnection) Start() {
  181. }
  182. func (f FakeConnection) ID() protocol.DeviceID {
  183. return f.id
  184. }
  185. func (f FakeConnection) Name() string {
  186. return ""
  187. }
  188. func (f FakeConnection) Option(string) string {
  189. return ""
  190. }
  191. func (FakeConnection) Index(string, []protocol.FileInfo, uint32, []protocol.Option) error {
  192. return nil
  193. }
  194. func (FakeConnection) IndexUpdate(string, []protocol.FileInfo, uint32, []protocol.Option) error {
  195. return nil
  196. }
  197. func (f FakeConnection) Request(folder, name string, offset int64, size int, hash []byte, flags uint32, options []protocol.Option) ([]byte, error) {
  198. return f.requestData, nil
  199. }
  200. func (FakeConnection) ClusterConfig(protocol.ClusterConfigMessage) {}
  201. func (FakeConnection) Ping() bool {
  202. return true
  203. }
  204. func (FakeConnection) Closed() bool {
  205. return false
  206. }
  207. func (FakeConnection) Statistics() protocol.Statistics {
  208. return protocol.Statistics{}
  209. }
  210. func BenchmarkRequest(b *testing.B) {
  211. db := db.OpenMemory()
  212. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  213. m.AddFolder(defaultFolderConfig)
  214. m.ServeBackground()
  215. m.ScanFolder("default")
  216. const n = 1000
  217. files := make([]protocol.FileInfo, n)
  218. t := time.Now().Unix()
  219. for i := 0; i < n; i++ {
  220. files[i] = protocol.FileInfo{
  221. Name: fmt.Sprintf("file%d", i),
  222. Modified: t,
  223. Blocks: []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}},
  224. }
  225. }
  226. fc := FakeConnection{
  227. id: device1,
  228. requestData: []byte("some data to return"),
  229. }
  230. m.AddConnection(Connection{
  231. &net.TCPConn{},
  232. fc,
  233. ConnectionTypeDirectAccept,
  234. }, protocol.HelloMessage{})
  235. m.Index(device1, "default", files, 0, nil)
  236. b.ResetTimer()
  237. for i := 0; i < b.N; i++ {
  238. data, err := m.requestGlobal(device1, "default", files[i%n].Name, 0, 32, nil, 0, nil)
  239. if err != nil {
  240. b.Error(err)
  241. }
  242. if data == nil {
  243. b.Error("nil data")
  244. }
  245. }
  246. }
  247. func TestDeviceRename(t *testing.T) {
  248. hello := protocol.HelloMessage{
  249. ClientName: "syncthing",
  250. ClientVersion: "v0.9.4",
  251. }
  252. defer os.Remove("tmpconfig.xml")
  253. rawCfg := config.New(device1)
  254. rawCfg.Devices = []config.DeviceConfiguration{
  255. {
  256. DeviceID: device1,
  257. },
  258. }
  259. cfg := config.Wrap("tmpconfig.xml", rawCfg)
  260. db := db.OpenMemory()
  261. m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  262. if cfg.Devices()[device1].Name != "" {
  263. t.Errorf("Device already has a name")
  264. }
  265. conn := Connection{
  266. &net.TCPConn{},
  267. FakeConnection{
  268. id: device1,
  269. requestData: []byte("some data to return"),
  270. },
  271. ConnectionTypeDirectAccept,
  272. }
  273. m.AddConnection(conn, hello)
  274. m.ServeBackground()
  275. if cfg.Devices()[device1].Name != "" {
  276. t.Errorf("Device already has a name")
  277. }
  278. m.Close(device1, protocol.ErrTimeout)
  279. hello.DeviceName = "tester"
  280. m.AddConnection(conn, hello)
  281. if cfg.Devices()[device1].Name != "tester" {
  282. t.Errorf("Device did not get a name")
  283. }
  284. m.Close(device1, protocol.ErrTimeout)
  285. hello.DeviceName = "tester2"
  286. m.AddConnection(conn, hello)
  287. if cfg.Devices()[device1].Name != "tester" {
  288. t.Errorf("Device name got overwritten")
  289. }
  290. cfgw, err := config.Load("tmpconfig.xml", protocol.LocalDeviceID)
  291. if err != nil {
  292. t.Error(err)
  293. return
  294. }
  295. if cfgw.Devices()[device1].Name != "tester" {
  296. t.Errorf("Device name not saved in config")
  297. }
  298. m.Close(device1, protocol.ErrTimeout)
  299. opts := cfg.Options()
  300. opts.OverwriteNames = true
  301. cfg.SetOptions(opts)
  302. hello.DeviceName = "tester2"
  303. m.AddConnection(conn, hello)
  304. if cfg.Devices()[device1].Name != "tester2" {
  305. t.Errorf("Device name not overwritten")
  306. }
  307. }
  308. func TestClusterConfig(t *testing.T) {
  309. cfg := config.New(device1)
  310. cfg.Devices = []config.DeviceConfiguration{
  311. {
  312. DeviceID: device1,
  313. Introducer: true,
  314. },
  315. {
  316. DeviceID: device2,
  317. },
  318. }
  319. cfg.Folders = []config.FolderConfiguration{
  320. {
  321. ID: "folder1",
  322. Devices: []config.FolderDeviceConfiguration{
  323. {DeviceID: device1},
  324. {DeviceID: device2},
  325. },
  326. },
  327. {
  328. ID: "folder2",
  329. Devices: []config.FolderDeviceConfiguration{
  330. {DeviceID: device1},
  331. {DeviceID: device2},
  332. },
  333. },
  334. }
  335. db := db.OpenMemory()
  336. m := NewModel(config.Wrap("/tmp/test", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  337. m.AddFolder(cfg.Folders[0])
  338. m.AddFolder(cfg.Folders[1])
  339. m.ServeBackground()
  340. cm := m.generateClusterConfig(device2)
  341. if l := len(cm.Folders); l != 2 {
  342. t.Fatalf("Incorrect number of folders %d != 2", l)
  343. }
  344. r := cm.Folders[0]
  345. if r.ID != "folder1" {
  346. t.Errorf("Incorrect folder %q != folder1", r.ID)
  347. }
  348. if l := len(r.Devices); l != 2 {
  349. t.Errorf("Incorrect number of devices %d != 2", l)
  350. }
  351. if id := r.Devices[0].ID; !bytes.Equal(id, device1[:]) {
  352. t.Errorf("Incorrect device ID %x != %x", id, device1)
  353. }
  354. if r.Devices[0].Flags&protocol.FlagIntroducer == 0 {
  355. t.Error("Device1 should be flagged as Introducer")
  356. }
  357. if id := r.Devices[1].ID; !bytes.Equal(id, device2[:]) {
  358. t.Errorf("Incorrect device ID %x != %x", id, device2)
  359. }
  360. if r.Devices[1].Flags&protocol.FlagIntroducer != 0 {
  361. t.Error("Device2 should not be flagged as Introducer")
  362. }
  363. r = cm.Folders[1]
  364. if r.ID != "folder2" {
  365. t.Errorf("Incorrect folder %q != folder2", r.ID)
  366. }
  367. if l := len(r.Devices); l != 2 {
  368. t.Errorf("Incorrect number of devices %d != 2", l)
  369. }
  370. if id := r.Devices[0].ID; !bytes.Equal(id, device1[:]) {
  371. t.Errorf("Incorrect device ID %x != %x", id, device1)
  372. }
  373. if r.Devices[0].Flags&protocol.FlagIntroducer == 0 {
  374. t.Error("Device1 should be flagged as Introducer")
  375. }
  376. if id := r.Devices[1].ID; !bytes.Equal(id, device2[:]) {
  377. t.Errorf("Incorrect device ID %x != %x", id, device2)
  378. }
  379. if r.Devices[1].Flags&protocol.FlagIntroducer != 0 {
  380. t.Error("Device2 should not be flagged as Introducer")
  381. }
  382. }
  383. func TestIgnores(t *testing.T) {
  384. arrEqual := func(a, b []string) bool {
  385. if len(a) != len(b) {
  386. return false
  387. }
  388. for i := range a {
  389. if a[i] != b[i] {
  390. return false
  391. }
  392. }
  393. return true
  394. }
  395. // Assure a clean start state
  396. ioutil.WriteFile("testdata/.stfolder", nil, 0644)
  397. ioutil.WriteFile("testdata/.stignore", []byte(".*\nquux\n"), 0644)
  398. db := db.OpenMemory()
  399. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  400. m.AddFolder(defaultFolderConfig)
  401. m.StartFolderRO("default")
  402. m.ServeBackground()
  403. expected := []string{
  404. ".*",
  405. "quux",
  406. }
  407. ignores, _, err := m.GetIgnores("default")
  408. if err != nil {
  409. t.Error(err)
  410. }
  411. if !arrEqual(ignores, expected) {
  412. t.Errorf("Incorrect ignores: %v != %v", ignores, expected)
  413. }
  414. ignores = append(ignores, "pox")
  415. err = m.SetIgnores("default", ignores)
  416. if err != nil {
  417. t.Error(err)
  418. }
  419. ignores2, _, err := m.GetIgnores("default")
  420. if err != nil {
  421. t.Error(err)
  422. }
  423. if arrEqual(expected, ignores2) {
  424. t.Errorf("Incorrect ignores: %v == %v", ignores2, expected)
  425. }
  426. if !arrEqual(ignores, ignores2) {
  427. t.Errorf("Incorrect ignores: %v != %v", ignores2, ignores)
  428. }
  429. err = m.SetIgnores("default", expected)
  430. if err != nil {
  431. t.Error(err)
  432. }
  433. ignores, _, err = m.GetIgnores("default")
  434. if err != nil {
  435. t.Error(err)
  436. }
  437. if !arrEqual(ignores, expected) {
  438. t.Errorf("Incorrect ignores: %v != %v", ignores, expected)
  439. }
  440. ignores, _, err = m.GetIgnores("doesnotexist")
  441. if err == nil {
  442. t.Error("No error")
  443. }
  444. err = m.SetIgnores("doesnotexist", expected)
  445. if err == nil {
  446. t.Error("No error")
  447. }
  448. // Invalid path, marker should be missing, hence returns an error.
  449. m.AddFolder(config.FolderConfiguration{ID: "fresh", RawPath: "XXX"})
  450. ignores, _, err = m.GetIgnores("fresh")
  451. if err == nil {
  452. t.Error("No error")
  453. }
  454. }
  455. func TestRefuseUnknownBits(t *testing.T) {
  456. db := db.OpenMemory()
  457. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  458. m.AddFolder(defaultFolderConfig)
  459. m.ServeBackground()
  460. m.ScanFolder("default")
  461. m.Index(device1, "default", []protocol.FileInfo{
  462. {
  463. Name: "invalid1",
  464. Flags: (protocol.FlagsAll + 1) &^ protocol.FlagInvalid,
  465. },
  466. {
  467. Name: "invalid2",
  468. Flags: (protocol.FlagsAll + 2) &^ protocol.FlagInvalid,
  469. },
  470. {
  471. Name: "invalid3",
  472. Flags: (1 << 31) &^ protocol.FlagInvalid,
  473. },
  474. {
  475. Name: "valid",
  476. Flags: protocol.FlagsAll &^ (protocol.FlagInvalid | protocol.FlagSymlink),
  477. },
  478. }, 0, nil)
  479. for _, name := range []string{"invalid1", "invalid2", "invalid3"} {
  480. f, ok := m.CurrentGlobalFile("default", name)
  481. if ok || f.Name == name {
  482. t.Error("Invalid file found or name match")
  483. }
  484. }
  485. f, ok := m.CurrentGlobalFile("default", "valid")
  486. if !ok || f.Name != "valid" {
  487. t.Error("Valid file not found or name mismatch", ok, f)
  488. }
  489. }
  490. func TestROScanRecovery(t *testing.T) {
  491. ldb := db.OpenMemory()
  492. set := db.NewFileSet("default", ldb)
  493. set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
  494. {Name: "dummyfile"},
  495. })
  496. fcfg := config.FolderConfiguration{
  497. ID: "default",
  498. RawPath: "testdata/rotestfolder",
  499. RescanIntervalS: 1,
  500. }
  501. cfg := config.Wrap("/tmp/test", config.Configuration{
  502. Folders: []config.FolderConfiguration{fcfg},
  503. Devices: []config.DeviceConfiguration{
  504. {
  505. DeviceID: device1,
  506. },
  507. },
  508. })
  509. os.RemoveAll(fcfg.RawPath)
  510. m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil)
  511. m.AddFolder(fcfg)
  512. m.StartFolderRO("default")
  513. m.ServeBackground()
  514. waitFor := func(status string) error {
  515. timeout := time.Now().Add(2 * time.Second)
  516. for {
  517. if time.Now().After(timeout) {
  518. return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
  519. }
  520. _, _, err := m.State("default")
  521. if err == nil && status == "" {
  522. return nil
  523. }
  524. if err != nil && err.Error() == status {
  525. return nil
  526. }
  527. time.Sleep(10 * time.Millisecond)
  528. }
  529. }
  530. if err := waitFor("folder path missing"); err != nil {
  531. t.Error(err)
  532. return
  533. }
  534. os.Mkdir(fcfg.RawPath, 0700)
  535. if err := waitFor("folder marker missing"); err != nil {
  536. t.Error(err)
  537. return
  538. }
  539. fd, err := os.Create(filepath.Join(fcfg.RawPath, ".stfolder"))
  540. if err != nil {
  541. t.Error(err)
  542. return
  543. }
  544. fd.Close()
  545. if err := waitFor(""); err != nil {
  546. t.Error(err)
  547. return
  548. }
  549. os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
  550. if err := waitFor("folder marker missing"); err != nil {
  551. t.Error(err)
  552. return
  553. }
  554. os.Remove(fcfg.RawPath)
  555. if err := waitFor("folder path missing"); err != nil {
  556. t.Error(err)
  557. return
  558. }
  559. }
  560. func TestRWScanRecovery(t *testing.T) {
  561. ldb := db.OpenMemory()
  562. set := db.NewFileSet("default", ldb)
  563. set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
  564. {Name: "dummyfile"},
  565. })
  566. fcfg := config.FolderConfiguration{
  567. ID: "default",
  568. RawPath: "testdata/rwtestfolder",
  569. RescanIntervalS: 1,
  570. }
  571. cfg := config.Wrap("/tmp/test", config.Configuration{
  572. Folders: []config.FolderConfiguration{fcfg},
  573. Devices: []config.DeviceConfiguration{
  574. {
  575. DeviceID: device1,
  576. },
  577. },
  578. })
  579. os.RemoveAll(fcfg.RawPath)
  580. m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb, nil)
  581. m.AddFolder(fcfg)
  582. m.StartFolderRW("default")
  583. m.ServeBackground()
  584. waitFor := func(status string) error {
  585. timeout := time.Now().Add(2 * time.Second)
  586. for {
  587. if time.Now().After(timeout) {
  588. return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
  589. }
  590. _, _, err := m.State("default")
  591. if err == nil && status == "" {
  592. return nil
  593. }
  594. if err != nil && err.Error() == status {
  595. return nil
  596. }
  597. time.Sleep(10 * time.Millisecond)
  598. }
  599. }
  600. if err := waitFor("folder path missing"); err != nil {
  601. t.Error(err)
  602. return
  603. }
  604. os.Mkdir(fcfg.RawPath, 0700)
  605. if err := waitFor("folder marker missing"); err != nil {
  606. t.Error(err)
  607. return
  608. }
  609. fd, err := os.Create(filepath.Join(fcfg.RawPath, ".stfolder"))
  610. if err != nil {
  611. t.Error(err)
  612. return
  613. }
  614. fd.Close()
  615. if err := waitFor(""); err != nil {
  616. t.Error(err)
  617. return
  618. }
  619. os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
  620. if err := waitFor("folder marker missing"); err != nil {
  621. t.Error(err)
  622. return
  623. }
  624. os.Remove(fcfg.RawPath)
  625. if err := waitFor("folder path missing"); err != nil {
  626. t.Error(err)
  627. return
  628. }
  629. }
  630. func TestGlobalDirectoryTree(t *testing.T) {
  631. db := db.OpenMemory()
  632. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  633. m.AddFolder(defaultFolderConfig)
  634. m.ServeBackground()
  635. b := func(isfile bool, path ...string) protocol.FileInfo {
  636. flags := uint32(protocol.FlagDirectory)
  637. blocks := []protocol.BlockInfo{}
  638. if isfile {
  639. flags = 0
  640. blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
  641. }
  642. return protocol.FileInfo{
  643. Name: filepath.Join(path...),
  644. Flags: flags,
  645. Modified: 0x666,
  646. Blocks: blocks,
  647. }
  648. }
  649. filedata := []interface{}{time.Unix(0x666, 0), 0xa}
  650. testdata := []protocol.FileInfo{
  651. b(false, "another"),
  652. b(false, "another", "directory"),
  653. b(true, "another", "directory", "afile"),
  654. b(false, "another", "directory", "with"),
  655. b(false, "another", "directory", "with", "a"),
  656. b(true, "another", "directory", "with", "a", "file"),
  657. b(true, "another", "directory", "with", "file"),
  658. b(true, "another", "file"),
  659. b(false, "other"),
  660. b(false, "other", "rand"),
  661. b(false, "other", "random"),
  662. b(false, "other", "random", "dir"),
  663. b(false, "other", "random", "dirx"),
  664. b(false, "other", "randomx"),
  665. b(false, "some"),
  666. b(false, "some", "directory"),
  667. b(false, "some", "directory", "with"),
  668. b(false, "some", "directory", "with", "a"),
  669. b(true, "some", "directory", "with", "a", "file"),
  670. b(true, "rootfile"),
  671. }
  672. expectedResult := map[string]interface{}{
  673. "another": map[string]interface{}{
  674. "directory": map[string]interface{}{
  675. "afile": filedata,
  676. "with": map[string]interface{}{
  677. "a": map[string]interface{}{
  678. "file": filedata,
  679. },
  680. "file": filedata,
  681. },
  682. },
  683. "file": filedata,
  684. },
  685. "other": map[string]interface{}{
  686. "rand": map[string]interface{}{},
  687. "random": map[string]interface{}{
  688. "dir": map[string]interface{}{},
  689. "dirx": map[string]interface{}{},
  690. },
  691. "randomx": map[string]interface{}{},
  692. },
  693. "some": map[string]interface{}{
  694. "directory": map[string]interface{}{
  695. "with": map[string]interface{}{
  696. "a": map[string]interface{}{
  697. "file": filedata,
  698. },
  699. },
  700. },
  701. },
  702. "rootfile": filedata,
  703. }
  704. mm := func(data interface{}) string {
  705. bytes, err := json.Marshal(data)
  706. if err != nil {
  707. panic(err)
  708. }
  709. return string(bytes)
  710. }
  711. m.Index(device1, "default", testdata, 0, nil)
  712. result := m.GlobalDirectoryTree("default", "", -1, false)
  713. if mm(result) != mm(expectedResult) {
  714. t.Errorf("Does not match:\n%#v\n%#v", result, expectedResult)
  715. }
  716. result = m.GlobalDirectoryTree("default", "another", -1, false)
  717. if mm(result) != mm(expectedResult["another"]) {
  718. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult["another"]))
  719. }
  720. result = m.GlobalDirectoryTree("default", "", 0, false)
  721. currentResult := map[string]interface{}{
  722. "another": map[string]interface{}{},
  723. "other": map[string]interface{}{},
  724. "some": map[string]interface{}{},
  725. "rootfile": filedata,
  726. }
  727. if mm(result) != mm(currentResult) {
  728. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  729. }
  730. result = m.GlobalDirectoryTree("default", "", 1, false)
  731. currentResult = map[string]interface{}{
  732. "another": map[string]interface{}{
  733. "directory": map[string]interface{}{},
  734. "file": filedata,
  735. },
  736. "other": map[string]interface{}{
  737. "rand": map[string]interface{}{},
  738. "random": map[string]interface{}{},
  739. "randomx": map[string]interface{}{},
  740. },
  741. "some": map[string]interface{}{
  742. "directory": map[string]interface{}{},
  743. },
  744. "rootfile": filedata,
  745. }
  746. if mm(result) != mm(currentResult) {
  747. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  748. }
  749. result = m.GlobalDirectoryTree("default", "", -1, true)
  750. currentResult = map[string]interface{}{
  751. "another": map[string]interface{}{
  752. "directory": map[string]interface{}{
  753. "with": map[string]interface{}{
  754. "a": map[string]interface{}{},
  755. },
  756. },
  757. },
  758. "other": map[string]interface{}{
  759. "rand": map[string]interface{}{},
  760. "random": map[string]interface{}{
  761. "dir": map[string]interface{}{},
  762. "dirx": map[string]interface{}{},
  763. },
  764. "randomx": map[string]interface{}{},
  765. },
  766. "some": map[string]interface{}{
  767. "directory": map[string]interface{}{
  768. "with": map[string]interface{}{
  769. "a": map[string]interface{}{},
  770. },
  771. },
  772. },
  773. }
  774. if mm(result) != mm(currentResult) {
  775. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  776. }
  777. result = m.GlobalDirectoryTree("default", "", 1, true)
  778. currentResult = map[string]interface{}{
  779. "another": map[string]interface{}{
  780. "directory": map[string]interface{}{},
  781. },
  782. "other": map[string]interface{}{
  783. "rand": map[string]interface{}{},
  784. "random": map[string]interface{}{},
  785. "randomx": map[string]interface{}{},
  786. },
  787. "some": map[string]interface{}{
  788. "directory": map[string]interface{}{},
  789. },
  790. }
  791. if mm(result) != mm(currentResult) {
  792. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  793. }
  794. result = m.GlobalDirectoryTree("default", "another", 0, false)
  795. currentResult = map[string]interface{}{
  796. "directory": map[string]interface{}{},
  797. "file": filedata,
  798. }
  799. if mm(result) != mm(currentResult) {
  800. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  801. }
  802. result = m.GlobalDirectoryTree("default", "some/directory", 0, false)
  803. currentResult = map[string]interface{}{
  804. "with": map[string]interface{}{},
  805. }
  806. if mm(result) != mm(currentResult) {
  807. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  808. }
  809. result = m.GlobalDirectoryTree("default", "some/directory", 1, false)
  810. currentResult = map[string]interface{}{
  811. "with": map[string]interface{}{
  812. "a": map[string]interface{}{},
  813. },
  814. }
  815. if mm(result) != mm(currentResult) {
  816. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  817. }
  818. result = m.GlobalDirectoryTree("default", "some/directory", 2, false)
  819. currentResult = map[string]interface{}{
  820. "with": map[string]interface{}{
  821. "a": map[string]interface{}{
  822. "file": filedata,
  823. },
  824. },
  825. }
  826. if mm(result) != mm(currentResult) {
  827. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  828. }
  829. result = m.GlobalDirectoryTree("default", "another", -1, true)
  830. currentResult = map[string]interface{}{
  831. "directory": map[string]interface{}{
  832. "with": map[string]interface{}{
  833. "a": map[string]interface{}{},
  834. },
  835. },
  836. }
  837. if mm(result) != mm(currentResult) {
  838. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  839. }
  840. // No prefix matching!
  841. result = m.GlobalDirectoryTree("default", "som", -1, false)
  842. currentResult = map[string]interface{}{}
  843. if mm(result) != mm(currentResult) {
  844. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  845. }
  846. }
  847. func TestGlobalDirectorySelfFixing(t *testing.T) {
  848. db := db.OpenMemory()
  849. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  850. m.AddFolder(defaultFolderConfig)
  851. m.ServeBackground()
  852. b := func(isfile bool, path ...string) protocol.FileInfo {
  853. flags := uint32(protocol.FlagDirectory)
  854. blocks := []protocol.BlockInfo{}
  855. if isfile {
  856. flags = 0
  857. blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
  858. }
  859. return protocol.FileInfo{
  860. Name: filepath.Join(path...),
  861. Flags: flags,
  862. Modified: 0x666,
  863. Blocks: blocks,
  864. }
  865. }
  866. filedata := []interface{}{time.Unix(0x666, 0).Format(time.RFC3339), 0xa}
  867. testdata := []protocol.FileInfo{
  868. b(true, "another", "directory", "afile"),
  869. b(true, "another", "directory", "with", "a", "file"),
  870. b(true, "another", "directory", "with", "file"),
  871. b(false, "other", "random", "dirx"),
  872. b(false, "other", "randomx"),
  873. b(false, "some", "directory", "with", "x"),
  874. b(true, "some", "directory", "with", "a", "file"),
  875. b(false, "this", "is", "a", "deep", "invalid", "directory"),
  876. b(true, "xthis", "is", "a", "deep", "invalid", "file"),
  877. }
  878. expectedResult := map[string]interface{}{
  879. "another": map[string]interface{}{
  880. "directory": map[string]interface{}{
  881. "afile": filedata,
  882. "with": map[string]interface{}{
  883. "a": map[string]interface{}{
  884. "file": filedata,
  885. },
  886. "file": filedata,
  887. },
  888. },
  889. },
  890. "other": map[string]interface{}{
  891. "random": map[string]interface{}{
  892. "dirx": map[string]interface{}{},
  893. },
  894. "randomx": map[string]interface{}{},
  895. },
  896. "some": map[string]interface{}{
  897. "directory": map[string]interface{}{
  898. "with": map[string]interface{}{
  899. "a": map[string]interface{}{
  900. "file": filedata,
  901. },
  902. "x": map[string]interface{}{},
  903. },
  904. },
  905. },
  906. "this": map[string]interface{}{
  907. "is": map[string]interface{}{
  908. "a": map[string]interface{}{
  909. "deep": map[string]interface{}{
  910. "invalid": map[string]interface{}{
  911. "directory": map[string]interface{}{},
  912. },
  913. },
  914. },
  915. },
  916. },
  917. "xthis": map[string]interface{}{
  918. "is": map[string]interface{}{
  919. "a": map[string]interface{}{
  920. "deep": map[string]interface{}{
  921. "invalid": map[string]interface{}{
  922. "file": filedata,
  923. },
  924. },
  925. },
  926. },
  927. },
  928. }
  929. mm := func(data interface{}) string {
  930. bytes, err := json.Marshal(data)
  931. if err != nil {
  932. panic(err)
  933. }
  934. return string(bytes)
  935. }
  936. m.Index(device1, "default", testdata, 0, nil)
  937. result := m.GlobalDirectoryTree("default", "", -1, false)
  938. if mm(result) != mm(expectedResult) {
  939. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult))
  940. }
  941. result = m.GlobalDirectoryTree("default", "xthis/is/a/deep", -1, false)
  942. currentResult := map[string]interface{}{
  943. "invalid": map[string]interface{}{
  944. "file": filedata,
  945. },
  946. }
  947. if mm(result) != mm(currentResult) {
  948. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  949. }
  950. result = m.GlobalDirectoryTree("default", "xthis/is/a/deep", -1, true)
  951. currentResult = map[string]interface{}{
  952. "invalid": map[string]interface{}{},
  953. }
  954. if mm(result) != mm(currentResult) {
  955. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  956. }
  957. // !!! This is actually BAD, because we don't have enough level allowance
  958. // to accept this file, hence the tree is left unbuilt !!!
  959. result = m.GlobalDirectoryTree("default", "xthis", 1, false)
  960. currentResult = map[string]interface{}{}
  961. if mm(result) != mm(currentResult) {
  962. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  963. }
  964. }
  965. func genDeepFiles(n, d int) []protocol.FileInfo {
  966. rand.Seed(int64(n))
  967. files := make([]protocol.FileInfo, n)
  968. t := time.Now().Unix()
  969. for i := 0; i < n; i++ {
  970. path := ""
  971. for i := 0; i <= d; i++ {
  972. path = filepath.Join(path, strconv.Itoa(rand.Int()))
  973. }
  974. sofar := ""
  975. for _, path := range filepath.SplitList(path) {
  976. sofar = filepath.Join(sofar, path)
  977. files[i] = protocol.FileInfo{
  978. Name: sofar,
  979. }
  980. i++
  981. }
  982. files[i].Modified = t
  983. files[i].Blocks = []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}}
  984. }
  985. return files
  986. }
  987. func BenchmarkTree_10000_50(b *testing.B) {
  988. benchmarkTree(b, 10000, 50)
  989. }
  990. func BenchmarkTree_100_50(b *testing.B) {
  991. benchmarkTree(b, 100, 50)
  992. }
  993. func BenchmarkTree_100_10(b *testing.B) {
  994. benchmarkTree(b, 100, 10)
  995. }
  996. func benchmarkTree(b *testing.B, n1, n2 int) {
  997. db := db.OpenMemory()
  998. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  999. m.AddFolder(defaultFolderConfig)
  1000. m.ServeBackground()
  1001. m.ScanFolder("default")
  1002. files := genDeepFiles(n1, n2)
  1003. m.Index(device1, "default", files, 0, nil)
  1004. b.ResetTimer()
  1005. for i := 0; i < b.N; i++ {
  1006. m.GlobalDirectoryTree("default", "", -1, false)
  1007. }
  1008. b.ReportAllocs()
  1009. }
  1010. func TestIgnoreDelete(t *testing.T) {
  1011. db := db.OpenMemory()
  1012. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
  1013. // This folder should ignore external deletes
  1014. cfg := defaultFolderConfig
  1015. cfg.IgnoreDelete = true
  1016. m.AddFolder(cfg)
  1017. m.ServeBackground()
  1018. m.StartFolderRW("default")
  1019. m.ScanFolder("default")
  1020. // Get a currently existing file
  1021. f, ok := m.CurrentGlobalFile("default", "foo")
  1022. if !ok {
  1023. t.Fatal("foo should exist")
  1024. }
  1025. // Mark it for deletion
  1026. f.Flags = protocol.FlagDeleted
  1027. f.Version = f.Version.Update(142) // arbitrary short remote ID
  1028. f.Blocks = nil
  1029. // Send the index
  1030. m.Index(device1, "default", []protocol.FileInfo{f}, 0, nil)
  1031. // Make sure we ignored it
  1032. f, ok = m.CurrentGlobalFile("default", "foo")
  1033. if !ok {
  1034. t.Fatal("foo should exist")
  1035. }
  1036. if f.IsDeleted() {
  1037. t.Fatal("foo should not be marked for deletion")
  1038. }
  1039. }
  1040. func TestUnifySubs(t *testing.T) {
  1041. cases := []struct {
  1042. in []string // input to unifySubs
  1043. exists []string // paths that exist in the database
  1044. out []string // expected output
  1045. }{
  1046. {
  1047. // 0. trailing slashes are cleaned, known paths are just passed on
  1048. []string{"foo/", "bar//"},
  1049. []string{"foo", "bar"},
  1050. []string{"bar", "foo"}, // the output is sorted
  1051. },
  1052. {
  1053. // 1. "foo/bar" gets trimmed as it's covered by foo
  1054. []string{"foo", "bar/", "foo/bar/"},
  1055. []string{"foo", "bar"},
  1056. []string{"bar", "foo"},
  1057. },
  1058. {
  1059. // 2. "" gets simplified to the empty list; ie scan all
  1060. []string{"foo", ""},
  1061. []string{"foo"},
  1062. nil,
  1063. },
  1064. {
  1065. // 3. "foo/bar" is unknown, but it's kept
  1066. // because its parent is known
  1067. []string{"foo/bar"},
  1068. []string{"foo"},
  1069. []string{"foo/bar"},
  1070. },
  1071. {
  1072. // 4. two independent known paths, both are kept
  1073. // "usr/lib" is not a prefix of "usr/libexec"
  1074. []string{"usr/lib", "usr/libexec"},
  1075. []string{"usr", "usr/lib", "usr/libexec"},
  1076. []string{"usr/lib", "usr/libexec"},
  1077. },
  1078. {
  1079. // 5. "usr/lib" is a prefix of "usr/lib/exec"
  1080. []string{"usr/lib", "usr/lib/exec"},
  1081. []string{"usr", "usr/lib", "usr/libexec"},
  1082. []string{"usr/lib"},
  1083. },
  1084. {
  1085. // 6. .stignore and .stfolder are special and are passed on
  1086. // verbatim even though they are unknown
  1087. []string{".stfolder", ".stignore"},
  1088. []string{},
  1089. []string{".stfolder", ".stignore"},
  1090. },
  1091. {
  1092. // 7. but the presense of something else unknown forces an actual
  1093. // scan
  1094. []string{".stfolder", ".stignore", "foo/bar"},
  1095. []string{},
  1096. []string{".stfolder", ".stignore", "foo"},
  1097. },
  1098. {
  1099. // 8. explicit request to scan all
  1100. nil,
  1101. []string{"foo"},
  1102. nil,
  1103. },
  1104. {
  1105. // 9. empty list of subs
  1106. []string{},
  1107. []string{"foo"},
  1108. nil,
  1109. },
  1110. }
  1111. if runtime.GOOS == "windows" {
  1112. // Fixup path separators
  1113. for i := range cases {
  1114. for j, p := range cases[i].in {
  1115. cases[i].in[j] = filepath.FromSlash(p)
  1116. }
  1117. for j, p := range cases[i].exists {
  1118. cases[i].exists[j] = filepath.FromSlash(p)
  1119. }
  1120. for j, p := range cases[i].out {
  1121. cases[i].out[j] = filepath.FromSlash(p)
  1122. }
  1123. }
  1124. }
  1125. for i, tc := range cases {
  1126. exists := func(f string) bool {
  1127. for _, e := range tc.exists {
  1128. if f == e {
  1129. return true
  1130. }
  1131. }
  1132. return false
  1133. }
  1134. out := unifySubs(tc.in, exists)
  1135. if diff, equal := messagediff.PrettyDiff(tc.out, out); !equal {
  1136. t.Errorf("Case %d failed; got %v, expected %v, diff:\n%s", i, out, tc.out, diff)
  1137. }
  1138. }
  1139. }