blob: ad90d6aafd46939c3d857bca209d509e6638d57c [file] [log] [blame] [edit]
// Copyright 2016 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package state
import (
"sort"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/syzkaller/pkg/rpctype"
)
type TestState struct {
t *testing.T
dir string
state *State
}
func MakeTestState(t *testing.T) *TestState {
t.Parallel()
dir := t.TempDir()
state, err := Make(dir)
if err != nil {
t.Fatalf("failed to make state: %v", err)
}
return &TestState{t, dir, state}
}
func (ts *TestState) Reload() {
ts.state.Flush()
state, err := Make(ts.dir)
if err != nil {
ts.t.Fatalf("failed to make state: %v", err)
}
ts.state = state
}
func (ts *TestState) Connect(name, domain string, fresh bool, calls []string, corpus [][]byte) {
ts.t.Helper()
if err := ts.state.Connect(name, domain, fresh, calls, corpus); err != nil {
ts.t.Fatalf("Connect failed: %v", err)
}
}
func (ts *TestState) Sync(name string, add [][]byte, del []string) (string, []rpctype.HubInput, int) {
ts.t.Helper()
domain, inputs, pending, err := ts.state.Sync(name, add, del)
if err != nil {
ts.t.Fatalf("Sync failed: %v", err)
}
sort.Slice(inputs, func(i, j int) bool {
if inputs[i].Domain != inputs[j].Domain {
return inputs[i].Domain < inputs[j].Domain
}
return string(inputs[i].Prog) < string(inputs[j].Prog)
})
return domain, inputs, pending
}
func (ts *TestState) AddRepro(name string, repro []byte) {
ts.t.Helper()
if err := ts.state.AddRepro(name, repro); err != nil {
ts.t.Fatalf("AddRepro failed: %v", err)
}
}
func (ts *TestState) PendingRepro(name string) []byte {
ts.t.Helper()
repro, err := ts.state.PendingRepro(name)
if err != nil {
ts.t.Fatalf("PendingRepro failed: %v", err)
}
return repro
}
func TestBasic(t *testing.T) {
st := MakeTestState(t)
if _, _, _, err := st.state.Sync("foo", nil, nil); err == nil {
t.Fatalf("synced with unconnected manager")
}
calls := []string{"read", "write"}
st.Connect("foo", "", false, calls, nil)
st.Sync("foo", nil, nil)
}
func TestRepro(t *testing.T) {
st := MakeTestState(t)
st.Connect("foo", "", false, []string{"open", "read", "write"}, nil)
st.Connect("bar", "", false, []string{"open", "read", "close"}, nil)
expectPendingRepro := func(name, result string) {
t.Helper()
repro := st.PendingRepro(name)
if string(repro) != result {
t.Fatalf("PendingRepro returned %q, want %q", string(repro), result)
}
}
expectPendingRepro("foo", "")
expectPendingRepro("bar", "")
st.AddRepro("foo", []byte("open()"))
expectPendingRepro("foo", "")
expectPendingRepro("bar", "open()")
expectPendingRepro("bar", "")
// This repro is already present.
st.AddRepro("bar", []byte("open()"))
st.AddRepro("bar", []byte("read()"))
st.AddRepro("bar", []byte("open()\nread()"))
// This does not satisfy foo's call set.
st.AddRepro("bar", []byte("close()"))
expectPendingRepro("bar", "")
// Check how persistence works.
st.Reload()
st.Connect("foo", "", false, []string{"open", "read", "write"}, nil)
st.Connect("bar", "", false, []string{"open", "read", "close"}, nil)
expectPendingRepro("bar", "")
expectPendingRepro("foo", "read()")
expectPendingRepro("foo", "open()\nread()")
expectPendingRepro("foo", "")
}
func TestDomain(t *testing.T) {
st := MakeTestState(t)
st.Connect("client0", "", false, []string{"open"}, nil)
st.Connect("client1", "domain1", false, []string{"open"}, nil)
st.Connect("client2", "domain2", false, []string{"open"}, nil)
st.Connect("client3", "domain3", false, []string{"open"}, nil)
{
domain, inputs, pending := st.Sync("client0", [][]byte{[]byte("open(0x0)")}, nil)
if domain != "" || len(inputs) != 0 || pending != 0 {
t.Fatalf("bad sync result: %v, %v, %v", domain, inputs, pending)
}
}
{
domain, inputs, pending := st.Sync("client0", [][]byte{[]byte("open(0x1)")}, nil)
if domain != "" || len(inputs) != 0 || pending != 0 {
t.Fatalf("bad sync result: %v, %v, %v", domain, inputs, pending)
}
}
{
domain, inputs, pending := st.Sync("client1", [][]byte{[]byte("open(0x2)"), []byte("open(0x1)")}, nil)
if domain != "domain1" || pending != 0 {
t.Fatalf("bad sync result: %v, %v, %v", domain, inputs, pending)
}
if diff := cmp.Diff(inputs, []rpctype.HubInput{
{Domain: "", Prog: []byte("open(0x0)")},
}); diff != "" {
t.Fatal(diff)
}
}
{
_, inputs, _ := st.Sync("client2", [][]byte{[]byte("open(0x3)")}, nil)
if diff := cmp.Diff(inputs, []rpctype.HubInput{
{Domain: "", Prog: []byte("open(0x0)")},
{Domain: "domain1", Prog: []byte("open(0x1)")},
{Domain: "domain1", Prog: []byte("open(0x2)")},
}); diff != "" {
t.Fatal(diff)
}
}
{
_, inputs, _ := st.Sync("client0", nil, nil)
if diff := cmp.Diff(inputs, []rpctype.HubInput{
{Domain: "domain1", Prog: []byte("open(0x2)")},
{Domain: "domain2", Prog: []byte("open(0x3)")},
}); diff != "" {
t.Fatal(diff)
}
}
st.Reload()
st.Connect("client3", "domain3", false, []string{"open"}, nil)
{
_, inputs, _ := st.Sync("client3", nil, nil)
if diff := cmp.Diff(inputs, []rpctype.HubInput{
{Domain: "", Prog: []byte("open(0x0)")},
{Domain: "domain1", Prog: []byte("open(0x1)")},
{Domain: "domain1", Prog: []byte("open(0x2)")},
{Domain: "domain2", Prog: []byte("open(0x3)")},
}); diff != "" {
t.Fatal(diff)
}
}
}