1// Copyright 2017 syzkaller project authors. All rights reserved.
2// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3
4package csource
5
6import (
7	"fmt"
8	"reflect"
9	"testing"
10)
11
12func TestParseOptions(t *testing.T) {
13	for _, opts := range allOptionsSingle("linux") {
14		data := opts.Serialize()
15		got, err := DeserializeOptions(data)
16		if err != nil {
17			t.Fatalf("failed to deserialize %q: %v", data, err)
18		}
19		if !reflect.DeepEqual(got, opts) {
20			t.Fatalf("opts changed, got:\n%+v\nwant:\n%+v", got, opts)
21		}
22	}
23}
24
25func TestParseOptionsCanned(t *testing.T) {
26	// Dashboard stores csource options with syzkaller reproducers,
27	// so we need to be able to parse old formats.
28	// nolint: lll
29	canned := map[string]Options{
30		`{"threaded":true,"collide":true,"repeat":true,"procs":10,"sandbox":"namespace",
31		"fault":true,"fault_call":1,"fault_nth":2,"tun":true,"tmpdir":true,"cgroups":true,
32		"netdev":true,"resetnet":true,
33		"segv":true,"waitrepeat":true,"debug":true,"repro":true}`: {
34			Threaded:      true,
35			Collide:       true,
36			Repeat:        true,
37			Procs:         10,
38			Sandbox:       "namespace",
39			Fault:         true,
40			FaultCall:     1,
41			FaultNth:      2,
42			EnableTun:     true,
43			UseTmpDir:     true,
44			EnableCgroups: true,
45			EnableNetdev:  true,
46			ResetNet:      true,
47			HandleSegv:    true,
48			Repro:         true,
49		},
50		"{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}": {
51			Threaded:      true,
52			Collide:       true,
53			Repeat:        true,
54			Procs:         1,
55			Sandbox:       "none",
56			Fault:         false,
57			FaultCall:     -1,
58			FaultNth:      0,
59			EnableTun:     true,
60			UseTmpDir:     true,
61			EnableCgroups: false,
62			HandleSegv:    true,
63			Repro:         false,
64		},
65		"{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}": {
66			Threaded:      true,
67			Collide:       true,
68			Repeat:        true,
69			Procs:         1,
70			Sandbox:       "",
71			Fault:         false,
72			FaultCall:     -1,
73			FaultNth:      0,
74			EnableTun:     true,
75			UseTmpDir:     true,
76			EnableCgroups: false,
77			HandleSegv:    true,
78			Repro:         false,
79		},
80		"{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}": {
81			Threaded:      false,
82			Collide:       true,
83			Repeat:        true,
84			Procs:         1,
85			Sandbox:       "namespace",
86			Fault:         false,
87			FaultCall:     -1,
88			FaultNth:      0,
89			EnableTun:     true,
90			UseTmpDir:     true,
91			EnableCgroups: true,
92			HandleSegv:    true,
93			Repro:         false,
94		},
95	}
96	for data, want := range canned {
97		got, err := DeserializeOptions([]byte(data))
98		if err != nil {
99			t.Fatalf("failed to deserialize %q: %v", data, err)
100		}
101		if !reflect.DeepEqual(got, want) {
102			t.Fatalf("deserialize %q\ngot:\n%+v\nwant:\n%+v", data, got, want)
103		}
104	}
105}
106
107func allOptionsSingle(OS string) []Options {
108	var opts []Options
109	fields := reflect.TypeOf(Options{}).NumField()
110	for i := 0; i < fields; i++ {
111		// Because of constraints on options, we need some defaults
112		// (e.g. no collide without threaded).
113		opt := Options{
114			Threaded:  true,
115			Repeat:    true,
116			Sandbox:   "none",
117			UseTmpDir: true,
118		}
119		opts = append(opts, enumerateField(OS, opt, i)...)
120	}
121	return opts
122}
123
124func allOptionsPermutations(OS string) []Options {
125	opts := []Options{{}}
126	fields := reflect.TypeOf(Options{}).NumField()
127	for i := 0; i < fields; i++ {
128		var newOpts []Options
129		for _, opt := range opts {
130			newOpts = append(newOpts, enumerateField(OS, opt, i)...)
131		}
132		opts = newOpts
133	}
134	return opts
135}
136
137func enumerateField(OS string, opt Options, field int) []Options {
138	var opts []Options
139	s := reflect.ValueOf(&opt).Elem()
140	fldName := s.Type().Field(field).Name
141	fld := s.Field(field)
142	if fldName == "Sandbox" {
143		for _, sandbox := range []string{"", "none", "setuid", "namespace"} {
144			fld.SetString(sandbox)
145			opts = append(opts, opt)
146		}
147	} else if fldName == "Procs" {
148		for _, procs := range []int64{1, 4} {
149			fld.SetInt(procs)
150			opts = append(opts, opt)
151		}
152	} else if fldName == "RepeatTimes" {
153		for _, times := range []int64{0, 10} {
154			fld.SetInt(times)
155			opts = append(opts, opt)
156		}
157	} else if fldName == "FaultCall" {
158		opts = append(opts, opt)
159	} else if fldName == "FaultNth" {
160		opts = append(opts, opt)
161	} else if fld.Kind() == reflect.Bool {
162		for _, v := range []bool{false, true} {
163			fld.SetBool(v)
164			opts = append(opts, opt)
165		}
166	} else {
167		panic(fmt.Sprintf("field '%v' is not boolean", fldName))
168	}
169	var checked []Options
170	for _, opt := range opts {
171		if err := opt.Check(OS); err == nil {
172			checked = append(checked, opt)
173		}
174	}
175	return checked
176}
177