511ee61980
This way provide both Time and TimeNano in the event. For the display of the JSONMessage, use either, but prefer TimeNano Proving only TimeNano would break Subscribers that are using the `Time` field, so both are set for backwards compatibility. The events logging uses nano formatting, but only provides a Unix() time, therefor ordering may get lost in the output. Example: ``` 2015-09-15T14:18:51.000000000-04:00 ee46febd64ac629f7de9cd8bf58582e6f263d97ff46896adc5b508db804682da: (from busybox) resize 2015-09-15T14:18:51.000000000-04:00 a78c9149b1c0474502a117efaa814541926c2ae6ec3c76607e1c931b84c3a44b: (from busybox) resize ``` By having a field just for Nano time, when set, the marshalling back to `time.Unix(sec int64, nsec int64)` has zeros exactly where it needs to. This does not break any existing use of jsonmessage.JSONMessage, but now allows for use of `UnixNano()` and get event formatting that has distinguishable order. Example: ``` 2015-09-15T15:37:23.810295632-04:00 6adcf8ed9f5f5ec059a915466cd1cde86a18b4a085fc3af405e9cc9fecbbbbaf: (from busybox) resize 2015-09-15T15:37:23.810412202-04:00 6b7c5bfdc3f902096f5a91e628f21bd4b56e32590c5b4b97044aafc005ddcb0d: (from busybox) resize ``` Including tests for TimeNano and updated event API reference doc. Signed-off-by: Vincent Batts <vbatts@redhat.com>
231 lines
6.5 KiB
Go
231 lines
6.5 KiB
Go
package jsonmessage
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/docker/docker/pkg/term"
|
|
"github.com/docker/docker/pkg/timeutils"
|
|
)
|
|
|
|
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) {
|
|
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"
|
|
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"
|
|
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"
|
|
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(timeutils.RFC3339NanoFixed)),
|
|
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(timeutils.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(timeutils.RFC3339NanoFixed)),
|
|
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(timeutils.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(timeutils.RFC3339NanoFixed)),
|
|
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(timeutils.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); 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%c[%dB", 27, 0),
|
|
},
|
|
// With progress
|
|
"{ \"id\": \"ID\", \"status\": \"status\", \"progress\": \"ProgressMessage\" }": {
|
|
"ID: status ProgressMessage",
|
|
fmt.Sprintf("\n%c[%dAID: status ProgressMessage%c[%dB", 27, 0, 27, 0),
|
|
},
|
|
// 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, 0, 27, 27, 0),
|
|
},
|
|
}
|
|
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); 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); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if data.String() != expectedMessages[1] {
|
|
t.Fatalf("Expected an [%v], got [%v]", expectedMessages[1], data.String())
|
|
}
|
|
}
|
|
|
|
}
|