blob: d3ef3badc7c3697ee4bd986ccdaeb1d2ae323baf [file] [log] [blame] [edit]
/* Copyright 2012 Marc-Antoine Ruel. Licensed under the Apache License, Version
2.0 (the "License"); you may not use this file except in compliance with the
License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable law or
agreed to in writing, software distributed under the License 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 subcommandstest includes tools to help with concurrent testing.
package subcommandstest
import (
"bytes"
"fmt"
"io"
"log"
"os"
"strings"
"testing"
"github.com/maruel/subcommands"
"github.com/maruel/ut"
)
// Logging is a global object so it can't be checked for when tests are run in
// parallel.
var bufLog bytes.Buffer
// DisableLogOutput redirect log's default logger to a buffer and returns it.
// This function should be called in an init function.
func DisableLogOutput() *bytes.Buffer {
log.SetOutput(&bufLog)
log.SetFlags(log.Lmicroseconds)
return &bufLog
}
// TB wraps a testing.T object and adds functionality specific to
// command_support.
//
// BUG: testing.TB is now a thing, so this struct should likely be renamed to
// reduce confusion?
type TB struct {
*testing.T
bufLog bytes.Buffer
bufOut bytes.Buffer
bufErr bytes.Buffer
log *log.Logger
}
// MakeTB returns a fully initialized TB instance.
func MakeTB(t *testing.T) *TB {
tb := &TB{T: t}
tb.log = log.New(&tb.bufLog, "", log.Lmicroseconds)
return tb
}
// PrintIf trims the whitespace around a string encoded as []byte and prints it
// only if not empty.
func PrintIf(b []byte, name string) {
s := strings.TrimSpace(string(b))
if len(s) != 0 {
fmt.Fprintf(os.Stderr, "\n\\/ \\/ %s \\/ \\/\n%s\n/\\ /\\ %s /\\ /\\\n", name, s, name)
}
}
// CheckBuffer asserts the content of os.Stdout and os.Stderr mocks.
func (t *TB) CheckBuffer(out, err bool) {
if out {
// Print Stderr to see what happened.
ut.AssertEqualf(t, true, t.bufOut.Len() != 0, "Expected stdout")
} else {
ut.AssertEqualf(t, t.bufOut.Len(), 0, "Unexpected stdout")
}
if err {
ut.AssertEqualf(t, true, t.bufErr.Len() != 0, "Expected stderr")
} else {
ut.AssertEqualf(t, t.bufErr.Len(), 0, "Unexpected stderr")
}
t.bufOut.Reset()
t.bufErr.Reset()
}
// CheckOut asserts that what was printed out Application.GetOut() matches what
// is expected.
// TODO(maruel): It doesn't matches the use case where the match must be fuzzy,
// for example when non-deterministic data is included in the output.
func (t *TB) CheckOut(expected string) {
actual := t.bufOut.String()
ut.AssertEqual(t, expected, actual)
t.bufOut.Reset()
}
// Verbose sets the current context as verbose. It immediately prints out all
// logs generated for this specific test case up to now and redirects the log
// to os.Stderr so the following log is directly output to the console.
func (t *TB) Verbose() {
if t.bufLog.Len() != 0 {
_, _ = os.Stderr.Write(t.bufLog.Bytes())
}
t.log = log.New(os.Stderr, "", log.Lmicroseconds)
}
// ApplicationMock wrap both an Application and a TB. ApplicationMock
// implements GetOut and GetErr and adds GetLog(). GetLog() is implemented by
// TB.
type ApplicationMock struct {
subcommands.Application
*TB
}
// GetOut implements subcommands.Application.
func (a *ApplicationMock) GetOut() io.Writer {
return &a.bufOut
}
// GetErr implements subcommands.Application.
func (a *ApplicationMock) GetErr() io.Writer {
return &a.bufErr
}
// MakeAppMock returns an initialized ApplicationMock.
func MakeAppMock(t *testing.T, a subcommands.Application) *ApplicationMock {
return &ApplicationMock{a, MakeTB(t)}
}