blob: 13afa84d32b22aa053009f5d55a46aff50f5756b [file] [log] [blame]
// Copyright 2017 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 csource
import (
"fmt"
"reflect"
"testing"
)
func TestParseOptions(t *testing.T) {
for _, opts := range allOptionsSingle("linux") {
data := opts.Serialize()
got, err := DeserializeOptions(data)
if err != nil {
t.Fatalf("failed to deserialize %q: %v", data, err)
}
if !reflect.DeepEqual(got, opts) {
t.Fatalf("opts changed, got:\n%+v\nwant:\n%+v", got, opts)
}
}
}
func TestParseOptionsCanned(t *testing.T) {
// Dashboard stores csource options with syzkaller reproducers,
// so we need to be able to parse old formats.
// nolint: lll
canned := map[string]Options{
`{"threaded":true,"collide":true,"repeat":true,"procs":10,"sandbox":"namespace",
"fault":true,"fault_call":1,"fault_nth":2,"tun":true,"tmpdir":true,"cgroups":true,
"netdev":true,"resetnet":true,
"segv":true,"waitrepeat":true,"debug":true,"repro":true}`: {
Threaded: true,
Collide: true,
Repeat: true,
Procs: 10,
Sandbox: "namespace",
Fault: true,
FaultCall: 1,
FaultNth: 2,
EnableTun: true,
EnableNetDev: true,
EnableNetReset: true,
EnableCgroups: true,
EnableBinfmtMisc: false,
EnableCloseFds: true,
UseTmpDir: true,
HandleSegv: true,
Repro: true,
},
`{"threaded":true,"collide":true,"repeat":true,"procs":10,"sandbox":"android_untrusted_app",
"fault":true,"fault_call":1,"fault_nth":2,"tun":true,"tmpdir":true,"cgroups":true,
"netdev":true,"resetnet":true,
"segv":true,"waitrepeat":true,"debug":true,"repro":true}`: {
Threaded: true,
Collide: true,
Repeat: true,
Procs: 10,
Sandbox: "android_untrusted_app",
Fault: true,
FaultCall: 1,
FaultNth: 2,
EnableTun: true,
EnableNetDev: true,
EnableNetReset: true,
EnableCgroups: true,
EnableBinfmtMisc: false,
EnableCloseFds: true,
UseTmpDir: true,
HandleSegv: true,
Repro: true,
},
"{Threaded:true Collide:true Repeat:true Procs:1 Sandbox:none Fault:false FaultCall:-1 FaultNth:0 EnableTun:true UseTmpDir:true HandleSegv:true WaitRepeat:true Debug:false Repro:false}": {
Threaded: true,
Collide: true,
Repeat: true,
Procs: 1,
Sandbox: "none",
Fault: false,
FaultCall: -1,
FaultNth: 0,
EnableTun: true,
EnableCgroups: false,
EnableBinfmtMisc: false,
EnableCloseFds: true,
UseTmpDir: true,
HandleSegv: true,
Repro: false,
},
"{Threaded:true Collide:true Repeat:true Procs:1 Sandbox: Fault:false FaultCall:-1 FaultNth:0 EnableTun:true UseTmpDir:true HandleSegv:true WaitRepeat:true Debug:false Repro:false}": {
Threaded: true,
Collide: true,
Repeat: true,
Procs: 1,
Sandbox: "",
Fault: false,
FaultCall: -1,
FaultNth: 0,
EnableTun: true,
EnableCgroups: false,
EnableBinfmtMisc: false,
EnableCloseFds: true,
UseTmpDir: true,
HandleSegv: true,
Repro: false,
},
"{Threaded:false Collide:true Repeat:true Procs:1 Sandbox:namespace Fault:false FaultCall:-1 FaultNth:0 EnableTun:true UseTmpDir:true EnableCgroups:true HandleSegv:true WaitRepeat:true Debug:false Repro:false}": {
Threaded: false,
Collide: true,
Repeat: true,
Procs: 1,
Sandbox: "namespace",
Fault: false,
FaultCall: -1,
FaultNth: 0,
EnableTun: true,
EnableCgroups: true,
EnableBinfmtMisc: false,
EnableCloseFds: true,
UseTmpDir: true,
HandleSegv: true,
Repro: false,
},
}
for data, want := range canned {
got, err := DeserializeOptions([]byte(data))
if err != nil {
t.Fatalf("failed to deserialize %q: %v", data, err)
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("deserialize %q\ngot:\n%+v\nwant:\n%+v", data, got, want)
}
}
}
func allOptionsSingle(OS string) []Options {
var opts []Options
fields := reflect.TypeOf(Options{}).NumField()
for i := 0; i < fields; i++ {
// Because of constraints on options, we need some defaults
// (e.g. no collide without threaded).
opt := Options{
Threaded: true,
Repeat: true,
Sandbox: "none",
UseTmpDir: true,
}
opts = append(opts, enumerateField(OS, opt, i)...)
}
return opts
}
func allOptionsPermutations(OS string) []Options {
opts := []Options{{}}
fields := reflect.TypeOf(Options{}).NumField()
for i := 0; i < fields; i++ {
var newOpts []Options
for _, opt := range opts {
newOpts = append(newOpts, enumerateField(OS, opt, i)...)
}
opts = newOpts
}
return opts
}
func enumerateField(OS string, opt Options, field int) []Options {
var opts []Options
s := reflect.ValueOf(&opt).Elem()
fldName := s.Type().Field(field).Name
fld := s.Field(field)
if fldName == "Sandbox" {
for _, sandbox := range []string{"", "none", "setuid", "namespace", "android_untrusted_app"} {
fld.SetString(sandbox)
opts = append(opts, opt)
}
} else if fldName == "Procs" {
for _, procs := range []int64{1, 4} {
fld.SetInt(procs)
opts = append(opts, opt)
}
} else if fldName == "RepeatTimes" {
for _, times := range []int64{0, 10} {
fld.SetInt(times)
opts = append(opts, opt)
}
} else if fldName == "FaultCall" {
opts = append(opts, opt)
} else if fldName == "FaultNth" {
opts = append(opts, opt)
} else if fld.Kind() == reflect.Bool {
for _, v := range []bool{false, true} {
fld.SetBool(v)
opts = append(opts, opt)
}
} else {
panic(fmt.Sprintf("field '%v' is not boolean", fldName))
}
var checked []Options
for _, opt := range opts {
if err := opt.Check(OS); err == nil {
checked = append(checked, opt)
}
}
return checked
}
func TestParseFeaturesFlags(t *testing.T) {
tests := []struct {
Enable string
Disable string
Default bool
Features map[string]bool
}{
{"none", "none", true, map[string]bool{
"tun": true, "net_dev": true, "net_reset": true, "cgroups": true, "binfmt_misc": true, "close_fds": true,
}},
{"none", "none", false, map[string]bool{
"tun": false, "net_dev": false, "net_reset": false, "cgroups": false, "binfmt_misc": false, "close_fds": false,
}},
{"all", "none", true, map[string]bool{
"tun": true, "net_dev": true, "net_reset": true, "cgroups": true, "binfmt_misc": true, "close_fds": true,
}},
{"", "none", true, map[string]bool{
"tun": false, "net_dev": false, "net_reset": false, "cgroups": false, "binfmt_misc": false, "close_fds": false,
}},
{"none", "all", true, map[string]bool{
"tun": false, "net_dev": false, "net_reset": false, "cgroups": false, "binfmt_misc": false, "close_fds": false,
}},
{"none", "", true, map[string]bool{
"tun": true, "net_dev": true, "net_reset": true, "cgroups": true, "binfmt_misc": true, "close_fds": true,
}},
{"tun,net_dev", "none", true, map[string]bool{
"tun": true, "net_dev": true, "net_reset": false, "cgroups": false, "binfmt_misc": false, "close_fds": false,
}},
{"none", "cgroups,net_dev", true, map[string]bool{
"tun": true, "net_dev": false, "net_reset": true, "cgroups": false, "binfmt_misc": true, "close_fds": true,
}},
{"close_fds", "none", true, map[string]bool{
"tun": false, "net_dev": false, "net_reset": false, "cgroups": false, "binfmt_misc": false, "close_fds": true,
}},
}
for i, test := range tests {
features, err := ParseFeaturesFlags(test.Enable, test.Disable, test.Default)
if err != nil {
t.Fatalf("failed to parse features flags: %v", err)
}
for name, feature := range features {
if feature.Enabled != test.Features[name] {
t.Fatalf("test #%v: invalid value for feature flag %s: got %v, want %v",
i, name, feature.Enabled, test.Features[name])
}
}
}
}