// 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,
			UseTmpDir:     true,
			EnableCgroups: true,
			EnableNetdev:  true,
			ResetNet:      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,
			UseTmpDir:     true,
			EnableCgroups: false,
			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,
			UseTmpDir:     true,
			EnableCgroups: false,
			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,
			UseTmpDir:     true,
			EnableCgroups: 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"} {
			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
}