package client import ( "bytes" "errors" "io" "sync" "testing" "time" ) func TestStreamBufferRoundTrip(t *testing.T) { s := newStreamBuffer() want := []byte("usbip frame") if _, err := s.Write(want); err != nil { t.Fatalf("Write: %v", err) } got := make([]byte, len(want)) if _, err := io.ReadFull(s, got); err != nil { t.Fatalf("ReadFull: %v", err) } if !bytes.Equal(got, want) { t.Errorf("read %q, want %q", got, want) } } // The whole point of replacing io.Pipe: a write must return immediately even // when nobody is reading, because it happens on the WebSocket read loop. func TestStreamBufferWriteNeverBlocks(t *testing.T) { s := newStreamBuffer() done := make(chan struct{}) go func() { defer close(done) for i := 0; i < 100; i++ { if _, err := s.Write(make([]byte, 1024)); err != nil { t.Errorf("Write %d: %v", i, err) return } } }() select { case <-done: case <-time.After(2 * time.Second): t.Fatal("writes blocked with no reader — this is what froze the client") } if got := s.Buffered(); got != 100*1024 { t.Errorf("buffered %d bytes, want %d", got, 100*1024) } } func TestStreamBufferReadBlocksUntilData(t *testing.T) { s := newStreamBuffer() read := make(chan []byte, 1) go func() { buf := make([]byte, 4) n, err := s.Read(buf) if err != nil { t.Errorf("Read: %v", err) read <- nil return } read <- buf[:n] }() // Give the reader time to park in Read before any data exists. time.Sleep(50 * time.Millisecond) select { case <-read: t.Fatal("Read returned before data was written") default: } s.Write([]byte("ping")) select { case got := <-read: if string(got) != "ping" { t.Errorf("read %q, want %q", got, "ping") } case <-time.After(time.Second): t.Fatal("Read did not wake up after Write") } } func TestStreamBufferCloseGivesEOFAfterDraining(t *testing.T) { s := newStreamBuffer() s.Write([]byte("tail")) s.Close() // Buffered data must still be readable after Close. got := make([]byte, 4) if _, err := io.ReadFull(s, got); err != nil { t.Fatalf("reading buffered data after Close: %v", err) } if string(got) != "tail" { t.Errorf("read %q, want %q", got, "tail") } if _, err := s.Read(make([]byte, 4)); err != io.EOF { t.Errorf("Read after drain = %v, want io.EOF", err) } } func TestStreamBufferCloseWakesBlockedReader(t *testing.T) { s := newStreamBuffer() errCh := make(chan error, 1) go func() { _, err := s.Read(make([]byte, 4)) errCh <- err }() time.Sleep(50 * time.Millisecond) s.Close() select { case err := <-errCh: if err != io.EOF { t.Errorf("blocked Read woke with %v, want io.EOF", err) } case <-time.After(time.Second): t.Fatal("Close did not wake the blocked reader") } } func TestStreamBufferOverflowFailsInsteadOfGrowing(t *testing.T) { s := newStreamBufferLimit(1024) if _, err := s.Write(make([]byte, 1000)); err != nil { t.Fatalf("first write: %v", err) } if _, err := s.Write(make([]byte, 100)); !errors.Is(err, ErrStreamOverflow) { t.Fatalf("overflowing write = %v, want ErrStreamOverflow", err) } // Further writes keep failing rather than silently resuming. if _, err := s.Write([]byte("x")); !errors.Is(err, ErrStreamOverflow) { t.Errorf("write after overflow = %v, want ErrStreamOverflow", err) } // Buffered data is still drainable, then the error surfaces. if _, err := io.ReadFull(s, make([]byte, 1000)); err != nil { t.Fatalf("draining after overflow: %v", err) } if _, err := s.Read(make([]byte, 4)); !errors.Is(err, ErrStreamOverflow) { t.Errorf("Read after drain = %v, want ErrStreamOverflow", err) } } func TestStreamBufferWriteAfterClose(t *testing.T) { s := newStreamBuffer() s.Close() if _, err := s.Write([]byte("late")); err != io.ErrClosedPipe { t.Errorf("Write after Close = %v, want io.ErrClosedPipe", err) } } // Concurrent writers and one reader, the shape the share path actually has. func TestStreamBufferConcurrent(t *testing.T) { s := newStreamBuffer() const writers = 8 const perWriter = 200 const chunk = 64 var wg sync.WaitGroup wg.Add(writers) for i := 0; i < writers; i++ { go func() { defer wg.Done() for j := 0; j < perWriter; j++ { if _, err := s.Write(make([]byte, chunk)); err != nil { t.Errorf("Write: %v", err) return } } }() } total := writers * perWriter * chunk readDone := make(chan int, 1) go func() { got := 0 buf := make([]byte, 128) for got < total { n, err := s.Read(buf) if err != nil { break } got += n } readDone <- got }() wg.Wait() select { case got := <-readDone: if got != total { t.Errorf("read %d bytes, want %d", got, total) } case <-time.After(5 * time.Second): t.Fatal("concurrent read/write did not finish") } }