github.com/kunnos/engine@v1.13.1/daemon/logger/awslogs/cloudwatchlogs_test.go (about)

     1  package awslogs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  	"reflect"
     8  	"runtime"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/aws/aws-sdk-go/aws"
    14  	"github.com/aws/aws-sdk-go/aws/awserr"
    15  	"github.com/aws/aws-sdk-go/aws/request"
    16  	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
    17  	"github.com/docker/docker/daemon/logger"
    18  	"github.com/docker/docker/daemon/logger/loggerutils"
    19  	"github.com/docker/docker/dockerversion"
    20  )
    21  
    22  const (
    23  	groupName         = "groupName"
    24  	streamName        = "streamName"
    25  	sequenceToken     = "sequenceToken"
    26  	nextSequenceToken = "nextSequenceToken"
    27  	logline           = "this is a log line"
    28  )
    29  
    30  func TestNewAWSLogsClientUserAgentHandler(t *testing.T) {
    31  	ctx := logger.Context{
    32  		Config: map[string]string{
    33  			regionKey: "us-east-1",
    34  		},
    35  	}
    36  
    37  	client, err := newAWSLogsClient(ctx)
    38  	if err != nil {
    39  		t.Fatal(err)
    40  	}
    41  	realClient, ok := client.(*cloudwatchlogs.CloudWatchLogs)
    42  	if !ok {
    43  		t.Fatal("Could not cast client to cloudwatchlogs.CloudWatchLogs")
    44  	}
    45  	buildHandlerList := realClient.Handlers.Build
    46  	request := &request.Request{
    47  		HTTPRequest: &http.Request{
    48  			Header: http.Header{},
    49  		},
    50  	}
    51  	buildHandlerList.Run(request)
    52  	expectedUserAgentString := fmt.Sprintf("Docker %s (%s) %s/%s (%s; %s; %s)",
    53  		dockerversion.Version, runtime.GOOS, aws.SDKName, aws.SDKVersion, runtime.Version(), runtime.GOOS, runtime.GOARCH)
    54  	userAgent := request.HTTPRequest.Header.Get("User-Agent")
    55  	if userAgent != expectedUserAgentString {
    56  		t.Errorf("Wrong User-Agent string, expected \"%s\" but was \"%s\"",
    57  			expectedUserAgentString, userAgent)
    58  	}
    59  }
    60  
    61  func TestNewAWSLogsClientRegionDetect(t *testing.T) {
    62  	ctx := logger.Context{
    63  		Config: map[string]string{},
    64  	}
    65  
    66  	mockMetadata := newMockMetadataClient()
    67  	newRegionFinder = func() regionFinder {
    68  		return mockMetadata
    69  	}
    70  	mockMetadata.regionResult <- &regionResult{
    71  		successResult: "us-east-1",
    72  	}
    73  
    74  	_, err := newAWSLogsClient(ctx)
    75  	if err != nil {
    76  		t.Fatal(err)
    77  	}
    78  }
    79  
    80  func TestCreateSuccess(t *testing.T) {
    81  	mockClient := newMockClient()
    82  	stream := &logStream{
    83  		client:        mockClient,
    84  		logGroupName:  groupName,
    85  		logStreamName: streamName,
    86  	}
    87  	mockClient.createLogStreamResult <- &createLogStreamResult{}
    88  
    89  	err := stream.create()
    90  
    91  	if err != nil {
    92  		t.Errorf("Received unexpected err: %v\n", err)
    93  	}
    94  	argument := <-mockClient.createLogStreamArgument
    95  	if argument.LogGroupName == nil {
    96  		t.Fatal("Expected non-nil LogGroupName")
    97  	}
    98  	if *argument.LogGroupName != groupName {
    99  		t.Errorf("Expected LogGroupName to be %s", groupName)
   100  	}
   101  	if argument.LogStreamName == nil {
   102  		t.Fatal("Expected non-nil LogGroupName")
   103  	}
   104  	if *argument.LogStreamName != streamName {
   105  		t.Errorf("Expected LogStreamName to be %s", streamName)
   106  	}
   107  }
   108  
   109  func TestCreateError(t *testing.T) {
   110  	mockClient := newMockClient()
   111  	stream := &logStream{
   112  		client: mockClient,
   113  	}
   114  	mockClient.createLogStreamResult <- &createLogStreamResult{
   115  		errorResult: errors.New("Error!"),
   116  	}
   117  
   118  	err := stream.create()
   119  
   120  	if err == nil {
   121  		t.Fatal("Expected non-nil err")
   122  	}
   123  }
   124  
   125  func TestCreateAlreadyExists(t *testing.T) {
   126  	mockClient := newMockClient()
   127  	stream := &logStream{
   128  		client: mockClient,
   129  	}
   130  	mockClient.createLogStreamResult <- &createLogStreamResult{
   131  		errorResult: awserr.New(resourceAlreadyExistsCode, "", nil),
   132  	}
   133  
   134  	err := stream.create()
   135  
   136  	if err != nil {
   137  		t.Fatal("Expected nil err")
   138  	}
   139  }
   140  
   141  func TestPublishBatchSuccess(t *testing.T) {
   142  	mockClient := newMockClient()
   143  	stream := &logStream{
   144  		client:        mockClient,
   145  		logGroupName:  groupName,
   146  		logStreamName: streamName,
   147  		sequenceToken: aws.String(sequenceToken),
   148  	}
   149  	mockClient.putLogEventsResult <- &putLogEventsResult{
   150  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   151  			NextSequenceToken: aws.String(nextSequenceToken),
   152  		},
   153  	}
   154  	events := []wrappedEvent{
   155  		{
   156  			inputLogEvent: &cloudwatchlogs.InputLogEvent{
   157  				Message: aws.String(logline),
   158  			},
   159  		},
   160  	}
   161  
   162  	stream.publishBatch(events)
   163  	if stream.sequenceToken == nil {
   164  		t.Fatal("Expected non-nil sequenceToken")
   165  	}
   166  	if *stream.sequenceToken != nextSequenceToken {
   167  		t.Errorf("Expected sequenceToken to be %s, but was %s", nextSequenceToken, *stream.sequenceToken)
   168  	}
   169  	argument := <-mockClient.putLogEventsArgument
   170  	if argument == nil {
   171  		t.Fatal("Expected non-nil PutLogEventsInput")
   172  	}
   173  	if argument.SequenceToken == nil {
   174  		t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
   175  	}
   176  	if *argument.SequenceToken != sequenceToken {
   177  		t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", sequenceToken, *argument.SequenceToken)
   178  	}
   179  	if len(argument.LogEvents) != 1 {
   180  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   181  	}
   182  	if argument.LogEvents[0] != events[0].inputLogEvent {
   183  		t.Error("Expected event to equal input")
   184  	}
   185  }
   186  
   187  func TestPublishBatchError(t *testing.T) {
   188  	mockClient := newMockClient()
   189  	stream := &logStream{
   190  		client:        mockClient,
   191  		logGroupName:  groupName,
   192  		logStreamName: streamName,
   193  		sequenceToken: aws.String(sequenceToken),
   194  	}
   195  	mockClient.putLogEventsResult <- &putLogEventsResult{
   196  		errorResult: errors.New("Error!"),
   197  	}
   198  
   199  	events := []wrappedEvent{
   200  		{
   201  			inputLogEvent: &cloudwatchlogs.InputLogEvent{
   202  				Message: aws.String(logline),
   203  			},
   204  		},
   205  	}
   206  
   207  	stream.publishBatch(events)
   208  	if stream.sequenceToken == nil {
   209  		t.Fatal("Expected non-nil sequenceToken")
   210  	}
   211  	if *stream.sequenceToken != sequenceToken {
   212  		t.Errorf("Expected sequenceToken to be %s, but was %s", sequenceToken, *stream.sequenceToken)
   213  	}
   214  }
   215  
   216  func TestPublishBatchInvalidSeqSuccess(t *testing.T) {
   217  	mockClient := newMockClientBuffered(2)
   218  	stream := &logStream{
   219  		client:        mockClient,
   220  		logGroupName:  groupName,
   221  		logStreamName: streamName,
   222  		sequenceToken: aws.String(sequenceToken),
   223  	}
   224  	mockClient.putLogEventsResult <- &putLogEventsResult{
   225  		errorResult: awserr.New(invalidSequenceTokenCode, "use token token", nil),
   226  	}
   227  	mockClient.putLogEventsResult <- &putLogEventsResult{
   228  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   229  			NextSequenceToken: aws.String(nextSequenceToken),
   230  		},
   231  	}
   232  
   233  	events := []wrappedEvent{
   234  		{
   235  			inputLogEvent: &cloudwatchlogs.InputLogEvent{
   236  				Message: aws.String(logline),
   237  			},
   238  		},
   239  	}
   240  
   241  	stream.publishBatch(events)
   242  	if stream.sequenceToken == nil {
   243  		t.Fatal("Expected non-nil sequenceToken")
   244  	}
   245  	if *stream.sequenceToken != nextSequenceToken {
   246  		t.Errorf("Expected sequenceToken to be %s, but was %s", nextSequenceToken, *stream.sequenceToken)
   247  	}
   248  
   249  	argument := <-mockClient.putLogEventsArgument
   250  	if argument == nil {
   251  		t.Fatal("Expected non-nil PutLogEventsInput")
   252  	}
   253  	if argument.SequenceToken == nil {
   254  		t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
   255  	}
   256  	if *argument.SequenceToken != sequenceToken {
   257  		t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", sequenceToken, *argument.SequenceToken)
   258  	}
   259  	if len(argument.LogEvents) != 1 {
   260  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   261  	}
   262  	if argument.LogEvents[0] != events[0].inputLogEvent {
   263  		t.Error("Expected event to equal input")
   264  	}
   265  
   266  	argument = <-mockClient.putLogEventsArgument
   267  	if argument == nil {
   268  		t.Fatal("Expected non-nil PutLogEventsInput")
   269  	}
   270  	if argument.SequenceToken == nil {
   271  		t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
   272  	}
   273  	if *argument.SequenceToken != "token" {
   274  		t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", "token", *argument.SequenceToken)
   275  	}
   276  	if len(argument.LogEvents) != 1 {
   277  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   278  	}
   279  	if argument.LogEvents[0] != events[0].inputLogEvent {
   280  		t.Error("Expected event to equal input")
   281  	}
   282  }
   283  
   284  func TestPublishBatchAlreadyAccepted(t *testing.T) {
   285  	mockClient := newMockClient()
   286  	stream := &logStream{
   287  		client:        mockClient,
   288  		logGroupName:  groupName,
   289  		logStreamName: streamName,
   290  		sequenceToken: aws.String(sequenceToken),
   291  	}
   292  	mockClient.putLogEventsResult <- &putLogEventsResult{
   293  		errorResult: awserr.New(dataAlreadyAcceptedCode, "use token token", nil),
   294  	}
   295  
   296  	events := []wrappedEvent{
   297  		{
   298  			inputLogEvent: &cloudwatchlogs.InputLogEvent{
   299  				Message: aws.String(logline),
   300  			},
   301  		},
   302  	}
   303  
   304  	stream.publishBatch(events)
   305  	if stream.sequenceToken == nil {
   306  		t.Fatal("Expected non-nil sequenceToken")
   307  	}
   308  	if *stream.sequenceToken != "token" {
   309  		t.Errorf("Expected sequenceToken to be %s, but was %s", "token", *stream.sequenceToken)
   310  	}
   311  
   312  	argument := <-mockClient.putLogEventsArgument
   313  	if argument == nil {
   314  		t.Fatal("Expected non-nil PutLogEventsInput")
   315  	}
   316  	if argument.SequenceToken == nil {
   317  		t.Fatal("Expected non-nil PutLogEventsInput.SequenceToken")
   318  	}
   319  	if *argument.SequenceToken != sequenceToken {
   320  		t.Errorf("Expected PutLogEventsInput.SequenceToken to be %s, but was %s", sequenceToken, *argument.SequenceToken)
   321  	}
   322  	if len(argument.LogEvents) != 1 {
   323  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   324  	}
   325  	if argument.LogEvents[0] != events[0].inputLogEvent {
   326  		t.Error("Expected event to equal input")
   327  	}
   328  }
   329  
   330  func TestCollectBatchSimple(t *testing.T) {
   331  	mockClient := newMockClient()
   332  	stream := &logStream{
   333  		client:        mockClient,
   334  		logGroupName:  groupName,
   335  		logStreamName: streamName,
   336  		sequenceToken: aws.String(sequenceToken),
   337  		messages:      make(chan *logger.Message),
   338  	}
   339  	mockClient.putLogEventsResult <- &putLogEventsResult{
   340  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   341  			NextSequenceToken: aws.String(nextSequenceToken),
   342  		},
   343  	}
   344  	ticks := make(chan time.Time)
   345  	newTicker = func(_ time.Duration) *time.Ticker {
   346  		return &time.Ticker{
   347  			C: ticks,
   348  		}
   349  	}
   350  
   351  	go stream.collectBatch()
   352  
   353  	stream.Log(&logger.Message{
   354  		Line:      []byte(logline),
   355  		Timestamp: time.Time{},
   356  	})
   357  
   358  	ticks <- time.Time{}
   359  	stream.Close()
   360  
   361  	argument := <-mockClient.putLogEventsArgument
   362  	if argument == nil {
   363  		t.Fatal("Expected non-nil PutLogEventsInput")
   364  	}
   365  	if len(argument.LogEvents) != 1 {
   366  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   367  	}
   368  	if *argument.LogEvents[0].Message != logline {
   369  		t.Errorf("Expected message to be %s but was %s", logline, *argument.LogEvents[0].Message)
   370  	}
   371  }
   372  
   373  func TestCollectBatchTicker(t *testing.T) {
   374  	mockClient := newMockClient()
   375  	stream := &logStream{
   376  		client:        mockClient,
   377  		logGroupName:  groupName,
   378  		logStreamName: streamName,
   379  		sequenceToken: aws.String(sequenceToken),
   380  		messages:      make(chan *logger.Message),
   381  	}
   382  	mockClient.putLogEventsResult <- &putLogEventsResult{
   383  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   384  			NextSequenceToken: aws.String(nextSequenceToken),
   385  		},
   386  	}
   387  	ticks := make(chan time.Time)
   388  	newTicker = func(_ time.Duration) *time.Ticker {
   389  		return &time.Ticker{
   390  			C: ticks,
   391  		}
   392  	}
   393  
   394  	go stream.collectBatch()
   395  
   396  	stream.Log(&logger.Message{
   397  		Line:      []byte(logline + " 1"),
   398  		Timestamp: time.Time{},
   399  	})
   400  	stream.Log(&logger.Message{
   401  		Line:      []byte(logline + " 2"),
   402  		Timestamp: time.Time{},
   403  	})
   404  
   405  	ticks <- time.Time{}
   406  
   407  	// Verify first batch
   408  	argument := <-mockClient.putLogEventsArgument
   409  	if argument == nil {
   410  		t.Fatal("Expected non-nil PutLogEventsInput")
   411  	}
   412  	if len(argument.LogEvents) != 2 {
   413  		t.Errorf("Expected LogEvents to contain 2 elements, but contains %d", len(argument.LogEvents))
   414  	}
   415  	if *argument.LogEvents[0].Message != logline+" 1" {
   416  		t.Errorf("Expected message to be %s but was %s", logline+" 1", *argument.LogEvents[0].Message)
   417  	}
   418  	if *argument.LogEvents[1].Message != logline+" 2" {
   419  		t.Errorf("Expected message to be %s but was %s", logline+" 2", *argument.LogEvents[0].Message)
   420  	}
   421  
   422  	stream.Log(&logger.Message{
   423  		Line:      []byte(logline + " 3"),
   424  		Timestamp: time.Time{},
   425  	})
   426  
   427  	ticks <- time.Time{}
   428  	argument = <-mockClient.putLogEventsArgument
   429  	if argument == nil {
   430  		t.Fatal("Expected non-nil PutLogEventsInput")
   431  	}
   432  	if len(argument.LogEvents) != 1 {
   433  		t.Errorf("Expected LogEvents to contain 1 elements, but contains %d", len(argument.LogEvents))
   434  	}
   435  	if *argument.LogEvents[0].Message != logline+" 3" {
   436  		t.Errorf("Expected message to be %s but was %s", logline+" 3", *argument.LogEvents[0].Message)
   437  	}
   438  
   439  	stream.Close()
   440  
   441  }
   442  
   443  func TestCollectBatchClose(t *testing.T) {
   444  	mockClient := newMockClient()
   445  	stream := &logStream{
   446  		client:        mockClient,
   447  		logGroupName:  groupName,
   448  		logStreamName: streamName,
   449  		sequenceToken: aws.String(sequenceToken),
   450  		messages:      make(chan *logger.Message),
   451  	}
   452  	mockClient.putLogEventsResult <- &putLogEventsResult{
   453  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   454  			NextSequenceToken: aws.String(nextSequenceToken),
   455  		},
   456  	}
   457  	var ticks = make(chan time.Time)
   458  	newTicker = func(_ time.Duration) *time.Ticker {
   459  		return &time.Ticker{
   460  			C: ticks,
   461  		}
   462  	}
   463  
   464  	go stream.collectBatch()
   465  
   466  	stream.Log(&logger.Message{
   467  		Line:      []byte(logline),
   468  		Timestamp: time.Time{},
   469  	})
   470  
   471  	// no ticks
   472  	stream.Close()
   473  
   474  	argument := <-mockClient.putLogEventsArgument
   475  	if argument == nil {
   476  		t.Fatal("Expected non-nil PutLogEventsInput")
   477  	}
   478  	if len(argument.LogEvents) != 1 {
   479  		t.Errorf("Expected LogEvents to contain 1 element, but contains %d", len(argument.LogEvents))
   480  	}
   481  	if *argument.LogEvents[0].Message != logline {
   482  		t.Errorf("Expected message to be %s but was %s", logline, *argument.LogEvents[0].Message)
   483  	}
   484  }
   485  
   486  func TestCollectBatchLineSplit(t *testing.T) {
   487  	mockClient := newMockClient()
   488  	stream := &logStream{
   489  		client:        mockClient,
   490  		logGroupName:  groupName,
   491  		logStreamName: streamName,
   492  		sequenceToken: aws.String(sequenceToken),
   493  		messages:      make(chan *logger.Message),
   494  	}
   495  	mockClient.putLogEventsResult <- &putLogEventsResult{
   496  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   497  			NextSequenceToken: aws.String(nextSequenceToken),
   498  		},
   499  	}
   500  	var ticks = make(chan time.Time)
   501  	newTicker = func(_ time.Duration) *time.Ticker {
   502  		return &time.Ticker{
   503  			C: ticks,
   504  		}
   505  	}
   506  
   507  	go stream.collectBatch()
   508  
   509  	longline := strings.Repeat("A", maximumBytesPerEvent)
   510  	stream.Log(&logger.Message{
   511  		Line:      []byte(longline + "B"),
   512  		Timestamp: time.Time{},
   513  	})
   514  
   515  	// no ticks
   516  	stream.Close()
   517  
   518  	argument := <-mockClient.putLogEventsArgument
   519  	if argument == nil {
   520  		t.Fatal("Expected non-nil PutLogEventsInput")
   521  	}
   522  	if len(argument.LogEvents) != 2 {
   523  		t.Errorf("Expected LogEvents to contain 2 elements, but contains %d", len(argument.LogEvents))
   524  	}
   525  	if *argument.LogEvents[0].Message != longline {
   526  		t.Errorf("Expected message to be %s but was %s", longline, *argument.LogEvents[0].Message)
   527  	}
   528  	if *argument.LogEvents[1].Message != "B" {
   529  		t.Errorf("Expected message to be %s but was %s", "B", *argument.LogEvents[1].Message)
   530  	}
   531  }
   532  
   533  func TestCollectBatchMaxEvents(t *testing.T) {
   534  	mockClient := newMockClientBuffered(1)
   535  	stream := &logStream{
   536  		client:        mockClient,
   537  		logGroupName:  groupName,
   538  		logStreamName: streamName,
   539  		sequenceToken: aws.String(sequenceToken),
   540  		messages:      make(chan *logger.Message),
   541  	}
   542  	mockClient.putLogEventsResult <- &putLogEventsResult{
   543  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   544  			NextSequenceToken: aws.String(nextSequenceToken),
   545  		},
   546  	}
   547  	var ticks = make(chan time.Time)
   548  	newTicker = func(_ time.Duration) *time.Ticker {
   549  		return &time.Ticker{
   550  			C: ticks,
   551  		}
   552  	}
   553  
   554  	go stream.collectBatch()
   555  
   556  	line := "A"
   557  	for i := 0; i <= maximumLogEventsPerPut; i++ {
   558  		stream.Log(&logger.Message{
   559  			Line:      []byte(line),
   560  			Timestamp: time.Time{},
   561  		})
   562  	}
   563  
   564  	// no ticks
   565  	stream.Close()
   566  
   567  	argument := <-mockClient.putLogEventsArgument
   568  	if argument == nil {
   569  		t.Fatal("Expected non-nil PutLogEventsInput")
   570  	}
   571  	if len(argument.LogEvents) != maximumLogEventsPerPut {
   572  		t.Errorf("Expected LogEvents to contain %d elements, but contains %d", maximumLogEventsPerPut, len(argument.LogEvents))
   573  	}
   574  
   575  	argument = <-mockClient.putLogEventsArgument
   576  	if argument == nil {
   577  		t.Fatal("Expected non-nil PutLogEventsInput")
   578  	}
   579  	if len(argument.LogEvents) != 1 {
   580  		t.Errorf("Expected LogEvents to contain %d elements, but contains %d", 1, len(argument.LogEvents))
   581  	}
   582  }
   583  
   584  func TestCollectBatchMaxTotalBytes(t *testing.T) {
   585  	mockClient := newMockClientBuffered(1)
   586  	stream := &logStream{
   587  		client:        mockClient,
   588  		logGroupName:  groupName,
   589  		logStreamName: streamName,
   590  		sequenceToken: aws.String(sequenceToken),
   591  		messages:      make(chan *logger.Message),
   592  	}
   593  	mockClient.putLogEventsResult <- &putLogEventsResult{
   594  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   595  			NextSequenceToken: aws.String(nextSequenceToken),
   596  		},
   597  	}
   598  	var ticks = make(chan time.Time)
   599  	newTicker = func(_ time.Duration) *time.Ticker {
   600  		return &time.Ticker{
   601  			C: ticks,
   602  		}
   603  	}
   604  
   605  	go stream.collectBatch()
   606  
   607  	longline := strings.Repeat("A", maximumBytesPerPut)
   608  	stream.Log(&logger.Message{
   609  		Line:      []byte(longline + "B"),
   610  		Timestamp: time.Time{},
   611  	})
   612  
   613  	// no ticks
   614  	stream.Close()
   615  
   616  	argument := <-mockClient.putLogEventsArgument
   617  	if argument == nil {
   618  		t.Fatal("Expected non-nil PutLogEventsInput")
   619  	}
   620  	bytes := 0
   621  	for _, event := range argument.LogEvents {
   622  		bytes += len(*event.Message)
   623  	}
   624  	if bytes > maximumBytesPerPut {
   625  		t.Errorf("Expected <= %d bytes but was %d", maximumBytesPerPut, bytes)
   626  	}
   627  
   628  	argument = <-mockClient.putLogEventsArgument
   629  	if len(argument.LogEvents) != 1 {
   630  		t.Errorf("Expected LogEvents to contain 1 elements, but contains %d", len(argument.LogEvents))
   631  	}
   632  	message := *argument.LogEvents[0].Message
   633  	if message[len(message)-1:] != "B" {
   634  		t.Errorf("Expected message to be %s but was %s", "B", message[len(message)-1:])
   635  	}
   636  }
   637  
   638  func TestCollectBatchWithDuplicateTimestamps(t *testing.T) {
   639  	mockClient := newMockClient()
   640  	stream := &logStream{
   641  		client:        mockClient,
   642  		logGroupName:  groupName,
   643  		logStreamName: streamName,
   644  		sequenceToken: aws.String(sequenceToken),
   645  		messages:      make(chan *logger.Message),
   646  	}
   647  	mockClient.putLogEventsResult <- &putLogEventsResult{
   648  		successResult: &cloudwatchlogs.PutLogEventsOutput{
   649  			NextSequenceToken: aws.String(nextSequenceToken),
   650  		},
   651  	}
   652  	ticks := make(chan time.Time)
   653  	newTicker = func(_ time.Duration) *time.Ticker {
   654  		return &time.Ticker{
   655  			C: ticks,
   656  		}
   657  	}
   658  
   659  	go stream.collectBatch()
   660  
   661  	times := maximumLogEventsPerPut
   662  	expectedEvents := []*cloudwatchlogs.InputLogEvent{}
   663  	timestamp := time.Now()
   664  	for i := 0; i < times; i++ {
   665  		line := fmt.Sprintf("%d", i)
   666  		if i%2 == 0 {
   667  			timestamp.Add(1 * time.Nanosecond)
   668  		}
   669  		stream.Log(&logger.Message{
   670  			Line:      []byte(line),
   671  			Timestamp: timestamp,
   672  		})
   673  		expectedEvents = append(expectedEvents, &cloudwatchlogs.InputLogEvent{
   674  			Message:   aws.String(line),
   675  			Timestamp: aws.Int64(timestamp.UnixNano() / int64(time.Millisecond)),
   676  		})
   677  	}
   678  
   679  	ticks <- time.Time{}
   680  	stream.Close()
   681  
   682  	argument := <-mockClient.putLogEventsArgument
   683  	if argument == nil {
   684  		t.Fatal("Expected non-nil PutLogEventsInput")
   685  	}
   686  	if len(argument.LogEvents) != times {
   687  		t.Errorf("Expected LogEvents to contain %d elements, but contains %d", times, len(argument.LogEvents))
   688  	}
   689  	for i := 0; i < times; i++ {
   690  		if !reflect.DeepEqual(*argument.LogEvents[i], *expectedEvents[i]) {
   691  			t.Errorf("Expected event to be %v but was %v", *expectedEvents[i], *argument.LogEvents[i])
   692  		}
   693  	}
   694  }
   695  
   696  func TestCreateTagSuccess(t *testing.T) {
   697  	mockClient := newMockClient()
   698  	ctx := logger.Context{
   699  		ContainerName: "/test-container",
   700  		ContainerID:   "container-abcdefghijklmnopqrstuvwxyz01234567890",
   701  		Config:        map[string]string{"tag": "{{.Name}}/{{.FullID}}"},
   702  	}
   703  	logStreamName, e := loggerutils.ParseLogTag(ctx, loggerutils.DefaultTemplate)
   704  	if e != nil {
   705  		t.Errorf("Error generating tag: %q", e)
   706  	}
   707  	stream := &logStream{
   708  		client:        mockClient,
   709  		logGroupName:  groupName,
   710  		logStreamName: logStreamName,
   711  	}
   712  	mockClient.createLogStreamResult <- &createLogStreamResult{}
   713  
   714  	err := stream.create()
   715  
   716  	if err != nil {
   717  		t.Errorf("Received unexpected err: %v\n", err)
   718  	}
   719  	argument := <-mockClient.createLogStreamArgument
   720  
   721  	if *argument.LogStreamName != "test-container/container-abcdefghijklmnopqrstuvwxyz01234567890" {
   722  		t.Errorf("Expected LogStreamName to be %s", "test-container/container-abcdefghijklmnopqrstuvwxyz01234567890")
   723  	}
   724  }