blob: 8108858223bbb111660305b436d1d6a2fa518e52 [file] [log] [blame]
// Copyright 2015 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 types
import (
"flag"
"fmt"
"strings"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestStreamNameAsFlag(t *testing.T) {
t.Parallel()
Convey(`Given an FlagSet configured with a StreamName flag`, t, func() {
var stream StreamName
fs := flag.NewFlagSet("test", flag.ContinueOnError)
fs.Var(&stream, "stream", "The stream name.")
Convey(`When the stream flag is set to "test"`, func() {
err := fs.Parse([]string{"-stream", "test"})
So(err, ShouldBeNil)
Convey(`The stream variable should be populated with "test".`, func() {
So(stream, ShouldEqual, "test")
})
})
Convey(`An invalid stream name should fail to parse.`, func() {
err := fs.Parse([]string{"-stream", "_beginsWithUnderscore"})
So(err, ShouldNotBeNil)
})
})
}
func TestStreamName(t *testing.T) {
t.Parallel()
Convey(`MakeStreamName`, t, func() {
type e struct {
t []string // Test value.
e string // Expected value.
}
for _, entry := range []e{
{[]string{""}, "FILL"},
{[]string{"", ""}, "FILL/FILL"},
{[]string{"", "foo", "ba!r", "¿baz"}, "FILL/foo/ba_r/FILL_baz"},
{[]string{"foo", "bar baz"}, "foo/bar_baz"},
} {
Convey(fmt.Sprintf(`Transforms "%#v" into "%s".`, entry.t, entry.e), func() {
s, err := MakeStreamName("FILL", entry.t...)
So(err, ShouldBeNil)
So(s, ShouldEqual, StreamName(entry.e))
So(s.Validate(), ShouldBeNil)
})
}
Convey(`Fails if the fill string is not a valid path.`, func() {
_, err := MakeStreamName("__not_a_path", "", "b", "c")
So(err, ShouldNotBeNil)
_, err = MakeStreamName("", "", "b", "c")
So(err, ShouldNotBeNil)
})
Convey(`Doesn't care about the fill string if it's not needed.`, func() {
_, err := MakeStreamName("", "a", "b", "c")
So(err, ShouldBeNil)
})
})
Convey(`StreamName.Trim`, t, func() {
type e struct {
t string // Test value.
e string // Expected value.
}
for _, entry := range []e{
{``, ``},
{`foo/bar`, `foo/bar`},
{`/foo/bar`, `foo/bar`},
{`foo/bar/`, `foo/bar`},
{`//foo//bar///`, `foo//bar`},
} {
Convey(fmt.Sprintf(`On "%s", returns "%s".`, entry.t, entry.e), func() {
So(StreamName(entry.t).Trim(), ShouldEqual, entry.e)
})
}
})
Convey(`StreamName.Namespaces`, t, func() {
type e struct {
t string // Test value.
e []StreamName // Expected value.
}
for _, entry := range []e{
{``, []StreamName{``}},
{`foo`, []StreamName{``}},
{`foo/bar`, []StreamName{`foo/`, ``}},
{`foo/bar/baz`, []StreamName{`foo/bar/`, `foo/`, ``}},
// This is malformed input (GIGO), but don't want infinite loop
{`/`, []StreamName{`/`, ``}},
} {
Convey(fmt.Sprintf(`On "%s", returns "%s".`, entry.t, entry.e), func() {
So(StreamName(entry.t).Namespaces(), ShouldResemble, entry.e)
})
}
})
Convey(`StreamName.AsNamespace`, t, func() {
type e struct {
t string // Test value.
e string // Expected value.
}
for _, entry := range []e{
{``, ``},
{`foo`, `foo/`},
{`foo/`, `foo/`},
{`foo/bar`, `foo/bar/`},
{`foo/bar/`, `foo/bar/`},
} {
Convey(fmt.Sprintf(`On "%s", returns "%s".`, entry.t, entry.e), func() {
So(StreamName(entry.t).AsNamespace(), ShouldEqual, entry.e)
})
}
})
Convey(`StreamName.Validate`, t, func() {
type e struct {
t string // Test value
v bool // Is the test value expected to be valid?
}
for _, entry := range []e{
{``, false}, // Empty
{`/foo/bar`, false}, // Begins with separator.
{`foo/bar/`, false}, // Ends with separator.
{`foo/bar`, true},
{`foo//bar`, false}, // Double separator.
{`foo^bar`, false}, // Illegal character.
{`abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789/a:_-.`, true},
{`_foo/bar`, false}, // Does not begin with alphanumeric.
{`foo/_bar/baz`, false}, // Segment begins with non-alphanumeric.
} {
if entry.v {
Convey(fmt.Sprintf(`"%s" is valid.`, entry.t), func() {
So(StreamName(entry.t).Validate(), ShouldBeNil)
})
} else {
Convey(fmt.Sprintf(`"%s" is invalid.`, entry.t), func() {
So(StreamName(entry.t).Validate(), ShouldNotBeNil)
})
}
}
Convey(`A stream name that is too long will not validate.`, func() {
So(StreamName(strings.Repeat("A", MaxStreamNameLength)).Validate(), ShouldBeNil)
So(StreamName(strings.Repeat("A", MaxStreamNameLength+1)).Validate(), ShouldNotBeNil)
})
})
Convey(`StreamName.Join`, t, func() {
type e struct {
a string // Initial stream name.
b string // Join value.
e string // Expected value.
}
for _, entry := range []e{
{"foo", "bar", "foo/+/bar"},
{"", "", "/+/"},
{"foo", "", "foo/+/"},
{"", "bar", "/+/bar"},
{"/foo/", "/bar/baz/", "foo/+/bar/baz"},
} {
Convey(fmt.Sprintf(`Joining "%s" to "%s" yields "%s".`, entry.a, entry.b, entry.e), func() {
So(StreamName(entry.a).Join(StreamName(entry.b)), ShouldEqual, StreamPath(entry.e))
})
}
})
Convey(`StreamName.Segments, StreamName.SegmentCount`, t, func() {
type e struct {
s StreamName // Initial stream name.
p []string // Expected split pieces.
n int // Expected number of segments.
}
for _, entry := range []e{
{StreamName(""), []string(nil), 0},
{StreamName("foo"), []string{"foo"}, 1},
{StreamName("foo/bar"), []string{"foo", "bar"}, 2},
{StreamName("foo/bar/baz"), []string{"foo", "bar", "baz"}, 3},
} {
Convey(fmt.Sprintf(`Stream Name "%s" has %d segments: %v`, entry.s, entry.n, entry.p), func() {
So(entry.s.Segments(), ShouldResemble, entry.p)
So(len(entry.s.Segments()), ShouldEqual, entry.s.SegmentCount())
So(len(entry.s.Segments()), ShouldEqual, entry.n)
})
}
})
Convey(`StreamName.Split`, t, func() {
for _, tc := range []struct {
s StreamName
base StreamName
last StreamName
}{
{"", "", ""},
{"foo", "", "foo"},
{"/foo", "", "foo"},
{"foo/bar", "foo", "bar"},
{"foo/bar/baz", "foo/bar", "baz"},
} {
Convey(fmt.Sprintf(`Stream name %q splits into %q and %q.`, tc.s, tc.base, tc.last), func() {
base, last := tc.s.Split()
So(base, ShouldEqual, tc.base)
So(last, ShouldEqual, tc.last)
})
}
})
}
func TestStreamPath(t *testing.T) {
t.Parallel()
Convey(`StreamPath.Split, StreamPath.Validate`, t, func() {
type e struct {
p string // The stream path.
prefix string // The split prefix.
name string // The split name.
sep bool
valid bool
}
for _, entry := range []e{
{"", "", "", false, false},
{"foo/+", "foo", "", true, false},
{"+foo", "+foo", "", false, false},
{"+/foo", "", "foo", true, false},
{"a/+/b", "a", "b", true, true},
{"a/+/", "a", "", true, false},
{"a/+foo/+/b", "a/+foo", "b", true, false},
{"fo+o/bar+/+/baz", "fo+o/bar+", "baz", true, false},
{"a/+/b/+/c", "a", "b/+/c", true, false},
{"/+/", "", "", true, false},
{"foo/bar/+/baz/qux", "foo/bar", "baz/qux", true, true},
} {
Convey(fmt.Sprintf(`Stream Path "%s" splits into "%s" and "%s".`, entry.p, entry.prefix, entry.name), func() {
prefix, sep, name := StreamPath(entry.p).SplitParts()
So(prefix, ShouldEqual, entry.prefix)
So(sep, ShouldEqual, entry.sep)
So(name, ShouldEqual, entry.name)
})
if entry.valid {
Convey(fmt.Sprintf(`Stream Path "%s" is valid`, entry.p), func() {
So(StreamPath(entry.p).Validate(), ShouldBeNil)
})
} else {
Convey(fmt.Sprintf(`Stream Path "%s" is not valid`, entry.p), func() {
So(StreamPath(entry.p).Validate(), ShouldNotBeNil)
})
}
}
})
Convey(`StreamPath.SplitLast`, t, func() {
for _, tc := range []struct {
p StreamPath
c []string
}{
{"", []string{""}},
{"/", []string{"/"}},
{"foo", []string{"foo"}},
{"foo/bar/+/baz", []string{"baz", "+", "bar", "foo"}},
{"/foo/+/bar/", []string{"", "bar", "+", "/foo"}},
} {
Convey(fmt.Sprintf(`Splitting %q repeatedly yields %v`, tc.p, tc.c), func() {
var parts []string
p := tc.p
for {
var end string
p, end = p.SplitLast()
parts = append(parts, end)
if p == "" {
break
}
}
So(parts, ShouldResemble, tc.c)
})
}
})
}