@@ -1025,6 +1025,110 @@ def fails()!:
10251025 })
10261026}
10271027
1028+ // TestFailError verifies the fail-style half of the Go-boundary taxonomy
1029+ // (doc/error_handling.md, "The Go boundary"): a fail() call surfaces to Go as
1030+ // an *EvalError wrapping a *FailError — carrying the Starlark error value when
1031+ // fail was given one — and a Go builtin returning a *FailError raises the
1032+ // equivalent failure. Incidental builtin errors must not match *FailError.
1033+ func TestFailError (t * testing.T ) {
1034+ t .Run ("fail with an error value" , func (t * testing.T ) {
1035+ const src = `
1036+ errors = error_tags("Boom")
1037+ def explode()!:
1038+ return errors.Boom(message = "kaboom", extra = 7)
1039+ e = explode() catch err:
1040+ recover err
1041+ fail(e)
1042+ `
1043+ _ , err := starlark .ExecFile (& starlark.Thread {}, "failerr.star" , src , nil )
1044+ var evalErr * starlark.EvalError
1045+ if ! errors .As (err , & evalErr ) {
1046+ t .Fatalf ("err = %v (%T), want *starlark.EvalError" , err , err )
1047+ }
1048+ var failErr * starlark.FailError
1049+ if ! errors .As (err , & failErr ) {
1050+ t .Fatalf ("err = %v, want it to wrap *starlark.FailError" , err )
1051+ }
1052+ if failErr .StarlarkError == nil {
1053+ t .Fatal ("StarlarkError = nil, want the error value passed to fail" )
1054+ }
1055+ if got := failErr .StarlarkError .Tag ().Name (); got != "Boom" {
1056+ t .Errorf ("tag = %q, want Boom" , got )
1057+ }
1058+ if got := failErr .StarlarkError .Message (); got != "kaboom" {
1059+ t .Errorf ("message = %q, want kaboom" , got )
1060+ }
1061+ })
1062+
1063+ t .Run ("fail without an error value" , func (t * testing.T ) {
1064+ _ , err := starlark .ExecFile (& starlark.Thread {}, "fail.star" , `fail("plain")` , nil )
1065+ var failErr * starlark.FailError
1066+ if ! errors .As (err , & failErr ) {
1067+ t .Fatalf ("err = %v, want it to wrap *starlark.FailError" , err )
1068+ }
1069+ if failErr .StarlarkError != nil {
1070+ t .Errorf ("StarlarkError = %v, want nil for fail with no error value" , failErr .StarlarkError )
1071+ }
1072+ })
1073+
1074+ t .Run ("module-level try surfaces a FailError" , func (t * testing.T ) {
1075+ const src = `
1076+ errors = error_tags("Cfg")
1077+ def load_config()!:
1078+ return errors.Cfg(message = "config missing")
1079+ config = try load_config()
1080+ `
1081+ _ , err := starlark .ExecFile (& starlark.Thread {}, "topltry.star" , src , nil )
1082+ var failErr * starlark.FailError
1083+ if ! errors .As (err , & failErr ) {
1084+ t .Fatalf ("err = %v, want it to wrap *starlark.FailError" , err )
1085+ }
1086+ if failErr .StarlarkError == nil || failErr .StarlarkError .Message () != "config missing" {
1087+ t .Errorf ("StarlarkError = %v, want the error with message %q" , failErr .StarlarkError , "config missing" )
1088+ }
1089+ })
1090+
1091+ t .Run ("builtin returning FailError mimics fail" , func (t * testing.T ) {
1092+ tag := starlark .NewErrorTag ("Corrupt" )
1093+ msg := "cache corrupted"
1094+ mustSync := starlark .NewBuiltin ("must_sync" , func (* starlark.Thread , * starlark.Builtin , starlark.Tuple , []starlark.Tuple ) (starlark.Value , error ) {
1095+ return nil , & starlark.FailError {
1096+ Msg : "fail: cache corrupted" ,
1097+ StarlarkError : starlark .NewError (tag , & msg , nil , nil ),
1098+ }
1099+ })
1100+ predeclared := starlark.StringDict {"must_sync" : mustSync }
1101+ _ , err := starlark .ExecFile (& starlark.Thread {}, "builtin.star" , `must_sync()` , predeclared )
1102+ var evalErr * starlark.EvalError
1103+ if ! errors .As (err , & evalErr ) {
1104+ t .Fatalf ("err = %v (%T), want *starlark.EvalError" , err , err )
1105+ }
1106+ var failErr * starlark.FailError
1107+ if ! errors .As (err , & failErr ) {
1108+ t .Fatalf ("err = %v, want it to wrap *starlark.FailError" , err )
1109+ }
1110+ if failErr .StarlarkError == nil || failErr .StarlarkError .Tag () != tag {
1111+ t .Errorf ("StarlarkError = %v, want the error with tag Corrupt" , failErr .StarlarkError )
1112+ }
1113+ })
1114+
1115+ t .Run ("incidental builtin error is not a FailError" , func (t * testing.T ) {
1116+ faulty := starlark .NewBuiltin ("faulty" , func (* starlark.Thread , * starlark.Builtin , starlark.Tuple , []starlark.Tuple ) (starlark.Value , error ) {
1117+ return nil , fmt .Errorf ("disk on fire" )
1118+ })
1119+ predeclared := starlark.StringDict {"faulty" : faulty }
1120+ _ , err := starlark .ExecFile (& starlark.Thread {}, "faulty.star" , `faulty()` , predeclared )
1121+ var evalErr * starlark.EvalError
1122+ if ! errors .As (err , & evalErr ) {
1123+ t .Fatalf ("err = %v (%T), want *starlark.EvalError" , err , err )
1124+ }
1125+ var failErr * starlark.FailError
1126+ if errors .As (err , & failErr ) {
1127+ t .Errorf ("incidental builtin error incorrectly matched *FailError: %v" , err )
1128+ }
1129+ })
1130+ }
1131+
10281132// TestPlainCallFromBuiltinSurfacesReturnedError checks that when a Go builtin
10291133// invokes a !-function via plain Call, an uncaught error comes back to the
10301134// builtin on the error channel as a ReturnedError, rather than being deposited
0 commit comments