HTRACE-285. htraced tool: fix query parsing and add query_test (cmccabe)
diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go b/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go
index f3668ea..14b7f73 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go
@@ -43,7 +43,7 @@
 const EXIT_SUCCESS = 0
 const EXIT_FAILURE = 1
 
-var verbose *bool
+var verbose bool
 
 const USAGE = `The Apache HTrace command-line tool.  This tool retrieves and modifies settings and
 other data on a running htraced daemon.
@@ -68,7 +68,7 @@
 	app.Flag("Dmy.key", "Set configuration key 'my.key' to 'my.value'.  Replace 'my.key' "+
 		"with any key you want to set.").Default("my.value").String()
 	addr := app.Flag("addr", "Server address.").String()
-	verbose = app.Flag("verbose", "Verbose.").Default("false").Bool()
+	verbose = *app.Flag("verbose", "Verbose.").Default("false").Bool()
 	version := app.Command("version", "Print the version of this program.")
 	serverInfo := app.Command("serverInfo", "Print information retrieved from an htraced server.")
 	serverStats := app.Command("serverStats", "Print statistics retrieved from the htraced server.")
@@ -100,7 +100,7 @@
 	queryArg := query.Arg("query", "The query string to send.  Query strings have the format "+
 		"[TYPE] [OPERATOR] [CONST], joined by AND statements.").Required().String()
 	rawQuery := app.Command("rawQuery", "Send a raw JSON query to htraced.")
-	rawQueryArg := query.Arg("json", "The query JSON to send.").Required().String()
+	rawQueryArg := rawQuery.Arg("json", "The query JSON to send.").Required().String()
 	cmd := kingpin.MustParse(app.Parse(os.Args[1:]))
 
 	// Add the command-line settings into the configuration.
@@ -327,7 +327,7 @@
 		}
 		spans = append(spans, &span)
 	}
-	if *verbose {
+	if verbose {
 		fmt.Printf("Writing ")
 		prefix := ""
 		for i := range spans {
@@ -390,7 +390,7 @@
 		if err == nil {
 			_, err = fmt.Fprintf(w, "%s\n", span.ToJson())
 		}
-		if *verbose {
+		if verbose {
 			numSpans++
 			now := time.Now()
 			if !now.Before(nextLogTime) {
diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/queries.go b/htrace-htraced/go/src/org/apache/htrace/htrace/queries.go
index 4ff246c..442df4f 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htrace/queries.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htrace/queries.go
@@ -36,12 +36,12 @@
 		switch {
 		case c == prevQuote:
 			prevQuote = rune(0)
-			return false
+			return true
 		case prevQuote != rune(0):
 			return false
 		case unicode.In(c, unicode.Quotation_Mark):
 			prevQuote = c
-			return false
+			return true
 		default:
 			return unicode.IsSpace(c)
 		}
@@ -57,7 +57,7 @@
 }
 
 func (ps *predicateParser) Parse() (*common.Predicate, error) {
-	if ps.curToken > len(ps.tokens) {
+	if ps.curToken >= len(ps.tokens) {
 		return nil, nil
 	}
 	if ps.curToken > 0 {
@@ -72,7 +72,7 @@
 				"token %d", ps.curToken))
 		}
 	}
-	field := common.Field(ps.tokens[ps.curToken])
+	field := common.Field(strings.ToLower(ps.tokens[ps.curToken]))
 	if !field.IsValid() {
 		return nil, errors.New(fmt.Sprintf("Invalid field specifier at token %d.  "+
 			"Can't understand %s.  Valid field specifiers are %v", ps.curToken,
@@ -83,7 +83,7 @@
 		return nil, errors.New(fmt.Sprintf("Nothing found after field specifier "+
 			"at token %d", ps.curToken))
 	}
-	op := common.Op(ps.tokens[ps.curToken])
+	op := common.Op(strings.ToLower(ps.tokens[ps.curToken]))
 	if !op.IsValid() {
 		return nil, errors.New(fmt.Sprintf("Invalid operation specifier at token %d.  "+
 			"Can't understand %s.  Valid operation specifiers are %v", ps.curToken,
@@ -95,20 +95,31 @@
 			"at token %d", ps.curToken))
 	}
 	val := ps.tokens[ps.curToken]
+	ps.curToken++
 	return &common.Predicate{Op: op, Field: field, Val: val}, nil
 }
 
 func parseQueryString(str string) ([]common.Predicate, error) {
 	ps := predicateParser{tokens: tokenize(str)}
+	if verbose {
+		fmt.Printf("Running query [ ")
+		prefix := ""
+		for tokenIdx := range(ps.tokens) {
+			fmt.Printf("%s'%s'", prefix, ps.tokens[tokenIdx])
+			prefix = ", "
+		}
+		fmt.Printf(" ]\n")
+	}
 	preds := make([]common.Predicate, 0)
 	for {
 		pred, err := ps.Parse()
-		if pred == nil {
-			break
-		}
 		if err != nil {
 			return nil, err
 		}
+		if pred == nil {
+			break
+		}
+		preds = append(preds, *pred)
 	}
 	if len(preds) == 0 {
 		return nil, errors.New("Empty query string")
@@ -140,7 +151,7 @@
 
 // Send a query.
 func doQuery(hcl *htrace.Client, query *common.Query) error {
-	if *verbose {
+	if verbose {
 		qbytes, err := json.Marshal(*query)
 		if err != nil {
 			qbytes = []byte("marshaling error: " + err.Error())
@@ -151,7 +162,7 @@
 	if err != nil {
 		return err
 	}
-	if *verbose {
+	if verbose {
 		fmt.Printf("%d results...\n", len(spans))
 	}
 	for i := range spans {
diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/query_test.go b/htrace-htraced/go/src/org/apache/htrace/htrace/query_test.go
new file mode 100644
index 0000000..cab1e92
--- /dev/null
+++ b/htrace-htraced/go/src/org/apache/htrace/htrace/query_test.go
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 main
+
+import (
+	"encoding/json"
+	"org/apache/htrace/common"
+	"reflect"
+	"testing"
+)
+
+func predsToStr(preds []common.Predicate) string {
+	b, err := json.MarshalIndent(preds, "", "  ")
+	if err != nil {
+		return "JSON marshaling error: " + err.Error()
+	}
+	return string(b)
+}
+
+func checkParseQueryString(t *testing.T, str string, epreds []common.Predicate) {
+	preds, err := parseQueryString(str)
+	if err != nil {
+		t.Fatalf("got unexpected parseQueryString error: %s\n", err.Error())
+	}
+	if !reflect.DeepEqual(preds, epreds) {
+		t.Fatalf("Unexpected result from parseQueryString.  " +
+			"Expected: %s, got: %s\n", predsToStr(epreds), predsToStr(preds))
+	}
+}
+
+func TestParseQueryString(t *testing.T) {
+	verbose = testing.Verbose()
+	checkParseQueryString(t, "description eq ls", []common.Predicate {
+		common.Predicate {
+			Op: common.EQUALS,
+			Field: common.DESCRIPTION,
+			Val: "ls",
+		},
+	})
+	checkParseQueryString(t, "begin gt 123 and end le 456", []common.Predicate {
+		common.Predicate {
+			Op: common.GREATER_THAN,
+			Field: common.BEGIN_TIME,
+			Val: "123",
+		},
+		common.Predicate {
+			Op: common.LESS_THAN_OR_EQUALS,
+			Field: common.END_TIME,
+			Val: "456",
+		},
+	})
+	checkParseQueryString(t, `DESCRIPTION cn "Foo Bar" and ` +
+		`BEGIN ge "999" and SPANID eq "4565d8abc4f70ac1216a3f1834c6860b"`,
+		[]common.Predicate {
+		common.Predicate {
+			Op: common.CONTAINS,
+			Field: common.DESCRIPTION,
+			Val: "Foo Bar",
+		},
+		common.Predicate {
+			Op: common.GREATER_THAN_OR_EQUALS,
+			Field: common.BEGIN_TIME,
+			Val: "999",
+		},
+		common.Predicate {
+			Op: common.EQUALS,
+			Field: common.SPAN_ID,
+			Val: "4565d8abc4f70ac1216a3f1834c6860b",
+		},
+	})
+}