| #!/usr/bin/env vpython | |
| # | |
| # Copyright 2017 The Chromium Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Tests for java_deobfuscate.""" | |
| import argparse | |
| import os | |
| import subprocess | |
| import sys | |
| import tempfile | |
| import unittest | |
| # Set by command-line argument. | |
| _JAVA_DEOBFUSCATE_PATH = None | |
| LINE_PREFIXES = [ | |
| '', | |
| # logcat -v threadtime | |
| '09-08 14:38:35.535 18029 18084 E qcom_sensors_hal: ', | |
| # logcat | |
| 'W/GCM (15158): ', | |
| 'W/GCM ( 158): ', | |
| ] | |
| TEST_MAP = """\ | |
| this.was.Deobfuscated -> FOO: | |
| int[] mFontFamily -> a | |
| 1:3:void someMethod(int,android.os.Bundle):65:67 -> bar | |
| never.Deobfuscated -> NOTFOO: | |
| int[] mFontFamily -> a | |
| 1:3:void someMethod(int,android.os.Bundle):65:67 -> bar | |
| """ | |
| TEST_DATA = [ | |
| '', | |
| 'FOO', | |
| 'FOO.bar', | |
| 'Here is a FOO', | |
| 'Here is a class FOO', | |
| 'Here is a class FOO baz', | |
| 'Here is a "FOO" baz', | |
| 'Here is a type "FOO" baz', | |
| 'Here is a "FOO.bar" baz', | |
| 'SomeError: SomeFrameworkClass in isTestClass for FOO', | |
| 'Here is a FOO.bar', | |
| 'Here is a FOO.bar baz', | |
| 'END FOO#bar', | |
| 'new-instance 3810 (LSome/Framework/Class;) in LFOO;', | |
| 'FOO: Error message', | |
| 'Caused by: FOO: Error message', | |
| '\tat FOO.bar(PG:1)', | |
| '\t at\t FOO.bar\t (\t PG:\t 1\t )', | |
| ('Unable to start activity ComponentInfo{garbage.in/here.test}:' | |
| ' java.lang.NullPointerException: Attempt to invoke interface method' | |
| ' \'void FOO.bar(int,android.os.Bundle)\' on a null object reference'), | |
| ('Caused by: java.lang.NullPointerException: Attempt to read from field' | |
| ' \'int[] FOO.a\' on a null object reference'), | |
| 'java.lang.VerifyError: FOO', | |
| ('java.lang.NoSuchFieldError: No instance field a of type ' | |
| 'Ljava/lang/Class; in class LFOO;'), | |
| 'NOTFOO: Object of type FOO was not destroyed...', | |
| ] | |
| EXPECTED_OUTPUT = [ | |
| '', | |
| 'this.was.Deobfuscated', | |
| 'this.was.Deobfuscated.someMethod', | |
| 'Here is a FOO', | |
| 'Here is a class this.was.Deobfuscated', | |
| 'Here is a class FOO baz', | |
| 'Here is a "FOO" baz', | |
| 'Here is a type "this.was.Deobfuscated" baz', | |
| 'Here is a "this.was.Deobfuscated.someMethod" baz', | |
| 'SomeError: SomeFrameworkClass in isTestClass for this.was.Deobfuscated', | |
| 'Here is a this.was.Deobfuscated.someMethod', | |
| 'Here is a FOO.bar baz', | |
| 'END this.was.Deobfuscated#someMethod', | |
| 'new-instance 3810 (LSome/Framework/Class;) in Lthis/was/Deobfuscated;', | |
| 'this.was.Deobfuscated: Error message', | |
| 'Caused by: this.was.Deobfuscated: Error message', | |
| '\tat this.was.Deobfuscated.someMethod(Deobfuscated.java:65)', | |
| ('\t at\t this.was.Deobfuscated.someMethod\t ' | |
| '(\t Deobfuscated.java:\t 65\t )'), | |
| ('Unable to start activity ComponentInfo{garbage.in/here.test}:' | |
| ' java.lang.NullPointerException: Attempt to invoke interface method' | |
| ' \'void this.was.Deobfuscated.someMethod(int,android.os.Bundle)\' on a' | |
| ' null object reference'), | |
| ('Caused by: java.lang.NullPointerException: Attempt to read from field' | |
| ' \'int[] this.was.Deobfuscated.mFontFamily\' on a null object reference'), | |
| 'java.lang.VerifyError: this.was.Deobfuscated', | |
| ('java.lang.NoSuchFieldError: No instance field mFontFamily of type ' | |
| 'Ljava/lang/Class; in class Lthis/was/Deobfuscated;'), | |
| 'NOTFOO: Object of type this.was.Deobfuscated was not destroyed...', | |
| ] | |
| TEST_DATA = [s + '\n' for s in TEST_DATA] | |
| EXPECTED_OUTPUT = [s + '\n' for s in EXPECTED_OUTPUT] | |
| class JavaDeobfuscateTest(unittest.TestCase): | |
| def __init__(self, *args, **kwargs): | |
| super(JavaDeobfuscateTest, self).__init__(*args, **kwargs) | |
| self._map_file = None | |
| def setUp(self): | |
| self._map_file = tempfile.NamedTemporaryFile() | |
| self._map_file.write(TEST_MAP) | |
| self._map_file.flush() | |
| def tearDown(self): | |
| if self._map_file: | |
| self._map_file.close() | |
| def _testImpl(self, input_lines=None, expected_output_lines=None, | |
| prefix=''): | |
| self.assertTrue(bool(input_lines) == bool(expected_output_lines)) | |
| if not input_lines: | |
| input_lines = [prefix + x for x in TEST_DATA] | |
| if not expected_output_lines: | |
| expected_output_lines = [prefix + x for x in EXPECTED_OUTPUT] | |
| cmd = [_JAVA_DEOBFUSCATE_PATH, self._map_file.name] | |
| proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) | |
| proc_output, _ = proc.communicate(''.join(input_lines)) | |
| actual_output_lines = proc_output.splitlines(True) | |
| for actual, expected in zip(actual_output_lines, expected_output_lines): | |
| self.assertTrue( | |
| actual == expected or actual.replace('bar', 'someMethod') == expected, | |
| msg=''.join([ | |
| 'Deobfuscation failed.\n', | |
| ' actual: %s' % actual, | |
| ' expected: %s' % expected])) | |
| def testNoPrefix(self): | |
| self._testImpl(prefix='') | |
| def testThreadtimePrefix(self): | |
| self._testImpl(prefix='09-08 14:38:35.535 18029 18084 E qcom_sensors_hal: ') | |
| def testStandardPrefix(self): | |
| self._testImpl(prefix='W/GCM (15158): ') | |
| def testStandardPrefixWithPadding(self): | |
| self._testImpl(prefix='W/GCM ( 158): ') | |
| @unittest.skip('causes java_deobfuscate to hang, see crbug.com/876539') | |
| def testIndefiniteHang(self): | |
| # Test for crbug.com/876539. | |
| self._testImpl( | |
| input_lines=[ | |
| 'VFY: unable to resolve virtual method 2: LFOO;' | |
| + '.onDescendantInvalidated ' | |
| + '(Landroid/view/View;Landroid/view/View;)V', | |
| ], | |
| expected_output_lines=[ | |
| 'VFY: unable to resolve virtual method 2: Lthis.was.Deobfuscated;' | |
| + '.onDescendantInvalidated ' | |
| + '(Landroid/view/View;Landroid/view/View;)V', | |
| ]) | |
| if __name__ == '__main__': | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--java-deobfuscate-path', type=os.path.realpath, | |
| required=True) | |
| known_args, unittest_args = parser.parse_known_args() | |
| _JAVA_DEOBFUSCATE_PATH = known_args.java_deobfuscate_path | |
| unittest_args = [sys.argv[0]] + unittest_args | |
| unittest.main(argv=unittest_args) |