Make query_checker tests pass in go1.5.

Go 1.5 has moved to storing time zone data in a zip file. The error messages
returned have also changed, causing the query_checker tests to break under 1.5.

This CL addresses the issue by allowing tests to return one of a range of
errors. If the test specifies an expectation of vErrorContaining, the error
returned by the code-under-test will match if:

* its ID matches the expected error
* its error message contains the text of the expected error

This is more lenient than the previous test. Also, the offset parameter is not
checked.

Change-Id: I6bb264a9b171783380fa9a9d0e0ee635cc858534
diff --git a/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go b/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go
index 2e2057d..93947b2 100644
--- a/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go
+++ b/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go
@@ -19,8 +19,61 @@
 	"v.io/v23/verror"
 	_ "v.io/x/ref/runtime/factories/generic"
 	"v.io/x/ref/test"
+	"strings"
 )
 
+// oneOf represents an expectation that more than one type of error may be
+// returned by the corresponding test.
+type oneOf []interface{}
+
+func (o oneOf) matches(err error) bool {
+	for _, e := range o {
+		switch e := e.(type) {
+		case error:
+			if e.Error() == err.Error() {
+				return true
+			}
+		case vErrorContaining:
+			vErr, ok := err.(verror.E)
+			if !ok {
+				continue
+			}
+			if e.matches(vErr) {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// containing represents an expectation that an error will be of the given
+// VError type and will contain the given string.
+type vErrorContaining struct {
+	id verror.ID
+	message string
+}
+
+func newVErrorContaining(id verror.ID, message string) vErrorContaining {
+	return vErrorContaining{
+		id: id,
+		message: message,
+	}
+}
+
+func (v vErrorContaining) matches(err verror.E) bool {
+	if err.ID != v.id {
+		return false
+	}
+	if !strings.Contains(err.Error(), v.message) {
+		return false
+	}
+	return true
+}
+
+func (v vErrorContaining) String() string {
+	return fmt.Sprintf("VError containing ID=%s, message=%s", v.id, v.message)
+}
+
 type mockDB struct {
 	ctx *context.T
 }
@@ -80,7 +133,7 @@
 
 type parseSelectErrorTest struct {
 	query string
-	err   error
+	err   interface{}
 }
 
 func TestQueryChecker(t *testing.T) {
@@ -432,17 +485,53 @@
 		{"select v from Customer where v.ZipCode is not 94303", syncql.NewErrIsIsNotRequireRhsNil(db.GetContext(), 46)},
 		{"select v from Customer where v.ZipCode is not true", syncql.NewErrIsIsNotRequireRhsNil(db.GetContext(), 46)},
 		{"select v from Customer where v.ZipCode is not 943.03", syncql.NewErrIsIsNotRequireRhsNil(db.GetContext(), 46)},
-		{"select v from Customer where Type(v) = \"Customer\" and Year(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 74, errors.New("unknown time zone ABC"))},
-		{"select v from Customer where Type(v) = \"Customer\" and Month(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 75, errors.New("unknown time zone ABC"))},
-		{"select v from Customer where Type(v) = \"Customer\" and Day(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 73, errors.New("unknown time zone ABC"))},
-		{"select v from Customer where Type(v) = \"Customer\" and Hour(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 74, errors.New("unknown time zone ABC"))},
-		{"select v from Customer where Type(v) = \"Customer\" and Minute(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 76, errors.New("unknown time zone ABC"))},
-		{"select v from Customer where Type(v) = \"Customer\" and Second(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 76, errors.New("unknown time zone ABC"))},
+		// TODO(jkline): check offset for the 1.5 tests.
+		{"select v from Customer where Type(v) = \"Customer\" and Year(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{}{
+			// Go1.4
+			syncql.NewErrLocationConversionError(db.GetContext(), 74, errors.New("unknown time zone ABC")),
+			// Go1.5
+			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
+		})},
+		{"select v from Customer where Type(v) = \"Customer\" and Month(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{} {
+			// Go1.4
+			syncql.NewErrLocationConversionError(db.GetContext(), 75, errors.New("unknown time zone ABC")),
+			// Go1.5
+			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
+		})},
+		{"select v from Customer where Type(v) = \"Customer\" and Day(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{} {
+			// Go1.4
+			syncql.NewErrLocationConversionError(db.GetContext(), 73, errors.New("unknown time zone ABC")),
+			// Go1.5
+			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
+		})},
+		{"select v from Customer where Type(v) = \"Customer\" and Hour(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{} {
+			// Go1.4
+			syncql.NewErrLocationConversionError(db.GetContext(), 74, errors.New("unknown time zone ABC")),
+			// Go1.5
+			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
+		})},
+		{"select v from Customer where Type(v) = \"Customer\" and Minute(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{} {
+			// Go1.4
+			syncql.NewErrLocationConversionError(db.GetContext(), 76, errors.New("unknown time zone ABC")),
+			// Go1.5
+			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
+		})},
+		{"select v from Customer where Type(v) = \"Customer\" and Second(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{} {
+			// Go1.4
+			syncql.NewErrLocationConversionError(db.GetContext(), 76, errors.New("unknown time zone ABC")),
+			// Go1.5
+			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
+		})},
 		{"select v from Customer where Type(v) = \"Customer\" and Now(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrFunctionArgCount(db.GetContext(), 54, "Now", 0, 2)},
 		{"select v from Customer where Type(v) = \"Customer\" and Lowercase(v.Name, 2) = \"smith\"", syncql.NewErrFunctionArgCount(db.GetContext(), 54, "Lowercase", 1, 2)},
 		{"select v from Customer where Type(v) = \"Customer\" and Uppercase(v.Name, 2) = \"SMITH\"", syncql.NewErrFunctionArgCount(db.GetContext(), 54, "Uppercase", 1, 2)},
 		{"select Time() from Customer", syncql.NewErrFunctionArgCount(db.GetContext(), 7, "Time", 2, 0)},
-		{"select Year(v.InvoiceDate, \"Foo\") from Customer where Type(v) = \"Invoice\"", syncql.NewErrLocationConversionError(db.GetContext(), 27, errors.New("unknown time zone Foo"))},
+		{"select Year(v.InvoiceDate, \"Foo\") from Customer where Type(v) = \"Invoice\"", oneOf([]interface{}{
+			// Go1.4
+			syncql.NewErrLocationConversionError(db.GetContext(), 27, errors.New("unknown time zone Foo")),
+			// Go1.5
+			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find Foo in zip file"),
+		})},
 		{"select K from Customer where Type(v) = \"Invoice\"", syncql.NewErrDidYouMeanLowercaseK(db.GetContext(), 7)},
 		{"select V from Customer where Type(v) = \"Invoice\"", syncql.NewErrDidYouMeanLowercaseV(db.GetContext(), 7)},
 		{"select k from Customer where K = \"001\"", syncql.NewErrDidYouMeanLowercaseK(db.GetContext(), 29)},
@@ -457,8 +546,15 @@
 		} else {
 			err := query_checker.Check(&db, s)
 			// Test both that the IDs compare and the text compares (since the offset needs to match).
-			if verror.ErrorID(err) != verror.ErrorID(test.err) || err.Error() != test.err.Error() {
-				t.Errorf("query: %s; got %v, want %v", test.query, err, test.err)
+			switch e := test.err.(type) {
+			case error:
+				if verror.ErrorID(err) != verror.ErrorID(e) || err.Error() != e.Error() {
+					t.Errorf("query: %s; got %v, want %v", test.query, err, test.err)
+				}
+			case oneOf:
+				if !e.matches(err) {
+					t.Errorf("query: %s; got %v, want one of %s", test.query, err, e)
+				}
 			}
 		}
 	}
diff --git a/v23/syncbase/nosql/internal/query/query_functions/date_funcs.go b/v23/syncbase/nosql/internal/query/query_functions/date_funcs.go
index 6ec82e8..b2ea0b4 100644
--- a/v23/syncbase/nosql/internal/query/query_functions/date_funcs.go
+++ b/v23/syncbase/nosql/internal/query/query_functions/date_funcs.go
@@ -15,9 +15,9 @@
 	"v.io/syncbase/v23/syncbase/nosql/syncql"
 )
 
-// If possible, check if arg is convertable to a location.  Fields and not yet computed
+// If possible, check if arg is convertible to a location.  Fields and not yet computed
 // functions cannot be checked and will just return nil.
-func checkIfPossibleThatArgIsConvertableToLocation(db query_db.Database, arg *query_parser.Operand) error {
+func checkIfPossibleThatArgIsConvertibleToLocation(db query_db.Database, arg *query_parser.Operand) error {
 	var locStr *query_parser.Operand
 	var err error
 	switch arg.Type {
@@ -187,8 +187,8 @@
 func secondArgLocationCheck(db query_db.Database, off int64, args []*query_parser.Operand) error {
 	// At this point, for the args that can be evaluated before execution, it is known that
 	// there are two args, a time followed by a string.
-	// Just need to check that the 2nd arg is convertable to a location.
-	if err := checkIfPossibleThatArgIsConvertableToLocation(db, args[1]); err != nil {
+	// Just need to check that the 2nd arg is convertible to a location.
+	if err := checkIfPossibleThatArgIsConvertibleToLocation(db, args[1]); err != nil {
 		return err
 	}
 	return nil