Compare commits

...

14 Commits

14 changed files with 609 additions and 69 deletions

View File

@@ -1,5 +1,34 @@
CHANGELOG
v1.16.3 - 2025-11-21
Changed:
- msg: fixups and testing.
v1.16.2 - 2025-11-21
Changed:
- msg: fill debug null pointer deref.
v1.16.1 - 2025-11-21
Changed:
- msg: rename functions for ergonomics.
v1.16.0 - 2025-11-20
Added:
- msg: package for command line outputs.
Changed:
- lib: add DummyWriteCloser
- Miscellaneous linter fixes and documentation updates.
v1.15.8 - 2025-11-20
Changed:
- certlib: fix CSR FileKind, add test cases.
v1.15.7 - 2025-11-19
Changed:

View File

@@ -84,6 +84,7 @@ Contents:
lib/ Commonly-useful functions for writing Go programs.
log/ A syslog library.
logging/ A logging library.
msg/ Output library for command line programs.
mwc/ MultiwriteCloser implementation.
sbuf/ A byte buffer that can be wiped.
seekbuf/ A read-seekable byte buffer.

View File

@@ -171,11 +171,16 @@ func (ka KeyAlgo) String() string {
case x509.RSA:
return fmt.Sprintf("RSA-%d", ka.Size)
case x509.ECDSA:
if ka.curve == nil {
return fmt.Sprintf("ECDSA (unknown %d)", ka.Size)
}
return fmt.Sprintf("ECDSA-%s", ka.curve.Params().Name)
case x509.Ed25519:
return "Ed25519"
case x509.DSA:
return "DSA"
case x509.UnknownPublicKeyAlgorithm:
fallthrough // make linter happy
default:
return "unknown"
}
@@ -242,7 +247,7 @@ func publicKeyAlgoFromCert(cert *x509.Certificate) KeyAlgo {
}
func publicKeyAlgoFromCSR(csr *x509.CertificateRequest) KeyAlgo {
return publicKeyAlgoFromPublicKey(csr.PublicKeyAlgorithm)
return publicKeyAlgoFromPublicKey(csr.PublicKey)
}
type FileType struct {

View File

@@ -146,9 +146,12 @@ var (
ecTestCACert = "testdata/ec-ca-cert.pem"
ecTestCAPriv = "testdata/ec-ca-priv.pem"
ecTestCAReq = "testdata/ec-ca-cert.csr"
rsaTestCACert = "testdata/rsa-ca-cert.pem"
rsaTestCAPriv = "testdata/rsa-ca-priv.pem"
rsaTestCAReq = "testdata/rsa-ca-cert.csr"
)
func TestFileTypeEC(t *testing.T) {
func TestFileTypeECPrivate(t *testing.T) {
ft, err := FileKind(ecTestCAPriv)
assert.NoErrorT(t, err)
@@ -171,3 +174,120 @@ func TestFileTypeEC(t *testing.T) {
t.Errorf("certlib: expected algo '%s', got '%s'", expectedAlgo, ft.Algo)
}
}
func TestFileTypeECCertRequest(t *testing.T) {
ft, err := FileKind(ecTestCAReq)
assert.NoErrorT(t, err)
if ft.Format != FormatPEM {
t.Errorf("certlib: expected format '%s', got '%s'", FormatPEM, ft.Format)
}
if ft.Type != strings.ToLower(pemTypeCertificateRequest) {
t.Errorf("certlib: expected type '%s', got '%s'",
strings.ToLower(pemTypeCertificateRequest), ft.Type)
}
expectedAlgo := KeyAlgo{
Type: x509.ECDSA,
Size: 521,
curve: elliptic.P521(),
}
if ft.Algo.String() != expectedAlgo.String() {
t.Errorf("certlib: expected algo '%s', got '%s'", expectedAlgo, ft.Algo)
}
}
func TestFileTypeECCertificate(t *testing.T) {
ft, err := FileKind(ecTestCACert)
assert.NoErrorT(t, err)
if ft.Format != FormatPEM {
t.Errorf("certlib: expected format '%s', got '%s'", FormatPEM, ft.Format)
}
if ft.Type != strings.ToLower(pemTypeCertificate) {
t.Errorf("certlib: expected type '%s', got '%s'",
strings.ToLower(pemTypeCertificate), ft.Type)
}
expectedAlgo := KeyAlgo{
Type: x509.ECDSA,
Size: 521,
curve: elliptic.P521(),
}
if ft.Algo.String() != expectedAlgo.String() {
t.Errorf("certlib: expected algo '%s', got '%s'", expectedAlgo, ft.Algo)
}
}
func TestFileTypeRSAPrivate(t *testing.T) {
ft, err := FileKind(rsaTestCAPriv)
assert.NoErrorT(t, err)
if ft.Format != FormatPEM {
t.Errorf("certlib: expected format '%s', got '%s'", FormatPEM, ft.Format)
}
if ft.Type != strings.ToLower(pemTypePrivateKey) {
t.Errorf("certlib: expected type '%s', got '%s'",
strings.ToLower(pemTypePrivateKey), ft.Type)
}
expectedAlgo := KeyAlgo{
Type: x509.RSA,
Size: 4096,
}
if ft.Algo.String() != expectedAlgo.String() {
t.Errorf("certlib: expected algo '%s', got '%s'", expectedAlgo, ft.Algo)
}
}
func TestFileTypeRSACertRequest(t *testing.T) {
ft, err := FileKind(rsaTestCAReq)
assert.NoErrorT(t, err)
if ft.Format != FormatPEM {
t.Errorf("certlib: expected format '%s', got '%s'", FormatPEM, ft.Format)
}
if ft.Type != strings.ToLower(pemTypeCertificateRequest) {
t.Errorf("certlib: expected type '%s', got '%s'",
strings.ToLower(pemTypeCertificateRequest), ft.Type)
}
expectedAlgo := KeyAlgo{
Type: x509.RSA,
Size: 4096,
}
if ft.Algo.String() != expectedAlgo.String() {
t.Errorf("certlib: expected algo '%s', got '%s'", expectedAlgo, ft.Algo)
}
}
func TestFileTypeRSACertificate(t *testing.T) {
ft, err := FileKind(rsaTestCACert)
assert.NoErrorT(t, err)
if ft.Format != FormatPEM {
t.Errorf("certlib: expected format '%s', got '%s'", FormatPEM, ft.Format)
}
if ft.Type != strings.ToLower(pemTypeCertificate) {
t.Errorf("certlib: expected type '%s', got '%s'",
strings.ToLower(pemTypeCertificate), ft.Type)
}
expectedAlgo := KeyAlgo{
Type: x509.RSA,
Size: 4096,
}
if ft.Algo.String() != expectedAlgo.String() {
t.Errorf("certlib: expected algo '%s', got '%s'", expectedAlgo, ft.Algo)
}
}

View File

@@ -1,8 +1,28 @@
-----BEGIN CERTIFICATE REQUEST-----
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
-----END CERTIFICATE REQUEST-----

View File

@@ -1,14 +1,34 @@
-----BEGIN CERTIFICATE-----
MIICHDCCAc6gAwIBAgIVAN1AKHhLNsqcBEKYCqgjEMG65hhvMAUGAytlcDCBiTEL
MAkGA1UEBhMCVVMxCTAHBgNVBAgTADEJMAcGA1UEBxMAMSIwIAYDVQQKExlXTlRS
TVVURSBIRUFWWSBJTkRVU1RSSUVTMR8wHQYDVQQLExZDUllQVE9HUkFQSElDIFNF
UlZJQ0VTMR8wHQYDVQQDExZXTlRSTVVURSBURVNUIFJTQSBDQSAxMB4XDTI1MTEx
OTIxMDQyNVoXDTQ1MTExNDIyMDQyNVowgYkxCzAJBgNVBAYTAlVTMQkwBwYDVQQI
EwAxCTAHBgNVBAcTADEiMCAGA1UEChMZV05UUk1VVEUgSEVBVlkgSU5EVVNUUklF
UzEfMB0GA1UECxMWQ1JZUFRPR1JBUEhJQyBTRVJWSUNFUzEfMB0GA1UEAxMWV05U
Uk1VVEUgVEVTVCBSU0EgQ0EgMTAqMAUGAytlcAMhANS2otlgoblB9pKuC1nXaek5
XJqbrgc+huN3vXn7GRgao0UwQzAOBgNVHQ8BAf8EBAMCAgQwEgYDVR0TAQH/BAgw
BgEB/wIBAzAdBgNVHQ4EFgQUetUgY5rlFq+OCeYe0Eqmp8Ek488wBQYDK2VwA0EA
LIFZo6FQL+8q8h66Bm7favIh2AlqsXA45DpRUN2LpjNm/7NbTPDw52y8cLegUUMc
UhDyk20fGg5g6cLywC0mDA==
MIIF6DCCA9CgAwIBAgIVANc3mjaz6CKa3IT0+lJZ/hxvcbw6MA0GCSqGSIb3DQEB
CwUAMIGJMQswCQYDVQQGEwJVUzEJMAcGA1UECBMAMQkwBwYDVQQHEwAxIjAgBgNV
BAoTGVdOVFJNVVRFIEhFQVZZIElORFVTVFJJRVMxHzAdBgNVBAsTFkNSWVBUT0dS
QVBISUMgU0VSVklDRVMxHzAdBgNVBAMTFldOVFJNVVRFIFRFU1QgUlNBIENBIDEw
HhcNMjUxMTE5MjE1NzQ1WhcNNDUxMTE0MjI1NzQ1WjCBiTELMAkGA1UEBhMCVVMx
CTAHBgNVBAgTADEJMAcGA1UEBxMAMSIwIAYDVQQKExlXTlRSTVVURSBIRUFWWSBJ
TkRVU1RSSUVTMR8wHQYDVQQLExZDUllQVE9HUkFQSElDIFNFUlZJQ0VTMR8wHQYD
VQQDExZXTlRSTVVURSBURVNUIFJTQSBDQSAxMIICIjANBgkqhkiG9w0BAQEFAAOC
Ag8AMIICCgKCAgEA2rYSoww2dAPH8XBD1d/5SpgkKx15LF5EOT0IZFuxOgp4GVh+
w4yBnemCGxnWyucnIH6kXVxIzWo8kJcK9F2fXVD4V9nbnJ7ek5oBjnz0YlJsSlzG
K3B4nt2yJjexupmOHleebBfmofIITapqYoomgBSQnVgf+Bapfhsxg8bmhGjoqkLF
vqkqLlJwxOXuS+f19qCQcRUkt/ZmtAxbNMPCKOMkawpyMxZVlB6A/04ML7C63i6r
VkgVpcZb52WtCSjBUhldmsGQveWze7hEmINp05K8QfdUkH3KPP38spP4kVTOBsxY
LJnYareCv8dj5eyh27wmw+pDEBY8LGLX2CL4zHsyTuM6oFKF8NdvLXmaeonEVTlV
yM+1fmM88i8OwXjoglfmU7BI0lpVCyn+zng4vhvNt6X8GwdbO1FwM3MDG85bd51k
fMIT6jtRaQzC8kRH5yIB8lJKbyygblCIvkZ0y+2KrBB7+XxuRsTCzg4CeSz1T5T3
/lLeQsmr3czD6h4h3ibxtEaoIUFAX05Zpk44c7mQRPz2Ov9QXRR2Logw0i+PV9tM
lUpl0ziAMRwsdOJ6/daqGazL7iZHudV2vRsLzLkg0tDhojg3gFSXNZSilZ0UFqxl
hol1aq3mwFVge8RW+ROGUmgOUKaYE9ALtO2+pQDSUSEiBa15HfcVT8P459MCAwEA
AaNFMEMwDgYDVR0PAQH/BAQDAgIEMBIGA1UdEwEB/wQIMAYBAf8CAQMwHQYDVR0O
BBYEFAf60HUhXFOzcdtO8MJC2sN5qsmmMA0GCSqGSIb3DQEBCwUAA4ICAQAHBYjp
hN6U00cqqU/tk1CyUuJsPq2tGGIb3PxN+PvGLrhx27P+F8a5Sn2zBbkweX5vCu+i
o8EPavHAARIA+gF0UyM5MwPZdjdhNHDRGdASPphx7ZBa0e5Qp2XFyruw6EwHztyK
m7cF45MslGiEjRc7cciR5AUElRFhgY2QAlCcA8Tp6h3XJVSlaDhf+sS1EWlseVJN
GU5+Mu1L9vA6aiCKVtDviETfr7PmSY1obMrq9pDIoyo1jwflu/kTtmqDkDMkI1MI
mGKoHuKfAtZHiavjL7DMilO6X6ZMNPSYl4snm2hovHnoemifGuwlJ/V+HnDIMQAs
B5U3NY+IV6vlEYW3CmUfTsFjUzVpS/o/X5GBhG3pTAg9jUgpVsLNuVJrCg5PNpSL
xXMWRxj/y5ITm0m0/agNAd80KEDvCTbdORdDz4iYVG/L/GoaH3yPcmrBsE+2pPQb
rR1ihPU02wjY/oqlVt3mNzqczXZYoOW7FoW3O4dpP10kPA4O17nUJJ0FOU/vWXCS
7TgJwdlzoTPptK7c9zoZcHwPY2j0BVVgSofKlKlR1tJvqxbDA16pw2nsWl+r53Uc
Emw7SdHQfvDdbt42PL9g1CYqiYba7J9WkRWOYegSdOYLuaddYKN36xhCwT6p2/HM
EaRCxfUq2tmFzL2NhJLJlvNhpe7Zt5s/UF1oiQ==
-----END CERTIFICATE-----

View File

@@ -1,3 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
eWRtwt2K2iouhIrKnXvlgEnfRUd9XXI=
-----END PRIVATE KEY-----

View File

@@ -1,5 +1,5 @@
key:
algorithm: ed25519
algorithm: rsa
size: 4096
subject:
common_name: WNTRMUTE TEST RSA CA 1

View File

@@ -101,7 +101,7 @@ func buildExtraForPath(st unix.Stat_t, path string, setUID, setGID int) []byte {
gid = uint32(setGID & 0xFFFFFFFF) //#nosec G115 - masked
}
}
mode := uint32(st.Mode & 0o7777)
mode := st.Mode & 0o7777
// Use portable helper to gather ctime
var cts int64

6
go.sum
View File

@@ -29,19 +29,15 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=

View File

@@ -22,35 +22,41 @@ import (
// Fetcher is an interface for fetching certificates from a remote source. It
// currently supports fetching from a server or a file.
type Fetcher interface {
// Get retrieves the leaf certificate from the source.
Get() (*x509.Certificate, error)
// GetChain retrieves the entire chain from the Fetcher.
GetChain() ([]*x509.Certificate, error)
// String returns a string representation of the Fetcher.
String() string
}
func NewFetcher(spec string, tcfg *tls.Config) (Fetcher, error) {
if fileutil.FileDoesExist(spec) || spec == "-" {
return NewFileFetcher(spec), nil
}
fetcher, err := ParseServer(spec, tcfg)
if err != nil {
return nil, err
}
fetcher.config = tcfg
return fetcher, nil
}
// ServerFetcher retrieves certificates from a TLS connection.
type ServerFetcher struct {
host string
port int
insecure bool
roots *x509.CertPool
}
// WithRoots sets the roots for the ServerFetcher.
func WithRoots(roots *x509.CertPool) func(*ServerFetcher) {
return func(sf *ServerFetcher) {
sf.roots = roots
}
}
// WithSkipVerify sets the insecure flag for the ServerFetcher.
func WithSkipVerify() func(*ServerFetcher) {
return func(sf *ServerFetcher) {
sf.insecure = true
}
config *tls.Config
}
// ParseServer parses a server string into a ServerFetcher. It can be a URL or a
// a host:port pair.
func ParseServer(host string) (*ServerFetcher, error) {
func ParseServer(host string, cfg *tls.Config) (*ServerFetcher, error) {
target, err := hosts.ParseHost(host)
if err != nil {
return nil, fmt.Errorf("failed to parse server: %w", err)
@@ -59,6 +65,7 @@ func ParseServer(host string) (*ServerFetcher, error) {
return &ServerFetcher{
host: target.Host,
port: target.Port,
config: cfg,
}, nil
}
@@ -68,10 +75,7 @@ func (sf *ServerFetcher) String() string {
func (sf *ServerFetcher) GetChain() ([]*x509.Certificate, error) {
opts := dialer.Opts{
TLSConfig: &tls.Config{
InsecureSkipVerify: sf.insecure, // #nosec G402 - no shit sherlock
RootCAs: sf.roots,
},
TLSConfig: sf.config,
}
conn, err := dialer.DialTLS(context.Background(), net.JoinHostPort(sf.host, lib.Itoa(sf.port, -1)), opts)
@@ -93,6 +97,7 @@ func (sf *ServerFetcher) Get() (*x509.Certificate, error) {
return certs[0], nil
}
// FileFetcher retrieves certificates from files on disk.
type FileFetcher struct {
path string
}
@@ -139,20 +144,11 @@ func (ff *FileFetcher) Get() (*x509.Certificate, error) {
// configuration will be used to control verification behavior (e.g.,
// InsecureSkipVerify, RootCAs).
func GetCertificateChain(spec string, cfg *tls.Config) ([]*x509.Certificate, error) {
if fileutil.FileDoesExist(spec) {
return NewFileFetcher(spec).GetChain()
}
fetcher, err := ParseServer(spec)
fetcher, err := NewFetcher(spec, cfg)
if err != nil {
return nil, err
}
if cfg != nil {
fetcher.insecure = cfg.InsecureSkipVerify
fetcher.roots = cfg.RootCAs
}
return fetcher.GetChain()
}

View File

@@ -5,6 +5,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
@@ -329,3 +330,20 @@ func HexEncode(b []byte, mode HexEncodeMode) string {
panic("invalid hex encode mode")
}
}
// DummyWriteCloser wraps an io.Writer in a struct with a no-op Close.
type DummyWriteCloser struct {
w io.Writer
}
func WithCloser(w io.Writer) io.WriteCloser {
return &DummyWriteCloser{w: w}
}
func (dwc *DummyWriteCloser) Write(p []byte) (int, error) {
return dwc.w.Write(p)
}
func (dwc *DummyWriteCloser) Close() error {
return nil
}

139
msg/msg.go Normal file
View File

@@ -0,0 +1,139 @@
// Package msg is a tool for handling commandline output based on
// flags for quiet, verbose, and debug modes. The default is to
// have all modes disabled.
//
// The Qprint messages will only output messages if quiet mode is
// disabled
// The Vprint messages will only output messages if verbose mode
// is enabled.
// The Dprint messages will only output messages if debug mode
// is enabled.
package msg
import (
"fmt"
"io"
"os"
"git.wntrmute.dev/kyle/goutils/lib"
"git.wntrmute.dev/kyle/goutils/dbg"
)
var (
enableQuiet bool
enableVerbose bool
debug = dbg.New()
w io.Writer = os.Stdout
)
func Reset() {
enableQuiet = false
enableVerbose = false
debug = dbg.New()
w = os.Stdout
}
func SetQuiet(q bool) {
enableQuiet = q
}
func SetVerbose(v bool) {
enableVerbose = v
}
func SetDebug(d bool) {
debug.Enabled = d
}
func Set(q, v, d bool) {
SetQuiet(q)
SetVerbose(v)
SetDebug(d)
}
func Qprint(a ...any) {
if enableQuiet {
return
}
fmt.Fprint(w, a...)
}
func Qprintf(format string, a ...any) {
if enableQuiet {
return
}
fmt.Fprintf(w, format, a...)
}
func Qprintln(a ...any) {
if enableQuiet {
return
}
fmt.Fprintln(w, a...)
}
func Dprint(a ...any) {
debug.Print(a...)
}
func Dprintf(format string, a ...any) {
debug.Printf(format, a...)
}
func Dprintln(a ...any) {
debug.Println(a...)
}
func StackTrace() {
debug.StackTrace()
}
func Vprint(a ...any) {
if !enableVerbose {
return
}
fmt.Fprint(w, a...)
}
func Vprintf(format string, a ...any) {
if !enableVerbose {
return
}
fmt.Fprintf(w, format, a...)
}
func Vprintln(a ...any) {
if !enableVerbose {
return
}
fmt.Fprintln(w, a...)
}
func Print(a ...any) {
fmt.Fprint(w, a...)
}
func Printf(format string, a ...any) {
fmt.Fprintf(w, format, a...)
}
func Println(a ...any) {
fmt.Fprintln(w, a...)
}
// SetWriter changes the output for messages.
func SetWriter(dst io.Writer) {
w = dst
dbgEnabled := debug.Enabled
debug = dbg.To(lib.WithCloser(w))
debug.Enabled = dbgEnabled
}

147
msg/msg_test.go Normal file
View File

@@ -0,0 +1,147 @@
package msg_test
import (
"bytes"
"testing"
"git.wntrmute.dev/kyle/goutils/msg"
)
func checkExpected(buf *bytes.Buffer, expected string) bool {
return buf.String() == expected
}
func resetBuf() *bytes.Buffer {
buf := &bytes.Buffer{}
msg.SetWriter(buf)
return buf
}
func TestVerbosePrint(t *testing.T) {
buf := resetBuf()
msg.SetVerbose(false) // ensure verbose is explicitly not set
msg.Vprint("hello, world")
if buf.Len() != 0 {
t.Fatalf("expected no output, have %s", buf.String())
}
msg.Vprintf("hello, %s", "world")
if buf.Len() != 0 {
t.Fatalf("expected no output, have %s", buf.String())
}
msg.Vprintln("hello, world")
if buf.Len() != 0 {
t.Fatalf("expected no output, have %s", buf.String())
}
msg.SetVerbose(true)
msg.Vprint("hello, world")
if !checkExpected(buf, "hello, world") {
t.Fatalf("expected output %q, have %q", "hello, world", buf.String())
}
buf.Reset()
msg.Vprintf("hello, %s", "world")
if !checkExpected(buf, "hello, world") {
t.Fatalf("expected output %q, have %q", "hello, world", buf.String())
}
buf.Reset()
msg.Vprintln("hello, world")
if !checkExpected(buf, "hello, world\n") {
t.Fatalf("expected output %q, have %q", "hello, world\n", buf.String())
}
}
func TestQuietPrint(t *testing.T) {
buf := resetBuf()
msg.SetQuiet(true)
msg.Qprint("hello, world")
if buf.Len() != 0 {
t.Fatalf("expected no output, have %s", buf.String())
}
msg.Qprintf("hello, %s", "world")
if buf.Len() != 0 {
t.Fatalf("expected no output, have %s", buf.String())
}
msg.Qprintln("hello, world")
if buf.Len() != 0 {
t.Fatalf("expected no output, have %s", buf.String())
}
msg.SetQuiet(false)
msg.Qprint("hello, world")
if !checkExpected(buf, "hello, world") {
t.Fatalf("expected output %q, have %q", "hello, world", buf.String())
}
buf.Reset()
msg.Qprintf("hello, %s", "world")
if !checkExpected(buf, "hello, world") {
t.Fatalf("expected output %q, have %q", "hello, world", buf.String())
}
buf.Reset()
msg.Qprintln("hello, world")
if !checkExpected(buf, "hello, world\n") {
t.Fatalf("expected output %q, have %q", "hello, world\n", buf.String())
}
}
func TestDebugPrint(t *testing.T) {
buf := resetBuf()
msg.SetDebug(false) // ensure debug is explicitly not set
msg.Dprint("hello, world")
if buf.Len() != 0 {
t.Fatalf("expected no output, have %s", buf.String())
}
msg.Dprintf("hello, %s", "world")
if buf.Len() != 0 {
t.Fatalf("expected no output, have %s", buf.String())
}
msg.Dprintln("hello, world")
if buf.Len() != 0 {
t.Fatalf("expected no output, have %s", buf.String())
}
msg.StackTrace()
if buf.Len() != 0 {
t.Fatalf("expected no output, have %s", buf.String())
}
msg.SetDebug(true)
msg.Dprint("hello, world")
if !checkExpected(buf, "hello, world") {
t.Fatalf("expected output %q, have %q", "hello, world", buf.String())
}
buf.Reset()
msg.Dprintf("hello, %s", "world")
if !checkExpected(buf, "hello, world") {
t.Fatalf("expected output %q, have %q", "hello, world", buf.String())
}
buf.Reset()
msg.Dprintln("hello, world")
if !checkExpected(buf, "hello, world\n") {
t.Fatalf("expected output %q, have %q", "hello, world\n", buf.String())
}
buf.Reset()
msg.StackTrace()
if buf.Len() == 0 {
t.Fatal("expected stack trace output, received no output")
}
}