From 050a9baadba49e9f681df4b95302d2c9f49ccc1b Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Mon, 25 Dec 2023 15:01:40 -0800 Subject: [PATCH] Add doc and tests for ioconn package (#291) --- libplugin/ioconn/cmd.go | 9 ++++++++ libplugin/ioconn/cmd_test.go | 31 +++++++++++++++++++++++++++ libplugin/ioconn/conn.go | 14 ++++++++++++- libplugin/ioconn/conn_test.go | 29 +++++++++++++++++++++++++ libplugin/ioconn/listener.go | 3 +++ libplugin/ioconn/listener_test.go | 35 +++++++++++++++++++++++++++++++ 6 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 libplugin/ioconn/cmd_test.go create mode 100644 libplugin/ioconn/conn_test.go create mode 100644 libplugin/ioconn/listener_test.go diff --git a/libplugin/ioconn/cmd.go b/libplugin/ioconn/cmd.go index cb68dbfc..b134ba68 100644 --- a/libplugin/ioconn/cmd.go +++ b/libplugin/ioconn/cmd.go @@ -11,6 +11,10 @@ type cmdconn struct { cmd *exec.Cmd } +// Close closes the cmdconn and releases any associated resources. +// It first closes the underlying connection and then kills the process if it is running. +// If an error occurs during the closing of the connection, that error is returned. +// If the process is running and cannot be killed, an error is returned. func (c *cmdconn) Close() error { err := c.conn.Close() @@ -21,6 +25,11 @@ func (c *cmdconn) Close() error { return err } +// DialCmd is a function that establishes a connection to a command's standard input, output, and error streams. +// It takes a *exec.Cmd as input and returns a net.Conn, io.ReadCloser, and error. +// The net.Conn represents the connection to the command's standard input and output streams. +// The io.ReadCloser represents the command's standard error stream. +// The error represents any error that occurred during the connection establishment. func DialCmd(cmd *exec.Cmd) (net.Conn, io.ReadCloser, error) { in, err := cmd.StdoutPipe() if err != nil { diff --git a/libplugin/ioconn/cmd_test.go b/libplugin/ioconn/cmd_test.go new file mode 100644 index 00000000..617378dc --- /dev/null +++ b/libplugin/ioconn/cmd_test.go @@ -0,0 +1,31 @@ +//go:build linux + +package ioconn_test + +import ( + "os/exec" + "testing" + + "github.com/tg123/sshpiper/libplugin/ioconn" +) + +func TestDialCmd(t *testing.T) { + cmd := exec.Command("cat") + + conn, _, err := ioconn.DialCmd(cmd) + if err != nil { + t.Errorf("DialCmd returned an error: %v", err) + } + defer conn.Close() + + go func() { + _, _ = conn.Write([]byte("world")) + }() + + buf := make([]byte, 5) + _, _ = conn.Read(buf) + + if string(buf) != "world" { + t.Errorf("unexpected string read: %v", string(buf)) + } +} diff --git a/libplugin/ioconn/conn.go b/libplugin/ioconn/conn.go index 5306ff7e..448a5459 100644 --- a/libplugin/ioconn/conn.go +++ b/libplugin/ioconn/conn.go @@ -22,7 +22,19 @@ type conn struct { out io.WriteCloser } +// Dial creates a new network connection using the provided input and output streams. +// It returns a net.Conn interface and an error, if any. +// The input stream is used for reading data from the connection, +// and the output stream is used for writing data to the connection. func Dial(in io.ReadCloser, out io.WriteCloser) (net.Conn, error) { + if in == nil { + return nil, fmt.Errorf("input stream is nil") + } + + if out == nil { + return nil, fmt.Errorf("output stream is nil") + } + return dial(in, out), nil } @@ -55,7 +67,7 @@ func (c *conn) Close() error { } if outerr == nil { - return outerr + return inerr } return fmt.Errorf("io close error in: %v, out: %v", inerr, outerr) diff --git a/libplugin/ioconn/conn_test.go b/libplugin/ioconn/conn_test.go new file mode 100644 index 00000000..3ca1d347 --- /dev/null +++ b/libplugin/ioconn/conn_test.go @@ -0,0 +1,29 @@ +package ioconn_test + +import ( + "io" + "testing" + + "github.com/tg123/sshpiper/libplugin/ioconn" +) + +func TestDial(t *testing.T) { + in, out := io.Pipe() + + conn, err := ioconn.Dial(in, out) + if err != nil { + t.Errorf("Dial returned an error: %v", err) + } + defer conn.Close() + + go func() { + + _, _ = conn.Write([]byte("hello")) + }() + buf := make([]byte, 5) + _, _ = conn.Read(buf) + + if string(buf) != "hello" { + t.Errorf("unexpected string read: %v", string(buf)) + } +} diff --git a/libplugin/ioconn/listener.go b/libplugin/ioconn/listener.go index 1d33d7c4..a4f35ddd 100644 --- a/libplugin/ioconn/listener.go +++ b/libplugin/ioconn/listener.go @@ -26,6 +26,9 @@ func (l *singleConnListener) Close() error { return l.conn.Close() } +// ListenFromSingleIO creates a net.Listener from a single input/output connection. +// It takes an io.ReadCloser and an io.WriteCloser as parameters and returns a net.Listener and an error. +// The returned net.Listener can be used to accept incoming connections. func ListenFromSingleIO(in io.ReadCloser, out io.WriteCloser) (net.Listener, error) { l := &singleConnListener{ conn{in, out}, diff --git a/libplugin/ioconn/listener_test.go b/libplugin/ioconn/listener_test.go new file mode 100644 index 00000000..fcc5e42e --- /dev/null +++ b/libplugin/ioconn/listener_test.go @@ -0,0 +1,35 @@ +package ioconn_test + +import ( + "io" + "testing" + + "github.com/tg123/sshpiper/libplugin/ioconn" +) + +func TestListenFromSingleIO(t *testing.T) { + in, out := io.Pipe() + + l, err := ioconn.ListenFromSingleIO(in, out) + if err != nil { + t.Errorf("ListenFromSingleIO returned an error: %v", err) + } + + conn, err := l.Accept() + if err != nil { + t.Errorf("Accept returned an error: %v", err) + } + + defer conn.Close() + defer l.Close() + + go func() { + _, _ = conn.Write([]byte("hello")) + }() + + buf := make([]byte, 5) + _, _ = conn.Read(buf) + if string(buf) != "hello" { + t.Errorf("unexpected string read: %v", string(buf)) + } +}