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