langtools/test/tools/javac/classfiles/attributes/Module/ModuleTestBase.java
author jjg
Wed, 10 Aug 2016 15:47:46 -0700
changeset 40308 274367a99f98
parent 36778 e04318f39f92
child 42407 f3702cff2933
permissions -rw-r--r--
8136930: Simplify use of module-system options by custom launchers 8160489: Multiple -Xpatch lines ignored by javac 8156998: javac should support new option -XinheritRuntimeEnvironment Reviewed-by: jlahoda, ksrini

/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

import com.sun.tools.classfile.ClassFile;
import com.sun.tools.classfile.ConstantPool;
import com.sun.tools.classfile.ConstantPoolException;
import com.sun.tools.classfile.Module_attribute;
import com.sun.tools.javac.util.Pair;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import toolbox.JavacTask;
import toolbox.Task;
import toolbox.ToolBox;

public class ModuleTestBase {
    protected final ToolBox tb = new ToolBox();
    private final TestResult tr = new TestResult();


    protected void run() throws Exception {
        boolean noTests = true;
        for (Method method : this.getClass().getMethods()) {
            if (method.isAnnotationPresent(Test.class)) {
                noTests = false;
                try {
                    tr.addTestCase(method.getName());
                    method.invoke(this, Paths.get(method.getName()));
                } catch (Throwable th) {
                    tr.addFailure(th);
                }
            }
        }
        if (noTests) throw new AssertionError("Tests are not found.");
        tr.checkStatus();
    }

    protected void testModuleAttribute(Path modulePath, ModuleDescriptor moduleDescriptor) throws Exception {
        ClassFile classFile = ClassFile.read(modulePath.resolve("module-info.class"));
        Module_attribute moduleAttribute = (Module_attribute) classFile.getAttribute("Module");
        ConstantPool constantPool = classFile.constant_pool;

        testRequires(moduleDescriptor, moduleAttribute, constantPool);
        testExports(moduleDescriptor, moduleAttribute, constantPool);
        testProvides(moduleDescriptor, moduleAttribute, constantPool);
        testUses(moduleDescriptor, moduleAttribute, constantPool);
    }

    private void testRequires(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
        tr.checkEquals(module.requires_count, moduleDescriptor.requires.size(), "Wrong amount of requires.");

        List<Pair<String, Integer>> actualRequires = new ArrayList<>();
        for (Module_attribute.RequiresEntry require : module.requires) {
            actualRequires.add(Pair.of(
                    require.getRequires(constantPool), require.requires_flags));
        }
        tr.checkContains(actualRequires, moduleDescriptor.requires, "Lists of requires don't match");
    }

    private void testExports(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPool.InvalidIndex, ConstantPool.UnexpectedEntry {
        tr.checkEquals(module.exports_count, moduleDescriptor.exports.size(), "Wrong amount of exports.");
        for (Module_attribute.ExportsEntry export : module.exports) {
            String pkg = constantPool.getUTF8Value(export.exports_index);
            if (tr.checkTrue(moduleDescriptor.exports.containsKey(pkg), "Unexpected export " + pkg)) {
                List<String> expectedTo = moduleDescriptor.exports.get(pkg);
                tr.checkEquals(export.exports_to_count, expectedTo.size(), "Wrong amount of exports to");
                List<String> actualTo = new ArrayList<>();
                for (int toIdx : export.exports_to_index) {
                    actualTo.add(constantPool.getUTF8Value(toIdx));
                }
                tr.checkContains(actualTo, expectedTo, "Lists of \"exports to\" don't match.");
            }
        }
    }

    private void testUses(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
        tr.checkEquals(module.uses_count, moduleDescriptor.uses.size(), "Wrong amount of uses.");
        List<String> actualUses = new ArrayList<>();
        for (int usesIdx : module.uses_index) {
            String uses = constantPool.getClassInfo(usesIdx).getBaseName().replace('/', '.');
            actualUses.add(uses);
        }
        tr.checkContains(actualUses, moduleDescriptor.uses, "Lists of uses don't match");
    }

    private void testProvides(ModuleDescriptor moduleDescriptor, Module_attribute module, ConstantPool constantPool) throws ConstantPoolException {
        tr.checkEquals(module.provides_count, moduleDescriptor.provides.size(), "Wrong amount of provides.");
        List<Pair<String, String>> actualProvides = new ArrayList<>();
        for (Module_attribute.ProvidesEntry provide : module.provides) {
            String provides = constantPool.getClassInfo(provide.provides_index).getBaseName().replace('/', '.');
            String with = constantPool.getClassInfo(provide.with_index).getBaseName().replace('/', '.');
            actualProvides.add(Pair.of(provides, with));
        }
        tr.checkContains(actualProvides, moduleDescriptor.provides, "Lists of provides don't match");
    }

    protected void compile(Path base, String... options) throws IOException {
        new JavacTask(tb)
                .options(options)
                .files(findJavaFiles(base))
                .run(Task.Expect.SUCCESS)
                .writeAll();
    }

    private static Path[] findJavaFiles(Path src) throws IOException {
        return Files.find(src, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith(".java"))
                .toArray(Path[]::new);
    }

    @Retention(RetentionPolicy.RUNTIME)
    @interface Test {
    }

    class ModuleDescriptor {

        private final String name;
        //pair is name of module and flag(public,mandated,synthetic)
        private final List<Pair<String, Integer>> requires = new ArrayList<>();

        {
            requires.add(new Pair<>("java.base", Module_attribute.ACC_MANDATED));
        }

        private final Map<String, List<String>> exports = new HashMap<>();

        //List of service and implementation
        private final List<Pair<String, String>> provides = new ArrayList<>();
        private final List<String> uses = new ArrayList<>();

        private static final String LINE_END = ";\n";

        StringBuilder content = new StringBuilder("module ");

        public ModuleDescriptor(String moduleName) {
            this.name = moduleName;
            content.append(name).append('{').append('\n');
        }

        public ModuleDescriptor requires(String... requires) {
            for (String require : requires) {
                this.requires.add(Pair.of(require, 0));
                content.append("    requires ").append(require).append(LINE_END);
            }
            return this;
        }

        public ModuleDescriptor requiresPublic(String... requiresPublic) {
            for (String require : requiresPublic) {
                this.requires.add(new Pair<>(require, Module_attribute.ACC_PUBLIC));
                content.append("    requires public ").append(require).append(LINE_END);
            }
            return this;
        }

        public ModuleDescriptor exports(String... exports) {
            for (String export : exports) {
                this.exports.putIfAbsent(export, new ArrayList<>());
                content.append("    exports ").append(export).append(LINE_END);
            }
            return this;
        }

        public ModuleDescriptor exportsTo(String exports, String to) {
            List<String> tos = Pattern.compile(",")
                    .splitAsStream(to)
                    .map(String::trim)
                    .collect(Collectors.toList());
            this.exports.computeIfAbsent(exports, k -> new ArrayList<>()).addAll(tos);
            content.append("    exports ").append(exports).append(" to ").append(to).append(LINE_END);
            return this;
        }

        public ModuleDescriptor provides(String provides, String with) {
            this.provides.add(Pair.of(provides, with));
            content.append("    provides ").append(provides).append(" with ").append(with).append(LINE_END);
            return this;
        }

        public ModuleDescriptor uses(String... uses) {
            Collections.addAll(this.uses, uses);
            for (String use : uses) {
                content.append("    uses ").append(use).append(LINE_END);
            }
            return this;
        }

        public ModuleDescriptor write(Path path) throws IOException {
            String src = content.append('}').toString();

            tb.createDirectories(path);
            tb.writeJavaFiles(path, src);
            return this;
        }
    }
}