// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, // either express or implied. See the License for the specific language governing // permissions and limitations under the License. package iohandler import ( "fmt" "strings" "sync" "testing" "time" "github.com/aws/amazon-ssm-agent/agent/contracts" iomodulemock "github.com/aws/amazon-ssm-agent/agent/framework/processor/executer/iohandler/iomodule/mock" multiwritermock "github.com/aws/amazon-ssm-agent/agent/framework/processor/executer/iohandler/multiwriter/mock" "github.com/aws/amazon-ssm-agent/agent/mocks/context" "github.com/aws/amazon-ssm-agent/agent/mocks/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) type truncateOutputTest struct { stdout string stderr string capacity int expected string } const ( sampleSize = 100 longMessage = `This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. This is a sample text. 1234567890. This is a sample text. This is a sample text` ) var testData = []truncateOutputTest{ //{stdout, stderr, capacity, expected} {"", "", sampleSize, ""}, {"sample output", "", sampleSize, "sample output"}, {"", "sample error", sampleSize, "\n----------ERROR-------\nsample error"}, {"sample output", "sample error", sampleSize, "sample output\n----------ERROR-------\nsample error"}, {longMessage, "", sampleSize, "This is a sample text. This is a sample text. This is a sample text. This is \n---Output truncated---"}, {"", longMessage, sampleSize, "\n----------ERROR-------\nThis is a sample text. This is a sample text. This is\n---Error truncated----"}, {longMessage, longMessage, sampleSize, "This is a sampl\n---Output truncated---\n----------ERROR-------\nThis is a sampl\n---Error truncated----"}, { strings.Repeat("o", ((sampleSize - len(errTitle)) / 2)), //stdout strings.Repeat("e", sampleSize*3), //stderr sampleSize, //capacity "oooooooooooooooooooooooooooooooooooooo\n----------ERROR-------\neeeeeeeeeeeeeee\n---Error truncated----", //expected }, } func TestTruncateOutput(t *testing.T) { for i, test := range testData { actual := TruncateOutput(test.stdout, test.stderr, test.capacity) assert.Equal(t, test.expected, actual, "failed test case: %v", i) } } var logger = log.NewMockLog() func TestRegisterOutputSource(t *testing.T) { mockDocumentIOMultiWriter := new(multiwritermock.MockDocumentIOMultiWriter) mockContext := context.NewMockDefault() mockDocumentIOMultiWriter.On("AddWriter", mock.Anything).Times(2) wg := new(sync.WaitGroup) mockDocumentIOMultiWriter.On("GetWaitGroup").Return(wg) // Add 2 to WaitGroup to simulate two AddWriter calls wg.Add(2) // Create multiple test IOModules testModule1 := new(iomodulemock.MockIOModule) testModule1.On("Read", mockContext, mock.Anything, mock.AnythingOfType("int")).Return() testModule2 := new(iomodulemock.MockIOModule) testModule2.On("Read", mockContext, mock.Anything, mock.AnythingOfType("int")).Return() output := NewDefaultIOHandler(mockContext, contracts.IOConfiguration{}) output.RegisterOutputSource(mockDocumentIOMultiWriter, testModule1, testModule2) // Sleep a bit to allow threads to finish in RegisterOutputSource to check WaitGroup time.Sleep(250 * time.Millisecond) } func TestSucceeded(t *testing.T) { output := DefaultIOHandler{} output.MarkAsSucceeded() assert.Equal(t, output.ExitCode, 0) assert.Equal(t, output.Status, contracts.ResultStatusSuccess) assert.True(t, output.Status.IsSuccess()) assert.False(t, output.Status.IsReboot()) } func TestFailed(t *testing.T) { output := DefaultIOHandler{} output.MarkAsFailed(fmt.Errorf("Error message")) assert.Equal(t, output.ExitCode, 1) assert.Equal(t, output.Status, contracts.ResultStatusFailed) assert.Contains(t, output.GetStderr(), "Error message") assert.False(t, output.Status.IsSuccess()) assert.False(t, output.Status.IsReboot()) } func TestMarkAsInProgress(t *testing.T) { output := DefaultIOHandler{} output.MarkAsInProgress() assert.Equal(t, output.ExitCode, 0) assert.Equal(t, output.Status, contracts.ResultStatusInProgress) assert.False(t, output.Status.IsSuccess()) assert.False(t, output.Status.IsReboot()) } func TestMarkAsSuccessWithReboot(t *testing.T) { output := DefaultIOHandler{} output.MarkAsSuccessWithReboot() assert.Equal(t, output.ExitCode, 0) assert.Equal(t, output.Status, contracts.ResultStatusSuccessAndReboot) assert.True(t, output.Status.IsSuccess()) assert.True(t, output.Status.IsReboot()) } func TestAppendInfo(t *testing.T) { output := DefaultIOHandler{} output.AppendInfo("Info message") output.AppendInfo("Second entry") assert.Contains(t, output.GetStdout(), "Info message") assert.Contains(t, output.GetStdout(), "Second entry") } func TestAppendSpecialChars(t *testing.T) { output := DefaultIOHandler{} var testString = "%v`~!@#$%^&*()-_=+[{]}|\\;:'\",<.>/?" output.AppendInfo(testString) output.AppendError(testString) assert.Contains(t, output.GetStdout(), testString) assert.Contains(t, output.GetStderr(), testString) } func TestAppendFormat(t *testing.T) { output := DefaultIOHandler{} var testString = "%v`~!@#$%^&*()-_=+[{]}|\\;:'\",<.>/?%%" // The first % is a %v - a variable to be replaced and we provided a value for it. // The second % isn't escaped and is treated as a fmt parameter, but no value is provided for it. // The double %% is an escaped single literal %. var testStringFormatted = "foo`~!@#$%!^(MISSING)&*()-_=+[{]}|\\;:'\",<.>/?%" output.AppendInfof(testString, "foo") output.AppendErrorf(testString, "foo") assert.Contains(t, output.GetStdout(), testStringFormatted) assert.Contains(t, output.GetStderr(), testStringFormatted) }