jsonmessage_test.go 7.7 KB

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