github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/rowexec/processor_utils_test.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package rowexec 12 13 import ( 14 "context" 15 "fmt" 16 "sort" 17 "strings" 18 "testing" 19 20 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 21 "github.com/cockroachdb/cockroach/pkg/sql/execinfra" 22 "github.com/cockroachdb/cockroach/pkg/sql/execinfrapb" 23 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 24 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 25 "github.com/cockroachdb/cockroach/pkg/sql/types" 26 "github.com/cockroachdb/cockroach/pkg/testutils/distsqlutils" 27 "github.com/cockroachdb/cockroach/pkg/util/encoding" 28 ) 29 30 type ProcessorTestConfig struct { 31 // ProcessorTest takes ownership of the evalCtx passed through this FlowCtx. 32 FlowCtx *execinfra.FlowCtx 33 34 BeforeTestCase func(p execinfra.Processor, inputs []execinfra.RowSource, output execinfra.RowReceiver) 35 AfterTestCase func(p execinfra.Processor, inputs []execinfra.RowSource, output execinfra.RowReceiver) 36 } 37 38 func DefaultProcessorTestConfig() ProcessorTestConfig { 39 st := cluster.MakeTestingClusterSettings() 40 evalCtx := tree.MakeTestingEvalContext(st) 41 return ProcessorTestConfig{ 42 FlowCtx: &execinfra.FlowCtx{ 43 Cfg: &execinfra.ServerConfig{Settings: st}, 44 EvalCtx: &evalCtx, 45 }, 46 } 47 } 48 49 // ProcessorTestCaseRows is a number of rows of go values with an associated 50 // schema that can be converted to sqlbase.EncDatumRows. 51 type ProcessorTestCaseRows struct { 52 Rows [][]interface{} 53 Types []*types.T 54 } 55 56 // toEncDatum converts a go value to an EncDatum. 57 func toEncDatum(datumType *types.T, v interface{}) sqlbase.EncDatum { 58 d := func() tree.Datum { 59 switch concreteType := v.(type) { 60 case int: 61 if datumType.Family() == types.DecimalFamily { 62 dd := &tree.DDecimal{} 63 dd.SetInt64(int64(v.(int))) 64 return dd 65 } 66 return tree.NewDInt(tree.DInt(v.(int))) 67 case bool: 68 return tree.MakeDBool(tree.DBool(v.(bool))) 69 case nil: 70 return tree.DNull 71 default: 72 panic(fmt.Sprintf("type %T not supported yet", concreteType)) 73 } 74 }() 75 // Initialize both EncDatum.Datum, and EncDatum.encoded. 76 encoded, err := sqlbase.EncodeTableKey(nil, d, encoding.Ascending) 77 if err != nil { 78 panic(err) 79 } 80 encodedDatum := sqlbase.EncDatumFromEncoded(sqlbase.DatumEncoding_ASCENDING_KEY, encoded) 81 encodedDatum.Datum = d 82 return encodedDatum 83 } 84 85 func (r ProcessorTestCaseRows) toEncDatumRows() sqlbase.EncDatumRows { 86 result := make(sqlbase.EncDatumRows, len(r.Rows)) 87 for i, row := range r.Rows { 88 if len(row) != len(r.Types) { 89 panic("mismatched number of columns and number of types") 90 } 91 result[i] = make(sqlbase.EncDatumRow, len(row)) 92 for j, col := range row { 93 result[i][j] = toEncDatum(r.Types[j], col) 94 } 95 } 96 return result 97 } 98 99 // ProcessorTestCase is the specification for a test that creates a processor 100 // given the struct fields, runs it with the given input, and verifies that 101 // the output is expected. 102 type ProcessorTestCase struct { 103 Name string 104 Input ProcessorTestCaseRows 105 Output ProcessorTestCaseRows 106 107 // SecondInput can be optionally set by processors that take in two inputs. 108 SecondInput *ProcessorTestCaseRows 109 110 // DisableSort disables the sorting of the output produced by the processor 111 // before checking for expected output. 112 DisableSort bool 113 114 // ProcessorCoreUnion is the spec to be passed in to NewProcessor when 115 // creating the processor to run this test case. 116 ProcessorCore execinfrapb.ProcessorCoreUnion 117 118 // Post is the PostProcessSpec to be used when creating the processor. 119 Post execinfrapb.PostProcessSpec 120 } 121 122 // ProcessorTest runs one or more ProcessorTestCases. 123 type ProcessorTest struct { 124 config ProcessorTestConfig 125 } 126 127 // MakeProcessorTest makes a ProcessorTest with the given config. 128 func MakeProcessorTest(config ProcessorTestConfig) ProcessorTest { 129 return ProcessorTest{ 130 config: config, 131 } 132 } 133 134 // RunTestCases runs the given ProcessorTestCases. 135 func (p *ProcessorTest) RunTestCases( 136 ctx context.Context, t *testing.T, testCases []ProcessorTestCase, 137 ) { 138 var processorID int32 139 for _, tc := range testCases { 140 inputs := make([]execinfra.RowSource, 1, 2) 141 inputs[0] = distsqlutils.NewRowBuffer( 142 tc.Input.Types, tc.Input.toEncDatumRows(), distsqlutils.RowBufferArgs{}, 143 ) 144 if tc.SecondInput != nil { 145 inputs[1] = distsqlutils.NewRowBuffer( 146 tc.SecondInput.Types, tc.SecondInput.toEncDatumRows(), distsqlutils.RowBufferArgs{}, 147 ) 148 } 149 output := distsqlutils.NewRowBuffer( 150 tc.Output.Types, nil, distsqlutils.RowBufferArgs{}, 151 ) 152 153 processor, err := NewProcessor( 154 ctx, 155 p.config.FlowCtx, 156 processorID, 157 &tc.ProcessorCore, 158 &tc.Post, 159 inputs, 160 []execinfra.RowReceiver{output}, 161 nil, /* localProcessors */ 162 ) 163 if err != nil { 164 t.Fatalf("test case %s processor creation failed %s", tc.Name, err) 165 } 166 processorID++ 167 168 if p.config.BeforeTestCase != nil { 169 p.config.BeforeTestCase(processor, inputs, output) 170 } 171 172 processor.Run(ctx) 173 174 if p.config.AfterTestCase != nil { 175 p.config.AfterTestCase(processor, inputs, output) 176 } 177 178 expectedRows := tc.Output.toEncDatumRows() 179 expected := make([]string, len(expectedRows)) 180 for i, row := range expectedRows { 181 expected[i] = row.String(tc.Output.Types) 182 } 183 if !tc.DisableSort { 184 sort.Strings(expected) 185 } 186 187 var returned []string 188 for { 189 row := output.NextNoMeta(t) 190 if row == nil { 191 break 192 } 193 returned = append(returned, row.String(tc.Output.Types)) 194 } 195 if !tc.DisableSort { 196 sort.Strings(returned) 197 } 198 199 expStr := strings.Join(expected, "") 200 retStr := strings.Join(returned, "") 201 if expStr != retStr { 202 t.Errorf( 203 "test case %s (DisableSort=%t) invalid results; expected\n%s\ngot:\n%s", 204 tc.Name, 205 tc.DisableSort, 206 expStr, 207 retStr, 208 ) 209 } 210 } 211 } 212 213 func (p ProcessorTest) Close(ctx context.Context) { 214 p.config.FlowCtx.EvalCtx.Stop(ctx) 215 }