Skip to content

Commit

Permalink
planner: split test data from test cases (pingcap#12091)
Browse files Browse the repository at this point in the history
  • Loading branch information
alivxxx committed Sep 27, 2019
1 parent b736230 commit 13d558e
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 1 deletion.
12 changes: 11 additions & 1 deletion planner/core/physical_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,30 @@ import (
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/sessionctx/stmtctx"
"github.com/pingcap/tidb/util/testleak"
"github.com/pingcap/tidb/util/testutil"
)

var _ = Suite(&testPlanSuite{})

type testPlanSuite struct {
*parser.Parser

is infoschema.InfoSchema

testData testutil.TestData
}

func (s *testPlanSuite) SetUpSuite(c *C) {
s.is = infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable()})
s.Parser = parser.New()
s.Parser.EnableWindowFunc(true)

var err error
s.testData, err = testutil.LoadTestSuiteData("testdata", "plan_suite")
c.Assert(err, IsNil)
}

func (s *testPlanSuite) TearDownSuite(c *C) {
c.Assert(s.testData.GenerateOutputIfNeeded(), IsNil)
}

func (s *testPlanSuite) TestDAGPlanBuilderSimpleCase(c *C) {
Expand Down
18 changes: 18 additions & 0 deletions planner/core/testdata/plan_suite_in.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"name": "TestIndexHint",
"cases": [
// simple case
"select /*+ USE_INDEX(t, c_d_e) */ * from t",
"select /*+ USE_INDEX(t, c_d_e) */ * from t t1",
"select /*+ USE_INDEX(t1, c_d_e) */ * from t t1",
"select /*+ USE_INDEX(t1, c_d_e), USE_INDEX(t2, f) */ * from t t1, t t2 where t1.a = t2.b",
// test multiple indexes
"select /*+ USE_INDEX(t, c_d_e, f, g) */ * from t order by f",
// use TablePath when the hint only contains table.
"select /*+ USE_INDEX(t) */ f from t where f > 10",
// there will be a warning instead of error when index not exist
"select /*+ USE_INDEX(t, no_such_index) */ * from t"
]
}
]
35 changes: 35 additions & 0 deletions planner/core/testdata/plan_suite_out.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[
{
"Name": "TestIndexHint",
"Cases": [
{
"Best": "IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))",
"HasWarn": false
},
{
"Best": "TableReader(Table(t))",
"HasWarn": false
},
{
"Best": "IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))",
"HasWarn": false
},
{
"Best": "LeftHashJoin{IndexLookUp(Index(t.c_d_e)[[NULL,+inf]], Table(t))-\u003eIndexLookUp(Index(t.f)[[NULL,+inf]], Table(t))}(test.t1.a,test.t2.b)",
"HasWarn": false
},
{
"Best": "IndexLookUp(Index(t.f)[[NULL,+inf]], Table(t))",
"HasWarn": false
},
{
"Best": "TableReader(Table(t)-\u003eSel([gt(test.t.f, 10)]))",
"HasWarn": false
},
{
"Best": "TableReader(Table(t))",
"HasWarn": true
}
]
}
]
137 changes: 137 additions & 0 deletions util/testutil/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,19 @@
package testutil

import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strings"

"github.com/pingcap/check"
"github.com/pingcap/errors"
"github.com/pingcap/tidb/sessionctx/stmtctx"
"github.com/pingcap/tidb/types"
)
Expand Down Expand Up @@ -108,3 +117,131 @@ func RowsWithSep(sep string, args ...string) [][]interface{} {
}
return rows
}

// record is a flag used for generate test result.
var record bool

func init() {
flag.BoolVar(&record, "record", false, "to generate test result")
}

type testCases struct {
Name string
Cases *json.RawMessage // For delayed parse.
decodedOut interface{} // For generate output.
}

// TestData stores all the data of a test suite.
type TestData struct {
input []testCases
output []testCases
filePathPrefix string
funcMap map[string]int
}

// LoadTestSuiteData loads test suite data from file.
func LoadTestSuiteData(dir, suiteName string) (res TestData, err error) {
res.filePathPrefix = filepath.Join(dir, suiteName)
res.input, err = loadTestSuiteCases(fmt.Sprintf("%s_in.json", res.filePathPrefix))
if err != nil {
return res, err
}
if record {
res.output = make([]testCases, len(res.input), len(res.input))
for i := range res.input {
res.output[i].Name = res.input[i].Name
}
} else {
res.output, err = loadTestSuiteCases(fmt.Sprintf("%s_out.json", res.filePathPrefix))
if err != nil {
return res, err
}
if len(res.input) != len(res.output) {
return res, errors.New(fmt.Sprintf("Number of test input cases %d does not match test output cases %d", len(res.input), len(res.output)))
}
}
res.funcMap = make(map[string]int, len(res.input))
for i, test := range res.input {
res.funcMap[test.Name] = i
if test.Name != res.output[i].Name {
return res, errors.New(fmt.Sprintf("Input name of the %d-case %s does not match output %s", i, test.Name, res.output[i].Name))
}
}
return res, nil
}

func loadTestSuiteCases(filePath string) (res []testCases, err error) {
jsonFile, err := os.Open(filePath)
if err != nil {
return res, err
}
defer jsonFile.Close()
byteValue, err := ioutil.ReadAll(jsonFile)
if err != nil {
return res, err
}
// Remove comments, since they are not allowed in json.
re := regexp.MustCompile("(?s)//.*?\n")
err = json.Unmarshal(re.ReplaceAll(byteValue, nil), &res)
return res, err
}

// GetTestCases gets the test cases for a test function.
func (t *TestData) GetTestCases(c *check.C, in interface{}, out interface{}) {
// Extract caller's name.
pc, _, _, ok := runtime.Caller(1)
c.Assert(ok, check.IsTrue)
details := runtime.FuncForPC(pc)
funcNameIdx := strings.LastIndex(details.Name(), ".")
funcName := details.Name()[funcNameIdx+1:]

casesIdx, ok := t.funcMap[funcName]
c.Assert(ok, check.IsTrue, check.Commentf("Must get test %s", funcName))
err := json.Unmarshal(*t.input[casesIdx].Cases, in)
c.Assert(err, check.IsNil)
if !record {
err = json.Unmarshal(*t.output[casesIdx].Cases, out)
c.Assert(err, check.IsNil)
} else {
// Init for generate output file.
inputLen := reflect.ValueOf(in).Elem().Len()
v := reflect.ValueOf(out).Elem()
if v.Kind() == reflect.Slice {
v.Set(reflect.MakeSlice(v.Type(), inputLen, inputLen))
}
}
t.output[casesIdx].decodedOut = out
}

// OnRecord execute the function to update result.
func (t *TestData) OnRecord(updateFunc func()) {
if record {
updateFunc()
}
}

// GenerateOutputIfNeeded generate the output file.
func (t *TestData) GenerateOutputIfNeeded() error {
if !record {
return nil
}
for i, test := range t.output {
bytes, err := json.MarshalIndent(test.decodedOut, "", " ")
if err != nil {
return err
}
rm := json.RawMessage(bytes)
t.output[i].Cases = &rm
}
res, err := json.MarshalIndent(t.output, "", " ")
if err != nil {
return err
}
file, err := os.Create(fmt.Sprintf("%s_out.json", t.filePathPrefix))
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(res)
return err
}

0 comments on commit 13d558e

Please sign in to comment.