1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.io.File;
25import java.io.IOException;
26import java.nio.file.Files;
27import java.nio.file.Path;
28import java.util.*;
29import java.util.function.Function;
30import java.util.function.Supplier;
31import java.util.regex.*;
32import java.util.stream.Collectors;
33import java.util.stream.Stream;
34
35import com.sun.tools.classfile.*;
36
37/**
38 * The tests work as follows. Firstly, it looks through the test cases
39 * and extracts the appropriate compiled classes. Each test case contains
40 * a set of expected classes, methods and fields. Those class members must not have
41 * the Synthetic attribute, while other found classes, methods and fields must have
42 * the Synthetic attribute if they are not in the set of expected class members.
43 *
44 * Each test executes SyntheticTestDriver specifying the name of test cases and
45 * the number of expected synthetic classes. Each test class is annotated by
46 * annotations which contains non-synthetic class members.
47 *
48 * See the appropriate class for more information about a test case.
49 */
50public class SyntheticTestDriver extends TestResult {
51
52    private static final String ACC_SYNTHETIC = "ACC_SYNTHETIC";
53
54    private final String testCaseName;
55    private final Map<String, ClassFile> classes;
56    private final Map<String, ExpectedClass> expectedClasses;
57
58    public static void main(String[] args)
59            throws TestFailedException, ConstantPoolException, IOException, ClassNotFoundException {
60        if (args.length != 1 && args.length != 2) {
61            throw new IllegalArgumentException("Usage: SyntheticTestDriver <class-name> [<number-of-synthetic-classes>]");
62        }
63        int numberOfSyntheticClasses = args.length == 1 ? 0 : Integer.parseInt(args[1]);
64        new SyntheticTestDriver(args[0]).test(numberOfSyntheticClasses);
65    }
66
67    public SyntheticTestDriver(String testCaseName) throws IOException, ConstantPoolException, ClassNotFoundException {
68        Class<?> clazz = Class.forName(testCaseName);
69        this.testCaseName = testCaseName;
70        this.expectedClasses = Stream.of(clazz.getAnnotationsByType(ExpectedClass.class))
71                .collect(Collectors.toMap(ExpectedClass::className, Function.identity()));
72        this.classes = new HashMap<>();
73        Path classDir = getClassDir().toPath();
74        Pattern filePattern = Pattern.compile(Pattern.quote(testCaseName.replace('.', File.separatorChar)) + ".*\\.class");
75        List<Path> paths = Files.walk(classDir)
76                .map(p -> classDir.relativize(p.toAbsolutePath()))
77                .filter(p -> filePattern.matcher(p.toString()).matches())
78                .collect(Collectors.toList());
79        for (Path path : paths) {
80            String className = path.toString().replace(".class", "").replace(File.separatorChar, '.');
81            classes.put(className, readClassFile(classDir.resolve(path).toFile()));
82        }
83        if (classes.isEmpty()) {
84            throw new RuntimeException("Classes have not been found.");
85        }
86        boolean success = classes.entrySet().stream()
87                .allMatch(e -> e.getKey().startsWith(testCaseName));
88        if (!success) {
89            classes.forEach((className, $) -> printf("Found class: %s\n", className));
90            throw new RuntimeException("Found classes are not from the test case : " + testCaseName);
91        }
92    }
93
94    private String getMethodName(ClassFile classFile, Method method)
95            throws ConstantPoolException, Descriptor.InvalidDescriptor {
96        String methodName = method.getName(classFile.constant_pool);
97        String parameters = method.descriptor.getParameterTypes(classFile.constant_pool);
98        return methodName + parameters;
99    }
100
101    public void test(int expectedNumberOfSyntheticClasses) throws TestFailedException {
102        try {
103            addTestCase(testCaseName);
104            Set<String> foundClasses = new HashSet<>();
105
106            int numberOfSyntheticClasses = 0;
107            for (Map.Entry<String, ClassFile> entry : classes.entrySet()) {
108                String className = entry.getKey();
109                ClassFile classFile = entry.getValue();
110                foundClasses.add(className);
111                if (testAttribute(
112                        classFile,
113                        () -> (Synthetic_attribute) classFile.getAttribute(Attribute.Synthetic),
114                        classFile.access_flags::getClassFlags,
115                        expectedClasses.keySet(),
116                        className,
117                        "Testing class " + className)) {
118                    ++numberOfSyntheticClasses;
119                }
120                ExpectedClass expectedClass = expectedClasses.get(className);
121                Set<String> expectedMethods = expectedClass != null
122                        ? toSet(expectedClass.expectedMethods())
123                        : new HashSet<>();
124                int numberOfSyntheticMethods = 0;
125                Set<String> foundMethods = new HashSet<>();
126                for (Method method : classFile.methods) {
127                    String methodName = getMethodName(classFile, method);
128                    foundMethods.add(methodName);
129                    if (testAttribute(
130                            classFile,
131                            () -> (Synthetic_attribute) method.attributes.get(Attribute.Synthetic),
132                            method.access_flags::getMethodFlags,
133                            expectedMethods,
134                            methodName,
135                            "Testing method " + methodName + " in class "
136                                    + className)) {
137                        ++numberOfSyntheticMethods;
138                    }
139                }
140                checkContains(foundMethods, expectedMethods,
141                        "Checking that all methods of class " + className
142                                + " without Synthetic attribute have been found");
143                checkEquals(numberOfSyntheticMethods,
144                        expectedClass == null ? 0 : expectedClass.expectedNumberOfSyntheticMethods(),
145                        "Checking number of synthetic methods in class: " + className);
146
147                Set<String> expectedFields = expectedClass != null
148                        ? toSet(expectedClass.expectedFields())
149                        : new HashSet<>();
150                int numberOfSyntheticFields = 0;
151                Set<String> foundFields = new HashSet<>();
152                for (Field field : classFile.fields) {
153                    String fieldName = field.getName(classFile.constant_pool);
154                    foundFields.add(fieldName);
155                    if (testAttribute(
156                            classFile,
157                            () -> (Synthetic_attribute) field.attributes.get(Attribute.Synthetic),
158                            field.access_flags::getFieldFlags,
159                            expectedFields,
160                            fieldName,
161                            "Testing field " + fieldName + " in class "
162                                    + className)) {
163                        ++numberOfSyntheticFields;
164                    }
165                }
166                checkContains(foundFields, expectedFields,
167                        "Checking that all fields of class " + className
168                                + " without Synthetic attribute have been found");
169                checkEquals(numberOfSyntheticFields,
170                        expectedClass == null ? 0 : expectedClass.expectedNumberOfSyntheticFields(),
171                        "Checking number of synthetic fields in class: " + className);
172            }
173            checkContains(foundClasses, expectedClasses.keySet(),
174                    "Checking that all classes have been found");
175            checkEquals(numberOfSyntheticClasses, expectedNumberOfSyntheticClasses,
176                    "Checking number of synthetic classes");
177        } catch (Exception e) {
178            addFailure(e);
179        } finally {
180            checkStatus();
181        }
182    }
183
184    private boolean testAttribute(ClassFile classFile,
185                               Supplier<Synthetic_attribute> getSyntheticAttribute,
186                               Supplier<Set<String>> getAccessFlags,
187                               Set<String> expectedMembers, String memberName,
188                               String info) throws ConstantPoolException {
189        echo(info);
190        String className = classFile.getName();
191        Synthetic_attribute attr = getSyntheticAttribute.get();
192        Set<String> flags = getAccessFlags.get();
193        if (expectedMembers.contains(memberName)) {
194            checkNull(attr, "Member must not have synthetic attribute : "
195                    + memberName);
196            checkFalse(flags.contains(ACC_SYNTHETIC),
197                    "Member must not have synthetic flag : " + memberName
198                            + " in class : " + className);
199            return false;
200        } else {
201            return checkNull(attr, "Synthetic attribute should not be generated")
202                    && checkTrue(flags.contains(ACC_SYNTHETIC), "Member must have synthetic flag : "
203                                + memberName + " in class : " + className);
204        }
205    }
206
207    private Set<String> toSet(String[] strings) {
208        HashSet<String> set = new HashSet<>();
209        Collections.addAll(set, strings);
210        return set;
211    }
212}
213