xref: /aosp_15_r20/external/cronet/components/cronet/tools/api_static_checks_unittest.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1#!/usr/bin/env python3
2# Copyright 2016 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""api_static_checks_unittest.py - Unittests for api_static_checks.py"""
7
8
9import contextlib
10import hashlib
11import io
12import os
13import shutil
14import sys
15import tempfile
16import unittest
17
18REPOSITORY_ROOT = os.path.abspath(os.path.join(
19    os.path.dirname(__file__), '..', '..', '..'))
20
21sys.path.append(os.path.join(REPOSITORY_ROOT, 'components'))
22from cronet.tools import api_static_checks  # pylint: disable=wrong-import-position
23
24# pylint: disable=useless-object-inheritance
25
26
27ERROR_PREFIX_CHECK_API_CALLS = (
28"""ERROR: Found the following calls from implementation classes through
29       API classes.  These could fail if older API is used that
30       does not contain newer methods.  Please call through a
31       wrapper class from VersionSafeCallbacks.
32""")
33
34
35ERROR_PREFIX_UPDATE_API = (
36"""ERROR: This API was modified or removed:
37             """)
38
39
40ERROR_SUFFIX_UPDATE_API = (
41"""
42
43       Cronet API methods and classes cannot be modified.
44""")
45
46
47CHECK_API_VERSION_PREFIX = (
48"""DO NOT EDIT THIS FILE, USE update_api.py TO UPDATE IT
49
50""")
51
52
53API_FILENAME = './android/api.txt'
54API_VERSION_FILENAME = './android/api_version.txt'
55
56
57@contextlib.contextmanager
58def capture_output():
59  # A contextmanger that collects the stdout and stderr of wrapped code
60
61  oldout,olderr = sys.stdout, sys.stderr
62  try:
63    out = [io.StringIO(), io.StringIO()]
64    sys.stdout,sys.stderr = out
65    yield out
66  finally:
67    sys.stdout,sys.stderr = oldout, olderr
68    out[0] = out[0].getvalue()
69    out[1] = out[1].getvalue()
70
71
72class ApiStaticCheckUnitTest(unittest.TestCase):
73  def setUp(self):
74    self.exe_path = os.path.join(REPOSITORY_ROOT, 'out')
75    self.temp_dir = tempfile.mkdtemp(dir=self.exe_path)
76    os.chdir(self.temp_dir)
77    os.mkdir('android')
78    with open(API_VERSION_FILENAME, 'w') as api_version_file:
79      api_version_file.write('0')
80    with open(API_FILENAME, 'w') as api_file:
81      api_file.write('}\nStamp: 7d9d25f71cb8a5aba86202540a20d405\n')
82    shutil.copytree(os.path.dirname(__file__), 'tools')
83
84
85  def tearDown(self):
86    shutil.rmtree(self.temp_dir)
87
88
89  def make_jar(self, java, class_name):
90    # Compile |java| wrapped in a class named |class_name| to a jar file and
91    # return jar filename.
92
93    java_filename = class_name + '.java'
94    class_filenames = class_name + '*.class'
95    jar_filename = class_name + '.jar'
96
97    with open(java_filename, 'w') as java_file:
98      java_file.write('public class %s {' % class_name)
99      java_file.write(java)
100      java_file.write('}')
101    os.system('javac %s' % java_filename)
102    os.system('jar cf %s %s' % (jar_filename, class_filenames))
103    return jar_filename
104
105
106  def run_check_api_calls(self, api_java, impl_java):
107    test = self
108    class MockOpts(object):
109      def __init__(self):
110        self.api_jar = test.make_jar(api_java, 'Api')
111        self.impl_jar = [test.make_jar(impl_java, 'Impl')]
112    opts = MockOpts()
113    with capture_output() as return_output:
114      return_code = api_static_checks.check_api_calls(opts)
115    return [return_code, return_output[0]]
116
117
118  def test_check_api_calls_success(self):
119    # Test simple classes with functions
120    self.assertEqual(self.run_check_api_calls(
121        'void a(){}', 'void b(){}'), [True, ''])
122    # Test simple classes with functions calling themselves
123    self.assertEqual(self.run_check_api_calls(
124        'void a(){} void b(){a();}', 'void c(){} void d(){c();}'), [True, ''])
125
126
127  def test_check_api_calls_failure(self):
128    # Test static call
129    self.assertEqual(self.run_check_api_calls(
130        'public static void a(){}', 'void b(){Api.a();}'),
131        [False, ERROR_PREFIX_CHECK_API_CALLS + 'Impl/b -> Api/a:()V\n'])
132    # Test virtual call
133    self.assertEqual(self.run_check_api_calls(
134        'public void a(){}', 'void b(){new Api().a();}'),
135        [False, ERROR_PREFIX_CHECK_API_CALLS + 'Impl/b -> Api/a:()V\n'])
136
137
138  def run_check_api_version(self, java):
139    OUT_FILENAME = 'out.txt'
140    return_code = os.system('./tools/update_api.py --api_jar %s > %s' %
141        (self.make_jar(java, 'Api'), OUT_FILENAME))
142    with open(API_FILENAME, 'r') as api_file:
143      api = api_file.read()
144    with open(API_VERSION_FILENAME, 'r') as api_version_file:
145      api_version = api_version_file.read()
146    with open(OUT_FILENAME, 'r') as out_file:
147      output = out_file.read()
148
149    # Verify stamp
150    api_stamp = api.split('\n')[-2]
151    stamp_length = len('Stamp: 78418460c193047980ae9eabb79293f2\n')
152    api = api[:-stamp_length]
153    api_hash = hashlib.md5()
154    api_hash.update(api.encode('utf-8'))
155    self.assertEqual(api_stamp, 'Stamp: %s' % api_hash.hexdigest())
156
157    return [return_code == 0, output, api, api_version]
158
159
160  def test_update_api_success(self):
161    # Test simple new API
162    self.assertEqual(self.run_check_api_version(
163        'public void a(){}'),
164        [True, '', CHECK_API_VERSION_PREFIX + """public class Api {
165  public Api();
166  public void a();
167}
168""", '1'])
169    # Test version number not increased when API not changed
170    self.assertEqual(self.run_check_api_version(
171        'public void a(){}'),
172        [True, '', CHECK_API_VERSION_PREFIX + """public class Api {
173  public Api();
174  public void a();
175}
176""", '1'])
177    # Test acceptable API method addition
178    self.assertEqual(self.run_check_api_version(
179        'public void a(){} public void b(){}'),
180        [True, '', CHECK_API_VERSION_PREFIX + """public class Api {
181  public Api();
182  public void a();
183  public void b();
184}
185""", '2'])
186    # Test version number not increased when API not changed
187    self.assertEqual(self.run_check_api_version(
188        'public void a(){} public void b(){}'),
189        [True, '', CHECK_API_VERSION_PREFIX + """public class Api {
190  public Api();
191  public void a();
192  public void b();
193}
194""", '2'])
195    # Test acceptable API class addition
196    self.assertEqual(self.run_check_api_version(
197        'public void a(){} public void b(){} public class C {}'),
198        [True, '', CHECK_API_VERSION_PREFIX + """public class Api$C {
199  public Api$C(Api);
200}
201public class Api {
202  public Api();
203  public void a();
204  public void b();
205}
206""", '3'])
207    # Test version number not increased when API not changed
208    self.assertEqual(self.run_check_api_version(
209        'public void a(){} public void b(){} public class C {}'),
210        [True, '', CHECK_API_VERSION_PREFIX + """public class Api$C {
211  public Api$C(Api);
212}
213public class Api {
214  public Api();
215  public void a();
216  public void b();
217}
218""", '3'])
219
220
221  def test_update_api_failure(self):
222    # Create a simple new API
223    self.assertEqual(self.run_check_api_version(
224        'public void a(){}'),
225        [True, '', CHECK_API_VERSION_PREFIX + """public class Api {
226  public Api();
227  public void a();
228}
229""", '1'])
230    # Test removing API method not allowed
231    self.assertEqual(self.run_check_api_version(''),
232        [False, ERROR_PREFIX_UPDATE_API + 'public void a();'
233            + ERROR_SUFFIX_UPDATE_API,
234            CHECK_API_VERSION_PREFIX + """public class Api {
235  public Api();
236  public void a();
237}
238""", '1'])
239    # Test modifying API method not allowed
240    self.assertEqual(self.run_check_api_version(
241        'public void a(int x){}'),
242        [False, ERROR_PREFIX_UPDATE_API + 'public void a();'
243            + ERROR_SUFFIX_UPDATE_API,
244            CHECK_API_VERSION_PREFIX + """public class Api {
245  public Api();
246  public void a();
247}
248""", '1'])
249