model_test.go 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211
  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. "math/rand"
  12. "os"
  13. "path/filepath"
  14. "reflect"
  15. "strconv"
  16. "testing"
  17. "time"
  18. "github.com/syncthing/protocol"
  19. "github.com/syncthing/syncthing/internal/config"
  20. "github.com/syncthing/syncthing/internal/db"
  21. "github.com/syndtr/goleveldb/leveldb"
  22. "github.com/syndtr/goleveldb/leveldb/storage"
  23. )
  24. var device1, device2 protocol.DeviceID
  25. var defaultConfig *config.Wrapper
  26. var defaultFolderConfig config.FolderConfiguration
  27. func init() {
  28. device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
  29. device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
  30. defaultFolderConfig = config.FolderConfiguration{
  31. ID: "default",
  32. Path: "testdata",
  33. Devices: []config.FolderDeviceConfiguration{
  34. {
  35. DeviceID: device1,
  36. },
  37. },
  38. }
  39. _defaultConfig := config.Configuration{
  40. Folders: []config.FolderConfiguration{defaultFolderConfig},
  41. Devices: []config.DeviceConfiguration{
  42. {
  43. DeviceID: device1,
  44. },
  45. },
  46. }
  47. defaultConfig = config.Wrap("/tmp/test", _defaultConfig)
  48. }
  49. var testDataExpected = map[string]protocol.FileInfo{
  50. "foo": {
  51. Name: "foo",
  52. Flags: 0,
  53. Modified: 0,
  54. 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}}},
  55. },
  56. "empty": {
  57. Name: "empty",
  58. Flags: 0,
  59. Modified: 0,
  60. 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}}},
  61. },
  62. "bar": {
  63. Name: "bar",
  64. Flags: 0,
  65. Modified: 0,
  66. 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}}},
  67. },
  68. }
  69. func init() {
  70. // Fix expected test data to match reality
  71. for n, f := range testDataExpected {
  72. fi, _ := os.Stat("testdata/" + n)
  73. f.Flags = uint32(fi.Mode())
  74. f.Modified = fi.ModTime().Unix()
  75. testDataExpected[n] = f
  76. }
  77. }
  78. func TestRequest(t *testing.T) {
  79. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  80. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  81. // device1 shares default, but device2 doesn't
  82. m.AddFolder(defaultFolderConfig)
  83. m.StartFolderRO("default")
  84. m.ScanFolder("default")
  85. // Existing, shared file
  86. bs, err := m.Request(device1, "default", "foo", 0, 6, nil, 0, nil)
  87. if err != nil {
  88. t.Error(err)
  89. }
  90. if bytes.Compare(bs, []byte("foobar")) != 0 {
  91. t.Errorf("Incorrect data from request: %q", string(bs))
  92. }
  93. // Existing, nonshared file
  94. bs, err = m.Request(device2, "default", "foo", 0, 6, nil, 0, nil)
  95. if err == nil {
  96. t.Error("Unexpected nil error on insecure file read")
  97. }
  98. if bs != nil {
  99. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  100. }
  101. // Nonexistent file
  102. bs, err = m.Request(device1, "default", "nonexistent", 0, 6, nil, 0, nil)
  103. if err == nil {
  104. t.Error("Unexpected nil error on insecure file read")
  105. }
  106. if bs != nil {
  107. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  108. }
  109. // Shared folder, but disallowed file name
  110. bs, err = m.Request(device1, "default", "../walk.go", 0, 6, nil, 0, nil)
  111. if err == nil {
  112. t.Error("Unexpected nil error on insecure file read")
  113. }
  114. if bs != nil {
  115. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  116. }
  117. // Larger block than available
  118. bs, err = m.Request(device1, "default", "foo", 0, 42, nil, 0, nil)
  119. if err == nil {
  120. t.Error("Unexpected nil error on insecure file read")
  121. }
  122. if bs != nil {
  123. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  124. }
  125. // Negative offset
  126. bs, err = m.Request(device1, "default", "foo", -4, 6, nil, 0, nil)
  127. if err == nil {
  128. t.Error("Unexpected nil error on insecure file read")
  129. }
  130. if bs != nil {
  131. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  132. }
  133. // Negative size
  134. bs, err = m.Request(device1, "default", "foo", 4, -4, nil, 0, nil)
  135. if err == nil {
  136. t.Error("Unexpected nil error on insecure file read")
  137. }
  138. if bs != nil {
  139. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  140. }
  141. }
  142. func genFiles(n int) []protocol.FileInfo {
  143. files := make([]protocol.FileInfo, n)
  144. t := time.Now().Unix()
  145. for i := 0; i < n; i++ {
  146. files[i] = protocol.FileInfo{
  147. Name: fmt.Sprintf("file%d", i),
  148. Modified: t,
  149. Blocks: []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}},
  150. }
  151. }
  152. return files
  153. }
  154. func BenchmarkIndex10000(b *testing.B) {
  155. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  156. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  157. m.AddFolder(defaultFolderConfig)
  158. m.ScanFolder("default")
  159. files := genFiles(10000)
  160. b.ResetTimer()
  161. for i := 0; i < b.N; i++ {
  162. m.Index(device1, "default", files, 0, nil)
  163. }
  164. }
  165. func BenchmarkIndex00100(b *testing.B) {
  166. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  167. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  168. m.AddFolder(defaultFolderConfig)
  169. m.ScanFolder("default")
  170. files := genFiles(100)
  171. b.ResetTimer()
  172. for i := 0; i < b.N; i++ {
  173. m.Index(device1, "default", files, 0, nil)
  174. }
  175. }
  176. func BenchmarkIndexUpdate10000f10000(b *testing.B) {
  177. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  178. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  179. m.AddFolder(defaultFolderConfig)
  180. m.ScanFolder("default")
  181. files := genFiles(10000)
  182. m.Index(device1, "default", files, 0, nil)
  183. b.ResetTimer()
  184. for i := 0; i < b.N; i++ {
  185. m.IndexUpdate(device1, "default", files, 0, nil)
  186. }
  187. }
  188. func BenchmarkIndexUpdate10000f00100(b *testing.B) {
  189. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  190. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  191. m.AddFolder(defaultFolderConfig)
  192. m.ScanFolder("default")
  193. files := genFiles(10000)
  194. m.Index(device1, "default", files, 0, nil)
  195. ufiles := genFiles(100)
  196. b.ResetTimer()
  197. for i := 0; i < b.N; i++ {
  198. m.IndexUpdate(device1, "default", ufiles, 0, nil)
  199. }
  200. }
  201. func BenchmarkIndexUpdate10000f00001(b *testing.B) {
  202. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  203. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  204. m.AddFolder(defaultFolderConfig)
  205. m.ScanFolder("default")
  206. files := genFiles(10000)
  207. m.Index(device1, "default", files, 0, nil)
  208. ufiles := genFiles(1)
  209. b.ResetTimer()
  210. for i := 0; i < b.N; i++ {
  211. m.IndexUpdate(device1, "default", ufiles, 0, nil)
  212. }
  213. }
  214. type FakeConnection struct {
  215. id protocol.DeviceID
  216. requestData []byte
  217. }
  218. func (FakeConnection) Close() error {
  219. return nil
  220. }
  221. func (f FakeConnection) ID() protocol.DeviceID {
  222. return f.id
  223. }
  224. func (f FakeConnection) Name() string {
  225. return ""
  226. }
  227. func (f FakeConnection) Option(string) string {
  228. return ""
  229. }
  230. func (FakeConnection) Index(string, []protocol.FileInfo, uint32, []protocol.Option) error {
  231. return nil
  232. }
  233. func (FakeConnection) IndexUpdate(string, []protocol.FileInfo, uint32, []protocol.Option) error {
  234. return nil
  235. }
  236. func (f FakeConnection) Request(folder, name string, offset int64, size int, hash []byte, flags uint32, options []protocol.Option) ([]byte, error) {
  237. return f.requestData, nil
  238. }
  239. func (FakeConnection) ClusterConfig(protocol.ClusterConfigMessage) {}
  240. func (FakeConnection) Ping() bool {
  241. return true
  242. }
  243. func (FakeConnection) Statistics() protocol.Statistics {
  244. return protocol.Statistics{}
  245. }
  246. func BenchmarkRequest(b *testing.B) {
  247. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  248. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  249. m.AddFolder(defaultFolderConfig)
  250. m.ScanFolder("default")
  251. const n = 1000
  252. files := make([]protocol.FileInfo, n)
  253. t := time.Now().Unix()
  254. for i := 0; i < n; i++ {
  255. files[i] = protocol.FileInfo{
  256. Name: fmt.Sprintf("file%d", i),
  257. Modified: t,
  258. Blocks: []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}},
  259. }
  260. }
  261. fc := FakeConnection{
  262. id: device1,
  263. requestData: []byte("some data to return"),
  264. }
  265. m.AddConnection(fc, fc)
  266. m.Index(device1, "default", files, 0, nil)
  267. b.ResetTimer()
  268. for i := 0; i < b.N; i++ {
  269. data, err := m.requestGlobal(device1, "default", files[i%n].Name, 0, 32, nil, 0, nil)
  270. if err != nil {
  271. b.Error(err)
  272. }
  273. if data == nil {
  274. b.Error("nil data")
  275. }
  276. }
  277. }
  278. func TestDeviceRename(t *testing.T) {
  279. ccm := protocol.ClusterConfigMessage{
  280. ClientName: "syncthing",
  281. ClientVersion: "v0.9.4",
  282. }
  283. defer os.Remove("tmpconfig.xml")
  284. cfg := config.New(device1)
  285. cfg.Devices = []config.DeviceConfiguration{
  286. {
  287. DeviceID: device1,
  288. },
  289. }
  290. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  291. m := NewModel(config.Wrap("tmpconfig.xml", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  292. if cfg.Devices[0].Name != "" {
  293. t.Errorf("Device already has a name")
  294. }
  295. m.ClusterConfig(device1, ccm)
  296. if cfg.Devices[0].Name != "" {
  297. t.Errorf("Device already has a name")
  298. }
  299. ccm.Options = []protocol.Option{
  300. {
  301. Key: "name",
  302. Value: "tester",
  303. },
  304. }
  305. m.ClusterConfig(device1, ccm)
  306. if cfg.Devices[0].Name != "tester" {
  307. t.Errorf("Device did not get a name")
  308. }
  309. ccm.Options[0].Value = "tester2"
  310. m.ClusterConfig(device1, ccm)
  311. if cfg.Devices[0].Name != "tester" {
  312. t.Errorf("Device name got overwritten")
  313. }
  314. cfgw, err := config.Load("tmpconfig.xml", protocol.LocalDeviceID)
  315. if err != nil {
  316. t.Error(err)
  317. return
  318. }
  319. if cfgw.Devices()[device1].Name != "tester" {
  320. t.Errorf("Device name not saved in config")
  321. }
  322. }
  323. func TestClusterConfig(t *testing.T) {
  324. cfg := config.New(device1)
  325. cfg.Devices = []config.DeviceConfiguration{
  326. {
  327. DeviceID: device1,
  328. Introducer: true,
  329. },
  330. {
  331. DeviceID: device2,
  332. },
  333. }
  334. cfg.Folders = []config.FolderConfiguration{
  335. {
  336. ID: "folder1",
  337. Devices: []config.FolderDeviceConfiguration{
  338. {DeviceID: device1},
  339. {DeviceID: device2},
  340. },
  341. },
  342. {
  343. ID: "folder2",
  344. Devices: []config.FolderDeviceConfiguration{
  345. {DeviceID: device1},
  346. {DeviceID: device2},
  347. },
  348. },
  349. }
  350. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  351. m := NewModel(config.Wrap("/tmp/test", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  352. m.AddFolder(cfg.Folders[0])
  353. m.AddFolder(cfg.Folders[1])
  354. cm := m.clusterConfig(device2)
  355. if l := len(cm.Folders); l != 2 {
  356. t.Fatalf("Incorrect number of folders %d != 2", l)
  357. }
  358. r := cm.Folders[0]
  359. if r.ID != "folder1" {
  360. t.Errorf("Incorrect folder %q != folder1", r.ID)
  361. }
  362. if l := len(r.Devices); l != 2 {
  363. t.Errorf("Incorrect number of devices %d != 2", l)
  364. }
  365. if id := r.Devices[0].ID; bytes.Compare(id, device1[:]) != 0 {
  366. t.Errorf("Incorrect device ID %x != %x", id, device1)
  367. }
  368. if r.Devices[0].Flags&protocol.FlagIntroducer == 0 {
  369. t.Error("Device1 should be flagged as Introducer")
  370. }
  371. if id := r.Devices[1].ID; bytes.Compare(id, device2[:]) != 0 {
  372. t.Errorf("Incorrect device ID %x != %x", id, device2)
  373. }
  374. if r.Devices[1].Flags&protocol.FlagIntroducer != 0 {
  375. t.Error("Device2 should not be flagged as Introducer")
  376. }
  377. r = cm.Folders[1]
  378. if r.ID != "folder2" {
  379. t.Errorf("Incorrect folder %q != folder2", r.ID)
  380. }
  381. if l := len(r.Devices); l != 2 {
  382. t.Errorf("Incorrect number of devices %d != 2", l)
  383. }
  384. if id := r.Devices[0].ID; bytes.Compare(id, device1[:]) != 0 {
  385. t.Errorf("Incorrect device ID %x != %x", id, device1)
  386. }
  387. if r.Devices[0].Flags&protocol.FlagIntroducer == 0 {
  388. t.Error("Device1 should be flagged as Introducer")
  389. }
  390. if id := r.Devices[1].ID; bytes.Compare(id, device2[:]) != 0 {
  391. t.Errorf("Incorrect device ID %x != %x", id, device2)
  392. }
  393. if r.Devices[1].Flags&protocol.FlagIntroducer != 0 {
  394. t.Error("Device2 should not be flagged as Introducer")
  395. }
  396. }
  397. func TestIgnores(t *testing.T) {
  398. arrEqual := func(a, b []string) bool {
  399. if len(a) != len(b) {
  400. return false
  401. }
  402. for i := range a {
  403. if a[i] != b[i] {
  404. return false
  405. }
  406. }
  407. return true
  408. }
  409. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  410. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  411. m.AddFolder(defaultFolderConfig)
  412. m.StartFolderRO("default")
  413. expected := []string{
  414. ".*",
  415. "quux",
  416. }
  417. ignores, _, err := m.GetIgnores("default")
  418. if err != nil {
  419. t.Error(err)
  420. }
  421. if !arrEqual(ignores, expected) {
  422. t.Errorf("Incorrect ignores: %v != %v", ignores, expected)
  423. }
  424. ignores = append(ignores, "pox")
  425. err = m.SetIgnores("default", ignores)
  426. if err != nil {
  427. t.Error(err)
  428. }
  429. ignores2, _, err := m.GetIgnores("default")
  430. if err != nil {
  431. t.Error(err)
  432. }
  433. if arrEqual(expected, ignores2) {
  434. t.Errorf("Incorrect ignores: %v == %v", ignores2, expected)
  435. }
  436. if !arrEqual(ignores, ignores2) {
  437. t.Errorf("Incorrect ignores: %v != %v", ignores2, ignores)
  438. }
  439. err = m.SetIgnores("default", expected)
  440. if err != nil {
  441. t.Error(err)
  442. }
  443. ignores, _, err = m.GetIgnores("default")
  444. if err != nil {
  445. t.Error(err)
  446. }
  447. if !arrEqual(ignores, expected) {
  448. t.Errorf("Incorrect ignores: %v != %v", ignores, expected)
  449. }
  450. ignores, _, err = m.GetIgnores("doesnotexist")
  451. if err == nil {
  452. t.Error("No error")
  453. }
  454. err = m.SetIgnores("doesnotexist", expected)
  455. if err == nil {
  456. t.Error("No error")
  457. }
  458. m.AddFolder(config.FolderConfiguration{ID: "fresh", Path: "XXX"})
  459. ignores, _, err = m.GetIgnores("fresh")
  460. if err != nil {
  461. t.Error(err)
  462. }
  463. if len(ignores) > 0 {
  464. t.Errorf("Expected no ignores, got: %v", ignores)
  465. }
  466. }
  467. func TestRefuseUnknownBits(t *testing.T) {
  468. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  469. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  470. m.AddFolder(defaultFolderConfig)
  471. m.ScanFolder("default")
  472. m.Index(device1, "default", []protocol.FileInfo{
  473. {
  474. Name: "invalid1",
  475. Flags: (protocol.FlagsAll + 1) &^ protocol.FlagInvalid,
  476. },
  477. {
  478. Name: "invalid2",
  479. Flags: (protocol.FlagsAll + 2) &^ protocol.FlagInvalid,
  480. },
  481. {
  482. Name: "invalid3",
  483. Flags: (1 << 31) &^ protocol.FlagInvalid,
  484. },
  485. {
  486. Name: "valid",
  487. Flags: protocol.FlagsAll &^ (protocol.FlagInvalid | protocol.FlagSymlink),
  488. },
  489. }, 0, nil)
  490. for _, name := range []string{"invalid1", "invalid2", "invalid3"} {
  491. f, ok := m.CurrentGlobalFile("default", name)
  492. if ok || f.Name == name {
  493. t.Error("Invalid file found or name match")
  494. }
  495. }
  496. f, ok := m.CurrentGlobalFile("default", "valid")
  497. if !ok || f.Name != "valid" {
  498. t.Error("Valid file not found or name mismatch", ok, f)
  499. }
  500. }
  501. func TestROScanRecovery(t *testing.T) {
  502. ldb, _ := leveldb.Open(storage.NewMemStorage(), nil)
  503. set := db.NewFileSet("default", ldb)
  504. set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
  505. {Name: "dummyfile"},
  506. })
  507. fcfg := config.FolderConfiguration{
  508. ID: "default",
  509. Path: "testdata/rotestfolder",
  510. RescanIntervalS: 1,
  511. }
  512. cfg := config.Wrap("/tmp/test", config.Configuration{
  513. Folders: []config.FolderConfiguration{fcfg},
  514. Devices: []config.DeviceConfiguration{
  515. {
  516. DeviceID: device1,
  517. },
  518. },
  519. })
  520. os.RemoveAll(fcfg.Path)
  521. m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
  522. m.AddFolder(fcfg)
  523. m.StartFolderRO("default")
  524. waitFor := func(status string) error {
  525. timeout := time.Now().Add(2 * time.Second)
  526. for {
  527. if time.Now().After(timeout) {
  528. return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
  529. }
  530. if m.cfg.Folders()["default"].Invalid == status {
  531. return nil
  532. }
  533. time.Sleep(10 * time.Millisecond)
  534. }
  535. }
  536. if err := waitFor("Folder path missing"); err != nil {
  537. t.Error(err)
  538. return
  539. }
  540. os.Mkdir(fcfg.Path, 0700)
  541. if err := waitFor("Folder marker missing"); err != nil {
  542. t.Error(err)
  543. return
  544. }
  545. fd, err := os.Create(filepath.Join(fcfg.Path, ".stfolder"))
  546. if err != nil {
  547. t.Error(err)
  548. return
  549. }
  550. fd.Close()
  551. if err := waitFor(""); err != nil {
  552. t.Error(err)
  553. return
  554. }
  555. os.Remove(filepath.Join(fcfg.Path, ".stfolder"))
  556. if err := waitFor("Folder marker missing"); err != nil {
  557. t.Error(err)
  558. return
  559. }
  560. os.Remove(fcfg.Path)
  561. if err := waitFor("Folder path missing"); err != nil {
  562. t.Error(err)
  563. return
  564. }
  565. }
  566. func TestRWScanRecovery(t *testing.T) {
  567. ldb, _ := leveldb.Open(storage.NewMemStorage(), nil)
  568. set := db.NewFileSet("default", ldb)
  569. set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
  570. {Name: "dummyfile"},
  571. })
  572. fcfg := config.FolderConfiguration{
  573. ID: "default",
  574. Path: "testdata/rwtestfolder",
  575. RescanIntervalS: 1,
  576. }
  577. cfg := config.Wrap("/tmp/test", config.Configuration{
  578. Folders: []config.FolderConfiguration{fcfg},
  579. Devices: []config.DeviceConfiguration{
  580. {
  581. DeviceID: device1,
  582. },
  583. },
  584. })
  585. os.RemoveAll(fcfg.Path)
  586. m := NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
  587. m.AddFolder(fcfg)
  588. m.StartFolderRW("default")
  589. waitFor := func(status string) error {
  590. timeout := time.Now().Add(2 * time.Second)
  591. for {
  592. if time.Now().After(timeout) {
  593. return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
  594. }
  595. if m.cfg.Folders()["default"].Invalid == status {
  596. return nil
  597. }
  598. time.Sleep(10 * time.Millisecond)
  599. }
  600. }
  601. if err := waitFor("Folder path missing"); err != nil {
  602. t.Error(err)
  603. return
  604. }
  605. os.Mkdir(fcfg.Path, 0700)
  606. if err := waitFor("Folder marker missing"); err != nil {
  607. t.Error(err)
  608. return
  609. }
  610. fd, err := os.Create(filepath.Join(fcfg.Path, ".stfolder"))
  611. if err != nil {
  612. t.Error(err)
  613. return
  614. }
  615. fd.Close()
  616. if err := waitFor(""); err != nil {
  617. t.Error(err)
  618. return
  619. }
  620. os.Remove(filepath.Join(fcfg.Path, ".stfolder"))
  621. if err := waitFor("Folder marker missing"); err != nil {
  622. t.Error(err)
  623. return
  624. }
  625. os.Remove(fcfg.Path)
  626. if err := waitFor("Folder path missing"); err != nil {
  627. t.Error(err)
  628. return
  629. }
  630. }
  631. func TestGlobalDirectoryTree(t *testing.T) {
  632. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  633. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  634. m.AddFolder(defaultFolderConfig)
  635. b := func(isfile bool, path ...string) protocol.FileInfo {
  636. var 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 := []int64{0x666, 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 !reflect.DeepEqual(result, expectedResult) {
  714. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult))
  715. }
  716. result = m.GlobalDirectoryTree("default", "another", -1, false)
  717. if !reflect.DeepEqual(result, 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 !reflect.DeepEqual(result, 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 !reflect.DeepEqual(result, 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 !reflect.DeepEqual(result, 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 !reflect.DeepEqual(result, 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 !reflect.DeepEqual(result, 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 !reflect.DeepEqual(result, 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 !reflect.DeepEqual(result, 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 !reflect.DeepEqual(result, 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 !reflect.DeepEqual(result, 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 !reflect.DeepEqual(result, 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, _ := leveldb.Open(storage.NewMemStorage(), nil)
  849. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  850. m.AddFolder(defaultFolderConfig)
  851. b := func(isfile bool, path ...string) protocol.FileInfo {
  852. var flags uint32 = protocol.FlagDirectory
  853. blocks := []protocol.BlockInfo{}
  854. if isfile {
  855. flags = 0
  856. 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}}}
  857. }
  858. return protocol.FileInfo{
  859. Name: filepath.Join(path...),
  860. Flags: flags,
  861. Modified: 0x666,
  862. Blocks: blocks,
  863. }
  864. }
  865. filedata := []int64{0x666, 0xa}
  866. testdata := []protocol.FileInfo{
  867. b(true, "another", "directory", "afile"),
  868. b(true, "another", "directory", "with", "a", "file"),
  869. b(true, "another", "directory", "with", "file"),
  870. b(false, "other", "random", "dirx"),
  871. b(false, "other", "randomx"),
  872. b(false, "some", "directory", "with", "x"),
  873. b(true, "some", "directory", "with", "a", "file"),
  874. b(false, "this", "is", "a", "deep", "invalid", "directory"),
  875. b(true, "xthis", "is", "a", "deep", "invalid", "file"),
  876. }
  877. expectedResult := map[string]interface{}{
  878. "another": map[string]interface{}{
  879. "directory": map[string]interface{}{
  880. "afile": filedata,
  881. "with": map[string]interface{}{
  882. "a": map[string]interface{}{
  883. "file": filedata,
  884. },
  885. "file": filedata,
  886. },
  887. },
  888. },
  889. "other": map[string]interface{}{
  890. "random": map[string]interface{}{
  891. "dirx": map[string]interface{}{},
  892. },
  893. "randomx": map[string]interface{}{},
  894. },
  895. "some": map[string]interface{}{
  896. "directory": map[string]interface{}{
  897. "with": map[string]interface{}{
  898. "a": map[string]interface{}{
  899. "file": filedata,
  900. },
  901. "x": map[string]interface{}{},
  902. },
  903. },
  904. },
  905. "this": map[string]interface{}{
  906. "is": map[string]interface{}{
  907. "a": map[string]interface{}{
  908. "deep": map[string]interface{}{
  909. "invalid": map[string]interface{}{
  910. "directory": map[string]interface{}{},
  911. },
  912. },
  913. },
  914. },
  915. },
  916. "xthis": map[string]interface{}{
  917. "is": map[string]interface{}{
  918. "a": map[string]interface{}{
  919. "deep": map[string]interface{}{
  920. "invalid": map[string]interface{}{
  921. "file": filedata,
  922. },
  923. },
  924. },
  925. },
  926. },
  927. }
  928. mm := func(data interface{}) string {
  929. bytes, err := json.Marshal(data)
  930. if err != nil {
  931. panic(err)
  932. }
  933. return string(bytes)
  934. }
  935. m.Index(device1, "default", testdata, 0, nil)
  936. result := m.GlobalDirectoryTree("default", "", -1, false)
  937. if !reflect.DeepEqual(result, expectedResult) {
  938. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult))
  939. }
  940. result = m.GlobalDirectoryTree("default", "xthis/is/a/deep", -1, false)
  941. currentResult := map[string]interface{}{
  942. "invalid": map[string]interface{}{
  943. "file": filedata,
  944. },
  945. }
  946. if !reflect.DeepEqual(result, currentResult) {
  947. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  948. }
  949. result = m.GlobalDirectoryTree("default", "xthis/is/a/deep", -1, true)
  950. currentResult = map[string]interface{}{
  951. "invalid": map[string]interface{}{},
  952. }
  953. if !reflect.DeepEqual(result, currentResult) {
  954. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  955. }
  956. // !!! This is actually BAD, because we don't have enough level allowance
  957. // to accept this file, hence the tree is left unbuilt !!!
  958. result = m.GlobalDirectoryTree("default", "xthis", 1, false)
  959. currentResult = map[string]interface{}{}
  960. if !reflect.DeepEqual(result, currentResult) {
  961. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  962. }
  963. }
  964. func genDeepFiles(n, d int) []protocol.FileInfo {
  965. rand.Seed(int64(n))
  966. files := make([]protocol.FileInfo, n)
  967. t := time.Now().Unix()
  968. for i := 0; i < n; i++ {
  969. path := ""
  970. for i := 0; i <= d; i++ {
  971. path = filepath.Join(path, strconv.Itoa(rand.Int()))
  972. }
  973. sofar := ""
  974. for _, path := range filepath.SplitList(path) {
  975. sofar = filepath.Join(sofar, path)
  976. files[i] = protocol.FileInfo{
  977. Name: sofar,
  978. }
  979. i++
  980. }
  981. files[i].Modified = t
  982. files[i].Blocks = []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}}
  983. }
  984. return files
  985. }
  986. func BenchmarkTree_10000_50(b *testing.B) {
  987. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  988. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  989. m.AddFolder(defaultFolderConfig)
  990. m.ScanFolder("default")
  991. files := genDeepFiles(10000, 50)
  992. m.Index(device1, "default", files, 0, nil)
  993. b.ResetTimer()
  994. for i := 0; i < b.N; i++ {
  995. m.GlobalDirectoryTree("default", "", -1, false)
  996. }
  997. }
  998. func BenchmarkTree_10000_10(b *testing.B) {
  999. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  1000. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  1001. m.AddFolder(defaultFolderConfig)
  1002. m.ScanFolder("default")
  1003. files := genDeepFiles(10000, 10)
  1004. m.Index(device1, "default", files, 0, nil)
  1005. b.ResetTimer()
  1006. for i := 0; i < b.N; i++ {
  1007. m.GlobalDirectoryTree("default", "", -1, false)
  1008. }
  1009. }
  1010. func BenchmarkTree_00100_50(b *testing.B) {
  1011. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  1012. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  1013. m.AddFolder(defaultFolderConfig)
  1014. m.ScanFolder("default")
  1015. files := genDeepFiles(100, 50)
  1016. m.Index(device1, "default", files, 0, nil)
  1017. b.ResetTimer()
  1018. for i := 0; i < b.N; i++ {
  1019. m.GlobalDirectoryTree("default", "", -1, false)
  1020. }
  1021. }
  1022. func BenchmarkTree_00100_10(b *testing.B) {
  1023. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  1024. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  1025. m.AddFolder(defaultFolderConfig)
  1026. m.ScanFolder("default")
  1027. files := genDeepFiles(100, 10)
  1028. m.Index(device1, "default", files, 0, nil)
  1029. b.ResetTimer()
  1030. for i := 0; i < b.N; i++ {
  1031. m.GlobalDirectoryTree("default", "", -1, false)
  1032. }
  1033. }