config_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. package worker
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "testing"
  7. "time"
  8. units "github.com/docker/go-units"
  9. . "github.com/smartystreets/goconvey/convey"
  10. )
  11. func TestConfig(t *testing.T) {
  12. var cfgBlob = `
  13. [global]
  14. name = "test_worker"
  15. log_dir = "/var/log/tunasync/{{.Name}}"
  16. mirror_dir = "/data/mirrors"
  17. concurrent = 10
  18. interval = 240
  19. retry = 3
  20. timeout = 86400
  21. [manager]
  22. api_base = "https://127.0.0.1:5000"
  23. token = "some_token"
  24. [server]
  25. hostname = "worker1.example.com"
  26. listen_addr = "127.0.0.1"
  27. listen_port = 6000
  28. ssl_cert = "/etc/tunasync.d/worker1.cert"
  29. ssl_key = "/etc/tunasync.d/worker1.key"
  30. [[mirrors]]
  31. name = "AOSP"
  32. provider = "command"
  33. upstream = "https://aosp.google.com/"
  34. interval = 720
  35. retry = 2
  36. timeout = 3600
  37. mirror_dir = "/data/git/AOSP"
  38. exec_on_success = [
  39. "bash -c 'echo ${TUNASYNC_JOB_EXIT_STATUS} > ${TUNASYNC_WORKING_DIR}/exit_status'"
  40. ]
  41. [mirrors.env]
  42. REPO = "/usr/local/bin/aosp-repo"
  43. [[mirrors]]
  44. name = "debian"
  45. provider = "two-stage-rsync"
  46. stage1_profile = "debian"
  47. upstream = "rsync://ftp.debian.org/debian/"
  48. use_ipv6 = true
  49. memory_limit = "256MiB"
  50. [[mirrors]]
  51. name = "fedora"
  52. provider = "rsync"
  53. upstream = "rsync://ftp.fedoraproject.org/fedora/"
  54. use_ipv6 = true
  55. memory_limit = "128M"
  56. exclude_file = "/etc/tunasync.d/fedora-exclude.txt"
  57. exec_on_failure = [
  58. "bash -c 'echo ${TUNASYNC_JOB_EXIT_STATUS} > ${TUNASYNC_WORKING_DIR}/exit_status'"
  59. ]
  60. `
  61. Convey("When giving invalid file", t, func() {
  62. cfg, err := LoadConfig("/path/to/invalid/file")
  63. So(err, ShouldNotBeNil)
  64. So(cfg, ShouldBeNil)
  65. })
  66. Convey("Everything should work on valid config file", t, func() {
  67. tmpfile, err := os.CreateTemp("", "tunasync")
  68. So(err, ShouldEqual, nil)
  69. defer os.Remove(tmpfile.Name())
  70. tmpDir, err := os.MkdirTemp("", "tunasync")
  71. So(err, ShouldBeNil)
  72. defer os.RemoveAll(tmpDir)
  73. incSection := fmt.Sprintf(
  74. "\n[include]\n"+
  75. "include_mirrors = \"%s/*.conf\"",
  76. tmpDir,
  77. )
  78. curCfgBlob := cfgBlob + incSection
  79. err = os.WriteFile(tmpfile.Name(), []byte(curCfgBlob), 0644)
  80. So(err, ShouldEqual, nil)
  81. defer tmpfile.Close()
  82. incBlob1 := `
  83. [[mirrors]]
  84. name = "debian-cd"
  85. provider = "two-stage-rsync"
  86. stage1_profile = "debian"
  87. use_ipv6 = true
  88. [[mirrors]]
  89. name = "debian-security"
  90. provider = "two-stage-rsync"
  91. stage1_profile = "debian"
  92. use_ipv6 = true
  93. `
  94. incBlob2 := `
  95. [[mirrors]]
  96. name = "ubuntu"
  97. provider = "two-stage-rsync"
  98. stage1_profile = "debian"
  99. use_ipv6 = true
  100. `
  101. err = os.WriteFile(filepath.Join(tmpDir, "debian.conf"), []byte(incBlob1), 0644)
  102. So(err, ShouldEqual, nil)
  103. err = os.WriteFile(filepath.Join(tmpDir, "ubuntu.conf"), []byte(incBlob2), 0644)
  104. So(err, ShouldEqual, nil)
  105. cfg, err := LoadConfig(tmpfile.Name())
  106. So(err, ShouldBeNil)
  107. So(cfg.Global.Name, ShouldEqual, "test_worker")
  108. So(cfg.Global.Interval, ShouldEqual, 240)
  109. So(cfg.Global.Retry, ShouldEqual, 3)
  110. So(cfg.Global.Timeout, ShouldEqual, 86400)
  111. So(cfg.Global.MirrorDir, ShouldEqual, "/data/mirrors")
  112. So(cfg.Manager.APIBase, ShouldEqual, "https://127.0.0.1:5000")
  113. So(cfg.Server.Hostname, ShouldEqual, "worker1.example.com")
  114. m := cfg.Mirrors[0]
  115. So(m.Name, ShouldEqual, "AOSP")
  116. So(m.MirrorDir, ShouldEqual, "/data/git/AOSP")
  117. So(m.Provider, ShouldEqual, provCommand)
  118. So(m.Interval, ShouldEqual, 720)
  119. So(m.Retry, ShouldEqual, 2)
  120. So(m.Timeout, ShouldEqual, 3600)
  121. So(m.Env["REPO"], ShouldEqual, "/usr/local/bin/aosp-repo")
  122. m = cfg.Mirrors[1]
  123. So(m.Name, ShouldEqual, "debian")
  124. So(m.MirrorDir, ShouldEqual, "")
  125. So(m.Provider, ShouldEqual, provTwoStageRsync)
  126. So(m.MemoryLimit.Value(), ShouldEqual, 256*units.MiB)
  127. m = cfg.Mirrors[2]
  128. So(m.Name, ShouldEqual, "fedora")
  129. So(m.MirrorDir, ShouldEqual, "")
  130. So(m.Provider, ShouldEqual, provRsync)
  131. So(m.ExcludeFile, ShouldEqual, "/etc/tunasync.d/fedora-exclude.txt")
  132. So(m.MemoryLimit.Value(), ShouldEqual, 128*units.MiB)
  133. m = cfg.Mirrors[3]
  134. So(m.Name, ShouldEqual, "debian-cd")
  135. So(m.MirrorDir, ShouldEqual, "")
  136. So(m.Provider, ShouldEqual, provTwoStageRsync)
  137. So(m.MemoryLimit.Value(), ShouldEqual, 0)
  138. m = cfg.Mirrors[4]
  139. So(m.Name, ShouldEqual, "debian-security")
  140. m = cfg.Mirrors[5]
  141. So(m.Name, ShouldEqual, "ubuntu")
  142. So(len(cfg.Mirrors), ShouldEqual, 6)
  143. })
  144. Convey("Everything should work on nested config file", t, func() {
  145. tmpfile, err := os.CreateTemp("", "tunasync")
  146. So(err, ShouldEqual, nil)
  147. defer os.Remove(tmpfile.Name())
  148. tmpDir, err := os.MkdirTemp("", "tunasync")
  149. So(err, ShouldBeNil)
  150. defer os.RemoveAll(tmpDir)
  151. incSection := fmt.Sprintf(
  152. "\n[include]\n"+
  153. "include_mirrors = \"%s/*.conf\"",
  154. tmpDir,
  155. )
  156. curCfgBlob := cfgBlob + incSection
  157. err = os.WriteFile(tmpfile.Name(), []byte(curCfgBlob), 0644)
  158. So(err, ShouldEqual, nil)
  159. defer tmpfile.Close()
  160. incBlob1 := `
  161. [[mirrors]]
  162. name = "ipv6s"
  163. use_ipv6 = true
  164. [[mirrors.mirrors]]
  165. name = "debians"
  166. mirror_subdir = "debian"
  167. provider = "two-stage-rsync"
  168. stage1_profile = "debian"
  169. [[mirrors.mirrors.mirrors]]
  170. name = "debian-security"
  171. upstream = "rsync://test.host/debian-security/"
  172. [[mirrors.mirrors.mirrors]]
  173. name = "ubuntu"
  174. stage1_profile = "ubuntu"
  175. upstream = "rsync://test.host2/ubuntu/"
  176. [[mirrors.mirrors]]
  177. name = "debian-cd"
  178. provider = "rsync"
  179. upstream = "rsync://test.host3/debian-cd/"
  180. `
  181. err = os.WriteFile(filepath.Join(tmpDir, "nest.conf"), []byte(incBlob1), 0644)
  182. So(err, ShouldEqual, nil)
  183. cfg, err := LoadConfig(tmpfile.Name())
  184. So(err, ShouldBeNil)
  185. So(cfg.Global.Name, ShouldEqual, "test_worker")
  186. So(cfg.Global.Interval, ShouldEqual, 240)
  187. So(cfg.Global.Retry, ShouldEqual, 3)
  188. So(cfg.Global.MirrorDir, ShouldEqual, "/data/mirrors")
  189. So(cfg.Manager.APIBase, ShouldEqual, "https://127.0.0.1:5000")
  190. So(cfg.Server.Hostname, ShouldEqual, "worker1.example.com")
  191. m := cfg.Mirrors[0]
  192. So(m.Name, ShouldEqual, "AOSP")
  193. So(m.MirrorDir, ShouldEqual, "/data/git/AOSP")
  194. So(m.Provider, ShouldEqual, provCommand)
  195. So(m.Interval, ShouldEqual, 720)
  196. So(m.Retry, ShouldEqual, 2)
  197. So(m.Env["REPO"], ShouldEqual, "/usr/local/bin/aosp-repo")
  198. m = cfg.Mirrors[1]
  199. So(m.Name, ShouldEqual, "debian")
  200. So(m.MirrorDir, ShouldEqual, "")
  201. So(m.Provider, ShouldEqual, provTwoStageRsync)
  202. m = cfg.Mirrors[2]
  203. So(m.Name, ShouldEqual, "fedora")
  204. So(m.MirrorDir, ShouldEqual, "")
  205. So(m.Provider, ShouldEqual, provRsync)
  206. So(m.ExcludeFile, ShouldEqual, "/etc/tunasync.d/fedora-exclude.txt")
  207. m = cfg.Mirrors[3]
  208. So(m.Name, ShouldEqual, "debian-security")
  209. So(m.MirrorDir, ShouldEqual, "")
  210. So(m.Provider, ShouldEqual, provTwoStageRsync)
  211. So(m.UseIPv6, ShouldEqual, true)
  212. So(m.Stage1Profile, ShouldEqual, "debian")
  213. m = cfg.Mirrors[4]
  214. So(m.Name, ShouldEqual, "ubuntu")
  215. So(m.MirrorDir, ShouldEqual, "")
  216. So(m.Provider, ShouldEqual, provTwoStageRsync)
  217. So(m.UseIPv6, ShouldEqual, true)
  218. So(m.Stage1Profile, ShouldEqual, "ubuntu")
  219. m = cfg.Mirrors[5]
  220. So(m.Name, ShouldEqual, "debian-cd")
  221. So(m.UseIPv6, ShouldEqual, true)
  222. So(m.Provider, ShouldEqual, provRsync)
  223. So(len(cfg.Mirrors), ShouldEqual, 6)
  224. })
  225. Convey("Providers can be inited from a valid config file", t, func() {
  226. tmpfile, err := os.CreateTemp("", "tunasync")
  227. So(err, ShouldEqual, nil)
  228. defer os.Remove(tmpfile.Name())
  229. err = os.WriteFile(tmpfile.Name(), []byte(cfgBlob), 0644)
  230. So(err, ShouldEqual, nil)
  231. defer tmpfile.Close()
  232. cfg, err := LoadConfig(tmpfile.Name())
  233. So(err, ShouldBeNil)
  234. providers := map[string]mirrorProvider{}
  235. for _, m := range cfg.Mirrors {
  236. p := newMirrorProvider(m, cfg)
  237. providers[p.Name()] = p
  238. }
  239. p := providers["AOSP"]
  240. So(p.Name(), ShouldEqual, "AOSP")
  241. So(p.LogDir(), ShouldEqual, "/var/log/tunasync/AOSP")
  242. So(p.LogFile(), ShouldEqual, "/var/log/tunasync/AOSP/latest.log")
  243. _, ok := p.(*cmdProvider)
  244. So(ok, ShouldBeTrue)
  245. for _, hook := range p.Hooks() {
  246. switch h := hook.(type) {
  247. case *execPostHook:
  248. So(h.command, ShouldResemble, []string{"bash", "-c", `echo ${TUNASYNC_JOB_EXIT_STATUS} > ${TUNASYNC_WORKING_DIR}/exit_status`})
  249. }
  250. }
  251. p = providers["debian"]
  252. So(p.Name(), ShouldEqual, "debian")
  253. So(p.LogDir(), ShouldEqual, "/var/log/tunasync/debian")
  254. So(p.LogFile(), ShouldEqual, "/var/log/tunasync/debian/latest.log")
  255. r2p, ok := p.(*twoStageRsyncProvider)
  256. So(ok, ShouldBeTrue)
  257. So(r2p.stage1Profile, ShouldEqual, "debian")
  258. So(r2p.WorkingDir(), ShouldEqual, "/data/mirrors/debian")
  259. p = providers["fedora"]
  260. So(p.Name(), ShouldEqual, "fedora")
  261. So(p.LogDir(), ShouldEqual, "/var/log/tunasync/fedora")
  262. So(p.LogFile(), ShouldEqual, "/var/log/tunasync/fedora/latest.log")
  263. rp, ok := p.(*rsyncProvider)
  264. So(ok, ShouldBeTrue)
  265. So(rp.WorkingDir(), ShouldEqual, "/data/mirrors/fedora")
  266. So(rp.excludeFile, ShouldEqual, "/etc/tunasync.d/fedora-exclude.txt")
  267. })
  268. Convey("MirrorSubdir should work", t, func() {
  269. tmpfile, err := os.CreateTemp("", "tunasync")
  270. So(err, ShouldEqual, nil)
  271. defer os.Remove(tmpfile.Name())
  272. cfgBlob1 := `
  273. [global]
  274. name = "test_worker"
  275. log_dir = "/var/log/tunasync/{{.Name}}"
  276. mirror_dir = "/data/mirrors"
  277. concurrent = 10
  278. interval = 240
  279. timeout = 86400
  280. retry = 3
  281. [manager]
  282. api_base = "https://127.0.0.1:5000"
  283. token = "some_token"
  284. [server]
  285. hostname = "worker1.example.com"
  286. listen_addr = "127.0.0.1"
  287. listen_port = 6000
  288. ssl_cert = "/etc/tunasync.d/worker1.cert"
  289. ssl_key = "/etc/tunasync.d/worker1.key"
  290. [[mirrors]]
  291. name = "ipv6s"
  292. use_ipv6 = true
  293. [[mirrors.mirrors]]
  294. name = "debians"
  295. mirror_subdir = "debian"
  296. provider = "two-stage-rsync"
  297. stage1_profile = "debian"
  298. [[mirrors.mirrors.mirrors]]
  299. name = "debian-security"
  300. upstream = "rsync://test.host/debian-security/"
  301. [[mirrors.mirrors.mirrors]]
  302. name = "ubuntu"
  303. stage1_profile = "ubuntu"
  304. upstream = "rsync://test.host2/ubuntu/"
  305. [[mirrors.mirrors]]
  306. name = "debian-cd"
  307. provider = "rsync"
  308. upstream = "rsync://test.host3/debian-cd/"
  309. `
  310. err = os.WriteFile(tmpfile.Name(), []byte(cfgBlob1), 0644)
  311. So(err, ShouldEqual, nil)
  312. defer tmpfile.Close()
  313. cfg, err := LoadConfig(tmpfile.Name())
  314. So(err, ShouldBeNil)
  315. providers := map[string]mirrorProvider{}
  316. for _, m := range cfg.Mirrors {
  317. p := newMirrorProvider(m, cfg)
  318. providers[p.Name()] = p
  319. }
  320. p := providers["debian-security"]
  321. So(p.Name(), ShouldEqual, "debian-security")
  322. So(p.LogDir(), ShouldEqual, "/var/log/tunasync/debian-security")
  323. So(p.LogFile(), ShouldEqual, "/var/log/tunasync/debian-security/latest.log")
  324. r2p, ok := p.(*twoStageRsyncProvider)
  325. So(ok, ShouldBeTrue)
  326. So(r2p.stage1Profile, ShouldEqual, "debian")
  327. So(r2p.WorkingDir(), ShouldEqual, "/data/mirrors/debian/debian-security")
  328. p = providers["ubuntu"]
  329. So(p.Name(), ShouldEqual, "ubuntu")
  330. So(p.LogDir(), ShouldEqual, "/var/log/tunasync/ubuntu")
  331. So(p.LogFile(), ShouldEqual, "/var/log/tunasync/ubuntu/latest.log")
  332. r2p, ok = p.(*twoStageRsyncProvider)
  333. So(ok, ShouldBeTrue)
  334. So(r2p.stage1Profile, ShouldEqual, "ubuntu")
  335. So(r2p.WorkingDir(), ShouldEqual, "/data/mirrors/debian/ubuntu")
  336. p = providers["debian-cd"]
  337. So(p.Name(), ShouldEqual, "debian-cd")
  338. So(p.LogDir(), ShouldEqual, "/var/log/tunasync/debian-cd")
  339. So(p.LogFile(), ShouldEqual, "/var/log/tunasync/debian-cd/latest.log")
  340. rp, ok := p.(*rsyncProvider)
  341. So(ok, ShouldBeTrue)
  342. So(rp.WorkingDir(), ShouldEqual, "/data/mirrors/debian-cd")
  343. So(p.Timeout(), ShouldEqual, 86400*time.Second)
  344. })
  345. Convey("rsync_override_only should work", t, func() {
  346. tmpfile, err := os.CreateTemp("", "tunasync")
  347. So(err, ShouldEqual, nil)
  348. defer os.Remove(tmpfile.Name())
  349. cfgBlob1 := `
  350. [global]
  351. name = "test_worker"
  352. log_dir = "/var/log/tunasync/{{.Name}}"
  353. mirror_dir = "/data/mirrors"
  354. concurrent = 10
  355. interval = 240
  356. retry = 3
  357. timeout = 86400
  358. [manager]
  359. api_base = "https://127.0.0.1:5000"
  360. token = "some_token"
  361. [server]
  362. hostname = "worker1.example.com"
  363. listen_addr = "127.0.0.1"
  364. listen_port = 6000
  365. ssl_cert = "/etc/tunasync.d/worker1.cert"
  366. ssl_key = "/etc/tunasync.d/worker1.key"
  367. [[mirrors]]
  368. name = "foo"
  369. provider = "rsync"
  370. upstream = "rsync://foo.bar/"
  371. interval = 720
  372. retry = 2
  373. timeout = 3600
  374. mirror_dir = "/data/foo"
  375. rsync_override = ["--bar", "baz"]
  376. rsync_override_only = true
  377. `
  378. err = os.WriteFile(tmpfile.Name(), []byte(cfgBlob1), 0644)
  379. So(err, ShouldEqual, nil)
  380. defer tmpfile.Close()
  381. cfg, err := LoadConfig(tmpfile.Name())
  382. So(err, ShouldBeNil)
  383. providers := map[string]mirrorProvider{}
  384. for _, m := range cfg.Mirrors {
  385. p := newMirrorProvider(m, cfg)
  386. providers[p.Name()] = p
  387. }
  388. p, ok := providers["foo"].(*rsyncProvider)
  389. So(ok, ShouldBeTrue)
  390. So(p.options, ShouldResemble, []string{"--bar", "baz"})
  391. })
  392. Convey("rsync global options should work", t, func() {
  393. tmpfile, err := os.CreateTemp("", "tunasync")
  394. So(err, ShouldEqual, nil)
  395. defer os.Remove(tmpfile.Name())
  396. cfgBlob1 := `
  397. [global]
  398. name = "test_worker"
  399. log_dir = "/var/log/tunasync/{{.Name}}"
  400. mirror_dir = "/data/mirrors"
  401. concurrent = 10
  402. interval = 240
  403. retry = 3
  404. timeout = 86400
  405. rsync_options = ["--global"]
  406. [manager]
  407. api_base = "https://127.0.0.1:5000"
  408. token = "some_token"
  409. [server]
  410. hostname = "worker1.example.com"
  411. listen_addr = "127.0.0.1"
  412. listen_port = 6000
  413. ssl_cert = "/etc/tunasync.d/worker1.cert"
  414. ssl_key = "/etc/tunasync.d/worker1.key"
  415. [[mirrors]]
  416. name = "foo"
  417. provider = "rsync"
  418. upstream = "rsync://foo.bar/"
  419. interval = 720
  420. retry = 2
  421. timeout = 3600
  422. mirror_dir = "/data/foo"
  423. rsync_override = ["--override"]
  424. rsync_options = ["--local"]
  425. `
  426. err = os.WriteFile(tmpfile.Name(), []byte(cfgBlob1), 0644)
  427. So(err, ShouldEqual, nil)
  428. defer tmpfile.Close()
  429. cfg, err := LoadConfig(tmpfile.Name())
  430. So(err, ShouldBeNil)
  431. providers := map[string]mirrorProvider{}
  432. for _, m := range cfg.Mirrors {
  433. p := newMirrorProvider(m, cfg)
  434. providers[p.Name()] = p
  435. }
  436. p, ok := providers["foo"].(*rsyncProvider)
  437. So(ok, ShouldBeTrue)
  438. So(p.options, ShouldResemble, []string{
  439. "--override", // from mirror.rsync_override
  440. "--timeout=120", // generated by newRsyncProvider
  441. "--global", // from global.rsync_options
  442. "--local", // from mirror.rsync_options
  443. })
  444. })
  445. Convey("success_exit_codes should work globally and per mirror", t, func() {
  446. tmpfile, err := os.CreateTemp("", "tunasync")
  447. So(err, ShouldEqual, nil)
  448. defer os.Remove(tmpfile.Name())
  449. cfgBlob1 := `
  450. [global]
  451. name = "test_worker"
  452. log_dir = "/var/log/tunasync/{{.Name}}"
  453. mirror_dir = "/data/mirrors"
  454. concurrent = 10
  455. interval = 240
  456. retry = 3
  457. timeout = 86400
  458. dangerous_global_success_exit_codes = [10, 20]
  459. [manager]
  460. api_base = "https://127.0.0.1:5000"
  461. token = "some_token"
  462. [server]
  463. hostname = "worker1.example.com"
  464. listen_addr = "127.0.0.1"
  465. listen_port = 6000
  466. ssl_cert = "/etc/tunasync.d/worker1.cert"
  467. ssl_key = "/etc/tunasync.d/worker1.key"
  468. [[mirrors]]
  469. name = "foo"
  470. provider = "rsync"
  471. upstream = "rsync://foo.bar/"
  472. interval = 720
  473. retry = 2
  474. timeout = 3600
  475. mirror_dir = "/data/foo"
  476. success_exit_codes = [30, 40]
  477. `
  478. err = os.WriteFile(tmpfile.Name(), []byte(cfgBlob1), 0644)
  479. So(err, ShouldEqual, nil)
  480. defer tmpfile.Close()
  481. cfg, err := LoadConfig(tmpfile.Name())
  482. So(err, ShouldBeNil)
  483. providers := map[string]mirrorProvider{}
  484. for _, m := range cfg.Mirrors {
  485. p := newMirrorProvider(m, cfg)
  486. providers[p.Name()] = p
  487. }
  488. p, ok := providers["foo"].(*rsyncProvider)
  489. So(ok, ShouldBeTrue)
  490. So(p.successExitCodes, ShouldResemble, []int{10, 20, 30, 40})
  491. })
  492. }