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( isPackage bool, args FArgs, ) (v any, set bool, err error) { set = true v, err = check(args) if isPackage { err = errors.New("unexpected package") } 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"), }}, } 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](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]([]Frame{{ Func: map[unique.Handle[Ident]]F{ unique.Make(Ident("gcc")): {F: func( isPackage bool, args FArgs, ) (v any, set bool, err error) { v = "\x00" if !isPackage { err = errors.New("not a package") } set = true got[0] = args return }, V: map[unique.Handle[Ident]]any{ unique.Make(Ident("binutils")): "binutils", unique.Make(Ident("mpc")): "mpc", unique.Make(Ident("zlib")): "zlib", unique.Make(Ident("libucontext")): "libucontext", unique.Make(Ident("kernel-headers")): "kernel-headers", }}, unique.Make(Ident("remoteTar")): {F: func( isPackage bool, args FArgs, ) (v any, set bool, err error) { if isPackage { err = errors.New("unexpected package") return } 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( isPackage bool, args FArgs, ) (v any, set bool, err error) { v = args if isPackage { err = errors.New("unexpected package") } set = true return }}, unique.Make(Ident("arch")): {F: func( isPackage bool, args FArgs, ) (v any, set bool, err error) { set = false if isPackage { err = errors.New("unexpected package") } got[1] = args return }}, unique.Make(Ident("noop")): {F: func( isPackage bool, args FArgs, ) (v any, set bool, err error) { set = false if isPackage { err = errors.New("unexpected package") } 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: []string{ "binutils", "mpc", "zlib", "libucontext", "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("gcc")): {F: func( bool, FArgs, ) (v any, set bool, err error) { return }, V: map[unique.Handle[Ident]]any{ unique.Make(Ident("binutils")): "binutils", unique.Make(Ident("mpc")): "mpc", unique.Make(Ident("zlib")): "zlib", unique.Make(Ident("libucontext")): "libucontext", unique.Make(Ident("kernel-headers")): "kernel-headers", }}, unique.Make(Ident("remoteTar")): {F: func( bool, 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( bool, FArgs, ) (v any, set bool, err error) { return }}, unique.Make(Ident("arch")): {F: func( bool, FArgs, ) (v any, set bool, err error) { return }}, unique.Make(Ident("noop")): {F: func( bool, 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](s, gcc); err != nil { b.Fatal(err) } } }