blob: 096d52dc8995f04ad26ad8d05d66726aae8c99bc [file] [log] [blame]
// Copyright 2021 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 tracing
// +build tracing
package provider
import (
"context"
"errors"
"fmt"
"syscall/zx"
"syscall/zx/zxwait"
"testing"
"time"
"unsafe"
fidlprovider "fidl/fuchsia/tracing/provider"
"go.fuchsia.dev/fuchsia/src/connectivity/network/netstack/tracing/trace"
)
const (
vmoSize uint64 = 4096
fifoSizeInPackets = 4
)
type fifoEvent struct {
packet packet
}
func newTestSession(t *testing.T, vmoSize uint64) (*session, zx.VMO, zx.Handle, chan fifoEvent) {
t.Helper()
vmo, err := zx.NewVMO(vmoSize, zx.VMOOption(0))
if err != nil {
t.Fatalf("zx.NewVMO failed: %s", err)
}
var fifoLocal, fifoRemote zx.Handle
if status := zx.Sys_fifo_create(fifoSizeInPackets, packetSize, 0, &fifoLocal, &fifoRemote); status != zx.ErrOk {
t.Fatalf("zx.Sys_fifo_create failed: %s", status)
}
ch := make(chan fifoEvent)
go readFifo(t, fifoLocal, ch)
s := newSession()
t.Cleanup(func() {
select {
case event, ok := <-ch:
if ok {
t.Fatalf("fifo had an unexpected event: %#v", event)
}
case <-time.After(10 * time.Second):
t.Fatalf("fifo is not closed")
}
if s.vmo.Handle().IsValid() {
t.Fatalf("vmo is not closed")
}
if s.fifo.IsValid() {
t.Fatalf("fifo is not closed")
}
})
return s, vmo, fifoRemote, ch
}
func recvFifoPacket(fifo zx.Handle, packet *packet) error {
const count = 1
var actual uint
if status := zx.Sys_fifo_read(fifo, packetSize, unsafe.Pointer(packet), count, &actual); status != zx.ErrOk {
return &zx.Error{Status: status, Text: "zx.Sys_fifo_read"}
}
if actual != count {
return fmt.Errorf("zx.Sys_fifo_read: actual = %d (want = %d)", actual, count)
}
return nil
}
func readFifo(t *testing.T, fifo zx.Handle, ch chan fifoEvent) {
signals := zx.Signals(zx.SignalFIFOReadable | zx.SignalFIFOPeerClosed)
for {
obs, err := zxwait.WaitContext(context.Background(), fifo, signals)
if err != nil {
t.Fatalf("zxwait.wait error: %s", err)
}
var packet packet
if obs&zx.SignalFIFOReadable != 0 {
if err := recvFifoPacket(fifo, &packet); err != nil {
t.Fatalf("recvFifoPacket error: %s", err)
}
ch <- fifoEvent{packet: packet}
} else if obs&zx.SignalFIFOPeerClosed != 0 {
close(ch)
fifo.Close()
return
} else {
t.Fatalf("zxwait.WaitContext returned unexpectedly: obs=%x", obs)
}
}
}
func mapBufferHeader(t *testing.T, vmo zx.VMO, vmoSize uint64) *trace.BufferHeader {
vaddr, err := zx.VMARRoot.Map(0, vmo, 0, vmoSize, zx.VMFlagPermRead)
if err != nil {
t.Fatalf("VMARRoot.Map(_, _, _, %d, _) = %s", vmoSize, err)
}
return (*trace.BufferHeader)(unsafe.Pointer(uintptr(vaddr)))
}
func TestSessionOneshotInitializeTerminate(t *testing.T) {
s, vmo, fifo, _ := newTestSession(t, vmoSize)
mode := fidlprovider.BufferingModeOneshot
categories := []string{"foo", "bar"}
if err := s.initializeEngine(mode, vmo, fifo, categories); err != nil {
t.Fatalf("initializeEngine(%d, _, _, _) = %s", mode, err)
}
header := mapBufferHeader(t, vmo, vmoSize)
if got, want := header.Magic(), trace.BufferHeaderMagic; got != want {
t.Errorf("got header.Magic() = %#x, want = %#x", got, want)
}
if err := s.terminateEngine(); err != nil {
t.Fatalf("terminateEngine() = %s", err)
}
}
func TestSessionOneshotInitializeStartTerminate(t *testing.T) {
s, vmo, fifo, ch := newTestSession(t, vmoSize)
mode := fidlprovider.BufferingModeOneshot
categories := []string{"foo", "bar"}
if err := s.initializeEngine(mode, vmo, fifo, categories); err != nil {
t.Fatalf("initializeEngine(%d, _, _, %v) = %s", mode, categories, err)
}
if err := s.startEngine(fidlprovider.BufferDispositionClearEntire, []string{}); err != nil {
t.Fatalf("startEngine(_, _) = %s", err)
}
event, ok := <-ch
if !ok {
t.Fatalf("fifo was closed unexpectedly")
}
if got, want := providerRequest(event.packet.request), providerStarted; got != want {
t.Fatalf("got providerRequest(%d) = %d, want = %d", event.packet.request, got, want)
}
if got, want := uint32(event.packet.data32), providerFifoProtocolVersion; got != want {
t.Fatalf("got event.packet.data32 = %d, want = %d", got, want)
}
if err := s.terminateEngine(); err != nil {
t.Fatalf("terminateEngine() = %s", err)
}
}
func TestSessionCircularNotSupported(t *testing.T) {
s, vmo, fifo, _ := newTestSession(t, vmoSize)
mode := fidlprovider.BufferingModeCircular
categories := []string{"foo", "bar"}
if got, want := s.initializeEngine(mode, vmo, fifo, categories), trace.ErrNotSupported; !errors.Is(got, want) {
t.Fatalf("got initializeEngine(%d, _, _, _) = %s, want = %s", mode, got, want)
}
}
func TestSessionStreamingNotSupported(t *testing.T) {
s, vmo, fifo, _ := newTestSession(t, vmoSize)
mode := fidlprovider.BufferingModeStreaming
categories := []string{"foo", "bar"}
if got, want := s.initializeEngine(mode, vmo, fifo, categories), trace.ErrNotSupported; !errors.Is(got, want) {
t.Fatalf("got initializeEngine(%d, _, _, _) = %s, want = %s", mode, got, want)
}
}
func TestIsCategoryEnabled(t *testing.T) {
testCases := []struct {
testName string
initCategories []string
enabledCategories []string
disabledCategories []string
}{
{
"empty",
nil,
[]string{"foo", "bar"}, // any category is enabled.
nil,
},
{
"foo bar",
[]string{"foo", "bar"},
[]string{"foo", "bar"},
[]string{"baz"},
},
}
for _, tc := range testCases {
t.Run(tc.testName, func(t *testing.T) {
s, vmo, fifo, _ := newTestSession(t, vmoSize)
mode := fidlprovider.BufferingModeOneshot
if err := s.initializeEngine(mode, vmo, fifo, tc.initCategories); err != nil {
t.Fatalf("initializeEngine(%d, _, _, %v) = %s", mode, tc.initCategories, err)
}
for _, category := range tc.enabledCategories {
if !s.IsCategoryEnabled(category) {
t.Errorf("category %s should be enabled", category)
}
}
for _, category := range tc.disabledCategories {
if s.IsCategoryEnabled(category) {
t.Errorf("category %s should not be enabled", category)
}
}
if err := s.terminateEngine(); err != nil {
t.Fatalf("terminateEngine() = %s", err)
}
})
}
}