예제 #1
0
// on the source rdonly guy, should only have one query to find min & max
func SourceRdonlyFactory(t *testing.T) func() (dbconnpool.PoolConnection, error) {
	return func() (dbconnpool.PoolConnection, error) {
		return &FakePoolConnection{
			t: t,
			ExpectedExecuteFetch: []ExpectedExecuteFetch{
				ExpectedExecuteFetch{
					Query: "SELECT MIN(id), MAX(id) FROM vt_ks.table1",
					QueryResult: &mproto.QueryResult{
						Fields: []mproto.Field{
							mproto.Field{
								Name: "min",
								Type: mproto.VT_LONGLONG,
							},
							mproto.Field{
								Name: "max",
								Type: mproto.VT_LONGLONG,
							},
						},
						Rows: [][]sqltypes.Value{
							[]sqltypes.Value{
								sqltypes.MakeString([]byte("100")),
								sqltypes.MakeString([]byte("200")),
							},
						},
					},
				},
			},
		}, nil
	}
}
예제 #2
0
func (sq *SqlQuery) StreamExecute(ctx context.Context, query *proto.Query, sendReply func(reply interface{}) error) error {
	// Custom parsing of the query we expect
	min := 100
	max := 200
	var err error
	parts := strings.Split(query.Sql, " ")
	for _, part := range parts {
		if strings.HasPrefix(part, "id>=") {
			min, err = strconv.Atoi(part[4:])
			if err != nil {
				return err
			}
		} else if strings.HasPrefix(part, "id<") {
			max, err = strconv.Atoi(part[3:])
		}
	}
	sq.t.Logf("SqlQuery: got query: %v with min %v max %v", *query, min, max)

	// Send the headers
	if err := sendReply(&mproto.QueryResult{
		Fields: []mproto.Field{
			mproto.Field{
				Name: "id",
				Type: mproto.VT_LONGLONG,
			},
			mproto.Field{
				Name: "msg",
				Type: mproto.VT_VARCHAR,
			},
			mproto.Field{
				Name: "keyspace_id",
				Type: mproto.VT_LONGLONG,
			},
		},
	}); err != nil {
		return err
	}

	// Send the values
	ksids := []uint64{0x2000000000000000, 0x6000000000000000}
	for i := min; i < max; i++ {
		if err := sendReply(&mproto.QueryResult{
			Rows: [][]sqltypes.Value{
				[]sqltypes.Value{
					sqltypes.MakeString([]byte(fmt.Sprintf("%v", i))),
					sqltypes.MakeString([]byte(fmt.Sprintf("Text for %v", i))),
					sqltypes.MakeString([]byte(fmt.Sprintf("%v", ksids[i%2]))),
				},
			},
		}); err != nil {
			return err
		}
	}
	// SELECT id, msg, keyspace_id FROM table1 WHERE id>=180 AND id<190 ORDER BY id
	return nil
}
예제 #3
0
// AsInterface converts the ValExpr to an interface. It converts
// ValTuple to []interface{}, ValArg to string, StrVal to sqltypes.String,
// NumVal to sqltypes.Numeric, NullVal to nil.
// Otherwise, it returns an error.
func AsInterface(node ValExpr) (interface{}, error) {
	switch node := node.(type) {
	case ValTuple:
		vals := make([]interface{}, 0, len(node))
		for _, val := range node {
			v, err := AsInterface(val)
			if err != nil {
				return nil, err
			}
			vals = append(vals, v)
		}
		return vals, nil
	case ValArg:
		return string(node), nil
	case ListArg:
		return string(node), nil
	case StrVal:
		return sqltypes.MakeString(node), nil
	case NumVal:
		n, err := sqltypes.BuildNumeric(string(node))
		if err != nil {
			return nil, fmt.Errorf("type mismatch: %s", err)
		}
		return n, nil
	case *NullVal:
		return nil, nil
	}
	return nil, fmt.Errorf("unexpected node %v", node)
}
예제 #4
0
파일: mysql.go 프로젝트: henryanand/vitess
func BuildValue(bytes []byte, fieldType uint32) sqltypes.Value {
	if bytes == nil {
		return sqltypes.NULL
	}
	switch fieldType {
	case C.MYSQL_TYPE_DECIMAL, C.MYSQL_TYPE_FLOAT, C.MYSQL_TYPE_DOUBLE, C.MYSQL_TYPE_NEWDECIMAL:
		return sqltypes.MakeFractional(bytes)
	case C.MYSQL_TYPE_TIMESTAMP:
		return sqltypes.MakeString(bytes)
	}
	// The below condition represents the following list of values:
	// C.MYSQL_TYPE_TINY, C.MYSQL_TYPE_SHORT, C.MYSQL_TYPE_LONG, C.MYSQL_TYPE_LONGLONG, C.MYSQL_TYPE_INT24, C.MYSQL_TYPE_YEAR:
	if fieldType <= C.MYSQL_TYPE_INT24 || fieldType == C.MYSQL_TYPE_YEAR {
		return sqltypes.MakeNumeric(bytes)
	}
	return sqltypes.MakeString(bytes)
}
예제 #5
0
func mapToSqlResults(row map[string]string) ([]proto.Field, []sqltypes.Value) {
	fields := make([]proto.Field, len(row))
	values := make([]sqltypes.Value, len(row))
	index := 0
	for key, value := range row {
		fields[index] = proto.Field{Name: key}
		values[index] = sqltypes.MakeString(([]byte)(value))
		index++
	}
	return fields, values
}
예제 #6
0
func TestAppendResult(t *testing.T) {
	qr := new(mproto.QueryResult)
	innerqr1 := &mproto.QueryResult{
		Fields: []mproto.Field{},
		Rows:   [][]sqltypes.Value{},
	}
	innerqr2 := &mproto.QueryResult{
		Fields: []mproto.Field{
			{Name: "foo", Type: 1},
		},
		RowsAffected: 1,
		InsertId:     1,
		Rows: [][]sqltypes.Value{
			{sqltypes.MakeString([]byte("abcd"))},
		},
	}
	// test one empty result
	appendResult(qr, innerqr1)
	appendResult(qr, innerqr2)
	if len(qr.Fields) != 1 {
		t.Errorf("want 1, got %v", len(qr.Fields))
	}
	if qr.RowsAffected != 1 {
		t.Errorf("want 1, got %v", qr.RowsAffected)
	}
	if qr.InsertId != 1 {
		t.Errorf("want 1, got %v", qr.InsertId)
	}
	if len(qr.Rows) != 1 {
		t.Errorf("want 1, got %v", len(qr.Rows))
	}
	// test two valid results
	qr = new(mproto.QueryResult)
	appendResult(qr, innerqr2)
	appendResult(qr, innerqr2)
	if len(qr.Fields) != 1 {
		t.Errorf("want 1, got %v", len(qr.Fields))
	}
	if qr.RowsAffected != 2 {
		t.Errorf("want 2, got %v", qr.RowsAffected)
	}
	if qr.InsertId != 1 {
		t.Errorf("want 1, got %v", qr.InsertId)
	}
	if len(qr.Rows) != 2 {
		t.Errorf("want 2, got %v", len(qr.Rows))
	}
}
예제 #7
0
func (rc *RowCache) decodeRow(b []byte) (row []sqltypes.Value) {
	rowlen := pack.Uint32(b)
	data := b[4+rowlen*4:]
	row = make([]sqltypes.Value, rowlen)
	for i := range row {
		length := pack.Uint32(b[4+i*4:])
		if length == 0xFFFFFFFF {
			continue
		}
		if length > uint32(len(data)) {
			// Corrupt data
			return nil
		}
		if rc.tableInfo.Columns[i].Category == schema.CAT_NUMBER {
			row[i] = sqltypes.MakeNumeric(data[:length])
		} else {
			row[i] = sqltypes.MakeString(data[:length])
		}
		data = data[length:]
	}
	return row
}
예제 #8
0
파일: codex.go 프로젝트: henryanand/vitess
func validateKey(tableInfo *TableInfo, key string) (newKey string) {
	if key == "" {
		// TODO: Verify auto-increment table
		return
	}
	pieces := strings.Split(key, ".")
	if len(pieces) != len(tableInfo.PKColumns) {
		// TODO: Verify auto-increment table
		return ""
	}
	pkValues := make([]sqltypes.Value, len(tableInfo.PKColumns))
	for i, piece := range pieces {
		if piece[0] == '\'' {
			s, err := base64.StdEncoding.DecodeString(piece[1 : len(piece)-1])
			if err != nil {
				log.Warningf("Error decoding key %s for table %s: %v", key, tableInfo.Name, err)
				internalErrors.Add("Mismatch", 1)
				return
			}
			pkValues[i] = sqltypes.MakeString(s)
		} else if piece == "null" {
			// TODO: Verify auto-increment table
			return ""
		} else {
			n, err := sqltypes.BuildNumeric(piece)
			if err != nil {
				log.Warningf("Error decoding key %s for table %s: %v", key, tableInfo.Name, err)
				internalErrors.Add("Mismatch", 1)
				return
			}
			pkValues[i] = n
		}
	}
	if newKey = buildKey(pkValues); newKey != key {
		log.Warningf("Error: Key mismatch, received: %s, computed: %s", key, newKey)
		internalErrors.Add("Mismatch", 1)
	}
	return newKey
}
예제 #9
0
파일: schema.go 프로젝트: henryanand/vitess
func (ta *Table) AddColumn(name string, columnType string, defval sqltypes.Value, extra string) {
	index := len(ta.Columns)
	ta.Columns = append(ta.Columns, TableColumn{Name: name})
	if strings.Contains(columnType, "int") {
		ta.Columns[index].Category = CAT_NUMBER
	} else if strings.HasPrefix(columnType, "varbinary") {
		ta.Columns[index].Category = CAT_VARBINARY
	} else {
		ta.Columns[index].Category = CAT_OTHER
	}
	if extra == "auto_increment" {
		ta.Columns[index].IsAuto = true
		// Ignore default value, if any
		return
	}
	if defval.IsNull() {
		return
	}
	if ta.Columns[index].Category == CAT_NUMBER {
		ta.Columns[index].Default = sqltypes.MakeNumeric(defval.Raw())
	} else {
		ta.Columns[index].Default = sqltypes.MakeString(defval.Raw())
	}
}
예제 #10
0
func TestRowSplitterString(t *testing.T) {
	shards := []*topo.ShardInfo{
		siBytes("", "E"),
		siBytes("E", "L"),
		siBytes("L", ""),
	}
	rs := NewRowSplitter(shards, key.KIT_BYTES, 1)

	// rows in different shards
	row0 := []sqltypes.Value{
		sqltypes.MakeString([]byte("Ignored Value")),
		sqltypes.MakeString([]byte("A")),
	}
	row1 := []sqltypes.Value{
		sqltypes.MakeString([]byte("Ignored Value")),
		sqltypes.MakeString([]byte("G")),
	}
	row2 := []sqltypes.Value{
		sqltypes.MakeString([]byte("Ignored Value")),
		sqltypes.MakeString([]byte("Q")),
	}

	// basic split
	rows := [][]sqltypes.Value{row0, row1, row2, row2, row1, row2, row0}
	result := rs.StartSplit()
	if err := rs.Split(result, rows); err != nil {
		t.Fatalf("Split failed: %v", err)
	}
	if len(result) != 3 {
		t.Fatalf("Bad column count: %v", rows)
	}
	if !reflect.DeepEqual(result[0], [][]sqltypes.Value{row0, row0}) {
		t.Fatalf("Bad result[0]: %v", result[0])
	}
	if !reflect.DeepEqual(result[1], [][]sqltypes.Value{row1, row1}) {
		t.Fatalf("Bad result[1]: %v", result[1])
	}
	if !reflect.DeepEqual(result[2], [][]sqltypes.Value{row2, row2, row2}) {
		t.Fatalf("Bad result[2]: %v", result[2])
	}
}
예제 #11
0
func TestRowSplitterUint64(t *testing.T) {
	shards := []*topo.ShardInfo{
		si("", "40"),
		si("40", "c0"),
		si("c0", ""),
	}
	rs := NewRowSplitter(shards, key.KIT_UINT64, 1)

	// rows in different shards
	row0 := []sqltypes.Value{
		sqltypes.MakeString([]byte("Ignored Value")),
		sqltypes.MakeString([]byte(fmt.Sprintf("%v", 0x1000000000000000))),
	}
	row1 := []sqltypes.Value{
		sqltypes.MakeString([]byte("Ignored Value")),
		sqltypes.MakeString([]byte(fmt.Sprintf("%v", 0x6000000000000000))),
	}
	row2 := []sqltypes.Value{
		sqltypes.MakeString([]byte("Ignored Value")),
		sqltypes.MakeString([]byte(fmt.Sprintf("%v", uint64(0xe000000000000000)))),
	}

	// basic split
	rows := [][]sqltypes.Value{row0, row1, row2, row2, row1, row2, row0}
	result := rs.StartSplit()
	if err := rs.Split(result, rows); err != nil {
		t.Fatalf("Split failed: %v", err)
	}
	if len(result) != 3 {
		t.Fatalf("Bad column count: %v", rows)
	}
	if !reflect.DeepEqual(result[0], [][]sqltypes.Value{row0, row0}) {
		t.Fatalf("Bad result[0]: %v", result[0])
	}
	if !reflect.DeepEqual(result[1], [][]sqltypes.Value{row1, row1}) {
		t.Fatalf("Bad result[1]: %v", result[1])
	}
	if !reflect.DeepEqual(result[2], [][]sqltypes.Value{row2, row2, row2}) {
		t.Fatalf("Bad result[2]: %v", result[2])
	}
}
예제 #12
0
var testExecuteFetchResult = &mproto.QueryResult{
	Fields: []mproto.Field{
		mproto.Field{
			Name: "column1",
			Type: mproto.VT_TINY_BLOB,
		},
		mproto.Field{
			Name: "column2",
			Type: mproto.VT_TIMESTAMP,
		},
	},
	RowsAffected: 10,
	InsertId:     32,
	Rows: [][]sqltypes.Value{
		[]sqltypes.Value{
			sqltypes.MakeString([]byte("ABC")),
		},
	},
}

func (fra *fakeRpcAgent) ExecuteFetch(ctx context.Context, query string, maxrows int, wantFields, disableBinlogs bool) (*mproto.QueryResult, error) {
	compare(fra.t, "ExecuteFetch query", query, testExecuteFetchQuery)
	compare(fra.t, "ExecuteFetch maxrows", maxrows, testExecuteFetchMaxRows)
	compareBool(fra.t, "ExecuteFetch wantFields", wantFields)
	compareBool(fra.t, "ExecuteFetch disableBinlogs", disableBinlogs)
	return testExecuteFetchResult, nil
}

func agentRpcTestExecuteFetch(ctx context.Context, t *testing.T, client tmclient.TabletManagerClient, ti *topo.TabletInfo) {
	qr, err := client.ExecuteFetch(ctx, ti, testExecuteFetchQuery, testExecuteFetchMaxRows, true, true)
	compareError(t, "ExecuteFetch", err, qr, testExecuteFetchResult)
예제 #13
0
파일: ast.go 프로젝트: henryanand/vitess
func (node StrVal) Format(buf *TrackedBuffer) {
	s := sqltypes.MakeString([]byte(node))
	s.EncodeSql(buf)
}
예제 #14
0
func TestParsedQuery(t *testing.T) {
	tcases := []struct {
		desc     string
		query    string
		bindVars map[string]interface{}
		output   string
	}{
		{
			"no subs",
			"select * from a where id = 2",
			map[string]interface{}{
				"id": 1,
			},
			"select * from a where id = 2",
		}, {
			"simple bindvar sub",
			"select * from a where id1 = :id1 and id2 = :id2",
			map[string]interface{}{
				"id1": 1,
				"id2": nil,
			},
			"select * from a where id1 = 1 and id2 = null",
		}, {
			"missing bind var",
			"select * from a where id1 = :id1 and id2 = :id2",
			map[string]interface{}{
				"id1": 1,
			},
			"missing bind var id2",
		}, {
			"unencodable bind var",
			"select * from a where id1 = :id",
			map[string]interface{}{
				"id": make([]int, 1),
			},
			"unsupported bind variable type []int: [0]",
		}, {
			"list inside bind vars",
			"select * from a where id in (:vals)",
			map[string]interface{}{
				"vals": []sqltypes.Value{
					sqltypes.MakeNumeric([]byte("1")),
					sqltypes.MakeString([]byte("aa")),
				},
			},
			"select * from a where id in (1, 'aa')",
		}, {
			"two lists inside bind vars",
			"select * from a where id in (:vals)",
			map[string]interface{}{
				"vals": [][]sqltypes.Value{
					[]sqltypes.Value{
						sqltypes.MakeNumeric([]byte("1")),
						sqltypes.MakeString([]byte("aa")),
					},
					[]sqltypes.Value{
						sqltypes.Value{},
						sqltypes.MakeString([]byte("bb")),
					},
				},
			},
			"select * from a where id in ((1, 'aa'), (null, 'bb'))",
		}, {
			"list bind vars",
			"select * from a where id in ::vals",
			map[string]interface{}{
				"vals": []interface{}{
					1,
					"aa",
				},
			},
			"select * from a where id in (1, 'aa')",
		}, {
			"list bind vars single argument",
			"select * from a where id in ::vals",
			map[string]interface{}{
				"vals": []interface{}{
					1,
				},
			},
			"select * from a where id in (1)",
		}, {
			"list bind vars 0 arguments",
			"select * from a where id in ::vals",
			map[string]interface{}{
				"vals": []interface{}{},
			},
			"empty list supplied for vals",
		}, {
			"non-list bind var supplied",
			"select * from a where id in ::vals",
			map[string]interface{}{
				"vals": 1,
			},
			"unexpected list arg type int for key vals",
		}, {
			"list bind var for non-list",
			"select * from a where id = :vals",
			map[string]interface{}{
				"vals": []interface{}{1},
			},
			"unexpected arg type []interface {} for key vals",
		}, {
			"single column tuple equality",
			// We have to use an incorrect construct to get around the parser.
			"select * from a where b = :equality",
			map[string]interface{}{
				"equality": TupleEqualityList{
					Columns: []string{"pk"},
					Rows: [][]sqltypes.Value{
						[]sqltypes.Value{sqltypes.MakeNumeric([]byte("1"))},
						[]sqltypes.Value{sqltypes.MakeString([]byte("aa"))},
					},
				},
			},
			"select * from a where b = pk in (1, 'aa')",
		}, {
			"multi column tuple equality",
			"select * from a where b = :equality",
			map[string]interface{}{
				"equality": TupleEqualityList{
					Columns: []string{"pk1", "pk2"},
					Rows: [][]sqltypes.Value{
						[]sqltypes.Value{
							sqltypes.MakeNumeric([]byte("1")),
							sqltypes.MakeString([]byte("aa")),
						},
						[]sqltypes.Value{
							sqltypes.MakeNumeric([]byte("2")),
							sqltypes.MakeString([]byte("bb")),
						},
					},
				},
			},
			"select * from a where b = (pk1 = 1 and pk2 = 'aa') or (pk1 = 2 and pk2 = 'bb')",
		}, {
			"0 rows",
			"select * from a where b = :equality",
			map[string]interface{}{
				"equality": TupleEqualityList{
					Columns: []string{"pk"},
					Rows:    [][]sqltypes.Value{},
				},
			},
			"cannot encode with 0 rows",
		}, {
			"values don't match column count",
			"select * from a where b = :equality",
			map[string]interface{}{
				"equality": TupleEqualityList{
					Columns: []string{"pk"},
					Rows: [][]sqltypes.Value{
						[]sqltypes.Value{
							sqltypes.MakeNumeric([]byte("1")),
							sqltypes.MakeString([]byte("aa")),
						},
					},
				},
			},
			"values don't match column count",
		},
	}

	for _, tcase := range tcases {
		tree, err := Parse(tcase.query)
		if err != nil {
			t.Errorf("parse failed for %s: %v", tcase.desc, err)
			continue
		}
		buf := NewTrackedBuffer(nil)
		buf.Myprintf("%v", tree)
		pq := buf.ParsedQuery()
		bytes, err := pq.GenerateQuery(tcase.bindVars)
		var got string
		if err != nil {
			got = err.Error()
		} else {
			got = string(bytes)
		}
		if got != tcase.output {
			t.Errorf("for test case: %s, got: '%s', want '%s'", tcase.desc, got, tcase.output)
		}
	}
}
예제 #15
0
		encoded: "E\x00\x00\x00\x04Fields\x00\x05\x00\x00\x00\x00?RowsAffected\x00\x00\x00\x00\x00\x00\x00\x00\x00?InsertId\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04Rows\x00\x05\x00\x00\x00\x00\x00",
	},
	// Only fields set
	{
		qr: QueryResult{
			Fields: []Field{
				{Name: "foo", Type: 1},
			},
		},
		encoded: "i\x00\x00\x00\x04Fields\x00)\x00\x00\x00\x030\x00!\x00\x00\x00\x05Name\x00\x03\x00\x00\x00\x00foo\x12Type\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00?RowsAffected\x00\x00\x00\x00\x00\x00\x00\x00\x00?InsertId\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04Rows\x00\x05\x00\x00\x00\x00\x00",
	},
	// Only rows, no fields
	{
		qr: QueryResult{
			Rows: [][]sqltypes.Value{
				{sqltypes.MakeString([]byte("abcd")), sqltypes.MakeNumeric([]byte("1234")), sqltypes.MakeFractional([]byte("1.234"))},
			},
		},
		encoded: "r\x00\x00\x00\x04Fields\x00\x05\x00\x00\x00\x00?RowsAffected\x00\x00\x00\x00\x00\x00\x00\x00\x00?InsertId\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04Rows\x002\x00\x00\x00\x040\x00*\x00\x00\x00\x050\x00\x04\x00\x00\x00\x00abcd\x051\x00\x04\x00\x00\x00\x001234\x052\x00\x05\x00\x00\x00\x001.234\x00\x00\x00",
	},
	// one row and one field
	{
		qr: QueryResult{
			Fields: []Field{
				{Name: "foo", Type: 1},
			},
			Rows: [][]sqltypes.Value{
				{sqltypes.MakeString([]byte("abcd")), sqltypes.MakeNumeric([]byte("1234")), sqltypes.MakeFractional([]byte("1.234")), sqltypes.Value{}},
			},
		},
		encoded: "",
예제 #16
0
func TestConvert(t *testing.T) {
	cases := []struct {
		Desc string
		Typ  int64
		Val  sqltypes.Value
		Want interface{}
	}{{
		Desc: "null",
		Typ:  VT_LONG,
		Val:  sqltypes.Value{},
		Want: nil,
	}, {
		Desc: "decimal",
		Typ:  VT_DECIMAL,
		Val:  sqltypes.MakeString([]byte("aa")),
		Want: "aa",
	}, {
		Desc: "tiny",
		Typ:  VT_TINY,
		Val:  sqltypes.MakeString([]byte("1")),
		Want: int64(1),
	}, {
		Desc: "short",
		Typ:  VT_SHORT,
		Val:  sqltypes.MakeString([]byte("1")),
		Want: int64(1),
	}, {
		Desc: "long",
		Typ:  VT_LONG,
		Val:  sqltypes.MakeString([]byte("1")),
		Want: int64(1),
	}, {
		Desc: "longlong",
		Typ:  VT_LONGLONG,
		Val:  sqltypes.MakeString([]byte("1")),
		Want: int64(1),
	}, {
		Desc: "int24",
		Typ:  VT_INT24,
		Val:  sqltypes.MakeString([]byte("1")),
		Want: int64(1),
	}, {
		Desc: "float",
		Typ:  VT_FLOAT,
		Val:  sqltypes.MakeString([]byte("1")),
		Want: float64(1),
	}, {
		Desc: "double",
		Typ:  VT_DOUBLE,
		Val:  sqltypes.MakeString([]byte("1")),
		Want: float64(1),
	}, {
		Desc: "large int",
		Typ:  VT_LONGLONG,
		Val:  sqltypes.MakeString([]byte("9223372036854775808")),
		Want: uint64(9223372036854775808),
	}, {
		Desc: "float for int",
		Typ:  VT_LONGLONG,
		Val:  sqltypes.MakeString([]byte("1.1")),
		Want: `strconv.ParseUint: parsing "1.1": invalid syntax`,
	}, {
		Desc: "string for float",
		Typ:  VT_FLOAT,
		Val:  sqltypes.MakeString([]byte("aa")),
		Want: `strconv.ParseFloat: parsing "aa": invalid syntax`,
	}}

	for _, c := range cases {
		r, err := Convert(c.Typ, c.Val)
		if err != nil {
			r = err.Error()
		} else if _, ok := r.([]byte); ok {
			r = string(r.([]byte))
		}
		if r != c.Want {
			t.Errorf("%s: %+v, want %+v", c.Desc, r, c.Want)
		}
	}
}