| // Copyright 2019 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package parser |
| |
| import ( |
| "errors" |
| "strings" |
| "testing" |
| |
| "github.com/google/cel-go/common" |
| "github.com/google/cel-go/common/ast" |
| "github.com/google/cel-go/common/operators" |
| |
| "google.golang.org/protobuf/proto" |
| |
| exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" |
| ) |
| |
| func TestUnparse(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| out interface{} |
| requiresMacroCalls bool |
| unparserOptions []UnparserOption |
| }{ |
| {name: "call_add", in: `a + b - c`}, |
| {name: "call_and", in: `a && b && c && d && e`}, |
| {name: "call_and_or", in: `a || b && (c || d) && e`}, |
| {name: "call_cond", in: `a ? b : c`}, |
| {name: "call_cond_nested_inner", in: `a ? (c ? d : e) : b`}, |
| {name: "call_cond_nested_outer", in: `a ? b : c ? d : e`, out: `a ? b : (c ? d : e)`}, |
| {name: "call_index", in: `a[1]["b"]`}, |
| {name: "call_index_eq", in: `x["a"].single_int32 == 23`}, |
| {name: "call_mul", in: `a * (b / c) % 0`}, |
| {name: "call_mul_add", in: `a + b * c`}, |
| {name: "call_mul_add_nested", in: `(a + b) * c / (d - e)`}, |
| {name: "call_mul_nested", in: `a * b / c % 0`}, |
| {name: "call_not", in: `!true`}, |
| {name: "call_neg", in: `-num`}, |
| {name: "call_or", in: `a || b || c || d || e`}, |
| {name: "call_neg_mult", in: `-(1 * 2)`}, |
| {name: "call_neg_add", in: `-(1 + 2)`}, |
| {name: "call_operator_precedence", in: `1 - (2 == -1)`}, |
| {name: "calc_distr_paren", in: `(1 + 2) * 3`}, |
| {name: "calc_distr_noparen", in: `1 + 2 * 3`}, |
| {name: "cond_tern_simple", in: `(x > 5) ? (x - 5) : 0`}, |
| {name: "cond_tern_neg_expr", in: `-((x > 5) ? (x - 5) : 0)`}, |
| {name: "cond_tern_neg_term", in: `-x ? (x - 5) : 0`}, |
| {name: "func_global", in: `size(a ? (b ? c : d) : e)`}, |
| {name: "func_member", in: `a.hello("world")`}, |
| {name: "func_no_arg", in: `zero()`}, |
| {name: "func_one_arg", in: `one("a")`}, |
| {name: "func_two_args", in: `and(d, 32u)`}, |
| {name: "func_var_args", in: `max(a, b, 100)`}, |
| {name: "func_neq", in: `x != "a"`}, |
| {name: "func_in", in: `a in b`}, |
| {name: "list_empty", in: `[]`}, |
| {name: "list_one", in: `[1]`}, |
| {name: "list_ints", in: `[1, 2, 3]`}, |
| {name: "list_doubles", in: `[1.0, 2.0, 3.0]`}, |
| {name: "list_doubles", in: `[1.1, 2.1, 3.1]`}, |
| {name: "list_uints", in: `[1u, 2u, 3u]`}, |
| {name: "list_numeric", in: `[1, 2.0, 3u]`}, |
| {name: "list_many", in: `["hello, world", "goodbye, world", "sure, why not?"]`}, |
| {name: "lit_bytes", in: `b"\303\203\302\277"`}, |
| {name: "lit_double", in: `-42.101`}, |
| {name: "lit_false", in: `false`}, |
| {name: "lit_int", in: `-405069`}, |
| {name: "lit_null", in: `null`}, |
| {name: "lit_string", in: `"hello:\t'world'"`}, |
| {name: "lit_string_quote", in: `"hello:\"world\""`}, |
| {name: "lit_true", in: `true`}, |
| {name: "lit_uint", in: `42u`}, |
| {name: "ident", in: `my_ident`}, |
| {name: "macro_has", in: `has(hello.world)`}, |
| {name: "map_empty", in: `{}`}, |
| {name: "map_lit_key", in: `{"a": a.b.c, b"\142": bytes(a.b.c)}`}, |
| {name: "map_expr_key", in: `{a: a, b: a.b, c: a.b.c, a ? b : c: false, a || b: true}`}, |
| {name: "msg_empty", in: `v1alpha1.Expr{}`}, |
| {name: "msg_fields", in: `v1alpha1.Expr{id: 1, call_expr: v1alpha1.Call_Expr{function: "name"}}`}, |
| {name: "select", in: `a.b.c`}, |
| {name: "idx_idx_sel", in: `a[b][c].name`}, |
| {name: "sel_expr_target", in: `(a + b).name`}, |
| {name: "sel_cond_target", in: `(a ? b : c).name`}, |
| {name: "idx_cond_target", in: `(a ? b : c)[0]`}, |
| {name: "cond_conj", in: `(a1 && a2) ? b : c`}, |
| {name: "cond_disj_conj", in: `a ? (b1 || b2) : (c1 && c2)`}, |
| {name: "call_cond_target", in: `(a ? b : c).method(d)`}, |
| {name: "cond_flat", in: `false && !true || false`}, |
| {name: "cond_paren", in: `false && (!true || false)`}, |
| {name: "cond_cond", in: `(false && !true || false) ? 2 : 3`}, |
| {name: "cond_binop", in: `(x < 5) ? x : 5`}, |
| {name: "cond_binop_binop", in: `(x > 5) ? (x - 5) : 0`}, |
| {name: "cond_cond_binop", in: `(x > 5) ? ((x > 10) ? (x - 10) : 5) : 0`}, |
| {name: "select_opt", in: `a.?b`}, |
| {name: "index_opt", in: `a[?b]`}, |
| {name: "list_lit_opt", in: `[?a, ?b, c]`}, |
| {name: "map_lit_opt", in: `{?a: b, c: d}`}, |
| {name: "msg_fields_opt", in: `v1alpha1.Expr{?id: id, call_expr: v1alpha1.Call_Expr{function: "name"}}`}, |
| {name: "select_quoted", in: "a.`b-c`"}, |
| {name: "opt_select_quoted", in: "a.?`b.c`"}, |
| {name: "message_create_quoted", in: "MyType{`in`: false}"}, |
| |
| // Equivalent expressions form unparse which do not match the originals. |
| {name: "call_add_equiv", in: `a+b-c`, out: `a + b - c`}, |
| {name: "call_cond_equiv", in: `a ? b : c`, out: `a ? b : c`}, |
| {name: "call_index_equiv", in: `a[ 1 ]["b"]`, out: `a[1]["b"]`}, |
| {name: "call_or_and_equiv", in: `(false && !true) || false`, out: `false && !true || false`}, |
| {name: "call_not_not_equiv", in: `!!true`, out: `true`}, |
| {name: "call_cond_equiv", in: `(a || b ? c : d).e`, out: `((a || b) ? c : d).e`}, |
| {name: "lit_quote_bytes_equiv", in: `b'aaa"bbb'`, out: `b"\141\141\141\042\142\142\142"`}, |
| {name: "select_equiv", in: `a . b . c`, out: `a.b.c`}, |
| |
| // These expressions require macro call tracking to be enabled. |
| { |
| name: "comp_all", |
| in: `[1, 2, 3].all(x, x > 0)`, |
| requiresMacroCalls: true, |
| }, |
| { |
| name: "comp_exists", |
| in: `[1, 2, 3].exists(x, x > 0)`, |
| requiresMacroCalls: true, |
| }, |
| { |
| name: "comp_map", |
| in: `[1, 2, 3].map(x, x >= 2, x * 4)`, |
| requiresMacroCalls: true, |
| }, |
| { |
| name: "comp_exists_one", |
| in: `[1, 2, 3].exists_one(x, x >= 2)`, |
| requiresMacroCalls: true, |
| }, |
| { |
| name: "comp_nested", |
| in: `[[1], [2], [3]].map(x, x.filter(y, y > 1))`, |
| requiresMacroCalls: true, |
| }, |
| { |
| name: "comp_chained", |
| in: `[1, 2, 3].map(x, x >= 2, x * 4).filter(x, x <= 10)`, |
| requiresMacroCalls: true, |
| }, |
| { |
| name: "comp_chained_opt", |
| in: `[?a, b[?0], c].map(x, x >= 2, x * 4).filter(x, x <= 10)`, |
| requiresMacroCalls: true, |
| }, |
| { |
| name: "comp_map_opt", |
| in: `{?a: b[?0]}.map(k, x >= 2, x * 4)`, |
| requiresMacroCalls: true, |
| }, |
| { |
| name: "comp_map_opt", |
| in: `{a: has(b.c)}.exists(k, k != "")`, |
| requiresMacroCalls: true, |
| }, |
| { |
| name: "comp_nested", |
| in: `{a: [1, 2].all(i > 0)}.exists(k, k != "")`, |
| requiresMacroCalls: true, |
| }, |
| |
| // These expressions will not be wrapped because they haven't met the |
| // conditions required by the provided unparser options |
| { |
| name: "call_no_wrap_no_operators", |
| in: "a + b + c + d", |
| out: "a + b + c + d", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| }, |
| }, |
| { |
| name: "call_no_wrap_column_limit_large_val", |
| in: "a + b + c + d", |
| out: "a + b + c + d", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(1000), |
| WrapOnOperators(operators.Add), |
| }, |
| }, |
| { |
| name: "call_no_wrap_column_limit_equal_length_to_input", |
| in: "a + b + c + d", |
| out: "a + b + c + d", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(13), |
| WrapOnOperators(operators.Add), |
| }, |
| }, |
| |
| // These expressions will be formatted based on the unparser options provided |
| { |
| name: "call_wrap_add", |
| in: "a + b - d * e", |
| out: "a +\nb - d * e", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Add), |
| }, |
| }, |
| { |
| name: "call_wrap_add_subtract", |
| in: "a * b + c - d * e", |
| out: "a * b +\nc -\nd * e", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Add, operators.Subtract), |
| }, |
| }, |
| { |
| name: "call_wrap_add_subtract", |
| in: "a * b + c - d * e", |
| out: "a * b +\nc -\nd * e", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Add, operators.Subtract), |
| }, |
| }, |
| { |
| name: "call_wrap_logical_and", |
| in: "a && b && c && d && e", |
| out: "a &&\nb &&\nc &&\nd &&\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.LogicalAnd), |
| }, |
| }, |
| { |
| name: "call_wrap_logical_and_2", |
| in: "a && b", |
| out: "a &&\nb", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.LogicalAnd), |
| }, |
| }, |
| { |
| name: "call_wrap_conditional", |
| in: "a ? b : c ? d : e", |
| out: "a ?\nb : (c ?\nd : e)", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Conditional), |
| }, |
| }, |
| { |
| name: "call_wrap_or", |
| in: "a || b || c || d || e", |
| out: "a ||\nb ||\nc ||\nd ||\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.LogicalOr), |
| }, |
| }, |
| { |
| name: "call_wrap_equals", |
| in: "a == b == c == d == e", |
| out: "a ==\nb ==\nc ==\nd ==\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Equals), |
| }, |
| }, |
| { |
| name: "call_wrap_greater", |
| in: "a > b > c > d > e", |
| out: "a >\nb >\nc >\nd >\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Greater), |
| }, |
| }, |
| { |
| name: "call_wrap_greater_equals", |
| in: "a >= b >= c >= d >= e", |
| out: "a >=\nb >=\nc >=\nd >=\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.GreaterEquals), |
| }, |
| }, |
| { |
| name: "call_wrap_in", |
| in: "a in b in c in d in e", |
| out: "a in\nb in\nc in\nd in\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.In), |
| }, |
| }, |
| { |
| name: "call_wrap_less", |
| in: "a < b < c < d < e", |
| out: "a <\nb <\nc <\nd <\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Less), |
| }, |
| }, |
| { |
| name: "call_wrap_less_equals", |
| in: "a <= b <= c <= d <= e", |
| out: "a <=\nb <=\nc <=\nd <=\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.LessEquals), |
| }, |
| }, |
| { |
| name: "call_wrap_not_equals", |
| in: "a != b != c != d != e", |
| out: "a !=\nb !=\nc !=\nd !=\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.NotEquals), |
| }, |
| }, |
| { |
| name: "call_wrap_divide", |
| in: "a / b / c / d / e", |
| out: "a /\nb /\nc /\nd /\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Divide), |
| }, |
| }, |
| { |
| name: "call_wrap_modulo", |
| in: "a % b % c % d % e", |
| out: "a %\nb %\nc %\nd %\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Modulo), |
| }, |
| }, |
| { |
| name: "call_wrap_multiply", |
| in: "a * b * c * d * e", |
| out: "a *\nb *\nc *\nd *\ne", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Multiply), |
| }, |
| }, |
| { |
| name: "call_wrap_logical_and_long_variables", |
| in: "longVariableA && longVariableB && longVariableC", |
| out: "longVariableA &&\nlongVariableB &&\nlongVariableC", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.LogicalAnd), |
| }, |
| }, |
| { |
| name: "comp_chained_wrap_comparisons", |
| in: "[1, 2, 3].map(x, x >= 2, x * 4).filter(x, x <= 10)", |
| out: "[1, 2, 3].map(x, x >=\n2, x * 4).filter(x, x <=\n10)", |
| requiresMacroCalls: true, |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.GreaterEquals, operators.LessEquals), |
| }, |
| }, |
| { |
| name: "call_wrap_before_add", |
| in: "a + b - d * e", |
| out: "a\n+ b - d * e", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Add), |
| WrapAfterColumnLimit(false), |
| }, |
| }, |
| { |
| name: "call_wrap_before_add_subtract", |
| in: "a * b + c - d * e", |
| out: "a * b\n+ c\n- d * e", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.Add, operators.Subtract), |
| WrapAfterColumnLimit(false), |
| }, |
| }, |
| { |
| name: "call_wrap_logical_and_long_variables", |
| in: "longVariableA && longVariableB && longVariableC", |
| out: "longVariableA\n&& longVariableB\n&& longVariableC", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| WrapOnOperators(operators.LogicalAnd), |
| WrapAfterColumnLimit(false), |
| }, |
| }, |
| { |
| name: "call_wrap_logical_and_long_input", |
| in: `"my-principal-group" in request.auth.claims && request.auth.claims.iat > now - duration("5m")`, |
| out: `"my-principal-group" in request.auth.claims &&` + "\n" + `request.auth.claims.iat > now - duration("5m")`, |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(40), |
| WrapOnOperators(operators.LogicalAnd), |
| }, |
| }, |
| { |
| name: "call_wrap_before_logical_and_long_input", |
| in: `"my-principal-group" in request.auth.claims && request.auth.claims.iat > now - duration("5m")`, |
| out: `"my-principal-group" in request.auth.claims` + "\n" + `&& request.auth.claims.iat > now - duration("5m")`, |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(40), |
| WrapOnOperators(operators.LogicalAnd), |
| WrapAfterColumnLimit(false), |
| }, |
| }, |
| { |
| // By default: |
| // - Column limit is at 80 |
| // - && and || are wrapped |
| // - Wrapping occurs after the symbol |
| name: "call_wrap_default", |
| in: `jwt.extra_claims.filter(c, c.startsWith("group")).all(c, jwt.extra_claims[c].all(g, g.endsWith("@acme.co"))) && jwt.extra_claims.exists(c, c.startsWith("group")) || request.auth.claims.group == "admin" || request.auth.principal == "user:me@acme.co"`, |
| out: `jwt.extra_claims.filter(c, c.startsWith("group")).all(c, jwt.extra_claims[c].all(g, g.endsWith("@acme.co"))) &&` + |
| "\n" + |
| `jwt.extra_claims.exists(c, c.startsWith("group")) || request.auth.claims.group == "admin" ||` + |
| "\n" + |
| `request.auth.principal == "user:me@acme.co"`, |
| requiresMacroCalls: true, |
| }, |
| { |
| // && and || are wrapped by default if only the column limit is specified |
| name: "call_wrap_default_operators", |
| in: "longVariableA && longVariableB || longVariableC + longVariableD - longVariableE", |
| out: "longVariableA &&\nlongVariableB ||\nlongVariableC + longVariableD - longVariableE", |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(3), |
| }, |
| }, |
| } |
| |
| for _, tst := range tests { |
| tc := tst |
| t.Run(tc.name, func(t *testing.T) { |
| prsr, err := NewParser( |
| Macros(AllMacros...), |
| PopulateMacroCalls(tc.requiresMacroCalls), |
| EnableOptionalSyntax(true), |
| EnableIdentEscapeSyntax(true), |
| ) |
| if err != nil { |
| t.Fatalf("NewParser() failed: %v", err) |
| } |
| p, iss := prsr.Parse(common.NewTextSource(tc.in)) |
| if len(iss.GetErrors()) > 0 { |
| t.Fatalf("parser.Parse(%s) failed: %v", tc.in, iss.ToDisplayString()) |
| } |
| out, err := Unparse(p.Expr(), p.SourceInfo(), tc.unparserOptions...) |
| |
| if err != nil { |
| t.Fatalf("Unparse(%s) failed: %v", tc.in, err) |
| } |
| var want interface{} = tc.in |
| if tc.out != nil { |
| want = tc.out |
| } |
| if out != want { |
| t.Errorf("Unparse() got '%s', wanted '%s'", out, want) |
| } |
| p2, iss := prsr.Parse(common.NewTextSource(out)) |
| if len(iss.GetErrors()) > 0 { |
| t.Fatalf("parser.Parse(%s) roundtrip failed: %v", tc.in, iss.ToDisplayString()) |
| } |
| before, err := ast.ExprToProto(p.Expr()) |
| if err != nil { |
| t.Fatalf("ast.ExprToProto() failed: %v", err) |
| } |
| after, err := ast.ExprToProto(p2.Expr()) |
| if err != nil { |
| t.Fatalf("ast.ExprToProto() failed: %v", err) |
| } |
| if !proto.Equal(before, after) { |
| t.Errorf("Roundtrip Parse() differs from original. Got '%v', wanted '%v'", before, after) |
| } |
| }) |
| } |
| } |
| |
| func TestUnparseErrors(t *testing.T) { |
| validConstantExpression := &exprpb.Expr{ |
| ExprKind: &exprpb.Expr_ConstExpr{ |
| ConstExpr: &exprpb.Constant{ |
| ConstantKind: &exprpb.Constant_NullValue{}, |
| }, |
| }, |
| } |
| tests := []struct { |
| name string |
| in *exprpb.Expr |
| err error |
| unparserOptions []UnparserOption |
| }{ |
| {name: "empty_expr", in: &exprpb.Expr{}, err: errors.New("unsupported expression")}, |
| { |
| name: "bad_args", |
| in: &exprpb.Expr{ |
| ExprKind: &exprpb.Expr_CallExpr{ |
| CallExpr: &exprpb.Expr_Call{ |
| Function: "_&&_", |
| Args: []*exprpb.Expr{{}, {}}, |
| }, |
| }, |
| }, |
| err: errors.New("unsupported expression"), |
| }, |
| { |
| name: "bad_struct", |
| in: &exprpb.Expr{ |
| ExprKind: &exprpb.Expr_StructExpr{ |
| StructExpr: &exprpb.Expr_CreateStruct{ |
| MessageName: "Msg", |
| Entries: []*exprpb.Expr_CreateStruct_Entry{ |
| {KeyKind: &exprpb.Expr_CreateStruct_Entry_FieldKey{FieldKey: "field"}}, |
| }, |
| }, |
| }, |
| }, |
| err: errors.New("unsupported expression"), |
| }, |
| { |
| name: "bad_map", |
| in: &exprpb.Expr{ |
| ExprKind: &exprpb.Expr_StructExpr{ |
| StructExpr: &exprpb.Expr_CreateStruct{ |
| Entries: []*exprpb.Expr_CreateStruct_Entry{ |
| {KeyKind: &exprpb.Expr_CreateStruct_Entry_FieldKey{FieldKey: "field"}}, |
| }, |
| }, |
| }, |
| }, |
| err: errors.New("unsupported expression"), |
| }, |
| { |
| name: "bad_index", |
| in: &exprpb.Expr{ |
| ExprKind: &exprpb.Expr_CallExpr{ |
| CallExpr: &exprpb.Expr_Call{ |
| Function: "_[_]", |
| Args: []*exprpb.Expr{{}, {}}, |
| }, |
| }, |
| }, |
| err: errors.New("unsupported expression"), |
| }, |
| { |
| name: "bad_unparser_option_wrap_column_zero", |
| in: validConstantExpression, |
| err: errors.New("Invalid unparser option. Wrap column value must be greater than or equal to 1. Got 0 instead"), |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(0), |
| }, |
| }, |
| { |
| name: "bad_unparser_option_wrap_column_negative", |
| in: validConstantExpression, |
| err: errors.New("Invalid unparser option. Wrap column value must be greater than or equal to 1. Got -1 instead"), |
| unparserOptions: []UnparserOption{ |
| WrapOnColumn(-1), |
| }, |
| }, |
| { |
| name: "bad_unparser_option_unsupported_operator", |
| in: validConstantExpression, |
| err: errors.New("Invalid unparser option. Unsupported operator: bogus"), |
| unparserOptions: []UnparserOption{ |
| WrapOnOperators("bogus"), |
| }, |
| }, |
| { |
| name: "bad_unparser_option_unary_operator", |
| in: validConstantExpression, |
| err: errors.New("Invalid unparser option. Unary operators are unsupported: " + operators.Negate), |
| unparserOptions: []UnparserOption{ |
| WrapOnOperators(operators.Negate), |
| }, |
| }, |
| } |
| |
| for _, tst := range tests { |
| tc := tst |
| t.Run(tc.name, func(t *testing.T) { |
| info := ast.NewSourceInfo(nil) |
| e, err := ast.ProtoToExpr(tc.in) |
| if err != nil { |
| t.Fatalf("ast.ProtoToExpr(%v) failed: %v", tc.in, err) |
| } |
| out, err := Unparse(e, info, tc.unparserOptions...) |
| if err == nil { |
| t.Fatalf("Unparse(%v) got %v, wanted error %v", tc.in, out, tc.err) |
| } |
| if !strings.Contains(err.Error(), tc.err.Error()) { |
| t.Errorf("Unparse(%v) got unexpected error: %v, wanted %v", tc.in, err, tc.err) |
| } |
| }) |
| } |
| } |