1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/pkg/jsonmessage/jsonmessage_test.go
Ian Campbell b08b437acc pkg/jsonmessage: Avoid undefined ANSI escape codes.
The ANSI escape codes \e[0A (cursor up 0 lines) and \e[0B (cursor down 0 lines)
are not well defined and are treated differently by different terminals. In
particular xterm treats 0 as a missing parameter and therefore defaults to 1,
whereas rxvt-unicode treats these escapes as a request to move 0 lines.

However the use of these codes is unnecessary and were really just hiding the
fact that we were not correctly computing diff when adding a new line. Having
added the new line to the ids map and output the corresponding \n we need to
then calculate a correct diff of 1 rather than leaving it as the default 0
(which xterm then interprets as 1). The fix is to pull the diff calculation out
of the else case and to always do it.

With this in place we can then avoid outputting escapes for moving 0 lines.
Actually diff should never be 0 to start with any more, but check to be safe.

This fixes corruption of `docker pull` seen with rxvt-unicode (and likely other
terminals in that family) seen in #28111. Tested with rxvt-unicode
($TERM=rxvt-unicode), xterm ($TERM=xterm), mlterm ($TERM=mlterm) and aterm
($TERM=kterm).

The test cases have been updated to match the new behaviour.

Signed-off-by: Ian Campbell <ian.campbell@docker.com>
2016-11-10 14:10:47 +00:00

245 lines
6.8 KiB
Go

package jsonmessage
import (
"bytes"
"fmt"
"strings"
"testing"
"time"
"github.com/docker/docker/pkg/jsonlog"
"github.com/docker/docker/pkg/term"
)
func TestError(t *testing.T) {
je := JSONError{404, "Not found"}
if je.Error() != "Not found" {
t.Fatalf("Expected 'Not found' got '%s'", je.Error())
}
}
func TestProgress(t *testing.T) {
termsz, err := term.GetWinsize(0)
if err != nil {
// we can safely ignore the err here
termsz = nil
}
jp := JSONProgress{}
if jp.String() != "" {
t.Fatalf("Expected empty string, got '%s'", jp.String())
}
expected := " 1 B"
jp2 := JSONProgress{Current: 1}
if jp2.String() != expected {
t.Fatalf("Expected %q, got %q", expected, jp2.String())
}
expectedStart := "[==========> ] 20 B/100 B"
if termsz != nil && termsz.Width <= 110 {
expectedStart = " 20 B/100 B"
}
jp3 := JSONProgress{Current: 20, Total: 100, Start: time.Now().Unix()}
// Just look at the start of the string
// (the remaining time is really hard to test -_-)
if jp3.String()[:len(expectedStart)] != expectedStart {
t.Fatalf("Expected to start with %q, got %q", expectedStart, jp3.String())
}
expected = "[=========================> ] 50 B/100 B"
if termsz != nil && termsz.Width <= 110 {
expected = " 50 B/100 B"
}
jp4 := JSONProgress{Current: 50, Total: 100}
if jp4.String() != expected {
t.Fatalf("Expected %q, got %q", expected, jp4.String())
}
// this number can't be negative gh#7136
expected = "[==================================================>] 50 B"
if termsz != nil && termsz.Width <= 110 {
expected = " 50 B"
}
jp5 := JSONProgress{Current: 50, Total: 40}
if jp5.String() != expected {
t.Fatalf("Expected %q, got %q", expected, jp5.String())
}
}
func TestJSONMessageDisplay(t *testing.T) {
now := time.Now()
messages := map[JSONMessage][]string{
// Empty
JSONMessage{}: {"\n", "\n"},
// Status
JSONMessage{
Status: "status",
}: {
"status\n",
"status\n",
},
// General
JSONMessage{
Time: now.Unix(),
ID: "ID",
From: "From",
Status: "status",
}: {
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(jsonlog.RFC3339NanoFixed)),
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(jsonlog.RFC3339NanoFixed)),
},
// General, with nano precision time
JSONMessage{
TimeNano: now.UnixNano(),
ID: "ID",
From: "From",
Status: "status",
}: {
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
},
// General, with both times Nano is preferred
JSONMessage{
Time: now.Unix(),
TimeNano: now.UnixNano(),
ID: "ID",
From: "From",
Status: "status",
}: {
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
},
// Stream over status
JSONMessage{
Status: "status",
Stream: "stream",
}: {
"stream",
"stream",
},
// With progress message
JSONMessage{
Status: "status",
ProgressMessage: "progressMessage",
}: {
"status progressMessage",
"status progressMessage",
},
// With progress, stream empty
JSONMessage{
Status: "status",
Stream: "",
Progress: &JSONProgress{Current: 1},
}: {
"",
fmt.Sprintf("%c[2K\rstatus 1 B\r", 27),
},
}
// The tests :)
for jsonMessage, expectedMessages := range messages {
// Without terminal
data := bytes.NewBuffer([]byte{})
if err := jsonMessage.Display(data, false); err != nil {
t.Fatal(err)
}
if data.String() != expectedMessages[0] {
t.Fatalf("Expected [%v], got [%v]", expectedMessages[0], data.String())
}
// With terminal
data = bytes.NewBuffer([]byte{})
if err := jsonMessage.Display(data, true); err != nil {
t.Fatal(err)
}
if data.String() != expectedMessages[1] {
t.Fatalf("Expected [%v], got [%v]", expectedMessages[1], data.String())
}
}
}
// Test JSONMessage with an Error. It will return an error with the text as error, not the meaning of the HTTP code.
func TestJSONMessageDisplayWithJSONError(t *testing.T) {
data := bytes.NewBuffer([]byte{})
jsonMessage := JSONMessage{Error: &JSONError{404, "Can't find it"}}
err := jsonMessage.Display(data, true)
if err == nil || err.Error() != "Can't find it" {
t.Fatalf("Expected a JSONError 404, got [%v]", err)
}
jsonMessage = JSONMessage{Error: &JSONError{401, "Anything"}}
err = jsonMessage.Display(data, true)
if err == nil || err.Error() != "Authentication is required." {
t.Fatalf("Expected an error [Authentication is required.], got [%v]", err)
}
}
func TestDisplayJSONMessagesStreamInvalidJSON(t *testing.T) {
var (
inFd uintptr
)
data := bytes.NewBuffer([]byte{})
reader := strings.NewReader("This is not a 'valid' JSON []")
inFd, _ = term.GetFdInfo(reader)
if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err == nil && err.Error()[:17] != "invalid character" {
t.Fatalf("Should have thrown an error (invalid character in ..), got [%v]", err)
}
}
func TestDisplayJSONMessagesStream(t *testing.T) {
var (
inFd uintptr
)
messages := map[string][]string{
// empty string
"": {
"",
""},
// Without progress & ID
"{ \"status\": \"status\" }": {
"status\n",
"status\n",
},
// Without progress, with ID
"{ \"id\": \"ID\",\"status\": \"status\" }": {
"ID: status\n",
fmt.Sprintf("ID: status\n"),
},
// With progress
"{ \"id\": \"ID\", \"status\": \"status\", \"progress\": \"ProgressMessage\" }": {
"ID: status ProgressMessage",
fmt.Sprintf("\n%c[%dAID: status ProgressMessage%c[%dB", 27, 1, 27, 1),
},
// With progressDetail
"{ \"id\": \"ID\", \"status\": \"status\", \"progressDetail\": { \"Current\": 1} }": {
"", // progressbar is disabled in non-terminal
fmt.Sprintf("\n%c[%dA%c[2K\rID: status 1 B\r%c[%dB", 27, 1, 27, 27, 1),
},
}
for jsonMessage, expectedMessages := range messages {
data := bytes.NewBuffer([]byte{})
reader := strings.NewReader(jsonMessage)
inFd, _ = term.GetFdInfo(reader)
// Without terminal
if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err != nil {
t.Fatal(err)
}
if data.String() != expectedMessages[0] {
t.Fatalf("Expected an [%v], got [%v]", expectedMessages[0], data.String())
}
// With terminal
data = bytes.NewBuffer([]byte{})
reader = strings.NewReader(jsonMessage)
if err := DisplayJSONMessagesStream(reader, data, inFd, true, nil); err != nil {
t.Fatal(err)
}
if data.String() != expectedMessages[1] {
t.Fatalf("Expected an [%v], got [%v]", expectedMessages[1], data.String())
}
}
}