syncthingprocess.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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. "bufio"
  10. "bytes"
  11. "encoding/json"
  12. "errors"
  13. "fmt"
  14. "io"
  15. "io/ioutil"
  16. "log"
  17. "net/http"
  18. "os"
  19. "os/exec"
  20. "strconv"
  21. "time"
  22. "github.com/syncthing/protocol"
  23. )
  24. var env = []string{
  25. "HOME=.",
  26. "STGUIAPIKEY=" + apiKey,
  27. "STNORESTART=1",
  28. }
  29. type syncthingProcess struct {
  30. instance string
  31. argv []string
  32. port int
  33. apiKey string
  34. csrfToken string
  35. lastEvent int
  36. id protocol.DeviceID
  37. cmd *exec.Cmd
  38. logfd *os.File
  39. }
  40. func (p *syncthingProcess) start() error {
  41. if p.logfd == nil {
  42. logfd, err := os.Create("logs/" + getTestName() + "-" + p.instance + ".out")
  43. if err != nil {
  44. return err
  45. }
  46. p.logfd = logfd
  47. }
  48. binary := "../bin/syncthing"
  49. // We check to see if there's an instance specific binary we should run,
  50. // for example if we are running integration tests between different
  51. // versions. If there isn't, we just go with the default.
  52. if _, err := os.Stat(binary + "-" + p.instance); err == nil {
  53. binary = binary + "-" + p.instance
  54. }
  55. if _, err := os.Stat(binary + "-" + p.instance + ".exe"); err == nil {
  56. binary = binary + "-" + p.instance + ".exe"
  57. }
  58. argv := append(p.argv, "-no-browser")
  59. cmd := exec.Command(binary, argv...)
  60. cmd.Stdout = p.logfd
  61. cmd.Stderr = p.logfd
  62. cmd.Env = append(os.Environ(), env...)
  63. err := cmd.Start()
  64. if err != nil {
  65. return err
  66. }
  67. p.cmd = cmd
  68. for {
  69. time.Sleep(250 * time.Millisecond)
  70. resp, err := p.get("/rest/system/status")
  71. if err != nil {
  72. continue
  73. }
  74. var sysData map[string]interface{}
  75. err = json.NewDecoder(resp.Body).Decode(&sysData)
  76. resp.Body.Close()
  77. if err != nil {
  78. // This one is unexpected. Print it.
  79. log.Println("/rest/system/status (JSON):", err)
  80. continue
  81. }
  82. id, err := protocol.DeviceIDFromString(sysData["myID"].(string))
  83. if err != nil {
  84. // This one is unexpected. Print it.
  85. log.Println("/rest/system/status (myID):", err)
  86. continue
  87. }
  88. p.id = id
  89. return nil
  90. }
  91. }
  92. func (p *syncthingProcess) stop() (*os.ProcessState, error) {
  93. p.cmd.Process.Signal(os.Kill)
  94. p.cmd.Wait()
  95. fd, err := os.Open(p.logfd.Name())
  96. if err != nil {
  97. return p.cmd.ProcessState, err
  98. }
  99. defer fd.Close()
  100. raceConditionStart := []byte("WARNING: DATA RACE")
  101. raceConditionSep := []byte("==================")
  102. panicConditionStart := []byte("panic:")
  103. panicConditionSep := []byte(p.id.String()[:5])
  104. sc := bufio.NewScanner(fd)
  105. race := false
  106. _panic := false
  107. for sc.Scan() {
  108. line := sc.Bytes()
  109. if race || _panic {
  110. if bytes.Contains(line, panicConditionSep) {
  111. _panic = false
  112. continue
  113. }
  114. fmt.Printf("%s\n", line)
  115. if bytes.Contains(line, raceConditionSep) {
  116. race = false
  117. }
  118. } else if bytes.Contains(line, raceConditionStart) {
  119. fmt.Printf("%s\n", raceConditionSep)
  120. fmt.Printf("%s\n", raceConditionStart)
  121. race = true
  122. if err == nil {
  123. err = errors.New("Race condition detected")
  124. }
  125. } else if bytes.Contains(line, panicConditionStart) {
  126. _panic = true
  127. if err == nil {
  128. err = errors.New("Panic detected")
  129. }
  130. }
  131. }
  132. return p.cmd.ProcessState, err
  133. }
  134. func (p *syncthingProcess) get(path string) (*http.Response, error) {
  135. client := &http.Client{
  136. Timeout: 30 * time.Second,
  137. Transport: &http.Transport{
  138. DisableKeepAlives: true,
  139. },
  140. }
  141. req, err := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d%s", p.port, path), nil)
  142. if err != nil {
  143. return nil, err
  144. }
  145. if p.apiKey != "" {
  146. req.Header.Add("X-API-Key", p.apiKey)
  147. }
  148. if p.csrfToken != "" {
  149. req.Header.Add("X-CSRF-Token", p.csrfToken)
  150. }
  151. resp, err := client.Do(req)
  152. if err != nil {
  153. return nil, err
  154. }
  155. return resp, nil
  156. }
  157. func (p *syncthingProcess) post(path string, data io.Reader) (*http.Response, error) {
  158. client := &http.Client{
  159. Timeout: 600 * time.Second,
  160. Transport: &http.Transport{
  161. DisableKeepAlives: true,
  162. },
  163. }
  164. req, err := http.NewRequest("POST", fmt.Sprintf("http://127.0.0.1:%d%s", p.port, path), data)
  165. if err != nil {
  166. return nil, err
  167. }
  168. if p.apiKey != "" {
  169. req.Header.Add("X-API-Key", p.apiKey)
  170. }
  171. if p.csrfToken != "" {
  172. req.Header.Add("X-CSRF-Token", p.csrfToken)
  173. }
  174. req.Header.Add("Content-Type", "application/json")
  175. resp, err := client.Do(req)
  176. if err != nil {
  177. return nil, err
  178. }
  179. return resp, nil
  180. }
  181. func (p *syncthingProcess) peerCompletion() (map[string]int, error) {
  182. resp, err := p.get("/rest/debug/peerCompletion")
  183. if err != nil {
  184. return nil, err
  185. }
  186. defer resp.Body.Close()
  187. comp := map[string]int{}
  188. err = json.NewDecoder(resp.Body).Decode(&comp)
  189. // Remove ourselves from the set. In the remaining map, all peers should
  190. // be att 100% if we're in sync.
  191. for id := range comp {
  192. if id == p.id.String() {
  193. delete(comp, id)
  194. }
  195. }
  196. return comp, err
  197. }
  198. func (p *syncthingProcess) allPeersInSync() error {
  199. comp, err := p.peerCompletion()
  200. if err != nil {
  201. return err
  202. }
  203. for id, val := range comp {
  204. if val != 100 {
  205. return fmt.Errorf("%.7s at %d%%", id, val)
  206. }
  207. }
  208. return nil
  209. }
  210. type model struct {
  211. GlobalBytes int
  212. GlobalDeleted int
  213. GlobalFiles int
  214. InSyncBytes int
  215. InSyncFiles int
  216. Invalid string
  217. LocalBytes int
  218. LocalDeleted int
  219. LocalFiles int
  220. NeedBytes int
  221. NeedFiles int
  222. State string
  223. StateChanged time.Time
  224. Version int
  225. }
  226. func (p *syncthingProcess) model(folder string) (model, error) {
  227. resp, err := p.get("/rest/db/status?folder=" + folder)
  228. if err != nil {
  229. return model{}, err
  230. }
  231. var res model
  232. err = json.NewDecoder(resp.Body).Decode(&res)
  233. if err != nil {
  234. return model{}, err
  235. }
  236. return res, nil
  237. }
  238. type event struct {
  239. ID int
  240. Time time.Time
  241. Type string
  242. Data interface{}
  243. }
  244. func (p *syncthingProcess) events() ([]event, error) {
  245. resp, err := p.get(fmt.Sprintf("/rest/events?since=%d", p.lastEvent))
  246. if err != nil {
  247. return nil, err
  248. }
  249. defer resp.Body.Close()
  250. var evs []event
  251. err = json.NewDecoder(resp.Body).Decode(&evs)
  252. if err != nil {
  253. return nil, err
  254. }
  255. p.lastEvent = evs[len(evs)-1].ID
  256. return evs, err
  257. }
  258. type versionResp struct {
  259. Version string
  260. }
  261. func (p *syncthingProcess) version() (string, error) {
  262. resp, err := p.get("/rest/system/version")
  263. if err != nil {
  264. return "", err
  265. }
  266. defer resp.Body.Close()
  267. var v versionResp
  268. err = json.NewDecoder(resp.Body).Decode(&v)
  269. if err != nil {
  270. return "", err
  271. }
  272. return v.Version, nil
  273. }
  274. func (p *syncthingProcess) rescan(folder string) error {
  275. resp, err := p.post("/rest/db/scan?folder="+folder, nil)
  276. if err != nil {
  277. return err
  278. }
  279. data, _ := ioutil.ReadAll(resp.Body)
  280. resp.Body.Close()
  281. if resp.StatusCode != 200 {
  282. return fmt.Errorf("Rescan %q: status code %d: %s", folder, resp.StatusCode, data)
  283. }
  284. return nil
  285. }
  286. func (p *syncthingProcess) rescanNext(folder string, next time.Duration) error {
  287. resp, err := p.post("/rest/db/scan?folder="+folder+"&next="+strconv.Itoa(int(next.Seconds())), nil)
  288. if err != nil {
  289. return err
  290. }
  291. data, _ := ioutil.ReadAll(resp.Body)
  292. resp.Body.Close()
  293. if resp.StatusCode != 200 {
  294. return fmt.Errorf("Rescan %q: status code %d: %s", folder, resp.StatusCode, data)
  295. }
  296. return nil
  297. }
  298. func (p *syncthingProcess) reset(folder string) error {
  299. resp, err := p.post("/rest/system/reset?folder="+folder, nil)
  300. if err != nil {
  301. return err
  302. }
  303. data, _ := ioutil.ReadAll(resp.Body)
  304. resp.Body.Close()
  305. if resp.StatusCode != 200 {
  306. return fmt.Errorf("Reset %q: status code %d: %s", folder, resp.StatusCode, data)
  307. }
  308. return nil
  309. }
  310. func allDevicesInSync(p []syncthingProcess) error {
  311. for _, device := range p {
  312. if err := device.allPeersInSync(); err != nil {
  313. return fmt.Errorf("%.7s: %v", device.id.String(), err)
  314. }
  315. }
  316. return nil
  317. }