ProjectManager is now partially unit tested.

git-svn-id: http://google-refine.googlecode.com/svn/trunk@1015 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
Iain Sproat 2010-06-21 19:57:31 +00:00
parent f690c55fab
commit 0d7b3b0e9c
7 changed files with 345 additions and 27 deletions

View File

@ -6,6 +6,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; 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.PreferenceStore;
import com.metaweb.gridworks.preference.TopList; import com.metaweb.gridworks.preference.TopList;
/**
* ProjectManager is responsible for loading and saving the workspace and projects.
*
*
*/
public abstract class ProjectManager { public abstract class ProjectManager {
// last n expressions used across all projects // last n expressions used across all projects
static protected final int s_expressionHistoryMax = 100; static protected final int s_expressionHistoryMax = 100;
@ -49,6 +54,14 @@ public abstract class ProjectManager {
static public ProjectManager singleton; static public ProjectManager singleton;
protected ProjectManager(){
_projectsMetadata = new HashMap<Long, ProjectMetadata>();
_preferenceStore = new PreferenceStore();
_projects = new HashMap<Long, Project>();
preparePreferenceStore(_preferenceStore);
}
/** /**
* Registers the project in the memory of the current session * Registers the project in the memory of the current session
* @param project * @param project
@ -102,26 +115,28 @@ public abstract class ProjectManager {
*/ */
public void ensureProjectSaved(long id) { public void ensureProjectSaved(long id) {
synchronized(this){ synchronized(this){
ProjectMetadata metadata = _projectsMetadata.get(id); ProjectMetadata metadata = this.getProjectMetadata(id);
if (metadata != null) { if (metadata != null) {
try { try {
saveMetadata(metadata, id); saveMetadata(metadata, id);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }//FIXME what should be the behaviour if metadata is null? i.e. not found
Project project = _projects.get(id); Project project = getProject(id);
if (project != null && metadata.getModified().after(project.lastSave)) { if (project != null && metadata != null && metadata.getModified().after(project.getLastSave())) {
try { try {
saveProject(project); saveProject(project);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); 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 * Save project metadata to the data store
* @param metadata * @param metadata
@ -138,7 +153,7 @@ public abstract class ProjectManager {
/** /**
* Save workspace and all projects to data store * Save workspace and all projects to data store
* @param b * @param allModified
*/ */
public void save(boolean allModified) { public void save(boolean allModified) {
if (allModified || _busy == 0) { if (allModified || _busy == 0) {
@ -175,23 +190,23 @@ public abstract class ProjectManager {
*/ */
protected void saveProjects(boolean allModified) { protected void saveProjects(boolean allModified) {
List<SaveRecord> records = new ArrayList<SaveRecord>(); List<SaveRecord> records = new ArrayList<SaveRecord>();
Date now = new Date(); Date startTimeOfSave = new Date();
synchronized (this) { synchronized (this) {
for (long id : _projectsMetadata.keySet()) { for (long id : _projectsMetadata.keySet()) {
ProjectMetadata metadata = _projectsMetadata.get(id); ProjectMetadata metadata = getProjectMetadata(id);
Project project = _projects.get(id); Project project = getProject(id);
if (project != null) { if (project != null) {
boolean hasUnsavedChanges = boolean hasUnsavedChanges =
metadata.getModified().getTime() > project.lastSave.getTime(); metadata.getModified().getTime() > project.getLastSave().getTime();
if (hasUnsavedChanges) { if (hasUnsavedChanges) {
long msecsOverdue = now.getTime() - project.lastSave.getTime(); long msecsOverdue = startTimeOfSave.getTime() - project.getLastSave().getTime();
records.add(new SaveRecord(project, msecsOverdue)); 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 * 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. * 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; for (int i = 0;
i < records.size() && i < records.size() &&
(allModified || (new Date().getTime() - now.getTime() < s_quickSaveTimeout)); (allModified || (new Date().getTime() - startTimeOfSave.getTime() < s_quickSaveTimeout));
i++) { i++) {
try { try {

View File

@ -8,7 +8,6 @@ import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.HashMap;
import java.util.Properties; import java.util.Properties;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@ -27,14 +26,13 @@ import com.metaweb.gridworks.ProjectManager;
import com.metaweb.gridworks.ProjectMetadata; import com.metaweb.gridworks.ProjectMetadata;
import com.metaweb.gridworks.history.HistoryEntryManager; import com.metaweb.gridworks.history.HistoryEntryManager;
import com.metaweb.gridworks.model.Project; import com.metaweb.gridworks.model.Project;
import com.metaweb.gridworks.preference.PreferenceStore;
import com.metaweb.gridworks.preference.TopList; import com.metaweb.gridworks.preference.TopList;
public class FileProjectManager extends ProjectManager { public class FileProjectManager extends ProjectManager {
protected File _workspaceDir; 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) { static public synchronized void initialize(File dir) {
if (singleton == null) { if (singleton == null) {
@ -45,15 +43,10 @@ public class FileProjectManager extends ProjectManager {
} }
protected FileProjectManager(File dir) { protected FileProjectManager(File dir) {
super();
_workspaceDir = dir; _workspaceDir = dir;
_workspaceDir.mkdirs(); _workspaceDir.mkdirs();
_projectsMetadata = new HashMap<Long, ProjectMetadata>();
_preferenceStore = new PreferenceStore();
_projects = new HashMap<Long, Project>();
preparePreferenceStore(_preferenceStore);
load(); load();
} }

View File

@ -4,7 +4,6 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.LineNumberReader; import java.io.LineNumberReader;
import java.util.Date;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@ -47,7 +46,7 @@ public class ProjectUtilities {
oldFile.delete(); oldFile.delete();
} }
project.lastSave = new Date(); project.setLastSave();
logger.info("Saved project '{}'",id); logger.info("Saved project '{}'",id);
} }

View File

@ -31,7 +31,7 @@ public class Project {
final public History history; final public History history;
transient public ProcessManager processManager = new ProcessManager(); 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"); final static Logger logger = LoggerFactory.getLogger("project");
@ -49,6 +49,16 @@ public class Project {
this.history = new History(this); 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() { public ProjectMetadata getMetadata() {
return ProjectManager.singleton.getProjectMetadata(id); return ProjectManager.singleton.getProjectMetadata(id);
} }
@ -132,7 +142,7 @@ public class Project {
public void update() { public void update() {
columnModel.update(); columnModel.update();
recordModel.update(this); recordModel.update(this);
} }

View File

@ -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
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}