github.com/mongodb/grip@v0.0.0-20240213223901-f906268d82b9/message/composer_test.go (about)

     1  package message
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"runtime"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/mongodb/grip/level"
    11  	"github.com/pkg/errors"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func TestPopulatedMessageComposerConstructors(t *testing.T) {
    17  	const testMsg = "hello"
    18  	assert := assert.New(t)
    19  	// map objects to output
    20  	cases := map[Composer]string{
    21  		NewString(testMsg):                                                      testMsg,
    22  		NewDefaultMessage(level.Error, testMsg):                                 testMsg,
    23  		NewExtendedString(testMsg):                                              testMsg,
    24  		NewExtendedDefaultMessage(level.Error, testMsg):                         testMsg,
    25  		NewBytes([]byte(testMsg)):                                               testMsg,
    26  		NewBytesMessage(level.Error, []byte(testMsg)):                           testMsg,
    27  		NewExtendedBytes([]byte(testMsg)):                                       testMsg,
    28  		NewExtendedBytesMessage(level.Error, []byte(testMsg)):                   testMsg,
    29  		NewError(errors.New(testMsg)):                                           testMsg,
    30  		NewErrorMessage(level.Error, errors.New(testMsg)):                       testMsg,
    31  		NewExtendedError(errors.New(testMsg)):                                   testMsg,
    32  		NewExtendedErrorMessage(level.Error, errors.New(testMsg)):               testMsg,
    33  		NewErrorWrap(errors.New(testMsg), ""):                                   testMsg,
    34  		NewErrorWrapMessage(level.Error, errors.New(testMsg), ""):               testMsg,
    35  		NewFormatted(string(testMsg[0])+"%s", testMsg[1:]):                      testMsg,
    36  		NewFormattedMessage(level.Error, string(testMsg[0])+"%s", testMsg[1:]):  testMsg,
    37  		WrapError(errors.New(testMsg), ""):                                      testMsg,
    38  		WrapErrorf(errors.New(testMsg), ""):                                     testMsg,
    39  		NewLine(testMsg, ""):                                                    testMsg,
    40  		NewLineMessage(level.Error, testMsg, ""):                                testMsg,
    41  		NewLine(testMsg):                                                        testMsg,
    42  		NewLineMessage(level.Error, testMsg):                                    testMsg,
    43  		MakeGroupComposer(NewString(testMsg)):                                   testMsg,
    44  		NewGroupComposer([]Composer{NewString(testMsg)}):                        testMsg,
    45  		MakeJiraMessage(&JiraIssue{Summary: testMsg, Type: "Something"}):        testMsg,
    46  		NewJiraMessage("", testMsg, JiraField{Key: "type", Value: "Something"}): testMsg,
    47  		NewFieldsMessage(level.Error, testMsg, Fields{}):                        fmt.Sprintf("[message='%s']", testMsg),
    48  		NewFields(level.Error, Fields{"test": testMsg}):                         fmt.Sprintf("[test='%s']", testMsg),
    49  		MakeFieldsMessage(testMsg, Fields{}):                                    fmt.Sprintf("[message='%s']", testMsg),
    50  		MakeFields(Fields{"test": testMsg}):                                     fmt.Sprintf("[test='%s']", testMsg),
    51  		NewExtendedFieldsMessage(level.Error, testMsg, Fields{}):                fmt.Sprintf("[message='%s']", testMsg),
    52  		NewExtendedFields(level.Error, Fields{"test": testMsg}):                 fmt.Sprintf("[test='%s']", testMsg),
    53  		MakeExtendedFieldsMessage(testMsg, Fields{}):                            fmt.Sprintf("[message='%s']", testMsg),
    54  		MakeExtendedFields(Fields{"test": testMsg}):                             fmt.Sprintf("[test='%s']", testMsg),
    55  		NewErrorWrappedComposer(errors.New("hello"), NewString("world")):        "world: hello",
    56  		When(true, testMsg):                                                     testMsg,
    57  		Whenf(true, testMsg):                                                    testMsg,
    58  		Whenln(true, testMsg):                                                   testMsg,
    59  		NewEmailMessage(level.Error, Email{
    60  			Recipients: []string{"someone@example.com"},
    61  			Subject:    "Test msg",
    62  			Body:       testMsg,
    63  		}): fmt.Sprintf("To: someone@example.com; Body: %s", testMsg),
    64  		NewGithubStatusMessage(level.Error, "tests", GithubStateError, "https://example.com", testMsg): fmt.Sprintf("tests error: %s (https://example.com)", testMsg),
    65  		NewGithubStatusMessageWithRepo(level.Error, GithubStatus{
    66  			Owner: "mongodb",
    67  			Repo:  "grip",
    68  			Ref:   "master",
    69  
    70  			Context:     "tests",
    71  			State:       GithubStateError,
    72  			URL:         "https://example.com",
    73  			Description: testMsg,
    74  		}): fmt.Sprintf("mongodb/grip@master tests error: %s (https://example.com)", testMsg),
    75  		NewJIRACommentMessage(level.Error, "ABC-123", testMsg): testMsg,
    76  		NewSlackMessage(level.Error, "@someone", testMsg, nil): fmt.Sprintf("@someone: %s", testMsg),
    77  	}
    78  
    79  	for msg, output := range cases {
    80  		assert.NotNil(msg)
    81  		assert.NotEmpty(output)
    82  		assert.Implements((*Composer)(nil), msg)
    83  		assert.True(msg.Loggable())
    84  		assert.NotNil(msg.Raw())
    85  
    86  		if strings.HasPrefix(output, "[") {
    87  			output = strings.Trim(output, "[]")
    88  			assert.True(strings.Contains(msg.String(), output), fmt.Sprintf("%T: %s (%s)", msg, msg.String(), output))
    89  
    90  		} else {
    91  			// run the string test to make sure it doesn't change:
    92  			assert.Equal(msg.String(), output, "%T", msg)
    93  			assert.Equal(msg.String(), output, "%T", msg)
    94  		}
    95  
    96  		if msg.Priority() != level.Invalid {
    97  			assert.Equal(msg.Priority(), level.Error)
    98  		}
    99  
   100  		// check message annotation functionality
   101  		switch msg.(type) {
   102  		case *GroupComposer:
   103  			continue
   104  		case *slackMessage:
   105  			continue
   106  		default:
   107  			assert.NoError(msg.Annotate("k1", "foo"), "%T", msg)
   108  			assert.Error(msg.Annotate("k1", "foo"), "%T", msg)
   109  			assert.NoError(msg.Annotate("k2", "foo"), "%T", msg)
   110  		}
   111  	}
   112  }
   113  
   114  func TestUnpopulatedMessageComposers(t *testing.T) {
   115  	assert := assert.New(t)
   116  	// map objects to output
   117  	cases := []Composer{
   118  		&stringMessage{},
   119  		NewString(""),
   120  		NewDefaultMessage(level.Error, ""),
   121  		&bytesMessage{},
   122  		NewBytes([]byte{}),
   123  		NewBytesMessage(level.Error, []byte{}),
   124  		&ProcessInfo{},
   125  		&SystemInfo{},
   126  		&lineMessenger{},
   127  		NewLine(),
   128  		NewLineMessage(level.Error),
   129  		&formatMessenger{},
   130  		NewFormatted(""),
   131  		NewFormattedMessage(level.Error, ""),
   132  		NewStack(1, ""),
   133  		NewStackLines(1),
   134  		NewStackFormatted(1, ""),
   135  		MakeGroupComposer(),
   136  		&GroupComposer{},
   137  		&GoRuntimeInfo{},
   138  		When(false, ""),
   139  		Whenf(false, "", ""),
   140  		Whenln(false, "", ""),
   141  		NewEmailMessage(level.Error, Email{}),
   142  		NewGithubStatusMessage(level.Error, "", GithubState(""), "", ""),
   143  		NewGithubStatusMessageWithRepo(level.Error, GithubStatus{}),
   144  		NewJIRACommentMessage(level.Error, "", ""),
   145  		NewSlackMessage(level.Error, "", "", nil),
   146  	}
   147  
   148  	for idx, msg := range cases {
   149  		assert.False(msg.Loggable(), "%d:%T", idx, msg)
   150  	}
   151  }
   152  
   153  func TestDataCollecterComposerConstructors(t *testing.T) {
   154  	const testMsg = "hello"
   155  	// map objects to output (prefix)
   156  
   157  	t.Run("Single", func(t *testing.T) {
   158  		for _, test := range []struct {
   159  			Name       string
   160  			Msg        Composer
   161  			Expected   string
   162  			ShouldSkip bool
   163  		}{
   164  			{
   165  				Name: "ProcessInfoCurrentProc",
   166  				Msg:  NewProcessInfo(level.Error, int32(os.Getpid()), testMsg),
   167  			},
   168  			{
   169  				Name:     "NewSystemInfo",
   170  				Msg:      NewSystemInfo(level.Error, testMsg),
   171  				Expected: testMsg,
   172  			},
   173  
   174  			{
   175  				Name:     "MakeSystemInfo",
   176  				Msg:      MakeSystemInfo(testMsg),
   177  				Expected: testMsg,
   178  			},
   179  			{
   180  				Name:       "CollectProcInfoPidOne",
   181  				Msg:        CollectProcessInfo(int32(1)),
   182  				ShouldSkip: runtime.GOOS == "windows",
   183  			},
   184  			{
   185  				Name: "CollectProcInfoSelf",
   186  				Msg:  CollectProcessInfoSelf(),
   187  			},
   188  			{
   189  				Name: "CollectSystemInfo",
   190  				Msg:  CollectSystemInfo(),
   191  			},
   192  			{
   193  				Name: "CollectBasicGoStats",
   194  				Msg:  CollectBasicGoStats(),
   195  			},
   196  			{
   197  				Name: "CollectGoStatsDeltas",
   198  				Msg:  CollectGoStatsDeltas(),
   199  			},
   200  			{
   201  				Name: "CollectGoStatsRates",
   202  				Msg:  CollectGoStatsRates(),
   203  			},
   204  			{
   205  				Name: "CollectGoStatsTotals",
   206  				Msg:  CollectGoStatsTotals(),
   207  			},
   208  			{
   209  				Name:     "MakeGoStatsDelta",
   210  				Msg:      MakeGoStatsDeltas(testMsg),
   211  				Expected: testMsg,
   212  			},
   213  			{
   214  				Name:     "MakeGoStatsRates",
   215  				Msg:      MakeGoStatsRates(testMsg),
   216  				Expected: testMsg,
   217  			},
   218  			{
   219  				Name:     "MakeGoStatsTotals",
   220  				Msg:      MakeGoStatsTotals(testMsg),
   221  				Expected: testMsg,
   222  			},
   223  			{
   224  				Name:     "NewGoStatsDeltas",
   225  				Msg:      NewGoStatsDeltas(level.Error, testMsg),
   226  				Expected: testMsg,
   227  			},
   228  			{
   229  				Name:     "NewGoStatsRates",
   230  				Msg:      NewGoStatsRates(level.Error, testMsg),
   231  				Expected: testMsg,
   232  			},
   233  			{
   234  				Name:     "NewGoStatsTotals",
   235  				Msg:      NewGoStatsTotals(level.Error, testMsg),
   236  				Expected: testMsg,
   237  			},
   238  		} {
   239  			if test.ShouldSkip {
   240  				continue
   241  			}
   242  			t.Run(test.Name, func(t *testing.T) {
   243  				assert.NotNil(t, test.Msg)
   244  				assert.NotNil(t, test.Msg.Raw())
   245  				assert.Implements(t, (*Composer)(nil), test.Msg)
   246  				assert.True(t, test.Msg.Loggable())
   247  				assert.True(t, strings.HasPrefix(test.Msg.String(), test.Expected), "%T: %s", test.Msg, test.Msg)
   248  			})
   249  		}
   250  	})
   251  
   252  	t.Run("Multi", func(t *testing.T) {
   253  		for _, test := range []struct {
   254  			Name       string
   255  			Group      []Composer
   256  			ShouldSkip bool
   257  		}{
   258  			{
   259  				Name:  "SelfWithChildren",
   260  				Group: CollectProcessInfoSelfWithChildren(),
   261  			},
   262  			{
   263  				Name:       "PidOneWithChildren",
   264  				Group:      CollectProcessInfoWithChildren(int32(1)),
   265  				ShouldSkip: runtime.GOOS == "windows",
   266  			},
   267  			{
   268  				Name:  "AllProcesses",
   269  				Group: CollectAllProcesses(),
   270  			},
   271  		} {
   272  			if test.ShouldSkip {
   273  				continue
   274  			}
   275  			t.Run(test.Name, func(t *testing.T) {
   276  				require.True(t, len(test.Group) >= 1)
   277  				for _, msg := range test.Group {
   278  					assert.NotNil(t, msg)
   279  					assert.Implements(t, (*Composer)(nil), msg)
   280  					assert.NotEqual(t, "", msg.String())
   281  					assert.True(t, msg.Loggable())
   282  				}
   283  			})
   284  
   285  		}
   286  	})
   287  }
   288  
   289  func TestStackMessages(t *testing.T) {
   290  	const testMsg = "hello"
   291  	var stackMsg = "message/composer_test"
   292  
   293  	assert := assert.New(t)
   294  	// map objects to output (prefix)
   295  	cases := map[Composer]string{
   296  		NewStack(1, testMsg):                testMsg,
   297  		NewStackLines(1, testMsg):           testMsg,
   298  		NewStackLines(1):                    "",
   299  		NewStackFormatted(1, "%s", testMsg): testMsg,
   300  		NewStackFormatted(1, string(testMsg[0])+"%s", testMsg[1:]): testMsg,
   301  
   302  		// with 0 frame
   303  		NewStack(0, testMsg):                testMsg,
   304  		NewStackLines(0, testMsg):           testMsg,
   305  		NewStackLines(0):                    "",
   306  		NewStackFormatted(0, "%s", testMsg): testMsg,
   307  		NewStackFormatted(0, string(testMsg[0])+"%s", testMsg[1:]): testMsg,
   308  	}
   309  
   310  	for msg, text := range cases {
   311  		assert.NotNil(msg)
   312  		assert.Implements((*Composer)(nil), msg)
   313  		assert.NotNil(msg.Raw())
   314  		if text != "" {
   315  			assert.True(msg.Loggable())
   316  		}
   317  
   318  		diagMsg := fmt.Sprintf("%T: %+v", msg, msg)
   319  		assert.True(strings.Contains(msg.String(), text), diagMsg)
   320  		assert.True(strings.Contains(msg.String(), stackMsg), diagMsg)
   321  	}
   322  }
   323  
   324  func TestComposerConverter(t *testing.T) {
   325  	const testMsg = "hello world"
   326  	assert := assert.New(t)
   327  
   328  	cases := []interface{}{
   329  		NewLine(testMsg),
   330  		testMsg,
   331  		errors.New(testMsg),
   332  		[]string{testMsg},
   333  		[]interface{}{testMsg},
   334  		[]byte(testMsg),
   335  		[]Composer{NewString(testMsg)},
   336  	}
   337  
   338  	for _, msg := range cases {
   339  		comp := ConvertToComposer(level.Error, msg)
   340  		assert.True(comp.Loggable())
   341  		assert.Equal(testMsg, comp.String(), "%T", msg)
   342  	}
   343  
   344  	cases = []interface{}{
   345  		nil,
   346  		"",
   347  		[]interface{}{},
   348  		[]string{},
   349  		[]byte{},
   350  		Fields{},
   351  		map[string]interface{}{},
   352  	}
   353  
   354  	for _, msg := range cases {
   355  		comp := ConvertToComposer(level.Error, msg)
   356  		assert.False(comp.Loggable())
   357  		assert.Equal("", comp.String(), "%T", msg)
   358  	}
   359  
   360  	outputCases := map[string]interface{}{
   361  		"1":            1,
   362  		"2":            int32(2),
   363  		"[message='3'": Fields{"message": 3},
   364  		"[message='4'": map[string]interface{}{"message": "4"},
   365  	}
   366  
   367  	for out, in := range outputCases {
   368  		comp := ConvertToComposer(level.Error, in)
   369  		assert.True(comp.Loggable())
   370  		assert.True(strings.HasPrefix(comp.String(), out))
   371  	}
   372  }
   373  
   374  func TestJiraMessageComposerConstructor(t *testing.T) {
   375  	const testMsg = "hello"
   376  	assert := assert.New(t)
   377  	reporterField := JiraField{Key: "Reporter", Value: "Annie"}
   378  	assigneeField := JiraField{Key: "Assignee", Value: "Sejin"}
   379  	typeField := JiraField{Key: "Type", Value: "Bug"}
   380  	labelsField := JiraField{Key: "Labels", Value: []string{"Soul", "Pop"}}
   381  	unknownField := JiraField{Key: "Artist", Value: "Adele"}
   382  	msg := NewJiraMessage("project", testMsg, reporterField, assigneeField, typeField, labelsField, unknownField)
   383  	issue := msg.Raw().(*JiraIssue)
   384  
   385  	assert.Equal(issue.Project, "project")
   386  	assert.Equal(issue.Summary, testMsg)
   387  	assert.Equal(issue.Reporter, reporterField.Value)
   388  	assert.Equal(issue.Assignee, assigneeField.Value)
   389  	assert.Equal(issue.Type, typeField.Value)
   390  	assert.Equal(issue.Labels, labelsField.Value)
   391  	assert.Equal(issue.Fields[unknownField.Key], unknownField.Value)
   392  }
   393  
   394  func TestProcessTreeDoesNotHaveDuplicates(t *testing.T) {
   395  	assert := assert.New(t)
   396  
   397  	procs := CollectProcessInfoWithChildren(1)
   398  	seen := make(map[int32]struct{})
   399  
   400  	for _, p := range procs {
   401  		pinfo, ok := p.(*ProcessInfo)
   402  		assert.True(ok)
   403  		seen[pinfo.Pid] = struct{}{}
   404  	}
   405  
   406  	assert.Equal(len(seen), len(procs))
   407  }
   408  
   409  func TestJiraIssueAnnotationOnlySupportsStrings(t *testing.T) {
   410  	assert := assert.New(t)
   411  
   412  	m := &jiraMessage{
   413  		issue: &JiraIssue{},
   414  	}
   415  
   416  	assert.Error(m.Annotate("k", 1))
   417  	assert.Error(m.Annotate("k", true))
   418  	assert.Error(m.Annotate("k", nil))
   419  }
   420  
   421  type causer interface {
   422  	Cause() error
   423  }
   424  
   425  func TestErrorComposers(t *testing.T) {
   426  	for name, cmp := range map[string]ErrorComposer{
   427  		"Wrapped": WrapError(errors.New("err"), "msg"),
   428  		"Plain":   NewError(errors.New("err")),
   429  	} {
   430  		t.Run(name, func(t *testing.T) {
   431  			t.Run("Interfaces", func(t *testing.T) {
   432  				assert.Implements(t, (*error)(nil), cmp)
   433  				assert.Implements(t, (*causer)(nil), cmp)
   434  			})
   435  			t.Run("Value", func(t *testing.T) {
   436  				assert.Equal(t, cmp.Error(), cmp.String())
   437  			})
   438  			t.Run("Causer", func(t *testing.T) {
   439  				cause := errors.Cause(cmp)
   440  				assert.NotEqual(t, cause, cmp)
   441  			})
   442  			t.Run("ExtendedFormat", func(t *testing.T) {
   443  				assert.NotEqual(t, fmt.Sprintf("%+v", cmp), fmt.Sprintf("%v", cmp))
   444  			})
   445  		})
   446  	}
   447  }