test/jdk/sun/net/www/protocol/jar/MultiReleaseJarURLConnection.java
author darcy
Wed, 31 Jan 2018 11:25:34 -0800
changeset 48690 374c63fbe652
parent 47216 71c04702a3d5
child 54841 43439afaab4a
permissions -rw-r--r--
8194070: Update various tests to pass under JDK 11 Reviewed-by: alanb, sherman, psandoz

/*
 * Copyright (c) 2015, 2018, 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.
 */

/*
 * @test
 * @bug 8132734 8144062 8159785 8194070
 * @summary Test that URL connections to multi-release jars can be runtime versioned
 * @library /lib/testlibrary/java/util/jar
 * @modules jdk.compiler
 *          jdk.httpserver
 *          jdk.jartool
 * @build Compiler JarBuilder CreateMultiReleaseTestJars SimpleHttpServer
 * @run testng MultiReleaseJarURLConnection
 */

import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.jar.JarFile;

import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class MultiReleaseJarURLConnection {
    String userdir = System.getProperty("user.dir",".");
    String unversioned = userdir + "/unversioned.jar";
    String unsigned = userdir + "/multi-release.jar";
    String signed = userdir + "/signed-multi-release.jar";
    SimpleHttpServer server;

    @BeforeClass
    public void initialize() throws Exception {
        CreateMultiReleaseTestJars creator = new CreateMultiReleaseTestJars();
        creator.compileEntries();
        creator.buildUnversionedJar();
        creator.buildMultiReleaseJar();
        creator.buildSignedMultiReleaseJar();

        server = new SimpleHttpServer();
        server.start();

    }

    @AfterClass
    public void close() throws IOException {
        // Windows requires server to stop before file is deleted
        if (server != null)
            server.stop();
        Files.delete(Paths.get(unversioned));
        Files.delete(Paths.get(unsigned));
        Files.delete(Paths.get(signed));
    }

    @DataProvider(name = "data")
    public Object[][] createData() {
        return new Object[][]{
                {"unversioned", unversioned},
                {"unsigned", unsigned},
                {"signed", signed}
        };
    }

    @Test(dataProvider = "data")
    public void testRuntimeVersioning(String style, String file) throws Exception {
        String urlFile = "jar:file:" + file + "!/";
        String baseUrlEntry = urlFile + "version/Version.java";
        String rtreturn = "return " + Runtime.version().major();

        Assert.assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8"));
        // #runtime is "magic" for a multi-release jar, but not for unversioned jar
        Assert.assertTrue(readAndCompare(new URL(baseUrlEntry + "#runtime"),
                style.equals("unversioned") ? "return 8" : rtreturn));
        // #fragment or any other fragment is not magic
        Assert.assertTrue(readAndCompare(new URL(baseUrlEntry + "#fragment"), "return 8"));
        // cached entities not affected
        Assert.assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8"));

        // the following tests will not work with unversioned jars
        if (style.equals("unversioned"))
            return;

        // direct access to versioned entry
        String versUrlEntry = urlFile + "META-INF/versions/" + Runtime.version().major()
                + "/version/Version.java";
        Assert.assertTrue(readAndCompare(new URL(versUrlEntry), rtreturn));
        // adding any fragment does not change things
        Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#runtime"), rtreturn));
        Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#fragment"), rtreturn));
    }

    @Test(dataProvider = "data")
    public void testCachedJars(String style, String file) throws Exception {
        String urlFile = "jar:file:" + file + "!/";

        URL rootUrl = new URL(urlFile);
        JarURLConnection juc = (JarURLConnection)rootUrl.openConnection();
        JarFile rootJar = juc.getJarFile();
        Runtime.Version root = rootJar.getVersion();

        URL runtimeUrl = new URL(urlFile + "#runtime");
        juc = (JarURLConnection)runtimeUrl.openConnection();
        JarFile runtimeJar = juc.getJarFile();
        Runtime.Version runtime = runtimeJar.getVersion();
        if (style.equals("unversioned")) {
            Assert.assertEquals(root, runtime);
        } else {
            Assert.assertNotEquals(root, runtime);
        }

        juc = (JarURLConnection)rootUrl.openConnection();
        JarFile jar = juc.getJarFile();
        Assert.assertEquals(jar.getVersion(), root);
        Assert.assertEquals(jar, rootJar);

        juc = (JarURLConnection)runtimeUrl.openConnection();
        jar = juc.getJarFile();
        Assert.assertEquals(jar.getVersion(), runtime);
        Assert.assertEquals(jar, runtimeJar);

        rootJar.close();
        runtimeJar.close();
        jar.close(); // probably not needed
    }

    @DataProvider(name = "resourcedata")
    public Object[][] createResourceData() throws Exception {
        return new Object[][]{
                {"unversioned", Paths.get(unversioned).toUri().toURL()},
                {"unsigned", Paths.get(unsigned).toUri().toURL()},
                {"signed", Paths.get(signed).toUri().toURL()},
                {"unversioned", new URL("file:" + unversioned)},
                {"unsigned", new URL("file:" + unsigned)},
                {"signed", new URL("file:" + signed)},
                {"unversioned", new URL("jar:file:" + unversioned + "!/")},
                {"unsigned", new URL("jar:file:" + unsigned + "!/")},
                {"signed", new URL("jar:file:" + signed + "!/")},
                // external jar received via http protocol
                {"http", new URL("jar:http://localhost:" + server.getPort() + "/multi-release.jar!/")},
                {"http", new URL("http://localhost:" + server.getPort() + "/multi-release.jar")},

        };
    }

    @Test(dataProvider = "resourcedata")
    public void testResources(String style, URL url) throws Throwable {
        //System.out.println("  testing " + style + " url: " + url);
        URL[] urls = {url};
        URLClassLoader cldr = new URLClassLoader(urls);
        Class<?> vcls = cldr.loadClass("version.Version");

        // verify we are loading a runtime versioned class
        MethodType mt = MethodType.methodType(int.class);
        MethodHandle mh = MethodHandles.lookup().findVirtual(vcls, "getVersion", mt);
        Assert.assertEquals((int)mh.invoke(vcls.newInstance()),
                style.equals("unversioned") ? 8 : Runtime.version().major());

        // now get a resource and verify that we don't have a fragment attached
        Enumeration<URL> vclsUrlEnum = cldr.getResources("version/Version.class");
        Assert.assertTrue(vclsUrlEnum.hasMoreElements());
        URL vclsUrls[] = new URL[] {
            vcls.getResource("/version/Version.class"),
            vcls.getResource("Version.class"),
            cldr.getResource("version/Version.class"),
            vclsUrlEnum.nextElement()
        };
        Assert.assertFalse(vclsUrlEnum.hasMoreElements());
        for (URL vclsUrl : vclsUrls) {
            String fragment = vclsUrl.getRef();
            Assert.assertNull(fragment);

            // and verify that the the url is a reified pointer to the runtime entry
            String rep = vclsUrl.toString();
            //System.out.println("    getResource(\"/version/Version.class\") returned: " + rep);
            if (style.equals("http")) {
                Assert.assertTrue(rep.startsWith("jar:http:"));
            } else {
                Assert.assertTrue(rep.startsWith("jar:file:"));
            }
            String suffix;
            if (style.equals("unversioned")) {
                suffix = ".jar!/version/Version.class";
            } else {
                suffix = ".jar!/META-INF/versions/" + Runtime.version().major()
                        + "/version/Version.class";
            }
            Assert.assertTrue(rep.endsWith(suffix));
        }
        cldr.close();
    }

    private boolean readAndCompare(URL url, String match) throws Exception {
        boolean result;
        // necessary to do it this way, instead of openStream(), so we can
        // close underlying JarFile, otherwise windows can't delete the file
        URLConnection conn = url.openConnection();
        try (InputStream is = conn.getInputStream()) {
            byte[] bytes = is.readAllBytes();
            result = (new String(bytes)).contains(match);
        }
        if (conn instanceof JarURLConnection) {
            ((JarURLConnection)conn).getJarFile().close();
        }
        return result;
    }
}