github.com/ethereum/go-ethereum@v1.14.3/tests/init_test.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package tests 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 "io" 24 "os" 25 "path/filepath" 26 "reflect" 27 "regexp" 28 "runtime" 29 "sort" 30 "strings" 31 "testing" 32 33 "github.com/ethereum/go-ethereum/params" 34 ) 35 36 var ( 37 baseDir = filepath.Join(".", "testdata") 38 blockTestDir = filepath.Join(baseDir, "BlockchainTests") 39 stateTestDir = filepath.Join(baseDir, "GeneralStateTests") 40 legacyStateTestDir = filepath.Join(baseDir, "LegacyTests", "Constantinople", "GeneralStateTests") 41 transactionTestDir = filepath.Join(baseDir, "TransactionTests") 42 rlpTestDir = filepath.Join(baseDir, "RLPTests") 43 difficultyTestDir = filepath.Join(baseDir, "BasicTests") 44 executionSpecBlockchainTestDir = filepath.Join(".", "spec-tests", "fixtures", "blockchain_tests") 45 executionSpecStateTestDir = filepath.Join(".", "spec-tests", "fixtures", "state_tests") 46 benchmarksDir = filepath.Join(".", "evm-benchmarks", "benchmarks") 47 ) 48 49 func readJSON(reader io.Reader, value interface{}) error { 50 data, err := io.ReadAll(reader) 51 if err != nil { 52 return fmt.Errorf("error reading JSON file: %v", err) 53 } 54 if err = json.Unmarshal(data, &value); err != nil { 55 if syntaxerr, ok := err.(*json.SyntaxError); ok { 56 line := findLine(data, syntaxerr.Offset) 57 return fmt.Errorf("JSON syntax error at line %v: %v", line, err) 58 } 59 return err 60 } 61 return nil 62 } 63 64 func readJSONFile(fn string, value interface{}) error { 65 file, err := os.Open(fn) 66 if err != nil { 67 return err 68 } 69 defer file.Close() 70 71 err = readJSON(file, value) 72 if err != nil { 73 return fmt.Errorf("%s in file %s", err.Error(), fn) 74 } 75 return nil 76 } 77 78 // findLine returns the line number for the given offset into data. 79 func findLine(data []byte, offset int64) (line int) { 80 line = 1 81 for i, r := range string(data) { 82 if int64(i) >= offset { 83 return 84 } 85 if r == '\n' { 86 line++ 87 } 88 } 89 return 90 } 91 92 // testMatcher controls skipping and chain config assignment to tests. 93 type testMatcher struct { 94 configpat []testConfig 95 failpat []testFailure 96 skiploadpat []*regexp.Regexp 97 slowpat []*regexp.Regexp 98 runonlylistpat *regexp.Regexp 99 } 100 101 type testConfig struct { 102 p *regexp.Regexp 103 config params.ChainConfig 104 } 105 106 type testFailure struct { 107 p *regexp.Regexp 108 reason string 109 } 110 111 // slow adds expected slow tests matching the pattern. 112 func (tm *testMatcher) slow(pattern string) { 113 tm.slowpat = append(tm.slowpat, regexp.MustCompile(pattern)) 114 } 115 116 // skipLoad skips JSON loading of tests matching the pattern. 117 func (tm *testMatcher) skipLoad(pattern string) { 118 tm.skiploadpat = append(tm.skiploadpat, regexp.MustCompile(pattern)) 119 } 120 121 // fails adds an expected failure for tests matching the pattern. 122 // 123 //nolint:unused 124 func (tm *testMatcher) fails(pattern string, reason string) { 125 if reason == "" { 126 panic("empty fail reason") 127 } 128 tm.failpat = append(tm.failpat, testFailure{regexp.MustCompile(pattern), reason}) 129 } 130 131 func (tm *testMatcher) runonly(pattern string) { 132 tm.runonlylistpat = regexp.MustCompile(pattern) 133 } 134 135 // config defines chain config for tests matching the pattern. 136 func (tm *testMatcher) config(pattern string, cfg params.ChainConfig) { 137 tm.configpat = append(tm.configpat, testConfig{regexp.MustCompile(pattern), cfg}) 138 } 139 140 // findSkip matches name against test skip patterns. 141 func (tm *testMatcher) findSkip(name string) (reason string, skipload bool) { 142 isWin32 := runtime.GOARCH == "386" && runtime.GOOS == "windows" 143 for _, re := range tm.slowpat { 144 if re.MatchString(name) { 145 if testing.Short() { 146 return "skipped in -short mode", false 147 } 148 if isWin32 { 149 return "skipped on 32bit windows", false 150 } 151 } 152 } 153 for _, re := range tm.skiploadpat { 154 if re.MatchString(name) { 155 return "skipped by skipLoad", true 156 } 157 } 158 return "", false 159 } 160 161 // findConfig returns the chain config matching defined patterns. 162 func (tm *testMatcher) findConfig(t *testing.T) *params.ChainConfig { 163 for _, m := range tm.configpat { 164 if m.p.MatchString(t.Name()) { 165 return &m.config 166 } 167 } 168 return new(params.ChainConfig) 169 } 170 171 // checkFailure checks whether a failure is expected. 172 func (tm *testMatcher) checkFailure(t *testing.T, err error) error { 173 failReason := "" 174 for _, m := range tm.failpat { 175 if m.p.MatchString(t.Name()) { 176 failReason = m.reason 177 break 178 } 179 } 180 if failReason != "" { 181 t.Logf("expected failure: %s", failReason) 182 if err != nil { 183 t.Logf("error: %v", err) 184 return nil 185 } 186 return errors.New("test succeeded unexpectedly") 187 } 188 return err 189 } 190 191 // walk invokes its runTest argument for all subtests in the given directory. 192 // 193 // runTest should be a function of type func(t *testing.T, name string, x <TestType>), 194 // where TestType is the type of the test contained in test files. 195 func (tm *testMatcher) walk(t *testing.T, dir string, runTest interface{}) { 196 // Walk the directory. 197 dirinfo, err := os.Stat(dir) 198 if os.IsNotExist(err) || !dirinfo.IsDir() { 199 fmt.Fprintf(os.Stderr, "can't find test files in %s, did you clone the tests submodule?\n", dir) 200 t.Skip("missing test files") 201 } 202 err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 203 name := filepath.ToSlash(strings.TrimPrefix(path, dir+string(filepath.Separator))) 204 if info.IsDir() { 205 if _, skipload := tm.findSkip(name + "/"); skipload { 206 return filepath.SkipDir 207 } 208 return nil 209 } 210 if filepath.Ext(path) == ".json" { 211 t.Run(name, func(t *testing.T) { tm.runTestFile(t, path, name, runTest) }) 212 } 213 return nil 214 }) 215 if err != nil { 216 t.Fatal(err) 217 } 218 } 219 220 func (tm *testMatcher) runTestFile(t *testing.T, path, name string, runTest interface{}) { 221 if r, _ := tm.findSkip(name); r != "" { 222 t.Skip(r) 223 } 224 if tm.runonlylistpat != nil { 225 if !tm.runonlylistpat.MatchString(name) { 226 t.Skip("Skipped by runonly") 227 } 228 } 229 t.Parallel() 230 231 // Load the file as map[string]<testType>. 232 m := makeMapFromTestFunc(runTest) 233 if err := readJSONFile(path, m.Addr().Interface()); err != nil { 234 t.Fatal(err) 235 } 236 237 // Run all tests from the map. Don't wrap in a subtest if there is only one test in the file. 238 keys := sortedMapKeys(m) 239 if len(keys) == 1 { 240 runTestFunc(runTest, t, name, m, keys[0]) 241 } else { 242 for _, key := range keys { 243 name := name + "/" + key 244 t.Run(key, func(t *testing.T) { 245 if r, _ := tm.findSkip(name); r != "" { 246 t.Skip(r) 247 } 248 runTestFunc(runTest, t, name, m, key) 249 }) 250 } 251 } 252 } 253 254 func makeMapFromTestFunc(f interface{}) reflect.Value { 255 stringT := reflect.TypeOf("") 256 testingT := reflect.TypeOf((*testing.T)(nil)) 257 ftyp := reflect.TypeOf(f) 258 if ftyp.Kind() != reflect.Func || ftyp.NumIn() != 3 || ftyp.NumOut() != 0 || ftyp.In(0) != testingT || ftyp.In(1) != stringT { 259 panic(fmt.Sprintf("bad test function type: want func(*testing.T, string, <TestType>), have %s", ftyp)) 260 } 261 testType := ftyp.In(2) 262 mp := reflect.New(reflect.MapOf(stringT, testType)) 263 return mp.Elem() 264 } 265 266 func sortedMapKeys(m reflect.Value) []string { 267 keys := make([]string, m.Len()) 268 for i, k := range m.MapKeys() { 269 keys[i] = k.String() 270 } 271 sort.Strings(keys) 272 return keys 273 } 274 275 func runTestFunc(runTest interface{}, t *testing.T, name string, m reflect.Value, key string) { 276 reflect.ValueOf(runTest).Call([]reflect.Value{ 277 reflect.ValueOf(t), 278 reflect.ValueOf(name), 279 m.MapIndex(reflect.ValueOf(key)), 280 }) 281 } 282 283 func TestMatcherRunonlylist(t *testing.T) { 284 t.Parallel() 285 tm := new(testMatcher) 286 tm.runonly("invalid*") 287 tm.walk(t, rlpTestDir, func(t *testing.T, name string, test *RLPTest) { 288 if name[:len("invalidRLPTest.json")] != "invalidRLPTest.json" { 289 t.Fatalf("invalid test found: %s != invalidRLPTest.json", name) 290 } 291 }) 292 }