Skip to content

Commit

Permalink
log: Add shared Spy implementation (#692)
Browse files Browse the repository at this point in the history
This consolidates the multiple Spy fx.Printer implementations into one.
The previous implementations operated by wrapping a Buffer and accessing
the result with `String()`. The methods used by us were,

    Reset()
    String() string

The new Spy implementation exposes both these methods, as well as a
`Messages() []string` method which exposes all logged messages
separately, which allows for a more accurate way to verify the logged
entries.
  • Loading branch information
abhinav authored Jan 5, 2020
1 parent 1488d1d commit 3f21605
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 29 deletions.
18 changes: 5 additions & 13 deletions app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,13 @@ import (

. "go.uber.org/fx"
"go.uber.org/fx/fxtest"
"go.uber.org/fx/internal/fxlog"
"go.uber.org/multierr"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type printerSpy struct {
*bytes.Buffer
}

func (ps printerSpy) Printf(format string, args ...interface{}) {
fmt.Fprintf(ps.Buffer, format, args...)
ps.Buffer.WriteRune('\n')
}

func NewForTest(t testing.TB, opts ...Option) *App {
testOpts := []Option{Logger(fxtest.NewTestPrinter(t))}
opts = append(testOpts, opts...)
Expand All @@ -71,7 +63,7 @@ func TestNewApp(t *testing.T) {
// after applying other options. This prevents the app configuration
// (e.g., logging) from changing halfway through our provides.

spy := &printerSpy{&bytes.Buffer{}}
spy := new(fxlog.Spy)
app := fxtest.New(t, Provide(func() struct{} { return struct{}{} }), Logger(spy))
defer app.RequireStart().RequireStop()
assert.Contains(t, spy.String(), "PROVIDE\tstruct {}")
Expand Down Expand Up @@ -405,7 +397,7 @@ func TestOptions(t *testing.T) {
})

t.Run("Error", func(t *testing.T) {
spy := printerSpy{&bytes.Buffer{}}
spy := new(fxlog.Spy)
New(
Provide(&bytes.Buffer{}), // error, not a constructor
Logger(spy),
Expand Down Expand Up @@ -520,7 +512,7 @@ func TestAppStart(t *testing.T) {
})

t.Run("InvokeNonFunction", func(t *testing.T) {
spy := printerSpy{&bytes.Buffer{}}
spy := new(fxlog.Spy)

app := New(Logger(spy), Invoke(struct{}{}))
err := app.Err()
Expand Down Expand Up @@ -680,7 +672,7 @@ func TestDone(t *testing.T) {
}

func TestReplaceLogger(t *testing.T) {
spy := printerSpy{&bytes.Buffer{}}
spy := new(fxlog.Spy)
app := fxtest.New(t, Logger(spy))
app.RequireStart().RequireStop()
assert.Contains(t, spy.String(), "RUNNING")
Expand Down
19 changes: 3 additions & 16 deletions internal/fxlog/fxlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ package fxlog
import (
"bytes"
"errors"
"fmt"
"os"
"testing"

Expand All @@ -33,18 +32,6 @@ import (
sample "go.uber.org/fx/internal/fxlog/sample.git"
)

type spy struct {
*bytes.Buffer
}

func newSpy() *spy {
return &spy{bytes.NewBuffer(nil)}
}

func (s *spy) Printf(format string, is ...interface{}) {
fmt.Fprintln(s, fmt.Sprintf(format, is...))
}

// stubs the exit call, returns a function that restores a real exit function
// and asserts that the stub was called.
func stubExit() func(testing.TB) {
Expand All @@ -62,7 +49,7 @@ func TestNew(t *testing.T) {
}

func TestPrint(t *testing.T) {
sink := newSpy()
sink := new(Spy)
logger := &Logger{sink}

t.Run("printf", func(t *testing.T) {
Expand Down Expand Up @@ -158,14 +145,14 @@ func TestPrint(t *testing.T) {
}

func TestPanic(t *testing.T) {
sink := newSpy()
sink := new(Spy)
logger := &Logger{sink}
assert.Panics(t, func() { logger.Panic(errors.New("foo")) })
assert.Equal(t, "[Fx] foo\n", sink.String())
}

func TestFatal(t *testing.T) {
sink := newSpy()
sink := new(Spy)
logger := &Logger{sink}

undo := stubExit()
Expand Down
62 changes: 62 additions & 0 deletions internal/fxlog/spy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package fxlog

import (
"fmt"
"strings"
)

// Spy is an Fx Printer that captures logged statements. It may be used in
// tests of Fx logs.
type Spy struct {
msgs []string
}

var _ Printer = &Spy{}

// Printf logs the given message, formatting it in printf-style.
func (s *Spy) Printf(msg string, args ...interface{}) {
s.msgs = append(s.msgs, fmt.Sprintf(msg, args...))
}

// Messages returns a copy of all captured messages.
func (s *Spy) Messages() []string {
msgs := make([]string, len(s.msgs))
copy(msgs, s.msgs)
return msgs
}

// String returns all logged messages as a single newline-delimited string.
func (s *Spy) String() string {
if len(s.msgs) == 0 {
return ""
}

// trailing newline because all log entries should have a newline
// after them.
return strings.Join(s.msgs, "\n") + "\n"
}

// Reset clears all messages from the Spy.
func (s *Spy) Reset() {
s.msgs = s.msgs[:0]
}
64 changes: 64 additions & 0 deletions internal/fxlog/spy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package fxlog

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
)

func TestSpy(t *testing.T) {
var s Spy

t.Run("empty spy", func(t *testing.T) {
assert.Empty(t, s.Messages(), "messages must be empty")
assert.Empty(t, s.String(), "string must be empty")
})

s.Printf("foo bar")
t.Run("unformatted message", func(t *testing.T) {
assert.Equal(t, []string{"foo bar"}, s.Messages(), "messages must match")
assert.Equal(t, "foo bar\n", s.String(), "string must match")
})

s.Printf("something went wrong: %v", errors.New("great sadness"))
t.Run("formatted message", func(t *testing.T) {
assert.Equal(t, []string{
"foo bar",
"something went wrong: great sadness",
}, s.Messages())
assert.Equal(t, "foo bar\nsomething went wrong: great sadness\n", s.String())
})

s.Reset()
t.Run("reset", func(t *testing.T) {
assert.Empty(t, s.Messages(), "messages must be empty")
assert.Empty(t, s.String(), "string must be empty")
})

s.Printf("baz qux")
t.Run("use after reset", func(t *testing.T) {
assert.Equal(t, []string{"baz qux"}, s.Messages(), "messages must match")
assert.Equal(t, "baz qux\n", s.String(), "string must match")
})
}

0 comments on commit 3f21605

Please sign in to comment.