├── r2r.1 ├── Makefile ├── README.md └── r2r.v /r2r.1: -------------------------------------------------------------------------------- 1 | .Dd Dec 29, 2019 2 | .Dt R2R 1 3 | .Sh NAME 4 | .Nm r2r 5 | .Nd radare regression testsuite 6 | .Sh SYNOPSIS 7 | .Nm r2r 8 | .Op Fl n 9 | .Op Fl h 10 | .Op Fl v 11 | .Op Fl q 12 | .Op Fl i 13 | .Op Fl d Ar dbpath 14 | .Op Fl r Ar r2path 15 | .Op Fl j Ar jobs 16 | .Op Fl t Ar timeout 17 | .Op [keyword] 18 | .Sh DESCRIPTION 19 | Run all the radare2-regressions tests matching a specific word in the name. 20 | .Pp 21 | TODO: this manpage is work-in-progress 22 | .Pp 23 | You need radare2 to be available in $PATH. 24 | .Sh OPTIONS 25 | .Bl -tag -width Fl 26 | .It Fl n 27 | Do not run any test, just parse them 28 | .El 29 | .Sh USAGE 30 | .Pp 31 | Use the -n flag to dont run any test. Just load them. 32 | .Pp 33 | $ r2r -n 34 | [r2r] Loading tests... 35 | .Pp 36 | .Sh SEE ALSO 37 | .Pp 38 | .Xr radare2(1) , 39 | .Sh AUTHORS 40 | .Pp 41 | Written by pancake . 42 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | -include ../../config-user.mk 2 | 3 | VCOMMIT=8da12eb8a77ac8afb4dba78439d93f5af4e7a623 4 | BINDIR=$(shell r2 -H R2_PREFIX)/bin 5 | CWD=$(shell pwd) 6 | V=v/v 7 | 8 | all: $(V) 9 | $(V) -prod r2r.v 10 | #$(V) -g r2r.v 11 | 12 | mrproper: clean 13 | rm -rf v 14 | 15 | $(V): v 16 | cd v && git pull 17 | cd v && git reset --hard $(VCOMMIT) 18 | $(MAKE) -C v 19 | $(MAKE) ~/.vmodules/radare/r2 20 | 21 | ~/.vmodules/radare/r2: 22 | $(V) install radare.r2 || \ 23 | git clone --depth=1 https://github.com/radare/v-r2 ~/.vmodules/radare/r2 24 | 25 | clean: 26 | rm -f r2r 27 | rm -rf ~/.vmodules/radare/r2 28 | rm -rf v 29 | 30 | fmt: 31 | $(V) fmt -w r2r.v 32 | 33 | v: 34 | git clone https://github.com/vlang/v 35 | cd v && git reset --hard $(VCOMMIT) 36 | 37 | install: uninstall 38 | v -prod r2r.v 39 | rm -f $(BINDIR)/r2r 40 | ln -fs $(CWD)/r2r $(shell r2 -H R2_PREFIX)/bin 41 | mkdir -p "${DESTDIR}${MANDIR}/man1" 42 | cp -rf r2r.1 "${DESTDIR}${MANDIR}/man1" 43 | 44 | uninstall: 45 | rm -f $(BINDIR)/r2r 46 | rm -f "${DESTDIR}${MANDIR}/man1/r2r.1" 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | r2r rewrite in V 2 | ================ 3 | 4 | This is the full rewrite of the r2 regressions testsuite in the V programming language. 5 | 6 | Reasons behind using V are: 7 | 8 | * Go-like syntax: Easy to maintain and for newcomers 9 | * Portability: Compiles to C with no dependencies 10 | * Speed: Just use native apis instead of spawn all the things 11 | 12 | The current testsuite is written in NodeJS and have some issues: 13 | 14 | * Hard to architect structured js, ts helps, but its just layers on layers 15 | * Some lost promises happen in travis which are hard to debug 16 | * Simplify the testsuite to cleanup broken or badly written tests 17 | * Have a single entrypoint to run ALL the tests (unit, fuzz, asm, ..) 18 | * Latest versions of NodeJS don't run on net/open/free-BSD 19 | 20 | Things to be done: 21 | 22 | * Implement the interactive mode to fix failing tests 23 | * Clone+build V if not in the $PATH 24 | 25 | Stuff to improve: 26 | 27 | * Proper error handling 28 | * Timeouts without using rarun2 29 | * Improve r2pipe.v performance 30 | 31 | --pancake 32 | -------------------------------------------------------------------------------- /r2r.v: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | import ( 4 | os 5 | sync 6 | time 7 | term 8 | json 9 | flag 10 | radare.r2 11 | ) 12 | 13 | /* 14 | fn C.r_cons_begin() bool 15 | fn C.r_cons_is_breaked() bool 16 | fn C.r_cons_break_push(a, b voidptr) bool 17 | fn C.r_cons_break_pop() bool 18 | */ 19 | 20 | const ( 21 | default_jobs = 2 22 | default_targets = 'arch json asm fuzz cmd unit' 23 | default_timeout = 10 24 | default_asm_bits = 32 25 | default_radare2 = 'radare2' 26 | default_dbpath = 'new/db' // XXX use execpath as relative reference to it 27 | cmd_test_paths = ['cmd', 'extras', 'tools'] // all dirs under db/ that contain regular command tests 28 | r2r_version = '0.2' 29 | ) 30 | 31 | // /////////////// 32 | struct R2r { 33 | mut: 34 | r2 &r2.R2 35 | cmd_tests []R2rCmdTest 36 | asm_tests []R2rAsmTest 37 | targets []string 38 | jobs int 39 | timeout int 40 | wg &sync.WaitGroup 41 | success int 42 | failed int 43 | fixed int 44 | broken int 45 | db_path string 46 | r2_path string 47 | show_quiet bool 48 | interactive bool 49 | r2r_home string 50 | filter_by_files []string 51 | } 52 | 53 | fn autodetect_dbpath() string { 54 | return os.join_path(r2r_home(),default_dbpath) 55 | } 56 | 57 | fn r2r_home() string { 58 | home := os.base_dir(os.real_path(os.executable())) 59 | return os.join_path(home,'..') 60 | } 61 | 62 | fn control_c() { 63 | println('\nInterrupted.') 64 | exit(1) 65 | } 66 | 67 | pub fn main() { 68 | mut r2r := R2r{r2:0, wg:0} 69 | mut fp := flag.new_flag_parser(os.args) 70 | fp.application(os.file_name(os.executable())) 71 | // fp.version(r2r_version) 72 | show_norun := fp.bool('norun', `n`, false, 'Dont run the tests') 73 | show_help := fp.bool('help', `h`, false, 'Show this help screen') 74 | r2r.jobs = fp.int('jobs', `j`, default_jobs, 'Spawn N jobs in parallel to run tests ($default_jobs).' + 75 | ' Set to 0 for 1 job per test.') 76 | r2r.timeout = fp.int('timeout', `t`, default_timeout, 'How much time to wait to consider a fail ($default_timeout)') 77 | show_version := fp.bool('version', `v`, false, 'Show version information') 78 | r2r.r2r_home = r2r_home() 79 | r2r.show_quiet = fp.bool('quiet', `q`, false, 'Silent output of OK tests') 80 | r2r.interactive = fp.bool('interactive', `i`, false, 'Prompt to manually fix failing tests (TODO)') 81 | r2r.db_path = fp.string('dbpath', `d`, autodetect_dbpath(), 'Set database path db/') 82 | r2r.r2_path = fp.string('r2', `r`, default_radare2, 'Set path/name to radare2 executable') 83 | if show_help { 84 | println(fp.usage()) 85 | println('ARGS:') 86 | println(' ${default_targets}') 87 | println(' or path/to/test/file') 88 | println('\nExamples:') 89 | println(' \$ r2r cmd /write run only the cmd tests in the /write file') 90 | println(' \$ time r2r -n benchmark time spent parsing test files') 91 | println(' \$ r2r -j 4 json asm run json and asm tests using 4 jobs in parallel') 92 | return 93 | } 94 | if show_version { 95 | println(r2r_version) 96 | return 97 | } 98 | if r2r.jobs < 0 { 99 | eprintln('Invalid number of thread selected with -j') 100 | exit(1) 101 | } 102 | args := fp.finalize() or { 103 | eprintln('Error: ' + err) 104 | exit(1) 105 | } 106 | r2r.targets = args[1..] 107 | for a in r2r.targets { 108 | a.index('/') or { continue } 109 | r2r.filter_by_files << a 110 | } 111 | 112 | if r2r.interactive { 113 | eprintln('Warning: interactive mode not yet implemented in V. Use the node testsuite for this') 114 | p := os.join_path(r2r.r2r_home,'new') 115 | if !os.is_dir(os.join_path(p,'node_modules')) { 116 | exit(1) 117 | } 118 | a := r2r.targets.join(' ') 119 | _ = os.system('cd $p && npm i') 120 | r := os.system('cd $p && node_modules/.bin/r2r -i $a') 121 | exit(r) 122 | } 123 | if r2r.targets.index('help') != -1 { 124 | eprintln(default_targets) 125 | exit(0) 126 | } 127 | println('[r2r] Loading tests') 128 | r2r.load_tests() 129 | if !show_norun { 130 | r2r.run_tests() 131 | r2r.show_report() 132 | } 133 | rc := r2r.return_code() 134 | exit(rc) 135 | } 136 | 137 | fn (r2r mut R2r) run_tests() { 138 | if r2r.wants('json') { 139 | r2r.run_jsn_tests() 140 | } 141 | if r2r.wants('unit') { 142 | r2r.run_unit_tests() 143 | } 144 | if r2r.wants('asm') { 145 | r2r.run_asm_tests() 146 | } 147 | if r2r.wants('fuzz') { 148 | r2r.run_fuz_tests() 149 | } 150 | if r2r.wants_any_cmd_tests() { 151 | r2r.run_cmd_tests() 152 | } 153 | } 154 | 155 | // make a PR for V to have this in os.mktmpdir() 156 | fn C.mkdtemp(template charptr) byteptr 157 | 158 | 159 | fn mktmpdir(template string) string { 160 | tp := if template == '' { 'temp.XXXXXX' } else { template } 161 | dir := os.join_path(os.temp_dir(),tp) 162 | res := C.mkdtemp(dir.str) 163 | return tos_clone(res) 164 | } 165 | 166 | 167 | fn (r2r R2r)return_code() int { 168 | if r2r.failed == 0 { 169 | return 0 170 | } 171 | return 1 172 | } 173 | 174 | struct R2rCmdTest { 175 | mut: 176 | name string 177 | file string 178 | args string 179 | source string 180 | cmds string 181 | expect string 182 | expect_err string 183 | // mutable 184 | broken bool 185 | failed bool 186 | fixed bool 187 | } 188 | 189 | struct R2rAsmTest { 190 | mut: 191 | arch string 192 | bits int 193 | mode string 194 | inst string 195 | data string 196 | offs u64 197 | bige bool 198 | cpu string 199 | } 200 | 201 | // TODO: not yet used 202 | struct R2JsonTest { 203 | mut: 204 | name string 205 | } 206 | 207 | fn (test R2rCmdTest) parse_slurp(v string) (string,string) { 208 | mut res := '' 209 | mut slurp_token := '' 210 | if v.starts_with("'") || v.starts_with("'") { 211 | eprintln('Warning: Deprecated syntax, use < 0 { 239 | if line == slurp_token { 240 | *slurp_target = slurp_data 241 | slurp_data = '' 242 | slurp_token = '' 243 | } 244 | else { 245 | slurp_data += '${line}\n' 246 | } 247 | continue 248 | } 249 | if line.len == 0 { 250 | continue 251 | } 252 | kv := line.split_nth('=', 2) 253 | if kv.len == 0 { 254 | continue 255 | } 256 | k := kv[0] 257 | v := if kv.len == 2 { kv[1] } else { '' } 258 | match k { 259 | 'CMDS' { 260 | if v.len > 0 { 261 | a,b := test.parse_slurp(v) 262 | test.cmds = a 263 | slurp_token = b 264 | if slurp_token.len > 0 { 265 | slurp_target = &test.cmds 266 | } 267 | } 268 | else { 269 | panic('Warning: Missing arg to CMDS') 270 | } 271 | } 272 | 'EXPECT' { 273 | if v.len > 0 { 274 | a,b := test.parse_slurp(v) 275 | test.expect = a 276 | slurp_token = b 277 | if slurp_token.len > 0 { 278 | slurp_target = &test.expect 279 | } 280 | } 281 | else { 282 | eprintln('Warning: Missing value for EXPECT') 283 | } 284 | } 285 | 'EXPECT_ERR' { 286 | if v.len > 0 { 287 | a,b := test.parse_slurp(v) 288 | test.expect_err = a 289 | slurp_token = b 290 | if slurp_token.len > 0 { 291 | slurp_target = &test.expect_err 292 | } 293 | } 294 | else { 295 | eprintln('Warning: Missing value for EXPECT_ERR') 296 | } 297 | } 298 | 'BROKEN' { 299 | if v.len > 0 { 300 | test.broken = v[0] != `0` 301 | } 302 | else { 303 | eprintln('Warning: Missing value for BROKEN in ${test.source}') 304 | } 305 | } 306 | 'ARGS' { 307 | if v.len > 0 { 308 | test.args = line[5..line.len] 309 | } 310 | else { 311 | eprintln('Warning: Missing value for ARGS in ${test.source}') 312 | } 313 | } 314 | 'FILE' { 315 | test.file = line[5..] 316 | } 317 | 'NAME' { 318 | test.name = line[5..] 319 | // XXX this is slow. we need a hashtable for O(1) 320 | for t in r2r.cmd_tests { 321 | if t.name == test.name { 322 | eprintln('Warning: Duplicated test name "${t.name}" in test.source') 323 | } 324 | } 325 | } 326 | 'RUN' { 327 | if v.len > 0 { 328 | eprintln('Warning: RUN statement doesnt require a value') 329 | } 330 | if test.name.len == 0 { 331 | eprintln('Warning: Invalid test name in ${test.source}') 332 | } 333 | else { 334 | if test.name == '' { 335 | eprintln('No test name to run') 336 | } 337 | else { 338 | if test.file == '' { 339 | test.file = '-' 340 | } 341 | r2r.cmd_tests << test 342 | } 343 | test = R2rCmdTest{} 344 | test.source = testfile 345 | } 346 | } 347 | else {} 348 | } 349 | } 350 | } 351 | 352 | /* 353 | // only useful for r2pipe mode, right now we do plain spawns with pipes 354 | 355 | fn (r2r R2r)run_commands(test R2rCmdTest) string { 356 | res := '' 357 | for cmd in cmds { 358 | if isnil(cmd) { 359 | continue 360 | } 361 | res += r2r.r2.cmd(cmd) 362 | } 363 | return res 364 | } 365 | */ 366 | 367 | fn (r2r mut R2r) test_failed(test R2rCmdTest, a string, b string) string { 368 | if test.broken { 369 | r2r.broken++ 370 | return term.blue('BR') 371 | } 372 | println('# File: ${test.file}') 373 | println(term.ok_message(test.cmds)) 374 | println(term.fail_message(a)) 375 | println('# ----') 376 | println(term.ok_message(b)) 377 | r2r.failed++ 378 | return term.red('XX') 379 | } 380 | 381 | fn (r2r R2r) wants(s string) bool { 382 | // eprintln('want ${s}') 383 | if s.contains('/') { 384 | return true 385 | } 386 | if r2r.targets.len < 1 { 387 | return true 388 | } 389 | return r2r.targets.index(s) != -1 390 | } 391 | 392 | fn (r2r R2r) wants_any_cmd_tests() bool { 393 | if r2r.filter_by_files.len > 0 { 394 | return true 395 | } 396 | if r2r.wants('arch') { 397 | return true 398 | } 399 | for cmd_test_path in cmd_test_paths { 400 | if r2r.wants(cmd_test_path) { 401 | return true 402 | } 403 | } 404 | return false 405 | } 406 | 407 | fn (r2r mut R2r) test_fixed(test R2rCmdTest) string { 408 | r2r.fixed++ 409 | return term.yellow('FX') 410 | } 411 | 412 | fn (r2r mut R2r) run_asm_test_native(test R2rAsmTest, dismode bool) { 413 | r2r.r2.break_begin() 414 | test_expect := if dismode { test.inst.trim_space() } else { test.data.trim_space() } 415 | time_start := time.ticks() 416 | r2r.r2.cmd('e asm.arch=${test.arch}') 417 | r2r.r2.cmd('e asm.bits=${test.bits}') 418 | if test.cpu != '' { 419 | r2r.r2.cmd('e asm.cpu=${test.cpu}') 420 | } 421 | if test.offs != 0 { 422 | r2r.r2.cmd('s ${test.offs}') 423 | } 424 | else { 425 | r2r.r2.cmd('s 0') 426 | } 427 | 428 | be := if test.mode.contains('E') { 'true' } else { 'false' } 429 | r2r.r2.cmd('e cfg.bigendian=${be}') 430 | 431 | res := r2r.r2.cmd(if dismode { '"pad ${test.data}"' } else { '"pa ${test.inst}"' }) 432 | 433 | mut mark := term.green('OK') 434 | if res.trim_space() == test_expect { 435 | if test.mode.contains('B') { 436 | mark = term.yellow('FX') 437 | r2r.fixed++ 438 | } else { 439 | r2r.success++ 440 | } 441 | } 442 | else { 443 | if test.mode.contains('B') { 444 | mark = term.blue('BR') 445 | r2r.broken++ 446 | } 447 | else { 448 | mark = term.red('XX') 449 | r2r.failed++ 450 | } 451 | } 452 | time_end := time.ticks() 453 | times := time_end - time_start 454 | if !r2r.show_quiet || !mark.contains('OK') { 455 | println('[${mark}] ${test.mode} (time ${times}) ${test.arch} ${test.bits} : ${test.data} ${test.inst}') 456 | } 457 | if r2r.r2.break_end() { 458 | eprintln('Interrupted') 459 | exit(1) 460 | } 461 | r2r.wg.done() 462 | } 463 | 464 | fn (r2r mut R2r) run_asm_test(test R2rAsmTest, dismode bool) { 465 | if !isnil(r2r.r2) { 466 | r2r.run_asm_test_native(test, dismode) 467 | return 468 | } 469 | // TODO: use the r2 api instead of spawning all the time 470 | mut args := []string 471 | args << '-a ${test.arch}' 472 | args << '-b ${test.bits}' 473 | if test.cpu != '' { 474 | args << '-c ${test.cpu}' 475 | } 476 | if test.offs != 0 { 477 | args << '-o ${test.offs}' 478 | } 479 | if test.mode.contains('E') { 480 | args << '-e' 481 | } 482 | if dismode { 483 | args << '-d' 484 | args << test.data 485 | } 486 | else { 487 | args << '"${test.inst}"' 488 | } 489 | rasm2_flags := args.join(' ') 490 | time_start := time.ticks() 491 | tmp_dir := mktmpdir('') 492 | tmp_output := os.join_path(tmp_dir,'output.txt') 493 | os.system('rasm2 ${rasm2_flags} > ${tmp_output}') 494 | res := os.read_file(tmp_output) or { 495 | panic(err) 496 | } 497 | os.rm(tmp_output) 498 | os.rmdir(tmp_dir) 499 | mut mark := term.green('OK') 500 | test_expect := if dismode { test.inst.trim_space() } else { test.data.trim_space() } 501 | if res.trim_space() == test_expect { 502 | if test.mode.contains('B') { 503 | mark = term.yellow('FX') 504 | } 505 | } 506 | else { 507 | if test.mode.contains('B') { 508 | mark = term.blue('BR') 509 | } 510 | else { 511 | mark = term.red('XX') 512 | } 513 | } 514 | time_end := time.ticks() 515 | times := time_end - time_start 516 | println('[${mark}] ${test.mode} (time ${times}) ${test.arch} ${test.bits} : ${test.data} ${test.inst}') 517 | r2r.wg.done() 518 | } 519 | 520 | fn handle_control_c() { 521 | $if windows { 522 | // ^C not handled on windows 523 | } $else { 524 | os.signal(C.SIGINT, control_c) 525 | } 526 | } 527 | 528 | fn (r2r mut R2r) run_cmd_test(test R2rCmdTest) { 529 | time_start := time.ticks() 530 | tmp_dir := mktmpdir('') 531 | tmp_script := os.join_path(tmp_dir,'script.r2') 532 | tmp_stderr := os.join_path(tmp_dir,'stderr.txt') 533 | tmp_output := os.join_path(tmp_dir,'output.txt') 534 | os.write_file(tmp_script, test.cmds) 535 | // TODO: handle timeout 536 | r2 := '${r2r.r2_path} -e scr.utf8=0 -e scr.interactive=0 -e scr.color=0 -NQ' 537 | cwd := os.getwd() 538 | os.chdir('../new') // fix runnig directory 539 | os.system('${r2} -i ${tmp_script} ${test.args} ${test.file} 2> ${tmp_stderr} > ${tmp_output}') 540 | os.chdir(cwd) 541 | res := os.read_file(tmp_output) or { 542 | panic(err) 543 | } 544 | errstr := os.read_file(tmp_stderr) or { 545 | panic(err) 546 | } 547 | os.rm(tmp_script) 548 | os.rm(tmp_output) 549 | os.rm(tmp_stderr) 550 | os.rmdir(tmp_dir) 551 | mut mark := term.green('OK') 552 | test_expect := test.expect.trim_space() 553 | if res.trim_space() != test_expect { 554 | mark = r2r.test_failed(test, test_expect, res) 555 | } 556 | else { 557 | if test.expect_err != '' && errstr.trim_space() != test.expect_err.trim_space() { 558 | mark = r2r.test_failed(test, test.expect_err, errstr) 559 | } 560 | else if test.broken { 561 | mark = r2r.test_fixed(test) 562 | } 563 | else { 564 | r2r.success++ 565 | } 566 | } 567 | handle_control_c() 568 | time_end := time.ticks() 569 | times := time_end - time_start 570 | println('[${mark}] (time ${times}) ${test.source} : ${test.name}') 571 | // count results 572 | r2r.wg.done() 573 | } 574 | 575 | fn (r2r mut R2r) run_fuz_test(ff string, pc int) bool { 576 | handle_control_c() 577 | cmd := 'rarun2 timeout=${default_timeout} system="${r2r.r2_path} -qq -A -n ${ff}"' 578 | res := os.system(cmd) == 0 579 | handle_control_c() 580 | 581 | mark := if res { term.green('OK') } else { term.red('XX') } 582 | if res { 583 | r2r.success++ 584 | } else { 585 | r2r.failed++ 586 | } 587 | if !r2r.show_quiet || !res { 588 | println('[${mark}] ${pc}% ${ff}') 589 | } 590 | r2r.wg.done() 591 | return res 592 | } 593 | 594 | fn (r2r R2r) git_clone(ghpath, localpath string) { 595 | os.system('cd ${r2r.db_path}/.. ; git clone --depth 1 https://github.com/${ghpath} ${localpath}') 596 | } 597 | 598 | fn (r2r mut R2r) run_fuz_tests() { 599 | home := r2r_home() 600 | fuzz_path := '${home}/bins/fuzzed' 601 | r2r.wg = sync.new_waitgroup() 602 | // open and analyze all the files in bins/fuzzed 603 | if !os.is_dir(fuzz_path) { 604 | r2r.git_clone('radareorg/radare2-testbins', 'bins') 605 | r2r.git_clone('radareorg/radare2-fuzztargets', 'fuzz/targets') 606 | } 607 | files := os.ls(fuzz_path) or { 608 | panic(err) 609 | } 610 | mut c := r2r.jobs 611 | mut n := 0 612 | t := files.len 613 | for file in files { 614 | ff := os.join_path(fuzz_path, file) 615 | pc := n * 100 / t 616 | handle_control_c() 617 | r2r.wg.add(1) 618 | go r2r.run_fuz_test(ff, pc) 619 | c-- 620 | if c == 0 { 621 | handle_control_c() 622 | r2r.wg.wait() 623 | c = r2r.jobs 624 | } 625 | n++ 626 | } 627 | r2r.wg.wait() 628 | } 629 | 630 | fn (r2r mut R2r) load_asm_test(testfile string) { 631 | lines := os.read_lines(testfile) or { 632 | panic(err) 633 | } 634 | for line in lines { 635 | if line.len == 0 || line.starts_with('#') { 636 | continue 637 | } 638 | words := line.split('"') 639 | if words.len == 3 { 640 | mut at := R2rAsmTest{} 641 | at.mode = words[0].trim_space() 642 | at.inst = words[1].trim_space() 643 | data := words[2].trim_space() 644 | if data.contains(' ') { 645 | w := data.split(' ') 646 | at.data = w[0].trim_space() 647 | at.offs = w[1].trim_space().u64() 648 | } 649 | else { 650 | at.data = data 651 | } 652 | abs := testfile.split('/') 653 | values := abs[abs.len - 1].split('_') 654 | if values.len == 1 { 655 | at.arch = values[0] 656 | at.bits = default_asm_bits 657 | } 658 | else if values.len == 2 { 659 | at.arch = values[0] 660 | at.bits = values[1].int() 661 | } 662 | else if values.len == 3 { 663 | at.arch = values[0] 664 | at.cpu = values[1] 665 | at.bits = values[2].int() 666 | } 667 | else { 668 | eprintln('Warning: Invalid asm/cpu/bits filename ${abs}') 669 | } 670 | if at.bits == 0 { 671 | eprintln('Warning: Invalid asm.bits settings in ${abs}') 672 | at.bits = default_asm_bits 673 | } 674 | r2r.asm_tests << at 675 | } 676 | else { 677 | eprintln('Warning: Invalid asm test for ${testfile} in ${line}') 678 | } 679 | } 680 | } 681 | 682 | fn (r2r mut R2r) load_asm_tests(testpath string) { 683 | r2r.wg = sync.new_waitgroup() 684 | files := os.ls(testpath) or { 685 | panic(err) 686 | } 687 | for file in files { 688 | if file.starts_with('.') { 689 | continue 690 | } 691 | f := os.join_path(testpath,file) 692 | if os.is_dir(f) { 693 | r2r.load_asm_tests(f) 694 | } 695 | else { 696 | r2r.load_asm_test(f) 697 | } 698 | } 699 | r2r.wg.wait() 700 | } 701 | 702 | fn (r2r mut R2r) run_unit_tests() bool { 703 | wd := os.getwd() 704 | _ = os.system('make -C ${r2r.r2r_home}/unit') // TODO: rewrite in V instead of depending on a makefile 705 | unit_path := '${r2r.db_path}/../../unit/bin' 706 | unit_home := '${r2r.db_path}/../..' 707 | if !os.is_dir(unit_path) { 708 | eprintln('Cannot open unit_path') 709 | return false 710 | } 711 | os.chdir(unit_home) 712 | println('[r2r] Running unit tests from ${unit_path}') 713 | files := os.ls(unit_path) or { 714 | return false 715 | } 716 | for file in files { 717 | fpath := os.join_path(unit_path, file) 718 | if is_executable(fpath, file) { 719 | // TODO: filter OK 720 | cmd := if r2r.show_quiet { '(${fpath} ;echo \$? > .a) | grep -v OK || [ "\$(shell cat .a)" = 0 ]' } else { '$fpath' } 721 | if os.system(cmd) == 0 { 722 | r2r.success++ 723 | } 724 | else { 725 | r2r.failed++ 726 | } 727 | } 728 | } 729 | os.chdir(wd) 730 | return true 731 | } 732 | 733 | fn is_executable(abspath, filename string) bool { 734 | if filename.starts_with('r2-') { 735 | return false 736 | } 737 | if os.is_dir(abspath) { 738 | return false 739 | } 740 | return os.is_executable(abspath) 741 | } 742 | 743 | fn (r2r mut R2r) run_asm_tests() { 744 | mut c := r2r.jobs 745 | r2r.r2 = r2.new() 746 | // assemble/disassemble and compare 747 | for at in r2r.asm_tests { 748 | if at.mode.contains('a') { 749 | r2r.wg.add(1) 750 | if isnil(r2r.r2) { 751 | go r2r.run_asm_test(at, false) 752 | } 753 | else { 754 | r2r.run_asm_test(at, false) 755 | } 756 | if r2r.jobs > 0 { 757 | c-- 758 | if c < 1 { 759 | r2r.wg.wait() 760 | c = r2r.jobs 761 | } 762 | } 763 | } 764 | if at.mode.contains('d') { 765 | r2r.wg.add(1) 766 | if isnil(r2r.r2) { 767 | go r2r.run_asm_test(at, true) 768 | } 769 | else { 770 | r2r.run_asm_test(at, true) 771 | } 772 | if r2r.jobs > 0 { 773 | c-- 774 | if c < 1 { 775 | r2r.wg.wait() 776 | c = r2r.jobs 777 | } 778 | } 779 | } 780 | } 781 | r2r.wg.wait() 782 | } 783 | 784 | fn (r2r mut R2r) run_jsn_tests() { 785 | json_path := '${r2r.db_path}/json' 786 | files := os.ls(json_path) or { 787 | panic(err) 788 | } 789 | for file in files { 790 | f := os.join_path(json_path,file) 791 | lines := os.read_lines(f) or { 792 | panic(err) 793 | } 794 | for line in lines { 795 | if line.trim_space().len == 0 { 796 | continue 797 | } 798 | ok := r2r.run_jsn_test(line) 799 | mut mark := term.green('OK') 800 | if ok { 801 | r2r.success++ 802 | } 803 | else { 804 | if line.contains('BROKEN') { 805 | mark = term.blue('BR') 806 | r2r.broken++ 807 | } 808 | else { 809 | mark = term.red('XX') 810 | r2r.failed++ 811 | } 812 | } 813 | if !ok || !r2r.show_quiet { 814 | println('[${mark}] json ${line}') 815 | } 816 | } 817 | } 818 | } 819 | 820 | fn (r2r R2r)filtered_file(file string) bool { 821 | if r2r.filter_by_files.len == 0 { 822 | return false 823 | } 824 | for fbf in r2r.filter_by_files { 825 | if file.ends_with(fbf) { 826 | return false 827 | } 828 | } 829 | return true 830 | } 831 | 832 | fn (r2r mut R2r) run_cmd_tests() { 833 | println('[r2r] Running cmd tests') 834 | // r2r.r2 = r2.new() 835 | // TODO: use lock 836 | r2r.wg = sync.new_waitgroup() 837 | println('Adding ${r2r.cmd_tests.len} watchgooses') 838 | // r2r.wg.add(r2r.cmd_tests.len) 839 | mut c := r2r.jobs 840 | for t in r2r.cmd_tests { 841 | if r2r.filtered_file(t.source) { 842 | continue 843 | } 844 | handle_control_c() 845 | r2r.wg.add(1) 846 | go r2r.run_cmd_test(t) 847 | if r2r.jobs > 0 { 848 | c-- 849 | if c < 1 { 850 | r2r.wg.wait() 851 | c = r2r.jobs 852 | } 853 | } 854 | } 855 | r2r.wg.wait() 856 | } 857 | 858 | fn (r2r R2r) show_report() { 859 | total := r2r.broken + r2r.fixed + r2r.failed + r2r.success 860 | println('') 861 | println(' Broken: ${r2r.broken}') 862 | println(' Fixed: ${r2r.fixed}') 863 | println('Success: ${r2r.success}') 864 | println(' Failed: ${r2r.failed}') 865 | println(' Total: ${total}') 866 | } 867 | 868 | fn (r2r mut R2r) load_cmd_tests(testpath string) { 869 | files := os.ls(testpath) or { 870 | panic(err) 871 | } 872 | for file in files { 873 | if file.starts_with('.') { 874 | continue 875 | } 876 | f := os.join_path(testpath, file) 877 | if os.is_dir(f) { 878 | r2r.load_cmd_tests(f) 879 | } 880 | else { 881 | r2r.load_cmd_test(f) 882 | } 883 | } 884 | } 885 | 886 | struct DummyStruct { 887 | no string 888 | } 889 | 890 | fn (r2r mut R2r) run_jsn_test(cmd string) bool { 891 | if isnil(r2r.r2) { 892 | r2r.r2 = r2.new() 893 | // _ = r2r.r2.cmd('o /bin/ls') 894 | } 895 | jsonstr := r2r.r2.cmd(cmd) 896 | if jsonstr.trim_space() == '' { 897 | return true 898 | } 899 | // verify json 900 | /* 901 | _ = json.decode(DummyStruct, jsonstr) or { 902 | eprintln('[r2r] json ${cmd} = ${jsonstr}') 903 | return false 904 | } 905 | return true 906 | */ 907 | return os.system("echo '${jsonstr}' | jq . > /dev/null") == 0 908 | } 909 | 910 | fn (r2r R2r) load_jsn_tests(testpath string) { 911 | // implementation is in run_jsn_tests 912 | // nothing to load for now 913 | } 914 | 915 | fn (r2r mut R2r) load_tests() { 916 | r2r.cmd_tests = [] 917 | if !os.is_dir(r2r.db_path) { 918 | eprintln('Cannot open -d ${r2r.db_path}') 919 | return 920 | } 921 | if r2r.wants('json') { 922 | r2r.load_jsn_tests('${r2r.db_path}/json') 923 | } 924 | if r2r.wants('asm') { 925 | r2r.load_asm_tests('${r2r.db_path}/asm') 926 | } 927 | for f in r2r.filter_by_files { 928 | if os.exists(f) { 929 | r2r.load_cmd_test(f) 930 | } 931 | } 932 | for cmd_test_path in cmd_test_paths { 933 | if r2r.wants(cmd_test_path) { 934 | r2r.load_cmd_tests('${r2r.db_path}/${cmd_test_path}') 935 | } 936 | } 937 | if r2r.wants('arch') { 938 | $if x64 { 939 | $if linux { 940 | p := '${r2r.db_path}/archos' 941 | r2r.load_cmd_tests('$p/linux-x64/') 942 | } $else { 943 | $if macos { 944 | p := '${r2r.db_path}/archos' 945 | r2r.load_cmd_tests('$p/darwin-x64/') 946 | } $else { 947 | eprintln('Warning: archos tests not supported for current platform') 948 | } 949 | } 950 | } $else { 951 | eprintln('Warning: archos tests not supported for current platform') 952 | } 953 | } 954 | } 955 | --------------------------------------------------------------------------------