github.com/Jeffail/benthos/v3@v3.65.0/lib/processor/subprocess_test.go (about) 1 package processor 2 3 import ( 4 "os" 5 "path" 6 "reflect" 7 "testing" 8 "time" 9 10 "github.com/Jeffail/benthos/v3/lib/log" 11 "github.com/Jeffail/benthos/v3/lib/message" 12 "github.com/Jeffail/benthos/v3/lib/metrics" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 ) 16 17 func TestSubprocessWithSed(t *testing.T) { 18 t.Skip("disabled for now") 19 20 conf := NewConfig() 21 conf.Type = TypeSubprocess 22 conf.Subprocess.Name = "sed" 23 conf.Subprocess.Args = []string{"s/foo/bar/g", "-u"} 24 25 proc, err := New(conf, nil, log.Noop(), metrics.Noop()) 26 if err != nil { 27 t.Skipf("Not sure if this is due to missing executable: %v", err) 28 } 29 30 exp := [][]byte{ 31 []byte(`hello bar world`), 32 []byte(`hello baz world`), 33 []byte(`bar`), 34 } 35 msgIn := message.New([][]byte{ 36 []byte(`hello foo world`), 37 []byte(`hello baz world`), 38 []byte(`foo`), 39 }) 40 msgs, res := proc.ProcessMessage(msgIn) 41 if len(msgs) != 1 { 42 t.Fatal("Wrong count of messages") 43 } 44 if res != nil { 45 t.Fatalf("Non-nil result: %v", res.Error()) 46 } 47 48 if act := message.GetAllBytes(msgs[0]); !reflect.DeepEqual(exp, act) { 49 t.Errorf("Wrong results: %s != %s", act, exp) 50 } 51 52 proc.CloseAsync() 53 if err := proc.WaitForClose(time.Second); err != nil { 54 t.Error(err) 55 } 56 } 57 58 func TestSubprocessWithCat(t *testing.T) { 59 t.Skip("disabled for now") 60 61 conf := NewConfig() 62 conf.Type = TypeSubprocess 63 conf.Subprocess.Name = "cat" 64 65 proc, err := New(conf, nil, log.Noop(), metrics.Noop()) 66 if err != nil { 67 t.Skipf("Not sure if this is due to missing executable: %v", err) 68 } 69 70 exp := [][]byte{ 71 []byte(`hello bar world`), 72 []byte(`hello baz world`), 73 []byte(`bar`), 74 } 75 msgIn := message.New([][]byte{ 76 []byte(`hello bar world`), 77 []byte(`hello baz world`), 78 []byte(`bar`), 79 }) 80 msgs, res := proc.ProcessMessage(msgIn) 81 if len(msgs) != 1 { 82 t.Fatal("Wrong count of messages") 83 } 84 if res != nil { 85 t.Fatalf("Non-nil result: %v", res.Error()) 86 } 87 88 if act := message.GetAllBytes(msgs[0]); !reflect.DeepEqual(exp, act) { 89 t.Errorf("Wrong results: %s != %s", act, exp) 90 } 91 92 proc.CloseAsync() 93 if err := proc.WaitForClose(time.Second); err != nil { 94 t.Error(err) 95 } 96 } 97 98 func TestSubprocessLineBreaks(t *testing.T) { 99 t.Skip("disabled for now") 100 101 conf := NewConfig() 102 conf.Type = TypeSubprocess 103 conf.Subprocess.Name = "sed" 104 conf.Subprocess.Args = []string{`s/\(^$\)\|\(foo\)/bar/`, "-u"} 105 106 proc, err := New(conf, nil, log.Noop(), metrics.Noop()) 107 if err != nil { 108 t.Skipf("Not sure if this is due to missing executable: %v", err) 109 } 110 111 exp := [][]byte{ 112 []byte("hello bar\nbar world"), 113 []byte("hello bar bar world"), 114 []byte("hello bar\nbar world\n"), 115 []byte("bar"), 116 []byte("hello bar\nbar\nbar world\n"), 117 } 118 msgIn := message.New([][]byte{ 119 []byte("hello foo\nfoo world"), 120 []byte("hello foo bar world"), 121 []byte("hello foo\nfoo world\n"), 122 []byte(""), 123 []byte("hello foo\n\nfoo world\n"), 124 }) 125 msgs, res := proc.ProcessMessage(msgIn) 126 if len(msgs) != 1 { 127 t.Fatalf("Wrong count of messages %d", len(msgs)) 128 } 129 if res != nil { 130 t.Fatalf("Non-nil result: %v", res.Error()) 131 } 132 133 if act := message.GetAllBytes(msgs[0]); !reflect.DeepEqual(exp, act) { 134 t.Errorf("Wrong results: %s != %s", act, exp) 135 } 136 137 proc.CloseAsync() 138 if err := proc.WaitForClose(time.Second); err != nil { 139 t.Error(err) 140 } 141 } 142 143 func TestSubprocessWithErrors(t *testing.T) { 144 conf := NewConfig() 145 conf.Type = TypeSubprocess 146 conf.Subprocess.Name = "sh" 147 conf.Subprocess.Args = []string{"-c", "cat 1>&2"} 148 149 proc, err := New(conf, nil, log.Noop(), metrics.Noop()) 150 if err != nil { 151 t.Skipf("Not sure if this is due to missing executable: %v", err) 152 } 153 154 msgIn := message.New([][]byte{ 155 []byte(`hello bar world`), 156 }) 157 158 msgs, _ := proc.ProcessMessage(msgIn) 159 160 if !HasFailed(msgs[0].Get(0)) { 161 t.Errorf("Expected subprocessor to fail") 162 } 163 164 proc.CloseAsync() 165 if err := proc.WaitForClose(time.Second); err != nil { 166 t.Error(err) 167 } 168 } 169 170 func testProgram(t *testing.T, program string) string { 171 t.Helper() 172 173 dir := t.TempDir() 174 175 pathStr := path.Join(dir, "main.go") 176 require.NoError(t, os.WriteFile(pathStr, []byte(program), 0o666)) 177 178 return pathStr 179 } 180 181 func TestSubprocessHappy(t *testing.T) { 182 filePath := testProgram(t, `package main 183 184 import ( 185 "bufio" 186 "encoding/binary" 187 "flag" 188 "fmt" 189 "log" 190 "os" 191 "strings" 192 ) 193 194 func lengthPrefixedUInt32BESplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) { 195 const prefixBytes int = 4 196 if atEOF { 197 return 0, nil, nil 198 } 199 if len(data) < prefixBytes { 200 // request more data 201 return 0, nil, nil 202 } 203 l := binary.BigEndian.Uint32(data) 204 bytesToRead := int(l) 205 206 if len(data)-prefixBytes >= bytesToRead { 207 return prefixBytes + bytesToRead, data[prefixBytes : prefixBytes+bytesToRead], nil 208 } else { 209 // request more data 210 return 0, nil, nil 211 } 212 } 213 214 var stdinCodec *string = flag.String("stdinCodec", "lines", "format to use for input") 215 var stdoutCodec *string = flag.String("stdoutCodec", "lines", "format for use for output") 216 217 func main() { 218 flag.Parse() 219 220 scanner := bufio.NewScanner(os.Stdin) 221 if *stdinCodec == "length_prefixed_uint32_be" { 222 scanner.Split(lengthPrefixedUInt32BESplitFunc) 223 } 224 225 lenBuf := make([]byte, 4) 226 for scanner.Scan() { 227 res := strings.ToUpper(scanner.Text()) 228 switch *stdoutCodec { 229 case "length_prefixed_uint32_be": 230 buf := []byte(res) 231 binary.BigEndian.PutUint32(lenBuf, uint32(len(buf))) 232 _, _ = os.Stdout.Write(lenBuf) 233 _, _ = os.Stdout.Write(buf) 234 break 235 case "netstring": 236 fmt.Printf("%d:%s,",len(res),res) 237 break 238 default: 239 fmt.Println(res) 240 } 241 } 242 243 if err := scanner.Err(); err != nil { 244 log.Println(err) 245 } 246 } 247 `) 248 f := func(formatSend string, formatRecv string, extra bool) { 249 conf := NewConfig() 250 conf.Type = TypeSubprocess 251 conf.Subprocess.Name = "go" 252 conf.Subprocess.Args = []string{"run", filePath, "-stdinCodec", formatSend, "-stdoutCodec", formatRecv} 253 conf.Subprocess.CodecSend = formatSend 254 conf.Subprocess.CodecRecv = formatRecv 255 256 proc, err := New(conf, nil, log.Noop(), metrics.Noop()) 257 require.NoError(t, err) 258 259 exp := [][]byte{ 260 []byte(`FOO`), 261 []byte(`FOÖ`), 262 []byte(`BAR`), 263 []byte(`BAZ`), 264 } 265 if extra { 266 exp = append(exp, []byte(``), []byte("|{O\n\r\nO}|")) 267 } 268 269 msgIn := message.New([][]byte{ 270 []byte(`foo`), 271 []byte(`foö`), 272 []byte(`bar`), 273 []byte(`baz`), 274 }) 275 if extra { 276 msgIn.Append(message.NewPart([]byte(``))) 277 msgIn.Append(message.NewPart([]byte("|{o\n\r\no}|"))) 278 } 279 280 msgs, res := proc.ProcessMessage(msgIn) 281 require.Len(t, msgs, 1) 282 require.Nil(t, res) 283 284 for i := 0; i < msgIn.Len(); i++ { 285 assert.Empty(t, msgs[0].Get(i).Metadata().Get(FailFlagKey)) 286 } 287 assert.Equal(t, exp, message.GetAllBytes(msgs[0])) 288 289 proc.CloseAsync() 290 assert.NoError(t, proc.WaitForClose(time.Second)) 291 } 292 f("lines", "lines", false) 293 f("length_prefixed_uint32_be", "lines", false) 294 f("lines", "length_prefixed_uint32_be", false) 295 f("length_prefixed_uint32_be", "netstring", true) 296 f("length_prefixed_uint32_be", "length_prefixed_uint32_be", true) 297 }