xref: /aosp_15_r20/frameworks/base/ravenwood/scripts/convert-androidtest.py (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1*d57664e9SAndroid Build Coastguard Worker#!/usr/bin/python3
2*d57664e9SAndroid Build Coastguard Worker# Copyright (C) 2024 The Android Open Source Project
3*d57664e9SAndroid Build Coastguard Worker#
4*d57664e9SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*d57664e9SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*d57664e9SAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*d57664e9SAndroid Build Coastguard Worker#
8*d57664e9SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
9*d57664e9SAndroid Build Coastguard Worker#
10*d57664e9SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*d57664e9SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*d57664e9SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*d57664e9SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*d57664e9SAndroid Build Coastguard Worker# limitations under the License.
15*d57664e9SAndroid Build Coastguard Worker
16*d57664e9SAndroid Build Coastguard Worker# This script converts a legacy test class (using AndroidTestCase, TestCase or
17*d57664e9SAndroid Build Coastguard Worker# InstrumentationTestCase to a modern style test class, in a best-effort manner.
18*d57664e9SAndroid Build Coastguard Worker#
19*d57664e9SAndroid Build Coastguard Worker# Usage:
20*d57664e9SAndroid Build Coastguard Worker#  convert-androidtest.py TARGET-FILE [TARGET-FILE ...]
21*d57664e9SAndroid Build Coastguard Worker#
22*d57664e9SAndroid Build Coastguard Worker# Caveats:
23*d57664e9SAndroid Build Coastguard Worker#   - It adds all the extra imports, even if they're not needed.
24*d57664e9SAndroid Build Coastguard Worker#   - It won't sort imports.
25*d57664e9SAndroid Build Coastguard Worker#   - It also always adds getContext() and getTestContext().
26*d57664e9SAndroid Build Coastguard Worker#
27*d57664e9SAndroid Build Coastguard Worker
28*d57664e9SAndroid Build Coastguard Workerimport sys
29*d57664e9SAndroid Build Coastguard Workerimport fileinput
30*d57664e9SAndroid Build Coastguard Workerimport re
31*d57664e9SAndroid Build Coastguard Workerimport subprocess
32*d57664e9SAndroid Build Coastguard Worker
33*d57664e9SAndroid Build Coastguard Worker# Print message on console
34*d57664e9SAndroid Build Coastguard Workerdef log(msg):
35*d57664e9SAndroid Build Coastguard Worker    print(msg, file=sys.stderr)
36*d57664e9SAndroid Build Coastguard Worker
37*d57664e9SAndroid Build Coastguard Worker
38*d57664e9SAndroid Build Coastguard Worker# Matches `extends AndroidTestCase` (or another similar base class)
39*d57664e9SAndroid Build Coastguard Workerre_extends = re.compile(
40*d57664e9SAndroid Build Coastguard Worker    r''' \b extends \s+ (AndroidTestCase|TestCase|InstrumentationTestCase) \s* ''',
41*d57664e9SAndroid Build Coastguard Worker    re.S + re.X)
42*d57664e9SAndroid Build Coastguard Worker
43*d57664e9SAndroid Build Coastguard Worker
44*d57664e9SAndroid Build Coastguard Worker# Look into given files and return the files that have `re_extends`.
45*d57664e9SAndroid Build Coastguard Workerdef find_target_files(files):
46*d57664e9SAndroid Build Coastguard Worker    ret = []
47*d57664e9SAndroid Build Coastguard Worker
48*d57664e9SAndroid Build Coastguard Worker    for file in files:
49*d57664e9SAndroid Build Coastguard Worker        try:
50*d57664e9SAndroid Build Coastguard Worker            with open(file, 'r') as f:
51*d57664e9SAndroid Build Coastguard Worker                data = f.read()
52*d57664e9SAndroid Build Coastguard Worker
53*d57664e9SAndroid Build Coastguard Worker                if re_extends.search(data):
54*d57664e9SAndroid Build Coastguard Worker                    ret.append(file)
55*d57664e9SAndroid Build Coastguard Worker
56*d57664e9SAndroid Build Coastguard Worker        except FileNotFoundError as e:
57*d57664e9SAndroid Build Coastguard Worker            log(f'Failed to open file {file}: {e}')
58*d57664e9SAndroid Build Coastguard Worker
59*d57664e9SAndroid Build Coastguard Worker    return ret
60*d57664e9SAndroid Build Coastguard Worker
61*d57664e9SAndroid Build Coastguard Worker
62*d57664e9SAndroid Build Coastguard Workerdef main(args):
63*d57664e9SAndroid Build Coastguard Worker    files = args
64*d57664e9SAndroid Build Coastguard Worker
65*d57664e9SAndroid Build Coastguard Worker    # Find the files that should be processed.
66*d57664e9SAndroid Build Coastguard Worker    files = find_target_files(files)
67*d57664e9SAndroid Build Coastguard Worker
68*d57664e9SAndroid Build Coastguard Worker    if len(files) == 0:
69*d57664e9SAndroid Build Coastguard Worker        log("No target files found.")
70*d57664e9SAndroid Build Coastguard Worker        return 0
71*d57664e9SAndroid Build Coastguard Worker
72*d57664e9SAndroid Build Coastguard Worker    # Process the files.
73*d57664e9SAndroid Build Coastguard Worker    with fileinput.input(files=(files), inplace = True, backup = '.bak') as f:
74*d57664e9SAndroid Build Coastguard Worker        import_seen = False
75*d57664e9SAndroid Build Coastguard Worker        carry_over = ''
76*d57664e9SAndroid Build Coastguard Worker        class_body_started = False
77*d57664e9SAndroid Build Coastguard Worker        class_seen = False
78*d57664e9SAndroid Build Coastguard Worker
79*d57664e9SAndroid Build Coastguard Worker        def on_file_start():
80*d57664e9SAndroid Build Coastguard Worker            nonlocal import_seen, carry_over, class_body_started, class_seen
81*d57664e9SAndroid Build Coastguard Worker            import_seen = False
82*d57664e9SAndroid Build Coastguard Worker            carry_over = ''
83*d57664e9SAndroid Build Coastguard Worker            class_body_started = False
84*d57664e9SAndroid Build Coastguard Worker            class_seen = False
85*d57664e9SAndroid Build Coastguard Worker
86*d57664e9SAndroid Build Coastguard Worker        for line in f:
87*d57664e9SAndroid Build Coastguard Worker            if (fileinput.filelineno() == 1):
88*d57664e9SAndroid Build Coastguard Worker                log(f"Processing: {fileinput.filename()}")
89*d57664e9SAndroid Build Coastguard Worker                on_file_start()
90*d57664e9SAndroid Build Coastguard Worker
91*d57664e9SAndroid Build Coastguard Worker            line = line.rstrip('\n')
92*d57664e9SAndroid Build Coastguard Worker
93*d57664e9SAndroid Build Coastguard Worker            # Carry over a certain line to the next line.
94*d57664e9SAndroid Build Coastguard Worker            if re.search(r'''@Override\b''', line):
95*d57664e9SAndroid Build Coastguard Worker                carry_over = carry_over + line + '\n'
96*d57664e9SAndroid Build Coastguard Worker                continue
97*d57664e9SAndroid Build Coastguard Worker
98*d57664e9SAndroid Build Coastguard Worker            if carry_over:
99*d57664e9SAndroid Build Coastguard Worker                line = carry_over + line
100*d57664e9SAndroid Build Coastguard Worker                carry_over = ''
101*d57664e9SAndroid Build Coastguard Worker
102*d57664e9SAndroid Build Coastguard Worker
103*d57664e9SAndroid Build Coastguard Worker            # Remove the base class from the class definition.
104*d57664e9SAndroid Build Coastguard Worker            line = re_extends.sub('', line)
105*d57664e9SAndroid Build Coastguard Worker
106*d57664e9SAndroid Build Coastguard Worker            # Add a @RunWith.
107*d57664e9SAndroid Build Coastguard Worker            if not class_seen and re.search(r'''\b class \b''', line, re.X):
108*d57664e9SAndroid Build Coastguard Worker                class_seen = True
109*d57664e9SAndroid Build Coastguard Worker                print("@RunWith(AndroidJUnit4.class)")
110*d57664e9SAndroid Build Coastguard Worker
111*d57664e9SAndroid Build Coastguard Worker
112*d57664e9SAndroid Build Coastguard Worker            # Inject extra imports.
113*d57664e9SAndroid Build Coastguard Worker            if not import_seen and re.search(r'''^import\b''', line):
114*d57664e9SAndroid Build Coastguard Worker                import_seen = True
115*d57664e9SAndroid Build Coastguard Worker                print("""\
116*d57664e9SAndroid Build Coastguard Workerimport android.content.Context;
117*d57664e9SAndroid Build Coastguard Workerimport androidx.test.platform.app.InstrumentationRegistry;
118*d57664e9SAndroid Build Coastguard Worker
119*d57664e9SAndroid Build Coastguard Workerimport static junit.framework.TestCase.assertEquals;
120*d57664e9SAndroid Build Coastguard Workerimport static junit.framework.TestCase.assertSame;
121*d57664e9SAndroid Build Coastguard Workerimport static junit.framework.TestCase.assertNotSame;
122*d57664e9SAndroid Build Coastguard Workerimport static junit.framework.TestCase.assertTrue;
123*d57664e9SAndroid Build Coastguard Workerimport static junit.framework.TestCase.assertFalse;
124*d57664e9SAndroid Build Coastguard Workerimport static junit.framework.TestCase.assertNull;
125*d57664e9SAndroid Build Coastguard Workerimport static junit.framework.TestCase.assertNotNull;
126*d57664e9SAndroid Build Coastguard Workerimport static junit.framework.TestCase.fail;
127*d57664e9SAndroid Build Coastguard Worker
128*d57664e9SAndroid Build Coastguard Workerimport org.junit.After;
129*d57664e9SAndroid Build Coastguard Workerimport org.junit.Before;
130*d57664e9SAndroid Build Coastguard Workerimport org.junit.runner.RunWith;
131*d57664e9SAndroid Build Coastguard Workerimport org.junit.Test;
132*d57664e9SAndroid Build Coastguard Worker
133*d57664e9SAndroid Build Coastguard Workerimport androidx.test.ext.junit.runners.AndroidJUnit4;
134*d57664e9SAndroid Build Coastguard Worker""")
135*d57664e9SAndroid Build Coastguard Worker
136*d57664e9SAndroid Build Coastguard Worker            # Add @Test to the test methods.
137*d57664e9SAndroid Build Coastguard Worker            if re.search(r'''^ \s* public \s* void \s* test''', line, re.X):
138*d57664e9SAndroid Build Coastguard Worker                print("    @Test")
139*d57664e9SAndroid Build Coastguard Worker
140*d57664e9SAndroid Build Coastguard Worker            # Convert setUp/tearDown to @Before/@After.
141*d57664e9SAndroid Build Coastguard Worker            if re.search(r''' ^\s+ ( \@Override \s+ ) ? (public|protected) \s+ void \s+ (setUp|tearDown) ''',
142*d57664e9SAndroid Build Coastguard Worker                        line, re.X):
143*d57664e9SAndroid Build Coastguard Worker                if re.search('setUp', line):
144*d57664e9SAndroid Build Coastguard Worker                    print('    @Before')
145*d57664e9SAndroid Build Coastguard Worker                else:
146*d57664e9SAndroid Build Coastguard Worker                    print('    @After')
147*d57664e9SAndroid Build Coastguard Worker
148*d57664e9SAndroid Build Coastguard Worker                line = re.sub(r''' \s* \@Override \s* \n ''', '', line, 0, re.X)
149*d57664e9SAndroid Build Coastguard Worker                line = re.sub(r'''protected''', 'public', line, 0, re.X)
150*d57664e9SAndroid Build Coastguard Worker
151*d57664e9SAndroid Build Coastguard Worker            # Remove the super setUp / tearDown call.
152*d57664e9SAndroid Build Coastguard Worker            if re.search(r''' \b super \. (setUp|tearDown) \b ''', line, re.X):
153*d57664e9SAndroid Build Coastguard Worker                continue
154*d57664e9SAndroid Build Coastguard Worker
155*d57664e9SAndroid Build Coastguard Worker            # Convert mContext to getContext().
156*d57664e9SAndroid Build Coastguard Worker            line = re.sub(r'''\b mContext \b ''', 'getContext()', line, 0, re.X)
157*d57664e9SAndroid Build Coastguard Worker
158*d57664e9SAndroid Build Coastguard Worker            # Print the processed line.
159*d57664e9SAndroid Build Coastguard Worker            print(line)
160*d57664e9SAndroid Build Coastguard Worker
161*d57664e9SAndroid Build Coastguard Worker            # Add getContext() / getTestContext() at the beginning of the class.
162*d57664e9SAndroid Build Coastguard Worker            if not class_body_started and re.search(r'''\{''', line):
163*d57664e9SAndroid Build Coastguard Worker                class_body_started = True
164*d57664e9SAndroid Build Coastguard Worker                print("""\
165*d57664e9SAndroid Build Coastguard Worker    private Context getContext() {
166*d57664e9SAndroid Build Coastguard Worker        return InstrumentationRegistry.getInstrumentation().getTargetContext();
167*d57664e9SAndroid Build Coastguard Worker    }
168*d57664e9SAndroid Build Coastguard Worker
169*d57664e9SAndroid Build Coastguard Worker    private Context getTestContext() {
170*d57664e9SAndroid Build Coastguard Worker        return InstrumentationRegistry.getInstrumentation().getContext();
171*d57664e9SAndroid Build Coastguard Worker    }
172*d57664e9SAndroid Build Coastguard Worker""")
173*d57664e9SAndroid Build Coastguard Worker
174*d57664e9SAndroid Build Coastguard Worker
175*d57664e9SAndroid Build Coastguard Worker    # Run diff
176*d57664e9SAndroid Build Coastguard Worker    for file in files:
177*d57664e9SAndroid Build Coastguard Worker        subprocess.call(["diff", "-u", "--color=auto", f"{file}.bak", file])
178*d57664e9SAndroid Build Coastguard Worker
179*d57664e9SAndroid Build Coastguard Worker    log(f'{len(files)} file(s) converted.')
180*d57664e9SAndroid Build Coastguard Worker
181*d57664e9SAndroid Build Coastguard Worker    return 0
182*d57664e9SAndroid Build Coastguard Worker
183*d57664e9SAndroid Build Coastguard Workerif __name__ == '__main__':
184*d57664e9SAndroid Build Coastguard Worker    sys.exit(main(sys.argv[1:]))
185