tstkit

package
v0.27.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 3, 2025 License: MIT Imports: 11 Imported by: 0

README

The tstkit Package

The tstkit package provides a set of reusable test helpers to streamline Go testing. Instead of rewriting common test utilities, this package offers a curated collection. Its goal is to balance simplicity and functionality, focusing on practical tools generic.

The Buffer Type

The Buffer type, defined in buffer.go, is a thread-safe wrapper around bytes.Buffer. It supports three kinds of behavior for test cleanup:

WetBuffer

A WetBuffer uses Buffer and ensures it's written to and its contents are examined during the test.

func TestAction(t *testing.T) {
    // --- Given ---
    buf := tstkit.WetBuffer(t, "wet-buffer")

    // --- When ---
    Action(buf) // Writes to buf.

    // --- Then ---
    // Fails if Action doesn't write to buf or if buf.String() is not called.
    assert.Equal(t, "expected output", buf.String())   
}

To skip the examination requirement:

buf := tstkit.WetBuffer(t, "wet-buffer").SkipExamine()
buf.WriteString("data") // No failure for unexamined content
DryBuffer

A DryBuffer ensures the buffer remains empty.

func TestAction(t *testing.T) {
    // --- Given ---
    buf := tstkit.DryBuffer(t, "dry-buffer")

    // --- When ---
    DoSomething(buf) // Must not write to buf.

    // --- Then ---
    // Fails if DoSomething writes to buf.
}

Clocks

The tstkit implements four functions with the same signature as time.Time which can be used to inject deterministic clocks.

  • ClockStartingAt - returns current time with given offset.
  • ClockFixed - always returns the same time.
  • ClockDeterministic - returns time advanced by given duration no mather how fast you call it.
  • TikTak - like ClockDeterministic with duration set to 1 second.

ErrReader

ErrReader wraps an io.Reader to simplify testing of read, seek, or close errors. It limits the number of bytes read, similar to io.LimitedReader, and allows specifying a custom error to return when the byte limit is reached or during specific operations (e.g., Seek or Close for readers implementing io.Seeker or io.Closer).

rdr := strings.NewReader("some text")
rcs := ErrReader(rdr, 4, WithReadErr(errors.New("my error")))

data, err := io.ReadAll(rcs)

fmt.Printf("error: %v\n", err)
fmt.Printf(" data: %s\n", string(data))
// Output:
// error: my error
//  data: some

ErrWriter

ErrWriter wraps an io.Writer to simplify testing of write and close errors. It limits the number of bytes written, and allows specifying a custom error to return when the byte limit is reached or during call to Close method (for writing implementing io.Closer).

dst := &bytes.Buffer{}
ce := errors.New("my error")
ew := ErrWriter(dst, 3, WithWriteErr(ce))

n, err := ew.Write([]byte{0, 1, 2, 3})

fmt.Printf("    n: %d\n", n)
fmt.Printf("error: %v\n", err)
fmt.Printf("  dst: %v\n", dst.Bytes())
// Output:
//     n: 3
// error: my error
//   dst: [0 1 2]

Documentation

Overview

Package tstkit provides utilities for testing.

Index

Examples

Constants

View Source
const (
	BufferDry   = "dry"     // BufferDry enforces no data is written.
	BufferWet   = "wet"     // BufferWet enforces data is written and examined.
	BuffDefault = "default" // BuffDefault applies no cleanup checks.
)

Buffer kinds define the behavior of a Buffer during test cleanup.

Variables

View Source
var ErrRead = errors.New("read error")

ErrRead is the default error used by ErrorReader.

View Source
var ErrWrite = errors.New("write error")

ErrWrite is the default write error used by ErrorWriter.

Functions

func ClockDeterministic added in v0.9.0

func ClockDeterministic(start time.Time, tick time.Duration) func() time.Time

ClockDeterministic returns the function with the same signature as time.Now and returning time advancing by the given tick with every call no matter how fast or slow you call it.

Example
start := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
clk := ClockDeterministic(start, time.Hour)

fmt.Println(clk())
fmt.Println(clk())
fmt.Println(clk())
Output:

2022-01-01 00:00:00 +0000 UTC
2022-01-01 01:00:00 +0000 UTC
2022-01-01 02:00:00 +0000 UTC

func ClockFixed added in v0.9.0

func ClockFixed(tim time.Time) func() time.Time

ClockFixed returns the function with the same signature as time.Now which always returns the given time.

Example
clk := ClockFixed(time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC))

fmt.Println(clk())
fmt.Println(clk())
fmt.Println(clk())
Output:

2022-01-01 00:00:00 +0000 UTC
2022-01-01 00:00:00 +0000 UTC
2022-01-01 00:00:00 +0000 UTC

func ClockStartingAt added in v0.9.0

func ClockStartingAt(tim time.Time) func() time.Time

ClockStartingAt returns the function with the same signature as time.Now and returning time as if the current time was set to the given value.

func ReadAllFromStart added in v0.26.0

func ReadAllFromStart(rs io.ReadSeeker) []byte

ReadAllFromStart seeks to the beginning of "rs" and reads it till gets io.EOF or any other error. Then seeks back to the position where "rs" was before the call. Panics on error.

func SHA1File added in v0.27.0

func SHA1File(pth string) string

SHA1File returns SHA1 hash of the file. Panics on error.

func SHA1Reader added in v0.27.0

func SHA1Reader(r io.Reader) string

SHA1Reader returns SHA1 hash off everything in the reader. Panics on error.

func TikTak added in v0.9.0

func TikTak(start time.Time) func() time.Time

TikTak returns a deterministic clock advancing one second for each call.

Example
start := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
clk := TikTak(start)

fmt.Println(clk())
fmt.Println(clk())
fmt.Println(clk())
Output:

2022-01-01 00:00:00 +0000 UTC
2022-01-01 00:00:01 +0000 UTC
2022-01-01 00:00:02 +0000 UTC

func WithReadErr added in v0.25.0

func WithReadErr(err error) func(*ErrorReader)

WithReadErr is an ErrReader option setting custom read error.

func WithReaderCloseErr added in v0.25.0

func WithReaderCloseErr(err error) func(*ErrorReader)

WithReaderCloseErr is an ErrReader option setting close error.

func WithSeekErr added in v0.25.0

func WithSeekErr(err error) func(*ErrorReader)

WithSeekErr is an ErrReader option setting custom seek error.

func WithWriteCloseErr added in v0.25.0

func WithWriteCloseErr(err error) func(writer *ErrorWriter)

WithWriteCloseErr is an ErrWriter option setting custom close error.

func WithWriteErr added in v0.25.0

func WithWriteErr(err error) func(writer *ErrorWriter)

WithWriteErr is an ErrWriter option setting custom write error.

Types

type Buffer

type Buffer struct {
	// contains filtered or unexported fields
}

Buffer is a thread-safe buffer that wraps bytes.Buffer. It tracks write and read operations and supports test cleanup checks based on its kind.

func DryBuffer

func DryBuffer(t tester.T, names ...string) *Buffer

DryBuffer creates a thread-safe Buffer with the dry kind BufferDry, which fails the test during cleanup if any data was written to it. An optional name can be provided for use in test log output. The provided tester.T is used to register cleanup checks and report failures.

Example:

buf := DryBuffer(t, "dry-buffer")
buf.WriteString("data") // Will fail test during cleanup.

func NewBuffer

func NewBuffer(names ...string) *Buffer

NewBuffer creates a new thread-safe Buffer with the BuffDefault kind. An optional name can be provided. If no name is provided, Buffer.Name returns an empty string.

Example:

buf := NewBuffer("my-buffer") // Named buffer.
buf := NewBuffer()            // Unnamed buffer.

func WetBuffer

func WetBuffer(t tester.T, names ...string) *Buffer

WetBuffer creates a thread-safe Buffer with the wet kind BufferWet, which fails the test during cleanup if no data was written or if the contents were not read via Buffer.String. An optional name can be provided for use in test log output. The provided tester.T is used to register cleanup checks and report failures.

Example:

buf := WetBuffer(t, "wet-buffer")
buf.WriteString("data")
// Must call buf.SkipExamine() or buf.String() to avoid test failure.

func (*Buffer) Kind

func (buf *Buffer) Kind() string

Kind returns the buffer's kind.

func (*Buffer) MustWriteString

func (buf *Buffer) MustWriteString(s string) int

MustWriteString writes the string s to the buffer, incrementing the write counter. It panics if the write operation fails. This is useful in tests where write failures are unexpected and should halt execution.

func (*Buffer) Name

func (buf *Buffer) Name() string

Name returns the buffer's name or an empty string if no name was provided during creation.

func (*Buffer) Reset

func (buf *Buffer) Reset()

Reset clears the buffer's contents and resets the write and read counters. It is thread-safe and prepares the buffer for reuse in tests.

func (*Buffer) SkipExamine

func (buf *Buffer) SkipExamine() *Buffer

SkipExamine disables the cleanup requirement for the test to examine the buffer. This is useful when the buffer is WetBuffer but we do not want to examine what was written to it. Implements fluent interface.

func (*Buffer) String

func (buf *Buffer) String() string

String returns the current contents of the buffer as a string, incrementing the read counter. It is thread-safe and implements the fmt.Stringer interface and is intended for inspecting buffer data during tests.

func (*Buffer) Write

func (buf *Buffer) Write(p []byte) (n int, err error)

Write writes the byte slice p to the buffer, incrementing the write counter. It is thread-safe and implements the io.Writer interface.

func (*Buffer) WriteString

func (buf *Buffer) WriteString(s string) (n int, err error)

WriteString writes the string s to the buffer, incrementing the write counter. It is thread-safe and implements the io.StringWriter interface.

type ErrorReader added in v0.25.0

type ErrorReader struct {
	// contains filtered or unexported fields
}

ErrorReader is an io.Reader that reads up to n bytes from an underlying source returning an error. If n is negative, it behaves as a standard io.Reader with no byte limit.

For readers implementing io.Closer, use WithReaderCloseErr to specify an error to return from the Close method when called.

For readers implementing io.Seeker, use WithSeekErr to specify an error to return from the Seek method when called.

func ErrReader added in v0.25.0

func ErrReader(r io.Reader, n int, opts ...func(*ErrorReader)) *ErrorReader

ErrReader wraps reader r and allows control when to return read, seek, and close errors. It acts like a regular reader if n < 0 and no options were provided.

With WithReadErr option, you can set what error is returned.

With WithSeekErr option, you can set an error that should be returned when the Seek method is called. The underlying Seek method will be called if the seek error is not provided and provided io.Reader can be cast to io.Seeker.

With WithReaderCloseErr you can set an error to be returned when the Close method is called. If the provided io.Reader can be cast to io.Closer, the original Close method will always be called, but the original error returned from that call will only be returned if the WithReaderCloseErr was not defined.

Example
rdr := strings.NewReader("some text")
rcs := ErrReader(rdr, 3)

data, err := io.ReadAll(rcs)

fmt.Printf("error: %v\n", err)
fmt.Printf(" data: %s\n", string(data))
Output:

error: read error
 data: som
Example (Custom_error)
mye := errors.New("my error")
rdr := strings.NewReader("some text")
rcs := ErrReader(rdr, 4, WithReadErr(mye))

data, err := io.ReadAll(rcs)

fmt.Printf("error: %v\n", err)
fmt.Printf(" data: %s\n", string(data))
Output:

error: my error
 data: some

func (*ErrorReader) Close added in v0.25.0

func (rcs *ErrorReader) Close() error

func (*ErrorReader) Read added in v0.25.0

func (rcs *ErrorReader) Read(p []byte) (int, error)

Read implements io.Reader which returns an error after reading n bytes.

func (*ErrorReader) Seek added in v0.25.0

func (rcs *ErrorReader) Seek(offset int64, whence int) (int64, error)

type ErrorWriter added in v0.25.0

type ErrorWriter struct {
	// contains filtered or unexported fields
}

ErrorWriter is an io.Writer that writes up to n bytes to an underlying source returning an error. If n is negative, it behaves as a standard io.Writer with no byte limit.

For writers implementing io.Closer, use WithWriteCloseErr to specify an error to return from the Close method when called.

func ErrWriter added in v0.25.0

func ErrWriter(w io.Writer, n int, opts ...func(*ErrorWriter)) *ErrorWriter

ErrWriter wraps writer w and allows control when to return read and close errors. It acts like a regular writer if n < 0 and no options were provided.

With WithWriteErr option, you can set what error is returned.

With WithWriteCloseErr you can set an error to be returned when the Close method is called. If the provided io.Writer can be cast to io.Closer, the original Close method will always be called, but the original error returned from that call will only be returned if the WithReaderCloseErr was not defined.

Example
dst := &bytes.Buffer{}
ce := errors.New("my error")
ew := ErrWriter(dst, 3, WithWriteErr(ce))

n, err := ew.Write([]byte{0, 1, 2, 3})

fmt.Printf("    n: %d\n", n)
fmt.Printf("error: %v\n", err)
fmt.Printf("  dst: %v\n", dst.Bytes())
Output:

    n: 3
error: my error
  dst: [0 1 2]

func (*ErrorWriter) Close added in v0.25.0

func (ew *ErrorWriter) Close() error

func (*ErrorWriter) Write added in v0.25.0

func (ew *ErrorWriter) Write(p []byte) (int, error)

Write writes to the underlying buffer and returns error if number of written bytes is equal to the predefined limit.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL