syncthingprocess.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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/status")
  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/status (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/status (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. panicConditionStart := []byte("panic:")
  101. panicConditionSep := []byte(p.id.String()[:5])
  102. sc := bufio.NewScanner(fd)
  103. race := false
  104. _panic := false
  105. for sc.Scan() {
  106. line := sc.Bytes()
  107. if race || _panic {
  108. if bytes.Contains(line, panicConditionSep) {
  109. _panic = false
  110. continue
  111. }
  112. fmt.Printf("%s\n", line)
  113. if bytes.Contains(line, raceConditionSep) {
  114. race = false
  115. }
  116. } else if bytes.Contains(line, raceConditionStart) {
  117. fmt.Printf("%s\n", raceConditionSep)
  118. fmt.Printf("%s\n", raceConditionStart)
  119. race = true
  120. if err == nil {
  121. err = errors.New("Race condition detected")
  122. }
  123. } else if bytes.Contains(line, panicConditionStart) {
  124. _panic = true
  125. if err == nil {
  126. err = errors.New("Panic detected")
  127. }
  128. }
  129. }
  130. return err
  131. }
  132. func (p *syncthingProcess) get(path string) (*http.Response, error) {
  133. client := &http.Client{
  134. Timeout: 30 * time.Second,
  135. Transport: &http.Transport{
  136. DisableKeepAlives: true,
  137. },
  138. }
  139. req, err := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d%s", p.port, path), nil)
  140. if err != nil {
  141. return nil, err
  142. }
  143. if p.apiKey != "" {
  144. req.Header.Add("X-API-Key", p.apiKey)
  145. }
  146. if p.csrfToken != "" {
  147. req.Header.Add("X-CSRF-Token", p.csrfToken)
  148. }
  149. resp, err := client.Do(req)
  150. if err != nil {
  151. return nil, err
  152. }
  153. return resp, nil
  154. }
  155. func (p *syncthingProcess) post(path string, data io.Reader) (*http.Response, error) {
  156. client := &http.Client{
  157. Timeout: 600 * time.Second,
  158. Transport: &http.Transport{
  159. DisableKeepAlives: true,
  160. },
  161. }
  162. req, err := http.NewRequest("POST", fmt.Sprintf("http://127.0.0.1:%d%s", p.port, path), data)
  163. if err != nil {
  164. return nil, err
  165. }
  166. if p.apiKey != "" {
  167. req.Header.Add("X-API-Key", p.apiKey)
  168. }
  169. if p.csrfToken != "" {
  170. req.Header.Add("X-CSRF-Token", p.csrfToken)
  171. }
  172. req.Header.Add("Content-Type", "application/json")
  173. resp, err := client.Do(req)
  174. if err != nil {
  175. return nil, err
  176. }
  177. return resp, nil
  178. }
  179. func (p *syncthingProcess) peerCompletion() (map[string]int, error) {
  180. resp, err := p.get("/rest/debug/peerCompletion")
  181. if err != nil {
  182. return nil, err
  183. }
  184. defer resp.Body.Close()
  185. comp := map[string]int{}
  186. err = json.NewDecoder(resp.Body).Decode(&comp)
  187. // Remove ourselves from the set. In the remaining map, all peers should
  188. // be att 100% if we're in sync.
  189. for id := range comp {
  190. if id == p.id.String() {
  191. delete(comp, id)
  192. }
  193. }
  194. return comp, err
  195. }
  196. func (p *syncthingProcess) allPeersInSync() error {
  197. comp, err := p.peerCompletion()
  198. if err != nil {
  199. return err
  200. }
  201. for id, val := range comp {
  202. if val != 100 {
  203. return fmt.Errorf("%.7s at %d%%", id, val)
  204. }
  205. }
  206. return nil
  207. }
  208. type model struct {
  209. GlobalBytes int
  210. GlobalDeleted int
  211. GlobalFiles int
  212. InSyncBytes int
  213. InSyncFiles int
  214. Invalid string
  215. LocalBytes int
  216. LocalDeleted int
  217. LocalFiles int
  218. NeedBytes int
  219. NeedFiles int
  220. State string
  221. StateChanged time.Time
  222. Version int
  223. }
  224. func (p *syncthingProcess) model(folder string) (model, error) {
  225. resp, err := p.get("/rest/db/status?folder=" + folder)
  226. if err != nil {
  227. return model{}, err
  228. }
  229. var res model
  230. err = json.NewDecoder(resp.Body).Decode(&res)
  231. if err != nil {
  232. return model{}, err
  233. }
  234. return res, nil
  235. }
  236. type event struct {
  237. ID int
  238. Time time.Time
  239. Type string
  240. Data interface{}
  241. }
  242. func (p *syncthingProcess) events() ([]event, error) {
  243. resp, err := p.get(fmt.Sprintf("/rest/events?since=%d", p.lastEvent))
  244. if err != nil {
  245. return nil, err
  246. }
  247. defer resp.Body.Close()
  248. var evs []event
  249. err = json.NewDecoder(resp.Body).Decode(&evs)
  250. if err != nil {
  251. return nil, err
  252. }
  253. p.lastEvent = evs[len(evs)-1].ID
  254. return evs, err
  255. }
  256. type versionResp struct {
  257. Version string
  258. }
  259. func (p *syncthingProcess) version() (string, error) {
  260. resp, err := p.get("/rest/system/version")
  261. if err != nil {
  262. return "", err
  263. }
  264. defer resp.Body.Close()
  265. var v versionResp
  266. err = json.NewDecoder(resp.Body).Decode(&v)
  267. if err != nil {
  268. return "", err
  269. }
  270. return v.Version, nil
  271. }
  272. func (p *syncthingProcess) rescan(folder string) error {
  273. resp, err := p.post("/rest/db/scan?folder="+folder, nil)
  274. if err != nil {
  275. return err
  276. }
  277. data, _ := ioutil.ReadAll(resp.Body)
  278. resp.Body.Close()
  279. if resp.StatusCode != 200 {
  280. return fmt.Errorf("Rescan %q: status code %d: %s", folder, resp.StatusCode, data)
  281. }
  282. return nil
  283. }
  284. func (p *syncthingProcess) reset(folder string) error {
  285. resp, err := p.post("/rest/system/reset?folder="+folder, nil)
  286. if err != nil {
  287. return err
  288. }
  289. data, _ := ioutil.ReadAll(resp.Body)
  290. resp.Body.Close()
  291. if resp.StatusCode != 200 {
  292. return fmt.Errorf("Reset %q: status code %d: %s", folder, resp.StatusCode, data)
  293. }
  294. return nil
  295. }
  296. func allDevicesInSync(p []syncthingProcess) error {
  297. for _, device := range p {
  298. if err := device.allPeersInSync(); err != nil {
  299. return fmt.Errorf("%.7s: %v", device.id.String(), err)
  300. }
  301. }
  302. return nil
  303. }