blob: 362269fc5b9b705a81175420f5331c94f0cd0f5d [file] [log] [blame]
// Copyright 2020 The LUCI Authors.
//
// 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 mask
import (
"fmt"
"testing"
. "github.com/smartystreets/goconvey/convey"
. "go.chromium.org/luci/common/testing/assertions"
)
func TestParsePath(t *testing.T) {
Convey("Expect path parsing", t, func() {
Convey("succeeds when parsing scalar field", func() {
tryParsePath("str").andExpectPath("str")
tryParsePath("num").andExpectPath("num")
})
Convey("fails when given subfield for a scalar field", func() {
tryParsePath("str.str").andExpectErrorLike("scalar field cannot have subfield: \"str\"")
})
Convey("fails for invalid delimiter", func() {
tryParsePath("str@").andExpectErrorLike("expected delimiter: .; got @")
})
Convey("succeeds when parsing repeated field", func() {
tryParsePath("strs").andExpectPath("strs")
tryParsePath("nums").andExpectPath("nums")
tryParsePath("msgs").andExpectPath("msgs")
})
Convey("succeeds when parsing repeated field and path ends with star", func() {
// trailing stars are kept
tryParsePath("strs.*").andExpectPath("strs", "*")
tryParsePath("nums.*").andExpectPath("nums", "*")
tryParsePath("msgs.*").andExpectPath("msgs", "*")
})
Convey("fails when parsing repeated field and path ends with index", func() {
tryParsePath("strs.1").andExpectErrorLike("expected a star following a repeated field; got token: \"1\"")
tryParsePath("nums.2").andExpectErrorLike("expected a star following a repeated field; got token: \"2\"")
tryParsePath("msgs.a").andExpectErrorLike("expected a star following a repeated field; got token: \"a\"")
})
Convey("succeeds when parsing map field (key type integer, scalar value)", func() {
tryParsePath("map_num_str.1").andExpectPath("map_num_str", "1")
tryParsePath("map_num_str.-1").andExpectPath("map_num_str", "-1")
tryParsePath("map_num_str.*").andExpectPath("map_num_str", "*")
})
Convey("succeeds when parsing map field (key type string, scalar value)", func() {
// unquoted
tryParsePath("map_str_num.abcd").andExpectPath("map_str_num", "abcd")
tryParsePath("map_str_num._ab_cd").andExpectPath("map_str_num", "_ab_cd")
// quoted
tryParsePath("map_str_num.`abcd`").andExpectPath("map_str_num", "abcd")
tryParsePath("map_str_num.`_ab.cd`").andExpectPath("map_str_num", "_ab.cd")
tryParsePath("map_str_num.`ab``cd`").andExpectPath("map_str_num", "ab`cd")
tryParsePath("map_str_num.*").andExpectPath("map_str_num", "*")
})
Convey("succeeds when parsing map field (key type boolean, scalar value)", func() {
tryParsePath("map_bool_str.false").andExpectPath("map_bool_str", "false")
tryParsePath("map_bool_str.true").andExpectPath("map_bool_str", "true")
tryParsePath("map_bool_str.*").andExpectPath("map_bool_str", "*")
})
Convey("fails when parsing map field with incompatible key type", func() {
tryParsePath("map_num_str.a").andExpectErrorLike("expected map key kind int32; got token: \"a\"")
tryParsePath("map_str_num.1").andExpectErrorLike("expected map key kind string; got token: \"1\"")
tryParsePath("map_bool_str.not_a_bool").andExpectErrorLike("expected map key kind bool; got token: \"not_a_bool\"")
})
Convey("succeeds when parsing map field (value type message)", func() {
tryParsePath("map_str_msg.some_key.str").andExpectPath("map_str_msg",
"some_key", "str")
tryParsePath("map_str_msg.*.str").andExpectPath("map_str_msg", "*", "str")
})
Convey("succeeds when parsing message field", func() {
tryParsePath("msg.str").andExpectPath("msg", "str")
tryParsePath("msg.*").andExpectPath("msg", "*")
tryParsePath("msg.msg.msg.*").andExpectPath("msg", "msg", "msg", "*")
})
Convey("fails when parsing message field and given a non-string field name", func() {
tryParsePath("msg.123").andExpectErrorLike("expected a field name of type string; got token: \"123\"")
})
Convey("fails when parsing message field and star is not the last token", func() {
tryParsePath("msg.*.str").andExpectErrorLike("expected end of string; got token: \"str\"")
})
Convey("fails when parsing message field with unknown subfield", func() {
tryParsePath("msg.unknown_field").andExpectErrorLike(fmt.Sprintf("field \"unknown_field\" does not exist in message %s", testMsgDescriptor.Name()))
tryParsePath("msg.msg.unknown_field").andExpectErrorLike(fmt.Sprintf("field \"unknown_field\" does not exist in message %s", testMsgDescriptor.Name()))
})
Convey("succeeds when parsing repeated message fields given subfield", func() {
tryParsePath("msgs.*.str").andExpectPath("msgs", "*", "str")
})
Convey("fails when ends delimiter", func() {
tryParsePath("msg.").andExpectErrorLike("path can't end with delimiter: .")
})
Convey("fails when quoted string is not closed", func() {
tryParsePath("`quoted``str").andExpectErrorLike("a quoted string is never closed; got: \"quoted`str\"")
})
Convey("fails when an integer literal has only minus sign", func() {
tryParsePath("map_num_str.-").andExpectErrorLike("expected digit following minus sign for negative numbers; got minus sign only")
})
Convey("fails when multiple delimiters", func() {
tryParsePath("msg..str").andExpectErrorLike("unexpected token: .")
tryParsePath("msg..").andExpectErrorLike("unexpected token: .")
})
Convey("succeeds for json name", func() {
p, err := parsePath("jsonName", testMsgDescriptor, true)
So(err, ShouldBeNil)
So(p, ShouldResemble, path{"json_name"})
p, err = parsePath("another_json_name", testMsgDescriptor, true)
So(err, ShouldBeNil)
So(p, ShouldResemble, path{"json_name_option"})
})
})
}
// parseResult is a helper struct to enable descriptive test for path parsing
type parseResult struct {
p path
err error
}
func tryParsePath(rawPath string) parseResult {
p, err := parsePath(rawPath, testMsgDescriptor, false)
return parseResult{
p: p,
err: err,
}
}
func (res parseResult) andExpectErrorLike(errorSubstring string) {
So(res.err, ShouldErrLike, errorSubstring)
}
func (res parseResult) andExpectPath(segments ...string) {
So(res.err, ShouldBeNil)
expectedPath := make(path, len(segments))
for i, seg := range segments {
expectedPath[i] = seg
}
So(res.p, ShouldResemble, expectedPath)
}