ModuleTestBase.java revision 3294:9adfb22ff08f
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 com.sun.tools.classfile.ClassFile;
25import com.sun.tools.classfile.ConstantPool;
26import com.sun.tools.classfile.ConstantPoolException;
27import com.sun.tools.classfile.Module_attribute;
28import com.sun.tools.javac.util.Pair;
29
30import java.io.IOException;
31import java.lang.annotation.Retention;
32import java.lang.annotation.RetentionPolicy;
33import java.lang.reflect.Method;
34import java.nio.file.Files;
35import java.nio.file.Path;
36import java.nio.file.Paths;
37import java.util.ArrayList;
38import java.util.Collections;
39import java.util.HashMap;
40import java.util.List;
41import java.util.Map;
42import java.util.regex.Pattern;
43import java.util.stream.Collectors;
44
45public class ModuleTestBase {
46    protected final ToolBox tb = new ToolBox();
47    private final TestResult tr = new TestResult();
48
49
50    protected void run() throws Exception {
51        boolean noTests = true;
52        for (Method method : this.getClass().getMethods()) {
53            if (method.isAnnotationPresent(Test.class)) {
54                noTests = false;
55                try {
56                    tr.addTestCase(method.getName());
57                    method.invoke(this, Paths.get(method.getName()));
58                } catch (Throwable th) {
59                    tr.addFailure(th);
60                }
61            }
62        }
63        if (noTests) throw new AssertionError("Tests are not found.");
64        tr.checkStatus();
65    }
66
67    protected void testModuleAttribute(Path modulePath, ModuleDescriptor moduleDescriptor) throws Exception {
68        ClassFile classFile = ClassFile.read(modulePath.resolve("module-info.class"));
69        Module_attribute moduleAttribute = (Module_attribute) classFile.getAttribute("Module");
70        ConstantPool constantPool = classFile.constant_pool;
71
72        testRequires(moduleDescriptor, moduleAttribute, constantPool);
73        testExports(moduleDescriptor, moduleAttribute, constantPool);
74        testProvides(moduleDescriptor, moduleAttribute, constantPool);
75        testUses(moduleDescriptor, moduleAttribute, constantPool);
76    }
77
78    private void testRequires(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
79        tr.checkEquals(module.requires_count, moduleDescriptor.requires.size(), "Wrong amount of requires.");
80
81        List<Pair<String, Integer>> actualRequires = new ArrayList<>();
82        for (Module_attribute.RequiresEntry require : module.requires) {
83            actualRequires.add(Pair.of(
84                    require.getRequires(constantPool), require.requires_flags));
85        }
86        tr.checkContains(actualRequires, moduleDescriptor.requires, "Lists of requires don't match");
87    }
88
89    private void testExports(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPool.InvalidIndex, ConstantPool.UnexpectedEntry {
90        tr.checkEquals(module.exports_count, moduleDescriptor.exports.size(), "Wrong amount of exports.");
91        for (Module_attribute.ExportsEntry export : module.exports) {
92            String pkg = constantPool.getUTF8Value(export.exports_index);
93            if (tr.checkTrue(moduleDescriptor.exports.containsKey(pkg), "Unexpected export " + pkg)) {
94                List<String> expectedTo = moduleDescriptor.exports.get(pkg);
95                tr.checkEquals(export.exports_to_count, expectedTo.size(), "Wrong amount of exports to");
96                List<String> actualTo = new ArrayList<>();
97                for (int toIdx : export.exports_to_index) {
98                    actualTo.add(constantPool.getUTF8Value(toIdx));
99                }
100                tr.checkContains(actualTo, expectedTo, "Lists of \"exports to\" don't match.");
101            }
102        }
103    }
104
105    private void testUses(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
106        tr.checkEquals(module.uses_count, moduleDescriptor.uses.size(), "Wrong amount of uses.");
107        List<String> actualUses = new ArrayList<>();
108        for (int usesIdx : module.uses_index) {
109            String uses = constantPool.getClassInfo(usesIdx).getBaseName().replace('/', '.');
110            actualUses.add(uses);
111        }
112        tr.checkContains(actualUses, moduleDescriptor.uses, "Lists of uses don't match");
113    }
114
115    private void testProvides(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
116        tr.checkEquals(module.provides_count, moduleDescriptor.provides.size(), "Wrong amount of provides.");
117        List<Pair<String, String>> actualProvides = new ArrayList<>();
118        for (Module_attribute.ProvidesEntry provide : module.provides) {
119            String provides = constantPool.getClassInfo(provide.provides_index).getBaseName().replace('/', '.');
120            String with = constantPool.getClassInfo(provide.with_index).getBaseName().replace('/', '.');
121            actualProvides.add(Pair.of(provides, with));
122        }
123        tr.checkContains(actualProvides, moduleDescriptor.provides, "Lists of provides don't match");
124    }
125
126    protected void compile(Path base) throws IOException {
127        tb.new JavacTask()
128                .files(findJavaFiles(base))
129                .run(ToolBox.Expect.SUCCESS)
130                .writeAll();
131    }
132
133    private static Path[] findJavaFiles(Path src) throws IOException {
134        return Files.find(src, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith(".java"))
135                .toArray(Path[]::new);
136    }
137
138    @Retention(RetentionPolicy.RUNTIME)
139    @interface Test {
140    }
141
142    class ModuleDescriptor {
143
144        private final String name;
145        //pair is name of module and flag(public,mandated,synthetic)
146        private final List<Pair<String, Integer>> requires = new ArrayList<>();
147
148        {
149            requires.add(new Pair<>("java.base", Module_attribute.ACC_MANDATED));
150        }
151
152        private final Map<String, List<String>> exports = new HashMap<>();
153
154        //List of service and implementation
155        private final List<Pair<String, String>> provides = new ArrayList<>();
156        private final List<String> uses = new ArrayList<>();
157
158        private static final String LINE_END = ";\n";
159
160        StringBuilder content = new StringBuilder("module ");
161
162        public ModuleDescriptor(String moduleName) {
163            this.name = moduleName;
164            content.append(name).append('{').append('\n');
165        }
166
167        public ModuleDescriptor requires(String... requires) {
168            for (String require : requires) {
169                this.requires.add(Pair.of(require, 0));
170                content.append("    requires ").append(require).append(LINE_END);
171            }
172            return this;
173        }
174
175        public ModuleDescriptor requiresPublic(String... requiresPublic) {
176            for (String require : requiresPublic) {
177                this.requires.add(new Pair<>(require, Module_attribute.ACC_PUBLIC));
178                content.append("    requires public ").append(require).append(LINE_END);
179            }
180            return this;
181        }
182
183        public ModuleDescriptor exports(String... exports) {
184            for (String export : exports) {
185                this.exports.putIfAbsent(export, new ArrayList<>());
186                content.append("    exports ").append(export).append(LINE_END);
187            }
188            return this;
189        }
190
191        public ModuleDescriptor exportsTo(String exports, String to) {
192            List<String> tos = Pattern.compile(",")
193                    .splitAsStream(to)
194                    .map(String::trim)
195                    .collect(Collectors.toList());
196            this.exports.computeIfAbsent(exports, k -> new ArrayList<>()).addAll(tos);
197            content.append("    exports ").append(exports).append(" to ").append(to).append(LINE_END);
198            return this;
199        }
200
201        public ModuleDescriptor provides(String provides, String with) {
202            this.provides.add(Pair.of(provides, with));
203            content.append("    provides ").append(provides).append(" with ").append(with).append(LINE_END);
204            return this;
205        }
206
207        public ModuleDescriptor uses(String... uses) {
208            Collections.addAll(this.uses, uses);
209            for (String use : uses) {
210                content.append("    uses ").append(use).append(LINE_END);
211            }
212            return this;
213        }
214
215        public ModuleDescriptor write(Path path) throws IOException {
216            String src = content.append('}').toString();
217
218            tb.createDirectories(path);
219            tb.writeJavaFiles(path, src);
220            return this;
221        }
222    }
223}
224