github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/tests/init_test.go (about) 1 // Copyright 2015 The Spectrum Authors 2 // This file is part of the Spectrum library. 3 // 4 // The Spectrum 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 Spectrum 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 Spectrum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package tests 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "reflect" 27 "regexp" 28 "sort" 29 "strings" 30 "testing" 31 32 "github.com/SmartMeshFoundation/Spectrum/params" 33 ) 34 35 var ( 36 baseDir = filepath.Join(".", "testdata") 37 blockTestDir = filepath.Join(baseDir, "BlockchainTests") 38 stateTestDir = filepath.Join(baseDir, "GeneralStateTests") 39 transactionTestDir = filepath.Join(baseDir, "TransactionTests") 40 vmTestDir = filepath.Join(baseDir, "VMTests") 41 rlpTestDir = filepath.Join(baseDir, "RLPTests") 42 difficultyTestDir = filepath.Join(baseDir, "BasicTests") 43 ) 44 45 func readJson(reader io.Reader, value interface{}) error { 46 data, err := ioutil.ReadAll(reader) 47 if err != nil { 48 return fmt.Errorf("error reading JSON file: %v", err) 49 } 50 if err = json.Unmarshal(data, &value); err != nil { 51 if syntaxerr, ok := err.(*json.SyntaxError); ok { 52 line := findLine(data, syntaxerr.Offset) 53 return fmt.Errorf("JSON syntax error at line %v: %v", line, err) 54 } 55 return err 56 } 57 return nil 58 } 59 60 func readJsonFile(fn string, value interface{}) error { 61 file, err := os.Open(fn) 62 if err != nil { 63 return err 64 } 65 defer file.Close() 66 67 err = readJson(file, value) 68 if err != nil { 69 return fmt.Errorf("%s in file %s", err.Error(), fn) 70 } 71 return nil 72 } 73 74 // findLine returns the line number for the given offset into data. 75 func findLine(data []byte, offset int64) (line int) { 76 line = 1 77 for i, r := range string(data) { 78 if int64(i) >= offset { 79 return 80 } 81 if r == '\n' { 82 line++ 83 } 84 } 85 return 86 } 87 88 // testMatcher controls skipping and chain config assignment to tests. 89 type testMatcher struct { 90 configpat []testConfig 91 failpat []testFailure 92 skiploadpat []*regexp.Regexp 93 skipshortpat []*regexp.Regexp 94 } 95 96 type testConfig struct { 97 p *regexp.Regexp 98 config params.ChainConfig 99 } 100 101 type testFailure struct { 102 p *regexp.Regexp 103 reason string 104 } 105 106 // skipShortMode skips tests matching when the -short flag is used. 107 func (tm *testMatcher) skipShortMode(pattern string) { 108 tm.skipshortpat = append(tm.skipshortpat, regexp.MustCompile(pattern)) 109 } 110 111 // skipLoad skips JSON loading of tests matching the pattern. 112 func (tm *testMatcher) skipLoad(pattern string) { 113 tm.skiploadpat = append(tm.skiploadpat, regexp.MustCompile(pattern)) 114 } 115 116 // fails adds an expected failure for tests matching the pattern. 117 func (tm *testMatcher) fails(pattern string, reason string) { 118 if reason == "" { 119 panic("empty fail reason") 120 } 121 tm.failpat = append(tm.failpat, testFailure{regexp.MustCompile(pattern), reason}) 122 } 123 124 // config defines chain config for tests matching the pattern. 125 func (tm *testMatcher) config(pattern string, cfg params.ChainConfig) { 126 tm.configpat = append(tm.configpat, testConfig{regexp.MustCompile(pattern), cfg}) 127 } 128 129 // findSkip matches name against test skip patterns. 130 func (tm *testMatcher) findSkip(name string) (reason string, skipload bool) { 131 if testing.Short() { 132 for _, re := range tm.skipshortpat { 133 if re.MatchString(name) { 134 return "skipped in -short mode", false 135 } 136 } 137 } 138 for _, re := range tm.skiploadpat { 139 if re.MatchString(name) { 140 return "skipped by skipLoad", true 141 } 142 } 143 return "", false 144 } 145 146 // findConfig returns the chain config matching defined patterns. 147 func (tm *testMatcher) findConfig(name string) *params.ChainConfig { 148 // TODO(fjl): name can be derived from testing.T when min Go version is 1.8 149 for _, m := range tm.configpat { 150 if m.p.MatchString(name) { 151 return &m.config 152 } 153 } 154 return new(params.ChainConfig) 155 } 156 157 // checkFailure checks whether a failure is expected. 158 func (tm *testMatcher) checkFailure(t *testing.T, name string, err error) error { 159 // TODO(fjl): name can be derived from t when min Go version is 1.8 160 failReason := "" 161 for _, m := range tm.failpat { 162 if m.p.MatchString(name) { 163 failReason = m.reason 164 break 165 } 166 } 167 if failReason != "" { 168 t.Logf("expected failure: %s", failReason) 169 if err != nil { 170 t.Logf("error: %v", err) 171 return nil 172 } else { 173 return fmt.Errorf("test succeeded unexpectedly") 174 } 175 } 176 return err 177 } 178 179 // walk invokes its runTest argument for all subtests in the given directory. 180 // 181 // runTest should be a function of type func(t *testing.T, name string, x <TestType>), 182 // where TestType is the type of the test contained in test files. 183 func (tm *testMatcher) walk(t *testing.T, dir string, runTest interface{}) { 184 // Walk the directory. 185 dirinfo, err := os.Stat(dir) 186 if os.IsNotExist(err) || !dirinfo.IsDir() { 187 fmt.Fprintf(os.Stderr, "can't find test files in %s, did you clone the tests submodule?\n", dir) 188 t.Skip("missing test files") 189 } 190 err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 191 name := filepath.ToSlash(strings.TrimPrefix(path, dir+string(filepath.Separator))) 192 if info.IsDir() { 193 if _, skipload := tm.findSkip(name + "/"); skipload { 194 return filepath.SkipDir 195 } 196 return nil 197 } 198 if filepath.Ext(path) == ".json" { 199 t.Run(name, func(t *testing.T) { tm.runTestFile(t, path, name, runTest) }) 200 } 201 return nil 202 }) 203 if err != nil { 204 t.Fatal(err) 205 } 206 } 207 208 func (tm *testMatcher) runTestFile(t *testing.T, path, name string, runTest interface{}) { 209 if r, _ := tm.findSkip(name); r != "" { 210 t.Skip(r) 211 } 212 t.Parallel() 213 214 // Load the file as map[string]<testType>. 215 m := makeMapFromTestFunc(runTest) 216 if err := readJsonFile(path, m.Addr().Interface()); err != nil { 217 t.Fatal(err) 218 } 219 220 // Run all tests from the map. Don't wrap in a subtest if there is only one test in the file. 221 keys := sortedMapKeys(m) 222 if len(keys) == 1 { 223 runTestFunc(runTest, t, name, m, keys[0]) 224 } else { 225 for _, key := range keys { 226 name := name + "/" + key 227 t.Run(key, func(t *testing.T) { 228 if r, _ := tm.findSkip(name); r != "" { 229 t.Skip(r) 230 } 231 runTestFunc(runTest, t, name, m, key) 232 }) 233 } 234 } 235 } 236 237 func makeMapFromTestFunc(f interface{}) reflect.Value { 238 stringT := reflect.TypeOf("") 239 testingT := reflect.TypeOf((*testing.T)(nil)) 240 ftyp := reflect.TypeOf(f) 241 if ftyp.Kind() != reflect.Func || ftyp.NumIn() != 3 || ftyp.NumOut() != 0 || ftyp.In(0) != testingT || ftyp.In(1) != stringT { 242 panic(fmt.Sprintf("bad test function type: want func(*testing.T, string, <TestType>), have %s", ftyp)) 243 } 244 testType := ftyp.In(2) 245 mp := reflect.New(reflect.MapOf(stringT, testType)) 246 return mp.Elem() 247 } 248 249 func sortedMapKeys(m reflect.Value) []string { 250 keys := make([]string, m.Len()) 251 for i, k := range m.MapKeys() { 252 keys[i] = k.String() 253 } 254 sort.Strings(keys) 255 return keys 256 } 257 258 func runTestFunc(runTest interface{}, t *testing.T, name string, m reflect.Value, key string) { 259 reflect.ValueOf(runTest).Call([]reflect.Value{ 260 reflect.ValueOf(t), 261 reflect.ValueOf(name), 262 m.MapIndex(reflect.ValueOf(key)), 263 }) 264 }