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  }