model_test.go 31 KB

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