001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.api.project;
043:
044: import java.io.IOException;
045: import java.lang.ref.Reference;
046: import java.lang.ref.WeakReference;
047: import java.util.Arrays;
048: import java.util.Collections;
049: import java.util.HashSet;
050: import java.util.Set;
051: import java.util.logging.Level;
052: import org.netbeans.junit.Log;
053: import org.netbeans.junit.NbTestCase;
054: import org.netbeans.modules.projectapi.TimedWeakReference;
055: import org.openide.filesystems.FileAttributeEvent;
056: import org.openide.filesystems.FileChangeListener;
057: import org.openide.filesystems.FileEvent;
058: import org.openide.filesystems.FileObject;
059: import org.openide.filesystems.FileRenameEvent;
060: import org.openide.util.Mutex;
061: import org.openide.util.test.MockLookup;
062:
063: /* XXX tests needed:
064: * - testModifiedProjectsNotGCd
065: * ensure that modified projects cannot be collected
066: * and that unmodified projects can (incl. after a save)
067: * - testIsProject
068: * - testCallFindProjectWithinLoadProjectProhibited
069: * - testDeleteAndRecreateProject
070: */
071:
072: /**
073: * Test ProjectManager find and save functionality.
074: * @author Jesse Glick
075: */
076: public class ProjectManagerTest extends NbTestCase implements
077: FileChangeListener {
078:
079: static {
080: // For easier testing.
081: TimedWeakReference.TIMEOUT = 1000;
082: }
083:
084: public ProjectManagerTest(String name) {
085: super (name);
086: }
087:
088: private FileObject scratch;
089: private FileObject goodproject;
090: private FileObject goodproject2;
091: private FileObject badproject;
092: private FileObject mysteryproject;
093: private ProjectManager pm;
094:
095: protected @Override
096: Level logLevel() {
097: return Level.FINE;
098: }
099:
100: protected void setUp() throws Exception {
101: super .setUp();
102: scratch = TestUtil.makeScratchDir(this );
103: scratch.getFileSystem().addFileChangeListener(this );
104: goodproject = scratch.createFolder("good");
105: goodproject.createFolder("testproject");
106: goodproject2 = scratch.createFolder("good2");
107: goodproject2.createFolder("testproject");
108: badproject = scratch.createFolder("bad");
109: badproject.createFolder("testproject").createData("broken");
110: mysteryproject = scratch.createFolder("mystery");
111: MockLookup.setInstances(TestUtil.testProjectFactory());
112: pm = ProjectManager.getDefault();
113: pm.reset();
114: TestUtil.BROKEN_PROJECT_LOAD_LOCK = null;
115: }
116:
117: protected void tearDown() throws Exception {
118: scratch.getFileSystem().removeFileChangeListener(this );
119: scratch = null;
120: goodproject = null;
121: badproject = null;
122: mysteryproject = null;
123: pm = null;
124: super .tearDown();
125: }
126:
127: public void testFindProject() throws Exception {
128: Project p = null;
129: CharSequence log = Log.enable("TIMER", Level.FINE);
130: try {
131: p = pm.findProject(goodproject);
132: } catch (IOException e) {
133: fail("Should not fail to load goodproject: " + e);
134: }
135: if (log.toString().indexOf("Project") < 0) {
136: fail("Shall log a message to timers/counters:\n" + log);
137: }
138: assertNotNull("Should have recognized goodproject", p);
139: assertEquals("Correct project directory set", goodproject, p
140: .getProjectDirectory());
141: Project p2 = null;
142: try {
143: p2 = pm.findProject(badproject);
144: fail("Should not have succeeded loading badproject");
145: } catch (IOException e) {
146: // OK
147: }
148: try {
149: p2 = pm.findProject(mysteryproject);
150: } catch (IOException e) {
151: fail("Should not have failed loading mysteryproject: " + e);
152: }
153: assertNull("Should not have been able to load mysteryproject",
154: p2);
155: assertEquals("Repeated find calls should give same result", p,
156: pm.findProject(goodproject));
157: assertEquals(
158: "ProjectFactory was called only once on goodproject",
159: 1, TestUtil.projectLoadCount(goodproject));
160: }
161:
162: public void testFindProjectGC() throws Exception {
163: Project p = null;
164: try {
165: p = pm.findProject(goodproject);
166: } catch (IOException e) {
167: fail("Should not fail to load goodproject: " + e);
168: }
169: assertNotNull("Should have recognized goodproject", p);
170: assertEquals(
171: "ProjectFactory was called once so far on goodproject",
172: 1, TestUtil.projectLoadCount(goodproject));
173: Reference<?> pref = new WeakReference<Object>(p);
174: p = null;
175: Thread.sleep(TimedWeakReference.TIMEOUT); // make sure it is not being held strongly
176: assertGC(
177: "Can collect an unused project with project directory still set",
178: pref);
179: p = pm.findProject(goodproject);
180: assertNotNull("Can load goodproject again", p);
181: assertEquals("Correct project directory set", goodproject, p
182: .getProjectDirectory());
183: assertEquals("ProjectFactory was called again on goodproject",
184: 2, TestUtil.projectLoadCount(goodproject));
185: pref = new WeakReference<Object>(p);
186: p = null;
187: Reference<?> dirref = new WeakReference<Object>(goodproject);
188: goodproject = null;
189: assertGC("Collected the project directory", dirref);
190: assertGC(
191: "Can collect an unused project with project directory discarded",
192: pref);
193: goodproject = scratch.getFileObject("good");
194: assertNotNull("goodproject dir still exists", goodproject);
195: p = pm.findProject(goodproject);
196: assertNotNull("Can load goodproject yet again", p);
197: assertEquals("Correct project directory set again",
198: goodproject, p.getProjectDirectory());
199: assertEquals(
200: "ProjectFactory was called only once on new goodproject folder object",
201: 1, TestUtil.projectLoadCount(goodproject));
202: }
203:
204: public void testFindProjectDoesNotCacheLoadErrors()
205: throws Exception {
206: Project p = null;
207: try {
208: p = pm.findProject(badproject);
209: fail("Should not have been able to load badproject");
210: } catch (IOException e) {
211: // Expected.
212: }
213: FileObject badprojectSubdir = badproject
214: .getFileObject("testproject");
215: assertNotNull("Has testproject", badprojectSubdir);
216: FileObject brokenFile = badprojectSubdir
217: .getFileObject("broken");
218: assertNotNull("Has broken file", brokenFile);
219: brokenFile.delete();
220: try {
221: p = pm.findProject(badproject);
222: } catch (IOException e) {
223: fail("badproject has been corrected, should not fail to load now: "
224: + e);
225: }
226: assertNotNull("Loaded project", p);
227: assertEquals("Right project dir", badproject, p
228: .getProjectDirectory());
229: badprojectSubdir.createData("broken");
230: Project p2 = null;
231: try {
232: p2 = pm.findProject(badproject);
233: } catch (IOException e) {
234: fail("badproject is broken on disk but should still be in cache: "
235: + e);
236: }
237: assertEquals("Cached badproject", p, p2);
238: Reference<?> pref = new WeakReference<Object>(p);
239: p = null;
240: p2 = null;
241: assertGC("Collected badproject cache", pref);
242: try {
243: p = pm.findProject(badproject);
244: fail("Should not have been able to load badproject now that it is rebroken and not in cache");
245: } catch (IOException e) {
246: // Expected.
247: }
248: }
249:
250: public void testModify() throws Exception {
251: Project p1 = pm.findProject(goodproject);
252: Project p2 = pm.findProject(goodproject2);
253: Set<Project> p1p2 = new HashSet<Project>(Arrays.asList(p1, p2));
254: assertEquals("start with no modified projects", Collections
255: .emptySet(), pm.getModifiedProjects());
256: assertTrue("p1 is not yet modified", !pm.isModified(p1));
257: assertTrue("p2 is not yet modified", !pm.isModified(p2));
258: TestUtil.modify(p1);
259: assertEquals("just p1 has been modified", Collections
260: .singleton(p1), pm.getModifiedProjects());
261: assertTrue("p1 is modified", pm.isModified(p1));
262: assertTrue("p2 is still not modified", !pm.isModified(p2));
263: TestUtil.modify(p2);
264: assertEquals("now both p1 and p2 have been modified", p1p2, pm
265: .getModifiedProjects());
266: assertTrue("p1 is modified", pm.isModified(p1));
267: assertTrue("and p2 is modified too", pm.isModified(p2));
268: }
269:
270: public void testSave() throws Exception {
271: Project p1 = pm.findProject(goodproject);
272: Project p2 = pm.findProject(goodproject2);
273: Set<Project> p1p2 = new HashSet<Project>(Arrays.asList(p1, p2));
274: assertEquals("start with no modified projects", Collections
275: .emptySet(), pm.getModifiedProjects());
276: assertEquals("p1 has never been saved", 0, TestUtil
277: .projectSaveCount(p1));
278: assertEquals("p2 has never been saved", 0, TestUtil
279: .projectSaveCount(p2));
280: TestUtil.modify(p1);
281: assertEquals("just p1 was modified", Collections.singleton(p1),
282: pm.getModifiedProjects());
283: TestUtil.modify(p2);
284: assertEquals("both p1 and p2 were modified now", p1p2, pm
285: .getModifiedProjects());
286: pm.saveProject(p1);
287: assertEquals("p1 was saved so just p2 is modified", Collections
288: .singleton(p2), pm.getModifiedProjects());
289: assertEquals("p1 was saved once", 1, TestUtil
290: .projectSaveCount(p1));
291: assertEquals("p2 has not yet been saved", 0, TestUtil
292: .projectSaveCount(p2));
293: pm.saveProject(p2);
294: assertEquals("now p1 and p2 are both saved", Collections
295: .emptySet(), pm.getModifiedProjects());
296: assertEquals("p1 was saved once", 1, TestUtil
297: .projectSaveCount(p1));
298: assertEquals("p2 has now been saved once", 1, TestUtil
299: .projectSaveCount(p2));
300: pm.saveProject(p2);
301: assertEquals("saving p2 again has no effect", Collections
302: .emptySet(), pm.getModifiedProjects());
303: assertEquals("p1 still saved just once", 1, TestUtil
304: .projectSaveCount(p1));
305: assertEquals(
306: "redundant call to save did not really save again", 1,
307: TestUtil.projectSaveCount(p2));
308: TestUtil.modify(p1);
309: TestUtil.modify(p2);
310: assertEquals("both p1 and p2 modified again", p1p2, pm
311: .getModifiedProjects());
312: pm.saveAllProjects();
313: assertEquals("saveAllProjects saved both p1 and p2",
314: Collections.EMPTY_SET, pm.getModifiedProjects());
315: assertEquals("p1 was saved again by saveAllProjects", 2,
316: TestUtil.projectSaveCount(p1));
317: assertEquals("p2 was saved again by saveAllProjects", 2,
318: TestUtil.projectSaveCount(p2));
319: pm.saveAllProjects();
320: assertEquals("saveAllProjects twice has no effect",
321: Collections.EMPTY_SET, pm.getModifiedProjects());
322: assertEquals("p1 still only saved twice", 2, TestUtil
323: .projectSaveCount(p1));
324: assertEquals("p2 still only saved twice", 2, TestUtil
325: .projectSaveCount(p2));
326: }
327:
328: public void testSaveError() throws Exception {
329: Project p1 = pm.findProject(goodproject);
330: Project p2 = pm.findProject(goodproject2);
331: TestUtil.modify(p1);
332: TestUtil.modify(p2);
333: Set<Project> p1p2 = new HashSet<Project>(Arrays.asList(p1, p2));
334: assertEquals("both p1 and p2 are modified", p1p2, pm
335: .getModifiedProjects());
336: TestUtil
337: .setProjectSaveWillFail(p1, new IOException("expected"));
338: try {
339: pm.saveProject(p1);
340: fail("Saving p1 should have failed with an IOException");
341: } catch (IOException e) {
342: // Good.
343: }
344: assertTrue("p1 is still modified", pm.isModified(p1));
345: assertEquals("both p1 and p2 are still modified", p1p2, pm
346: .getModifiedProjects());
347: pm.saveProject(p1);
348: assertEquals("p1 was saved so just p2 is modified", Collections
349: .singleton(p2), pm.getModifiedProjects());
350: assertEquals("p1 was saved once", 1, TestUtil
351: .projectSaveCount(p1));
352: TestUtil.modify(p1);
353: TestUtil.setProjectSaveWillFail(p1, new RuntimeException(
354: "expected"));
355: try {
356: pm.saveProject(p1);
357: fail("Saving p1 should have failed with a RuntimeException");
358: } catch (RuntimeException e) {
359: // Good.
360: }
361: assertTrue("p1 is still modified", pm.isModified(p1));
362: TestUtil.setProjectSaveWillFail(p1, new Error("expected"));
363: try {
364: pm.saveProject(p1);
365: fail("Saving p1 should have failed with an Error");
366: } catch (Error e) {
367: // Good.
368: }
369: assertTrue("p1 is still modified", pm.isModified(p1));
370: assertEquals("both p1 and p2 are still modified", p1p2, pm
371: .getModifiedProjects());
372: TestUtil
373: .setProjectSaveWillFail(p1, new IOException("expected"));
374: try {
375: pm.saveAllProjects();
376: fail("Saving p1 should have failed with an IOException");
377: } catch (IOException e) {
378: // Good.
379: }
380: assertTrue("p1 is still modified", pm.isModified(p1));
381: assertTrue("p1 is still in the modified set", pm
382: .getModifiedProjects().contains(p1));
383: assertEquals("p1 was still only saved once", 1, TestUtil
384: .projectSaveCount(p1));
385: pm.saveAllProjects();
386: assertEquals("both p1 and p2 are now saved",
387: Collections.EMPTY_SET, pm.getModifiedProjects());
388: assertEquals("p1 was now saved twice", 2, TestUtil
389: .projectSaveCount(p1));
390: assertEquals(
391: "p2 was saved exactly once (by one or the other saveAllProjects)",
392: 1, TestUtil.projectSaveCount(p2));
393: }
394:
395: public void testClearNonProjectCache() throws Exception {
396: FileObject p1 = scratch.createFolder("p1");
397: p1.createFolder("testproject");
398: Project proj1 = pm.findProject(p1);
399: assertNotNull("p1 immediately recognized as a project", proj1);
400: FileObject p2 = scratch.createFolder("p2");
401: assertNull("p2 not yet recognized as a project", pm
402: .findProject(p2));
403: FileObject p2a = scratch.createFolder("p2a");
404: assertNull("p2a not yet recognized as a project", pm
405: .findProject(p2a));
406: FileObject p3 = scratch.createFolder("p3");
407: FileObject p3broken = p3.createFolder("testproject")
408: .createData("broken");
409: try {
410: pm.findProject(p3);
411: fail("p3 should throw an error");
412: } catch (IOException e) {
413: // Correct.
414: }
415: p2.createFolder("testproject");
416: p2a.createFolder("testproject");
417: p3broken.delete();
418: pm.clearNonProjectCache();
419: assertNotNull("now p2 is recognized as a project", pm
420: .findProject(p2));
421: assertNotNull("now p2a is recognized as a project", pm
422: .findProject(p2a));
423: assertNotNull("now p3 is recognized as a non-broken project",
424: pm.findProject(p3));
425: assertEquals("p1 still recognized as a project", proj1, pm
426: .findProject(p1));
427: }
428:
429: public void testLoadExceptionWithConcurrentLoad() throws Exception {
430: TestUtil.BROKEN_PROJECT_LOAD_LOCK = new Object();
431: final Object GOING = new Object();
432: // Enter from one thread and start to load a broken project, but then pause.
433: synchronized (GOING) {
434: new Thread("initial load") {
435: public void run() {
436: try {
437: synchronized (GOING) {
438: GOING.notify();
439: }
440: pm.findProject(badproject);
441: assert false : "Should not have loaded project successfully #1";
442: } catch (IOException e) {
443: // Right.
444: }
445: }
446: }.start();
447: GOING.wait();
448: }
449: // Now #1 should be waiting on BROKEN_PROJECT_LOAD_LOCK.
450: Object previousLoadLock = TestUtil.BROKEN_PROJECT_LOAD_LOCK;
451: TestUtil.BROKEN_PROJECT_LOAD_LOCK = null; // don't block second load
452: // In another thread, start to try to load the same project.
453: //System.err.println("midpoint");
454: synchronized (GOING) {
455: final boolean[] FINISHED_2 = new boolean[1];
456: new Thread("parallel load") {
457: public void run() {
458: synchronized (GOING) {
459: GOING.notify();
460: }
461: try {
462: pm.findProject(badproject);
463: assert false : "Should not have loaded project successfully #2";
464: } catch (IOException e) {
465: // Right.
466: FINISHED_2[0] = true;
467: }
468: synchronized (GOING) {
469: GOING.notify();
470: }
471: }
472: }.start();
473: // Make sure #2 is running.
474: GOING.wait();
475: // XXX race condition here - what if we continue in main thr before pm.fP is called in #2??
476: assertFalse("not yet finished #2", FINISHED_2[0]);
477: // Now let #1 continue.
478: synchronized (previousLoadLock) {
479: previousLoadLock.notify();
480: }
481: // And wait (a reasonable amount of time) for #2 to exit.
482: GOING.wait(9999);
483: assertTrue("finished #2 without deadlock", FINISHED_2[0]);
484: }
485: }
486:
487: public void testNotifyDeleted() throws Exception {
488: FileObject p1 = scratch.createFolder("p1");
489: FileObject p1TestProject = p1.createFolder("testproject");
490:
491: Project project1 = pm.findProject(p1);
492:
493: assertNotNull("project1 is recognized", project1);
494: p1TestProject.delete();
495: TestUtil.notifyDeleted(project1);
496:
497: assertFalse("project1 is not valid", pm.isValid(project1));
498: assertNull("project1 is deleted", pm.findProject(p1));
499:
500: FileObject p2 = scratch.createFolder("p2");
501: FileObject p2TestProject = p2.createFolder("testproject");
502:
503: Project project2 = pm.findProject(p2);
504:
505: assertNotNull("project2 is recognized", project2);
506: TestUtil.notifyDeleted(project2);
507:
508: assertFalse("project2 is not valid", pm.isValid(project2));
509:
510: Project project2b = pm.findProject(p2);
511:
512: assertTrue("project2 is newly recognized",
513: project2b != project2);
514: assertNotNull("project2 is newly recognized", project2b);
515:
516: FileObject p3 = scratch.createFolder("p3");
517: FileObject p3TestProject = p3.createFolder("testproject");
518:
519: Project project3 = pm.findProject(p3);
520:
521: assertNotNull("project3 is recognized", project3);
522: TestUtil.modify(project3);
523: assertTrue("project3 is modified", pm.isModified(project3));
524: TestUtil.notifyDeleted(project3);
525:
526: assertFalse("project3 is not valid", pm.isValid(project3));
527:
528: boolean wasException = false;
529: try {
530: pm.isModified(project3);
531: } catch (IllegalArgumentException e) {
532: //the project is no longer recognized by the ProjectManager.
533: wasException = true;
534: }
535:
536: assertTrue(
537: "the project is no longer recognized by the ProjectManager",
538: wasException);
539:
540: FileObject p4 = scratch.createFolder("p4");
541: FileObject p4TestProject = p4.createFolder("testproject");
542:
543: Project project4 = pm.findProject(p4);
544:
545: assertNotNull("project4 is recognized", project4);
546: TestUtil.notifyDeleted(project4);
547:
548: assertFalse("project4 is not valid", pm.isValid(project3));
549:
550: wasException = false;
551: try {
552: TestUtil.notifyDeleted(project4);
553: } catch (IllegalStateException e) {
554: //the project is no longer recognized by the ProjectManager.
555: wasException = true;
556: }
557:
558: assertTrue(
559: "An IllegalStateException was thrown when calling notifyDeleted twice.",
560: wasException);
561: }
562:
563: public void testClearNonProjectCacheInWriteAccess()
564: throws Exception {
565: ProjectManager.mutex().writeAccess(
566: new Mutex.ExceptionAction<Void>() {
567: public Void run() throws Exception {
568: testClearNonProjectCache();
569: return null;
570: }
571: });
572: }
573:
574: public void testClearNonProjectCacheInReadAccess() throws Exception {
575: ProjectManager.mutex().readAccess(
576: new Mutex.ExceptionAction<Void>() {
577: public Void run() throws Exception {
578: testClearNonProjectCache();
579: return null;
580: }
581: });
582: }
583:
584: private void assertNoAccess() {
585: assertFalse("No read access", ProjectManager.mutex()
586: .isReadAccess());
587: assertFalse("No write access", ProjectManager.mutex()
588: .isWriteAccess());
589: }
590:
591: public void fileFolderCreated(FileEvent fe) {
592: assertNoAccess();
593: }
594:
595: public void fileDataCreated(FileEvent fe) {
596: assertNoAccess();
597: }
598:
599: public void fileChanged(FileEvent fe) {
600: assertNoAccess();
601: }
602:
603: public void fileDeleted(FileEvent fe) {
604: assertNoAccess();
605: }
606:
607: public void fileRenamed(FileRenameEvent fe) {
608: assertNoAccess();
609: }
610:
611: public void fileAttributeChanged(FileAttributeEvent fe) {
612: assertNoAccess();
613: }
614:
615: }
|