package cbpfc

import (
	"bytes"
	"strings"
	"testing"

	"github.com/cilium/ebpf"
	"golang.org/x/net/bpf"
)

func TestFunctionName(t *testing.T) {
	checkName := func(t *testing.T, name string, valid bool) {
		t.Helper()

		_, err := ToC([]bpf.Instruction{bpf.RetA{}}, COpts{
			FunctionName: name,
		})
		if valid && err != nil {
			t.Fatalf("valid function name %s rejected: %v", name, err)
		}
		if !valid {
			requireError(t, err, "invalid FunctionName")
		}
	}

	checkName(t, "", false)
	checkName(t, "0foo", false)
	checkName(t, "0foo\nfoo", false)
	checkName(t, "foo_bar2", true)
	checkName(t, "a2", true)
}

func TestToCNoInline(t *testing.T) {
	elf, err := ToC([]bpf.Instruction{
		bpf.RetConstant{Val: 1},
	}, COpts{
		FunctionName: "filter",
		NoInline:     true,
	})
	if err != nil {
		t.Fatal(err)
	}

	if strings.Contains(elf, "__always_inline__") {
		t.Fatal("generated C source should not contain __always_inline__ when NoInline is true")
	}
}

func TestNoInline(t *testing.T) {
	elf, err := buildC([]bpf.Instruction{
		bpf.RetConstant{Val: 1},
	}, entryPoint, COpts{
		FunctionName: "filter",
		NoInline:     true,
	})
	if err != nil {
		t.Fatal(err)
	}

	spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(elf))
	if err != nil {
		t.Fatal(err)
	}

	in := make([]byte, 14)
	if res := testProg(t, spec.Programs[entryPoint], in); res != match {
		t.Fatalf("expected match, got %v", res)
	}
}

const entryPoint = "xdp_filter"

// cBackend compiles classic BPF to C, which is compiled with clang
func cBackend(tb testing.TB, insns []bpf.Instruction, in []byte, opts backendOpts) result {
	elf, err := buildC(insns, entryPoint, COpts{FunctionName: "filter", PacketStartMaxOffset: opts.offset})
	if err != nil {
		tb.Fatal(err)
	}

	// load ELF
	spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(elf))
	if err != nil {
		tb.Fatal(err)
	}

	return testProg(tb, spec.Programs[entryPoint], in)
}
