|  | #!/usr/bin/env python | 
|  | # Copyright 2016 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. | 
|  |  | 
|  | """api_static_checks_unittest.py - Unittests for api_static_checks.py""" | 
|  |  | 
|  |  | 
|  | import contextlib | 
|  | from cStringIO import StringIO | 
|  | import md5 | 
|  | import os | 
|  | import shutil | 
|  | import sys | 
|  | import tempfile | 
|  | import unittest | 
|  |  | 
|  | REPOSITORY_ROOT = os.path.abspath(os.path.join( | 
|  | os.path.dirname(__file__), '..', '..', '..')) | 
|  |  | 
|  | sys.path.append(os.path.join(REPOSITORY_ROOT, 'components')) | 
|  | from cronet.tools import api_static_checks  # pylint: disable=wrong-import-position | 
|  |  | 
|  |  | 
|  | ERROR_PREFIX_CHECK_API_CALLS = ( | 
|  | """ERROR: Found the following calls from implementation classes through | 
|  | API classes.  These could fail if older API is used that | 
|  | does not contain newer methods.  Please call through a | 
|  | wrapper class from VersionSafeCallbacks. | 
|  | """) | 
|  |  | 
|  |  | 
|  | ERROR_PREFIX_UPDATE_API = ( | 
|  | """ERROR: This API was modified or removed: | 
|  | """) | 
|  |  | 
|  |  | 
|  | ERROR_SUFFIX_UPDATE_API = ( | 
|  | """ | 
|  |  | 
|  | Cronet API methods and classes cannot be modified. | 
|  | """) | 
|  |  | 
|  |  | 
|  | CHECK_API_VERSION_PREFIX = ( | 
|  | """DO NOT EDIT THIS FILE, USE update_api.py TO UPDATE IT | 
|  |  | 
|  | """) | 
|  |  | 
|  |  | 
|  | API_FILENAME = './android/api.txt' | 
|  | API_VERSION_FILENAME = './android/api_version.txt' | 
|  |  | 
|  |  | 
|  | @contextlib.contextmanager | 
|  | def capture_output(): | 
|  | # A contextmanger that collects the stdout and stderr of wrapped code | 
|  |  | 
|  | oldout,olderr = sys.stdout, sys.stderr | 
|  | try: | 
|  | out=[StringIO(), StringIO()] | 
|  | sys.stdout,sys.stderr = out | 
|  | yield out | 
|  | finally: | 
|  | sys.stdout,sys.stderr = oldout, olderr | 
|  | out[0] = out[0].getvalue() | 
|  | out[1] = out[1].getvalue() | 
|  |  | 
|  |  | 
|  | class ApiStaticCheckUnitTest(unittest.TestCase): | 
|  | def setUp(self): | 
|  | self.temp_dir = tempfile.mkdtemp() | 
|  | os.chdir(self.temp_dir) | 
|  | os.mkdir('android') | 
|  | with open(API_VERSION_FILENAME, 'w') as api_version_file: | 
|  | api_version_file.write('0') | 
|  | with open(API_FILENAME, 'w') as api_file: | 
|  | api_file.write('}\nStamp: 7d9d25f71cb8a5aba86202540a20d405\n') | 
|  | shutil.copytree(os.path.dirname(__file__), 'tools') | 
|  |  | 
|  |  | 
|  | def tearDown(self): | 
|  | shutil.rmtree(self.temp_dir) | 
|  |  | 
|  |  | 
|  | def make_jar(self, java, class_name): | 
|  | # Compile |java| wrapped in a class named |class_name| to a jar file and | 
|  | # return jar filename. | 
|  |  | 
|  | java_filename = class_name + '.java' | 
|  | class_filenames = class_name + '*.class' | 
|  | jar_filename = class_name + '.jar' | 
|  |  | 
|  | with open(java_filename, 'w') as java_file: | 
|  | java_file.write('public class %s {' % class_name) | 
|  | java_file.write(java) | 
|  | java_file.write('}') | 
|  | os.system('javac %s' % java_filename) | 
|  | os.system('jar cf %s %s' % (jar_filename, class_filenames)) | 
|  | return jar_filename | 
|  |  | 
|  |  | 
|  | def run_check_api_calls(self, api_java, impl_java): | 
|  | test = self | 
|  | class MockOpts(object): | 
|  | def __init__(self): | 
|  | self.api_jar = test.make_jar(api_java, 'Api') | 
|  | self.impl_jar = [test.make_jar(impl_java, 'Impl')] | 
|  | opts = MockOpts() | 
|  | with capture_output() as return_output: | 
|  | return_code = api_static_checks.check_api_calls(opts) | 
|  | return [return_code, return_output[0]] | 
|  |  | 
|  |  | 
|  | def test_check_api_calls_success(self): | 
|  | # Test simple classes with functions | 
|  | self.assertEqual(self.run_check_api_calls( | 
|  | 'void a(){}', 'void b(){}'), [True, '']) | 
|  | # Test simple classes with functions calling themselves | 
|  | self.assertEqual(self.run_check_api_calls( | 
|  | 'void a(){} void b(){a();}', 'void c(){} void d(){c();}'), [True, '']) | 
|  |  | 
|  |  | 
|  | def test_check_api_calls_failure(self): | 
|  | # Test static call | 
|  | self.assertEqual(self.run_check_api_calls( | 
|  | 'public static void a(){}', 'void b(){Api.a();}'), | 
|  | [False, ERROR_PREFIX_CHECK_API_CALLS + 'Impl/b -> Api/a:()V\n']) | 
|  | # Test virtual call | 
|  | self.assertEqual(self.run_check_api_calls( | 
|  | 'public void a(){}', 'void b(){new Api().a();}'), | 
|  | [False, ERROR_PREFIX_CHECK_API_CALLS + 'Impl/b -> Api/a:()V\n']) | 
|  |  | 
|  |  | 
|  | def run_check_api_version(self, java): | 
|  | OUT_FILENAME = 'out.txt' | 
|  | return_code = os.system('./tools/update_api.py --api_jar %s > %s' % | 
|  | (self.make_jar(java, 'Api'), OUT_FILENAME)) | 
|  | with open(API_FILENAME, 'r') as api_file: | 
|  | api = api_file.read() | 
|  | with open(API_VERSION_FILENAME, 'r') as api_version_file: | 
|  | api_version = api_version_file.read() | 
|  | with open(OUT_FILENAME, 'r') as out_file: | 
|  | output = out_file.read() | 
|  |  | 
|  | # Verify stamp | 
|  | api_stamp = api.split('\n')[-2] | 
|  | stamp_length = len('Stamp: 78418460c193047980ae9eabb79293f2\n') | 
|  | api = api[:-stamp_length] | 
|  | api_hash = md5.new() | 
|  | api_hash.update(api) | 
|  | self.assertEquals(api_stamp, 'Stamp: %s' % api_hash.hexdigest()) | 
|  |  | 
|  | return [return_code == 0, output, api, api_version] | 
|  |  | 
|  |  | 
|  | def test_update_api_success(self): | 
|  | # Test simple new API | 
|  | self.assertEqual(self.run_check_api_version( | 
|  | 'public void a(){}'), | 
|  | [True, '', CHECK_API_VERSION_PREFIX + """public class Api { | 
|  | public Api(); | 
|  | public void a(); | 
|  | } | 
|  | """, '1']) | 
|  | # Test version number not increased when API not changed | 
|  | self.assertEqual(self.run_check_api_version( | 
|  | 'public void a(){}'), | 
|  | [True, '', CHECK_API_VERSION_PREFIX + """public class Api { | 
|  | public Api(); | 
|  | public void a(); | 
|  | } | 
|  | """, '1']) | 
|  | # Test acceptable API method addition | 
|  | self.assertEqual(self.run_check_api_version( | 
|  | 'public void a(){} public void b(){}'), | 
|  | [True, '', CHECK_API_VERSION_PREFIX + """public class Api { | 
|  | public Api(); | 
|  | public void a(); | 
|  | public void b(); | 
|  | } | 
|  | """, '2']) | 
|  | # Test version number not increased when API not changed | 
|  | self.assertEqual(self.run_check_api_version( | 
|  | 'public void a(){} public void b(){}'), | 
|  | [True, '', CHECK_API_VERSION_PREFIX + """public class Api { | 
|  | public Api(); | 
|  | public void a(); | 
|  | public void b(); | 
|  | } | 
|  | """, '2']) | 
|  | # Test acceptable API class addition | 
|  | self.assertEqual(self.run_check_api_version( | 
|  | 'public void a(){} public void b(){} public class C {}'), | 
|  | [True, '', CHECK_API_VERSION_PREFIX + """public class Api$C { | 
|  | public Api$C(Api); | 
|  | } | 
|  | public class Api { | 
|  | public Api(); | 
|  | public void a(); | 
|  | public void b(); | 
|  | } | 
|  | """, '3']) | 
|  | # Test version number not increased when API not changed | 
|  | self.assertEqual(self.run_check_api_version( | 
|  | 'public void a(){} public void b(){} public class C {}'), | 
|  | [True, '', CHECK_API_VERSION_PREFIX + """public class Api$C { | 
|  | public Api$C(Api); | 
|  | } | 
|  | public class Api { | 
|  | public Api(); | 
|  | public void a(); | 
|  | public void b(); | 
|  | } | 
|  | """, '3']) | 
|  |  | 
|  |  | 
|  | def test_update_api_failure(self): | 
|  | # Create a simple new API | 
|  | self.assertEqual(self.run_check_api_version( | 
|  | 'public void a(){}'), | 
|  | [True, '', CHECK_API_VERSION_PREFIX + """public class Api { | 
|  | public Api(); | 
|  | public void a(); | 
|  | } | 
|  | """, '1']) | 
|  | # Test removing API method not allowed | 
|  | self.assertEqual(self.run_check_api_version(''), | 
|  | [False, ERROR_PREFIX_UPDATE_API + 'public void a();' | 
|  | + ERROR_SUFFIX_UPDATE_API, | 
|  | CHECK_API_VERSION_PREFIX + """public class Api { | 
|  | public Api(); | 
|  | public void a(); | 
|  | } | 
|  | """, '1']) | 
|  | # Test modifying API method not allowed | 
|  | self.assertEqual(self.run_check_api_version( | 
|  | 'public void a(int x){}'), | 
|  | [False, ERROR_PREFIX_UPDATE_API + 'public void a();' | 
|  | + ERROR_SUFFIX_UPDATE_API, | 
|  | CHECK_API_VERSION_PREFIX + """public class Api { | 
|  | public Api(); | 
|  | public void a(); | 
|  | } | 
|  | """, '1']) |