Use `Path` instead of string for file paths

This centralizes verification and common operations, like converting the
path to a source file to the path for a built object.

It also embeds the configuration knowledge into the path, so that we can
remove "${SrcDir}/path" from the ninja file. When SrcDir is '.', that
leads to paths like './path' instead of just 'path' like make is doing,
causing differences in compiled binaries.

Change-Id: Ib4e8910a6e867ce1b7b420d927c04f1142a7589e
diff --git a/common/paths_test.go b/common/paths_test.go
new file mode 100644
index 0000000..16ede0d
--- /dev/null
+++ b/common/paths_test.go
@@ -0,0 +1,167 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// 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 common
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+type strsTestCase struct {
+	in  []string
+	out string
+	err []error
+}
+
+var commonValidatePathTestCases = []strsTestCase{
+	{
+		in:  []string{""},
+		out: "",
+	},
+	{
+		in:  []string{"a/b"},
+		out: "a/b",
+	},
+	{
+		in:  []string{"a/b", "c"},
+		out: "a/b/c",
+	},
+	{
+		in:  []string{"a/.."},
+		out: ".",
+	},
+	{
+		in:  []string{"."},
+		out: ".",
+	},
+	{
+		in:  []string{".."},
+		out: "",
+		err: []error{errors.New("Path is outside directory: ..")},
+	},
+	{
+		in:  []string{"../a"},
+		out: "",
+		err: []error{errors.New("Path is outside directory: ../a")},
+	},
+	{
+		in:  []string{"b/../../a"},
+		out: "",
+		err: []error{errors.New("Path is outside directory: ../a")},
+	},
+	{
+		in:  []string{"/a"},
+		out: "",
+		err: []error{errors.New("Path is outside directory: /a")},
+	},
+}
+
+var validateSafePathTestCases = append(commonValidatePathTestCases, []strsTestCase{
+	{
+		in:  []string{"$host/../$a"},
+		out: "$a",
+	},
+}...)
+
+var validatePathTestCases = append(commonValidatePathTestCases, []strsTestCase{
+	{
+		in:  []string{"$host/../$a"},
+		out: "",
+		err: []error{errors.New("Path contains invalid character($): $host/../$a")},
+	},
+	{
+		in:  []string{"$host/.."},
+		out: "",
+		err: []error{errors.New("Path contains invalid character($): $host/..")},
+	},
+}...)
+
+func TestValidateSafePath(t *testing.T) {
+	for _, testCase := range validateSafePathTestCases {
+		ctx := &configErrorWrapper{}
+		out := validateSafePath(ctx, testCase.in...)
+		check(t, "validateSafePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err)
+	}
+}
+
+func TestValidatePath(t *testing.T) {
+	for _, testCase := range validatePathTestCases {
+		ctx := &configErrorWrapper{}
+		out := validatePath(ctx, testCase.in...)
+		check(t, "validatePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err)
+	}
+}
+
+func TestOptionalPath(t *testing.T) {
+	var path OptionalPath
+	checkInvalidOptionalPath(t, path)
+
+	path = OptionalPathForPath(nil)
+	checkInvalidOptionalPath(t, path)
+}
+
+func checkInvalidOptionalPath(t *testing.T, path OptionalPath) {
+	if path.Valid() {
+		t.Errorf("Uninitialized OptionalPath should not be valid")
+	}
+	if path.String() != "" {
+		t.Errorf("Uninitialized OptionalPath String() should return \"\", not %q", path.String())
+	}
+	defer func() {
+		if r := recover(); r == nil {
+			t.Errorf("Expected a panic when calling Path() on an uninitialized OptionalPath")
+		}
+	}()
+	path.Path()
+}
+
+func check(t *testing.T, testType, testString string,
+	got interface{}, err []error,
+	expected interface{}, expectedErr []error) {
+
+	printedTestCase := false
+	e := func(s string, expected, got interface{}) {
+		if !printedTestCase {
+			t.Errorf("test case %s: %s", testType, testString)
+			printedTestCase = true
+		}
+		t.Errorf("incorrect %s", s)
+		t.Errorf("  expected: %s", p(expected))
+		t.Errorf("       got: %s", p(got))
+	}
+
+	if !reflect.DeepEqual(expectedErr, err) {
+		e("errors:", expectedErr, err)
+	}
+
+	if !reflect.DeepEqual(expected, got) {
+		e("output:", expected, got)
+	}
+}
+
+func p(in interface{}) string {
+	if v, ok := in.([]interface{}); ok {
+		s := make([]string, len(v))
+		for i := range v {
+			s[i] = fmt.Sprintf("%#v", v[i])
+		}
+		return "[" + strings.Join(s, ", ") + "]"
+	} else {
+		return fmt.Sprintf("%#v", in)
+	}
+}