Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions contracts/process/process.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package process

import (
"context"
"io"
"time"
)

// OutputType represents the type of output stream produced by a running process.
type OutputType int

const (
// OutputTypeStdout indicates output written to the standard output stream.
OutputTypeStdout OutputType = iota

// OutputTypeStderr indicates output written to the standard error stream.
OutputTypeStderr
)

// OnOutputFunc is a callback function invoked when the process produces output.
// The typ parameter indicates whether the data came from stdout or stderr,
// and line contains the raw output bytes (typically a line of text).
type OnOutputFunc func(typ OutputType, line []byte)

// Process defines an interface for configuring and running external processes.
//
// Implementations are mutable and should not be reused concurrently.
// Each method modifies the same underlying process configuration.
type Process interface {
// Env adds or overrides environment variables for the process.
// Modifies the current process configuration.
Env(vars map[string]string) Process

// Input sets the stdin source for the process.
// By default, processes run without stdin input.
Input(in io.Reader) Process
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in io.Reader vs string, which is better?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I think using io.Reader as input is more flexible than using a plain string. It allows the process to handle various data sources efficiently—such as files, network streams, or buffers—without needing to load everything into memory. This makes the code more versatile and scalable.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

io.Reader is better because it’s the standard way in Go to handle input from different sources like files, terminal, or network. If you only use string, the function becomes less flexible. When you already have a string, you can easily turn it into an io.Reader with strings.NewReader().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, can we add some quick functions to build different io.Reader?

Copy link
Copy Markdown
Member Author

@krishankumar01 krishankumar01 Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hwbrzzl Each stdlib type has its own way to get an io.Reader, so we don’t need to handle it:

  • stringstrings.NewReader("hello")
  • []bytebytes.NewReader(data)
  • *os.File → already implements io.Reader

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, the quick functions is optional for now.


// Path sets the working directory where the process will be executed.
Path(path string) Process

// Quietly suppresses all process output, discarding both stdout and stderr.
Quietly() Process

// OnOutput registers a handler to receive stdout and stderr output
// while the process runs. Multiple handlers may be chained depending
// on the implementation.
OnOutput(handler OnOutputFunc) Process

// Run starts the process, waits for it to complete, and returns the result.
// It returns an error if the process cannot be started or if execution fails.
Run(name string, arg ...string) (Result, error)

// Start begins running the process asynchronously and returns a Running
// handle to monitor and control its execution. The caller must later
// wait or terminate the process explicitly.
Start(name string, arg ...string) (Running, error)

// Timeout sets a maximum execution duration for the process.
// If the timeout is exceeded, the process will be terminated.
// A zero duration disables the timeout.
Timeout(timeout time.Duration) Process

// TTY attaches the process to a pseudo-terminal, enabling interactive
// behavior (such as programs that require a TTY for input/output).
TTY() Process

// WithContext binds the process lifecycle to the provided context.
// If the context is canceled or reaches its deadline, the process
// will be terminated. When combined with Timeout, the earlier of
// the two deadlines takes effect.
WithContext(ctx context.Context) Process
}
34 changes: 34 additions & 0 deletions contracts/process/result.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package process

// Result represents the outcome of a finished process execution.
// It provides access to exit status, captured output, and helper
// methods for inspecting process behavior.
type Result interface {
// Successful reports whether the process exited with a zero exit code.
Successful() bool

// Failed reports whether the process exited with a non-zero exit code.
Failed() bool

// ExitCode returns the process exit code. A zero value typically
// indicates success, while non-zero indicates failure.
ExitCode() int

// Output returns the full contents written to stdout by the process.
Output() string

// ErrorOutput returns the full contents written to stderr by the process.
ErrorOutput() string

// Command returns the full command string used to start the process,
// including program name and arguments.
Command() string

// SeeInOutput reports whether the given substring is present
// in the process stdout output.
SeeInOutput(needle string) bool

// SeeInErrorOutput reports whether the given substring is present
// in the process stderr output.
SeeInErrorOutput(needle string) bool
}
49 changes: 49 additions & 0 deletions contracts/process/running.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package process

import (
"os"
"time"
)

// Running represents a handle to a process that has been started and is still active.
// It provides methods for inspecting process state, retrieving output, and controlling
// its lifecycle.
type Running interface {
// PID returns the operating system process ID.
PID() int

// Running reports whether the process still exists according to the OS.
//
// NOTE: This may return true for a "zombie" process (terminated but not
// reaped). You must eventually call Wait() to reap the process and release
// resources. A common pattern is to poll Running() in a goroutine and then
// call Wait() in the main flow to ensure cleanup.
Running() bool

// Output returns the complete stdout captured from the process so far.
Output() string

// ErrorOutput returns the complete stderr captured from the process so far.
ErrorOutput() string

// LatestOutput returns the most recent chunk of stdout produced by the process.
// The definition of "latest" is implementation dependent.
LatestOutput() string

// LatestErrorOutput returns the most recent chunk of stderr produced by the process.
// The definition of "latest" is implementation dependent.
LatestErrorOutput() string

// Wait blocks until the process exits and returns its final Result.
// This call is required to reap the process and release system resources.
Wait() Result

// Stop attempts to gracefully stop the process by sending the provided signal
// (defaulting to SIGTERM). If the process does not exit within the given timeout,
// it is forcibly killed (SIGKILL).
Stop(timeout time.Duration, sig ...os.Signal) error

// Signal sends the given signal to the process. Returns an error if the process
// has not been started or no longer exists.
Signal(sig os.Signal) error
}
69 changes: 69 additions & 0 deletions mocks/process/OnOutputFunc.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading