syncthingprocess.go 6.5 KB

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