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