utils_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. package integration
  2. import (
  3. "io"
  4. "io/ioutil"
  5. "os"
  6. "os/exec"
  7. "path/filepath"
  8. "runtime"
  9. "strconv"
  10. "strings"
  11. "testing"
  12. "time"
  13. )
  14. func TestIsKilledFalseWithNonKilledProcess(t *testing.T) {
  15. var lsCmd *exec.Cmd
  16. if runtime.GOOS != "windows" {
  17. lsCmd = exec.Command("ls")
  18. } else {
  19. lsCmd = exec.Command("cmd", "/c", "dir")
  20. }
  21. err := lsCmd.Run()
  22. if IsKilled(err) {
  23. t.Fatalf("Expected the ls command to not be killed, was.")
  24. }
  25. }
  26. func TestIsKilledTrueWithKilledProcess(t *testing.T) {
  27. var longCmd *exec.Cmd
  28. if runtime.GOOS != "windows" {
  29. longCmd = exec.Command("top")
  30. } else {
  31. longCmd = exec.Command("powershell", "while ($true) { sleep 1 }")
  32. }
  33. // Start a command
  34. err := longCmd.Start()
  35. if err != nil {
  36. t.Fatal(err)
  37. }
  38. // Capture the error when *dying*
  39. done := make(chan error, 1)
  40. go func() {
  41. done <- longCmd.Wait()
  42. }()
  43. // Then kill it
  44. longCmd.Process.Kill()
  45. // Get the error
  46. err = <-done
  47. if !IsKilled(err) {
  48. t.Fatalf("Expected the command to be killed, was not.")
  49. }
  50. }
  51. func TestRunCommandWithOutput(t *testing.T) {
  52. var (
  53. echoHelloWorldCmd *exec.Cmd
  54. expected string
  55. )
  56. if runtime.GOOS != "windows" {
  57. echoHelloWorldCmd = exec.Command("echo", "hello", "world")
  58. expected = "hello world\n"
  59. } else {
  60. echoHelloWorldCmd = exec.Command("cmd", "/s", "/c", "echo", "hello", "world")
  61. expected = "hello world\r\n"
  62. }
  63. out, exitCode, err := RunCommandWithOutput(echoHelloWorldCmd)
  64. if out != expected || exitCode != 0 || err != nil {
  65. t.Fatalf("Expected command to output %s, got %s, %v with exitCode %v", expected, out, err, exitCode)
  66. }
  67. }
  68. func TestRunCommandWithOutputError(t *testing.T) {
  69. var (
  70. p string
  71. wrongCmd *exec.Cmd
  72. expected string
  73. expectedExitCode int
  74. )
  75. if runtime.GOOS != "windows" {
  76. p = "$PATH"
  77. wrongCmd = exec.Command("ls", "-z")
  78. expected = `ls: invalid option -- 'z'
  79. Try 'ls --help' for more information.
  80. `
  81. expectedExitCode = 2
  82. } else {
  83. p = "%PATH%"
  84. wrongCmd = exec.Command("cmd", "/s", "/c", "dir", "/Z")
  85. expected = "Invalid switch - " + strconv.Quote("Z") + ".\r\n"
  86. expectedExitCode = 1
  87. }
  88. cmd := exec.Command("doesnotexists")
  89. out, exitCode, err := RunCommandWithOutput(cmd)
  90. expectedError := `exec: "doesnotexists": executable file not found in ` + p
  91. if out != "" || exitCode != 127 || err == nil || err.Error() != expectedError {
  92. t.Fatalf("Expected command to output %s, got %s, %v with exitCode %v", expectedError, out, err, exitCode)
  93. }
  94. out, exitCode, err = RunCommandWithOutput(wrongCmd)
  95. if out != expected || exitCode != expectedExitCode || err == nil || !strings.Contains(err.Error(), "exit status "+strconv.Itoa(expectedExitCode)) {
  96. t.Fatalf("Expected command to output %s, got out:xxx%sxxx, err:%v with exitCode %v", expected, out, err, exitCode)
  97. }
  98. }
  99. func TestRunCommandWithStdoutStderr(t *testing.T) {
  100. echoHelloWorldCmd := exec.Command("echo", "hello", "world")
  101. stdout, stderr, exitCode, err := RunCommandWithStdoutStderr(echoHelloWorldCmd)
  102. expected := "hello world\n"
  103. if stdout != expected || stderr != "" || exitCode != 0 || err != nil {
  104. t.Fatalf("Expected command to output %s, got stdout:%s, stderr:%s, err:%v with exitCode %v", expected, stdout, stderr, err, exitCode)
  105. }
  106. }
  107. func TestRunCommandWithStdoutStderrError(t *testing.T) {
  108. p := "$PATH"
  109. if runtime.GOOS == "windows" {
  110. p = "%PATH%"
  111. }
  112. cmd := exec.Command("doesnotexists")
  113. stdout, stderr, exitCode, err := RunCommandWithStdoutStderr(cmd)
  114. expectedError := `exec: "doesnotexists": executable file not found in ` + p
  115. if stdout != "" || stderr != "" || exitCode != 127 || err == nil || err.Error() != expectedError {
  116. t.Fatalf("Expected command to output out:%s, stderr:%s, got stdout:%s, stderr:%s, err:%v with exitCode %v", "", "", stdout, stderr, err, exitCode)
  117. }
  118. wrongLsCmd := exec.Command("ls", "-z")
  119. expected := `ls: invalid option -- 'z'
  120. Try 'ls --help' for more information.
  121. `
  122. stdout, stderr, exitCode, err = RunCommandWithStdoutStderr(wrongLsCmd)
  123. if stdout != "" && stderr != expected || exitCode != 2 || err == nil || err.Error() != "exit status 2" {
  124. t.Fatalf("Expected command to output out:%s, stderr:%s, got stdout:%s, stderr:%s, err:%v with exitCode %v", "", expectedError, stdout, stderr, err, exitCode)
  125. }
  126. }
  127. func TestRunCommandWithOutputForDurationFinished(t *testing.T) {
  128. // TODO Windows: Port this test
  129. if runtime.GOOS == "windows" {
  130. t.Skip("Needs porting to Windows")
  131. }
  132. cmd := exec.Command("ls")
  133. out, exitCode, timedOut, err := RunCommandWithOutputForDuration(cmd, 50*time.Millisecond)
  134. if out == "" || exitCode != 0 || timedOut || err != nil {
  135. t.Fatalf("Expected the command to run for less 50 milliseconds and thus not time out, but did not : out:[%s], exitCode:[%d], timedOut:[%v], err:[%v]", out, exitCode, timedOut, err)
  136. }
  137. }
  138. func TestRunCommandWithOutputForDurationKilled(t *testing.T) {
  139. // TODO Windows: Port this test
  140. if runtime.GOOS == "windows" {
  141. t.Skip("Needs porting to Windows")
  142. }
  143. cmd := exec.Command("sh", "-c", "while true ; do echo 1 ; sleep .1 ; done")
  144. out, exitCode, timedOut, err := RunCommandWithOutputForDuration(cmd, 500*time.Millisecond)
  145. ones := strings.Split(out, "\n")
  146. if len(ones) != 6 || exitCode != 0 || !timedOut || err != nil {
  147. t.Fatalf("Expected the command to run for 500 milliseconds (and thus print six lines (five with 1, one empty) and time out, but did not : out:[%s], exitCode:%d, timedOut:%v, err:%v", out, exitCode, timedOut, err)
  148. }
  149. }
  150. func TestRunCommandWithOutputForDurationErrors(t *testing.T) {
  151. cmd := exec.Command("ls")
  152. cmd.Stdout = os.Stdout
  153. if _, _, _, err := RunCommandWithOutputForDuration(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stdout already set" {
  154. t.Fatalf("Expected an error as cmd.Stdout was already set, did not (err:%s).", err)
  155. }
  156. cmd = exec.Command("ls")
  157. cmd.Stderr = os.Stderr
  158. if _, _, _, err := RunCommandWithOutputForDuration(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stderr already set" {
  159. t.Fatalf("Expected an error as cmd.Stderr was already set, did not (err:%s).", err)
  160. }
  161. }
  162. func TestRunCommandWithOutputAndTimeoutFinished(t *testing.T) {
  163. // TODO Windows: Port this test
  164. if runtime.GOOS == "windows" {
  165. t.Skip("Needs porting to Windows")
  166. }
  167. cmd := exec.Command("ls")
  168. out, exitCode, err := RunCommandWithOutputAndTimeout(cmd, 50*time.Millisecond)
  169. if out == "" || exitCode != 0 || err != nil {
  170. t.Fatalf("Expected the command to run for less 50 milliseconds and thus not time out, but did not : out:[%s], exitCode:[%d], err:[%v]", out, exitCode, err)
  171. }
  172. }
  173. func TestRunCommandWithOutputAndTimeoutKilled(t *testing.T) {
  174. // TODO Windows: Port this test
  175. if runtime.GOOS == "windows" {
  176. t.Skip("Needs porting to Windows")
  177. }
  178. cmd := exec.Command("sh", "-c", "while true ; do echo 1 ; sleep .1 ; done")
  179. out, exitCode, err := RunCommandWithOutputAndTimeout(cmd, 500*time.Millisecond)
  180. ones := strings.Split(out, "\n")
  181. if len(ones) != 6 || exitCode != 0 || err == nil || err.Error() != "command timed out" {
  182. t.Fatalf("Expected the command to run for 500 milliseconds (and thus print six lines (five with 1, one empty) and time out with an error 'command timed out', but did not : out:[%s], exitCode:%d, err:%v", out, exitCode, err)
  183. }
  184. }
  185. func TestRunCommandWithOutputAndTimeoutErrors(t *testing.T) {
  186. cmd := exec.Command("ls")
  187. cmd.Stdout = os.Stdout
  188. if _, _, err := RunCommandWithOutputAndTimeout(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stdout already set" {
  189. t.Fatalf("Expected an error as cmd.Stdout was already set, did not (err:%s).", err)
  190. }
  191. cmd = exec.Command("ls")
  192. cmd.Stderr = os.Stderr
  193. if _, _, err := RunCommandWithOutputAndTimeout(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stderr already set" {
  194. t.Fatalf("Expected an error as cmd.Stderr was already set, did not (err:%s).", err)
  195. }
  196. }
  197. func TestRunCommand(t *testing.T) {
  198. // TODO Windows: Port this test
  199. if runtime.GOOS == "windows" {
  200. t.Skip("Needs porting to Windows")
  201. }
  202. p := "$PATH"
  203. if runtime.GOOS == "windows" {
  204. p = "%PATH%"
  205. }
  206. lsCmd := exec.Command("ls")
  207. exitCode, err := RunCommand(lsCmd)
  208. if exitCode != 0 || err != nil {
  209. t.Fatalf("Expected runCommand to run the command successfully, got: exitCode:%d, err:%v", exitCode, err)
  210. }
  211. var expectedError string
  212. exitCode, err = RunCommand(exec.Command("doesnotexists"))
  213. expectedError = `exec: "doesnotexists": executable file not found in ` + p
  214. if exitCode != 127 || err == nil || err.Error() != expectedError {
  215. t.Fatalf("Expected runCommand to run the command successfully, got: exitCode:%d, err:%v", exitCode, err)
  216. }
  217. wrongLsCmd := exec.Command("ls", "-z")
  218. expected := 2
  219. expectedError = `exit status 2`
  220. exitCode, err = RunCommand(wrongLsCmd)
  221. if exitCode != expected || err == nil || err.Error() != expectedError {
  222. t.Fatalf("Expected runCommand to run the command successfully, got: exitCode:%d, err:%v", exitCode, err)
  223. }
  224. }
  225. func TestRunCommandPipelineWithOutputWithNotEnoughCmds(t *testing.T) {
  226. _, _, err := RunCommandPipelineWithOutput(exec.Command("ls"))
  227. expectedError := "pipeline does not have multiple cmds"
  228. if err == nil || err.Error() != expectedError {
  229. t.Fatalf("Expected an error with %s, got err:%s", expectedError, err)
  230. }
  231. }
  232. func TestRunCommandPipelineWithOutputErrors(t *testing.T) {
  233. p := "$PATH"
  234. if runtime.GOOS == "windows" {
  235. p = "%PATH%"
  236. }
  237. cmd1 := exec.Command("ls")
  238. cmd1.Stdout = os.Stdout
  239. cmd2 := exec.Command("anything really")
  240. _, _, err := RunCommandPipelineWithOutput(cmd1, cmd2)
  241. if err == nil || err.Error() != "cannot set stdout pipe for anything really: exec: Stdout already set" {
  242. t.Fatalf("Expected an error, got %v", err)
  243. }
  244. cmdWithError := exec.Command("doesnotexists")
  245. cmdCat := exec.Command("cat")
  246. _, _, err = RunCommandPipelineWithOutput(cmdWithError, cmdCat)
  247. if err == nil || err.Error() != `starting doesnotexists failed with error: exec: "doesnotexists": executable file not found in `+p {
  248. t.Fatalf("Expected an error, got %v", err)
  249. }
  250. }
  251. func TestRunCommandPipelineWithOutput(t *testing.T) {
  252. cmds := []*exec.Cmd{
  253. // Print 2 characters
  254. exec.Command("echo", "-n", "11"),
  255. // Count the number or char from stdin (previous command)
  256. exec.Command("wc", "-m"),
  257. }
  258. out, exitCode, err := RunCommandPipelineWithOutput(cmds...)
  259. expectedOutput := "2\n"
  260. if out != expectedOutput || exitCode != 0 || err != nil {
  261. t.Fatalf("Expected %s for commands %v, got out:%s, exitCode:%d, err:%v", expectedOutput, cmds, out, exitCode, err)
  262. }
  263. }
  264. func TestConvertSliceOfStringsToMap(t *testing.T) {
  265. input := []string{"a", "b"}
  266. actual := ConvertSliceOfStringsToMap(input)
  267. for _, key := range input {
  268. if _, ok := actual[key]; !ok {
  269. t.Fatalf("Expected output to contains key %s, did not: %v", key, actual)
  270. }
  271. }
  272. }
  273. func TestCompareDirectoryEntries(t *testing.T) {
  274. tmpFolder, err := ioutil.TempDir("", "integration-cli-utils-compare-directories")
  275. if err != nil {
  276. t.Fatal(err)
  277. }
  278. defer os.RemoveAll(tmpFolder)
  279. file1 := filepath.Join(tmpFolder, "file1")
  280. file2 := filepath.Join(tmpFolder, "file2")
  281. os.Create(file1)
  282. os.Create(file2)
  283. fi1, err := os.Stat(file1)
  284. if err != nil {
  285. t.Fatal(err)
  286. }
  287. fi1bis, err := os.Stat(file1)
  288. if err != nil {
  289. t.Fatal(err)
  290. }
  291. fi2, err := os.Stat(file2)
  292. if err != nil {
  293. t.Fatal(err)
  294. }
  295. cases := []struct {
  296. e1 []os.FileInfo
  297. e2 []os.FileInfo
  298. shouldError bool
  299. }{
  300. // Empty directories
  301. {
  302. []os.FileInfo{},
  303. []os.FileInfo{},
  304. false,
  305. },
  306. // Same FileInfos
  307. {
  308. []os.FileInfo{fi1},
  309. []os.FileInfo{fi1},
  310. false,
  311. },
  312. // Different FileInfos but same names
  313. {
  314. []os.FileInfo{fi1},
  315. []os.FileInfo{fi1bis},
  316. false,
  317. },
  318. // Different FileInfos, different names
  319. {
  320. []os.FileInfo{fi1},
  321. []os.FileInfo{fi2},
  322. true,
  323. },
  324. }
  325. for _, elt := range cases {
  326. err := CompareDirectoryEntries(elt.e1, elt.e2)
  327. if elt.shouldError && err == nil {
  328. t.Fatalf("Should have return an error, did not with %v and %v", elt.e1, elt.e2)
  329. }
  330. if !elt.shouldError && err != nil {
  331. t.Fatalf("Should have not returned an error, but did : %v with %v and %v", err, elt.e1, elt.e2)
  332. }
  333. }
  334. }
  335. // FIXME make an "unhappy path" test for ListTar without "panicking" :-)
  336. func TestListTar(t *testing.T) {
  337. // TODO Windows: Figure out why this fails. Should be portable.
  338. if runtime.GOOS == "windows" {
  339. t.Skip("Failing on Windows - needs further investigation")
  340. }
  341. tmpFolder, err := ioutil.TempDir("", "integration-cli-utils-list-tar")
  342. if err != nil {
  343. t.Fatal(err)
  344. }
  345. defer os.RemoveAll(tmpFolder)
  346. // Let's create a Tar file
  347. srcFile := filepath.Join(tmpFolder, "src")
  348. tarFile := filepath.Join(tmpFolder, "src.tar")
  349. os.Create(srcFile)
  350. cmd := exec.Command("sh", "-c", "tar cf "+tarFile+" "+srcFile)
  351. _, err = cmd.CombinedOutput()
  352. if err != nil {
  353. t.Fatal(err)
  354. }
  355. reader, err := os.Open(tarFile)
  356. if err != nil {
  357. t.Fatal(err)
  358. }
  359. defer reader.Close()
  360. entries, err := ListTar(reader)
  361. if err != nil {
  362. t.Fatal(err)
  363. }
  364. if len(entries) != 1 && entries[0] != "src" {
  365. t.Fatalf("Expected a tar file with 1 entry (%s), got %v", srcFile, entries)
  366. }
  367. }
  368. func TestRandomTmpDirPath(t *testing.T) {
  369. path := RandomTmpDirPath("something", runtime.GOOS)
  370. prefix := "/tmp/something"
  371. if runtime.GOOS == "windows" {
  372. prefix = os.Getenv("TEMP") + `\something`
  373. }
  374. expectedSize := len(prefix) + 11
  375. if !strings.HasPrefix(path, prefix) {
  376. t.Fatalf("Expected generated path to have '%s' as prefix, got %s'", prefix, path)
  377. }
  378. if len(path) != expectedSize {
  379. t.Fatalf("Expected generated path to be %d, got %d", expectedSize, len(path))
  380. }
  381. }
  382. func TestConsumeWithSpeed(t *testing.T) {
  383. reader := strings.NewReader("1234567890")
  384. chunksize := 2
  385. bytes1, err := ConsumeWithSpeed(reader, chunksize, 1*time.Second, nil)
  386. if err != nil {
  387. t.Fatal(err)
  388. }
  389. if bytes1 != 10 {
  390. t.Fatalf("Expected to have read 10 bytes, got %d", bytes1)
  391. }
  392. }
  393. func TestConsumeWithSpeedWithStop(t *testing.T) {
  394. reader := strings.NewReader("1234567890")
  395. chunksize := 2
  396. stopIt := make(chan bool)
  397. go func() {
  398. time.Sleep(1 * time.Millisecond)
  399. stopIt <- true
  400. }()
  401. bytes1, err := ConsumeWithSpeed(reader, chunksize, 20*time.Millisecond, stopIt)
  402. if err != nil {
  403. t.Fatal(err)
  404. }
  405. if bytes1 != 2 {
  406. t.Fatalf("Expected to have read 2 bytes, got %d", bytes1)
  407. }
  408. }
  409. func TestParseCgroupPathsEmpty(t *testing.T) {
  410. cgroupMap := ParseCgroupPaths("")
  411. if len(cgroupMap) != 0 {
  412. t.Fatalf("Expected an empty map, got %v", cgroupMap)
  413. }
  414. cgroupMap = ParseCgroupPaths("\n")
  415. if len(cgroupMap) != 0 {
  416. t.Fatalf("Expected an empty map, got %v", cgroupMap)
  417. }
  418. cgroupMap = ParseCgroupPaths("something:else\nagain:here")
  419. if len(cgroupMap) != 0 {
  420. t.Fatalf("Expected an empty map, got %v", cgroupMap)
  421. }
  422. }
  423. func TestParseCgroupPaths(t *testing.T) {
  424. cgroupMap := ParseCgroupPaths("2:memory:/a\n1:cpuset:/b")
  425. if len(cgroupMap) != 2 {
  426. t.Fatalf("Expected a map with 2 entries, got %v", cgroupMap)
  427. }
  428. if value, ok := cgroupMap["memory"]; !ok || value != "/a" {
  429. t.Fatalf("Expected cgroupMap to contains an entry for 'memory' with value '/a', got %v", cgroupMap)
  430. }
  431. if value, ok := cgroupMap["cpuset"]; !ok || value != "/b" {
  432. t.Fatalf("Expected cgroupMap to contains an entry for 'cpuset' with value '/b', got %v", cgroupMap)
  433. }
  434. }
  435. func TestChannelBufferTimeout(t *testing.T) {
  436. expected := "11"
  437. buf := &ChannelBuffer{make(chan []byte, 1)}
  438. defer buf.Close()
  439. done := make(chan struct{}, 1)
  440. go func() {
  441. time.Sleep(100 * time.Millisecond)
  442. io.Copy(buf, strings.NewReader(expected))
  443. done <- struct{}{}
  444. }()
  445. // Wait long enough
  446. b := make([]byte, 2)
  447. _, err := buf.ReadTimeout(b, 50*time.Millisecond)
  448. if err == nil && err.Error() != "timeout reading from channel" {
  449. t.Fatalf("Expected an error, got %s", err)
  450. }
  451. <-done
  452. }
  453. func TestChannelBuffer(t *testing.T) {
  454. expected := "11"
  455. buf := &ChannelBuffer{make(chan []byte, 1)}
  456. defer buf.Close()
  457. go func() {
  458. time.Sleep(100 * time.Millisecond)
  459. io.Copy(buf, strings.NewReader(expected))
  460. }()
  461. // Wait long enough
  462. b := make([]byte, 2)
  463. _, err := buf.ReadTimeout(b, 200*time.Millisecond)
  464. if err != nil {
  465. t.Fatal(err)
  466. }
  467. if string(b) != expected {
  468. t.Fatalf("Expected '%s', got '%s'", expected, string(b))
  469. }
  470. }
  471. // FIXME doesn't work
  472. // func TestRunAtDifferentDate(t *testing.T) {
  473. // var date string
  474. // // Layout for date. MMDDhhmmYYYY
  475. // const timeLayout = "20060102"
  476. // expectedDate := "20100201"
  477. // theDate, err := time.Parse(timeLayout, expectedDate)
  478. // if err != nil {
  479. // t.Fatal(err)
  480. // }
  481. // RunAtDifferentDate(theDate, func() {
  482. // cmd := exec.Command("date", "+%Y%M%d")
  483. // out, err := cmd.Output()
  484. // if err != nil {
  485. // t.Fatal(err)
  486. // }
  487. // date = string(out)
  488. // })
  489. // }