model_test.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
  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/syndtr/goleveldb/leveldb"
  21. "github.com/syndtr/goleveldb/leveldb/storage"
  22. )
  23. var device1, device2 protocol.DeviceID
  24. var defaultConfig *config.Wrapper
  25. var defaultFolderConfig config.FolderConfiguration
  26. func init() {
  27. device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
  28. device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
  29. defaultFolderConfig = config.FolderConfiguration{
  30. ID: "default",
  31. Path: "testdata",
  32. Devices: []config.FolderDeviceConfiguration{
  33. {
  34. DeviceID: device1,
  35. },
  36. },
  37. }
  38. _defaultConfig := config.Configuration{
  39. Folders: []config.FolderConfiguration{defaultFolderConfig},
  40. Devices: []config.DeviceConfiguration{
  41. {
  42. DeviceID: device1,
  43. },
  44. },
  45. }
  46. defaultConfig = config.Wrap("/tmp/test", _defaultConfig)
  47. }
  48. var testDataExpected = map[string]protocol.FileInfo{
  49. "foo": {
  50. Name: "foo",
  51. Flags: 0,
  52. Modified: 0,
  53. 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}}},
  54. },
  55. "empty": {
  56. Name: "empty",
  57. Flags: 0,
  58. Modified: 0,
  59. 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}}},
  60. },
  61. "bar": {
  62. Name: "bar",
  63. Flags: 0,
  64. Modified: 0,
  65. 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}}},
  66. },
  67. }
  68. func init() {
  69. // Fix expected test data to match reality
  70. for n, f := range testDataExpected {
  71. fi, _ := os.Stat("testdata/" + n)
  72. f.Flags = uint32(fi.Mode())
  73. f.Modified = fi.ModTime().Unix()
  74. testDataExpected[n] = f
  75. }
  76. }
  77. func TestRequest(t *testing.T) {
  78. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  79. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  80. // device1 shares default, but device2 doesn't
  81. m.AddFolder(defaultFolderConfig)
  82. m.StartFolderRO("default")
  83. m.ScanFolder("default")
  84. // Existing, shared file
  85. bs, err := m.Request(device1, "default", "foo", 0, 6, nil, 0, nil)
  86. if err != nil {
  87. t.Error(err)
  88. }
  89. if bytes.Compare(bs, []byte("foobar")) != 0 {
  90. t.Errorf("Incorrect data from request: %q", string(bs))
  91. }
  92. // Existing, nonshared file
  93. bs, err = m.Request(device2, "default", "foo", 0, 6, nil, 0, nil)
  94. if err == nil {
  95. t.Error("Unexpected nil error on insecure file read")
  96. }
  97. if bs != nil {
  98. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  99. }
  100. // Nonexistent file
  101. bs, err = m.Request(device1, "default", "nonexistent", 0, 6, nil, 0, nil)
  102. if err == nil {
  103. t.Error("Unexpected nil error on insecure file read")
  104. }
  105. if bs != nil {
  106. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  107. }
  108. // Shared folder, but disallowed file name
  109. bs, err = m.Request(device1, "default", "../walk.go", 0, 6, nil, 0, nil)
  110. if err == nil {
  111. t.Error("Unexpected nil error on insecure file read")
  112. }
  113. if bs != nil {
  114. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  115. }
  116. // Larger block than available
  117. bs, err = m.Request(device1, "default", "foo", 0, 42, nil, 0, nil)
  118. if err == nil {
  119. t.Error("Unexpected nil error on insecure file read")
  120. }
  121. if bs != nil {
  122. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  123. }
  124. // Negative offset
  125. bs, err = m.Request(device1, "default", "foo", -4, 6, nil, 0, nil)
  126. if err == nil {
  127. t.Error("Unexpected nil error on insecure file read")
  128. }
  129. if bs != nil {
  130. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  131. }
  132. // Negative size
  133. bs, err = m.Request(device1, "default", "foo", 4, -4, nil, 0, nil)
  134. if err == nil {
  135. t.Error("Unexpected nil error on insecure file read")
  136. }
  137. if bs != nil {
  138. t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
  139. }
  140. }
  141. func genFiles(n int) []protocol.FileInfo {
  142. files := make([]protocol.FileInfo, n)
  143. t := time.Now().Unix()
  144. for i := 0; i < n; i++ {
  145. files[i] = protocol.FileInfo{
  146. Name: fmt.Sprintf("file%d", i),
  147. Modified: t,
  148. Blocks: []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}},
  149. }
  150. }
  151. return files
  152. }
  153. func BenchmarkIndex10000(b *testing.B) {
  154. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  155. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  156. m.AddFolder(defaultFolderConfig)
  157. m.ScanFolder("default")
  158. files := genFiles(10000)
  159. b.ResetTimer()
  160. for i := 0; i < b.N; i++ {
  161. m.Index(device1, "default", files, 0, nil)
  162. }
  163. }
  164. func BenchmarkIndex00100(b *testing.B) {
  165. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  166. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  167. m.AddFolder(defaultFolderConfig)
  168. m.ScanFolder("default")
  169. files := genFiles(100)
  170. b.ResetTimer()
  171. for i := 0; i < b.N; i++ {
  172. m.Index(device1, "default", files, 0, nil)
  173. }
  174. }
  175. func BenchmarkIndexUpdate10000f10000(b *testing.B) {
  176. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  177. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  178. m.AddFolder(defaultFolderConfig)
  179. m.ScanFolder("default")
  180. files := genFiles(10000)
  181. m.Index(device1, "default", files, 0, nil)
  182. b.ResetTimer()
  183. for i := 0; i < b.N; i++ {
  184. m.IndexUpdate(device1, "default", files, 0, nil)
  185. }
  186. }
  187. func BenchmarkIndexUpdate10000f00100(b *testing.B) {
  188. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  189. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  190. m.AddFolder(defaultFolderConfig)
  191. m.ScanFolder("default")
  192. files := genFiles(10000)
  193. m.Index(device1, "default", files, 0, nil)
  194. ufiles := genFiles(100)
  195. b.ResetTimer()
  196. for i := 0; i < b.N; i++ {
  197. m.IndexUpdate(device1, "default", ufiles, 0, nil)
  198. }
  199. }
  200. func BenchmarkIndexUpdate10000f00001(b *testing.B) {
  201. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  202. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  203. m.AddFolder(defaultFolderConfig)
  204. m.ScanFolder("default")
  205. files := genFiles(10000)
  206. m.Index(device1, "default", files, 0, nil)
  207. ufiles := genFiles(1)
  208. b.ResetTimer()
  209. for i := 0; i < b.N; i++ {
  210. m.IndexUpdate(device1, "default", ufiles, 0, nil)
  211. }
  212. }
  213. type FakeConnection struct {
  214. id protocol.DeviceID
  215. requestData []byte
  216. }
  217. func (FakeConnection) Close() error {
  218. return nil
  219. }
  220. func (f FakeConnection) ID() protocol.DeviceID {
  221. return f.id
  222. }
  223. func (f FakeConnection) Name() string {
  224. return ""
  225. }
  226. func (f FakeConnection) Option(string) string {
  227. return ""
  228. }
  229. func (FakeConnection) Index(string, []protocol.FileInfo, uint32, []protocol.Option) error {
  230. return nil
  231. }
  232. func (FakeConnection) IndexUpdate(string, []protocol.FileInfo, uint32, []protocol.Option) error {
  233. return nil
  234. }
  235. func (f FakeConnection) Request(folder, name string, offset int64, size int, hash []byte, flags uint32, options []protocol.Option) ([]byte, error) {
  236. return f.requestData, nil
  237. }
  238. func (FakeConnection) ClusterConfig(protocol.ClusterConfigMessage) {}
  239. func (FakeConnection) Ping() bool {
  240. return true
  241. }
  242. func (FakeConnection) Statistics() protocol.Statistics {
  243. return protocol.Statistics{}
  244. }
  245. func BenchmarkRequest(b *testing.B) {
  246. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  247. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  248. m.AddFolder(defaultFolderConfig)
  249. m.ScanFolder("default")
  250. const n = 1000
  251. files := make([]protocol.FileInfo, n)
  252. t := time.Now().Unix()
  253. for i := 0; i < n; i++ {
  254. files[i] = protocol.FileInfo{
  255. Name: fmt.Sprintf("file%d", i),
  256. Modified: t,
  257. Blocks: []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}},
  258. }
  259. }
  260. fc := FakeConnection{
  261. id: device1,
  262. requestData: []byte("some data to return"),
  263. }
  264. m.AddConnection(fc, fc)
  265. m.Index(device1, "default", files, 0, nil)
  266. b.ResetTimer()
  267. for i := 0; i < b.N; i++ {
  268. data, err := m.requestGlobal(device1, "default", files[i%n].Name, 0, 32, nil, 0, nil)
  269. if err != nil {
  270. b.Error(err)
  271. }
  272. if data == nil {
  273. b.Error("nil data")
  274. }
  275. }
  276. }
  277. func TestDeviceRename(t *testing.T) {
  278. ccm := protocol.ClusterConfigMessage{
  279. ClientName: "syncthing",
  280. ClientVersion: "v0.9.4",
  281. }
  282. defer os.Remove("tmpconfig.xml")
  283. cfg := config.New(device1)
  284. cfg.Devices = []config.DeviceConfiguration{
  285. {
  286. DeviceID: device1,
  287. },
  288. }
  289. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  290. m := NewModel(config.Wrap("tmpconfig.xml", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  291. if cfg.Devices[0].Name != "" {
  292. t.Errorf("Device already has a name")
  293. }
  294. m.ClusterConfig(device1, ccm)
  295. if cfg.Devices[0].Name != "" {
  296. t.Errorf("Device already has a name")
  297. }
  298. ccm.Options = []protocol.Option{
  299. {
  300. Key: "name",
  301. Value: "tester",
  302. },
  303. }
  304. m.ClusterConfig(device1, ccm)
  305. if cfg.Devices[0].Name != "tester" {
  306. t.Errorf("Device did not get a name")
  307. }
  308. ccm.Options[0].Value = "tester2"
  309. m.ClusterConfig(device1, ccm)
  310. if cfg.Devices[0].Name != "tester" {
  311. t.Errorf("Device name got overwritten")
  312. }
  313. cfgw, err := config.Load("tmpconfig.xml", protocol.LocalDeviceID)
  314. if err != nil {
  315. t.Error(err)
  316. return
  317. }
  318. if cfgw.Devices()[device1].Name != "tester" {
  319. t.Errorf("Device name not saved in config")
  320. }
  321. }
  322. func TestClusterConfig(t *testing.T) {
  323. cfg := config.New(device1)
  324. cfg.Devices = []config.DeviceConfiguration{
  325. {
  326. DeviceID: device1,
  327. Introducer: true,
  328. },
  329. {
  330. DeviceID: device2,
  331. },
  332. }
  333. cfg.Folders = []config.FolderConfiguration{
  334. {
  335. ID: "folder1",
  336. Devices: []config.FolderDeviceConfiguration{
  337. {DeviceID: device1},
  338. {DeviceID: device2},
  339. },
  340. },
  341. {
  342. ID: "folder2",
  343. Devices: []config.FolderDeviceConfiguration{
  344. {DeviceID: device1},
  345. {DeviceID: device2},
  346. },
  347. },
  348. }
  349. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  350. m := NewModel(config.Wrap("/tmp/test", cfg), protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  351. m.AddFolder(cfg.Folders[0])
  352. m.AddFolder(cfg.Folders[1])
  353. cm := m.clusterConfig(device2)
  354. if l := len(cm.Folders); l != 2 {
  355. t.Fatalf("Incorrect number of folders %d != 2", l)
  356. }
  357. r := cm.Folders[0]
  358. if r.ID != "folder1" {
  359. t.Errorf("Incorrect folder %q != folder1", r.ID)
  360. }
  361. if l := len(r.Devices); l != 2 {
  362. t.Errorf("Incorrect number of devices %d != 2", l)
  363. }
  364. if id := r.Devices[0].ID; bytes.Compare(id, device1[:]) != 0 {
  365. t.Errorf("Incorrect device ID %x != %x", id, device1)
  366. }
  367. if r.Devices[0].Flags&protocol.FlagIntroducer == 0 {
  368. t.Error("Device1 should be flagged as Introducer")
  369. }
  370. if id := r.Devices[1].ID; bytes.Compare(id, device2[:]) != 0 {
  371. t.Errorf("Incorrect device ID %x != %x", id, device2)
  372. }
  373. if r.Devices[1].Flags&protocol.FlagIntroducer != 0 {
  374. t.Error("Device2 should not be flagged as Introducer")
  375. }
  376. r = cm.Folders[1]
  377. if r.ID != "folder2" {
  378. t.Errorf("Incorrect folder %q != folder2", r.ID)
  379. }
  380. if l := len(r.Devices); l != 2 {
  381. t.Errorf("Incorrect number of devices %d != 2", l)
  382. }
  383. if id := r.Devices[0].ID; bytes.Compare(id, device1[:]) != 0 {
  384. t.Errorf("Incorrect device ID %x != %x", id, device1)
  385. }
  386. if r.Devices[0].Flags&protocol.FlagIntroducer == 0 {
  387. t.Error("Device1 should be flagged as Introducer")
  388. }
  389. if id := r.Devices[1].ID; bytes.Compare(id, device2[:]) != 0 {
  390. t.Errorf("Incorrect device ID %x != %x", id, device2)
  391. }
  392. if r.Devices[1].Flags&protocol.FlagIntroducer != 0 {
  393. t.Error("Device2 should not be flagged as Introducer")
  394. }
  395. }
  396. func TestIgnores(t *testing.T) {
  397. arrEqual := func(a, b []string) bool {
  398. if len(a) != len(b) {
  399. return false
  400. }
  401. for i := range a {
  402. if a[i] != b[i] {
  403. return false
  404. }
  405. }
  406. return true
  407. }
  408. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  409. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  410. m.AddFolder(defaultFolderConfig)
  411. m.StartFolderRO("default")
  412. expected := []string{
  413. ".*",
  414. "quux",
  415. }
  416. ignores, _, err := m.GetIgnores("default")
  417. if err != nil {
  418. t.Error(err)
  419. }
  420. if !arrEqual(ignores, expected) {
  421. t.Errorf("Incorrect ignores: %v != %v", ignores, expected)
  422. }
  423. ignores = append(ignores, "pox")
  424. err = m.SetIgnores("default", ignores)
  425. if err != nil {
  426. t.Error(err)
  427. }
  428. ignores2, _, err := m.GetIgnores("default")
  429. if err != nil {
  430. t.Error(err)
  431. }
  432. if arrEqual(expected, ignores2) {
  433. t.Errorf("Incorrect ignores: %v == %v", ignores2, expected)
  434. }
  435. if !arrEqual(ignores, ignores2) {
  436. t.Errorf("Incorrect ignores: %v != %v", ignores2, ignores)
  437. }
  438. err = m.SetIgnores("default", expected)
  439. if err != nil {
  440. t.Error(err)
  441. }
  442. ignores, _, err = m.GetIgnores("default")
  443. if err != nil {
  444. t.Error(err)
  445. }
  446. if !arrEqual(ignores, expected) {
  447. t.Errorf("Incorrect ignores: %v != %v", ignores, expected)
  448. }
  449. ignores, _, err = m.GetIgnores("doesnotexist")
  450. if err == nil {
  451. t.Error("No error")
  452. }
  453. err = m.SetIgnores("doesnotexist", expected)
  454. if err == nil {
  455. t.Error("No error")
  456. }
  457. m.AddFolder(config.FolderConfiguration{ID: "fresh", Path: "XXX"})
  458. ignores, _, err = m.GetIgnores("fresh")
  459. if err != nil {
  460. t.Error(err)
  461. }
  462. if len(ignores) > 0 {
  463. t.Errorf("Expected no ignores, got: %v", ignores)
  464. }
  465. }
  466. func TestRefuseUnknownBits(t *testing.T) {
  467. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  468. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  469. m.AddFolder(defaultFolderConfig)
  470. m.ScanFolder("default")
  471. m.Index(device1, "default", []protocol.FileInfo{
  472. {
  473. Name: "invalid1",
  474. Flags: (protocol.FlagsAll + 1) &^ protocol.FlagInvalid,
  475. },
  476. {
  477. Name: "invalid2",
  478. Flags: (protocol.FlagsAll + 2) &^ protocol.FlagInvalid,
  479. },
  480. {
  481. Name: "invalid3",
  482. Flags: (1 << 31) &^ protocol.FlagInvalid,
  483. },
  484. {
  485. Name: "valid",
  486. Flags: protocol.FlagsAll &^ (protocol.FlagInvalid | protocol.FlagSymlink),
  487. },
  488. }, 0, nil)
  489. for _, name := range []string{"invalid1", "invalid2", "invalid3"} {
  490. f, ok := m.CurrentGlobalFile("default", name)
  491. if ok || f.Name == name {
  492. t.Error("Invalid file found or name match")
  493. }
  494. }
  495. f, ok := m.CurrentGlobalFile("default", "valid")
  496. if !ok || f.Name != "valid" {
  497. t.Error("Valid file not found or name mismatch", ok, f)
  498. }
  499. }
  500. func TestGlobalDirectoryTree(t *testing.T) {
  501. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  502. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  503. m.AddFolder(defaultFolderConfig)
  504. b := func(isfile bool, path ...string) protocol.FileInfo {
  505. var flags uint32 = protocol.FlagDirectory
  506. blocks := []protocol.BlockInfo{}
  507. if isfile {
  508. flags = 0
  509. 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}}}
  510. }
  511. return protocol.FileInfo{
  512. Name: filepath.Join(path...),
  513. Flags: flags,
  514. Modified: 0x666,
  515. Blocks: blocks,
  516. }
  517. }
  518. filedata := []int64{0x666, 0xa}
  519. testdata := []protocol.FileInfo{
  520. b(false, "another"),
  521. b(false, "another", "directory"),
  522. b(true, "another", "directory", "afile"),
  523. b(false, "another", "directory", "with"),
  524. b(false, "another", "directory", "with", "a"),
  525. b(true, "another", "directory", "with", "a", "file"),
  526. b(true, "another", "directory", "with", "file"),
  527. b(true, "another", "file"),
  528. b(false, "other"),
  529. b(false, "other", "rand"),
  530. b(false, "other", "random"),
  531. b(false, "other", "random", "dir"),
  532. b(false, "other", "random", "dirx"),
  533. b(false, "other", "randomx"),
  534. b(false, "some"),
  535. b(false, "some", "directory"),
  536. b(false, "some", "directory", "with"),
  537. b(false, "some", "directory", "with", "a"),
  538. b(true, "some", "directory", "with", "a", "file"),
  539. b(true, "rootfile"),
  540. }
  541. expectedResult := map[string]interface{}{
  542. "another": map[string]interface{}{
  543. "directory": map[string]interface{}{
  544. "afile": filedata,
  545. "with": map[string]interface{}{
  546. "a": map[string]interface{}{
  547. "file": filedata,
  548. },
  549. "file": filedata,
  550. },
  551. },
  552. "file": filedata,
  553. },
  554. "other": map[string]interface{}{
  555. "rand": map[string]interface{}{},
  556. "random": map[string]interface{}{
  557. "dir": map[string]interface{}{},
  558. "dirx": map[string]interface{}{},
  559. },
  560. "randomx": map[string]interface{}{},
  561. },
  562. "some": map[string]interface{}{
  563. "directory": map[string]interface{}{
  564. "with": map[string]interface{}{
  565. "a": map[string]interface{}{
  566. "file": filedata,
  567. },
  568. },
  569. },
  570. },
  571. "rootfile": filedata,
  572. }
  573. mm := func(data interface{}) string {
  574. bytes, err := json.Marshal(data)
  575. if err != nil {
  576. panic(err)
  577. }
  578. return string(bytes)
  579. }
  580. m.Index(device1, "default", testdata, 0, nil)
  581. result := m.GlobalDirectoryTree("default", "", -1, false)
  582. if !reflect.DeepEqual(result, expectedResult) {
  583. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult))
  584. }
  585. result = m.GlobalDirectoryTree("default", "another", -1, false)
  586. if !reflect.DeepEqual(result, expectedResult["another"]) {
  587. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult["another"]))
  588. }
  589. result = m.GlobalDirectoryTree("default", "", 0, false)
  590. currentResult := map[string]interface{}{
  591. "another": map[string]interface{}{},
  592. "other": map[string]interface{}{},
  593. "some": map[string]interface{}{},
  594. "rootfile": filedata,
  595. }
  596. if !reflect.DeepEqual(result, currentResult) {
  597. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  598. }
  599. result = m.GlobalDirectoryTree("default", "", 1, false)
  600. currentResult = map[string]interface{}{
  601. "another": map[string]interface{}{
  602. "directory": map[string]interface{}{},
  603. "file": filedata,
  604. },
  605. "other": map[string]interface{}{
  606. "rand": map[string]interface{}{},
  607. "random": map[string]interface{}{},
  608. "randomx": map[string]interface{}{},
  609. },
  610. "some": map[string]interface{}{
  611. "directory": map[string]interface{}{},
  612. },
  613. "rootfile": filedata,
  614. }
  615. if !reflect.DeepEqual(result, currentResult) {
  616. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  617. }
  618. result = m.GlobalDirectoryTree("default", "", -1, true)
  619. currentResult = map[string]interface{}{
  620. "another": map[string]interface{}{
  621. "directory": map[string]interface{}{
  622. "with": map[string]interface{}{
  623. "a": map[string]interface{}{},
  624. },
  625. },
  626. },
  627. "other": map[string]interface{}{
  628. "rand": map[string]interface{}{},
  629. "random": map[string]interface{}{
  630. "dir": map[string]interface{}{},
  631. "dirx": map[string]interface{}{},
  632. },
  633. "randomx": map[string]interface{}{},
  634. },
  635. "some": map[string]interface{}{
  636. "directory": map[string]interface{}{
  637. "with": map[string]interface{}{
  638. "a": map[string]interface{}{},
  639. },
  640. },
  641. },
  642. }
  643. if !reflect.DeepEqual(result, currentResult) {
  644. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  645. }
  646. result = m.GlobalDirectoryTree("default", "", 1, true)
  647. currentResult = map[string]interface{}{
  648. "another": map[string]interface{}{
  649. "directory": map[string]interface{}{},
  650. },
  651. "other": map[string]interface{}{
  652. "rand": map[string]interface{}{},
  653. "random": map[string]interface{}{},
  654. "randomx": map[string]interface{}{},
  655. },
  656. "some": map[string]interface{}{
  657. "directory": map[string]interface{}{},
  658. },
  659. }
  660. if !reflect.DeepEqual(result, currentResult) {
  661. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  662. }
  663. result = m.GlobalDirectoryTree("default", "another", 0, false)
  664. currentResult = map[string]interface{}{
  665. "directory": map[string]interface{}{},
  666. "file": filedata,
  667. }
  668. if !reflect.DeepEqual(result, currentResult) {
  669. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  670. }
  671. result = m.GlobalDirectoryTree("default", "some/directory", 0, false)
  672. currentResult = map[string]interface{}{
  673. "with": map[string]interface{}{},
  674. }
  675. if !reflect.DeepEqual(result, currentResult) {
  676. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  677. }
  678. result = m.GlobalDirectoryTree("default", "some/directory", 1, false)
  679. currentResult = map[string]interface{}{
  680. "with": map[string]interface{}{
  681. "a": map[string]interface{}{},
  682. },
  683. }
  684. if !reflect.DeepEqual(result, currentResult) {
  685. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  686. }
  687. result = m.GlobalDirectoryTree("default", "some/directory", 2, false)
  688. currentResult = map[string]interface{}{
  689. "with": map[string]interface{}{
  690. "a": map[string]interface{}{
  691. "file": filedata,
  692. },
  693. },
  694. }
  695. if !reflect.DeepEqual(result, currentResult) {
  696. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  697. }
  698. result = m.GlobalDirectoryTree("default", "another", -1, true)
  699. currentResult = map[string]interface{}{
  700. "directory": map[string]interface{}{
  701. "with": map[string]interface{}{
  702. "a": map[string]interface{}{},
  703. },
  704. },
  705. }
  706. if !reflect.DeepEqual(result, currentResult) {
  707. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  708. }
  709. // No prefix matching!
  710. result = m.GlobalDirectoryTree("default", "som", -1, false)
  711. currentResult = map[string]interface{}{}
  712. if !reflect.DeepEqual(result, currentResult) {
  713. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  714. }
  715. }
  716. func TestGlobalDirectorySelfFixing(t *testing.T) {
  717. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  718. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  719. m.AddFolder(defaultFolderConfig)
  720. b := func(isfile bool, path ...string) protocol.FileInfo {
  721. var flags uint32 = protocol.FlagDirectory
  722. blocks := []protocol.BlockInfo{}
  723. if isfile {
  724. flags = 0
  725. 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}}}
  726. }
  727. return protocol.FileInfo{
  728. Name: filepath.Join(path...),
  729. Flags: flags,
  730. Modified: 0x666,
  731. Blocks: blocks,
  732. }
  733. }
  734. filedata := []int64{0x666, 0xa}
  735. testdata := []protocol.FileInfo{
  736. b(true, "another", "directory", "afile"),
  737. b(true, "another", "directory", "with", "a", "file"),
  738. b(true, "another", "directory", "with", "file"),
  739. b(false, "other", "random", "dirx"),
  740. b(false, "other", "randomx"),
  741. b(false, "some", "directory", "with", "x"),
  742. b(true, "some", "directory", "with", "a", "file"),
  743. b(false, "this", "is", "a", "deep", "invalid", "directory"),
  744. b(true, "xthis", "is", "a", "deep", "invalid", "file"),
  745. }
  746. expectedResult := map[string]interface{}{
  747. "another": map[string]interface{}{
  748. "directory": map[string]interface{}{
  749. "afile": filedata,
  750. "with": map[string]interface{}{
  751. "a": map[string]interface{}{
  752. "file": filedata,
  753. },
  754. "file": filedata,
  755. },
  756. },
  757. },
  758. "other": map[string]interface{}{
  759. "random": map[string]interface{}{
  760. "dirx": map[string]interface{}{},
  761. },
  762. "randomx": map[string]interface{}{},
  763. },
  764. "some": map[string]interface{}{
  765. "directory": map[string]interface{}{
  766. "with": map[string]interface{}{
  767. "a": map[string]interface{}{
  768. "file": filedata,
  769. },
  770. "x": map[string]interface{}{},
  771. },
  772. },
  773. },
  774. "this": map[string]interface{}{
  775. "is": map[string]interface{}{
  776. "a": map[string]interface{}{
  777. "deep": map[string]interface{}{
  778. "invalid": map[string]interface{}{
  779. "directory": map[string]interface{}{},
  780. },
  781. },
  782. },
  783. },
  784. },
  785. "xthis": map[string]interface{}{
  786. "is": map[string]interface{}{
  787. "a": map[string]interface{}{
  788. "deep": map[string]interface{}{
  789. "invalid": map[string]interface{}{
  790. "file": filedata,
  791. },
  792. },
  793. },
  794. },
  795. },
  796. }
  797. mm := func(data interface{}) string {
  798. bytes, err := json.Marshal(data)
  799. if err != nil {
  800. panic(err)
  801. }
  802. return string(bytes)
  803. }
  804. m.Index(device1, "default", testdata, 0, nil)
  805. result := m.GlobalDirectoryTree("default", "", -1, false)
  806. if !reflect.DeepEqual(result, expectedResult) {
  807. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult))
  808. }
  809. result = m.GlobalDirectoryTree("default", "xthis/is/a/deep", -1, false)
  810. currentResult := map[string]interface{}{
  811. "invalid": map[string]interface{}{
  812. "file": filedata,
  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", "xthis/is/a/deep", -1, true)
  819. currentResult = map[string]interface{}{
  820. "invalid": map[string]interface{}{},
  821. }
  822. if !reflect.DeepEqual(result, currentResult) {
  823. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  824. }
  825. // !!! This is actually BAD, because we don't have enough level allowance
  826. // to accept this file, hence the tree is left unbuilt !!!
  827. result = m.GlobalDirectoryTree("default", "xthis", 1, false)
  828. currentResult = map[string]interface{}{}
  829. if !reflect.DeepEqual(result, currentResult) {
  830. t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
  831. }
  832. }
  833. func genDeepFiles(n, d int) []protocol.FileInfo {
  834. rand.Seed(int64(n))
  835. files := make([]protocol.FileInfo, n)
  836. t := time.Now().Unix()
  837. for i := 0; i < n; i++ {
  838. path := ""
  839. for i := 0; i <= d; i++ {
  840. path = filepath.Join(path, strconv.Itoa(rand.Int()))
  841. }
  842. sofar := ""
  843. for _, path := range filepath.SplitList(path) {
  844. sofar = filepath.Join(sofar, path)
  845. files[i] = protocol.FileInfo{
  846. Name: sofar,
  847. }
  848. i++
  849. }
  850. files[i].Modified = t
  851. files[i].Blocks = []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}}
  852. }
  853. return files
  854. }
  855. func BenchmarkTree_10000_50(b *testing.B) {
  856. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  857. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  858. m.AddFolder(defaultFolderConfig)
  859. m.ScanFolder("default")
  860. files := genDeepFiles(10000, 50)
  861. m.Index(device1, "default", files, 0, nil)
  862. b.ResetTimer()
  863. for i := 0; i < b.N; i++ {
  864. m.GlobalDirectoryTree("default", "", -1, false)
  865. }
  866. }
  867. func BenchmarkTree_10000_10(b *testing.B) {
  868. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  869. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  870. m.AddFolder(defaultFolderConfig)
  871. m.ScanFolder("default")
  872. files := genDeepFiles(10000, 10)
  873. m.Index(device1, "default", files, 0, nil)
  874. b.ResetTimer()
  875. for i := 0; i < b.N; i++ {
  876. m.GlobalDirectoryTree("default", "", -1, false)
  877. }
  878. }
  879. func BenchmarkTree_00100_50(b *testing.B) {
  880. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  881. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  882. m.AddFolder(defaultFolderConfig)
  883. m.ScanFolder("default")
  884. files := genDeepFiles(100, 50)
  885. m.Index(device1, "default", files, 0, nil)
  886. b.ResetTimer()
  887. for i := 0; i < b.N; i++ {
  888. m.GlobalDirectoryTree("default", "", -1, false)
  889. }
  890. }
  891. func BenchmarkTree_00100_10(b *testing.B) {
  892. db, _ := leveldb.Open(storage.NewMemStorage(), nil)
  893. m := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db)
  894. m.AddFolder(defaultFolderConfig)
  895. m.ScanFolder("default")
  896. files := genDeepFiles(100, 10)
  897. m.Index(device1, "default", files, 0, nil)
  898. b.ResetTimer()
  899. for i := 0; i < b.N; i++ {
  900. m.GlobalDirectoryTree("default", "", -1, false)
  901. }
  902. }