forked from rosa/hakurei
Supported fields are still rather minimal, but evaluation works, and resulting artifacts cure correctly. Signed-off-by: Ophestra <cat@gensokyo.uk>
365 lines
8.2 KiB
Go
365 lines
8.2 KiB
Go
package azalea_test
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"unique"
|
|
|
|
. "hakurei.app/internal/rosa/azalea"
|
|
)
|
|
|
|
// makeStackCheck creates a stack with a single frame with a single function "f"
|
|
// which calls the check function internally.
|
|
func makeStackCheck(check func(args FArgs) (any, error)) []Frame {
|
|
return []Frame{{Func: map[unique.Handle[Ident]]F{
|
|
unique.Make(Ident("f")): {F: func(
|
|
args FArgs,
|
|
) (v any, set bool, err error) {
|
|
set = true
|
|
v, err = check(args)
|
|
return
|
|
}},
|
|
}}}
|
|
}
|
|
|
|
func TestEvaluate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
name string
|
|
data string
|
|
s []Frame
|
|
want string
|
|
err error
|
|
}{
|
|
{"apply unset", `f { v = unset; }`, makeStackCheck(func(
|
|
args FArgs,
|
|
) (v any, err error) {
|
|
v = "\xfd"
|
|
err = args.Apply(map[unique.Handle[Ident]]any{
|
|
unique.Make(Ident("v")): &v,
|
|
})
|
|
return
|
|
}), "\xfd", nil},
|
|
|
|
{"apply bad type", `f { v = 9; }`, makeStackCheck(func(
|
|
args FArgs,
|
|
) (v any, err error) {
|
|
v = "\xfd"
|
|
err = args.Apply(map[unique.Handle[Ident]]any{
|
|
unique.Make(Ident("v")): &v,
|
|
})
|
|
return
|
|
}), "", TypeError{
|
|
Concrete: reflect.TypeFor[int64](),
|
|
Asserted: reflect.TypeFor[string](),
|
|
}},
|
|
|
|
{"apply undefined", `f { v = 9; }`, makeStackCheck(func(
|
|
args FArgs,
|
|
) (v any, err error) {
|
|
v = "\xfd"
|
|
err = args.Apply(map[unique.Handle[Ident]]any{})
|
|
return
|
|
}), "", EvaluationError{
|
|
Expr: Func{
|
|
Ident: Ident("f"),
|
|
Args: []Arg{
|
|
{K: []Ident{"v"}, V: Val{Int(9)}},
|
|
},
|
|
},
|
|
Err: UndefinedError("v"),
|
|
}},
|
|
|
|
{"apply bound undefined", `f { _v* = "\x00"; v = _v; }`, makeStackCheck(func(
|
|
args FArgs,
|
|
) (v any, err error) {
|
|
v = "\xfd"
|
|
err = args.Apply(map[unique.Handle[Ident]]any{
|
|
unique.Make(Ident("v")): &v,
|
|
})
|
|
return
|
|
}), "\x00", nil},
|
|
|
|
{"undefined function", `f {}`, nil, "", EvaluationError{
|
|
Expr: Func{Ident: "f"},
|
|
Err: UndefinedError("f"),
|
|
}},
|
|
|
|
{"error wrap deep", `f { v = nil; }`, makeStackCheck(func(
|
|
FArgs,
|
|
) (any, error) {
|
|
panic("unreachable")
|
|
}), "", EvaluationError{
|
|
Expr: Ident("nil"),
|
|
Err: UndefinedError("nil"),
|
|
}},
|
|
|
|
{"common inputs", `package name { inputs, v = []; }`, nil, "", EvaluationError{
|
|
Expr: Func{
|
|
Ident: Ident("name"),
|
|
Package: true,
|
|
|
|
Args: []Arg{
|
|
{K: []Ident{
|
|
"inputs",
|
|
"v",
|
|
}, V: Val{Array(nil)}},
|
|
},
|
|
},
|
|
Err: ErrInvalidInputs,
|
|
}},
|
|
|
|
{"bound inputs", `package name { inputs* = []; }`, nil, "", EvaluationError{
|
|
Expr: Func{
|
|
Ident: Ident("name"),
|
|
Package: true,
|
|
|
|
Args: []Arg{
|
|
{K: []Ident{"inputs"}, V: Val{Array(nil)}, R: true},
|
|
},
|
|
},
|
|
Err: ErrInvalidInputs,
|
|
}},
|
|
|
|
{"bound runtime", `package name { runtime* = []; }`, nil, "", EvaluationError{
|
|
Expr: Func{
|
|
Ident: Ident("name"),
|
|
Package: true,
|
|
|
|
Args: []Arg{
|
|
{K: []Ident{"runtime"}, V: Val{Array(nil)}, R: true},
|
|
},
|
|
},
|
|
Err: ErrInvalidInputs,
|
|
}},
|
|
|
|
{"concat inputs", `package name { inputs = ""+""; }`, nil, "", EvaluationError{
|
|
Expr: Func{
|
|
Ident: Ident("name"),
|
|
Package: true,
|
|
|
|
Args: []Arg{
|
|
{K: []Ident{"inputs"}, V: Val{String(""), String("")}},
|
|
},
|
|
},
|
|
Err: ErrInvalidInputs,
|
|
}},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var expr Func
|
|
if e, err := Parse(strings.NewReader(tc.data)); err != nil {
|
|
t.Fatal(err)
|
|
} else if len(e) != 1 {
|
|
t.Fatalf("got expression %#v", e)
|
|
} else {
|
|
expr = e[0].(Func)
|
|
}
|
|
r, set, err := Evaluate[string](nil, tc.s, expr)
|
|
if set != (err == nil) {
|
|
t.Error("Evaluate: unexpected unset")
|
|
}
|
|
|
|
if r != tc.want {
|
|
t.Errorf("Evaluate: %q, want %q", r, tc.want)
|
|
}
|
|
|
|
var errEquals bool
|
|
if errors.As(err, new(TypeError)) {
|
|
errEquals = errors.Is(err, tc.err)
|
|
} else {
|
|
errEquals = reflect.DeepEqual(err, tc.err)
|
|
}
|
|
if !errEquals {
|
|
t.Errorf("Evaluate: error = %v, want %v", err, tc.err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEvaluateGCC(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var gcc Func
|
|
if e, err := Parse(strings.NewReader(sample)); err != nil {
|
|
t.Fatal(err)
|
|
} else {
|
|
gcc = e[0].(Func)
|
|
}
|
|
|
|
var got [3]FArgs
|
|
if r, set, err := Evaluate[string](func(
|
|
name Ident,
|
|
args FArgs,
|
|
) (v any, set bool, err error) {
|
|
v = "\x00"
|
|
set = true
|
|
got[0] = args
|
|
return
|
|
}, []Frame{{
|
|
Func: map[unique.Handle[Ident]]F{
|
|
unique.Make(Ident("remoteTar")): {F: func(
|
|
args FArgs,
|
|
) (v any, set bool, err error) {
|
|
var url, checksum string
|
|
var compress int
|
|
if err = args.Apply(map[unique.Handle[Ident]]any{
|
|
unique.Make(Ident("url")): &url,
|
|
unique.Make(Ident("checksum")): &checksum,
|
|
unique.Make(Ident("compress")): &compress,
|
|
}); err != nil {
|
|
return
|
|
}
|
|
|
|
if compress != 0xcafe {
|
|
err = fmt.Errorf("unexpected compress %#v", compress)
|
|
}
|
|
set = true
|
|
v = url + "?checksum=" + checksum
|
|
return
|
|
}, V: map[unique.Handle[Ident]]any{
|
|
unique.Make(Ident("gzip")): 0xcafe,
|
|
}},
|
|
|
|
unique.Make(Ident("make")): {F: func(
|
|
args FArgs,
|
|
) (v any, set bool, err error) {
|
|
v = args
|
|
set = true
|
|
return
|
|
}},
|
|
|
|
unique.Make(Ident("arch")): {F: func(
|
|
args FArgs,
|
|
) (v any, set bool, err error) {
|
|
set = false
|
|
got[1] = args
|
|
return
|
|
}},
|
|
|
|
unique.Make(Ident("noop")): {F: func(
|
|
args FArgs,
|
|
) (v any, set bool, err error) {
|
|
set = false
|
|
set = true
|
|
got[2] = args
|
|
return
|
|
}, V: map[unique.Handle[Ident]]any{
|
|
unique.Make(Ident("value")): "\xfd",
|
|
}},
|
|
},
|
|
}}, gcc); err != nil {
|
|
t.Fatal(err)
|
|
} else if r != "\x00" {
|
|
t.Fatalf("package: %q", r)
|
|
} else if !set {
|
|
t.Fatal("package: unset")
|
|
}
|
|
|
|
want := [...]FArgs{
|
|
{
|
|
{K: unique.Make(Ident("description")), V: "The GNU Compiler Collection"},
|
|
{K: unique.Make(Ident("website")), V: "https://www.gnu.org/software/gcc"},
|
|
{K: unique.Make(Ident("anitya")), V: int64(6502)},
|
|
|
|
{K: unique.Make(Ident("version")), V: "16.1.0", R: true},
|
|
{K: unique.Make(Ident("source")), V: "https://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-16.1.0/gcc-16.1.0.tar.gz?checksum=4ASoWbxaA2FW7PAB0zzHDPC5XnNhyaAyjtDPpGzceSLeYnEIXsNYZR3PA_Zu5P0K"},
|
|
{K: unique.Make(Ident("patches")), V: []string{"musl-off64_t-loff_t.patch", "musl-legacy-lfs.patch"}},
|
|
|
|
{K: unique.Make(Ident("exclusive")), V: true},
|
|
{K: unique.Make(Ident("exec")), V: FArgs{
|
|
{K: unique.Make(Ident("configure")), V: [][2]string{
|
|
{"disable-multilib", ""},
|
|
{"enable-default-pie", ""},
|
|
{"disable-nls", ""},
|
|
{"with-gnu-as", ""},
|
|
{"with-gnu-ld", ""},
|
|
{"with-system-zlib", ""},
|
|
{"enable-languages", "c,c++,go"},
|
|
{"with-native-system-header-dir", "/system/include"},
|
|
}},
|
|
{K: unique.Make(Ident("make")), V: []string{
|
|
"BOOT_CFLAGS='-O2 -g'",
|
|
"\x00",
|
|
"bootstrap",
|
|
}},
|
|
{K: unique.Make(Ident("skip-check")), V: true},
|
|
}},
|
|
|
|
{K: unique.Make(Ident("inputs")), V: Array{
|
|
{Ident("binutils")},
|
|
{Ident("mpc")},
|
|
{Ident("zlib")},
|
|
{Ident("libucontext")},
|
|
{Ident("kernel-headers")},
|
|
}},
|
|
},
|
|
{
|
|
{K: unique.Make(Ident("amd64")), V: "''"},
|
|
{K: unique.Make(Ident("arm64")), V: "''"},
|
|
{K: unique.Make(Ident("default"))},
|
|
},
|
|
{{K: unique.Make(Ident("key")), V: "\xfd"}},
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("package: args = %#v, want %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func BenchmarkEvaluate(b *testing.B) {
|
|
var gcc Func
|
|
if e, err := Parse(strings.NewReader(sample)); err != nil {
|
|
b.Fatal(err)
|
|
} else {
|
|
gcc = e[0].(Func)
|
|
}
|
|
|
|
s := []Frame{{
|
|
Func: map[unique.Handle[Ident]]F{
|
|
unique.Make(Ident("remoteTar")): {F: func(
|
|
FArgs,
|
|
) (v any, set bool, err error) {
|
|
return
|
|
}, V: map[unique.Handle[Ident]]any{
|
|
unique.Make(Ident("gzip")): 0xcafe,
|
|
}},
|
|
|
|
unique.Make(Ident("make")): {F: func(
|
|
FArgs,
|
|
) (v any, set bool, err error) {
|
|
return
|
|
}},
|
|
|
|
unique.Make(Ident("arch")): {F: func(
|
|
FArgs,
|
|
) (v any, set bool, err error) {
|
|
return
|
|
}},
|
|
|
|
unique.Make(Ident("noop")): {F: func(
|
|
FArgs,
|
|
) (v any, set bool, err error) {
|
|
return
|
|
}, V: map[unique.Handle[Ident]]any{
|
|
unique.Make(Ident("value")): "\xfd",
|
|
}},
|
|
},
|
|
}}
|
|
for b.Loop() {
|
|
if _, _, err := Evaluate[string](func(
|
|
Ident,
|
|
FArgs,
|
|
) (v any, set bool, err error) {
|
|
return
|
|
}, s, gcc); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|