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 }