jsonmessage_test.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. package jsonmessage // import "github.com/docker/docker/pkg/jsonmessage"
  2. import (
  3. "bytes"
  4. "fmt"
  5. "os"
  6. "strings"
  7. "testing"
  8. "time"
  9. "github.com/docker/docker/pkg/term"
  10. "github.com/gotestyourself/gotestyourself/assert"
  11. is "github.com/gotestyourself/gotestyourself/assert/cmp"
  12. )
  13. func TestError(t *testing.T) {
  14. je := JSONError{404, "Not found"}
  15. assert.Assert(t, is.Error(&je, "Not found"))
  16. }
  17. func TestProgressString(t *testing.T) {
  18. type expected struct {
  19. short string
  20. long string
  21. }
  22. shortAndLong := func(short, long string) expected {
  23. return expected{short: short, long: long}
  24. }
  25. start := time.Date(2017, 12, 3, 15, 10, 1, 0, time.UTC)
  26. timeAfter := func(delta time.Duration) func() time.Time {
  27. return func() time.Time {
  28. return start.Add(delta)
  29. }
  30. }
  31. var testcases = []struct {
  32. name string
  33. progress JSONProgress
  34. expected expected
  35. }{
  36. {
  37. name: "no progress",
  38. },
  39. {
  40. name: "progress 1",
  41. progress: JSONProgress{Current: 1},
  42. expected: shortAndLong(" 1B", " 1B"),
  43. },
  44. {
  45. name: "some progress with a start time",
  46. progress: JSONProgress{
  47. Current: 20,
  48. Total: 100,
  49. Start: start.Unix(),
  50. nowFunc: timeAfter(time.Second),
  51. },
  52. expected: shortAndLong(
  53. " 20B/100B 4s",
  54. "[==========> ] 20B/100B 4s",
  55. ),
  56. },
  57. {
  58. name: "some progress without a start time",
  59. progress: JSONProgress{Current: 50, Total: 100},
  60. expected: shortAndLong(
  61. " 50B/100B",
  62. "[=========================> ] 50B/100B",
  63. ),
  64. },
  65. {
  66. name: "current more than total is not negative gh#7136",
  67. progress: JSONProgress{Current: 50, Total: 40},
  68. expected: shortAndLong(
  69. " 50B",
  70. "[==================================================>] 50B",
  71. ),
  72. },
  73. {
  74. name: "with units",
  75. progress: JSONProgress{Current: 50, Total: 100, Units: "units"},
  76. expected: shortAndLong(
  77. "50/100 units",
  78. "[=========================> ] 50/100 units",
  79. ),
  80. },
  81. {
  82. name: "current more than total with units is not negative ",
  83. progress: JSONProgress{Current: 50, Total: 40, Units: "units"},
  84. expected: shortAndLong(
  85. "50 units",
  86. "[==================================================>] 50 units",
  87. ),
  88. },
  89. {
  90. name: "hide counts",
  91. progress: JSONProgress{Current: 50, Total: 100, HideCounts: true},
  92. expected: shortAndLong(
  93. "",
  94. "[=========================> ] ",
  95. ),
  96. },
  97. }
  98. for _, testcase := range testcases {
  99. t.Run(testcase.name, func(t *testing.T) {
  100. testcase.progress.winSize = 100
  101. assert.Equal(t, testcase.progress.String(), testcase.expected.short)
  102. testcase.progress.winSize = 200
  103. assert.Equal(t, testcase.progress.String(), testcase.expected.long)
  104. })
  105. }
  106. }
  107. func TestJSONMessageDisplay(t *testing.T) {
  108. now := time.Now()
  109. messages := map[JSONMessage][]string{
  110. // Empty
  111. {}: {"\n", "\n"},
  112. // Status
  113. {
  114. Status: "status",
  115. }: {
  116. "status\n",
  117. "status\n",
  118. },
  119. // General
  120. {
  121. Time: now.Unix(),
  122. ID: "ID",
  123. From: "From",
  124. Status: "status",
  125. }: {
  126. fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(RFC3339NanoFixed)),
  127. fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(RFC3339NanoFixed)),
  128. },
  129. // General, with nano precision time
  130. {
  131. TimeNano: now.UnixNano(),
  132. ID: "ID",
  133. From: "From",
  134. Status: "status",
  135. }: {
  136. fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)),
  137. fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)),
  138. },
  139. // General, with both times Nano is preferred
  140. {
  141. Time: now.Unix(),
  142. TimeNano: now.UnixNano(),
  143. ID: "ID",
  144. From: "From",
  145. Status: "status",
  146. }: {
  147. fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)),
  148. fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(RFC3339NanoFixed)),
  149. },
  150. // Stream over status
  151. {
  152. Status: "status",
  153. Stream: "stream",
  154. }: {
  155. "stream",
  156. "stream",
  157. },
  158. // With progress message
  159. {
  160. Status: "status",
  161. ProgressMessage: "progressMessage",
  162. }: {
  163. "status progressMessage",
  164. "status progressMessage",
  165. },
  166. // With progress, stream empty
  167. {
  168. Status: "status",
  169. Stream: "",
  170. Progress: &JSONProgress{Current: 1},
  171. }: {
  172. "",
  173. fmt.Sprintf("%c[1K%c[K\rstatus 1B\r", 27, 27),
  174. },
  175. }
  176. // The tests :)
  177. for jsonMessage, expectedMessages := range messages {
  178. // Without terminal
  179. data := bytes.NewBuffer([]byte{})
  180. if err := jsonMessage.Display(data, nil); err != nil {
  181. t.Fatal(err)
  182. }
  183. if data.String() != expectedMessages[0] {
  184. t.Fatalf("Expected %q,got %q", expectedMessages[0], data.String())
  185. }
  186. // With terminal
  187. data = bytes.NewBuffer([]byte{})
  188. if err := jsonMessage.Display(data, &noTermInfo{}); err != nil {
  189. t.Fatal(err)
  190. }
  191. if data.String() != expectedMessages[1] {
  192. t.Fatalf("\nExpected %q\n got %q", expectedMessages[1], data.String())
  193. }
  194. }
  195. }
  196. // Test JSONMessage with an Error. It will return an error with the text as error, not the meaning of the HTTP code.
  197. func TestJSONMessageDisplayWithJSONError(t *testing.T) {
  198. data := bytes.NewBuffer([]byte{})
  199. jsonMessage := JSONMessage{Error: &JSONError{404, "Can't find it"}}
  200. err := jsonMessage.Display(data, &noTermInfo{})
  201. if err == nil || err.Error() != "Can't find it" {
  202. t.Fatalf("Expected a JSONError 404, got %q", err)
  203. }
  204. jsonMessage = JSONMessage{Error: &JSONError{401, "Anything"}}
  205. err = jsonMessage.Display(data, &noTermInfo{})
  206. assert.Check(t, is.Error(err, "authentication is required"))
  207. }
  208. func TestDisplayJSONMessagesStreamInvalidJSON(t *testing.T) {
  209. var (
  210. inFd uintptr
  211. )
  212. data := bytes.NewBuffer([]byte{})
  213. reader := strings.NewReader("This is not a 'valid' JSON []")
  214. inFd, _ = term.GetFdInfo(reader)
  215. if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err == nil && err.Error()[:17] != "invalid character" {
  216. t.Fatalf("Should have thrown an error (invalid character in ..), got %q", err)
  217. }
  218. }
  219. func TestDisplayJSONMessagesStream(t *testing.T) {
  220. var (
  221. inFd uintptr
  222. )
  223. messages := map[string][]string{
  224. // empty string
  225. "": {
  226. "",
  227. ""},
  228. // Without progress & ID
  229. "{ \"status\": \"status\" }": {
  230. "status\n",
  231. "status\n",
  232. },
  233. // Without progress, with ID
  234. "{ \"id\": \"ID\",\"status\": \"status\" }": {
  235. "ID: status\n",
  236. fmt.Sprintf("ID: status\n"),
  237. },
  238. // With progress
  239. "{ \"id\": \"ID\", \"status\": \"status\", \"progress\": \"ProgressMessage\" }": {
  240. "ID: status ProgressMessage",
  241. fmt.Sprintf("\n%c[%dAID: status ProgressMessage%c[%dB", 27, 1, 27, 1),
  242. },
  243. // With progressDetail
  244. "{ \"id\": \"ID\", \"status\": \"status\", \"progressDetail\": { \"Current\": 1} }": {
  245. "", // progressbar is disabled in non-terminal
  246. fmt.Sprintf("\n%c[%dA%c[1K%c[K\rID: status 1B\r%c[%dB", 27, 1, 27, 27, 27, 1),
  247. },
  248. }
  249. // Use $TERM which is unlikely to exist, forcing DisplayJSONMessageStream to
  250. // (hopefully) use &noTermInfo.
  251. origTerm := os.Getenv("TERM")
  252. os.Setenv("TERM", "xyzzy-non-existent-terminfo")
  253. for jsonMessage, expectedMessages := range messages {
  254. data := bytes.NewBuffer([]byte{})
  255. reader := strings.NewReader(jsonMessage)
  256. inFd, _ = term.GetFdInfo(reader)
  257. // Without terminal
  258. if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err != nil {
  259. t.Fatal(err)
  260. }
  261. if data.String() != expectedMessages[0] {
  262. t.Fatalf("Expected an %q, got %q", expectedMessages[0], data.String())
  263. }
  264. // With terminal
  265. data = bytes.NewBuffer([]byte{})
  266. reader = strings.NewReader(jsonMessage)
  267. if err := DisplayJSONMessagesStream(reader, data, inFd, true, nil); err != nil {
  268. t.Fatal(err)
  269. }
  270. if data.String() != expectedMessages[1] {
  271. t.Fatalf("\nExpected %q\n got %q", expectedMessages[1], data.String())
  272. }
  273. }
  274. os.Setenv("TERM", origTerm)
  275. }