1#!/usr/bin/python3
2#
3# Copyright (C) 2015 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Generate java test files for test 964.
19"""
20
21import os
22import sys
23from pathlib import Path
24
25BUILD_TOP = os.getenv("ANDROID_BUILD_TOP")
26if BUILD_TOP is None:
27  print("ANDROID_BUILD_TOP not set. Please run build/envsetup.sh", file=sys.stderr)
28  sys.exit(1)
29
30# Allow us to import utils and mixins.
31sys.path.append(str(Path(BUILD_TOP)/"art"/"test"/"utils"/"python"))
32
33from testgen.utils import get_copyright, subtree_sizes, gensym, filter_blanks
34import testgen.mixins as mixins
35
36from functools import total_ordering
37import itertools
38import string
39
40# The max depth the tree can have.
41MAX_IFACE_DEPTH = 3
42
43class MainClass(mixins.DumpMixin, mixins.Named, mixins.JavaFileMixin):
44  """
45  A Main.java file containing the Main class and the main function. It will run
46  all the test functions we have.
47  """
48
49  MAIN_CLASS_TEMPLATE = """{copyright}
50class Main {{
51{test_groups}
52{main_func}
53}}
54"""
55
56  MAIN_FUNCTION_TEMPLATE = """
57  public static void main(String[] args) {{
58    {test_group_invoke}
59  }}
60"""
61
62  TEST_GROUP_INVOKE_TEMPLATE = """
63    {test_name}();
64"""
65
66  def __init__(self):
67    """
68    Initialize this MainClass. We start out with no tests.
69    """
70    self.tests = set()
71
72  def add_test(self, ty):
73    """
74    Add a test for the concrete type 'ty'
75    """
76    self.tests.add(Func(ty))
77
78  def get_expected(self):
79    """
80    Get the expected output of this test.
81    """
82    all_tests = sorted(self.tests)
83    return filter_blanks("\n".join(a.get_expected() for a in all_tests))
84
85  def get_name(self):
86    """
87    Gets the name of this class
88    """
89    return "Main"
90
91  def __str__(self):
92    """
93    Print the java code for this test.
94    """
95    all_tests = sorted(self.tests)
96    test_invoke = ""
97    test_groups = ""
98    for t in all_tests:
99      test_groups += str(t)
100    for t in all_tests:
101      test_invoke += self.TEST_GROUP_INVOKE_TEMPLATE.format(test_name=t.get_name())
102    main_func = self.MAIN_FUNCTION_TEMPLATE.format(test_group_invoke=test_invoke)
103
104    return self.MAIN_CLASS_TEMPLATE.format(copyright = get_copyright('java'),
105                                           test_groups = test_groups,
106                                           main_func = main_func)
107
108class Func(mixins.Named, mixins.NameComparableMixin):
109  """
110  A function that tests the functionality of a concrete type. Should only be
111  constructed by MainClass.add_test.
112  """
113
114  TEST_FUNCTION_TEMPLATE = """
115  public static void {fname}() {{
116    try {{
117      System.out.println("About to initialize {tree}");
118      {farg} v = new {farg}();
119      System.out.println("Initialized {tree}");
120      v.touchAll();
121      System.out.println("All of {tree} hierarchy initialized");
122      return;
123    }} catch (Error e) {{
124      e.printStackTrace(System.out);
125      return;
126    }}
127  }}
128"""
129
130  OUTPUT_FORMAT = """
131About to initialize {tree}
132{initialize_output}
133Initialized {tree}
134{touch_output}
135All of {tree} hierarchy initialized
136""".strip()
137
138  def __init__(self, farg):
139    """
140    Initialize a test function for the given argument
141    """
142    self.farg = farg
143
144  def __str__(self):
145    """
146    Print the java code for this test function.
147    """
148    return self.TEST_FUNCTION_TEMPLATE.format(fname=self.get_name(),
149                                              farg=self.farg.get_name(),
150                                              tree = self.farg.get_tree())
151
152  def get_name(self):
153    """
154    Gets the name of this test function
155    """
156    return "TEST_FUNC_{}".format(self.farg.get_name())
157
158  def get_expected(self):
159    """
160    Get the expected output of this function.
161    """
162    return self.OUTPUT_FORMAT.format(
163        tree = self.farg.get_tree(),
164        initialize_output = self.farg.get_initialize_output().strip(),
165        touch_output = self.farg.get_touch_output().strip())
166
167class TestClass(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.JavaFileMixin):
168  """
169  A class that will be instantiated to test interface initialization order.
170  """
171
172  TEST_CLASS_TEMPLATE = """{copyright}
173public class {class_name} implements {ifaces} {{
174  public void marker() {{
175    return;
176  }}
177
178  public void touchAll() {{
179{touch_calls}
180  }}
181}}
182"""
183
184  TOUCH_CALL_TEMPLATE = """
185    System.out.println("{class_name} touching {iface_name}");
186    {iface_name}.field.touch();
187"""
188
189  TOUCH_OUTPUT_TEMPLATE = """
190{class_name} touching {iface_name}
191{touch_output}
192""".strip()
193
194  def __init__(self, ifaces):
195    """
196    Initialize this test class which implements the given interfaces
197    """
198    self.ifaces = ifaces
199    self.class_name = "CLASS_"+gensym()
200
201  def get_name(self):
202    """
203    Gets the name of this interface
204    """
205    return self.class_name
206
207  def get_tree(self):
208    """
209    Print out a representation of the type tree of this class
210    """
211    return "[{fname} {iftree}]".format(fname = self.get_name(), iftree = print_tree(self.ifaces))
212
213  def get_initialize_output(self):
214    return "\n".join(map(lambda i: i.get_initialize_output().strip(), dump_tree(self.ifaces)))
215
216  def get_touch_output(self):
217    return "\n".join(map(lambda a: self.TOUCH_OUTPUT_TEMPLATE.format(
218                                      class_name = self.class_name,
219                                      iface_name = a.get_name(),
220                                      touch_output = a.get_touch_output()).strip(),
221                         self.get_all_interfaces()))
222
223  def get_all_interfaces(self):
224    """
225    Returns a set of all interfaces this class transitively implements
226    """
227    return sorted(set(dump_tree(self.ifaces)))
228
229  def __str__(self):
230    """
231    Print the java code for this class.
232    """
233    j_ifaces = ', '.join(map(lambda a: a.get_name(), self.ifaces))
234    touches  = '\n'.join(map(lambda a: self.TOUCH_CALL_TEMPLATE.format(class_name = self.class_name,
235                                                                       iface_name = a.get_name()),
236                             self.get_all_interfaces()))
237    return self.TEST_CLASS_TEMPLATE.format(copyright = get_copyright('java'),
238                                           ifaces = j_ifaces,
239                                           class_name = self.class_name,
240                                           touch_calls = touches)
241
242class TestInterface(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, mixins.JavaFileMixin):
243  """
244  An interface that will be used to test default method resolution order.
245  """
246
247  TEST_INTERFACE_TEMPLATE = """{copyright}
248public interface {class_name} {extends} {ifaces} {{
249  public static final Displayer field = new Displayer("{tree}");
250  public void marker();
251{funcs}
252}}
253"""
254
255  DEFAULT_FUNC_TEMPLATE = """
256  public default void {class_name}_DEFAULT_FUNC() {{ return; }}
257"""
258
259  OUTPUT_TEMPLATE = "initialization of {tree}"
260
261  def __init__(self, ifaces, default):
262    """
263    Initialize interface with the given super-interfaces
264    """
265    self.ifaces = ifaces
266    self.default = default
267    end = "_DEFAULT" if default else ""
268    self.class_name = "INTERFACE_"+gensym()+end
269    self.cloned = False
270    self.initialized = False
271
272  def clone(self):
273    """
274    Clones this interface, returning a new one with the same structure but
275    different name.
276    """
277    return TestInterface(tuple(map(lambda a: a.clone(), self.ifaces)), self.default)
278
279  def get_name(self):
280    """
281    Gets the name of this interface
282    """
283    return self.class_name
284
285  def __iter__(self):
286    """
287    Performs depth-first traversal of the interface tree this interface is the
288    root of. Does not filter out repeats.
289    """
290    for i in self.ifaces:
291      yield i
292      yield from i
293
294  def get_tree(self):
295    """
296    Print out a representation of the type tree of this class
297    """
298    return "[{class_name} {iftree}]".format(class_name = self.get_name(),
299                                            iftree = print_tree(self.ifaces))
300
301  def get_initialize_output(self):
302    """
303    Returns the expected output upon the class that implements this interface being initialized.
304    """
305    if self.default and not self.initialized:
306      self.initialized = True
307      return self.OUTPUT_TEMPLATE.format(tree = self.get_tree())
308    else:
309      return ""
310
311  def get_touch_output(self):
312    """
313    Returns the expected output upon this interface being touched.
314    """
315    if not self.default and not self.initialized:
316      self.initialized = True
317      return self.OUTPUT_TEMPLATE.format(tree = self.get_tree())
318    else:
319      return ""
320
321  def __str__(self):
322    """
323    Print the java code for this interface.
324    """
325    j_ifaces = ', '.join(map(lambda a: a.get_name(), self.ifaces))
326    if self.default:
327      funcs = self.DEFAULT_FUNC_TEMPLATE.format(class_name = self.class_name)
328    else:
329      funcs = ""
330    return self.TEST_INTERFACE_TEMPLATE.format(copyright = get_copyright('java'),
331                                               extends = "extends" if len(self.ifaces) else "",
332                                               ifaces = j_ifaces,
333                                               funcs = funcs,
334                                               tree = self.get_tree(),
335                                               class_name = self.class_name)
336
337def dump_tree(ifaces):
338  """
339  Yields all the interfaces transitively implemented by the set in
340  reverse-depth-first order
341  """
342  for i in ifaces:
343    yield from dump_tree(i.ifaces)
344    yield i
345
346def print_tree(ifaces):
347  """
348  Prints the tree for the given ifaces.
349  """
350  return " ".join(i.get_tree() for i in  ifaces)
351
352def clone_all(l):
353  return tuple(a.clone() for a in l)
354
355# Cached output of subtree_sizes for speed of access.
356SUBTREES = [set(tuple(l) for l in subtree_sizes(i))
357            for i in range(MAX_IFACE_DEPTH + 1)]
358
359def create_test_classes():
360  """
361  Yield all the test classes with the different interface trees
362  """
363  for num in range(1, MAX_IFACE_DEPTH + 1):
364    for split in SUBTREES[num]:
365      ifaces = []
366      for sub in split:
367        ifaces.append(list(create_interface_trees(sub)))
368      for supers in itertools.product(*ifaces):
369        yield TestClass(clone_all(supers))
370        for i in range(len(set(dump_tree(supers)) - set(supers))):
371          ns = clone_all(supers)
372          selected = sorted(set(dump_tree(ns)) - set(ns))[i]
373          yield TestClass(tuple([selected] + list(ns)))
374
375def create_interface_trees(num):
376  """
377  Yield all the interface trees up to 'num' depth.
378  """
379  if num == 0:
380    yield TestInterface(tuple(), False)
381    yield TestInterface(tuple(), True)
382    return
383  for split in SUBTREES[num]:
384    ifaces = []
385    for sub in split:
386      ifaces.append(list(create_interface_trees(sub)))
387    for supers in itertools.product(*ifaces):
388      yield TestInterface(clone_all(supers), False)
389      yield TestInterface(clone_all(supers), True)
390      # TODO Should add on some from higher up the tree.
391
392def create_all_test_files():
393  """
394  Creates all the objects representing the files in this test. They just need to
395  be dumped.
396  """
397  mc = MainClass()
398  classes = {mc}
399  for clazz in create_test_classes():
400    classes.add(clazz)
401    for i in dump_tree(clazz.ifaces):
402      classes.add(i)
403    mc.add_test(clazz)
404  return mc, classes
405
406def main(argv):
407  java_dir = Path(argv[1])
408  if not java_dir.exists() or not java_dir.is_dir():
409    print("{} is not a valid java dir".format(java_dir), file=sys.stderr)
410    sys.exit(1)
411  expected_txt = Path(argv[2])
412  mainclass, all_files = create_all_test_files()
413  with expected_txt.open('w') as out:
414    print(mainclass.get_expected(), file=out)
415  for f in all_files:
416    f.dump(java_dir)
417
418if __name__ == '__main__':
419  main(sys.argv)
420