testutil

package
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

README

Test Utilities

Note: This package is for internal use only and is not part of the public API. It is intended for use within Astrolabe's own test suite and may change without notice.

This package provides mock implementations and test helpers for the Astrolabe CLI/TUI project.

MockSerial

MockSerial is a configurable mock serial device for testing serial port interactions without requiring actual hardware.

Features
  • Pre-configured data streams: Emit a sequence of byte frames
  • Configurable delays: Simulate real-world timing between frames
  • Error injection: Test error handling by configuring read errors
  • Context cancellation: Properly handles context cancellation
  • Reusable: Reset and reuse for multiple test cases
  • Dynamic frames: Add frames during test execution
Basic Usage
import "github.com/cthonicasoftware/astrolabe-cli/internal/testutil"

// Create a mock with test data
mock := testutil.NewMockSerial(
    testutil.WithFrames([][]byte{
        []byte("Frame 1\n"),
        []byte("Frame 2\n"),
    }),
    testutil.WithFrameDelay(10*time.Millisecond),
)

ctx := context.Background()
mock.Open(ctx)
defer mock.Close()

// Read frames
for frame := range mock.Frames() {
    // Process frame
}
Configuration Options
WithFrames

Set the sequence of byte frames to emit:

frames := [][]byte{
    []byte("TEMP:23.5C\n"),
    []byte("TEMP:23.7C\n"),
}
mock := testutil.NewMockSerial(testutil.WithFrames(frames))
WithFrameDelay

Control the timing between frame emissions:

// Emit frames every 100ms
mock := testutil.NewMockSerial(
    testutil.WithFrameDelay(100*time.Millisecond),
)
WithConfig

Configure serial port parameters:

cfg := sources.Config{
    Port:        "/dev/ttyUSB0",
    Baud:        9600,
    Parity:      "E",
    DataBits:    7,
    StopBits:    "2",
    FlowControl: "hardware",
}
mock := testutil.NewMockSerial(testutil.WithConfig(cfg))
WithReadError

Simulate read errors after frames are exhausted:

mock := testutil.NewMockSerial(
    testutil.WithFrames(frames),
    testutil.WithReadError(errors.New("connection lost")),
)
Testing Patterns
Simulating a Real Device
// Simulate a sensor emitting readings
sensorData := [][]byte{
    []byte("TEMP:23.5C\n"),
    []byte("TEMP:23.7C\n"),
    []byte("TEMP:23.6C\n"),
}

mock := testutil.NewMockSerial(
    testutil.WithFrames(sensorData),
    testutil.WithFrameDelay(100*time.Millisecond),
)

ctx := context.Background()
mock.Open(ctx)
defer mock.Close()

for frame := range mock.Frames() {
    // Process sensor reading
}
Testing Context Cancellation
mock := testutil.NewMockSerial(
    testutil.WithFrames(manyFrames),
    testutil.WithFrameDelay(10*time.Millisecond),
)

ctx, cancel := context.WithCancel(context.Background())
mock.Open(ctx)
defer mock.Close()

// Read some frames
for i := 0; i < 5; i++ {
    <-mock.Frames()
}

// Cancel and verify cleanup
cancel()
_, ok := <-mock.Frames()
// ok should be false (channel closed)
Testing Error Handling
expectedError := errors.New("hardware failure")
mock := testutil.NewMockSerial(
    testutil.WithFrames([][]byte{[]byte("Last frame\n")}),
    testutil.WithReadError(expectedError),
)

mock.Open(context.Background())
defer mock.Close()

// Read until error
for frame := range mock.Frames() {
    // Process frame
}
// Channel closes after error
Reusing Mocks
mock := testutil.NewMockSerial(testutil.WithFrames(frames))

// First test run
mock.Open(ctx)
// ... read frames ...
mock.Close()

// Reset and reuse
mock.Reset()

// Second test run
mock.Open(ctx)
// ... read frames again ...
mock.Close()
API Reference
Constructor
func NewMockSerial(opts ...MockSerialOption) *MockSerial
Methods
  • Open(ctx context.Context) error - Open the mock serial port
  • Close() error - Close the mock serial port
  • Frames() <-chan []byte - Get the frame output channel
  • Meta() core.SourceMeta - Get source metadata
  • Reset() - Reset the mock for reuse
  • AddFrame(frame []byte) - Dynamically add a frame
  • FramesRemaining() int - Get count of unread frames
Interface Compatibility

MockSerial implements the same interface as sources.Serial, making it a drop-in replacement for testing:

type SerialDevice interface {
    Open(ctx context.Context) error
    Close() error
    Frames() <-chan []byte
    Meta() core.SourceMeta
}
Examples

See examples_test.go for complete working examples demonstrating various usage patterns.

Running Tests
# Run all testutil tests
go test ./internal/testutil/...

# Run with verbose output
go test -v ./internal/testutil/...

# Run specific test
go test -v ./internal/testutil/ -run TestMockSerial_BasicOperation

Future Test Utilities

Planned additions to this package:

  • MockTCPSource - Mock TCP socket source
  • MockFileSource - Mock file source with configurable content
  • NewTempRunDir() - Create temporary run directories for testing
  • MockHTTPServer() - Simulate QA backend API
  • AssertManifest() - Validate manifest structure

Contributing

When adding new test utilities:

  1. Follow the existing patterns (builder options, context support)
  2. Provide comprehensive tests
  3. Include usage examples
  4. Update this README with documentation
  5. Ensure interface compatibility with real implementations

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type MockSerial

type MockSerial struct {
	Config sources.Config
	// contains filtered or unexported fields
}

MockSerial simulates a serial device for testing. It provides configurable behavior including data streams, delays, and error injection.

func NewMockSerial

func NewMockSerial(opts ...MockSerialOption) *MockSerial

NewMockSerial creates a new mock serial device with the given options.

Example

ExampleNewMockSerial demonstrates creating a mock serial device.

package main

import (
	"fmt"

	"github.com/cthonicasoftware/astrolabe-cli/internal/testutil"
)

func main() {
	// Create a mock serial device with pre-configured data
	mock := testutil.NewMockSerial(
		testutil.WithFrames([][]byte{
			[]byte("Hello\n"),
			[]byte("World\n"),
		}),
	)

	fmt.Printf("Mock created with %d frames\n", mock.FramesRemaining())

}
Output:
Mock created with 2 frames

func (*MockSerial) AddFrame

func (m *MockSerial) AddFrame(frame []byte)

AddFrame appends a frame to the mock's frame list (useful for dynamic testing).

Example

ExampleMockSerial_AddFrame demonstrates adding frames dynamically.

package main

import (
	"fmt"

	"github.com/cthonicasoftware/astrolabe-cli/internal/testutil"
)

func main() {
	mock := testutil.NewMockSerial(
		testutil.WithFrames([][]byte{[]byte("Initial\n")}),
	)

	fmt.Printf("Initial frames: %d\n", mock.FramesRemaining())

	// Add more frames
	mock.AddFrame([]byte("Second\n"))
	mock.AddFrame([]byte("Third\n"))

	fmt.Printf("After adding: %d\n", mock.FramesRemaining())

}
Output:
Initial frames: 1
After adding: 3

func (*MockSerial) Close

func (m *MockSerial) Close() error

Close simulates closing the serial port.

func (*MockSerial) Frames

func (m *MockSerial) Frames() <-chan []byte

Frames returns the channel for reading frames from the mock serial port.

func (*MockSerial) FramesRemaining

func (m *MockSerial) FramesRemaining() int

FramesRemaining returns the number of frames yet to be emitted.

func (*MockSerial) Meta

func (m *MockSerial) Meta() core.SourceMeta

Meta returns the source metadata for the mock device.

Example

ExampleMockSerial_Meta demonstrates getting source metadata.

package main

import (
	"fmt"

	"github.com/cthonicasoftware/astrolabe-cli/internal/sources"
	"github.com/cthonicasoftware/astrolabe-cli/internal/testutil"
)

func main() {
	mock := testutil.NewMockSerial(
		testutil.WithConfig(sources.Config{
			Port: "/dev/ttyUSB0",
			Baud: 115200,
		}),
	)

	meta := mock.Meta()
	fmt.Printf("Kind: %s, Port: %s, Baud: %d\n", meta.Kind, meta.Port, meta.Baud)

}
Output:
Kind: serial, Port: /dev/ttyUSB0, Baud: 115200

func (*MockSerial) Open

func (m *MockSerial) Open(ctx context.Context) error

Open simulates opening the serial port and starts the read loop.

func (*MockSerial) Reset

func (m *MockSerial) Reset()

Reset allows the mock to be reused for another test.

type MockSerialOption

type MockSerialOption func(*MockSerial)

MockSerialOption configures a MockSerial device.

func WithConfig

func WithConfig(cfg sources.Config) MockSerialOption

WithConfig sets the serial configuration.

Example

ExampleWithConfig demonstrates configuring serial parameters.

package main

import (
	"fmt"

	"github.com/cthonicasoftware/astrolabe-cli/internal/sources"
	"github.com/cthonicasoftware/astrolabe-cli/internal/testutil"
)

func main() {
	// Configure mock with specific serial settings
	cfg := sources.Config{
		Port:        "/dev/ttyUSB0",
		Baud:        9600,
		Parity:      "E",
		DataBits:    7,
		StopBits:    "2",
		FlowControl: "hardware",
	}

	mock := testutil.NewMockSerial(testutil.WithConfig(cfg))

	meta := mock.Meta()
	fmt.Printf("Port: %s, Baud: %d\n", meta.Port, meta.Baud)

}
Output:
Port: /dev/ttyUSB0, Baud: 9600

func WithFrameDelay

func WithFrameDelay(delay time.Duration) MockSerialOption

WithFrameDelay sets the delay between frame emissions.

func WithFrames

func WithFrames(frames [][]byte) MockSerialOption

WithFrames sets the sequence of byte frames to emit.

func WithReadError

func WithReadError(err error) MockSerialOption

WithReadError configures an error to return after all frames are exhausted.

Jump to

Keyboard shortcuts

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