github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/_utils/terror_gen/checker_template.go (about)

     1  // Copyright 2019 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 main
    15  
    16  import (
    17  	"bufio"
    18  	"bytes"
    19  	"fmt"
    20  	"os"
    21  	"regexp"
    22  	"sort"
    23  	"strconv"
    24  	"strings"
    25  
    26  	"github.com/BurntSushi/toml"
    27  	"github.com/pingcap/tiflow/dm/pkg/terror"
    28  )
    29  
    30  const (
    31  	developErrorFile = "errors_develop.txt"
    32  	releaseErrorFile = "errors_release.txt"
    33  	checkerFile      = "{{.CheckerFile}}"
    34  	tomlErrorFile    = "../../errors.toml"
    35  )
    36  
    37  var dumpErrorRe = regexp.MustCompile("^([a-zA-Z].*),\\[code=([0-9]+).*$")
    38  
    39  type tomlErrorBody = struct {
    40  	Message     string   `toml:"message"`
    41  	Description string   `toml:"description"`
    42  	Workaround  string   `toml:"workaround"`
    43  	Tags        []string `toml:"tags"`
    44  }
    45  
    46  type tomlErrorItem = struct {
    47  	key  string
    48  	code terror.ErrCode
    49  	body string
    50  }
    51  
    52  // used to generate `errors.toml` in the repo's root.
    53  var tomlErrors []tomlErrorItem
    54  
    55  var errors = []struct {
    56  	name string
    57  	err  *terror.Error
    58  }{
    59  	// sample:
    60  	// {"ErrWorkerExecDDLTimeout", terror.ErrWorkerExecDDLTimeout},
    61  	// {{.ErrList}}
    62  }
    63  
    64  func genErrors() {
    65  	f, err := os.Create(developErrorFile)
    66  	if err != nil {
    67  		panic(err)
    68  	}
    69  	defer f.Close()
    70  	w := bufio.NewWriter(f)
    71  	for _, item := range errors {
    72  		s := strings.SplitN(item.err.Error(), " ", 2)
    73  		if len(s) > 1 {
    74  			// errName,[code:class:scope:level], "Message, RawCause, Workaround"
    75  			w.WriteString(fmt.Sprintf("%s,%s \"%s\"\n", item.name, s[0], strings.ReplaceAll(s[1], "\n", "\\n")))
    76  		} else {
    77  			// errName,[code:class:scope:level]
    78  			w.WriteString(fmt.Sprintf("%s,%s\n", item.name, s[0]))
    79  		}
    80  
    81  		body := tomlErrorBody{
    82  			Message:     item.err.Message(),
    83  			Description: "", // empty now
    84  			Workaround:  item.err.Workaround(),
    85  			Tags:        []string{item.err.Scope().String(), item.err.Level().String()},
    86  		}
    87  		var buf bytes.Buffer
    88  		enc := toml.NewEncoder(&buf)
    89  		err := enc.Encode(body)
    90  		if err != nil {
    91  			panic(err)
    92  		}
    93  
    94  		tomlErrors = append(tomlErrors, tomlErrorItem{
    95  			key:  fmt.Sprintf("error.DM-%s-%d", item.err.Class(), item.err.Code()),
    96  			code: item.err.Code(),
    97  			body: buf.String(),
    98  		})
    99  	}
   100  	w.Flush()
   101  
   102  	// sort according to the code.
   103  	sort.Slice(tomlErrors, func(i, j int) bool {
   104  		return tomlErrors[i].code < tomlErrors[j].code
   105  	})
   106  }
   107  
   108  func readErrorFile(filename string) map[string]int64 {
   109  	f, err := os.Open(filename)
   110  	if err != nil {
   111  		panic(err)
   112  	}
   113  	defer f.Close()
   114  
   115  	result := make(map[string]int64)
   116  	scanner := bufio.NewScanner(f)
   117  	for scanner.Scan() {
   118  		s := scanner.Text()
   119  		match := dumpErrorRe.FindStringSubmatch(s)
   120  		if len(match) != 3 {
   121  			panic(fmt.Sprintf("invalid error: %s", s))
   122  		}
   123  		code, err := strconv.ParseInt(match[2], 10, 64)
   124  		if err != nil {
   125  			panic(err)
   126  		}
   127  		result[match[1]] = code
   128  	}
   129  	return result
   130  }
   131  
   132  func compareErrors() bool {
   133  	changedErrorCode := make(map[string][]int64)
   134  	duplicateErrorCode := make(map[int64][]string)
   135  	release := readErrorFile(releaseErrorFile)
   136  	dev := readErrorFile(developErrorFile)
   137  
   138  	for name, code := range dev {
   139  		if releaseCode, ok := release[name]; ok && code != releaseCode {
   140  			changedErrorCode[name] = []int64{releaseCode, code}
   141  		}
   142  		if _, ok := duplicateErrorCode[code]; ok {
   143  			duplicateErrorCode[code] = append(duplicateErrorCode[code], name)
   144  		} else {
   145  			duplicateErrorCode[code] = []string{name}
   146  		}
   147  	}
   148  	for code, names := range duplicateErrorCode {
   149  		if len(names) == 1 {
   150  			delete(duplicateErrorCode, code)
   151  		}
   152  	}
   153  
   154  	// check each non-new-added error in develop version has the same error code with the release version
   155  	if len(changedErrorCode) > 0 {
   156  		os.Stderr.WriteString("\n************ error code not same with the release version ************\n")
   157  	}
   158  	for name, codes := range changedErrorCode {
   159  		fmt.Fprintf(os.Stderr, "name: %s release code: %d current code: %d\n", name, codes[0], codes[1])
   160  	}
   161  
   162  	// check each error in develop version has a unique error code
   163  	if len(duplicateErrorCode) > 0 {
   164  		os.Stderr.WriteString("\n************ error code not unique ************\n")
   165  	}
   166  	for code, names := range duplicateErrorCode {
   167  		fmt.Fprintf(os.Stderr, "code: %d names: %v\n", code, names)
   168  	}
   169  
   170  	return len(changedErrorCode) == 0 && len(duplicateErrorCode) == 0
   171  }
   172  
   173  func cleanup(success bool) {
   174  	if success {
   175  		if err := os.Rename(developErrorFile, releaseErrorFile); err != nil {
   176  			panic(err)
   177  		}
   178  
   179  		tef, err := os.Create(tomlErrorFile)
   180  		if err != nil {
   181  			panic(err)
   182  		}
   183  		defer tef.Close()
   184  		for _, item := range tomlErrors {
   185  			// generate to TOML file, poor man's method for ordered codes.
   186  			_, err = tef.WriteString(fmt.Sprintf("[%s]\n", item.key))
   187  			if err != nil {
   188  				panic(err)
   189  			}
   190  			_, err = tef.WriteString(item.body)
   191  			if err != nil {
   192  				panic(err)
   193  			}
   194  			_, err = tef.WriteString("\n")
   195  			if err != nil {
   196  				panic(err)
   197  			}
   198  		}
   199  
   200  		fmt.Println("check pass")
   201  	} else {
   202  		if err := os.Remove(developErrorFile); err != nil {
   203  			panic(err)
   204  		}
   205  		os.Exit(1)
   206  	}
   207  }
   208  
   209  func main() {
   210  	defer func() {
   211  		os.Remove(checkerFile)
   212  	}()
   213  	genErrors()
   214  	cleanup(compareErrors())
   215  }