github.com/manicqin/nomad@v0.9.5/command/helpers_test.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"os"
    10  	"reflect"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/hashicorp/nomad/api"
    16  	"github.com/hashicorp/nomad/helper"
    17  	"github.com/hashicorp/nomad/helper/flatmap"
    18  	"github.com/kr/pretty"
    19  	"github.com/mitchellh/cli"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  func TestHelpers_FormatKV(t *testing.T) {
    24  	t.Parallel()
    25  	in := []string{"alpha|beta", "charlie|delta", "echo|"}
    26  	out := formatKV(in)
    27  
    28  	expect := "alpha   = beta\n"
    29  	expect += "charlie = delta\n"
    30  	expect += "echo    = <none>"
    31  
    32  	if out != expect {
    33  		t.Fatalf("expect: %s, got: %s", expect, out)
    34  	}
    35  }
    36  
    37  func TestHelpers_FormatList(t *testing.T) {
    38  	t.Parallel()
    39  	in := []string{"alpha|beta||delta"}
    40  	out := formatList(in)
    41  
    42  	expect := "alpha  beta  <none>  delta"
    43  
    44  	if out != expect {
    45  		t.Fatalf("expect: %s, got: %s", expect, out)
    46  	}
    47  }
    48  
    49  func TestHelpers_NodeID(t *testing.T) {
    50  	t.Parallel()
    51  	srv, _, _ := testServer(t, false, nil)
    52  	defer srv.Shutdown()
    53  
    54  	meta := Meta{Ui: new(cli.MockUi)}
    55  	client, err := meta.Client()
    56  	if err != nil {
    57  		t.FailNow()
    58  	}
    59  
    60  	// This is because there is no client
    61  	if _, err := getLocalNodeID(client); err == nil {
    62  		t.Fatalf("getLocalNodeID() should fail")
    63  	}
    64  }
    65  
    66  func TestHelpers_LineLimitReader_NoTimeLimit(t *testing.T) {
    67  	t.Parallel()
    68  	helloString := `hello
    69  world
    70  this
    71  is
    72  a
    73  test`
    74  
    75  	noLines := "jskdfhjasdhfjkajkldsfdlsjkahfkjdsafa"
    76  
    77  	cases := []struct {
    78  		Input       string
    79  		Output      string
    80  		Lines       int
    81  		SearchLimit int
    82  	}{
    83  		{
    84  			Input:       helloString,
    85  			Output:      helloString,
    86  			Lines:       6,
    87  			SearchLimit: 1000,
    88  		},
    89  		{
    90  			Input: helloString,
    91  			Output: `world
    92  this
    93  is
    94  a
    95  test`,
    96  			Lines:       5,
    97  			SearchLimit: 1000,
    98  		},
    99  		{
   100  			Input:       helloString,
   101  			Output:      `test`,
   102  			Lines:       1,
   103  			SearchLimit: 1000,
   104  		},
   105  		{
   106  			Input:       helloString,
   107  			Output:      "",
   108  			Lines:       0,
   109  			SearchLimit: 1000,
   110  		},
   111  		{
   112  			Input:       helloString,
   113  			Output:      helloString,
   114  			Lines:       6,
   115  			SearchLimit: 1, // Exceed the limit
   116  		},
   117  		{
   118  			Input:       noLines,
   119  			Output:      noLines,
   120  			Lines:       10,
   121  			SearchLimit: 1000,
   122  		},
   123  		{
   124  			Input:       noLines,
   125  			Output:      noLines,
   126  			Lines:       10,
   127  			SearchLimit: 2,
   128  		},
   129  	}
   130  
   131  	for i, c := range cases {
   132  		in := ioutil.NopCloser(strings.NewReader(c.Input))
   133  		limit := NewLineLimitReader(in, c.Lines, c.SearchLimit, 0)
   134  		outBytes, err := ioutil.ReadAll(limit)
   135  		if err != nil {
   136  			t.Fatalf("case %d failed: %v", i, err)
   137  		}
   138  
   139  		out := string(outBytes)
   140  		if out != c.Output {
   141  			t.Fatalf("case %d: got %q; want %q", i, out, c.Output)
   142  		}
   143  	}
   144  }
   145  
   146  type testReadCloser struct {
   147  	data chan []byte
   148  }
   149  
   150  func (t *testReadCloser) Read(p []byte) (n int, err error) {
   151  	select {
   152  	case b, ok := <-t.data:
   153  		if !ok {
   154  			return 0, io.EOF
   155  		}
   156  
   157  		return copy(p, b), nil
   158  	case <-time.After(10 * time.Millisecond):
   159  		return 0, nil
   160  	}
   161  }
   162  
   163  func (t *testReadCloser) Close() error {
   164  	close(t.data)
   165  	return nil
   166  }
   167  
   168  func TestHelpers_LineLimitReader_TimeLimit(t *testing.T) {
   169  	t.Parallel()
   170  	// Create the test reader
   171  	in := &testReadCloser{data: make(chan []byte)}
   172  
   173  	// Set up the reader such that it won't hit the line/buffer limit and could
   174  	// only terminate if it hits the time limit
   175  	limit := NewLineLimitReader(in, 1000, 1000, 100*time.Millisecond)
   176  
   177  	expected := []byte("hello world")
   178  
   179  	errCh := make(chan error)
   180  	resultCh := make(chan []byte)
   181  	go func() {
   182  		defer close(resultCh)
   183  		defer close(errCh)
   184  		outBytes, err := ioutil.ReadAll(limit)
   185  		if err != nil {
   186  			errCh <- fmt.Errorf("ReadAll failed: %v", err)
   187  			return
   188  		}
   189  		resultCh <- outBytes
   190  	}()
   191  
   192  	// Send the data
   193  	in.data <- expected
   194  	in.Close()
   195  
   196  	select {
   197  	case err := <-errCh:
   198  		if err != nil {
   199  			t.Fatalf("ReadAll: %v", err)
   200  		}
   201  	case outBytes := <-resultCh:
   202  		if !reflect.DeepEqual(outBytes, expected) {
   203  			t.Fatalf("got:%s, expected,%s", string(outBytes), string(expected))
   204  		}
   205  	case <-time.After(1 * time.Second):
   206  		t.Fatalf("did not exit by time limit")
   207  	}
   208  }
   209  
   210  const (
   211  	job = `job "job1" {
   212          type = "service"
   213          datacenters = [ "dc1" ]
   214          group "group1" {
   215                  count = 1
   216                  task "task1" {
   217                          driver = "exec"
   218                          resources = {}
   219                  }
   220                  restart{
   221                          attempts = 10
   222                          mode = "delay"
   223  						interval = "15s"
   224                  }
   225          }
   226  }`
   227  )
   228  
   229  var (
   230  	expectedApiJob = &api.Job{
   231  		ID:          helper.StringToPtr("job1"),
   232  		Name:        helper.StringToPtr("job1"),
   233  		Type:        helper.StringToPtr("service"),
   234  		Datacenters: []string{"dc1"},
   235  		TaskGroups: []*api.TaskGroup{
   236  			{
   237  				Name:  helper.StringToPtr("group1"),
   238  				Count: helper.IntToPtr(1),
   239  				RestartPolicy: &api.RestartPolicy{
   240  					Attempts: helper.IntToPtr(10),
   241  					Interval: helper.TimeToPtr(15 * time.Second),
   242  					Mode:     helper.StringToPtr("delay"),
   243  				},
   244  
   245  				Tasks: []*api.Task{
   246  					{
   247  						Driver:    "exec",
   248  						Name:      "task1",
   249  						Resources: &api.Resources{},
   250  					},
   251  				},
   252  			},
   253  		},
   254  	}
   255  )
   256  
   257  // Test APIJob with local jobfile
   258  func TestJobGetter_LocalFile(t *testing.T) {
   259  	t.Parallel()
   260  	fh, err := ioutil.TempFile("", "nomad")
   261  	if err != nil {
   262  		t.Fatalf("err: %s", err)
   263  	}
   264  	defer os.Remove(fh.Name())
   265  	_, err = fh.WriteString(job)
   266  	if err != nil {
   267  		t.Fatalf("err: %s", err)
   268  	}
   269  
   270  	j := &JobGetter{}
   271  	aj, err := j.ApiJob(fh.Name())
   272  	if err != nil {
   273  		t.Fatalf("err: %s", err)
   274  	}
   275  
   276  	if !reflect.DeepEqual(expectedApiJob, aj) {
   277  		eflat := flatmap.Flatten(expectedApiJob, nil, false)
   278  		aflat := flatmap.Flatten(aj, nil, false)
   279  		t.Fatalf("got:\n%v\nwant:\n%v", aflat, eflat)
   280  	}
   281  }
   282  
   283  // Test StructJob with jobfile from HTTP Server
   284  func TestJobGetter_HTTPServer(t *testing.T) {
   285  	t.Parallel()
   286  	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   287  		fmt.Fprintf(w, job)
   288  	})
   289  	go http.ListenAndServe("127.0.0.1:12345", nil)
   290  
   291  	// Wait until HTTP Server starts certainly
   292  	time.Sleep(100 * time.Millisecond)
   293  
   294  	j := &JobGetter{}
   295  	aj, err := j.ApiJob("http://127.0.0.1:12345/")
   296  	if err != nil {
   297  		t.Fatalf("err: %s", err)
   298  	}
   299  	if !reflect.DeepEqual(expectedApiJob, aj) {
   300  		for _, d := range pretty.Diff(expectedApiJob, aj) {
   301  			t.Logf(d)
   302  		}
   303  		t.Fatalf("Unexpected file")
   304  	}
   305  }
   306  
   307  func TestPrettyTimeDiff(t *testing.T) {
   308  	// Grab the time and truncate to the nearest second. This allows our tests
   309  	// to be deterministic since we don't have to worry about rounding.
   310  	now := time.Now().Truncate(time.Second)
   311  
   312  	test_cases := []struct {
   313  		t1  time.Time
   314  		t2  time.Time
   315  		exp string
   316  	}{
   317  		{now, time.Unix(0, 0), ""}, // This is the upgrade path case
   318  		{now, now.Add(-10 * time.Millisecond), "0s ago"},
   319  		{now, now.Add(-740 * time.Second), "12m20s ago"},
   320  		{now, now.Add(-12 * time.Minute), "12m ago"},
   321  		{now, now.Add(-60 * time.Minute), "1h ago"},
   322  		{now, now.Add(-80 * time.Minute), "1h20m ago"},
   323  		{now, now.Add(-6 * time.Hour), "6h ago"},
   324  		{now.Add(-6 * time.Hour), now, "6h from now"},
   325  		{now, now.Add(-22165 * time.Second), "6h9m ago"},
   326  		{now, now.Add(-100 * time.Hour), "4d4h ago"},
   327  		{now, now.Add(-438000 * time.Minute), "10mo4d ago"},
   328  		{now, now.Add(-20460 * time.Hour), "2y4mo ago"},
   329  	}
   330  	for _, tc := range test_cases {
   331  		t.Run(tc.exp, func(t *testing.T) {
   332  			out := prettyTimeDiff(tc.t2, tc.t1)
   333  			if out != tc.exp {
   334  				t.Fatalf("expected :%v but got :%v", tc.exp, out)
   335  			}
   336  		})
   337  	}
   338  
   339  	var t1 time.Time
   340  	out := prettyTimeDiff(t1, time.Now())
   341  
   342  	if out != "" {
   343  		t.Fatalf("Expected empty output but got:%v", out)
   344  	}
   345  
   346  }
   347  
   348  // TestUiErrorWriter asserts that writer buffers and
   349  func TestUiErrorWriter(t *testing.T) {
   350  	t.Parallel()
   351  
   352  	var outBuf, errBuf bytes.Buffer
   353  	ui := &cli.BasicUi{
   354  		Writer:      &outBuf,
   355  		ErrorWriter: &errBuf,
   356  	}
   357  
   358  	w := &uiErrorWriter{ui: ui}
   359  
   360  	inputs := []string{
   361  		"some line\n",
   362  		"multiple\nlines\r\nhere",
   363  		" with  followup\nand",
   364  		" more lines ",
   365  		" without new line ",
   366  		"until here\nand then",
   367  		"some more",
   368  	}
   369  
   370  	partialAcc := ""
   371  	for _, in := range inputs {
   372  		n, err := w.Write([]byte(in))
   373  		require.NoError(t, err)
   374  		require.Equal(t, len(in), n)
   375  
   376  		// assert that writer emits partial result until last new line
   377  		partialAcc += strings.ReplaceAll(in, "\r\n", "\n")
   378  		lastNL := strings.LastIndex(partialAcc, "\n")
   379  		require.Equal(t, partialAcc[:lastNL+1], errBuf.String())
   380  	}
   381  
   382  	require.Empty(t, outBuf.String())
   383  
   384  	// note that the \r\n got replaced by \n
   385  	expectedErr := "some line\nmultiple\nlines\nhere with  followup\nand more lines  without new line until here\n"
   386  	require.Equal(t, expectedErr, errBuf.String())
   387  
   388  	// close emits the final line
   389  	err := w.Close()
   390  	require.NoError(t, err)
   391  
   392  	expectedErr += "and thensome more\n"
   393  	require.Equal(t, expectedErr, errBuf.String())
   394  }