lin
2025-07-30 fcd736bf35fd93b563e9bbf594f2aa7b62028cc9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
 
import re
import os
import glob
import sys
import subprocess
import argparse
 
# test command line arguments
argParser = argparse.ArgumentParser(description = 'dexter end-to-end test driver')
argParser.add_argument('-cmd', default = 'dexter')
argParser.add_argument('-root', help = 'Root location of the test data files')
argParser.add_argument('-update', action = 'store_true', help = 'Update the expected files')
args = argParser.parse_args()
 
# the bazel sandbox test data root
data_root = args.root or 'tools/dexter/testdata'
 
# update expected (golden) output?
if args.update:
  if args.root is None:
    print('ERROR: -update requires -root value')
    exit(1)
  print('\nUpdating expected output (test data root: %s)' % data_root)
 
# list of test cases
# ( <test_name> : { <test_case_config> } )
test_cases = {
  'map'             : { 'args' : '-m', 'input' : ['*.dex'] },
  'stats'           : { 'args' : '-s', 'input' : ['*.dex'] },
  'asm'             : { 'args' : '-d', 'input' : ['*.dex'] },
  'hello_stats'     : { 'args' : '-s -e Hello', 'input' : ['hello.dex'] },
  'am_stats'        : { 'args' : '-s -e android.app.ActivityManager', 'input' : ['large.dex'] },
  'rewrite'         : { 'args' : '-d -x full_rewrite', 'input' : ['*.dex'] },
  'entry_hook'      : { 'args' : '-d -x stress_entry_hook', 'input' : [
                          'entry_hooks.dex', 'hello.dex', 'medium.dex', 'min.dex' ] },
  'exit_hook'       : { 'args' : '-d -x stress_exit_hook', 'input' : [
                          'exit_hooks.dex', 'medium.dex', 'try_catch.dex' ] },
  'wrap_invoke'     : { 'args' : '-d -x stress_wrap_invoke', 'input' : [
                          'hello.dex', 'hello_nodebug.dex', 'medium.dex' ] },
  'mi'              : { 'args' : '-d -x test_method_instrumenter', 'input' : ['mi.dex'] },
  'find_method'     : { 'args' : '-x stress_find_method', 'input' : [
                          'hello.dex', 'entry_hooks.dex', 'medium.dex', 'large.dex', 'try_catch.dex' ] },
  'verbose_cfg'     : { 'args' : '-d --cfg=verbose', 'input' : ['*.dex'] },
  'compact_cfg'     : { 'args' : '-d --cfg=compact', 'input' : ['*.dex'] },
  'scratch_regs'    : { 'args' : '-d -x stress_scratch_regs', 'input' : ['*.dex'] },
  'regs_usage'      : { 'args' : '-x regs_histogram', 'input' : ['*.dex'] },
  'code_coverage'   : { 'args' : '-d -x code_coverage', 'input' : ['*.dex'] },
}
 
# run a shell command and returns the stdout content
def Run(cmd, stdin_content=None):
  return subprocess.Popen(
    args = cmd,
    shell = True,
    stdin = subprocess.PIPE,
    stdout = subprocess.PIPE,
    stderr = subprocess.STDOUT).communicate(input = stdin_content)[0]
 
tests = 0
failures = 0
 
# for each test_case, run dexter over the specified input (ex. *.dex)
#
# the expected ('golden') output has the same base name as the input .dex,
# for example (test_name = 'map') :
#
#    'hello.dex' -> 'expected/hello.map'
#
for test_name, test_config in sorted(test_cases.iteritems()):
  for input_pattern in test_config['input']:
    input_files = glob.glob(os.path.join(data_root, input_pattern))
 
    for input in input_files:
      tests = tests + 1
 
      # run dexter with the test arguments
      cmd = '%s %s %s' % (args.cmd, test_config['args'], input)
      actual_output = Run(cmd)
 
      # build the expected filename
      expected_filename = re.sub(r'\.dex', ('.%s' % test_name), os.path.basename(input))
      expected_filename = os.path.join(data_root, 'expected', expected_filename)
 
      if args.update:
        # update expected output file
        with open(expected_filename, "w") as f:
          f.write(actual_output)
      else:
        # compare the actual output with the expected output
        cmp_output = Run('diff "%s" -' % expected_filename, actual_output)
        if cmp_output:
          print('\nFAILED: expected output mismatch (%s)' % os.path.basename(expected_filename))
          print(cmp_output)
          failures = failures + 1
        else:
          print('ok: output matching (%s)' % os.path.basename(expected_filename))
 
if args.update:
  print('\nSUMMARY: updated expected output for %d tests\n' % tests)
else:
  print('\nSUMMARY: %d failure(s), %d test cases\n' % (failures, tests))
 
if failures != 0:
  exit(1)