ModuleTestBase.java revision 3822:d8766c39123a
1/*
2 * Copyright (c) 2015, 2016, 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.Arrays;
39import java.util.Collections;
40import java.util.HashMap;
41import java.util.LinkedHashMap;
42import java.util.List;
43import java.util.Map;
44import java.util.regex.Pattern;
45import java.util.stream.Collectors;
46
47import toolbox.JavacTask;
48import toolbox.Task;
49import toolbox.ToolBox;
50
51public class ModuleTestBase {
52    protected final ToolBox tb = new ToolBox();
53    private final TestResult tr = new TestResult();
54
55
56    protected void run() throws Exception {
57        boolean noTests = true;
58        for (Method method : this.getClass().getMethods()) {
59            if (method.isAnnotationPresent(Test.class)) {
60                noTests = false;
61                try {
62                    tr.addTestCase(method.getName());
63                    method.invoke(this, Paths.get(method.getName()));
64                } catch (Throwable th) {
65                    tr.addFailure(th);
66                }
67            }
68        }
69        if (noTests) throw new AssertionError("Tests are not found.");
70        tr.checkStatus();
71    }
72
73    protected void testModuleAttribute(Path modulePath, ModuleDescriptor moduleDescriptor) throws Exception {
74        ClassFile classFile = ClassFile.read(modulePath.resolve("module-info.class"));
75        Module_attribute moduleAttribute = (Module_attribute) classFile.getAttribute("Module");
76        ConstantPool constantPool = classFile.constant_pool;
77
78        testRequires(moduleDescriptor, moduleAttribute, constantPool);
79        testExports(moduleDescriptor, moduleAttribute, constantPool);
80        testProvides(moduleDescriptor, moduleAttribute, constantPool);
81        testUses(moduleDescriptor, moduleAttribute, constantPool);
82    }
83
84    private void testRequires(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
85        tr.checkEquals(module.requires_count, moduleDescriptor.requires.size(), "Wrong amount of requires.");
86
87        List<Pair<String, Integer>> actualRequires = new ArrayList<>();
88        for (Module_attribute.RequiresEntry require : module.requires) {
89            actualRequires.add(Pair.of(
90                    require.getRequires(constantPool).replace('/', '.'),
91                    require.requires_flags));
92        }
93        tr.checkContains(actualRequires, moduleDescriptor.requires, "Lists of requires don't match");
94    }
95
96    private void testExports(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
97        tr.checkEquals(module.exports_count, moduleDescriptor.exports.size(), "Wrong amount of exports.");
98        for (Module_attribute.ExportsEntry export : module.exports) {
99            String pkg = constantPool.getPackageInfo(export.exports_index).getName();
100            if (tr.checkTrue(moduleDescriptor.exports.containsKey(pkg), "Unexpected export " + pkg)) {
101                Export expectedExport = moduleDescriptor.exports.get(pkg);
102                tr.checkEquals(expectedExport.mask, export.exports_flags, "Wrong export flags");
103                List<String> expectedTo = expectedExport.to;
104                tr.checkEquals(export.exports_to_count, expectedTo.size(), "Wrong amount of exports to");
105                List<String> actualTo = new ArrayList<>();
106                for (int toIdx : export.exports_to_index) {
107                    actualTo.add(constantPool.getModuleInfo(toIdx).getName().replace('/', '.'));
108                }
109                tr.checkContains(actualTo, expectedTo, "Lists of \"exports to\" don't match.");
110            }
111        }
112    }
113
114    private void testUses(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
115        tr.checkEquals(module.uses_count, moduleDescriptor.uses.size(), "Wrong amount of uses.");
116        List<String> actualUses = new ArrayList<>();
117        for (int usesIdx : module.uses_index) {
118            String uses = constantPool.getClassInfo(usesIdx).getBaseName().replace('/', '.');
119            actualUses.add(uses);
120        }
121        tr.checkContains(actualUses, moduleDescriptor.uses, "Lists of uses don't match");
122    }
123
124    private void testProvides(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
125        int moduleProvidesCount = Arrays.asList(module.provides).stream()
126                .mapToInt(e -> e.with_index.length)
127                .sum();
128        int moduleDescriptorProvidesCount = moduleDescriptor.provides.values().stream()
129                .mapToInt(impls -> impls.size())
130                .sum();
131        tr.checkEquals(moduleProvidesCount, moduleDescriptorProvidesCount, "Wrong amount of provides.");
132        Map<String, List<String>> actualProvides = new HashMap<>();
133        for (Module_attribute.ProvidesEntry provide : module.provides) {
134            String provides = constantPool.getClassInfo(provide.provides_index).getBaseName().replace('/', '.');
135            List<String> impls = new ArrayList<>();
136            for (int i = 0; i < provide.with_count; i++) {
137                String with = constantPool.getClassInfo(provide.with_index[i]).getBaseName().replace('/', '.');
138                impls.add(with);
139            }
140            actualProvides.put(provides, impls);
141        }
142        tr.checkContains(actualProvides.entrySet(), moduleDescriptor.provides.entrySet(), "Lists of provides don't match");
143    }
144
145    protected void compile(Path base, String... options) throws IOException {
146        new JavacTask(tb)
147                .options(options)
148                .files(findJavaFiles(base))
149                .run(Task.Expect.SUCCESS)
150                .writeAll();
151    }
152
153    private static Path[] findJavaFiles(Path src) throws IOException {
154        return Files.find(src, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith(".java"))
155                .toArray(Path[]::new);
156    }
157
158    @Retention(RetentionPolicy.RUNTIME)
159    @interface Test {
160    }
161
162    interface Mask {
163        int getMask();
164    }
165
166    public enum RequiresFlag implements Mask {
167        TRANSITIVE("transitive", Module_attribute.ACC_TRANSITIVE),
168        STATIC("static", Module_attribute.ACC_STATIC_PHASE);
169
170        private final String token;
171        private final int mask;
172
173        RequiresFlag(String token, int mask) {
174            this.token = token;
175            this.mask = mask;
176        }
177
178        @Override
179        public int getMask() {
180            return mask;
181        }
182    }
183
184    public enum ExportFlag implements Mask {
185        SYNTHETIC("", Module_attribute.ACC_SYNTHETIC);
186
187        private final String token;
188        private final int mask;
189
190        ExportFlag(String token, int mask) {
191            this.token = token;
192            this.mask = mask;
193        }
194
195        @Override
196        public int getMask() {
197            return mask;
198        }
199    }
200
201    private class Export {
202        String pkg;
203        int mask;
204        List<String> to = new ArrayList<>();
205
206        public Export(String pkg, int mask) {
207            this.pkg = pkg;
208            this.mask = mask;
209        }
210    }
211
212    protected class ModuleDescriptor {
213
214        private final String name;
215        //pair is name of module and flag(public,mandated,synthetic)
216        private final List<Pair<String, Integer>> requires = new ArrayList<>();
217
218        {
219            requires.add(new Pair<>("java.base", Module_attribute.ACC_MANDATED));
220        }
221
222        private final Map<String, Export> exports = new HashMap<>();
223
224        //List of service and implementation
225        private final Map<String, List<String>> provides = new LinkedHashMap<>();
226        private final List<String> uses = new ArrayList<>();
227
228        private static final String LINE_END = ";\n";
229
230        StringBuilder content = new StringBuilder("module ");
231
232        public ModuleDescriptor(String moduleName) {
233            this.name = moduleName;
234            content.append(name).append('{').append('\n');
235        }
236
237        public ModuleDescriptor requires(String module) {
238            this.requires.add(Pair.of(module, 0));
239            content.append("    requires ").append(module).append(LINE_END);
240
241            return this;
242        }
243
244        public ModuleDescriptor requires(String module, RequiresFlag... flags) {
245            this.requires.add(new Pair<>(module, computeMask(flags)));
246
247            content.append("    requires ");
248            for (RequiresFlag flag : flags) {
249                content.append(flag.token).append(" ");
250            }
251            content.append(module).append(LINE_END);
252
253            return this;
254        }
255
256        public ModuleDescriptor exports(String pkg, ExportFlag... flags) {
257            this.exports.putIfAbsent(pkg, new Export(pkg, computeMask(flags)));
258            content.append("    exports ");
259            for (ExportFlag flag : flags) {
260                content.append(flag.token).append(" ");
261            }
262            content.append(pkg).append(LINE_END);
263            return this;
264        }
265
266        public ModuleDescriptor exportsTo(String pkg, String to, ExportFlag... flags) {
267            List<String> tos = Pattern.compile(",")
268                    .splitAsStream(to)
269                    .map(String::trim)
270                    .collect(Collectors.toList());
271            this.exports.computeIfAbsent(pkg, k -> new Export(pkg, computeMask(flags)))
272                    .to.addAll(tos);
273
274            content.append("    exports ");
275            for (ExportFlag flag : flags) {
276                content.append(flag.token).append(" ");
277            }
278            content.append(pkg).append(" to ").append(to).append(LINE_END);
279            return this;
280        }
281
282        public ModuleDescriptor provides(String provides, String... with) {
283            this.provides.put(provides, Arrays.asList(with));
284            content.append("    provides ")
285                    .append(provides)
286                    .append(" with ")
287                    .append(String.join(",", with))
288                    .append(LINE_END);
289            return this;
290        }
291
292        public ModuleDescriptor uses(String... uses) {
293            Collections.addAll(this.uses, uses);
294            for (String use : uses) {
295                content.append("    uses ").append(use).append(LINE_END);
296            }
297            return this;
298        }
299
300        public ModuleDescriptor write(Path path) throws IOException {
301            String src = content.append('}').toString();
302
303            tb.createDirectories(path);
304            tb.writeJavaFiles(path, src);
305            return this;
306        }
307
308        private int computeMask(Mask[] masks) {
309            return Arrays.stream(masks)
310                    .map(Mask::getMask)
311                    .reduce((a, b) -> a | b)
312                    .orElseGet(() -> 0);
313        }
314    }
315}
316