github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/pkg/pipeline/pipeline_test.go (about)

     1  // Copyright 2020 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package pipeline
    15  
    16  import (
    17  	stdCtx "context"
    18  	"fmt"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/pingcap/check"
    23  	"github.com/pingcap/errors"
    24  	"github.com/pingcap/log"
    25  	"github.com/pingcap/ticdc/cdc/model"
    26  	"github.com/pingcap/ticdc/pkg/context"
    27  	cerror "github.com/pingcap/ticdc/pkg/errors"
    28  	"github.com/pingcap/ticdc/pkg/util/testleak"
    29  	"go.uber.org/zap"
    30  )
    31  
    32  func TestSuite(t *testing.T) { check.TestingT(t) }
    33  
    34  type pipelineSuite struct{}
    35  
    36  var _ = check.Suite(&pipelineSuite{})
    37  
    38  type echoNode struct {
    39  }
    40  
    41  func (e echoNode) Init(ctx NodeContext) error {
    42  	ctx.SendToNextNode(PolymorphicEventMessage(&model.PolymorphicEvent{
    43  		Row: &model.RowChangedEvent{
    44  			Table: &model.TableName{
    45  				Schema: "init function is called in echo node",
    46  			},
    47  		},
    48  	}))
    49  	return nil
    50  }
    51  
    52  func (e echoNode) Receive(ctx NodeContext) error {
    53  	msg := ctx.Message()
    54  	log.Info("Receive message in echo node", zap.Any("msg", msg))
    55  	ctx.SendToNextNode(msg)
    56  	ctx.SendToNextNode(PolymorphicEventMessage(&model.PolymorphicEvent{
    57  		Row: &model.RowChangedEvent{
    58  			Table: &model.TableName{
    59  				Schema: "ECHO: " + msg.PolymorphicEvent.Row.Table.Schema,
    60  				Table:  "ECHO: " + msg.PolymorphicEvent.Row.Table.Table,
    61  			},
    62  		},
    63  	}))
    64  	return nil
    65  }
    66  
    67  func (e echoNode) Destroy(ctx NodeContext) error {
    68  	ctx.SendToNextNode(PolymorphicEventMessage(&model.PolymorphicEvent{
    69  		Row: &model.RowChangedEvent{
    70  			Table: &model.TableName{
    71  				Schema: "destory function is called in echo node",
    72  			},
    73  		},
    74  	}))
    75  	return nil
    76  }
    77  
    78  type checkNode struct {
    79  	c        *check.C
    80  	expected []Message
    81  	index    int
    82  }
    83  
    84  func (n *checkNode) Init(ctx NodeContext) error {
    85  	// do nothing
    86  	return nil
    87  }
    88  
    89  func (n *checkNode) Receive(ctx NodeContext) error {
    90  	msg := ctx.Message()
    91  
    92  	log.Info("Receive message in check node", zap.Any("msg", msg))
    93  	n.c.Assert(msg, check.DeepEquals, n.expected[n.index], check.Commentf("index: %d", n.index))
    94  	n.index++
    95  	return nil
    96  }
    97  
    98  func (n *checkNode) Destroy(ctx NodeContext) error {
    99  	n.c.Assert(n.index, check.Equals, len(n.expected))
   100  	return nil
   101  }
   102  
   103  func (s *pipelineSuite) TestPipelineUsage(c *check.C) {
   104  	defer testleak.AfterTest(c)()
   105  	ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{})
   106  	ctx, cancel := context.WithCancel(ctx)
   107  	ctx = context.WithErrorHandler(ctx, func(err error) error {
   108  		c.Fatal(err)
   109  		return err
   110  	})
   111  	runnersSize, outputChannelSize := 2, 64
   112  	p := NewPipeline(ctx, -1, runnersSize, outputChannelSize)
   113  	p.AppendNode(ctx, "echo node", echoNode{})
   114  	p.AppendNode(ctx, "check node", &checkNode{
   115  		c: c,
   116  		expected: []Message{
   117  			PolymorphicEventMessage(&model.PolymorphicEvent{
   118  				Row: &model.RowChangedEvent{
   119  					Table: &model.TableName{
   120  						Schema: "init function is called in echo node",
   121  					},
   122  				},
   123  			}),
   124  			PolymorphicEventMessage(&model.PolymorphicEvent{
   125  				Row: &model.RowChangedEvent{
   126  					Table: &model.TableName{
   127  						Schema: "I am built by test function",
   128  						Table:  "AA1",
   129  					},
   130  				},
   131  			}),
   132  			PolymorphicEventMessage(&model.PolymorphicEvent{
   133  				Row: &model.RowChangedEvent{
   134  					Table: &model.TableName{
   135  						Schema: "ECHO: I am built by test function",
   136  						Table:  "ECHO: AA1",
   137  					},
   138  				},
   139  			}),
   140  			PolymorphicEventMessage(&model.PolymorphicEvent{
   141  				Row: &model.RowChangedEvent{
   142  					Table: &model.TableName{
   143  						Schema: "I am built by test function",
   144  						Table:  "BB2",
   145  					},
   146  				},
   147  			}),
   148  			PolymorphicEventMessage(&model.PolymorphicEvent{
   149  				Row: &model.RowChangedEvent{
   150  					Table: &model.TableName{
   151  						Schema: "ECHO: I am built by test function",
   152  						Table:  "ECHO: BB2",
   153  					},
   154  				},
   155  			}),
   156  			PolymorphicEventMessage(&model.PolymorphicEvent{
   157  				Row: &model.RowChangedEvent{
   158  					Table: &model.TableName{
   159  						Schema: "destory function is called in echo node",
   160  					},
   161  				},
   162  			}),
   163  		},
   164  	})
   165  
   166  	err := p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{
   167  		Row: &model.RowChangedEvent{
   168  			Table: &model.TableName{
   169  				Schema: "I am built by test function",
   170  				Table:  "AA1",
   171  			},
   172  		},
   173  	}))
   174  	c.Assert(err, check.IsNil)
   175  	err = p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{
   176  		Row: &model.RowChangedEvent{
   177  			Table: &model.TableName{
   178  				Schema: "I am built by test function",
   179  				Table:  "BB2",
   180  			},
   181  		},
   182  	}))
   183  	c.Assert(err, check.IsNil)
   184  	cancel()
   185  	p.Wait()
   186  }
   187  
   188  type errorNode struct {
   189  	c     *check.C
   190  	index int
   191  }
   192  
   193  func (n *errorNode) Init(ctx NodeContext) error {
   194  	// do nothing
   195  	return nil
   196  }
   197  
   198  func (n *errorNode) Receive(ctx NodeContext) error {
   199  	msg := ctx.Message()
   200  	log.Info("Receive message in error node", zap.Any("msg", msg))
   201  	n.index++
   202  	if n.index >= 3 {
   203  		return errors.Errorf("error node throw an error, index: %d", n.index)
   204  	}
   205  	ctx.SendToNextNode(msg)
   206  	return nil
   207  }
   208  
   209  func (n *errorNode) Destroy(ctx NodeContext) error {
   210  	n.c.Assert(n.index, check.Equals, 3)
   211  	return nil
   212  }
   213  
   214  func (s *pipelineSuite) TestPipelineError(c *check.C) {
   215  	defer testleak.AfterTest(c)()
   216  	ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{})
   217  	ctx, cancel := context.WithCancel(ctx)
   218  	defer cancel()
   219  	ctx = context.WithErrorHandler(ctx, func(err error) error {
   220  		c.Assert(err.Error(), check.Equals, "error node throw an error, index: 3")
   221  		return nil
   222  	})
   223  	runnersSize, outputChannelSize := 3, 64
   224  	p := NewPipeline(ctx, -1, runnersSize, outputChannelSize)
   225  	p.AppendNode(ctx, "echo node", echoNode{})
   226  	p.AppendNode(ctx, "error node", &errorNode{c: c})
   227  	p.AppendNode(ctx, "check node", &checkNode{
   228  		c: c,
   229  		expected: []Message{
   230  			PolymorphicEventMessage(&model.PolymorphicEvent{
   231  				Row: &model.RowChangedEvent{
   232  					Table: &model.TableName{
   233  						Schema: "init function is called in echo node",
   234  					},
   235  				},
   236  			}),
   237  			PolymorphicEventMessage(&model.PolymorphicEvent{
   238  				Row: &model.RowChangedEvent{
   239  					Table: &model.TableName{
   240  						Schema: "I am built by test function",
   241  						Table:  "CC1",
   242  					},
   243  				},
   244  			}),
   245  		},
   246  	})
   247  
   248  	err := p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{
   249  		Row: &model.RowChangedEvent{
   250  			Table: &model.TableName{
   251  				Schema: "I am built by test function",
   252  				Table:  "CC1",
   253  			},
   254  		},
   255  	}))
   256  	c.Assert(err, check.IsNil)
   257  	// this line may be return an error because the pipeline maybe closed before this line was executed
   258  	//nolint:errcheck
   259  	p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{
   260  		Row: &model.RowChangedEvent{
   261  			Table: &model.TableName{
   262  				Schema: "I am built by test function",
   263  				Table:  "DD2",
   264  			},
   265  		},
   266  	}))
   267  	p.Wait()
   268  }
   269  
   270  type throwNode struct {
   271  	c     *check.C
   272  	index int
   273  }
   274  
   275  func (n *throwNode) Init(ctx NodeContext) error {
   276  	// do nothing
   277  	return nil
   278  }
   279  
   280  func (n *throwNode) Receive(ctx NodeContext) error {
   281  	msg := ctx.Message()
   282  	log.Info("Receive message in error node", zap.Any("msg", msg))
   283  	n.index++
   284  	if n.index >= 3 {
   285  		ctx.Throw(errors.Errorf("error node throw an error, index: %d", n.index))
   286  		return nil
   287  	}
   288  	ctx.SendToNextNode(msg)
   289  	return nil
   290  }
   291  
   292  func (n *throwNode) Destroy(ctx NodeContext) error {
   293  	n.c.Assert(map[int]bool{4: true, 6: true}, check.HasKey, n.index)
   294  	return nil
   295  }
   296  
   297  func (s *pipelineSuite) TestPipelineThrow(c *check.C) {
   298  	defer testleak.AfterTest(c)()
   299  	ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{})
   300  	ctx, cancel := context.WithCancel(ctx)
   301  	defer cancel()
   302  	var errs []error
   303  	ctx = context.WithErrorHandler(ctx, func(err error) error {
   304  		errs = append(errs, err)
   305  		return nil
   306  	})
   307  	runnersSize, outputChannelSize := 2, 64
   308  	p := NewPipeline(ctx, -1, runnersSize, outputChannelSize)
   309  	p.AppendNode(ctx, "echo node", echoNode{})
   310  	p.AppendNode(ctx, "error node", &throwNode{c: c})
   311  	err := p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{
   312  		Row: &model.RowChangedEvent{
   313  			Table: &model.TableName{
   314  				Schema: "I am built by test function",
   315  				Table:  "CC1",
   316  			},
   317  		},
   318  	}))
   319  	c.Assert(err, check.IsNil)
   320  	// whether err is nil is not determined
   321  	// If add some delay here, such as sleep 50ms, there will be more probability
   322  	// that the second message is not sent.
   323  	// time.Sleep(time.Millisecond * 50)
   324  	err = p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{
   325  		Row: &model.RowChangedEvent{
   326  			Table: &model.TableName{
   327  				Schema: "I am built by test function",
   328  				Table:  "DD2",
   329  			},
   330  		},
   331  	}))
   332  	if err != nil {
   333  		// pipeline closed before the second message was sent
   334  		c.Assert(cerror.ErrSendToClosedPipeline.Equal(err), check.IsTrue)
   335  		p.Wait()
   336  		c.Assert(len(errs), check.Equals, 2)
   337  		c.Assert(errs[0].Error(), check.Equals, "error node throw an error, index: 3")
   338  		c.Assert(errs[1].Error(), check.Equals, "error node throw an error, index: 4")
   339  	} else {
   340  		// the second message was sent before pipeline closed
   341  		p.Wait()
   342  		c.Assert(len(errs), check.Equals, 4)
   343  		c.Assert(errs[0].Error(), check.Equals, "error node throw an error, index: 3")
   344  		c.Assert(errs[1].Error(), check.Equals, "error node throw an error, index: 4")
   345  		c.Assert(errs[2].Error(), check.Equals, "error node throw an error, index: 5")
   346  		c.Assert(errs[3].Error(), check.Equals, "error node throw an error, index: 6")
   347  	}
   348  }
   349  
   350  func (s *pipelineSuite) TestPipelineAppendNode(c *check.C) {
   351  	defer testleak.AfterTest(c)()
   352  	ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{})
   353  	ctx, cancel := context.WithCancel(ctx)
   354  	ctx = context.WithErrorHandler(ctx, func(err error) error {
   355  		c.Fatal(err)
   356  		return err
   357  	})
   358  	runnersSize, outputChannelSize := 2, 64
   359  	p := NewPipeline(ctx, -1, runnersSize, outputChannelSize)
   360  	err := p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{
   361  		Row: &model.RowChangedEvent{
   362  			Table: &model.TableName{
   363  				Schema: "I am built by test function",
   364  				Table:  "CC1",
   365  			},
   366  		},
   367  	}))
   368  	c.Assert(err, check.IsNil)
   369  	err = p.SendToFirstNode(PolymorphicEventMessage(&model.PolymorphicEvent{
   370  		Row: &model.RowChangedEvent{
   371  			Table: &model.TableName{
   372  				Schema: "I am built by test function",
   373  				Table:  "DD2",
   374  			},
   375  		},
   376  	}))
   377  	c.Assert(err, check.IsNil)
   378  	p.AppendNode(ctx, "echo node", echoNode{})
   379  	// wait the echo node sent all messages to next node
   380  	time.Sleep(1 * time.Second)
   381  
   382  	p.AppendNode(ctx, "check node", &checkNode{
   383  		c: c,
   384  		expected: []Message{
   385  			PolymorphicEventMessage(&model.PolymorphicEvent{
   386  				Row: &model.RowChangedEvent{
   387  					Table: &model.TableName{
   388  						Schema: "init function is called in echo node",
   389  					},
   390  				},
   391  			}),
   392  			PolymorphicEventMessage(&model.PolymorphicEvent{
   393  				Row: &model.RowChangedEvent{
   394  					Table: &model.TableName{
   395  						Schema: "I am built by test function",
   396  						Table:  "CC1",
   397  					},
   398  				},
   399  			}),
   400  			PolymorphicEventMessage(&model.PolymorphicEvent{
   401  				Row: &model.RowChangedEvent{
   402  					Table: &model.TableName{
   403  						Schema: "ECHO: I am built by test function",
   404  						Table:  "ECHO: CC1",
   405  					},
   406  				},
   407  			}),
   408  			PolymorphicEventMessage(&model.PolymorphicEvent{
   409  				Row: &model.RowChangedEvent{
   410  					Table: &model.TableName{
   411  						Schema: "I am built by test function",
   412  						Table:  "DD2",
   413  					},
   414  				},
   415  			}),
   416  			PolymorphicEventMessage(&model.PolymorphicEvent{
   417  				Row: &model.RowChangedEvent{
   418  					Table: &model.TableName{
   419  						Schema: "ECHO: I am built by test function",
   420  						Table:  "ECHO: DD2",
   421  					},
   422  				},
   423  			}),
   424  			PolymorphicEventMessage(&model.PolymorphicEvent{
   425  				Row: &model.RowChangedEvent{
   426  					Table: &model.TableName{
   427  						Schema: "destory function is called in echo node",
   428  					},
   429  				},
   430  			}),
   431  		},
   432  	})
   433  
   434  	cancel()
   435  	p.Wait()
   436  }
   437  
   438  type panicNode struct {
   439  }
   440  
   441  func (e panicNode) Init(ctx NodeContext) error {
   442  	panic("panic in panicNode")
   443  }
   444  
   445  func (e panicNode) Receive(ctx NodeContext) error {
   446  	return nil
   447  }
   448  
   449  func (e panicNode) Destroy(ctx NodeContext) error {
   450  	return nil
   451  }
   452  
   453  func (s *pipelineSuite) TestPipelinePanic(c *check.C) {
   454  	defer testleak.AfterTest(c)()
   455  	// why skip this test?
   456  	// this test is panic expected, but the panic is not happened at the main goroutine.
   457  	// so we can't recover the panic through the defer code block at the main goroutine.
   458  	// the c.ExpectFailure() is not warking cause the same reason.
   459  	c.Skip("this test should be panic")
   460  	defer func() {
   461  		panicInfo := recover().(string)
   462  		c.Assert(panicInfo, check.Equals, "panic in panicNode")
   463  	}()
   464  	ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{})
   465  	ctx, cancel := context.WithCancel(ctx)
   466  	defer cancel()
   467  	ctx = context.WithErrorHandler(ctx, func(err error) error {
   468  		c.Fatal(err)
   469  		return err
   470  	})
   471  	ctx = context.WithErrorHandler(ctx, func(err error) error {
   472  		return nil
   473  	})
   474  	runnersSize, outputChannelSize := 1, 64
   475  	p := NewPipeline(ctx, -1, runnersSize, outputChannelSize)
   476  	p.AppendNode(ctx, "panic", panicNode{})
   477  	p.Wait()
   478  }
   479  
   480  type forward struct {
   481  	ch chan Message
   482  }
   483  
   484  func (n *forward) Init(ctx NodeContext) error {
   485  	return nil
   486  }
   487  
   488  func (n *forward) Receive(ctx NodeContext) error {
   489  	m := ctx.Message()
   490  	if n.ch != nil {
   491  		n.ch <- m
   492  	} else {
   493  		ctx.SendToNextNode(m)
   494  	}
   495  	return nil
   496  }
   497  
   498  func (n *forward) Destroy(ctx NodeContext) error {
   499  	return nil
   500  }
   501  
   502  // Run the benchmark
   503  // go test -benchmem -run='^$' -bench '^(BenchmarkPipeline)$' github.com/pingcap/ticdc/pkg/pipeline
   504  func BenchmarkPipeline(b *testing.B) {
   505  	ctx := context.NewContext(stdCtx.Background(), &context.GlobalVars{})
   506  	runnersSize, outputChannelSize := 2, 64
   507  
   508  	b.Run("BenchmarkPipeline", func(b *testing.B) {
   509  		for i := 1; i <= 8; i++ {
   510  			ctx, cancel := context.WithCancel(ctx)
   511  			ctx = context.WithErrorHandler(ctx, func(err error) error {
   512  				b.Fatal(err)
   513  				return err
   514  			})
   515  
   516  			ch := make(chan Message)
   517  			p := NewPipeline(ctx, -1, runnersSize, outputChannelSize)
   518  			for j := 0; j < i; j++ {
   519  				if (j + 1) == i {
   520  					// The last node
   521  					p.AppendNode(ctx, "forward node", &forward{ch: ch})
   522  				} else {
   523  					p.AppendNode(ctx, "forward node", &forward{})
   524  				}
   525  			}
   526  
   527  			b.ResetTimer()
   528  			b.Run(fmt.Sprintf("%d node(s)", i), func(b *testing.B) {
   529  				for i := 0; i < b.N; i++ {
   530  					err := p.SendToFirstNode(BarrierMessage(1))
   531  					if err != nil {
   532  						b.Fatal(err)
   533  					}
   534  					<-ch
   535  				}
   536  			})
   537  			b.StopTimer()
   538  			cancel()
   539  			p.Wait()
   540  		}
   541  	})
   542  }