blob: 8ec4c2903e0efa7dda9731caab0f7ebcdd0c88a9 [file] [log] [blame]
// Copyright 2022 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.
package botanist
import (
"context"
"io"
"strings"
"sync"
"testing"
"go.fuchsia.dev/fuchsia/tools/lib/streams"
)
type mockWriter struct {
w io.Writer
gotLock chan struct{}
finishWrite chan struct{}
done chan struct{}
}
func (m *mockWriter) Write(data []byte) (int, error) {
m.gotLock <- struct{}{}
// Start writing some of the data.
n, err := m.w.Write(data[:1])
if err != nil {
return n, err
}
// Wait for the signal to finish.
<-m.finishWrite
n2, err := m.w.Write(data[1:])
// Signal that the write is complete.
m.done <- struct{}{}
return n + n2, err
}
func TestLockedWriter(t *testing.T) {
var w strings.Builder
mock := &mockWriter{
w: &w,
gotLock: make(chan struct{}, 1),
finishWrite: make(chan struct{}, 1),
done: make(chan struct{}, 1),
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
writer := NewLockedWriter(ctx, mock)
go writer.Write([]byte("hello"))
// Wait for the lock to be acquired.
<-mock.gotLock
// Attempt to start a new write.
go writer.Write([]byte("bye"))
// Signal for the first write to complete.
mock.finishWrite <- struct{}{}
// Wait for the first write to complete.
<-mock.done
// At this point, the writer should have finished its first write and
// may have started the second.
if !strings.HasPrefix(w.String(), "hello") {
t.Errorf("got %q, want \"hello\"", w.String())
}
// Signal for the second write to complete.
mock.finishWrite <- struct{}{}
// Wait for the second write to complete.
<-mock.done
if w.String() != "hellobye" {
t.Errorf("got %q, want \"hellobye\"", w.String())
}
}
func write(t *testing.T, w io.Writer, data []string) {
for _, subdata := range data {
n, err := w.Write([]byte(subdata))
if err != nil {
t.Errorf("failed to write data %q: %s", subdata, err)
}
if n != len(subdata) {
t.Errorf("received %d bytes for writing, want %d", n, len(subdata))
}
}
}
func TestStdioWriters(t *testing.T) {
var w strings.Builder
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx = streams.ContextWithStdout(ctx, NewLockedWriter(ctx, &w))
stdout1, _, flush1 := NewStdioWriters(ctx, "s1")
stdout2, _, flush2 := NewStdioWriters(ctx, "s2")
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
write(t, stdout1, []string{"h", "i", " ", "f", "r", "o", "m", " st", "dout1", "\n", "extra"})
}()
go func() {
defer wg.Done()
write(t, stdout2, []string{"h", "e", "l", "l", "o ", "says std", "out2\n bye", "\n", "extra2"})
}()
// Wait for the writers to write all their lines.
wg.Wait()
// Flush the rest of the data not ending in a newline.
flush1()
flush2()
for _, expectedLines := range [][]string{
{"hi from stdout1", "extra"},
{"hello says stdout2", "bye", "extra2"},
} {
startIndex := 0
for _, line := range expectedLines {
i := strings.Index(w.String(), line)
if i < startIndex {
t.Errorf("line %q missing or out of order from: %q", line, w.String())
} else {
startIndex = i + len(line)
}
}
}
}