github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/jsonmessage/jsonmessage_test.go (about)

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