util.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  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. // +build integration
  7. package integration
  8. import (
  9. "crypto/md5"
  10. cr "crypto/rand"
  11. "errors"
  12. "fmt"
  13. "io"
  14. "io/ioutil"
  15. "log"
  16. "math/rand"
  17. "os"
  18. "path/filepath"
  19. "runtime"
  20. "sort"
  21. "strings"
  22. "testing"
  23. "time"
  24. "unicode"
  25. "github.com/syncthing/syncthing/lib/osutil"
  26. "github.com/syncthing/syncthing/lib/rc"
  27. "github.com/syncthing/syncthing/lib/symlinks"
  28. )
  29. func init() {
  30. rand.Seed(42)
  31. }
  32. const (
  33. id1 = "I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"
  34. id2 = "MRIW7OK-NETT3M4-N6SBWME-N25O76W-YJKVXPH-FUMQJ3S-P57B74J-GBITBAC"
  35. id3 = "373HSRP-QLPNLIE-JYKZVQF-P4PKZ63-R2ZE6K3-YD442U2-JHBGBQG-WWXAHAU"
  36. apiKey = "abc123"
  37. )
  38. func generateFiles(dir string, files, maxexp int, srcname string) error {
  39. fd, err := os.Open(srcname)
  40. if err != nil {
  41. return err
  42. }
  43. for i := 0; i < files; i++ {
  44. n := randomName()
  45. if rand.Float64() < 0.05 {
  46. // Some files and directories are dotfiles
  47. n = "." + n
  48. }
  49. p0 := filepath.Join(dir, string(n[0]), n[0:2])
  50. err = os.MkdirAll(p0, 0755)
  51. if err != nil {
  52. log.Fatal(err)
  53. }
  54. p1 := filepath.Join(p0, n)
  55. s := int64(1 << uint(rand.Intn(maxexp)))
  56. a := int64(128 * 1024)
  57. if a > s {
  58. a = s
  59. }
  60. s += rand.Int63n(a)
  61. if err := generateOneFile(fd, p1, s); err != nil {
  62. return err
  63. }
  64. }
  65. return nil
  66. }
  67. func generateOneFile(fd io.ReadSeeker, p1 string, s int64) error {
  68. src := io.LimitReader(&inifiteReader{fd}, int64(s))
  69. dst, err := os.Create(p1)
  70. if err != nil {
  71. return err
  72. }
  73. _, err = io.Copy(dst, src)
  74. if err != nil {
  75. return err
  76. }
  77. err = dst.Close()
  78. if err != nil {
  79. return err
  80. }
  81. _ = os.Chmod(p1, os.FileMode(rand.Intn(0777)|0400))
  82. t := time.Now().Add(-time.Duration(rand.Intn(30*86400)) * time.Second)
  83. err = os.Chtimes(p1, t, t)
  84. if err != nil {
  85. return err
  86. }
  87. return nil
  88. }
  89. func alterFiles(dir string) error {
  90. err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
  91. if os.IsNotExist(err) {
  92. // Something we deleted. Never mind.
  93. return nil
  94. }
  95. info, err = os.Stat(path)
  96. if err != nil {
  97. // Something we deleted while walking. Ignore.
  98. return nil
  99. }
  100. if strings.HasPrefix(filepath.Base(path), "test-") {
  101. return nil
  102. }
  103. switch filepath.Base(path) {
  104. case ".stfolder":
  105. return nil
  106. case ".stversions":
  107. return nil
  108. }
  109. // File structure is base/x/xy/xyz12345...
  110. // comps == 1: base (don't touch)
  111. // comps == 2: base/x (must be dir)
  112. // comps == 3: base/x/xy (must be dir)
  113. // comps > 3: base/x/xy/xyz12345... (can be dir or file)
  114. comps := len(strings.Split(path, string(os.PathSeparator)))
  115. r := rand.Intn(10)
  116. switch {
  117. case r == 0 && comps > 2:
  118. // Delete every tenth file or directory, except top levels
  119. return removeAll(path)
  120. case r == 1 && info.Mode().IsRegular():
  121. if info.Mode()&0200 != 0200 {
  122. // Not owner writable. Fix.
  123. if err = os.Chmod(path, 0644); err != nil {
  124. return err
  125. }
  126. }
  127. // Overwrite a random kilobyte of every tenth file
  128. fd, err := os.OpenFile(path, os.O_RDWR, 0644)
  129. if err != nil {
  130. return err
  131. }
  132. if info.Size() > 1024 {
  133. _, err = fd.Seek(rand.Int63n(info.Size()), os.SEEK_SET)
  134. if err != nil {
  135. return err
  136. }
  137. }
  138. _, err = io.Copy(fd, io.LimitReader(cr.Reader, 1024))
  139. if err != nil {
  140. return err
  141. }
  142. return fd.Close()
  143. // Change capitalization
  144. case r == 2 && comps > 3 && rand.Float64() < 0.2:
  145. if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
  146. // Syncthing is currently broken for case-only renames on case-
  147. // insensitive platforms.
  148. // https://github.com/syncthing/syncthing/issues/1787
  149. return nil
  150. }
  151. base := []rune(filepath.Base(path))
  152. for i, r := range base {
  153. if rand.Float64() < 0.5 {
  154. base[i] = unicode.ToLower(r)
  155. } else {
  156. base[i] = unicode.ToUpper(r)
  157. }
  158. }
  159. newPath := filepath.Join(filepath.Dir(path), string(base))
  160. if newPath != path {
  161. return osutil.TryRename(path, newPath)
  162. }
  163. /*
  164. This doesn't in fact work. Sometimes it appears to. We need to get this sorted...
  165. // Switch between files and directories
  166. case r == 3 && comps > 3 && rand.Float64() < 0.2:
  167. if !info.Mode().IsRegular() {
  168. err = removeAll(path)
  169. if err != nil {
  170. return err
  171. }
  172. d1 := []byte("I used to be a dir: " + path)
  173. err := ioutil.WriteFile(path, d1, 0644)
  174. if err != nil {
  175. return err
  176. }
  177. } else {
  178. err := os.Remove(path)
  179. if err != nil {
  180. return err
  181. }
  182. err = os.MkdirAll(path, 0755)
  183. if err != nil {
  184. return err
  185. }
  186. generateFiles(path, 10, 20, "../LICENSE")
  187. }
  188. return err
  189. */
  190. /*
  191. This fails. Bug?
  192. // Rename the file, while potentially moving it up in the directory hierarchy
  193. case r == 4 && comps > 2 && (info.Mode().IsRegular() || rand.Float64() < 0.2):
  194. rpath := filepath.Dir(path)
  195. if rand.Float64() < 0.2 {
  196. for move := rand.Intn(comps - 1); move > 0; move-- {
  197. rpath = filepath.Join(rpath, "..")
  198. }
  199. }
  200. return osutil.TryRename(path, filepath.Join(rpath, randomName()))
  201. */
  202. }
  203. return nil
  204. })
  205. if err != nil {
  206. return err
  207. }
  208. return generateFiles(dir, 25, 20, "../LICENSE")
  209. }
  210. func ReadRand(bs []byte) (int, error) {
  211. var r uint32
  212. for i := range bs {
  213. if i%4 == 0 {
  214. r = uint32(rand.Int63())
  215. }
  216. bs[i] = byte(r >> uint((i%4)*8))
  217. }
  218. return len(bs), nil
  219. }
  220. func randomName() string {
  221. var b [16]byte
  222. ReadRand(b[:])
  223. return fmt.Sprintf("%x", b[:])
  224. }
  225. type inifiteReader struct {
  226. rd io.ReadSeeker
  227. }
  228. func (i *inifiteReader) Read(bs []byte) (int, error) {
  229. n, err := i.rd.Read(bs)
  230. if err == io.EOF {
  231. err = nil
  232. i.rd.Seek(0, 0)
  233. }
  234. return n, err
  235. }
  236. // rm -rf
  237. func removeAll(dirs ...string) error {
  238. for _, dir := range dirs {
  239. files, err := osutil.Glob(dir)
  240. if err != nil {
  241. return err
  242. }
  243. for _, file := range files {
  244. // Set any non-writeable files and dirs to writeable. This is necessary for os.RemoveAll to work on Windows.
  245. filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
  246. if err != nil {
  247. return err
  248. }
  249. if info.Mode()&0700 != 0700 {
  250. os.Chmod(path, 0777)
  251. }
  252. return nil
  253. })
  254. os.RemoveAll(file)
  255. }
  256. }
  257. return nil
  258. }
  259. // Compare a number of directories. Returns nil if the contents are identical,
  260. // otherwise an error describing the first found difference.
  261. func compareDirectories(dirs ...string) error {
  262. chans := make([]chan fileInfo, len(dirs))
  263. for i := range chans {
  264. chans[i] = make(chan fileInfo)
  265. }
  266. errcs := make([]chan error, len(dirs))
  267. abort := make(chan struct{})
  268. for i := range dirs {
  269. errcs[i] = startWalker(dirs[i], chans[i], abort)
  270. }
  271. res := make([]fileInfo, len(dirs))
  272. for {
  273. numDone := 0
  274. for i := range chans {
  275. fi, ok := <-chans[i]
  276. if !ok {
  277. err, hasError := <-errcs[i]
  278. if hasError {
  279. close(abort)
  280. return err
  281. }
  282. numDone++
  283. }
  284. res[i] = fi
  285. }
  286. for i := 1; i < len(res); i++ {
  287. if res[i] != res[0] {
  288. close(abort)
  289. return fmt.Errorf("Mismatch; %#v (%s) != %#v (%s)", res[i], dirs[i], res[0], dirs[0])
  290. }
  291. }
  292. if numDone == len(dirs) {
  293. return nil
  294. }
  295. }
  296. }
  297. func directoryContents(dir string) ([]fileInfo, error) {
  298. res := make(chan fileInfo)
  299. errc := startWalker(dir, res, nil)
  300. var files []fileInfo
  301. for f := range res {
  302. files = append(files, f)
  303. }
  304. return files, <-errc
  305. }
  306. func mergeDirectoryContents(c ...[]fileInfo) []fileInfo {
  307. m := make(map[string]fileInfo)
  308. for _, l := range c {
  309. for _, f := range l {
  310. if cur, ok := m[f.name]; !ok || cur.mod < f.mod {
  311. m[f.name] = f
  312. }
  313. }
  314. }
  315. res := make([]fileInfo, len(m))
  316. i := 0
  317. for _, f := range m {
  318. res[i] = f
  319. i++
  320. }
  321. sort.Sort(fileInfoList(res))
  322. return res
  323. }
  324. func compareDirectoryContents(actual, expected []fileInfo) error {
  325. if len(actual) != len(expected) {
  326. return fmt.Errorf("len(actual) = %d; len(expected) = %d", len(actual), len(expected))
  327. }
  328. for i := range actual {
  329. if actual[i] != expected[i] {
  330. return fmt.Errorf("Mismatch; actual %#v != expected %#v", actual[i], expected[i])
  331. }
  332. }
  333. return nil
  334. }
  335. type fileInfo struct {
  336. name string
  337. mode os.FileMode
  338. mod int64
  339. hash [16]byte
  340. size int64
  341. }
  342. func (f fileInfo) String() string {
  343. return fmt.Sprintf("%s %04o %d %x", f.name, f.mode, f.mod, f.hash)
  344. }
  345. type fileInfoList []fileInfo
  346. func (l fileInfoList) Len() int {
  347. return len(l)
  348. }
  349. func (l fileInfoList) Less(a, b int) bool {
  350. return l[a].name < l[b].name
  351. }
  352. func (l fileInfoList) Swap(a, b int) {
  353. l[a], l[b] = l[b], l[a]
  354. }
  355. func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) chan error {
  356. walker := func(path string, info os.FileInfo, err error) error {
  357. if err != nil {
  358. return err
  359. }
  360. rn, _ := filepath.Rel(dir, path)
  361. if rn == "." || rn == ".stfolder" {
  362. return nil
  363. }
  364. if rn == ".stversions" {
  365. return filepath.SkipDir
  366. }
  367. var f fileInfo
  368. if info.Mode()&os.ModeSymlink != 0 {
  369. f = fileInfo{
  370. name: rn,
  371. mode: os.ModeSymlink,
  372. }
  373. tgt, _, err := symlinks.Read(path)
  374. if err != nil {
  375. return err
  376. }
  377. h := md5.New()
  378. h.Write([]byte(tgt))
  379. hash := h.Sum(nil)
  380. copy(f.hash[:], hash)
  381. } else if info.IsDir() {
  382. f = fileInfo{
  383. name: rn,
  384. mode: info.Mode(),
  385. // hash and modtime zero for directories
  386. }
  387. } else {
  388. f = fileInfo{
  389. name: rn,
  390. mode: info.Mode(),
  391. mod: info.ModTime().Truncate(time.Microsecond).UnixNano(),
  392. size: info.Size(),
  393. }
  394. sum, err := md5file(path)
  395. if err != nil {
  396. return err
  397. }
  398. f.hash = sum
  399. }
  400. select {
  401. case res <- f:
  402. return nil
  403. case <-abort:
  404. return errors.New("abort")
  405. }
  406. }
  407. errc := make(chan error)
  408. go func() {
  409. err := filepath.Walk(dir, walker)
  410. close(res)
  411. if err != nil {
  412. errc <- err
  413. }
  414. close(errc)
  415. }()
  416. return errc
  417. }
  418. func md5file(fname string) (hash [16]byte, err error) {
  419. f, err := os.Open(fname)
  420. if err != nil {
  421. return
  422. }
  423. defer f.Close()
  424. h := md5.New()
  425. io.Copy(h, f)
  426. hb := h.Sum(nil)
  427. copy(hash[:], hb)
  428. return
  429. }
  430. func isTimeout(err error) bool {
  431. if err == nil {
  432. return false
  433. }
  434. return strings.Contains(err.Error(), "use of closed network connection") ||
  435. strings.Contains(err.Error(), "request canceled while waiting")
  436. }
  437. func getTestName() string {
  438. callers := make([]uintptr, 100)
  439. runtime.Callers(1, callers)
  440. for i, caller := range callers {
  441. f := runtime.FuncForPC(caller)
  442. if f != nil {
  443. if f.Name() == "testing.tRunner" {
  444. testf := runtime.FuncForPC(callers[i-1])
  445. if testf != nil {
  446. path := strings.Split(testf.Name(), ".")
  447. return path[len(path)-1]
  448. }
  449. }
  450. }
  451. }
  452. return time.Now().String()
  453. }
  454. func checkedStop(t *testing.T, p *rc.Process) {
  455. if _, err := p.Stop(); err != nil {
  456. t.Fatal(err)
  457. }
  458. }
  459. func startInstance(t *testing.T, i int) *rc.Process {
  460. log.Printf("Starting instance %d...", i)
  461. addr := fmt.Sprintf("127.0.0.1:%d", 8080+i)
  462. log := fmt.Sprintf("logs/%s-%d-%d.out", getTestName(), i, time.Now().Unix()%86400)
  463. p := rc.NewProcess(addr)
  464. p.LogTo(log)
  465. if err := p.Start("../bin/syncthing", "-home", fmt.Sprintf("h%d", i), "-no-browser"); err != nil {
  466. t.Fatal(err)
  467. }
  468. p.AwaitStartup()
  469. return p
  470. }
  471. func symlinksSupported() bool {
  472. tmp, err := ioutil.TempDir("", "symlink-test")
  473. if err != nil {
  474. return false
  475. }
  476. defer os.RemoveAll(tmp)
  477. err = os.Symlink("tmp", filepath.Join(tmp, "link"))
  478. return err == nil
  479. }