mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
b08b437acc
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>
245 lines
6.8 KiB
Go
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())
|
|
}
|
|
}
|
|
|
|
}
|