org.sonar.classloader.ClassloaderBuilderTest.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.classloader.ClassloaderBuilderTest.java

Source

/*
 * sonar-classloader
 * Copyright (C) 2015-2016 SonarSource SA
 * mailto:contact AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.classloader;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.junit.Ignore;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;

public class ClassloaderBuilderTest {

    ClassloaderBuilder sut = new ClassloaderBuilder();

    @Test
    public void minimal_system_classloader() throws Exception {
        // create a classloader based on system classloader
        // -> access only to JRE
        Map<String, ClassLoader> classloaders = sut.newClassloader("example").build();

        assertThat(classloaders).hasSize(1);
        ClassLoader classloader = classloaders.get("example");
        assertThat(classloader.toString()).isEqualTo("ClassRealm{example}");
        assertThat(canLoadClass(classloader, HashMap.class.getName())).isTrue();
        assertThat(canLoadClass(classloader, Test.class.getName())).isFalse();
        assertThat(canLoadClass(classloader, "A")).isFalse();
        assertThat(canLoadResource(classloader, "a.txt")).isFalse();
    }

    /**
     * Classloader based on another one (the junit env in this example). No parent-child hierarchy.
     */
    @Test
    public void base_classloader() throws Exception {
        //
        Map<String, ClassLoader> classloaders = sut.newClassloader("example", getClass().getClassLoader()).build();

        assertThat(classloaders).hasSize(1);
        ClassLoader classloader = classloaders.get("example");
        assertThat(canLoadClass(classloader, HashMap.class.getName())).isTrue();
        assertThat(canLoadClass(classloader, Test.class.getName())).isTrue();
        assertThat(canLoadClass(classloader, "A")).isFalse();
        assertThat(canLoadResource(classloader, "a.txt")).isFalse();
    }

    @Test
    public void classloader_constituents() throws Exception {
        Map<String, ClassLoader> classloaders = sut.newClassloader("the-cl")
                .addURL("the-cl", new File("tester/a.jar").toURL())
                .addURL("the-cl", new File("tester/b.jar").toURL()).build();

        assertThat(classloaders).hasSize(1);
        ClassLoader self = classloaders.get("the-cl");
        assertThat(canLoadClass(self, "A")).isTrue();
        assertThat(canLoadResource(self, "a.txt")).isTrue();
        assertThat(canLoadClass(self, "B")).isTrue();
        assertThat(canLoadResource(self, "b.txt")).isTrue();
        assertThat(canLoadClass(self, "C")).isFalse();
        assertThat(canLoadResource(self, "c.txt")).isFalse();
    }

    /**
     * Parent -> child -> grand-child classloaders. Default order strategy is parent-first
     */
    @Test
    public void parent_child_relation() throws Exception {
        // parent contains class A -> access to only A
        // child contains class B -> access to A and B
        // grand-child contains class C -> access to A, B and C
        Map<String, ClassLoader> classloaders = sut.newClassloader("the-parent")
                .addURL("the-parent", new File("tester/a.jar").toURL())

                // order of declaration is not important -> declare grand-child before child
                .newClassloader("the-grand-child").addURL("the-grand-child", new File("tester/c.jar").toURL())
                .setParent("the-grand-child", "the-child", Mask.ALL)

                .newClassloader("the-child").addURL("the-child", new File("tester/b.jar").toURL())
                .setParent("the-child", "the-parent", Mask.ALL)

                .build();

        assertThat(classloaders).hasSize(3);

        ClassLoader parent = classloaders.get("the-parent");
        assertThat(canLoadClass(parent, "A")).isTrue();
        assertThat(canLoadClass(parent, "B")).isFalse();
        assertThat(canLoadClass(parent, "C")).isFalse();
        assertThat(canLoadResource(parent, "a.txt")).isTrue();
        assertThat(canLoadResource(parent, "b.txt")).isFalse();
        assertThat(canLoadResource(parent, "c.txt")).isFalse();

        ClassLoader child = classloaders.get("the-child");
        assertThat(canLoadClass(child, "A")).isTrue();
        assertThat(canLoadClass(child, "B")).isTrue();
        assertThat(canLoadClass(child, "C")).isFalse();
        assertThat(canLoadResource(child, "a.txt")).isTrue();
        assertThat(canLoadResource(child, "b.txt")).isTrue();
        assertThat(canLoadResource(child, "c.txt")).isFalse();

        ClassLoader grandChild = classloaders.get("the-grand-child");
        assertThat(canLoadClass(grandChild, "A")).isTrue();
        assertThat(canLoadClass(grandChild, "B")).isTrue();
        assertThat(canLoadClass(grandChild, "C")).isTrue();
        assertThat(canLoadResource(grandChild, "a.txt")).isTrue();
        assertThat(canLoadResource(grandChild, "b.txt")).isTrue();
        assertThat(canLoadResource(grandChild, "c.txt")).isTrue();
    }

    /**
     * Parent classloader can be created outside {@link org.sonar.classloader.ClassloaderBuilder}.
     * Default ordering strategy is parent-first.
     */
    @Test
    public void existing_parent() throws Exception {
        // parent contains JUnit
        // child contains class A -> access to A and JUnit
        ClassLoader parent = getClass().getClassLoader();
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("the-child")
                .addURL("the-child", new File("tester/a.jar").toURL()).setParent("the-child", parent, Mask.ALL)
                .build();

        assertThat(newClassloaders).hasSize(1);
        assertThat(canLoadClass(parent, Test.class.getName())).isTrue();
        assertThat(canLoadClass(parent, "A")).isFalse();
        ClassLoader child = newClassloaders.get("the-child");
        assertThat(canLoadClass(child, Test.class.getName())).isTrue();
        assertThat(canLoadClass(child, "A")).isTrue();
    }

    @Test
    public void parent_first_ordering() throws Exception {
        // parent contains version 1 of A
        // child contains version 2 of A
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("the-parent")
                .addURL("the-parent", new File("tester/a.jar").toURL())

                .newClassloader("the-child").addURL("the-child", new File("tester/a_v2.jar").toURL())
                .setParent("the-child", "the-parent", Mask.ALL).build();

        ClassLoader parent = newClassloaders.get("the-parent");
        assertThat(canLoadMethod(parent, "A", "version1")).isTrue();
        assertThat(canLoadMethod(parent, "A", "version2")).isFalse();
        assertThat(IOUtils.toString(parent.getResource("a.txt"))).startsWith("version 1 of a.txt");

        ClassLoader child = newClassloaders.get("the-child");
        assertThat(canLoadMethod(child, "A", "version1")).isTrue();
        assertThat(canLoadMethod(child, "A", "version2")).isFalse();
        assertThat(IOUtils.toString(child.getResource("a.txt"))).startsWith("version 1 of a.txt");
    }

    /**
     * - parent contains B and version 1 of A
     * - child contains version 2 of A -> sees B and version 2 of A
     */
    @Test
    public void self_first_ordering() throws Exception {

        Map<String, ClassLoader> newClassloaders = sut.newClassloader("the-parent")
                .addURL("the-parent", new File("tester/a.jar").toURL())
                .addURL("the-parent", new File("tester/b.jar").toURL())

                .newClassloader("the-child").addURL("the-child", new File("tester/a_v2.jar").toURL())
                .setParent("the-child", "the-parent", Mask.ALL)
                .setLoadingOrder("the-child", ClassloaderBuilder.LoadingOrder.SELF_FIRST).build();

        ClassLoader parent = newClassloaders.get("the-parent");
        assertThat(canLoadMethod(parent, "A", "version1")).isTrue();
        assertThat(canLoadMethod(parent, "A", "version2")).isFalse();
        assertThat(IOUtils.toString(parent.getResource("a.txt"))).startsWith("version 1 of a.txt");

        ClassLoader child = newClassloaders.get("the-child");
        assertThat(canLoadClass(child, "B")).isTrue();
        assertThat(canLoadMethod(child, "A", "version1")).isFalse();
        assertThat(canLoadMethod(child, "A", "version2")).isTrue();
        assertThat(IOUtils.toString(child.getResource("a.txt"))).startsWith("version 2 of a.txt");
        assertThat(Collections.list(child.getResources("b.txt"))).hasSize(1);
        ArrayList<URL> resources = Collections.list(child.getResources("a.txt"));
        assertThat(resources).hasSize(2);
        assertThat(IOUtils.toString(resources.get(0))).startsWith("version 2 of a.txt");
        assertThat(IOUtils.toString(resources.get(1))).startsWith("version 1 of a.txt");
    }

    /**
     * Prevent a classloader from loading some resources that are available in its own constituents.
     */
    @Test
    public void self_mask() throws Exception {
        Map<String, ClassLoader> classloaders = sut.newClassloader("the-cl")
                .addURL("the-cl", new File("tester/a.jar").toURL())
                .addURL("the-cl", new File("tester/b.jar").toURL())
                .setMask("the-cl", Mask.builder().exclude("A.class", "a.txt").build()).build();

        ClassLoader cl = classloaders.get("the-cl");
        assertThat(canLoadClass(cl, "A")).isFalse();
        assertThat(canLoadClass(cl, "B")).isTrue();
        assertThat(canLoadResource(cl, "a.txt")).isFalse();
        assertThat(canLoadResource(cl, "b.txt")).isTrue();
    }

    /**
     * Partial inheritance of parent classloader
     */
    @Test
    public void parent_mask() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("the-parent")
                .addURL("the-parent", new File("tester/a.jar").toURL())
                .addURL("the-parent", new File("tester/b.jar").toURL())

                .newClassloader("the-child").addURL("the-child", new File("tester/c.jar").toURL())
                .setParent("the-child", "the-parent", Mask.builder().exclude("A.class", "a.txt").build()).build();

        ClassLoader parent = newClassloaders.get("the-parent");
        assertThat(canLoadClass(parent, "A")).isTrue();
        assertThat(canLoadClass(parent, "B")).isTrue();
        assertThat(canLoadClass(parent, "C")).isFalse();
        assertThat(canLoadResource(parent, "a.txt")).isTrue();
        assertThat(canLoadResource(parent, "b.txt")).isTrue();
        assertThat(canLoadResource(parent, "c.txt")).isFalse();

        ClassLoader child = newClassloaders.get("the-child");
        assertThat(canLoadClass(child, "A")).isFalse();
        assertThat(canLoadClass(child, "B")).isTrue();
        assertThat(canLoadClass(child, "C")).isTrue();
        assertThat(canLoadResource(child, "a.txt")).isFalse();
        assertThat(canLoadResource(child, "b.txt")).isTrue();
        assertThat(canLoadResource(child, "c.txt")).isTrue();
    }

    /**
     * Parent classloader contains A and B, but exports only B to its children
     */
    @Test
    public void export_mask() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("the-parent")
                .addURL("the-parent", new File("tester/a.jar").toURL())
                .addURL("the-parent", new File("tester/b.jar").toURL())
                .setExportMask("the-parent", Mask.builder().exclude("A.class", "a.txt").build())

                .newClassloader("the-child").setParent("the-child", "the-parent", Mask.ALL).build();

        ClassLoader parent = newClassloaders.get("the-parent");
        assertThat(canLoadClass(parent, "A")).isTrue();
        assertThat(canLoadClass(parent, "B")).isTrue();
        assertThat(canLoadResource(parent, "a.txt")).isTrue();
        assertThat(canLoadResource(parent, "b.txt")).isTrue();

        ClassLoader child = newClassloaders.get("the-child");
        assertThat(canLoadClass(child, "A")).isFalse();
        assertThat(canLoadClass(child, "B")).isTrue();
        assertThat(canLoadResource(child, "a.txt")).isFalse();
        assertThat(canLoadResource(child, "b.txt")).isTrue();
    }

    /**
     * Parent classloader contains A, B and C, but exports only B and C to its children.
     * On the other side child classloader excludes B from its parent, so it benefits
     * only from C
     */
    @Test
    public void mix_of_import_and_export_masks() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("the-parent")
                .addURL("the-parent", new File("tester/a.jar").toURL())
                .addURL("the-parent", new File("tester/b.jar").toURL())
                .addURL("the-parent", new File("tester/c.jar").toURL())
                .setExportMask("the-parent", Mask.builder().exclude("A.class", "a.txt").build())

                .newClassloader("the-child")
                .setParent("the-child", "the-parent", Mask.builder().exclude("B.class", "b.txt").build()).build();

        ClassLoader parent = newClassloaders.get("the-parent");
        assertThat(canLoadClass(parent, "A")).isTrue();
        assertThat(canLoadClass(parent, "B")).isTrue();
        assertThat(canLoadClass(parent, "C")).isTrue();
        assertThat(canLoadResource(parent, "a.txt")).isTrue();
        assertThat(canLoadResource(parent, "b.txt")).isTrue();
        assertThat(canLoadResource(parent, "c.txt")).isTrue();

        ClassLoader child = newClassloaders.get("the-child");
        assertThat(canLoadClass(child, "A")).isFalse();
        assertThat(canLoadClass(child, "B")).isFalse();
        assertThat(canLoadClass(child, "C")).isTrue();
        assertThat(canLoadResource(child, "a.txt")).isFalse();
        assertThat(canLoadResource(child, "b.txt")).isFalse();
        assertThat(canLoadResource(child, "c.txt")).isTrue();
    }

    @Test
    public void fail_to_create_the_same_classloader_twice() throws Exception {
        sut.newClassloader("the-cl");
        try {
            sut.newClassloader("the-cl");
            fail();
        } catch (IllegalStateException e) {
            assertThat(e).hasMessage("The classloader 'the-cl' already exists. Can not create it twice.");
        }
    }

    @Test
    public void fail_if_missing_declaration() throws Exception {
        sut.newClassloader("the-cl");
        sut.setParent("the-cl", "missing", Mask.ALL);
        try {
            sut.build();
            fail();
        } catch (IllegalStateException e) {
            assertThat(e).hasMessage("The classloader 'missing' does not exist");
        }
    }

    @Test
    public void sibling() throws Exception {
        // sibling1 contains A
        // sibling2 contains B
        // child contains C -> see A, B and C
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("sib1")
                .addURL("sib1", new File("tester/a.jar").toURL())

                .newClassloader("sib2").addURL("sib2", new File("tester/b.jar").toURL())

                .newClassloader("the-child").addURL("the-child", new File("tester/c.jar").toURL())
                .addSibling("the-child", "sib1", Mask.ALL).addSibling("the-child", "sib2", Mask.ALL).build();

        ClassLoader sib1 = newClassloaders.get("sib1");
        assertThat(canLoadClass(sib1, "A")).isTrue();
        assertThat(canLoadClass(sib1, "B")).isFalse();
        assertThat(canLoadClass(sib1, "C")).isFalse();
        assertThat(canLoadResource(sib1, "a.txt")).isTrue();
        assertThat(canLoadResource(sib1, "b.txt")).isFalse();
        assertThat(canLoadResource(sib1, "c.txt")).isFalse();

        ClassLoader sib2 = newClassloaders.get("sib2");
        assertThat(canLoadClass(sib2, "A")).isFalse();
        assertThat(canLoadClass(sib2, "B")).isTrue();
        assertThat(canLoadClass(sib2, "C")).isFalse();
        assertThat(canLoadResource(sib2, "a.txt")).isFalse();
        assertThat(canLoadResource(sib2, "b.txt")).isTrue();
        assertThat(canLoadResource(sib2, "c.txt")).isFalse();

        ClassLoader child = newClassloaders.get("the-child");
        assertThat(canLoadClass(child, "A")).isTrue();
        assertThat(canLoadClass(child, "B")).isTrue();
        assertThat(canLoadClass(child, "C")).isTrue();
        assertThat(canLoadResource(child, "a.txt")).isTrue();
        assertThat(canLoadResource(child, "b.txt")).isTrue();
        assertThat(canLoadResource(child, "c.txt")).isTrue();
    }

    /**
     * Sibling classloader can be created outside {@link org.sonar.classloader.ClassloaderBuilder}.
     */
    @Test
    public void existing_sibling() throws Exception {
        // sibling1 contains JUnit
        // child contains A -> see JUnit and A
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("the-child")
                .addURL("the-child", new File("tester/a.jar").toURL())
                .addSibling("the-child", getClass().getClassLoader(), Mask.ALL).build();

        ClassLoader child = newClassloaders.get("the-child");
        assertThat(canLoadClass(child, Test.class.getName())).isTrue();
        assertThat(canLoadClass(child, "A")).isTrue();
    }

    /**
     * - sibling contains A and B
     * - child contains C and excludes A from sibling -> sees only B and C
     */
    @Test
    public void sibling_mask() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("sib1")
                .addURL("sib1", new File("tester/a.jar").toURL()).addURL("sib1", new File("tester/b.jar").toURL())

                .newClassloader("the-child").addURL("the-child", new File("tester/c.jar").toURL())
                .addSibling("the-child", "sib1", Mask.builder().exclude("A.class", "a.txt").build()).build();

        ClassLoader sib1 = newClassloaders.get("sib1");
        assertThat(canLoadClass(sib1, "A")).isTrue();
        assertThat(canLoadClass(sib1, "B")).isTrue();
        assertThat(canLoadResource(sib1, "a.txt")).isTrue();
        assertThat(canLoadResource(sib1, "b.txt")).isTrue();

        ClassLoader child = newClassloaders.get("the-child");
        assertThat(canLoadClass(child, "A")).isFalse();
        assertThat(canLoadClass(child, "B")).isTrue();
        assertThat(canLoadClass(child, "C")).isTrue();
        assertThat(canLoadResource(child, "a.txt")).isFalse();
        assertThat(canLoadResource(child, "b.txt")).isTrue();
        assertThat(canLoadResource(child, "c.txt")).isTrue();
        assertThat(Collections.list(child.getResources("a.txt"))).hasSize(0);
        assertThat(Collections.list(child.getResources("b.txt"))).hasSize(1);
        assertThat(Collections.list(child.getResources("c.txt"))).hasSize(1);
    }

    /**
     * - sibling contains A and B but exports only B
     * - child contains C -> sees only B and C
     */
    @Test
    public void sibling_export_mask() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("sib1")
                .addURL("sib1", new File("tester/a.jar").toURL()).addURL("sib1", new File("tester/b.jar").toURL())
                .setExportMask("sib1", Mask.builder().include("B.class", "b.txt").build())

                .newClassloader("the-child").addURL("the-child", new File("tester/c.jar").toURL())
                .addSibling("the-child", "sib1", Mask.ALL).build();

        ClassLoader sib1 = newClassloaders.get("sib1");
        assertThat(canLoadClass(sib1, "A")).isTrue();
        assertThat(canLoadClass(sib1, "B")).isTrue();
        assertThat(canLoadResource(sib1, "a.txt")).isTrue();
        assertThat(canLoadResource(sib1, "b.txt")).isTrue();

        ClassLoader child = newClassloaders.get("the-child");
        assertThat(canLoadClass(child, "A")).isFalse();
        assertThat(canLoadClass(child, "B")).isTrue();
        assertThat(canLoadClass(child, "C")).isTrue();
        assertThat(canLoadResource(child, "a.txt")).isFalse();
        assertThat(canLoadResource(child, "b.txt")).isTrue();
        assertThat(canLoadResource(child, "c.txt")).isTrue();
        assertThat(Collections.list(child.getResources("a.txt"))).hasSize(0);
        assertThat(Collections.list(child.getResources("b.txt"))).hasSize(1);
        assertThat(Collections.list(child.getResources("c.txt"))).hasSize(1);
    }

    /**
     * Sibling classloader is loaded previously self:
     * - sibling has version 1 of A
     * - self has version 2 of A -> sees version 1
     */
    @Test
    public void sibling_prevails_over_self() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("sib")
                .addURL("sib", new File("tester/a.jar").toURL())

                .newClassloader("self").addURL("self", new File("tester/a_v2.jar").toURL())
                .addSibling("self", "sib", Mask.ALL).build();

        ClassLoader sib = newClassloaders.get("sib");
        assertThat(canLoadMethod(sib, "A", "version1")).isTrue();
        assertThat(canLoadMethod(sib, "A", "version2")).isFalse();
        assertThat(IOUtils.toString(sib.getResource("a.txt"))).startsWith("version 1 of a.txt");

        ClassLoader self = newClassloaders.get("self");
        assertThat(canLoadMethod(self, "A", "version1")).isTrue();
        assertThat(canLoadMethod(self, "A", "version2")).isFalse();
        assertThat(IOUtils.toString(self.getResource("a.txt"))).startsWith("version 1 of a.txt");
    }

    /**
     * Sibling classloader is always loaded previously self, even if self-first strategy:
     * - sibling has version 1 of A
     * - self has version 2 of A -> sees version 1
     */
    @Test
    public void sibling_prevails_over_self_even_if_self_first() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("sib")
                .addURL("sib", new File("tester/a.jar").toURL())

                .newClassloader("self").addURL("self", new File("tester/a_v2.jar").toURL())
                .addSibling("self", "sib", Mask.ALL)
                .setLoadingOrder("self", ClassloaderBuilder.LoadingOrder.SELF_FIRST).build();

        ClassLoader sib = newClassloaders.get("sib");
        assertThat(canLoadMethod(sib, "A", "version1")).isTrue();
        assertThat(canLoadMethod(sib, "A", "version2")).isFalse();
        assertThat(IOUtils.toString(sib.getResource("a.txt"))).startsWith("version 1 of a.txt");

        ClassLoader self = newClassloaders.get("self");
        assertThat(canLoadMethod(self, "A", "version1")).isTrue();
        assertThat(canLoadMethod(self, "A", "version2")).isFalse();
        assertThat(IOUtils.toString(self.getResource("a.txt"))).startsWith("version 1 of a.txt");
    }

    /**
     * https://github.com/SonarSource/sonar-classloader/issues/1
     */
    @Test
    public void cycle_of_siblings() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("a")
                .addURL("a", new File("tester/a.jar").toURL())

                .newClassloader("b").addURL("b", new File("tester/b.jar").toURL())
                .addSibling("a", "b", Mask.builder().include("B.class", "b.txt").build())
                .addSibling("b", "a", Mask.builder().include("A.class", "a.txt").build()).build();

        ClassLoader a = newClassloaders.get("a");
        assertThat(canLoadClass(a, "A")).isTrue();
        assertThat(canLoadClass(a, "B")).isTrue();
        assertThat(IOUtils.toString(a.getResource("a.txt"))).isNotEmpty();
        assertThat(IOUtils.toString(a.getResource("b.txt"))).isNotEmpty();

        ClassLoader b = newClassloaders.get("b");
        assertThat(canLoadClass(b, "A")).isTrue();
        assertThat(canLoadClass(b, "B")).isTrue();
        assertThat(IOUtils.toString(b.getResource("a.txt"))).isNotEmpty();
        assertThat(IOUtils.toString(b.getResource("b.txt"))).isNotEmpty();
    }

    @Test
    public void getResources_from_parent_and_siblings() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("the-parent")
                .addURL("the-parent", new File("tester/a.jar").toURL())

                .newClassloader("the-sib").addURL("the-sib", new File("tester/b.jar").toURL())

                .newClassloader("the-child").addURL("the-child", new File("tester/c.jar").toURL())
                .setParent("the-child", "the-parent", Mask.ALL).addSibling("the-child", "the-sib", Mask.ALL)
                .build();

        ClassLoader parent = newClassloaders.get("the-parent");
        assertThat(Collections.list(parent.getResources("a.txt"))).hasSize(1);
        assertThat(Collections.list(parent.getResources("b.txt"))).hasSize(0);
        assertThat(Collections.list(parent.getResources("c.txt"))).hasSize(0);

        ClassLoader child = newClassloaders.get("the-child");
        assertThat(Collections.list(child.getResources("a.txt"))).hasSize(1);
        assertThat(Collections.list(child.getResources("b.txt"))).hasSize(1);
        assertThat(Collections.list(child.getResources("c.txt"))).hasSize(1);
    }

    @Test
    public void getResources_multiple_versions_with_parent_first_strategy() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("the-parent")
                .addURL("the-parent", new File("tester/a.jar").toURL())

                .newClassloader("the-child").addURL("the-child", new File("tester/a_v2.jar").toURL())
                .setParent("the-child", "the-parent", Mask.ALL).build();

        ClassLoader parent = newClassloaders.get("the-parent");
        assertThat(Collections.list(parent.getResources("a.txt"))).hasSize(1);

        ClassLoader child = newClassloaders.get("the-child");
        List<URL> childResources = Collections.list(child.getResources("a.txt"));
        assertThat(childResources).hasSize(2);
        assertThat(IOUtils.toString(childResources.get(0))).startsWith("version 1 of a.txt");
        assertThat(IOUtils.toString(childResources.get(1))).startsWith("version 2 of a.txt");
    }

    @Test
    public void resource_not_found_in_parent_first_strategy() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("the-parent")
                .addURL("the-parent", new File("tester/a.jar").toURL())

                .newClassloader("the-child").addURL("the-child", new File("tester/a_v2.jar").toURL())
                .setParent("the-child", "the-parent", Mask.ALL).build();

        ClassLoader parent = newClassloaders.get("the-child");
        assertThat(parent.getResource("missing")).isNull();
        try {
            parent.loadClass("missing");
            fail();
        } catch (ClassNotFoundException e) {
            // ok
        }
    }

    @Test
    public void resource_not_found_in_self_first_strategy() throws Exception {
        Map<String, ClassLoader> newClassloaders = sut.newClassloader("the-parent")
                .addURL("the-parent", new File("tester/a.jar").toURL())

                .newClassloader("the-child").addURL("the-child", new File("tester/a_v2.jar").toURL())
                .setParent("the-child", "the-parent", Mask.ALL)
                .setLoadingOrder("the-child", ClassloaderBuilder.LoadingOrder.SELF_FIRST).build();

        ClassLoader parent = newClassloaders.get("the-child");
        assertThat(parent.getResource("missing")).isNull();
        try {
            parent.loadClass("missing");
            fail();
        } catch (ClassNotFoundException e) {
            // ok
        }
    }

    private boolean canLoadClass(ClassLoader classloader, String classname) {
        try {
            classloader.loadClass(classname);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    private boolean canLoadMethod(ClassLoader classloader, String classname, String methodName) {
        try {
            Class clazz = classloader.loadClass(classname);
            return clazz.getMethod(methodName) != null;
        } catch (Exception e) {
            return false;
        }
    }

    private boolean canLoadResource(ClassLoader classloader, String name) {
        return classloader.getResource(name) != null;
    }
}