mirror of
https://github.com/postmannen/ctrl.git
synced 2024-12-14 12:37:31 +00:00
69995f76ca
updated references removed tui client removed ringbuffer persist store removed ringbuffer enabled audit logging moved audit logging to message readers disabled goreleaser update readme, cbor, zstd removed request type ping and pong update readme testing with cmd.WaitDelay for clicommand fixed readme removed ringbuffer flag default serialization set to cbor, default compression set to zstd, fixed race, removed event type ack and nack, also removed from subject. Fixed file stat error for copy log file removed remaining elements of the event type removed comments renamed toRingbufferCh to samToSendCh renamed directSAMSCh ro samSendLocalCh removed handler interface agpl3 license added license-change.md
655 lines
16 KiB
Go
655 lines
16 KiB
Go
package ctrl
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
natsserver "github.com/nats-io/nats-server/v2/server"
|
|
)
|
|
|
|
var logging = flag.Bool("logging", false, "set to true to enable the normal logger of the package")
|
|
var persistTmp = flag.Bool("persistTmp", false, "set to true to persist the tmp folder")
|
|
|
|
var tstSrv *server
|
|
var tstConf *Configuration
|
|
var tstNats *natsserver.Server
|
|
var tstTempDir string
|
|
|
|
func TestMain(m *testing.M) {
|
|
flag.Parse()
|
|
|
|
if *persistTmp {
|
|
tstTempDir = "tmp"
|
|
} else {
|
|
tstTempDir = os.TempDir()
|
|
}
|
|
|
|
// NB: Forcing this for now.
|
|
tstTempDir = "tmp"
|
|
|
|
tstNats = newNatsServerForTesting(42222)
|
|
if err := natsserver.Run(tstNats); err != nil {
|
|
natsserver.PrintAndDie(err.Error())
|
|
}
|
|
|
|
tstSrv, tstConf = newServerForTesting("127.0.0.1:42222", tstTempDir)
|
|
tstSrv.Start()
|
|
|
|
exitCode := m.Run()
|
|
|
|
tstSrv.Stop()
|
|
tstNats.Shutdown()
|
|
|
|
os.Exit(exitCode)
|
|
}
|
|
|
|
func newServerForTesting(addressAndPort string, testFolder string) (*server, *Configuration) {
|
|
|
|
// Start ctrl instance
|
|
// ---------------------------------------
|
|
// tempdir := t.TempDir()
|
|
|
|
// Create the config to run a ctrl instance.
|
|
//tempdir := "./tmp"
|
|
conf := newConfigurationDefaults()
|
|
if *logging {
|
|
conf.LogLevel = "warning"
|
|
}
|
|
conf.BrokerAddress = addressAndPort
|
|
conf.NodeName = "central"
|
|
conf.CentralNodeName = "central"
|
|
conf.ConfigFolder = testFolder
|
|
conf.SubscribersDataFolder = testFolder
|
|
conf.SocketFolder = testFolder
|
|
conf.SubscribersDataFolder = testFolder
|
|
conf.DatabaseFolder = testFolder
|
|
conf.IsCentralErrorLogger = true
|
|
conf.IsCentralAuth = true
|
|
conf.EnableDebug = false
|
|
conf.LogLevel = "none"
|
|
|
|
ctrlServer, err := NewServer(&conf, "test")
|
|
if err != nil {
|
|
log.Fatalf(" * failed: could not start the ctrl instance %v\n", err)
|
|
}
|
|
|
|
return ctrlServer, &conf
|
|
}
|
|
|
|
// Start up the nats-server message broker for testing purposes.
|
|
func newNatsServerForTesting(port int) *natsserver.Server {
|
|
// Start up the nats-server message broker.
|
|
nsOpt := &natsserver.Options{
|
|
Host: "127.0.0.1",
|
|
Port: port,
|
|
}
|
|
|
|
ns, err := natsserver.NewServer(nsOpt)
|
|
if err != nil {
|
|
log.Fatalf(" * failed: could not start the nats-server %v\n", err)
|
|
}
|
|
|
|
return ns
|
|
}
|
|
|
|
// Write message to socket for testing purposes.
|
|
func writeMsgsToSocketTest(conf *Configuration, messages []Message, t *testing.T) {
|
|
js, err := json.Marshal(messages)
|
|
if err != nil {
|
|
t.Fatalf("writeMsgsToSocketTest: %v\n ", err)
|
|
}
|
|
|
|
socket, err := net.Dial("unix", filepath.Join(conf.SocketFolder, "ctrl.sock"))
|
|
if err != nil {
|
|
t.Fatalf(" * failed: could to open socket file for writing: %v\n", err)
|
|
}
|
|
defer socket.Close()
|
|
|
|
_, err = socket.Write(js)
|
|
if err != nil {
|
|
t.Fatalf(" * failed: could not write to socket: %v\n", err)
|
|
}
|
|
|
|
}
|
|
|
|
func TestRequest(t *testing.T) {
|
|
if !*logging {
|
|
log.SetOutput(io.Discard)
|
|
}
|
|
|
|
type containsOrEquals int
|
|
const (
|
|
REQTestContains containsOrEquals = iota
|
|
REQTestEquals containsOrEquals = iota
|
|
fileContains containsOrEquals = iota
|
|
)
|
|
|
|
type viaSocketOrCh int
|
|
const (
|
|
viaSocket viaSocketOrCh = iota
|
|
viaCh viaSocketOrCh = iota
|
|
)
|
|
|
|
type test struct {
|
|
info string
|
|
message Message
|
|
want []byte
|
|
containsOrEquals
|
|
viaSocketOrCh
|
|
}
|
|
|
|
// Web server for testing.
|
|
{
|
|
h := func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("web page content"))
|
|
}
|
|
http.HandleFunc("/", h)
|
|
|
|
go func() {
|
|
http.ListenAndServe(":10080", nil)
|
|
}()
|
|
}
|
|
|
|
tests := []test{
|
|
{
|
|
info: "REQHello test",
|
|
message: Message{
|
|
ToNode: "errorCentral",
|
|
FromNode: "errorCentral",
|
|
Method: REQErrorLog,
|
|
MethodArgs: []string{},
|
|
MethodTimeout: 5,
|
|
Data: []byte("error data"),
|
|
// ReplyMethod: REQTest,
|
|
Directory: "error_log",
|
|
FileName: "error.results",
|
|
}, want: []byte("error data"),
|
|
containsOrEquals: fileContains,
|
|
viaSocketOrCh: viaCh,
|
|
},
|
|
{
|
|
info: "REQHello test",
|
|
message: Message{
|
|
ToNode: "central",
|
|
FromNode: "central",
|
|
Method: REQHello,
|
|
MethodArgs: []string{},
|
|
MethodTimeout: 5,
|
|
// ReplyMethod: REQTest,
|
|
Directory: "test",
|
|
FileName: "hello.results",
|
|
}, want: []byte("Received hello from \"central\""),
|
|
containsOrEquals: fileContains,
|
|
viaSocketOrCh: viaCh,
|
|
},
|
|
{
|
|
info: "REQCliCommand test, echo gris",
|
|
message: Message{
|
|
ToNode: "central",
|
|
FromNode: "central",
|
|
Method: REQCliCommand,
|
|
MethodArgs: []string{"bash", "-c", "echo gris"},
|
|
MethodTimeout: 5,
|
|
ReplyMethod: REQTest,
|
|
}, want: []byte("gris"),
|
|
containsOrEquals: REQTestEquals,
|
|
viaSocketOrCh: viaCh,
|
|
},
|
|
{
|
|
info: "REQCliCommand test via socket, echo sau",
|
|
message: Message{
|
|
ToNode: "central",
|
|
FromNode: "central",
|
|
Method: REQCliCommand,
|
|
MethodArgs: []string{"bash", "-c", "echo sau"},
|
|
MethodTimeout: 5,
|
|
ReplyMethod: REQTest,
|
|
}, want: []byte("sau"),
|
|
containsOrEquals: REQTestEquals,
|
|
viaSocketOrCh: viaSocket,
|
|
},
|
|
{
|
|
info: "REQCliCommand test, echo sau, result in file",
|
|
message: Message{
|
|
ToNode: "central",
|
|
FromNode: "central",
|
|
Method: REQCliCommand,
|
|
MethodArgs: []string{"bash", "-c", "echo sau"},
|
|
MethodTimeout: 5,
|
|
ReplyMethod: REQToFile,
|
|
Directory: "test",
|
|
FileName: "file1.result",
|
|
}, want: []byte("sau"),
|
|
containsOrEquals: fileContains,
|
|
viaSocketOrCh: viaCh,
|
|
},
|
|
{
|
|
info: "REQCliCommand test, echo several, result in file continous",
|
|
message: Message{
|
|
ToNode: "central",
|
|
FromNode: "central",
|
|
Method: REQCliCommand,
|
|
MethodArgs: []string{"bash", "-c", "echo giraff && echo sau && echo apekatt"},
|
|
MethodTimeout: 5,
|
|
ReplyMethod: REQToFile,
|
|
Directory: "test",
|
|
FileName: "file2.result",
|
|
}, want: []byte("sau"),
|
|
containsOrEquals: fileContains,
|
|
viaSocketOrCh: viaCh,
|
|
},
|
|
{
|
|
info: "REQHttpGet test, localhost:10080",
|
|
message: Message{
|
|
ToNode: "central",
|
|
FromNode: "central",
|
|
Method: REQHttpGet,
|
|
MethodArgs: []string{"http://localhost:10080"},
|
|
MethodTimeout: 5,
|
|
ReplyMethod: REQTest,
|
|
}, want: []byte("web page content"),
|
|
containsOrEquals: REQTestContains,
|
|
viaSocketOrCh: viaCh,
|
|
},
|
|
{
|
|
info: "REQOpProcessList test",
|
|
message: Message{
|
|
ToNode: "central",
|
|
FromNode: "central",
|
|
Method: REQOpProcessList,
|
|
MethodArgs: []string{},
|
|
MethodTimeout: 5,
|
|
ReplyMethod: REQTest,
|
|
}, want: []byte("central.REQHttpGet"),
|
|
containsOrEquals: REQTestContains,
|
|
viaSocketOrCh: viaCh,
|
|
},
|
|
}
|
|
|
|
// Range over the tests defined, and execute them, one at a time.
|
|
for _, tt := range tests {
|
|
switch tt.viaSocketOrCh {
|
|
case viaCh:
|
|
sam, err := newSubjectAndMessage(tt.message)
|
|
if err != nil {
|
|
t.Fatalf("newSubjectAndMessage failed: %v\n", err)
|
|
}
|
|
|
|
tstSrv.samToSendCh <- []subjectAndMessage{sam}
|
|
|
|
case viaSocket:
|
|
msgs := []Message{tt.message}
|
|
writeMsgsToSocketTest(tstConf, msgs, t)
|
|
|
|
}
|
|
|
|
switch tt.containsOrEquals {
|
|
case REQTestEquals:
|
|
result := <-tstSrv.errorKernel.testCh
|
|
resStr := string(result)
|
|
resStr = strings.TrimSuffix(resStr, "\n")
|
|
result = []byte(resStr)
|
|
|
|
if !bytes.Equal(result, tt.want) {
|
|
t.Fatalf(" \U0001F631 [FAILED] :%v : want: %v, got: %v\n", tt.info, string(tt.want), string(result))
|
|
}
|
|
t.Logf(" \U0001f600 [SUCCESS] : %v\n", tt.info)
|
|
|
|
case REQTestContains:
|
|
result := <-tstSrv.errorKernel.testCh
|
|
resStr := string(result)
|
|
resStr = strings.TrimSuffix(resStr, "\n")
|
|
result = []byte(resStr)
|
|
|
|
if !strings.Contains(string(result), string(tt.want)) {
|
|
t.Fatalf(" \U0001F631 [FAILED] :%v : want: %v, got: %v\n", tt.info, string(tt.want), string(result))
|
|
}
|
|
t.Logf(" \U0001f600 [SUCCESS] : %v\n", tt.info)
|
|
|
|
case fileContains:
|
|
resultFile := filepath.Join(tstConf.SubscribersDataFolder, tt.message.Directory, string(tt.message.FromNode), tt.message.FileName)
|
|
|
|
found, err := findStringInFileTest(string(tt.want), resultFile, tstConf, t)
|
|
if err != nil || found == false {
|
|
t.Fatalf(" \U0001F631 [FAILED] : %v: %v\n", tt.info, err)
|
|
|
|
}
|
|
|
|
t.Logf(" \U0001f600 [SUCCESS] : %v\n", tt.info)
|
|
}
|
|
}
|
|
|
|
// --- Other REQ tests that does not fit well into the general table above.
|
|
|
|
checkREQTailFileTest(tstSrv, tstConf, t, tstTempDir)
|
|
checkMetricValuesTest(tstSrv, tstConf, t, tstTempDir)
|
|
checkErrorKernelMalformedJSONtest(tstSrv, tstConf, t, tstTempDir)
|
|
checkREQCopySrc(tstSrv, tstConf, t, tstTempDir)
|
|
}
|
|
|
|
// Check the tailing of files type.
|
|
func checkREQTailFileTest(ctrlServer *server, conf *Configuration, t *testing.T, tmpDir string) error {
|
|
// Create a file with some content.
|
|
fp := filepath.Join(tmpDir, "test.file")
|
|
fh, err := os.OpenFile(fp, os.O_APPEND|os.O_RDWR|os.O_CREATE|os.O_SYNC, 0660)
|
|
if err != nil {
|
|
return fmt.Errorf(" * failed: unable to open temporary file: %v", err)
|
|
}
|
|
defer fh.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
// Write content to the file with specified intervals.
|
|
go func() {
|
|
for i := 1; i <= 10; i++ {
|
|
_, err = fh.Write([]byte("some file content\n"))
|
|
if err != nil {
|
|
fmt.Printf(" * failed: writing to temporary file: %v\n", err)
|
|
}
|
|
fh.Sync()
|
|
time.Sleep(time.Millisecond * 500)
|
|
|
|
// Check if we've received a done, else default to continuing.
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
// no done received, we're continuing.
|
|
}
|
|
|
|
}
|
|
}()
|
|
|
|
s := `[
|
|
{
|
|
"directory": "tail-files",
|
|
"fileName": "fileName.result",
|
|
"toNode": "central",
|
|
"methodArgs": ["` + fp + `"],
|
|
"method":"REQTailFile",
|
|
"ACKTimeout":5,
|
|
"retries":3,
|
|
"methodTimeout": 10
|
|
}
|
|
]`
|
|
|
|
writeToSocketTest(conf, s, t)
|
|
|
|
resultFile := filepath.Join(conf.SubscribersDataFolder, "tail-files", "central", "fileName.result")
|
|
|
|
// Wait n times for result file to be created.
|
|
n := 50
|
|
for i := 0; i <= n; i++ {
|
|
_, err := os.Stat(resultFile)
|
|
if os.IsNotExist(err) {
|
|
time.Sleep(time.Millisecond * 100)
|
|
continue
|
|
}
|
|
|
|
if os.IsNotExist(err) && i >= n {
|
|
cancel()
|
|
return fmt.Errorf(" \U0001F631 [FAILED] : checkREQTailFileTest: no result file created for request within the given time")
|
|
}
|
|
}
|
|
|
|
cancel()
|
|
|
|
_, err = findStringInFileTest("some file content", resultFile, conf, t)
|
|
if err != nil {
|
|
return fmt.Errorf(" \U0001F631 [FAILED] : checkREQTailFileTest: %v", err)
|
|
}
|
|
|
|
t.Logf(" \U0001f600 [SUCCESS] : checkREQTailFileTest\n")
|
|
return nil
|
|
}
|
|
|
|
// Check the file copier.
|
|
func checkREQCopySrc(ctrlServer *server, conf *Configuration, t *testing.T, tmpDir string) error {
|
|
testFiles := 5
|
|
|
|
for i := 1; i <= testFiles; i++ {
|
|
|
|
// Create a file with some content.
|
|
srcFileName := fmt.Sprintf("copysrc%v.file", i)
|
|
srcfp := filepath.Join(tmpDir, srcFileName)
|
|
fh, err := os.OpenFile(srcfp, os.O_APPEND|os.O_RDWR|os.O_CREATE|os.O_SYNC, 0660)
|
|
if err != nil {
|
|
t.Fatalf(" \U0001F631 [FAILED] : checkREQCopySrc: unable to open temporary file: %v", err)
|
|
}
|
|
defer fh.Close()
|
|
|
|
// Write content to the file.
|
|
|
|
_, err = fh.Write([]byte("some file content\n"))
|
|
if err != nil {
|
|
t.Fatalf(" \U0001F631 [FAILED] : checkREQCopySrc: writing to temporary file: %v\n", err)
|
|
}
|
|
|
|
dstFileName := fmt.Sprintf("copydst%v.file", i)
|
|
dstfp := filepath.Join(tmpDir, dstFileName)
|
|
|
|
s := `[
|
|
{
|
|
"toNode": "central",
|
|
"method":"REQCopySrc",
|
|
"methodArgs": ["` + srcfp + `","central","` + dstfp + `","20","10"],
|
|
"ACKTimeout":5,
|
|
"retries":3,
|
|
"methodTimeout": 10
|
|
}
|
|
]`
|
|
|
|
writeToSocketTest(conf, s, t)
|
|
|
|
// Wait n times for result file to be created.
|
|
n := 50
|
|
for i := 0; i <= n; i++ {
|
|
_, err := os.Stat(dstfp)
|
|
if os.IsNotExist(err) {
|
|
time.Sleep(time.Millisecond * 100)
|
|
continue
|
|
}
|
|
|
|
if os.IsNotExist(err) && i >= n {
|
|
t.Fatalf(" \U0001F631 [FAILED] : checkREQCopySrc: no result file created for request within the given time")
|
|
}
|
|
}
|
|
|
|
t.Logf(" \U0001f600 [SUCCESS] : src=%v, dst=%v", srcfp, dstfp)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkMetricValuesTest(ctrlServer *server, conf *Configuration, t *testing.T, tempDir string) error {
|
|
mfs, err := ctrlServer.metrics.promRegistry.Gather()
|
|
if err != nil {
|
|
return fmt.Errorf("error: promRegistry.gathering: %v", mfs)
|
|
}
|
|
|
|
if len(mfs) <= 0 {
|
|
return fmt.Errorf("error: promRegistry.gathering: did not find any metric families: %v", mfs)
|
|
}
|
|
|
|
found := false
|
|
for _, mf := range mfs {
|
|
if mf.GetName() == "ctrl_processes_total" {
|
|
found = true
|
|
|
|
m := mf.GetMetric()
|
|
|
|
if m[0].Gauge.GetValue() <= 0 {
|
|
return fmt.Errorf("error: promRegistry.gathering: did not find any running processes in metric for processes_total : %v", m[0].Gauge.GetValue())
|
|
}
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return fmt.Errorf("error: promRegistry.gathering: did not find specified metric processes_total")
|
|
}
|
|
|
|
t.Logf(" \U0001f600 [SUCCESS] : checkMetricValuesTest")
|
|
|
|
return nil
|
|
}
|
|
|
|
// Check errorKernel
|
|
func checkErrorKernelMalformedJSONtest(ctrlServer *server, conf *Configuration, t *testing.T, tempDir string) error {
|
|
|
|
// JSON message with error, missing brace.
|
|
m := `[
|
|
{
|
|
"directory": "some dir",
|
|
"fileName":"someext",
|
|
"toNode": "somenode",
|
|
"data": ["some data"],
|
|
"method": "REQErrorLog"
|
|
missing brace here.....
|
|
]`
|
|
|
|
writeToSocketTest(conf, m, t)
|
|
|
|
resultFile := filepath.Join(conf.SubscribersDataFolder, "errorLog", "errorCentral", "error.log")
|
|
|
|
// Wait n times for error file to be created.
|
|
n := 50
|
|
for i := 0; i <= n; i++ {
|
|
_, err := os.Stat(resultFile)
|
|
if os.IsNotExist(err) {
|
|
time.Sleep(time.Millisecond * 100)
|
|
continue
|
|
}
|
|
|
|
if os.IsNotExist(err) && i >= n {
|
|
return fmt.Errorf(" \U0001F631 [FAILED] : checkErrorKernelMalformedJSONtest: no result file created for request within the given time")
|
|
}
|
|
}
|
|
|
|
// Start checking if the result file is being updated.
|
|
chUpdated := make(chan bool)
|
|
go checkFileUpdated(resultFile, chUpdated)
|
|
|
|
// We wait 5 seconds for an update, or else we fail.
|
|
ticker := time.NewTicker(time.Second * 5)
|
|
|
|
for {
|
|
select {
|
|
case <-chUpdated:
|
|
// We got an update, so we continue to check if we find the string we're
|
|
// looking for.
|
|
found, err := findStringInFileTest("error: malformed json", resultFile, conf, t)
|
|
if !found && err != nil {
|
|
return fmt.Errorf(" \U0001F631 [FAILED] : checkErrorKernelMalformedJSONtest: %v", err)
|
|
}
|
|
|
|
if !found && err == nil {
|
|
continue
|
|
}
|
|
|
|
if found {
|
|
t.Logf(" \U0001f600 [SUCCESS] : checkErrorKernelMalformedJSONtest")
|
|
return nil
|
|
}
|
|
case <-ticker.C:
|
|
return fmt.Errorf(" * failed: did not get an update in the errorKernel log file")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if file are getting updated with new content.
|
|
func checkFileUpdated(fileRealPath string, fileUpdated chan bool) {
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
log.Println("Failed fsnotify.NewWatcher")
|
|
return
|
|
}
|
|
defer watcher.Close()
|
|
|
|
done := make(chan bool)
|
|
go func() {
|
|
//Give a true value to updated so it reads the file the first time.
|
|
fileUpdated <- true
|
|
for {
|
|
select {
|
|
case event := <-watcher.Events:
|
|
log.Println("event:", event)
|
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
|
log.Println("modified file:", event.Name)
|
|
//testing with an update chan to get updates
|
|
fileUpdated <- true
|
|
}
|
|
case err := <-watcher.Errors:
|
|
log.Println("error:", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
err = watcher.Add(fileRealPath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
<-done
|
|
}
|
|
|
|
// Check if a file contains the given string.
|
|
func findStringInFileTest(want string, fileName string, conf *Configuration, t *testing.T) (bool, error) {
|
|
// Wait n seconds for the results file to be created
|
|
n := 50
|
|
|
|
for i := 0; i <= n; i++ {
|
|
_, err := os.Stat(fileName)
|
|
if os.IsNotExist(err) {
|
|
time.Sleep(time.Millisecond * 100)
|
|
continue
|
|
}
|
|
|
|
if os.IsNotExist(err) && i >= n {
|
|
return false, fmt.Errorf(" * failed: no result file created for request within the given time\n")
|
|
}
|
|
}
|
|
|
|
fh, err := os.Open(fileName)
|
|
if err != nil {
|
|
return false, fmt.Errorf(" * failed: could not open result file: %v", err)
|
|
}
|
|
|
|
result, err := io.ReadAll(fh)
|
|
if err != nil {
|
|
return false, fmt.Errorf(" * failed: could not read result file: %v", err)
|
|
}
|
|
|
|
found := strings.Contains(string(result), want)
|
|
if !found {
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Write message to socket for testing purposes.
|
|
func writeToSocketTest(conf *Configuration, messageText string, t *testing.T) {
|
|
socket, err := net.Dial("unix", filepath.Join(conf.SocketFolder, "ctrl.sock"))
|
|
if err != nil {
|
|
t.Fatalf(" * failed: could to open socket file for writing: %v\n", err)
|
|
}
|
|
defer socket.Close()
|
|
|
|
_, err = socket.Write([]byte(messageText))
|
|
if err != nil {
|
|
t.Fatalf(" * failed: could not write to socket: %v\n", err)
|
|
}
|
|
|
|
}
|