Repository: scionproto/scion
Pull request #3720 (2020-04-20 09:01:56)

util: add more tests & clean up existing ones
Use the _test package.
Also add some helper methods in addr and xtest packages.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/scionproto/scion/3720)
<!-- Reviewable:end -->



Patch:
diff --git a/go/lib/addr/host.go b/go/lib/addr/host.go
index b6510330b..34eafac73 100644
--- a/go/lib/addr/host.go
+++ b/go/lib/addr/host.go
@@ -60,6 +60,8 @@ var (
 	ErrBadHostAddrType = serrors.New("unsupported host address type")
 	// ErrMalformedHostAddrType indicates a malformed host address type.
 	ErrMalformedHostAddrType = serrors.New("malformed host address type")
+	// ErrUnsupportedSVCAddress indicates an unsupported SVC address.
+	ErrUnsupportedSVCAddress = serrors.New("unsupported SVC address")
 )
 
 const (
diff --git a/go/lib/addr/isdas.go b/go/lib/addr/isdas.go
index b9af3e0bb..ad59dcb84 100644
--- a/go/lib/addr/isdas.go
+++ b/go/lib/addr/isdas.go
@@ -306,3 +306,16 @@ func (iaI IAInt) IA() IA {
 func (iaI IAInt) String() string {
 	return iaI.IA().String()
 }
+
+func (ia IAInt) MarshalText() ([]byte, error) {
+	return []byte(IAInt(ia).String()), nil
+}
+
+func (ia *IAInt) UnmarshalText(b []byte) error {
+	tIA, err := IAFromString(string(b))
+	if err != nil {
+		return nil
+	}
+	*ia = tIA.IAInt()
+	return nil
+}
diff --git a/go/lib/util/BUILD.bazel b/go/lib/util/BUILD.bazel
index a8b611eea..17f2459e0 100644
--- a/go/lib/util/BUILD.bazel
+++ b/go/lib/util/BUILD.bazel
@@ -31,15 +31,21 @@ go_library(
 go_test(
     name = "go_default_test",
     srcs = [
+        "aslist_test.go",
         "checksum_test.go",
         "duration_test.go",
+        "export_test.go",
+        "map_test.go",
         "padding_test.go",
         "time_test.go",
         "yaml_test.go",
     ],
+    data = glob(["testdata/**"]),
     embed = [":go_default_library"],
     deps = [
+        "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
+        "//go/lib/xtest:go_default_library",
         "@com_github_stretchr_testify//assert:go_default_library",
         "@com_github_stretchr_testify//require:go_default_library",
         "@in_gopkg_yaml_v2//:go_default_library",
diff --git a/go/lib/util/aslist.go b/go/lib/util/aslist.go
index 0976dc82e..8cea3590e 100644
--- a/go/lib/util/aslist.go
+++ b/go/lib/util/aslist.go
@@ -1,4 +1,5 @@
 // Copyright 2017 ETH Zurich
+// Copyright 2020 ETH Zurich, Anapaya Systems
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -23,50 +24,25 @@ import (
 	"github.com/scionproto/scion/go/lib/common"
 )
 
-type asData struct {
-	Core    []string `yaml:"Core"`
-	NonCore []string `yaml:"Non-core"`
-}
-
+// ASList is a list of ISD-AS identifiers grouped by core and non-core.
 type ASList struct {
-	Core    []addr.IA
-	NonCore []addr.IA
+	Core    []addr.IA `yaml:"Core"`
+	NonCore []addr.IA `yaml:"Non-core"`
 }
 
 // LoadASList parses the yaml file fileName and returns a structure with
 // non-core and core ASes.
 func LoadASList(fileName string) (*ASList, error) {
-	asList := &ASList{}
 	buffer, err := ioutil.ReadFile(fileName)
 	if err != nil {
 		return nil, common.NewBasicError("Unable to read from file", err, "name", fileName)
 	}
-	var locations asData
-	err = yaml.Unmarshal(buffer, &locations)
+	var asList ASList
+	err = yaml.Unmarshal(buffer, &asList)
 	if err != nil {
 		return nil, common.NewBasicError("Unable to parse YAML data", err)
 	}
-	asList.Core, err = parse(locations.Core)
-	if err != nil {
-		return nil, err
-	}
-	asList.NonCore, err = parse(locations.NonCore)
-	if err != nil {
-		return nil, err
-	}
-	return asList, nil
-}
-
-func parse(names []string) ([]addr.IA, error) {
-	var iaList []addr.IA
-	for _, name := range names {
-		ia, err := addr.IAFromString(name)
-		if err != nil {
-			return nil, common.NewBasicError("Unable to parse AS Name", err, "ISDAS", name)
-		}
-		iaList = append(iaList, ia)
-	}
-	return iaList, nil
+	return &asList, nil
 }
 
 // AllASes returns all ASes in the ASList as a slice.


Test patch:
diff --git a/go/lib/util/aslist_test.go b/go/lib/util/aslist_test.go
new file mode 100644
index 000000000..afdc5f9a5
--- /dev/null
+++ b/go/lib/util/aslist_test.go
@@ -0,0 +1,60 @@
+// Copyright 2020 Anapaya Systems
+//
+// 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 util_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/scionproto/scion/go/lib/addr"
+	"github.com/scionproto/scion/go/lib/util"
+	"github.com/scionproto/scion/go/lib/xtest"
+)
+
+func TestLoadASList(t *testing.T) {
+	tests := map[string]struct {
+		File     string
+		Error    assert.ErrorAssertionFunc
+		Expected *util.ASList
+	}{
+		"non-existing file": {
+			File:  "doesntexist.yml",
+			Error: assert.Error,
+		},
+		"invalid file": {
+			File:  "testdata/aslist_invalid.yml",
+			Error: assert.Error,
+		},
+		"valid file": {
+			File:  "testdata/aslist_valid.yml",
+			Error: assert.NoError,
+			Expected: &util.ASList{
+				Core: []addr.IA{xtest.MustParseIA("1-ff00:0:110")},
+				NonCore: []addr.IA{
+					xtest.MustParseIA("1-ff00:0:111"),
+					xtest.MustParseIA("1-ff00:0:112"),
+				},
+			},
+		},
+	}
+	for name, test := range tests {
+		t.Run(name, func(t *testing.T) {
+			asList, err := util.LoadASList(test.File)
+			test.Error(t, err)
+			assert.Equal(t, test.Expected, asList)
+		})
+	}
+}
diff --git a/go/lib/util/checksum_test.go b/go/lib/util/checksum_test.go
index 8f5df3a16..0a8e9204a 100644
--- a/go/lib/util/checksum_test.go
+++ b/go/lib/util/checksum_test.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package util
+package util_test
 
 import (
 	"encoding/binary"
@@ -23,6 +23,7 @@ import (
 	"github.com/stretchr/testify/assert"
 
 	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/util"
 )
 
 func TestChecksum(t *testing.T) {
@@ -43,7 +44,7 @@ func TestChecksum(t *testing.T) {
 	}
 	for _, test := range tests {
 		checksum := make([]byte, 2)
-		binary.BigEndian.PutUint16(checksum, Checksum(test.Input...))
+		binary.BigEndian.PutUint16(checksum, util.Checksum(test.Input...))
 		t.Run(fmt.Sprintf("Input %v", test.Input), func(t *testing.T) {
 			assert.Equal(t, test.Output[:], checksum)
 		})
@@ -57,6 +58,6 @@ func BenchmarkChecksum(b *testing.B) {
 	}
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		Checksum(data)
+		util.Checksum(data)
 	}
 }
diff --git a/go/lib/util/duration_test.go b/go/lib/util/duration_test.go
index 970adbd1a..a375f1eab 100644
--- a/go/lib/util/duration_test.go
+++ b/go/lib/util/duration_test.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package util
+package util_test
 
 import (
 	"fmt"
@@ -21,9 +21,11 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
+
+	"github.com/scionproto/scion/go/lib/util"
 )
 
-func Test_ParseDuration(t *testing.T) {
+func TestParseDuration(t *testing.T) {
 	tests := []struct {
 		input          string
 		output         time.Duration
@@ -45,14 +47,14 @@ func Test_ParseDuration(t *testing.T) {
 	}
 	for _, test := range tests {
 		t.Run(fmt.Sprintf("Input: %q", test.input), func(t *testing.T) {
-			ret, err := ParseDuration(test.input)
+			ret, err := util.ParseDuration(test.input)
 			test.errorAssertion(t, err)
 			assert.Equal(t, test.output, ret)
 		})
 	}
 }
 
-func Test_FmtDuration(t *testing.T) {
+func TestFmtDuration(t *testing.T) {
 	tests := []struct {
 		input  time.Duration
 		output string
@@ -64,13 +66,13 @@ func Test_FmtDuration(t *testing.T) {
 		{55 * time.Second, "55s"},
 		{66 * time.Hour, "66h"},
 		{48 * time.Hour, "2d"},
-		{30 * day, "30d"},
-		{35 * day, "5w"},
-		{101 * year, "101y"},
+		{30 * util.Day, "30d"},
+		{35 * util.Day, "5w"},
+		{101 * util.Year, "101y"},
 	}
 	for _, test := range tests {
 		t.Run(fmt.Sprintf("Input: %v", test.output), func(t *testing.T) {
-			ret := FmtDuration(test.input)
+			ret := util.FmtDuration(test.input)
 			assert.Equal(t, test.output, ret)
 		})
 	}
diff --git a/go/lib/util/export_test.go b/go/lib/util/export_test.go
new file mode 100644
index 000000000..5120f9a4e
--- /dev/null
+++ b/go/lib/util/export_test.go
@@ -0,0 +1,22 @@
+// Copyright 2020 Anapaya Systems
+//
+// 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 util
+
+// Test exports
+const (
+	Day  = day
+	Week = week
+	Year = year
+)
diff --git a/go/lib/util/map_test.go b/go/lib/util/map_test.go
new file mode 100644
index 000000000..ea6c7c2d4
--- /dev/null
+++ b/go/lib/util/map_test.go
@@ -0,0 +1,68 @@
+// Copyright 2019 Anapaya Systems
+//
+// 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 util_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/scionproto/scion/go/lib/util"
+)
+
+func TestLowerKeys(t *testing.T) {
+	t.Run("Nil map shouldn't do anything", func(t *testing.T) {
+		var m map[string]string
+		origMap := copyMap(m)
+		util.LowerKeys(m)
+		assert.Equal(t, origMap, m)
+	})
+
+	t.Run("Map with lowercase keys doesn't change", func(t *testing.T) {
+		m := map[string]string{
+			"key1": "val1",
+			"key2": "val2",
+		}
+		origMap := copyMap(m)
+		util.LowerKeys(m)
+		assert.Equal(t, origMap, m)
+	})
+	t.Run("Map with mixed keys changes", func(t *testing.T) {
+		m := map[string]string{
+			"Key1": "val1",
+			"kEy2": "val2",
+			"key3": "val3",
+		}
+		util.LowerKeys(m)
+		expectedM := map[string]string{
+			"key1": "val1",
+			"key2": "val2",
+			"key3": "val3",
+		}
+		assert.Equal(t, expectedM, m)
+	})
+
+}
+
+func copyMap(m map[string]string) map[string]string {
+	if m == nil {
+		return nil
+	}
+	c := make(map[string]string, len(m))
+	for k, v := range m {
+		c[k] = v
+	}
+	return c
+}
diff --git a/go/lib/util/padding_test.go b/go/lib/util/padding_test.go
index a22dc81a6..1c0cf9a4f 100644
--- a/go/lib/util/padding_test.go
+++ b/go/lib/util/padding_test.go
@@ -13,16 +13,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package util
+package util_test
 
 import (
 	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+
+	"github.com/scionproto/scion/go/lib/util"
 )
 
-func Test_CalcPadding(t *testing.T) {
+func TestCalcPadding(t *testing.T) {
 	cases := map[int]int{
 		0: 0, 1: 7, 2: 6, 3: 5, 4: 4, 5: 3, 6: 2, 7: 1,
 		8: 0, 9: 7, 10: 6, 11: 5, 12: 4, 13: 3, 14: 2, 15: 1,
@@ -30,7 +32,7 @@ func Test_CalcPadding(t *testing.T) {
 	}
 	for input, expected := range cases {
 		t.Run(fmt.Sprintf("Padding for %v should be %v", input, expected), func(t *testing.T) {
-			assert.Equal(t, expected, CalcPadding(input, 8),
+			assert.Equal(t, expected, util.CalcPadding(input, 8),
 				"CalcPadding should calculate the correct padding (8B block size)")
 		})
 	}
diff --git a/go/lib/util/testdata/aslist_invalid.yml b/go/lib/util/testdata/aslist_invalid.yml
new file mode 100644
index 000000000..d6611ea64
--- /dev/null
+++ b/go/lib/util/testdata/aslist_invalid.yml
@@ -0,0 +1,1 @@
+invalid: nope:
diff --git a/go/lib/util/testdata/aslist_valid.yml b/go/lib/util/testdata/aslist_valid.yml
new file mode 100644
index 000000000..e10285521
--- /dev/null
+++ b/go/lib/util/testdata/aslist_valid.yml
@@ -0,0 +1,5 @@
+Core:
+- 1-ff00:0:110
+Non-core:
+- 1-ff00:0:111
+- 1-ff00:0:112
diff --git a/go/lib/util/time_test.go b/go/lib/util/time_test.go
index d12a3f4a9..36118fbb5 100644
--- a/go/lib/util/time_test.go
+++ b/go/lib/util/time_test.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package util
+package util_test
 
 import (
 	"encoding/json"
@@ -21,6 +21,8 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+
+	"github.com/scionproto/scion/go/lib/util"
 )
 
 func TestUnixTimeUnmarshal(t *testing.T) {
@@ -51,23 +53,23 @@ func TestUnixTimeUnmarshal(t *testing.T) {
 	}
 	for name, test := range tests {
 		t.Run(name, func(t *testing.T) {
-			var u UnixTime
+			var u util.UnixTime
 			err := json.Unmarshal([]byte(test.Input), &u)
 			test.Assert(t, err)
 			if err != nil {
 				return
 			}
-			assert.Equal(t, SecsToTime(1356091932), u.Time)
+			assert.Equal(t, util.SecsToTime(1356091932), u.Time)
 		})
 	}
 }
 
 func TestUnixTimeMarshal(t *testing.T) {
 	s := struct {
-		Unix UnixTime
+		Unix util.UnixTime
 	}{
-		Unix: UnixTime{
-			Time: SecsToTime(1356091932),
+		Unix: util.UnixTime{
+			Time: util.SecsToTime(1356091932),
 		},
 	}
 	raw, err := json.Marshal(s)
diff --git a/go/lib/util/yaml_test.go b/go/lib/util/yaml_test.go
index f2cb7a3c6..fc63f962a 100644
--- a/go/lib/util/yaml_test.go
+++ b/go/lib/util/yaml_test.go
@@ -13,49 +13,51 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package util
+package util_test
 
 import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 	"gopkg.in/yaml.v2"
+
+	"github.com/scionproto/scion/go/lib/util"
 )
 
 // Interface assertions
-var _ yaml.Marshaler = (*B64Bytes)(nil)
-var _ yaml.Unmarshaler = (*B64Bytes)(nil)
+var _ yaml.Marshaler = (*util.B64Bytes)(nil)
+var _ yaml.Unmarshaler = (*util.B64Bytes)(nil)
 
 func Test_B64B_String(t *testing.T) {
-	b := B64Bytes{00, 01, 02, 03}
+	b := util.B64Bytes{00, 01, 02, 03}
 	assert.Equal(t, "00010203", b.String(), "String() should return hex-encoded string")
 }
 
 func Test_B64B_MarshalYAML(t *testing.T) {
-	b := B64Bytes("hello, world")
+	b := util.B64Bytes("hello, world")
 	out, _ := yaml.Marshal(&b)
 	assert.Equal(t, "aGVsbG8sIHdvcmxk\n", string(out),
 		"Should marshal to a base64-encoded yaml entry")
 }
 
 func Test_B64B_UnmarshalYAML_YAMLParseError(t *testing.T) {
-	var b B64Bytes
+	var b util.B64Bytes
 	err := yaml.Unmarshal([]byte("a: b"), &b)
 	assert.Error(t, err, "YAML parse error")
 	assert.Zero(t, b)
 }
 
 func Test_B64B_UnmarshalYAML_B64DecodeError(t *testing.T) {
-	var b B64Bytes
+	var b util.B64Bytes
 	err := yaml.Unmarshal([]byte("_"), &b)
 	assert.Error(t, err, "Base64 decode error")
 	assert.Zero(t, b)
 }
 
 func Test_B64B_UnmarshalYAML_Success(t *testing.T) {
-	var b B64Bytes
+	var b util.B64Bytes
 	err := yaml.Unmarshal([]byte("aGVsbG8sIHdvcmxk"), &b)
 	if assert.NoError(t, err, "Valid sequence unmarshaled") {
-		assert.Equal(t, B64Bytes("hello, world"), b)
+		assert.Equal(t, util.B64Bytes("hello, world"), b)
 	}
 }
diff --git a/go/lib/xtest/BUILD.bazel b/go/lib/xtest/BUILD.bazel
index 109ce690f..052338a21 100644
--- a/go/lib/xtest/BUILD.bazel
+++ b/go/lib/xtest/BUILD.bazel
@@ -14,6 +14,7 @@ go_library(
         "//go/lib/addr:go_default_library",
         "@com_github_smartystreets_goconvey//convey:go_default_library",
         "@com_github_stretchr_testify//assert:go_default_library",
+        "@com_github_stretchr_testify//require:go_default_library",
     ],
 )
 
diff --git a/go/lib/xtest/helpers.go b/go/lib/xtest/helpers.go
index 6fc1efe85..c66fd3b51 100644
--- a/go/lib/xtest/helpers.go
+++ b/go/lib/xtest/helpers.go
@@ -1,4 +1,5 @@
 // Copyright 2018 ETH Zurich
+// Copyright 2020 ETH Zurich, Anapaya Systems
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -21,6 +22,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"regexp"
 	"strings"
@@ -28,6 +30,7 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 
 	"github.com/scionproto/scion/go/lib/addr"
 )
@@ -78,6 +81,14 @@ func MustTempDir(dir, prefix string) (string, func()) {
 	}
 }
 
+// CopyDir copies "from" to "to", using the unix cp command.
+func CopyDir(t testing.TB, from, to string) {
+	t.Helper()
+	cmd := exec.Command("cp", "-rL", from, to)
+	out, err := cmd.CombinedOutput()
+	require.NoError(t, err, string(out))
+}
+
 // FailOnErr causes t to exit with a fatal error if err is non-nil.
 func FailOnErr(t testing.TB, err error, desc ...string) {
 	t.Helper()
@@ -155,6 +166,17 @@ func MustParseAS(s string) addr.AS {
 	return ia
 }
 
+// MustParseASes parses a list of comma separated AS strings. It panics in case
+// parsing fails.
+func MustParseASes(list string) []addr.AS {
+	l := strings.Split(list, ",")
+	var ases []addr.AS
+	for _, raw := range l {
+		ases = append(ases, MustParseAS(raw))
+	}
+	return ases
+}
+
 // MustParseHexString parses s and returns the corresponding byte slice.
 // It panics if the decoding fails.
 func MustParseHexString(s string) []byte {