| // Copyright 2026 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| //go:build linux | |
| package serial | |
| import ( | |
| "bytes" | |
| "errors" | |
| "fmt" | |
| "os" | |
| "testing" | |
| "github.com/creack/pty" | |
| "golang.org/x/sys/unix" | |
| ) | |
| func openPTY(t *testing.T) (*os.File, *os.File) { | |
| t.Helper() | |
| primary, secondary, err := pty.Open() | |
| if err != nil { | |
| if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.ENOENT) || errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) || errors.Is(err, unix.ENXIO) { | |
| t.Skipf("skipping test: pty unavailable in environment: %v", err) | |
| } | |
| t.Fatalf("pty.Open() failed: %v", err) | |
| } | |
| return primary, secondary | |
| } | |
| func TestOpenBaudRate(t *testing.T) { | |
| testCases := []struct { | |
| baudRate int | |
| wantRate uint32 | |
| }{ | |
| {baudRate: 9600, wantRate: unix.B9600}, | |
| {baudRate: 19200, wantRate: unix.B19200}, | |
| {baudRate: 38400, wantRate: unix.B38400}, | |
| {baudRate: 57600, wantRate: unix.B57600}, | |
| {baudRate: 115200, wantRate: unix.B115200}, | |
| {baudRate: 230400, wantRate: unix.B230400}, | |
| {baudRate: 460800, wantRate: unix.B460800}, | |
| {baudRate: 921600, wantRate: unix.B921600}, | |
| {baudRate: 1000000, wantRate: unix.B1000000}, | |
| {baudRate: 1500000, wantRate: unix.B1500000}, | |
| {baudRate: 2000000, wantRate: unix.B2000000}, | |
| } | |
| for _, tc := range testCases { | |
| t.Run(fmt.Sprintf("%d", tc.baudRate), func(t *testing.T) { | |
| primary, secondary := openPTY(t) | |
| defer primary.Close() | |
| defer secondary.Close() | |
| port, err := OpenWithOptions(secondary.Name(), tc.baudRate) | |
| if err != nil { | |
| t.Fatalf("OpenWithOptions(%q, %d) failed: %v", secondary.Name(), tc.baudRate, err) | |
| } | |
| defer port.Close() | |
| termios, err := unix.IoctlGetTermios(int(secondary.Fd()), unix.TCGETS) | |
| if err != nil { | |
| t.Fatalf("unix.IoctlGetTermios failed: %v", err) | |
| } | |
| if got := termios.Cflag & unix.CBAUD; got != tc.wantRate { | |
| t.Errorf("Cflag CBAUD mismatch: got %#x, want %#x", got, tc.wantRate) | |
| } | |
| if termios.Cflag&unix.CS8 == 0 { | |
| t.Errorf("expected CS8 to be set in Cflag: %#x", termios.Cflag) | |
| } | |
| if termios.Cflag&unix.CREAD == 0 { | |
| t.Errorf("expected CREAD to be set in Cflag: %#x", termios.Cflag) | |
| } | |
| if termios.Cflag&unix.CLOCAL == 0 { | |
| t.Errorf("expected CLOCAL to be set in Cflag: %#x", termios.Cflag) | |
| } | |
| if termios.Cflag&unix.PARENB != 0 { | |
| t.Errorf("expected PARENB not to be set in Cflag: %#x", termios.Cflag) | |
| } | |
| if termios.Cflag&unix.CSTOPB != 0 { | |
| t.Errorf("expected CSTOPB not to be set in Cflag: %#x", termios.Cflag) | |
| } | |
| }) | |
| } | |
| } | |
| func TestOpenUnsupportedBaudRate(t *testing.T) { | |
| if _, err := OpenWithOptions("/dev/null", 12345); err == nil { | |
| t.Errorf("expected error for unsupported baud rate 12345, got nil") | |
| } | |
| } | |
| func TestOpenReadWrite(t *testing.T) { | |
| primary, secondary := openPTY(t) | |
| defer primary.Close() | |
| defer secondary.Close() | |
| port, err := Open(secondary.Name()) | |
| if err != nil { | |
| t.Fatalf("Open(%q) failed: %v", secondary.Name(), err) | |
| } | |
| defer port.Close() | |
| // Write to primary, read from port | |
| msg := []byte("hello serial\n") | |
| if _, err := primary.Write(msg); err != nil { | |
| t.Fatalf("primary.Write() failed: %v", err) | |
| } | |
| buf := make([]byte, len(msg)) | |
| if _, err := port.Read(buf); err != nil { | |
| t.Fatalf("port.Read() failed: %v", err) | |
| } | |
| if !bytes.Equal(buf, msg) { | |
| t.Errorf("read mismatch: got %q, want %q", string(buf), string(msg)) | |
| } | |
| } |