1 /*
   2  * Copyright 2007-2008 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  20  * CA 95054 USA or visit www.sun.com if you need additional information or
  21  * have any questions.
  22  */
  23 
  24 import java.io.File;
  25 import java.io.IOException;
  26 import java.lang.reflect.Method;
  27 import java.module.Module;
  28 import java.module.ModuleArchiveInfo;
  29 import java.module.ModuleDefinition;
  30 import java.module.Modules;
  31 import java.module.Repository;
  32 import java.module.RepositoryEvent;
  33 import java.module.RepositoryListener;
  34 import java.module.ModuleDefinition;
  35 import java.module.VersionConstraint;
  36 import java.util.ArrayList;
  37 import java.util.List;
  38 import java.util.HashMap;
  39 import java.util.Map;
  40 import sun.module.JamUtils;
  41 import sun.module.repository.RepositoryUtils;
  42 
  43 /**
  44  * @test LocalRepositoryTest.java
  45  * @summary Test LocalRepository.
  46  * @library ../tools
  47  * @compile -XDignore.symbol.file LocalRepositoryTest.java EventChecker.java ../tools/JamBuilder.java
  48  * @run main LocalRepositoryTest
  49  * */
  50 public class LocalRepositoryTest {
  51     static final boolean debug = System.getProperty("module.debug") != null;
  52 
  53     static EventChecker ec = new EventChecker();
  54 
  55     private static void println(String s) {
  56         if (debug) System.err.println(s);
  57     }
  58 
  59     private static File makeTestDir(String name) throws IOException {
  60         File rc =
  61             new File(
  62                 System.getProperty("test.scratch", "."), name).getCanonicalFile();
  63         if (rc.exists() ) {
  64             JamUtils.recursiveDelete(rc);
  65         }
  66         rc.mkdirs();
  67         return rc;
  68     }
  69 
  70     public static void realMain(String[] args) throws Throwable {
  71         boolean onWindows = System.getProperty("os.platform").equalsIgnoreCase("windows");
  72 
  73         // Enables shadow file copies in the repository if we're running
  74         // on Windows. This is to prevent file locking in the
  75         // source location.
  76         if (onWindows) {
  77             System.setProperty("java.module.repository.shadowcopyfiles", "true");
  78         }
  79 
  80         File srcDir =
  81             new File(
  82                 System.getProperty("test.scratch", "."), "LocalRepoSrc").getCanonicalFile();
  83         if (srcDir.exists()) {
  84             JamUtils.recursiveDelete(srcDir);
  85         }
  86         File expandDir = makeTestDir("LocalRepoExpand");
  87 
  88         // Check that getApplicationRepository() doesn't return null and is
  89         // configured OK.
  90         Repository appRepo = Repository.getApplicationRepository();
  91         check(appRepo != null);
  92 
  93         Map<String, String> config = new HashMap<String, String>();
  94         config.put("sun.module.repository.LocalRepository.cacheDirectory",
  95                 expandDir.getAbsolutePath());
  96         Repository repo = Modules.newLocalRepository(
  97             "test", srcDir, config, appRepo);
  98 
  99         // Only REPOSITORY_INITIALIZED event should be fired.
 100         check(ec.initializeEventExists(repo));
 101         check(!ec.shutdownEventExists(repo));
 102         check(!ec.installEventExists(repo, null));
 103         check(!ec.uninstallEventExists(repo, null));
 104 
 105         // In a different repository, verify we get an exception if we require
 106         // the source dir to exist, but it does not.
 107         Repository r2 = null;
 108         try {
 109             config.put("sun.module.repository.LocalRepository.sourceLocationMustExist", "true");
 110             r2 = Modules.newLocalRepository(
 111                 "test", new File("doesNotExist"), config, appRepo);
 112             fail();
 113         } catch (IOException ex) {
 114             check(ex.getMessage().contains("does not exist or is not a directory"));
 115         } catch (Throwable t) {
 116             unexpected(t);
 117         }
 118 
 119         // No event should be fired.
 120         check(!ec.initializeEventExists(r2));
 121         check(!ec.shutdownEventExists(r2));
 122         check(!ec.installEventExists(r2, null));
 123         check(!ec.uninstallEventExists(r2, null));
 124 
 125         // Verify the repository is active and reloadable
 126         check(repo.isActive());
 127         check(repo.supportsReload());
 128 
 129         // Verify the repository is read-only, since it's source location does
 130         // not yet exist
 131         check(repo.isReadOnly());
 132 
 133         // Verify subsequent initialize is a no-op
 134         try {
 135             repo.initialize();
 136             pass();
 137         } catch (Throwable t) {
 138             unexpected(t);
 139         }
 140 
 141         // No event should be fired.
 142         check(!ec.initializeEventExists(repo));
 143         check(!ec.shutdownEventExists(repo));
 144         check(!ec.installEventExists(repo, null));
 145         check(!ec.uninstallEventExists(repo, null));
 146 
 147         // Check that find() and list() return nothing from repo, since the
 148         // its source dir does not exist.
 149         check(repo.list().size() == 0);
 150         check(findModuleDefsInRepository(repo).size() == 0);
 151 
 152         srcDir.mkdirs();
 153         repo.reload();
 154 
 155         // No event should be fired.
 156         check(!ec.initializeEventExists(repo));
 157         check(!ec.shutdownEventExists(repo));
 158         check(!ec.installEventExists(repo, null));
 159         check(!ec.uninstallEventExists(repo, null));
 160 
 161         // Verify that repository is now *not* read-only
 162         check(repo.isReadOnly() == false);
 163 
 164         // Check that find() and list() return nothing from repo, since its
 165         // source dir is empty.
 166         check(repo.list().size() == 0);
 167         check(findModuleDefsInRepository(repo).size() == 0);
 168 
 169         File jamDir = makeTestDir("LocalRepoJam");
 170 
 171         // Create a JAM file in the LocalRepository's sourceLocation
 172         File jamFile = JamBuilder.createJam(
 173             "localrepotest", "LocalRepoTestA", "LocalRepoModuleA", "3.1",
 174             null, null, false, srcDir);
 175 
 176         // Verify that we can reload from a read-only location
 177         boolean readOnlyChangeOK = (srcDir.setWritable(false) == true);
 178         repo.reload();
 179 
 180         if (readOnlyChangeOK) {
 181             if (!onWindows) {
 182                 // I/O APIs are not completely honest about writability of
 183                 // directories on Windows; see 4939819 and 6728842.
 184                 check(repo.isReadOnly());
 185             }
 186         }
 187 
 188         // Check initial module is installed
 189         List<ModuleArchiveInfo> installed = repo.list();
 190         println("=installed size " + installed.size());
 191         check(installed.size() == 1);
 192         ModuleArchiveInfo mai = installed.get(0);
 193         check(mai.getName().equals("LocalRepoModuleA"));
 194 
 195         // MODULE_ARCHIVE_INSTALLED event should be fired.
 196         check(!ec.initializeEventExists(repo));
 197         check(!ec.shutdownEventExists(repo));
 198         check(ec.installEventExists(repo, mai));
 199         check(!ec.uninstallEventExists(repo, null));
 200 
 201         // Verify module is runnable from read-only source location
 202         runModule(repo, "LocalRepoModuleA");
 203 
 204         // Verify that we can reload from a writable directory
 205         readOnlyChangeOK = (srcDir.setWritable(true) == true);
 206         repo.reload();
 207         if (readOnlyChangeOK) {
 208             check(repo.isReadOnly() == false);
 209         }
 210 
 211         // Check initial module is (still) installed
 212         installed = repo.list();
 213         println("=installed size " + installed.size());
 214         check(installed.size() == 1);
 215         check(installed.get(0).getName().equals("LocalRepoModuleA"));
 216 
 217         // No event should be fired.
 218         check(!ec.initializeEventExists(repo));
 219         check(!ec.shutdownEventExists(repo));
 220         check(!ec.installEventExists(repo, null));
 221         check(!ec.uninstallEventExists(repo, null));
 222 
 223         // Create platform-specific JAM
 224         final String platform = RepositoryUtils.getPlatform();
 225         final String arch = RepositoryUtils.getArch();
 226 
 227         jamFile = JamBuilder.createJam(
 228             "localrepotest", "LocalRepoTestB", "LocalRepoModuleB", "4.2",
 229             platform, arch, true, jamDir);
 230         mai = repo.install(jamFile.getCanonicalFile().toURI());
 231         check(mai != null);
 232         println("LocalRepoModuleB mai: " + mai);
 233 
 234         // MODULE_ARCHIVE_INSTALLED event should be fired.
 235         check(!ec.initializeEventExists(repo));
 236         check(!ec.shutdownEventExists(repo));
 237         check(ec.installEventExists(repo, mai));
 238         check(!ec.uninstallEventExists(repo, null));
 239 
 240         // Verify that same module cannot be over itself
 241         try {
 242             repo.install(jamFile.getCanonicalFile().toURI());
 243             fail();
 244         } catch (IllegalStateException ex) {
 245             pass();
 246             println("Caught expected " + ex);
 247         }
 248 
 249         // No event should be fired.
 250         check(!ec.initializeEventExists(repo));
 251         check(!ec.shutdownEventExists(repo));
 252         check(!ec.installEventExists(repo, null));
 253         check(!ec.uninstallEventExists(repo, null));
 254 
 255         // Check that all modules are installed
 256         installed = repo.list();
 257         check(installed.size() == 2);
 258         check(installed.get(0).getName().equals("LocalRepoModuleA"));
 259         check(installed.get(1).getName().equals("LocalRepoModuleB"));
 260 
 261         // Check that modules run
 262         runModule(repo, "LocalRepoModuleA");
 263         runModule(repo, "LocalRepoModuleB");
 264 
 265         for (File f : srcDir.listFiles()) {
 266             println("srcDir contains " + f.getName());
 267         }
 268 
 269         mai = installed.get(0);
 270         repo.uninstall(mai);
 271 
 272         // MODULE_ARCHIVE_UNINSTALLED should be fired.
 273         check(!ec.initializeEventExists(repo));
 274         check(!ec.shutdownEventExists(repo));
 275         check(!ec.installEventExists(repo, null));
 276         check(ec.uninstallEventExists(repo, mai));
 277 
 278         installed = repo.list();
 279         check(installed.size() == 1);
 280         check(installed.get(0).getName().equals("LocalRepoModuleB"));
 281 
 282         runModule(repo, "LocalRepoModuleB");
 283 
 284         int numFound = repo.findAll().size();
 285         int numInstalled = repo.list().size();
 286 
 287         // See below where reload() is checked
 288         ModuleArchiveInfo deleteMe = repo.list().get(0);
 289 
 290         // Create another JAM for a different platform binding that
 291         // does not match any in existence
 292         jamFile = JamBuilder.createJam(
 293             "localrepotest", "LocalRepoTestC", "LocalRepoModuleC", "2.7",
 294             "abc" + platform, "def" + arch, false, jamDir);
 295         mai = repo.install(jamFile.getCanonicalFile().toURI());
 296         check(mai != null);
 297         println("LocalRepoModuleC mai: " + mai);
 298 
 299         // MODULE_ARCHIVE_INSTALLED event should be fired.
 300         check(!ec.initializeEventExists(repo));
 301         check(!ec.shutdownEventExists(repo));
 302         check(ec.installEventExists(repo, mai));
 303         check(!ec.uninstallEventExists(repo, null));
 304 
 305         installed = repo.list();
 306         check(installed.size() == 2);
 307         mai = installed.get(1);
 308         String name = mai.getName();
 309         check(name.equals("LocalRepoModuleC"));
 310         ModuleDefinition md = repo.find(name);
 311         check(md == null);
 312 
 313         // Check that one more module is listed, but the same number
 314         // is findable.
 315         check(repo.list().size() == numInstalled + 1);
 316         check(repo.findAll().size() == numFound);
 317 
 318         // Verify that if a JAM file is removed and the repository reloaded,
 319         // the module corresponding to that JAM is not installed.
 320         jamFile = new File(deleteMe.getFileName());
 321         check(jamFile.isFile());
 322         jamFile.delete();
 323         repo.reload();
 324         check(repo.find(mai.getName()) == null);
 325 
 326         // MODULE_ARCHIVE_UNINSTALLED event should be fired.
 327         check(!ec.initializeEventExists(repo));
 328         check(!ec.shutdownEventExists(repo));
 329         check(!ec.installEventExists(repo, null));
 330         check(ec.uninstallEventExists(repo, deleteMe));
 331 
 332         // Verify that updating a module works
 333         JamBuilder jb = new JamBuilder(
 334             "localrepotest", "LocalRepoTestD", "LocalRepoModuleD", "4.2",
 335             platform, arch, false, jamDir);
 336         jb.setMethod("foo");
 337         jamFile = jb.createJam();
 338         mai = repo.install(jamFile.getCanonicalFile().toURI());
 339         runModule(repo, "LocalRepoModuleD", "foo");
 340 
 341         // MODULE_ARCHIVE_INSTALLED event should be fired.
 342         check(!ec.initializeEventExists(repo));
 343         check(!ec.shutdownEventExists(repo));
 344         check(ec.installEventExists(repo, mai));
 345         check(!ec.uninstallEventExists(repo, null));
 346 
 347         // Wait a bit before overwriting the just-created JAM
 348         Thread.currentThread().sleep(1000);
 349         jb = new JamBuilder(
 350             "localrepotest", "LocalRepoTestD", "LocalRepoModuleD", "4.2",
 351             platform, arch, false, srcDir); // Note: srcDir, not jamDir
 352         jb.setMethod("bar");
 353         jamFile = jb.createJam();
 354         repo.reload();
 355 
 356         // Both MODULE_ARCHIVE_UNINSTALLED and MODULE_ARCHIVE_INSTALLED events should be fired.
 357         check(!ec.initializeEventExists(repo));
 358         check(!ec.shutdownEventExists(repo));
 359         check(ec.installEventExists(repo, null));
 360         check(ec.uninstallEventExists(repo, mai));
 361 
 362         runModule(repo, "LocalRepoModuleD", "bar");
 363 
 364 
 365         // If there are three modules that have the same name and version but
 366         // different platform binding:
 367         // 1. Specific to xyz platform and arch
 368         // 2. Specific to current platform and arch
 369         // 3. Platform and arch neutral
 370         //
 371         // If they are installed in the order of #1, #2, #3, then #2 should
 372         // be used to provide the module definition.
 373         // Then, if they are uninstalled in the order of #1 and #2, then no
 374         // module definition would be returned from the repository even
 375         // #3 still exists, because #3 has not been loaded by the repository
 376         // before.
 377         //
 378         // On the other hand, if they are installed in the order of #1, #3,
 379         // #2, then #3 should be used to provide the module definition.
 380         // Then, if they are uninstalled in the order of #3 and #1, then
 381         // no module definition would be returned from the repository even
 382         // #2 still exists, because #2 has not been loaded by the repository.
 383         //
 384         File jamFile1, jamFile2, jamFile3;
 385         ModuleArchiveInfo mai1, mai2, mai3;
 386         jamFile1 = JamBuilder.createJam(
 387             "localrepotest", "LocalRepoTestE", "LocalRepoModuleE", "7.0",
 388             "xyz-platform", "xyz-arch", false, jamDir);
 389         jamFile2 = JamBuilder.createJam(
 390             "localrepotest", "LocalRepoTestE", "LocalRepoModuleE", "7.0",
 391             platform, arch, false, jamDir);
 392         jamFile3 = JamBuilder.createJam(
 393             "localrepotest", "LocalRepoTestE", "LocalRepoModuleE", "7.0",
 394             null, null, false, jamDir);
 395 
 396         // Installs #1, #2, and #3
 397         mai1 = repo.install(jamFile1.getCanonicalFile().toURI());
 398         check(mai1 != null);
 399         mai2 = repo.install(jamFile2.getCanonicalFile().toURI());
 400         check(mai2 != null);
 401         mai3 = repo.install(jamFile3.getCanonicalFile().toURI());
 402         check(mai3 != null);
 403 
 404         md = repo.find("LocalRepoModuleE", VersionConstraint.valueOf("7.0"));
 405         check(md != null);
 406         java.module.annotation.PlatformBinding platformBinding = md.getAnnotation
 407             (java.module.annotation.PlatformBinding.class);
 408         if (platformBinding != null) {
 409             if (platformBinding.platform().equals(platform)
 410                 && platformBinding.arch().equals(arch)) {
 411                 pass();
 412             } else {
 413                 fail();
 414             }
 415         } else {
 416             fail();
 417         }
 418 
 419         // Uninstall #1 and #2
 420         check(repo.uninstall(mai1));
 421         check(repo.uninstall(mai2));
 422 
 423         md = repo.find("LocalRepoModuleE", VersionConstraint.valueOf("7.0"));
 424         check (md == null);
 425 
 426         repo.reload();
 427         md = repo.find("LocalRepoModuleE", VersionConstraint.valueOf("7.0"));
 428         check (md != null);
 429 
 430         // Uninstall #3
 431         check(repo.uninstall(mai3));
 432 
 433         // Installs #1, #3, and #2
 434         mai1 = repo.install(jamFile1.getCanonicalFile().toURI());
 435         check(mai1 != null);
 436         mai3 = repo.install(jamFile3.getCanonicalFile().toURI());
 437         check(mai3 != null);
 438         mai2 = repo.install(jamFile2.getCanonicalFile().toURI());
 439         check(mai2 != null);
 440 
 441         md = repo.find("LocalRepoModuleE", VersionConstraint.valueOf("7.0"));
 442         check(md != null);
 443         platformBinding = md.getAnnotation
 444             (java.module.annotation.PlatformBinding.class);
 445         if (platformBinding == null) {
 446             pass();
 447         } else {
 448             fail();
 449         }
 450 
 451         // Uninstall #3 and #1
 452         check(repo.uninstall(mai3));
 453         check(repo.uninstall(mai1));
 454 
 455         md = repo.find("LocalRepoModuleE", VersionConstraint.valueOf("7.0"));
 456         check (md == null);
 457 
 458         // Clear event queues for the tests afterwards
 459         ec.clear();
 460 
 461         repo.shutdown();
 462 
 463         // REPOSITORY_SHUTDOWN event should be fired.
 464         check(!ec.initializeEventExists(repo));
 465         check(ec.shutdownEventExists(repo));
 466         check(!ec.installEventExists(repo, null));
 467         check(!ec.uninstallEventExists(repo, null));
 468 
 469         // Verify cannot initialize() after shutdown()
 470         try {
 471             repo.initialize();
 472             fail();
 473         } catch (IllegalStateException ex) {
 474             pass();
 475         }
 476 
 477         // No event should be fired.
 478         check(!ec.initializeEventExists(repo));
 479         check(!ec.shutdownEventExists(repo));
 480         check(!ec.installEventExists(repo, null));
 481         check(!ec.uninstallEventExists(repo, null));
 482 
 483         // When not debugging and there are no failures, remove test dirs
 484         if (!debug && failed == 0) {
 485             JamUtils.recursiveDelete(srcDir);
 486             JamUtils.recursiveDelete(jamDir);
 487             JamUtils.recursiveDelete(expandDir);
 488 
 489             repo.shutdown(); // sic: multiple shutdowns are OK
 490 
 491             // No event should be fired.
 492             check(!ec.initializeEventExists(repo));
 493             check(!ec.shutdownEventExists(repo));
 494             check(!ec.installEventExists(repo, null));
 495             check(!ec.uninstallEventExists(repo, null));
 496         }
 497     }
 498 
 499     static void runModule(Repository repo, String name) throws Exception {
 500         runModule(repo, name, null);
 501     }
 502 
 503     static void runModule(Repository repo, String name, String methodName) throws Exception {
 504         ModuleDefinition md = repo.find(name);
 505         check(md != null);
 506         println("=definition: " + md);
 507 
 508         Module m = md.getModuleInstance();
 509         String mainClass = md.getAnnotation(java.module.annotation.MainClass.class).value();
 510         println("=mainclass: " + mainClass);
 511         if (mainClass == null) {
 512             throw new Exception("No Main-Class attribute in the module definition");
 513         }
 514 
 515         println("=module: " + m);
 516         ClassLoader loader = m.getClassLoader();
 517         println("=loader: " + loader);
 518 
 519         Class<?> clazz = loader.loadClass(mainClass);
 520         println("=class: " + clazz);
 521 
 522         if (methodName == null) {
 523             Method method = clazz.getMethod("main", String[].class);
 524             method.invoke(null, (Object) (new String[0]));
 525         } else {
 526             Method method = clazz.getMethod(methodName, (Class<?>[]) null);
 527             String rc = (String) method.invoke(null, new Object[0]);
 528             check(methodName.equals(rc));
 529         }
 530         pass();
 531     }
 532 
 533     static List<ModuleDefinition> findModuleDefsInRepository(Repository r) {
 534         List<ModuleDefinition> result = new ArrayList<ModuleDefinition>();
 535         for (ModuleDefinition md : r.findAll()) {
 536             if (md.getRepository() == r) {
 537                 result.add(md);
 538             }
 539         }
 540         return result;
 541     }
 542 
 543     //--------------------- Infrastructure ---------------------------
 544     static volatile int passed = 0, failed = 0;
 545     static boolean pass() {passed++; return true;}
 546     static boolean fail() {failed++; Thread.dumpStack(); return false;}
 547     static boolean fail(String msg) {System.out.println(msg); return fail();}
 548     static void unexpected(Throwable t) {failed++; t.printStackTrace();}
 549     static boolean check(boolean cond) {if (cond) pass(); else fail(); return cond;}
 550     static boolean equal(Object x, Object y) {
 551         if (x == null ? y == null : x.equals(y)) return pass();
 552         else return fail(x + " not equal to " + y);}
 553     public static void main(String[] args) throws Throwable {
 554         try {realMain(args);} catch (Throwable t) {unexpected(t);}
 555         System.out.println("\nPassed = " + passed + " failed = " + failed);
 556         if (failed > 0) throw new AssertionError("Some tests failed");}
 557 }