common_test.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This program is free software: you can redistribute it and/or modify it
  4. // under the terms of the GNU General Public License as published by the Free
  5. // Software Foundation, either version 3 of the License, or (at your option)
  6. // any later version.
  7. //
  8. // This program is distributed in the hope that it will be useful, but WITHOUT
  9. // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10. // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  11. // more details.
  12. //
  13. // You should have received a copy of the GNU General Public License along
  14. // with this program. If not, see <http://www.gnu.org/licenses/>.
  15. // +build integration
  16. package integration_test
  17. import (
  18. "crypto/md5"
  19. "encoding/json"
  20. "errors"
  21. "fmt"
  22. "io"
  23. "log"
  24. "math/rand"
  25. "net/http"
  26. "os"
  27. "os/exec"
  28. "path/filepath"
  29. "time"
  30. "github.com/syncthing/syncthing/internal/symlinks"
  31. )
  32. func init() {
  33. rand.Seed(42)
  34. }
  35. const (
  36. id1 = "I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"
  37. id2 = "JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU"
  38. apiKey = "abc123"
  39. )
  40. var env = []string{
  41. "HOME=.",
  42. "STTRACE=model,protocol",
  43. "STGUIAPIKEY=" + apiKey,
  44. "STNORESTART=1",
  45. "STPERFSTATS=1",
  46. }
  47. type syncthingProcess struct {
  48. log string
  49. argv []string
  50. port int
  51. apiKey string
  52. csrfToken string
  53. lastEvent int
  54. cmd *exec.Cmd
  55. logfd *os.File
  56. }
  57. func (p *syncthingProcess) start() error {
  58. if p.logfd == nil {
  59. logfd, err := os.Create(p.log)
  60. if err != nil {
  61. return err
  62. }
  63. p.logfd = logfd
  64. }
  65. cmd := exec.Command("../bin/syncthing", p.argv...)
  66. cmd.Stdout = p.logfd
  67. cmd.Stderr = p.logfd
  68. cmd.Env = append(env, fmt.Sprintf("STPROFILER=:%d", p.port+1000))
  69. err := cmd.Start()
  70. if err != nil {
  71. return err
  72. }
  73. p.cmd = cmd
  74. for {
  75. resp, err := p.get("/")
  76. if err == nil {
  77. resp.Body.Close()
  78. return nil
  79. }
  80. time.Sleep(250 * time.Millisecond)
  81. }
  82. }
  83. func (p *syncthingProcess) stop() {
  84. p.cmd.Process.Signal(os.Interrupt)
  85. p.cmd.Wait()
  86. }
  87. func (p *syncthingProcess) get(path string) (*http.Response, error) {
  88. client := &http.Client{
  89. Timeout: 2 * time.Second,
  90. Transport: &http.Transport{
  91. DisableKeepAlives: true,
  92. },
  93. }
  94. req, err := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d%s", p.port, path), nil)
  95. if err != nil {
  96. return nil, err
  97. }
  98. if p.apiKey != "" {
  99. req.Header.Add("X-API-Key", p.apiKey)
  100. }
  101. if p.csrfToken != "" {
  102. req.Header.Add("X-CSRF-Token", p.csrfToken)
  103. }
  104. resp, err := client.Do(req)
  105. if err != nil {
  106. return nil, err
  107. }
  108. return resp, nil
  109. }
  110. func (p *syncthingProcess) post(path string, data io.Reader) (*http.Response, error) {
  111. client := &http.Client{
  112. Timeout: 2 * time.Second,
  113. Transport: &http.Transport{
  114. DisableKeepAlives: true,
  115. },
  116. }
  117. req, err := http.NewRequest("POST", fmt.Sprintf("http://127.0.0.1:%d%s", p.port, path), data)
  118. if err != nil {
  119. return nil, err
  120. }
  121. if p.apiKey != "" {
  122. req.Header.Add("X-API-Key", p.apiKey)
  123. }
  124. if p.csrfToken != "" {
  125. req.Header.Add("X-CSRF-Token", p.csrfToken)
  126. }
  127. req.Header.Add("Content-Type", "application/json")
  128. resp, err := client.Do(req)
  129. if err != nil {
  130. return nil, err
  131. }
  132. return resp, nil
  133. }
  134. func (p *syncthingProcess) peerCompletion() (map[string]int, error) {
  135. resp, err := p.get("/rest/debug/peerCompletion")
  136. if err != nil {
  137. return nil, err
  138. }
  139. defer resp.Body.Close()
  140. comp := map[string]int{}
  141. err = json.NewDecoder(resp.Body).Decode(&comp)
  142. return comp, err
  143. }
  144. type event struct {
  145. ID int
  146. Time time.Time
  147. Type string
  148. Data interface{}
  149. }
  150. func (p *syncthingProcess) events() ([]event, error) {
  151. resp, err := p.get(fmt.Sprintf("/rest/events?since=%d", p.lastEvent))
  152. if err != nil {
  153. return nil, err
  154. }
  155. defer resp.Body.Close()
  156. var evs []event
  157. err = json.NewDecoder(resp.Body).Decode(&evs)
  158. if err != nil {
  159. return nil, err
  160. }
  161. p.lastEvent = evs[len(evs)-1].ID
  162. return evs, err
  163. }
  164. type versionResp struct {
  165. Version string
  166. }
  167. func (p *syncthingProcess) version() (string, error) {
  168. resp, err := p.get("/rest/version")
  169. if err != nil {
  170. return "", err
  171. }
  172. defer resp.Body.Close()
  173. var v versionResp
  174. err = json.NewDecoder(resp.Body).Decode(&v)
  175. if err != nil {
  176. return "", err
  177. }
  178. return v.Version, nil
  179. }
  180. type fileGenerator struct {
  181. files int
  182. maxexp int
  183. srcname string
  184. }
  185. func generateFiles(dir string, files, maxexp int, srcname string) error {
  186. fd, err := os.Open(srcname)
  187. if err != nil {
  188. return err
  189. }
  190. for i := 0; i < files; i++ {
  191. n := randomName()
  192. p0 := filepath.Join(dir, string(n[0]), n[0:2])
  193. err = os.MkdirAll(p0, 0755)
  194. if err != nil {
  195. log.Fatal(err)
  196. }
  197. s := 1 << uint(rand.Intn(maxexp))
  198. a := 128 * 1024
  199. if a > s {
  200. a = s
  201. }
  202. s += rand.Intn(a)
  203. src := io.LimitReader(&inifiteReader{fd}, int64(s))
  204. p1 := filepath.Join(p0, n)
  205. dst, err := os.Create(p1)
  206. if err != nil {
  207. return err
  208. }
  209. _, err = io.Copy(dst, src)
  210. if err != nil {
  211. return err
  212. }
  213. err = dst.Close()
  214. if err != nil {
  215. return err
  216. }
  217. err = os.Chmod(p1, os.FileMode(rand.Intn(0777)|0400))
  218. if err != nil {
  219. return err
  220. }
  221. t := time.Now().Add(-time.Duration(rand.Intn(30*86400)) * time.Second)
  222. err = os.Chtimes(p1, t, t)
  223. if err != nil {
  224. return err
  225. }
  226. }
  227. return nil
  228. }
  229. func ReadRand(bs []byte) (int, error) {
  230. var r uint32
  231. for i := range bs {
  232. if i%4 == 0 {
  233. r = uint32(rand.Int63())
  234. }
  235. bs[i] = byte(r >> uint((i%4)*8))
  236. }
  237. return len(bs), nil
  238. }
  239. func randomName() string {
  240. var b [16]byte
  241. ReadRand(b[:])
  242. return fmt.Sprintf("%x", b[:])
  243. }
  244. type inifiteReader struct {
  245. rd io.ReadSeeker
  246. }
  247. func (i *inifiteReader) Read(bs []byte) (int, error) {
  248. n, err := i.rd.Read(bs)
  249. if err == io.EOF {
  250. err = nil
  251. i.rd.Seek(0, 0)
  252. }
  253. return n, err
  254. }
  255. // rm -rf
  256. func removeAll(dirs ...string) error {
  257. for _, dir := range dirs {
  258. os.RemoveAll(dir)
  259. }
  260. return nil
  261. }
  262. // Compare a number of directories. Returns nil if the contents are identical,
  263. // otherwise an error describing the first found difference.
  264. func compareDirectories(dirs ...string) error {
  265. chans := make([]chan fileInfo, len(dirs))
  266. for i := range chans {
  267. chans[i] = make(chan fileInfo)
  268. }
  269. abort := make(chan struct{})
  270. for i := range dirs {
  271. startWalker(dirs[i], chans[i], abort)
  272. }
  273. res := make([]fileInfo, len(dirs))
  274. for {
  275. numDone := 0
  276. for i := range chans {
  277. fi, ok := <-chans[i]
  278. if !ok {
  279. numDone++
  280. }
  281. res[i] = fi
  282. }
  283. for i := 1; i < len(res); i++ {
  284. if res[i] != res[0] {
  285. close(abort)
  286. return fmt.Errorf("Mismatch; %#v (%s) != %#v (%s)", res[i], dirs[i], res[0], dirs[0])
  287. }
  288. }
  289. if numDone == len(dirs) {
  290. return nil
  291. }
  292. }
  293. }
  294. type fileInfo struct {
  295. name string
  296. mode os.FileMode
  297. mod int64
  298. hash [16]byte
  299. }
  300. func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) {
  301. walker := func(path string, info os.FileInfo, err error) error {
  302. if err != nil {
  303. return err
  304. }
  305. rn, _ := filepath.Rel(dir, path)
  306. if rn == "." || rn == ".stfolder" {
  307. return nil
  308. }
  309. var f fileInfo
  310. if ok, err := symlinks.IsSymlink(path); err == nil && ok {
  311. f = fileInfo{
  312. name: rn,
  313. mode: os.ModeSymlink,
  314. }
  315. tgt, _, err := symlinks.Read(path)
  316. if err != nil {
  317. return err
  318. }
  319. h := md5.New()
  320. h.Write([]byte(tgt))
  321. hash := h.Sum(nil)
  322. copy(f.hash[:], hash)
  323. } else if info.IsDir() {
  324. f = fileInfo{
  325. name: rn,
  326. mode: info.Mode(),
  327. // hash and modtime zero for directories
  328. }
  329. } else {
  330. f = fileInfo{
  331. name: rn,
  332. mode: info.Mode(),
  333. mod: info.ModTime().Unix(),
  334. }
  335. sum, err := md5file(path)
  336. if err != nil {
  337. return err
  338. }
  339. f.hash = sum
  340. }
  341. select {
  342. case res <- f:
  343. return nil
  344. case <-abort:
  345. return errors.New("abort")
  346. }
  347. }
  348. go func() {
  349. filepath.Walk(dir, walker)
  350. close(res)
  351. }()
  352. }
  353. func md5file(fname string) (hash [16]byte, err error) {
  354. f, err := os.Open(fname)
  355. if err != nil {
  356. return
  357. }
  358. defer f.Close()
  359. h := md5.New()
  360. io.Copy(h, f)
  361. hb := h.Sum(nil)
  362. copy(hash[:], hb)
  363. return
  364. }