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 }