blob: f458b3dc51a446550b0300da77a01fae8fb955c6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2012 Tasktop Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.tasks.index.tests;
import static org.eclipse.mylyn.commons.sdk.util.CommonTestUtil.createTempFolder;
import static org.eclipse.mylyn.commons.sdk.util.CommonTestUtil.deleteFolderRecursively;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.mylyn.commons.core.DelegatingProgressMonitor;
import org.eclipse.mylyn.internal.tasks.core.AbstractTask;
import org.eclipse.mylyn.internal.tasks.core.LocalTask;
import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex;
import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex.TaskCollector;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.tasks.core.data.DefaultTaskSchema;
import org.eclipse.mylyn.tasks.core.data.TaskAttachmentMapper;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskMapper;
import org.junit.Test;
/**
* @author David Green
*/
public class TaskListIndexTest extends AbstractTaskListIndexTest {
private static final org.eclipse.mylyn.tasks.core.data.AbstractTaskSchema.Field FIELD_SUMMARY = DefaultTaskSchema
.getInstance().SUMMARY;
private static final org.eclipse.mylyn.tasks.core.data.AbstractTaskSchema.Field FIELD_DATE_CREATION = DefaultTaskSchema
.getInstance().DATE_CREATION;
private static class TestTaskCollector extends TaskCollector {
private final List<ITask> tasks = new ArrayList<ITask>();
@Override
public void collect(ITask task) {
tasks.add(task);
}
public List<ITask> getTasks() {
return tasks;
}
}
@Test
public void testMatchesLocalTaskOnSummary() throws InterruptedException {
setupIndex();
ITask task = context.createLocalTask();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
assertTrue(index.matches(task, task.getSummary()));
assertFalse(index.matches(task, "" + System.currentTimeMillis()));
index.setDefaultField(FIELD_SUMMARY);
assertTrue(index.matches(task, task.getSummary()));
assertFalse(index.matches(task, "" + System.currentTimeMillis()));
}
@Test
public void testMatchesLocalTaskOnDescription() throws InterruptedException {
setupIndex();
ITask task = context.createLocalTask();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
assertTrue(index.matches(task, ((LocalTask) task).getNotes()));
assertFalse(index.matches(task, "unlikely-akjfsaow"));
index.setDefaultField(FIELD_SUMMARY);
assertFalse(index.matches(task, ((LocalTask) task).getNotes()));
}
@Test
public void testMatchesRepositoryTaskOnSummary() throws InterruptedException, CoreException {
setupIndex();
ITask task = context.createRepositoryTask();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
String summary = task.getSummary();
assertTrue(index.matches(task, summary));
assertTrue(index.matches(task, summary.substring(0, summary.length() - 3) + "*"));
assertFalse(index.matches(task, "" + System.currentTimeMillis()));
index.setDefaultField(FIELD_SUMMARY);
assertTrue(index.matches(task, summary));
assertTrue(index.matches(task, summary.substring(0, summary.length() - 3) + "*"));
assertFalse(index.matches(task, "" + System.currentTimeMillis()));
}
@Test
public void testMatchesRepositoryTaskOnDescription() throws InterruptedException, CoreException {
setupIndex();
ITask task = context.createRepositoryTask();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
TaskData taskData = context.getDataManager().getTaskData(task);
assertNotNull(taskData);
TaskMapper taskMapping = context.getMockRepositoryConnector().getTaskMapping(taskData);
assertTrue(index.matches(task, taskMapping.getDescription()));
assertFalse(index.matches(task, "unlikely-akjfsaow"));
index.setDefaultField(FIELD_SUMMARY);
assertFalse(index.matches(task, taskMapping.getDescription()));
}
@Test
public void testFind() throws InterruptedException {
setupIndex();
ITask task = context.createLocalTask();
index.waitUntilIdle();
index.setDefaultField(FIELD_SUMMARY);
assertCanFindTask(task);
}
@Test
public void testMatchesRepositoryTaskOnCreationDate() throws InterruptedException, CoreException {
setupIndex();
ITask task = context.createRepositoryTask();
Date creationDate = task.getCreationDate();
assertNotNull(creationDate);
index.waitUntilIdle();
assertFalse(index.matches(task, FIELD_DATE_CREATION.getIndexKey() + ":[20010101 TO 20010105]"));
String matchDate = new SimpleDateFormat("yyyyMMdd").format(creationDate);
matchDate = Integer.toString(Integer.parseInt(matchDate) + 2);
String patternString = FIELD_DATE_CREATION.getIndexKey() + ":[20111019 TO " + matchDate + "]";
System.out.println(patternString);
assertTrue(index.matches(task, patternString));
}
@Test
public void testMatchesOnRepositoryUrl() throws Exception {
setupIndex();
ITask repositoryTask = context.createRepositoryTask();
ITask localTask = context.createLocalTask();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
TaskData taskData = context.getDataManager().getTaskData(repositoryTask);
// sanity
assertNotNull(taskData);
assertNotNull(taskData.getRepositoryUrl());
assertFalse(taskData.getRepositoryUrl().length() == 0);
// setup descriptions so that they will both match
final String content = "RepositoryUrl";
taskData.getRoot().getMappedAttribute(TaskAttribute.DESCRIPTION).setValue(content);
((AbstractTask) localTask).setNotes(content);
context.getDataManager().putSubmittedTaskData(repositoryTask, taskData, new DelegatingProgressMonitor());
Set<ITask> changedElements = new HashSet<ITask>();
changedElements.add(localTask);
changedElements.add(repositoryTask);
context.getTaskList().notifyElementsChanged(changedElements);
index.waitUntilIdle();
assertTrue(index.matches(localTask, content));
assertTrue(index.matches(repositoryTask, content));
String repositoryUrlQuery = content + " AND " + TaskListIndex.FIELD_REPOSITORY_URL.getIndexKey() + ":\""
+ index.escapeFieldValue(repositoryTask.getRepositoryUrl()) + "\"";
assertFalse(index.matches(localTask, repositoryUrlQuery));
assertTrue(index.matches(repositoryTask, repositoryUrlQuery));
}
@Test
public void testMatchesOnTaskKey() throws Exception {
setupIndex();
ITask repositoryTask = context.createRepositoryTask();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
TaskData taskData = context.getDataManager().getTaskData(repositoryTask);
// sanity
assertNotNull(taskData);
assertNotNull(taskData.getRoot().getMappedAttribute(TaskAttribute.TASK_KEY));
String taskKey = repositoryTask.getTaskKey();
assertTrue(taskKey.length() > 1);
final String querySuffix = " AND " + TaskListIndex.FIELD_CONTENT.getIndexKey() + ":\""
+ index.escapeFieldValue(taskData.getRoot().getMappedAttribute(TaskAttribute.SUMMARY).getValue())
+ "\"";
assertTrue(index.matches(repositoryTask,
TaskListIndex.FIELD_TASK_KEY.getIndexKey() + ":" + taskKey + querySuffix));
// does not match on task key prefix
assertTrue(index.matches(repositoryTask, TaskListIndex.FIELD_TASK_KEY.getIndexKey() + ":"
+ taskKey.substring(0, taskKey.length() - 1) + querySuffix));
}
@Test
public void testMatchesSummaryWithExpectedQueryBehaviour() throws InterruptedException {
setupIndex();
ITask task = context.createLocalTask();
task.setSummary("one two three");
context.getTaskList().notifyElementsChanged(Collections.singleton(task));
index.waitUntilIdle();
index.setDefaultField(FIELD_SUMMARY);
// default search (without logical operators) should behave as a prefix search
assertTrue(index.matches(task, "one"));
assertTrue(index.matches(task, "two"));
assertTrue(index.matches(task, "three"));
assertTrue(index.matches(task, "thr"));
assertFalse(index.matches(task, "one three"));
// wildcard search should not match multiple terms separated by whitespace
assertFalse(index.matches(task, "one*three"));
// wildcard search should match multiple characters in a single term
assertTrue(index.matches(task, "t*ee"));
// logical operator makes it work with multiple terms
assertTrue(index.matches(task, "one AND three"));
assertTrue(index.matches(task, "one OR three"));
assertTrue(index.matches(task, "one AND thr"));
// logical operator requiring a non-existant term should not match
assertFalse(index.matches(task, "one AND four"));
}
@Test
public void testMatchesNotesWithExpectedQueryBehaviourWithRepositoryTask()
throws InterruptedException, CoreException {
setupIndex();
ITask task = context.createRepositoryTask();
((AbstractTask) task).setNotes("one two three");
index.reindex();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_NOTES);
// default search (without logical operators)
assertFalse(index.matches(task, "asdf"));
assertTrue(index.matches(task, "one"));
assertTrue(index.matches(task, "two"));
assertTrue(index.matches(task, "three"));
assertTrue(index.matches(task, "thr"));
assertTrue(index.matches(task, "one two"));
assertTrue(index.matches(task, "one three"));
// wildcard search should not match multiple terms separated by whitespace
assertFalse(index.matches(task, "one*three"));
// wildcard search should match multiple characters in a single term
assertTrue(index.matches(task, "t*ee"));
// logical operator makes it work with multiple terms
assertTrue(index.matches(task, "one AND three"));
assertTrue(index.matches(task, "one OR three"));
assertTrue(index.matches(task, "one AND thr"));
// logical operator requiring a non-existant term should not match
assertFalse(index.matches(task, "one AND four"));
}
@Test
public void testMatchesNotesWithExpectedQueryBehaviourWithLocalTask() throws InterruptedException, CoreException {
setupIndex();
ITask task = context.createLocalTask();
((AbstractTask) task).setNotes("one two three");
context.getTaskList().notifyElementsChanged(Collections.singleton(task));
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_NOTES);
// default search (without logical operators)
assertFalse(index.matches(task, "asdf"));
assertTrue(index.matches(task, "one"));
assertTrue(index.matches(task, "two"));
assertTrue(index.matches(task, "three"));
assertTrue(index.matches(task, "thr"));
assertTrue(index.matches(task, "one two"));
assertTrue(index.matches(task, "one three"));
// wildcard search should not match multiple terms separated by whitespace
assertFalse(index.matches(task, "one*three"));
// wildcard search should match multiple characters in a single term
assertTrue(index.matches(task, "t*ee"));
// logical operator makes it work with multiple terms
assertTrue(index.matches(task, "one AND three"));
assertTrue(index.matches(task, "one OR three"));
assertTrue(index.matches(task, "one AND thr"));
// logical operator requiring a non-existant term should not match
assertFalse(index.matches(task, "one AND four"));
}
@Test
public void testMatchesContentWithExpectedQueryBehaviourWithRepositoryTask()
throws InterruptedException, CoreException {
setupIndex();
ITask task = context.createRepositoryTask();
((AbstractTask) task).setNotes("one two three");
index.reindex();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
// default search (without logical operators)
assertFalse(index.matches(task, "asdf"));
assertTrue(index.matches(task, "one"));
assertTrue(index.matches(task, "two"));
assertTrue(index.matches(task, "three"));
assertTrue(index.matches(task, "thr"));
assertTrue(index.matches(task, "one two"));
assertTrue(index.matches(task, "one three"));
// wildcard search should not match multiple terms separated by whitespace
assertFalse(index.matches(task, "one*three"));
// wildcard search should match multiple characters in a single term
assertTrue(index.matches(task, "t*ee"));
// logical operator makes it work with multiple terms
assertTrue(index.matches(task, "one AND three"));
assertTrue(index.matches(task, "one OR three"));
assertTrue(index.matches(task, "one AND thr"));
// logical operator requiring a non-existant term should not match
assertFalse(index.matches(task, "one AND four"));
}
@Test
public void testMatchesContentWithExpectedQueryBehaviourWithLocalTask() throws InterruptedException, CoreException {
setupIndex();
ITask task = context.createLocalTask();
((AbstractTask) task).setNotes("one two three");
context.getTaskList().notifyElementsChanged(Collections.singleton(task));
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
// default search (without logical operators)
assertFalse(index.matches(task, "asdf"));
assertTrue(index.matches(task, "one"));
assertTrue(index.matches(task, "two"));
assertTrue(index.matches(task, "three"));
assertTrue(index.matches(task, "thr"));
assertTrue(index.matches(task, "one two"));
assertTrue(index.matches(task, "one three"));
// wildcard search should not match multiple terms separated by whitespace
assertFalse(index.matches(task, "one*three"));
// wildcard search should match multiple characters in a single term
assertTrue(index.matches(task, "t*ee"));
// logical operator makes it work with multiple terms
assertTrue(index.matches(task, "one AND three"));
assertTrue(index.matches(task, "one OR three"));
assertTrue(index.matches(task, "one AND thr"));
// logical operator requiring a non-existant term should not match
assertFalse(index.matches(task, "one AND four"));
}
@Test
public void testCharacterEscaping() {
setupIndex();
for (String special : new String[] { "+", "-", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "\"", "~",
"*", "?", ":", "\\" }) {
assertEquals("a\\" + special + "b", index.escapeFieldValue("a" + special + "b"));
}
}
@Test
public void testAttributeMetadataAffectsIndexing() throws CoreException, InterruptedException {
setupIndex();
ITask repositoryTask = context.createRepositoryTask();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
TaskData taskData = context.getDataManager().getTaskData(repositoryTask);
// sanity
assertNotNull(taskData);
final String content = "c" + System.currentTimeMillis();
// setup data so that it will match
TaskAttribute attribute = taskData.getRoot().createAttribute("unusualIndexedAttribute");
attribute.setValue(content);
// update
context.getDataManager().putSubmittedTaskData(repositoryTask, taskData, new DelegatingProgressMonitor());
// verify index doesn't match search term
assertFalse(index.matches(repositoryTask, content));
// now make data indexable
attribute.getMetaData().putValue(TaskAttribute.META_INDEXED_AS_CONTENT, "true");
// update
context.getDataManager().putSubmittedTaskData(repositoryTask, taskData, new DelegatingProgressMonitor());
// should now match
assertTrue(index.matches(repositoryTask, content));
}
/**
* Verify that multiple threads can concurrently use the index to find tasks, i.e. that no threads are blocked from
* finding tasks by other threads.
*/
@Test
public void testMultithreadedAccessOnFind() throws CoreException, InterruptedException, ExecutionException {
setupIndex();
final ITask repositoryTask = context.createRepositoryTask();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
final int nThreads = 10;
final int[] concurrencyLevel = new int[1];
ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
try {
Collection<Callable<Object>> tasks = new HashSet<Callable<Object>>();
for (int x = 0; x < nThreads; ++x) {
tasks.add(new Callable<Object>() {
public Object call() throws Exception {
final int[] hitCount = new int[1];
index.find(repositoryTask.getSummary(), new TaskCollector() {
@Override
public void collect(ITask task) {
synchronized (concurrencyLevel) {
++concurrencyLevel[0];
if (concurrencyLevel[0] < nThreads) {
try {
concurrencyLevel.wait(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
concurrencyLevel.notifyAll();
}
}
++hitCount[0];
}
}, 100);
return hitCount[0] == 1;
}
});
}
List<Future<Object>> futures = executorService.invokeAll(tasks);
for (Future<Object> future : futures) {
assertEquals(Boolean.TRUE, future.get());
}
assertEquals(nThreads, concurrencyLevel[0]);
} finally {
executorService.shutdownNow();
}
}
@Test
public void testRepositoryUrlChanged() throws InterruptedException, CoreException {
setupIndex();
ITask repositoryTask = context.createRepositoryTask();
final String originalHandle = repositoryTask.getHandleIdentifier();
index.waitUntilIdle();
final String newUrl = context.getMockRepository().getRepositoryUrl() + "/changed";
context.refactorMockRepositoryUrl(newUrl);
assertFalse(originalHandle.equals(repositoryTask.getHandleIdentifier()));
index.waitUntilIdle();
assertTrue(index.matches(repositoryTask, TaskListIndex.FIELD_IDENTIFIER.getIndexKey() + ":"
+ index.escapeFieldValue(repositoryTask.getHandleIdentifier())));
}
@Test
public void testSetLocation() throws InterruptedException, IOException {
setupIndex();
index.setDefaultField(FIELD_SUMMARY);
ITask task = context.createLocalTask();
index.waitUntilIdle();
assertCanFindTask(task);
File newLocation = createTempFolder(TaskListIndexTest.class.getSimpleName());
try {
assertEquals(0, newLocation.list().length);
index.setLocation(newLocation);
index.waitUntilIdle();
assertCanFindTask(task);
assertFalse(newLocation.list().length == 0);
} finally {
disposeIndex();
deleteFolderRecursively(newLocation);
assertFalse(newLocation.exists());
}
}
@Test
public void testFindByTaskAttachmentName() throws CoreException, InterruptedException {
setupIndex();
ITask repositoryTask = context.createRepositoryTask();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
TaskData taskData = context.getDataManager().getTaskData(repositoryTask);
TaskAttribute attachmentAttribute = taskData.getRoot().createAttribute("attachment-0");
attachmentAttribute.getMetaData().setType(TaskAttribute.TYPE_ATTACHMENT);
TaskAttachmentMapper attachmentMapper = TaskAttachmentMapper.createFrom(attachmentAttribute);
attachmentMapper.setFileName("test-file.txt");
attachmentMapper.setDescription("test file " + System.currentTimeMillis());
attachmentMapper.applyTo(attachmentAttribute);
context.getDataManager().putSubmittedTaskData(repositoryTask, taskData, new DelegatingProgressMonitor());
context.getTaskList().notifyElementsChanged(Collections.singleton(repositoryTask));
index.waitUntilIdle();
assertTrue(index.matches(repositoryTask, "\"" + attachmentMapper.getDescription() + "\""));
assertTrue(index.matches(repositoryTask,
TaskListIndex.FIELD_ATTACHMENT_NAME.getIndexKey() + ":\"" + attachmentMapper.getFileName() + "\""));
assertFalse(index.matches(repositoryTask,
TaskListIndex.FIELD_CONTENT.getIndexKey() + ":\"" + attachmentMapper.getFileName() + "\""));
assertFalse(index.matches(repositoryTask,
TaskListIndex.FIELD_ATTACHMENT_NAME.getIndexKey() + ":\"" + attachmentMapper.getDescription() + "\""));
}
@Test
public void testFindWithComplexQuery() throws Exception {
setupIndex();
ITask task1 = context.createRepositoryTask();
setSummary(task1, "one two three");
ITask task2 = context.createRepositoryTask();
setSummary(task2, "two three four");
ITask task3 = context.createRepositoryTask();
setSummary(task3, "three four five");
String repositoryUrl = task1.getRepositoryUrl();
assertEquals(repositoryUrl, task2.getRepositoryUrl());
assertEquals(repositoryUrl, task3.getRepositoryUrl());
repositoryUrl = index.escapeFieldValue(repositoryUrl);
index.reindex();
index.waitUntilIdle();
index.setDefaultField(TaskListIndex.FIELD_CONTENT);
String pattern = "repository_url:%s AND (summary:%s* OR task_key:%s)";
String query = String.format(pattern, repositoryUrl, "five", task1.getTaskKey());
assertTrue(index.matches(task1, query));
assertFalse(index.matches(task2, query));
assertTrue(index.matches(task3, query));
query = String.format(pattern, repositoryUrl, task2.getTaskKey(), task2.getTaskKey());
assertFalse(index.matches(task1, query));
assertTrue(index.matches(task2, query));
assertFalse(index.matches(task3, query));
query = String.format(pattern, repositoryUrl, "two", "something.irrelevant");
assertTrue(index.matches(task1, query));
assertTrue(index.matches(task2, query));
assertFalse(index.matches(task3, query));
}
private void setSummary(ITask task, String summary) throws CoreException {
task.setSummary(summary);
TaskData taskData = context.getDataManager().getTaskData(task);
taskData.getRoot().getMappedAttribute(TaskAttribute.SUMMARY).setValue(summary);
context.getDataManager().putSubmittedTaskData(task, taskData, new DelegatingProgressMonitor());
context.getTaskList().notifyElementsChanged(Collections.singleton(task));
}
private void assertCanFindTask(ITask task) {
TestTaskCollector collector = new TestTaskCollector();
index.find(task.getSummary(), collector, 1000);
assertEquals(1, collector.getTasks().size());
assertTrue(collector.getTasks().contains(task));
}
}