initial code
This commit is contained in:
+378
@@ -0,0 +1,378 @@
|
||||
// Package lumberjack provides a rolling logger.
|
||||
//
|
||||
// Lumberjack is intended to be one part of a logging infrastructure.
|
||||
// It is not an all-in-one solution, but instead is a pluggable
|
||||
// component at the bottom of the logging stack that simply controls the files
|
||||
// to which logs are written.
|
||||
//
|
||||
// Lumberjack plays well with any logger that can write to an io.Writer,
|
||||
// including the standard library's log package.
|
||||
//
|
||||
// For example, to use lumberjack with the std lib's log package, just pass it
|
||||
// into the SetOutput function when your application starts:
|
||||
//
|
||||
// log.SetOutput(&lumberjack.Logger{
|
||||
// Dir: "/var/log/myapp/"
|
||||
// NameFormat: time.RFC822+".log",
|
||||
// MaxSize: lumberjack.Gigabyte,
|
||||
// Backups: 3,
|
||||
// MaxAge: lumberjack.Week * 4,
|
||||
// ))
|
||||
//
|
||||
// Note that lumberjack assumes whatever is writing to it will use locks to
|
||||
// prevent concurrent writes. Lumberjack does not implement its own lock.
|
||||
package lumberjack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// Some helper constants to make your declarations easier to read.
|
||||
Megabyte = 1024 * 1024
|
||||
Gigabyte = 1024 * Megabyte
|
||||
|
||||
// note that lumberjack days and weeks may not exactly conform to calendar
|
||||
// days and weeks due to daylight savings, leap seconds, etc.
|
||||
Day = 24 * time.Hour
|
||||
Week = 7 * Day
|
||||
|
||||
defaultNameFormat = "2006-01-02T15-04-05.000.log"
|
||||
defaultMaxSize = 100 * Megabyte
|
||||
)
|
||||
|
||||
// ensure we always implement io.WriteCloser
|
||||
var _ io.WriteCloser = &Logger{}
|
||||
|
||||
// Logger is an io.WriteCloser that writes to a log file in the given directory
|
||||
// with the given NameFormat. NameFormat should include a time formatting
|
||||
// layout in it that produces a valid filename for the OS. For more about time
|
||||
// formatting layouts, read http://golang.org/pkg/time/#pkg-constants.
|
||||
//
|
||||
// Logger opens or creates the logfile on first Write. If the most recently
|
||||
// modified file in the log file directory that matches the NameFormat is less
|
||||
// than MaxSize, that file will be appended to. If no file such exists, a new
|
||||
// file is created using the current time to generate the filename.
|
||||
//
|
||||
// Whenever a write would cause the current log file exceed MaxSize, a new file
|
||||
// is created using the current time.
|
||||
//
|
||||
// Cleaning Up Old Log Files
|
||||
//
|
||||
// Whenever a new file gets created, old log files may be deleted. The log file
|
||||
// directory is scanned for files that match NameFormat. The most recently
|
||||
// modified files which are newer than MaxAge (up to a number of files equal to
|
||||
// Backups) are retained, all other log files are deleted.
|
||||
//
|
||||
// Defaults
|
||||
//
|
||||
// If Dir is empty, the files will be created in os.TempDir().
|
||||
//
|
||||
// If NameFormat is empty, will be used as the
|
||||
// name format.
|
||||
//
|
||||
// If MaxSize is 0, 100 megabytes will be used as the max size.
|
||||
//
|
||||
// if MaxAge is 0, last modification time will not be used to delete old log
|
||||
// files.
|
||||
//
|
||||
// If Backups is 0, there's no limit to the number of old log files that will be
|
||||
// retained, as long as they're newer than MaxAge.
|
||||
//
|
||||
// If MaxAge and Backups are both 0, no old log files will be deteled.
|
||||
//
|
||||
// Thus, an default lumberjack.Logger struct will log to os.TempDir() with a 100
|
||||
// megabyte max size and never delete old log files.
|
||||
type Logger struct {
|
||||
// Dir determines the directory in which to store log files.
|
||||
// It defaults to os.TempDir() if empty.
|
||||
Dir string
|
||||
|
||||
// NameFormat is the time formatting layout used to generate filenames.
|
||||
// It defaults to "2006-01-02T15-04-05.000.log".
|
||||
NameFormat string
|
||||
|
||||
// MaxSize is the maximum size in bytes of the log file before it gets
|
||||
// rolled. It defaults to 100 megabytes.
|
||||
MaxSize int64
|
||||
|
||||
// MaxAge is the maximum time to retain old log files. The default is not
|
||||
// to remove old log files based on age.
|
||||
MaxAge time.Duration
|
||||
|
||||
// Backups is the maximum number of old log files to retain. The default is
|
||||
// to retain all old log files (though MaxAge may still cause them to get
|
||||
// deleted.)
|
||||
Backups int
|
||||
|
||||
// LocalTime determines if the time used for formatting the filename is the
|
||||
// computer's local time. The default is to use UTC time.
|
||||
LocalTime bool
|
||||
|
||||
size int64
|
||||
file *os.File
|
||||
}
|
||||
|
||||
// currentTime is only used for testing. Normally it's the time.Now() function.
|
||||
var currentTime = time.Now
|
||||
|
||||
// Write implements io.Writer. If a write would cause the log file to be larger
|
||||
// than MaxSize, a new log file is created using the current time formatted with
|
||||
// PathFormat. If the length of the write is greater than MaxSize, an error is
|
||||
// returned that satisfies IsWriteTooLong.
|
||||
func (l *Logger) Write(p []byte) (n int, err error) {
|
||||
writeLen := int64(len(p))
|
||||
if writeLen > l.max() {
|
||||
return 0, writeTooLongError{fmt.Errorf(
|
||||
"write length %d exceeds maximum file size %d", writeLen, l.max(),
|
||||
)}
|
||||
}
|
||||
f := l.file
|
||||
rotate := l.size+writeLen > l.max()
|
||||
if f == nil {
|
||||
if f, err = l.openExistingOrNew(len(p)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
} else if rotate {
|
||||
if f, err = l.openNew(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
n, err = f.Write(p)
|
||||
l.size += int64(n)
|
||||
|
||||
if rotate {
|
||||
if err := l.cleanup(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if l.file != nil && rotate {
|
||||
l.file.Close()
|
||||
}
|
||||
l.file = f
|
||||
return n, err
|
||||
}
|
||||
|
||||
// openNew opens a new log file for writing.
|
||||
func (l *Logger) openNew() (*os.File, error) {
|
||||
err := os.MkdirAll(l.dir(), 0744)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't make directories for new logfile: %s", err)
|
||||
}
|
||||
filename := l.genFilename()
|
||||
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't open new logfile: %s", err)
|
||||
}
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
// can't really do anything if close fails here
|
||||
_ = f.Close()
|
||||
return nil, fmt.Errorf("can't get size of new logfile: %s", err)
|
||||
}
|
||||
l.size = info.Size()
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// openExistingOrNew opens the most recently modified logfile in the log
|
||||
// directory, if the current write would not put it over MaxSize. If there is
|
||||
// no such file or the write would put it over the MaxSize, a new file is
|
||||
// created.
|
||||
func (l *Logger) openExistingOrNew(writeLen int) (*os.File, error) {
|
||||
files, err := ioutil.ReadDir(l.dir())
|
||||
if os.IsNotExist(err) {
|
||||
return l.openNew()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read files in log file directory: %s", err)
|
||||
}
|
||||
sort.Sort(byFormatTime{files, l.format()})
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if !l.isLogFile(f) {
|
||||
continue
|
||||
}
|
||||
|
||||
// the first file we find that matches our pattern will be the most
|
||||
// recently modified log file.
|
||||
if f.Size()+int64(writeLen) < l.max() {
|
||||
filename := filepath.Join(l.dir(), f.Name())
|
||||
file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err == nil {
|
||||
return file, nil
|
||||
}
|
||||
// if we fail to open the old log file for some reason, just ignore
|
||||
// it and open a new log file.
|
||||
}
|
||||
break
|
||||
}
|
||||
return l.openNew()
|
||||
}
|
||||
|
||||
// genFilename generates the name of the logfile from the current time.
|
||||
func (l *Logger) genFilename() string {
|
||||
t := currentTime()
|
||||
if !l.LocalTime {
|
||||
t = t.UTC()
|
||||
}
|
||||
return filepath.Join(l.dir(), t.Format(l.format()))
|
||||
}
|
||||
|
||||
// cleanup deletes old log files, keeping at most l.Backups files, as long as
|
||||
// none of them are older than MaxAge.
|
||||
func (l *Logger) cleanup() error {
|
||||
if l.Backups == 0 && l.MaxAge == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
files, err := l.oldLogFiles()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var deletes []os.FileInfo
|
||||
|
||||
if l.Backups > 0 {
|
||||
deletes = files[l.Backups:]
|
||||
files = files[:l.Backups]
|
||||
}
|
||||
|
||||
if l.MaxAge > 0 {
|
||||
cutoff := currentTime().Add(-1 * l.MaxAge)
|
||||
for _, f := range files {
|
||||
if f.ModTime().Before(cutoff) {
|
||||
deletes = append(deletes, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(deletes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
go deleteAll(l.dir(), deletes)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteAll(dir string, files []os.FileInfo) {
|
||||
// remove files on a separate goroutine
|
||||
for _, f := range files {
|
||||
// what am I going to do, log this?
|
||||
_ = os.Remove(filepath.Join(dir, f.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
// oldLogFiles returns the list of backup log files stored in the same
|
||||
// directory as the current log file, sorted by ModTime
|
||||
func (l *Logger) oldLogFiles() ([]os.FileInfo, error) {
|
||||
files, err := ioutil.ReadDir(l.dir())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read log file directory: %s", err)
|
||||
}
|
||||
logFiles := []os.FileInfo{}
|
||||
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
if filepath.Base(f.Name()) == filepath.Base(l.file.Name()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if l.isLogFile(f) {
|
||||
logFiles = append(logFiles, f)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(byFormatTime{logFiles, l.format()})
|
||||
|
||||
return logFiles, nil
|
||||
}
|
||||
|
||||
func (l *Logger) isLogFile(f os.FileInfo) bool {
|
||||
_, err := time.Parse(l.format(), filepath.Base(f.Name()))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Close implements io.Closer, and closes the current logfile.
|
||||
func (l *Logger) Close() error {
|
||||
if l.file != nil {
|
||||
err := l.file.Close()
|
||||
l.file = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Logger) max() int64 {
|
||||
if l.MaxSize == 0 {
|
||||
return defaultMaxSize
|
||||
}
|
||||
return l.MaxSize
|
||||
}
|
||||
|
||||
func (l *Logger) dir() string {
|
||||
if l.Dir != "" {
|
||||
return l.Dir
|
||||
}
|
||||
return os.TempDir()
|
||||
}
|
||||
|
||||
func (l *Logger) format() string {
|
||||
if l.NameFormat != "" {
|
||||
return l.NameFormat
|
||||
}
|
||||
return defaultNameFormat
|
||||
}
|
||||
|
||||
// byFormatTime sorts by newest time formatted in the name.
|
||||
type byFormatTime struct {
|
||||
files []os.FileInfo
|
||||
format string
|
||||
}
|
||||
|
||||
func (b byFormatTime) Less(i, j int) bool {
|
||||
return b.time(i).After(b.time(j))
|
||||
}
|
||||
|
||||
func (b byFormatTime) Swap(i, j int) {
|
||||
b.files[i], b.files[j] = b.files[j], b.files[i]
|
||||
}
|
||||
|
||||
func (b byFormatTime) Len() int {
|
||||
return len(b.files)
|
||||
}
|
||||
|
||||
func (b byFormatTime) time(i int) time.Time {
|
||||
t, err := time.Parse(b.format, filepath.Base(b.files[i].Name()))
|
||||
if err != nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// IsWriteTooLong returns whether the given error reports a write to Logger that
|
||||
// exceeds the Logger's MaxSize.
|
||||
func IsWriteTooLong(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := err.(writeTooLongError)
|
||||
return ok
|
||||
}
|
||||
|
||||
type writeTooLongError struct {
|
||||
error
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package lumberjack
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// make sure we set the format to something safe for windows, too.
|
||||
const format = "2006-01-02T15-04-05.000"
|
||||
|
||||
// this is the expected format for faketime goven the
|
||||
const timeString = "2009-11-10T13-22-33.444"
|
||||
|
||||
var fakeCurrentTime = time.Date(2009, time.November, 10, 13, 22, 33, 444000000, time.UTC)
|
||||
|
||||
func fakeTime() time.Time {
|
||||
return fakeCurrentTime
|
||||
}
|
||||
|
||||
func TestFakeTime(t *testing.T) {
|
||||
// test the tests
|
||||
s := fakeTime().Format(format)
|
||||
equals(timeString, s, t)
|
||||
}
|
||||
|
||||
func TestNewFile(t *testing.T) {
|
||||
currentTime = fakeTime
|
||||
dir := makeTempDir("TestNewFile", t)
|
||||
defer os.RemoveAll(dir)
|
||||
l := &Logger{
|
||||
Dir: dir,
|
||||
NameFormat: format,
|
||||
MaxSize: Megabyte,
|
||||
}
|
||||
defer l.Close()
|
||||
b := []byte("boo!")
|
||||
n, err := l.Write(b)
|
||||
isNil(err, t)
|
||||
equals(len(b), n, t)
|
||||
existsWithLen(logFile(dir), n, t)
|
||||
fileCount(dir, 1, t)
|
||||
}
|
||||
|
||||
func TestOpenExisting(t *testing.T) {
|
||||
currentTime = fakeTime
|
||||
dir := makeTempDir("TestOpenExisting", t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
filename := logFile(dir)
|
||||
data := []byte("foo!")
|
||||
err := ioutil.WriteFile(filename, data, 0644)
|
||||
isNil(err, t)
|
||||
existsWithLen(filename, len(data), t)
|
||||
|
||||
l := &Logger{
|
||||
Dir: dir,
|
||||
NameFormat: format,
|
||||
MaxSize: Megabyte,
|
||||
}
|
||||
defer l.Close()
|
||||
b := []byte("boo!")
|
||||
n, err := l.Write(b)
|
||||
isNil(err, t)
|
||||
equals(len(b), n, t)
|
||||
|
||||
// make sure the file got appended
|
||||
existsWithLen(filename, len(data)+n, t)
|
||||
|
||||
// make sure no other files were created
|
||||
fileCount(dir, 1, t)
|
||||
}
|
||||
|
||||
func TestWriteTooLong(t *testing.T) {
|
||||
currentTime = fakeTime
|
||||
dir := makeTempDir("TestWriteTooLong", t)
|
||||
defer os.RemoveAll(dir)
|
||||
l := &Logger{
|
||||
Dir: dir,
|
||||
NameFormat: format,
|
||||
MaxSize: 5,
|
||||
}
|
||||
defer l.Close()
|
||||
b := []byte("booooooooooooooo!")
|
||||
n, err := l.Write(b)
|
||||
assert(IsWriteTooLong(err), t,
|
||||
"Should have gotten write too long error, instead got %s (%T)", err, err)
|
||||
equals(0, n, t)
|
||||
_, err = os.Stat(logFile(dir))
|
||||
assert(os.IsNotExist(err), t, "File exists, but should not have been created")
|
||||
}
|
||||
|
||||
func TestRotate(t *testing.T) {
|
||||
currentTime = fakeTime
|
||||
dir := makeTempDir("TestRotate", t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
l := &Logger{
|
||||
Dir: dir,
|
||||
NameFormat: format,
|
||||
MaxSize: 10,
|
||||
}
|
||||
defer l.Close()
|
||||
b := []byte("boo!")
|
||||
n, err := l.Write(b)
|
||||
isNil(err, t)
|
||||
equals(len(b), n, t)
|
||||
|
||||
filename := logFile(dir)
|
||||
existsWithLen(filename, n, t)
|
||||
fileCount(dir, 1, t)
|
||||
|
||||
// set the current time one day later
|
||||
defer newFakeTime()()
|
||||
|
||||
b2 := []byte("foooooo!")
|
||||
n, err = l.Write(b2)
|
||||
isNil(err, t)
|
||||
equals(len(b2), n, t)
|
||||
|
||||
// this will use the new fake time
|
||||
newFilename := logFile(dir)
|
||||
existsWithLen(newFilename, n, t)
|
||||
|
||||
// make sure the old file still exists with the same size.
|
||||
existsWithLen(filename, len(b), t)
|
||||
|
||||
fileCount(dir, 2, t)
|
||||
}
|
||||
|
||||
func TestBackups(t *testing.T) {
|
||||
currentTime = fakeTime
|
||||
dir := makeTempDir("TestBackups", t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
l := &Logger{
|
||||
Dir: dir,
|
||||
NameFormat: format,
|
||||
MaxSize: 10,
|
||||
Backups: 1,
|
||||
}
|
||||
defer l.Close()
|
||||
b := []byte("boo!")
|
||||
n, err := l.Write(b)
|
||||
isNil(err, t)
|
||||
equals(len(b), n, t)
|
||||
|
||||
firstFilename := logFile(dir)
|
||||
existsWithLen(firstFilename, n, t)
|
||||
fileCount(dir, 1, t)
|
||||
|
||||
// set the current time one day later
|
||||
defer newFakeTime()()
|
||||
|
||||
// this will put us over the max
|
||||
b2 := []byte("foooooo!")
|
||||
n, err = l.Write(b2)
|
||||
isNil(err, t)
|
||||
equals(len(b2), n, t)
|
||||
|
||||
// this will use the new fake time
|
||||
secondFilename := logFile(dir)
|
||||
existsWithLen(secondFilename, n, t)
|
||||
|
||||
// make sure the old file still exists with the same size.
|
||||
existsWithLen(firstFilename, len(b), t)
|
||||
|
||||
fileCount(dir, 2, t)
|
||||
|
||||
// set the current time one day later
|
||||
defer newFakeTime()()
|
||||
|
||||
// this will make us rotate again
|
||||
n, err = l.Write(b2)
|
||||
isNil(err, t)
|
||||
equals(len(b2), n, t)
|
||||
|
||||
// this will use the new fake time
|
||||
thirdFilename := logFile(dir)
|
||||
existsWithLen(thirdFilename, n, t)
|
||||
|
||||
// we need to wait a little bit since the files get deleted on a different
|
||||
// goroutine.
|
||||
<-time.After(time.Millisecond * 10)
|
||||
|
||||
// should only have two files in the dir still
|
||||
fileCount(dir, 2, t)
|
||||
|
||||
// second file name should still exist
|
||||
existsWithLen(secondFilename, n, t)
|
||||
|
||||
// should have deleted the first filename
|
||||
notExist(firstFilename, t)
|
||||
}
|
||||
|
||||
// makeTempDir creates a file with a semi-unique name in the OS temp directory.
|
||||
// It should be based on the name of the test, to keep parallel tests from
|
||||
// colliding, and must be cleaned up after the test is finished.
|
||||
func makeTempDir(name string, t testing.TB) string {
|
||||
dir := time.Now().Format(name + format)
|
||||
dir = filepath.Join(os.TempDir(), dir)
|
||||
isNilUp(os.Mkdir(dir, 0777), t, 1)
|
||||
return dir
|
||||
}
|
||||
|
||||
// existsWithLen checks that the given file exists and has the correct length.
|
||||
func existsWithLen(path string, length int, t testing.TB) {
|
||||
info, err := os.Stat(path)
|
||||
isNilUp(err, t, 1)
|
||||
equalsUp(int64(length), info.Size(), t, 1)
|
||||
}
|
||||
|
||||
// logFile returns the log file name in the given directory for the current fake
|
||||
// time.
|
||||
func logFile(dir string) string {
|
||||
return filepath.Join(dir, fakeTime().Format(format))
|
||||
}
|
||||
|
||||
// fileCount checks that the number of files in the directory is exp.
|
||||
func fileCount(dir string, exp int, t testing.TB) {
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
isNilUp(err, t, 1)
|
||||
// Make sure no other files were created.
|
||||
equalsUp(exp, len(files), t, 1)
|
||||
}
|
||||
|
||||
// newFakeTime sets the fake "current time" to one day later.
|
||||
func newFakeTime() func() {
|
||||
old := fakeCurrentTime
|
||||
fakeCurrentTime = fakeCurrentTime.Add(Day)
|
||||
return func() {
|
||||
fakeCurrentTime = old
|
||||
}
|
||||
}
|
||||
|
||||
func notExist(path string, t testing.TB) {
|
||||
_, err := os.Stat(path)
|
||||
assertUp(os.IsNotExist(err), t, 1, "expected to get os.IsNotExist, but instead got %s", err)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package lumberjack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assert(condition bool, t testing.TB, msg string, v ...interface{}) {
|
||||
assertUp(condition, t, 1, msg, v...)
|
||||
}
|
||||
|
||||
func assertUp(condition bool, t testing.TB, caller int, msg string, v ...interface{}) {
|
||||
if !condition {
|
||||
_, file, line, _ := runtime.Caller(caller + 1)
|
||||
v = append([]interface{}{filepath.Base(file), line}, v...)
|
||||
fmt.Printf("%s:%d: "+msg+"\n", v...)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func equals(exp, act interface{}, t testing.TB) {
|
||||
equalsUp(exp, act, t, 1)
|
||||
}
|
||||
|
||||
func equalsUp(exp, act interface{}, t testing.TB, caller int) {
|
||||
if !reflect.DeepEqual(exp, act) {
|
||||
_, file, line, _ := runtime.Caller(caller + 1)
|
||||
fmt.Printf("%s:%d: exp: %v (%T), got: %v (%T)\n",
|
||||
filepath.Base(file), line, exp, exp, act, act)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func isNil(obtained interface{}, t testing.TB) {
|
||||
isNilUp(obtained, t, 1)
|
||||
}
|
||||
|
||||
func isNilUp(obtained interface{}, t testing.TB, caller int) {
|
||||
if !_isNil(obtained) {
|
||||
_, file, line, _ := runtime.Caller(caller + 1)
|
||||
fmt.Printf("%s:%d: expected nil, got: %v\n", filepath.Base(file), line, obtained)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func notNil(obtained interface{}, t testing.TB) {
|
||||
notNilUp(obtained, t, 1)
|
||||
}
|
||||
|
||||
func notNilUp(obtained interface{}, t testing.TB, caller int) {
|
||||
if _isNil(obtained) {
|
||||
_, file, line, _ := runtime.Caller(caller + 1)
|
||||
fmt.Printf("%s:%d: expected non-nil, got: %v\n", filepath.Base(file), line, obtained)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func _isNil(obtained interface{}) bool {
|
||||
if obtained == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
switch v := reflect.ValueOf(obtained); v.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return v.IsNil()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user