1 /*
   2  * Copyright (c) 2016, 2017, 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 
  24 /**
  25  * @test
  26  * @modules java.scripting
  27  * @library modules /lib/testlibrary
  28  * @build bananascript/*
  29  * @build JarUtils
  30  * @compile classpath/pearscript/org/pear/PearScriptEngineFactory.java
  31  *          classpath/pearscript/org/pear/PearScript.java
  32  * @run testng/othervm ModulesTest
  33  * @summary Basic test for ServiceLoader with a provider deployed as a module.
  34  */
  35 
  36 import java.io.File;
  37 import java.lang.module.Configuration;
  38 import java.lang.module.ModuleFinder;
  39 import java.nio.file.Files;
  40 import java.nio.file.Path;
  41 import java.nio.file.Paths;
  42 import java.nio.file.StandardCopyOption;
  43 import java.util.ArrayList;
  44 import java.util.Collections;
  45 import java.util.HashSet;
  46 import java.util.Iterator;
  47 import java.util.List;
  48 import java.util.Optional;
  49 import java.util.ServiceLoader;
  50 import java.util.ServiceLoader.Provider;
  51 import java.util.Set;
  52 import java.util.stream.Collectors;
  53 import java.util.stream.Stream;
  54 import javax.script.ScriptEngineFactory;
  55 
  56 import org.testng.annotations.Test;
  57 import org.testng.annotations.BeforeTest;
  58 import static org.testng.Assert.*;
  59 
  60 /**
  61  * Basic test for ServiceLoader. The test make use of two service providers:
  62  * 1. BananaScriptEngine - a ScriptEngineFactory deployed as a module on the
  63  *    module path. It implementations a singleton via the public static
  64  *    provider method.
  65  * 2. PearScriptEngine - a ScriptEngineFactory deployed on the class path
  66  *    with a service configuration file.
  67  */
  68 
  69 public class ModulesTest {
  70 
  71     // Copy the services configuration file for "pearscript" into place.
  72     @BeforeTest
  73     public void setup() throws Exception {
  74         Path src = Paths.get(System.getProperty("test.src"));
  75         Path classes = Paths.get(System.getProperty("test.classes"));
  76         String st = ScriptEngineFactory.class.getName();
  77         Path config = Paths.get("META-INF", "services", st);
  78         Path source = src.resolve("classpath").resolve("pearscript").resolve(config);
  79         Path target = classes.resolve(config);
  80         Files.createDirectories(target.getParent());
  81         Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
  82     }
  83 
  84     /**
  85      * Basic test of iterator() to ensure that providers located as modules
  86      * and on the class path are found.
  87      */
  88     @Test
  89     public void testIterator() {
  90         ServiceLoader<ScriptEngineFactory> loader
  91             = ServiceLoader.load(ScriptEngineFactory.class);
  92         Set<String> names = collectAll(loader)
  93                 .stream()
  94                 .map(ScriptEngineFactory::getEngineName)
  95                 .collect(Collectors.toSet());
  96         assertTrue(names.contains("BananaScriptEngine"));
  97         assertTrue(names.contains("PearScriptEngine"));
  98     }
  99 
 100     /**
 101      * Basic test of iterator() to test iteration order. Providers deployed
 102      * as named modules should be found before providers deployed on the class
 103      * path.
 104      */
 105     @Test
 106     public void testIteratorOrder() {
 107         ServiceLoader<ScriptEngineFactory> loader
 108             = ServiceLoader.load(ScriptEngineFactory.class);
 109         boolean foundUnnamed = false;
 110         for (ScriptEngineFactory factory : collectAll(loader)) {
 111             if (factory.getClass().getModule().isNamed()) {
 112                 if (foundUnnamed) {
 113                     assertTrue(false, "Named module element after unnamed");
 114                 }
 115             } else {
 116                 foundUnnamed = true;
 117             }
 118         }
 119     }
 120 
 121     /**
 122      * Basic test of Provider::type
 123      */
 124     @Test
 125     public void testProviderType() {
 126         Set<String> types = ServiceLoader.load(ScriptEngineFactory.class)
 127                 .stream()
 128                 .map(Provider::type)
 129                 .map(Class::getName)
 130                 .collect(Collectors.toSet());
 131         assertTrue(types.contains("org.banana.BananaScriptEngineFactory"));
 132         assertTrue(types.contains("org.pear.PearScriptEngineFactory"));
 133     }
 134 
 135     /**
 136      * Basic test of Provider::get
 137      */
 138     @Test
 139     public void testProviderGet() {
 140         Set<String> names = ServiceLoader.load(ScriptEngineFactory.class)
 141                 .stream()
 142                 .map(Provider::get)
 143                 .map(ScriptEngineFactory::getEngineName)
 144                 .collect(Collectors.toSet());
 145         assertTrue(names.contains("BananaScriptEngine"));
 146         assertTrue(names.contains("PearScriptEngine"));
 147     }
 148 
 149     /**
 150      * Basic test of the public static provider method. BananaScriptEngine
 151      * defines a provider method that returns the same instance.
 152      */
 153     @Test
 154     public void testSingleton() {
 155         Optional<Provider<ScriptEngineFactory>> oprovider
 156             = ServiceLoader.load(ScriptEngineFactory.class)
 157                 .stream()
 158                 .filter(p -> p.type().getName().equals("org.banana.BananaScriptEngineFactory"))
 159                 .findFirst();
 160         assertTrue(oprovider.isPresent());
 161         Provider<ScriptEngineFactory> provider = oprovider.get();
 162 
 163         // invoke Provider::get twice
 164         ScriptEngineFactory factory1 = provider.get();
 165         ScriptEngineFactory factory2 = provider.get();
 166         assertTrue(factory1 == factory2);
 167     }
 168 
 169     /**
 170      * Basic test of stream() to ensure that elements for providers in named
 171      * modules come before elements for providers in unnamed modules.
 172      */
 173     @Test
 174     public void testStreamOrder() {
 175         List<Class<?>> types = ServiceLoader.load(ScriptEngineFactory.class)
 176                 .stream()
 177                 .map(Provider::type)
 178                 .collect(Collectors.toList());
 179 
 180         boolean foundUnnamed = false;
 181         for (Class<?> factoryClass : types) {
 182             if (factoryClass.getModule().isNamed()) {
 183                 if (foundUnnamed) {
 184                     assertTrue(false, "Named module element after unnamed");
 185                 }
 186             } else {
 187                 foundUnnamed = true;
 188             }
 189         }
 190     }
 191 
 192     /**
 193      * Basic test of ServiceLoader.findFirst()
 194      */
 195     @Test
 196     public void testFindFirst() {
 197         Optional<ScriptEngineFactory> ofactory
 198             = ServiceLoader.load(ScriptEngineFactory.class).findFirst();
 199         assertTrue(ofactory.isPresent());
 200         ScriptEngineFactory factory = ofactory.get();
 201         assertTrue(factory.getClass().getModule().isNamed());
 202 
 203         class S { }
 204         assertFalse(ServiceLoader.load(S.class).findFirst().isPresent());
 205     }
 206 
 207     /**
 208      * Basic test ServiceLoader.load specifying the platform class loader.
 209      * The providers on the module path and class path should not be located.
 210      */
 211     @Test
 212     public void testWithPlatformClassLoader() {
 213         ClassLoader pcl = ClassLoader.getPlatformClassLoader();
 214 
 215         // iterator
 216         ServiceLoader<ScriptEngineFactory> loader
 217             = ServiceLoader.load(ScriptEngineFactory.class, pcl);
 218         Set<String> names = collectAll(loader)
 219                 .stream()
 220                 .map(ScriptEngineFactory::getEngineName)
 221                 .collect(Collectors.toSet());
 222         assertFalse(names.contains("BananaScriptEngine"));
 223         assertFalse(names.contains("PearScriptEngine"));
 224 
 225         // stream
 226         names = ServiceLoader.load(ScriptEngineFactory.class, pcl)
 227                 .stream()
 228                 .map(Provider::get)
 229                 .map(ScriptEngineFactory::getEngineName)
 230                 .collect(Collectors.toSet());
 231         assertFalse(names.contains("BananaScriptEngine"));
 232         assertFalse(names.contains("PearScriptEngine"));
 233     }
 234 
 235     /**
 236      * Basic test of ServiceLoader.load where the service provider module is an
 237      * automatic module.
 238      */
 239     @Test
 240     public void testWithAutomaticModule() throws Exception {
 241         Path here = Paths.get("");
 242         Path jar = Files.createTempDirectory(here, "lib").resolve("pearscript.jar");
 243         Path classes = Paths.get(System.getProperty("test.classes"));
 244 
 245         JarUtils.createJarFile(jar, classes, "META-INF", "org");
 246 
 247         ModuleFinder finder = ModuleFinder.of(jar);
 248         ModuleLayer bootLayer = ModuleLayer.boot();
 249         Configuration parent = bootLayer.configuration();
 250         Configuration cf = parent.resolveAndBind(finder, ModuleFinder.of(), Set.of());
 251         assertTrue(cf.modules().size() == 1);
 252 
 253         ClassLoader scl = ClassLoader.getSystemClassLoader();
 254         ModuleLayer layer = bootLayer.defineModulesWithOneLoader(cf, scl);
 255         assertTrue(layer.modules().size() == 1);
 256 
 257         ClassLoader loader = layer.findLoader("pearscript");
 258         ScriptEngineFactory factory;
 259 
 260         // load using the class loader as context
 261         factory = ServiceLoader.load(ScriptEngineFactory.class, loader)
 262                 .findFirst()
 263                 .orElse(null);
 264         assertNotNull(factory);
 265         assertTrue(factory.getClass().getClassLoader() == loader);
 266 
 267         // load using the layer as context
 268         factory = ServiceLoader.load(layer, ScriptEngineFactory.class)
 269                 .findFirst()
 270                 .orElse(null);
 271         assertNotNull(factory);
 272         assertTrue(factory.getClass().getClassLoader() == loader);
 273     }
 274 
 275     /**
 276      * Basic test of ServiceLoader.load, using the class loader for
 277      * a module in a custom layer as the context.
 278      */
 279     @Test
 280     public void testWithCustomLayer1() {
 281         ModuleLayer layer = createCustomLayer("bananascript");
 282 
 283         ClassLoader loader = layer.findLoader("bananascript");
 284         List<ScriptEngineFactory> providers
 285             = collectAll(ServiceLoader.load(ScriptEngineFactory.class, loader));
 286 
 287         // should have at least 2 x bananascript + pearscript
 288         assertTrue(providers.size() >= 3);
 289 
 290         // first element should be the provider in the custom layer
 291         ScriptEngineFactory factory = providers.get(0);
 292         assertTrue(factory.getClass().getClassLoader() == loader);
 293         assertTrue(factory.getClass().getModule().getLayer() == layer);
 294         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 295 
 296         // remainder should be the boot layer
 297         providers.remove(0);
 298         Set<String> names = providers.stream()
 299                 .map(ScriptEngineFactory::getEngineName)
 300                 .collect(Collectors.toSet());
 301         assertTrue(names.contains("BananaScriptEngine"));
 302         assertTrue(names.contains("PearScriptEngine"));
 303     }
 304 
 305     /**
 306      * Basic test of ServiceLoader.load using a custom Layer as the context.
 307      */
 308     @Test
 309     public void testWithCustomLayer2() {
 310         ModuleLayer layer = createCustomLayer("bananascript");
 311 
 312         List<ScriptEngineFactory> factories
 313             = collectAll(ServiceLoader.load(layer, ScriptEngineFactory.class));
 314 
 315         // should have at least 2 x bananascript
 316         assertTrue(factories.size() >= 2);
 317 
 318         // first element should be the provider in the custom layer
 319         ScriptEngineFactory factory = factories.get(0);
 320         assertTrue(factory.getClass().getModule().getLayer() == layer);
 321         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 322 
 323         // remainder should be the boot layer
 324         factories.remove(0);
 325         Set<String> names = factories.stream()
 326                 .map(ScriptEngineFactory::getEngineName)
 327                 .collect(Collectors.toSet());
 328         assertTrue(names.contains("BananaScriptEngine"));
 329         assertFalse(names.contains("PearScriptEngine"));
 330     }
 331 
 332     /**
 333      * Basic test of ServiceLoader.load with a tree of layers.
 334      *
 335      * Test scenario:
 336      * - boot layer contains "bananascript", maybe other script engines
 337      * - layer1, with boot layer as parent, contains "bananascript"
 338      * - layer2, with boot layer as parent, contains "bananascript"
 339      * - layer3, with layer1 ad layer as parents, contains "bananascript"
 340      *
 341      * ServiceLoader should locate all 4 script engine factories in DFS order.
 342      */
 343     @Test
 344     public void testWithCustomLayer3() {
 345         ModuleLayer bootLayer = ModuleLayer.boot();
 346         Configuration cf0 = bootLayer.configuration();
 347 
 348         // boot layer should contain "bananascript"
 349         List<ScriptEngineFactory> factories
 350             = collectAll(ServiceLoader.load(bootLayer, ScriptEngineFactory.class));
 351         int countInBootLayer = factories.size();
 352         assertTrue(countInBootLayer >= 1);
 353         assertTrue(factories.stream()
 354                 .map(p -> p.getEngineName())
 355                 .filter("BananaScriptEngine"::equals)
 356                 .findAny()
 357                 .isPresent());
 358 
 359         ClassLoader scl = ClassLoader.getSystemClassLoader();
 360         ModuleFinder finder = ModuleFinder.of(testModulePath());
 361 
 362         // layer1
 363         Configuration cf1 = cf0.resolveAndBind(finder, ModuleFinder.of(), Set.of());
 364         ModuleLayer layer1 = bootLayer.defineModulesWithOneLoader(cf1, scl);
 365         assertTrue(layer1.modules().size() == 1);
 366 
 367         // layer2
 368         Configuration cf2 = cf0.resolveAndBind(finder, ModuleFinder.of(), Set.of());
 369         ModuleLayer layer2 = bootLayer.defineModulesWithOneLoader(cf2, scl);
 370         assertTrue(layer2.modules().size() == 1);
 371 
 372         // layer3 with layer1 and layer2 as parents
 373         Configuration cf3 = Configuration.resolveAndBind(finder,
 374                 List.of(cf1, cf2),
 375                 ModuleFinder.of(),
 376                 Set.of());
 377         ModuleLayer layer3
 378             = ModuleLayer.defineModulesWithOneLoader(cf3, List.of(layer1, layer2), scl).layer();
 379         assertTrue(layer3.modules().size() == 1);
 380 
 381 
 382         // class loaders
 383         ClassLoader loader1 = layer1.findLoader("bananascript");
 384         ClassLoader loader2 = layer2.findLoader("bananascript");
 385         ClassLoader loader3 = layer3.findLoader("bananascript");
 386         assertTrue(loader1 != loader2);
 387         assertTrue(loader1 != loader3);
 388         assertTrue(loader2 != loader3);
 389 
 390         // load all factories with layer3 as the context
 391         factories = collectAll(ServiceLoader.load(layer3, ScriptEngineFactory.class));
 392         int count = factories.size();
 393         assertTrue(count == countInBootLayer + 3);
 394 
 395         // the ordering should be layer3, layer1, boot layer, layer2
 396 
 397         ScriptEngineFactory factory = factories.get(0);
 398         assertTrue(factory.getClass().getModule().getLayer() == layer3);
 399         assertTrue(factory.getClass().getClassLoader() == loader3);
 400         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 401 
 402         factory = factories.get(1);
 403         assertTrue(factory.getClass().getModule().getLayer() == layer1);
 404         assertTrue(factory.getClass().getClassLoader() == loader1);
 405         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 406 
 407         // boot layer "bananascript" and maybe other factories
 408         int last = count -1;
 409         boolean found = false;
 410         for (int i=2; i<last; i++) {
 411             factory = factories.get(i);
 412             assertTrue(factory.getClass().getModule().getLayer() == bootLayer);
 413             if (factory.getEngineName().equals("BananaScriptEngine")) {
 414                 assertFalse(found);
 415                 found = true;
 416             }
 417         }
 418         assertTrue(found);
 419 
 420         factory = factories.get(last);
 421         assertTrue(factory.getClass().getModule().getLayer() == layer2);
 422         assertTrue(factory.getClass().getClassLoader() == loader2);
 423         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 424     }
 425 
 426 
 427     // -- nulls --
 428 
 429     @Test(expectedExceptions = { NullPointerException.class })
 430     public void testLoadNull1() {
 431         ServiceLoader.load(null);
 432     }
 433 
 434     @Test(expectedExceptions = { NullPointerException.class })
 435     public void testLoadNull2() {
 436         ServiceLoader.load((Class<?>) null, ClassLoader.getSystemClassLoader());
 437     }
 438 
 439     @Test(expectedExceptions = { NullPointerException.class })
 440     public void testLoadNull3() {
 441         class S { }
 442         ServiceLoader.load((ModuleLayer) null, S.class);
 443     }
 444 
 445     @Test(expectedExceptions = { NullPointerException.class })
 446     public void testLoadNull4() {
 447         ServiceLoader.load(ModuleLayer.empty(), null);
 448     }
 449 
 450     @Test(expectedExceptions = { NullPointerException.class })
 451     public void testLoadNull5() {
 452         ServiceLoader.loadInstalled(null);
 453     }
 454 
 455     /**
 456      * Create a custom layer by resolving the given module names. The modules
 457      * are located on the test module path ({@code ${test.module.path}}).
 458      */
 459     private ModuleLayer createCustomLayer(String... modules) {
 460         ModuleFinder finder = ModuleFinder.of(testModulePath());
 461         Set<String> roots = new HashSet<>();
 462         Collections.addAll(roots, modules);
 463         ModuleLayer bootLayer = ModuleLayer.boot();
 464         Configuration parent = bootLayer.configuration();
 465         Configuration cf = parent.resolve(finder, ModuleFinder.of(), roots);
 466         ClassLoader scl = ClassLoader.getSystemClassLoader();
 467         ModuleLayer layer = bootLayer.defineModulesWithOneLoader(cf, scl);
 468         assertTrue(layer.modules().size() == 1);
 469         return layer;
 470     }
 471 
 472     private Path[] testModulePath() {
 473         String mp = System.getProperty("test.module.path");
 474         return Stream.of(mp.split(File.pathSeparator))
 475                 .map(Paths::get)
 476                 .toArray(Path[]::new);
 477     }
 478 
 479     private <E> List<E> collectAll(ServiceLoader<E> loader) {
 480         List<E> list = new ArrayList<>();
 481         Iterator<E> iterator = loader.iterator();
 482         while (iterator.hasNext()) {
 483             list.add(iterator.next());
 484         }
 485         return list;
 486     }
 487 }
 488