From 0d7b3b0e9c77dab59491598c82798d6735723ef3 Mon Sep 17 00:00:00 2001 From: Iain Sproat Date: Mon, 21 Jun 2010 19:57:31 +0000 Subject: [PATCH] ProjectManager is now partially unit tested. git-svn-id: http://google-refine.googlecode.com/svn/trunk@1015 7d457c2a-affb-35e4-300a-418c747d4874 --- .../com/metaweb/gridworks/ProjectManager.java | 43 ++-- .../gridworks/io/FileProjectManager.java | 11 +- .../gridworks/io/ProjectUtilities.java | 3 +- .../com/metaweb/gridworks/model/Project.java | 14 +- .../gridworks/tests/ProjectManagerStub.java | 79 +++++++ .../gridworks/tests/ProjectManagerTests.java | 212 ++++++++++++++++++ .../gridworks/tests/model/ProjectStub.java | 10 + 7 files changed, 345 insertions(+), 27 deletions(-) create mode 100644 main/tests/server/src/com/metaweb/gridworks/tests/ProjectManagerStub.java create mode 100644 main/tests/server/src/com/metaweb/gridworks/tests/ProjectManagerTests.java create mode 100644 main/tests/server/src/com/metaweb/gridworks/tests/model/ProjectStub.java diff --git a/main/src/com/metaweb/gridworks/ProjectManager.java b/main/src/com/metaweb/gridworks/ProjectManager.java index 2fb4359ee..db47c4045 100644 --- a/main/src/com/metaweb/gridworks/ProjectManager.java +++ b/main/src/com/metaweb/gridworks/ProjectManager.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -19,7 +20,11 @@ import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.preference.PreferenceStore; import com.metaweb.gridworks.preference.TopList; - +/** + * ProjectManager is responsible for loading and saving the workspace and projects. + * + * + */ public abstract class ProjectManager { // last n expressions used across all projects static protected final int s_expressionHistoryMax = 100; @@ -49,6 +54,14 @@ public abstract class ProjectManager { static public ProjectManager singleton; + protected ProjectManager(){ + _projectsMetadata = new HashMap(); + _preferenceStore = new PreferenceStore(); + _projects = new HashMap(); + + preparePreferenceStore(_preferenceStore); + } + /** * Registers the project in the memory of the current session * @param project @@ -102,26 +115,28 @@ public abstract class ProjectManager { */ public void ensureProjectSaved(long id) { synchronized(this){ - ProjectMetadata metadata = _projectsMetadata.get(id); + ProjectMetadata metadata = this.getProjectMetadata(id); if (metadata != null) { try { saveMetadata(metadata, id); } catch (Exception e) { e.printStackTrace(); } - } + }//FIXME what should be the behaviour if metadata is null? i.e. not found - Project project = _projects.get(id); - if (project != null && metadata.getModified().after(project.lastSave)) { + Project project = getProject(id); + if (project != null && metadata != null && metadata.getModified().after(project.getLastSave())) { try { saveProject(project); } catch (Exception e) { e.printStackTrace(); } - } + }//FIXME what should be the behaviour if project is null? i.e. not found or loaded. + //FIXME what should happen if the metadata is found, but not the project? or vice versa? } } + /** * Save project metadata to the data store * @param metadata @@ -138,7 +153,7 @@ public abstract class ProjectManager { /** * Save workspace and all projects to data store - * @param b + * @param allModified */ public void save(boolean allModified) { if (allModified || _busy == 0) { @@ -175,23 +190,23 @@ public abstract class ProjectManager { */ protected void saveProjects(boolean allModified) { List records = new ArrayList(); - Date now = new Date(); + Date startTimeOfSave = new Date(); synchronized (this) { for (long id : _projectsMetadata.keySet()) { - ProjectMetadata metadata = _projectsMetadata.get(id); - Project project = _projects.get(id); + ProjectMetadata metadata = getProjectMetadata(id); + Project project = getProject(id); if (project != null) { boolean hasUnsavedChanges = - metadata.getModified().getTime() > project.lastSave.getTime(); + metadata.getModified().getTime() > project.getLastSave().getTime(); if (hasUnsavedChanges) { - long msecsOverdue = now.getTime() - project.lastSave.getTime(); + long msecsOverdue = startTimeOfSave.getTime() - project.getLastSave().getTime(); records.add(new SaveRecord(project, msecsOverdue)); - } else if (now.getTime() - project.lastSave.getTime() > s_projectFlushDelay) { + } else if (startTimeOfSave.getTime() - project.getLastSave().getTime() > s_projectFlushDelay) { /* * It's been a while since the project was last saved and it hasn't been * modified. We can safely remove it from the cache to save some memory. @@ -222,7 +237,7 @@ public abstract class ProjectManager { for (int i = 0; i < records.size() && - (allModified || (new Date().getTime() - now.getTime() < s_quickSaveTimeout)); + (allModified || (new Date().getTime() - startTimeOfSave.getTime() < s_quickSaveTimeout)); i++) { try { diff --git a/main/src/com/metaweb/gridworks/io/FileProjectManager.java b/main/src/com/metaweb/gridworks/io/FileProjectManager.java index b6bb66fdd..936be1129 100644 --- a/main/src/com/metaweb/gridworks/io/FileProjectManager.java +++ b/main/src/com/metaweb/gridworks/io/FileProjectManager.java @@ -8,7 +8,6 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.HashMap; import java.util.Properties; import java.util.zip.GZIPInputStream; @@ -27,14 +26,13 @@ import com.metaweb.gridworks.ProjectManager; import com.metaweb.gridworks.ProjectMetadata; import com.metaweb.gridworks.history.HistoryEntryManager; import com.metaweb.gridworks.model.Project; -import com.metaweb.gridworks.preference.PreferenceStore; import com.metaweb.gridworks.preference.TopList; public class FileProjectManager extends ProjectManager { protected File _workspaceDir; - final static Logger logger = LoggerFactory.getLogger("file_project_manager"); + final static Logger logger = LoggerFactory.getLogger("FileProjectManager"); static public synchronized void initialize(File dir) { if (singleton == null) { @@ -45,15 +43,10 @@ public class FileProjectManager extends ProjectManager { } protected FileProjectManager(File dir) { + super(); _workspaceDir = dir; _workspaceDir.mkdirs(); - _projectsMetadata = new HashMap(); - _preferenceStore = new PreferenceStore(); - _projects = new HashMap(); - - preparePreferenceStore(_preferenceStore); - load(); } diff --git a/main/src/com/metaweb/gridworks/io/ProjectUtilities.java b/main/src/com/metaweb/gridworks/io/ProjectUtilities.java index 877672b31..24968e06f 100644 --- a/main/src/com/metaweb/gridworks/io/ProjectUtilities.java +++ b/main/src/com/metaweb/gridworks/io/ProjectUtilities.java @@ -4,7 +4,6 @@ import java.io.File; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; -import java.util.Date; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; @@ -47,7 +46,7 @@ public class ProjectUtilities { oldFile.delete(); } - project.lastSave = new Date(); + project.setLastSave(); logger.info("Saved project '{}'",id); } diff --git a/main/src/com/metaweb/gridworks/model/Project.java b/main/src/com/metaweb/gridworks/model/Project.java index ff5d6d42b..840d8499b 100644 --- a/main/src/com/metaweb/gridworks/model/Project.java +++ b/main/src/com/metaweb/gridworks/model/Project.java @@ -31,7 +31,7 @@ public class Project { final public History history; transient public ProcessManager processManager = new ProcessManager(); - transient public Date lastSave = new Date(); + transient private Date _lastSave = new Date(); final static Logger logger = LoggerFactory.getLogger("project"); @@ -49,6 +49,16 @@ public class Project { this.history = new History(this); } + public Date getLastSave(){ + return this._lastSave; + } + /** + * Sets the lastSave time to now + */ + public void setLastSave(){ + this._lastSave = new Date(); + } + public ProjectMetadata getMetadata() { return ProjectManager.singleton.getProjectMetadata(id); } @@ -132,7 +142,7 @@ public class Project { public void update() { columnModel.update(); - recordModel.update(this); + recordModel.update(this); } diff --git a/main/tests/server/src/com/metaweb/gridworks/tests/ProjectManagerStub.java b/main/tests/server/src/com/metaweb/gridworks/tests/ProjectManagerStub.java new file mode 100644 index 000000000..f1853fe76 --- /dev/null +++ b/main/tests/server/src/com/metaweb/gridworks/tests/ProjectManagerStub.java @@ -0,0 +1,79 @@ +package com.metaweb.gridworks.tests; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.tools.tar.TarOutputStream; + +import com.metaweb.gridworks.ProjectManager; +import com.metaweb.gridworks.ProjectMetadata; +import com.metaweb.gridworks.history.HistoryEntryManager; +import com.metaweb.gridworks.model.Project; + +/** + * Stub used to make protected methods public for testing + * + */ +public class ProjectManagerStub extends ProjectManager { + + public ProjectManagerStub(){ + super(); + } + + @Override + public void deleteProject(long projectID) { + // empty + + } + + @Override + public void exportProject(long projectId, TarOutputStream tos) throws IOException { + // empty + } + + @Override + public HistoryEntryManager getHistoryEntryManager() { + // empty + return null; + } + + @Override + public void importProject(long projectID, InputStream inputStream, boolean gziped) throws IOException { + // empty + } + + @Override + protected Project loadProject(long id) { + // empty + return null; + } + + @Override + public boolean loadProjectMetadata(long projectID) { + // empty + return false; + } + + @Override + public void saveMetadata(ProjectMetadata metadata, long projectId) throws Exception { + // empty + + } + + @Override + public void saveProject(Project project) { + // empty + } + + //Overridden to make public for testing + @Override + public void saveProjects(boolean allModified){ + super.saveProjects(allModified); + } + + @Override + protected void saveWorkspace() { + // empty + } + +} diff --git a/main/tests/server/src/com/metaweb/gridworks/tests/ProjectManagerTests.java b/main/tests/server/src/com/metaweb/gridworks/tests/ProjectManagerTests.java new file mode 100644 index 000000000..7e3186d2a --- /dev/null +++ b/main/tests/server/src/com/metaweb/gridworks/tests/ProjectManagerTests.java @@ -0,0 +1,212 @@ +package com.metaweb.gridworks.tests; + +import java.util.Date; +import java.util.GregorianCalendar; + +import org.mockito.Mockito; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.metaweb.gridworks.ProjectMetadata; +import com.metaweb.gridworks.model.Project; +import com.metaweb.gridworks.tests.model.ProjectStub; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.never; + +public class ProjectManagerTests extends GridworksTest { + ProjectManagerStub pm; + ProjectManagerStub SUT; + Project project; + ProjectMetadata metadata; + + @BeforeTest + public void init() { + logger = LoggerFactory.getLogger(this.getClass()); + } + + @BeforeMethod + public void SetUp(){ + pm = new ProjectManagerStub(); + SUT = spy(pm); + project = mock(Project.class); + metadata = mock(ProjectMetadata.class); + } + + @AfterMethod + public void TearDown(){ + metadata = null; + project = null; + SUT = null; + pm = null; + } + + @Test + public void canRegisterProject(){ + + SUT.registerProject(project, metadata); + + AssertProjectRegistered(); + + verifyNoMoreInteractions(project); + verifyNoMoreInteractions(metadata); + } + + //TODO test registerProject in race condition + + @Test + public void canEnsureProjectSave(){ + whenGetSaveTimes(project, metadata); + registerProject(); + + //run test + SUT.ensureProjectSaved(project.id); + + //assert and verify + AssertProjectRegistered(); + try { + verify(SUT, times(1)).saveMetadata(metadata, project.id); + } catch (Exception e) { + Assert.fail(); + } + this.verifySaveTimeCompared(1); + verify(SUT, times(1)).saveProject(project); + + //ensure end + verifyNoMoreInteractions(project); + verifyNoMoreInteractions(metadata); + } + + //TODO test ensureProjectSave in race condition + + @Test + public void canSaveAllModified(){ + whenGetSaveTimes(project, metadata); //5 minute difference + registerProject(project, metadata); + + //add a second project to the cache + Project project2 = spy(new ProjectStub(2)); + ProjectMetadata metadata2 = mock(ProjectMetadata.class); + whenGetSaveTimes(project2, metadata2, 10); //not modified since the last save but within 30 seconds flush limit + registerProject(project2, metadata2); + + //check that the two projects are not the same + Assert.assertFalse(project.id == project2.id); + + SUT.save(true); + + verifySaved(project, metadata); + + verifySaved(project2, metadata2); + + verify(SUT, times(1)).saveWorkspace(); + } + + @Test + public void canFlushFromCache(){ + + whenGetSaveTimes(project, metadata, -10 );//already saved (10 seconds before) + registerProject(project, metadata); + Assert.assertSame(SUT.getProject(0), project); + + SUT.save(true); + + verify(metadata, times(1)).getModified(); + verify(project, times(2)).getLastSave(); + verify(SUT, never()).saveProject(project); + Assert.assertEquals(SUT.getProject(0), null); + verifyNoMoreInteractions(project); + verifyNoMoreInteractions(metadata); + + verify(SUT, times(1)).saveWorkspace(); + } + + @Test + public void cannotSaveWhenBusy(){ + registerProject(); + SUT.setBusy(true); + + SUT.save(false); + + verify(SUT, never()).saveProjects(Mockito.anyBoolean()); + verify(SUT, never()).saveWorkspace(); + verifyNoMoreInteractions(project); + verifyNoMoreInteractions(metadata); + } + + //TODO test canSaveAllModifiedWithRaceCondition + + @Test + public void canSaveSomeModified(){ + registerProject(); + whenGetSaveTimes(project, metadata ); + + SUT.save(false); //not busy + + verifySaved(project, metadata); + verify(SUT, times(1)).saveWorkspace(); + + } + //TODO test canSaveAllModifiedWithRaceCondition + + //-------------helpers------------- + + protected void registerProject(){ + this.registerProject(project, metadata); + } + protected void registerProject(Project proj, ProjectMetadata meta){ + SUT.registerProject(proj, meta); + } + + protected void AssertProjectRegistered(){ + Assert.assertEquals(SUT.getProject(project.id), project); + Assert.assertEquals(SUT.getProjectMetadata(project.id), metadata); + } + + protected void whenGetSaveTimes(Project proj, ProjectMetadata meta){ + whenGetSaveTimes(proj, meta, 5); + } + protected void whenGetSaveTimes(Project proj, ProjectMetadata meta, int secondsDifference){ + whenProjectGetLastSave(proj); + whenMetadataGetModified(meta, secondsDifference); + } + + protected void whenProjectGetLastSave(Project proj){ + Date projectLastSaveDate = new GregorianCalendar(1970,01,02,00,30,00).getTime(); + when(proj.getLastSave()).thenReturn(projectLastSaveDate); + } + + protected void whenMetadataGetModified(ProjectMetadata meta){ + whenMetadataGetModified(meta, 5*60); + } + protected void whenMetadataGetModified(ProjectMetadata meta, int secondsDifference){ + Date metadataModifiedDate = new GregorianCalendar(1970,01,02,00, 30, secondsDifference).getTime(); + when(meta.getModified()).thenReturn(metadataModifiedDate); + } + + protected void verifySaveTimeCompared(int times){ + verifySaveTimeCompared(project, metadata, times); + } + protected void verifySaveTimeCompared(Project project, ProjectMetadata metadata, int times){ + verify(metadata, times(times)).getModified(); + verify(project, times(times)).getLastSave(); + } + + protected void verifySaved(Project proj, ProjectMetadata meta){ + verify(meta, times(1)).getModified(); + verify(proj, times(2)).getLastSave(); + verify(SUT, times(1)).saveProject(proj); + + verifyNoMoreInteractions(proj); + verifyNoMoreInteractions(meta); + } +} diff --git a/main/tests/server/src/com/metaweb/gridworks/tests/model/ProjectStub.java b/main/tests/server/src/com/metaweb/gridworks/tests/model/ProjectStub.java new file mode 100644 index 000000000..4ccadfc04 --- /dev/null +++ b/main/tests/server/src/com/metaweb/gridworks/tests/model/ProjectStub.java @@ -0,0 +1,10 @@ +package com.metaweb.gridworks.tests.model; + +import com.metaweb.gridworks.model.Project; + + +public class ProjectStub extends Project { + public ProjectStub(long id){ + super(id); + } +}