/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.api.vfs.impl.file; import org.eclipse.che.commons.lang.IoUtil; import org.eclipse.che.commons.lang.NameGenerator; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import java.io.File; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.CREATED; import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.DELETED; import static org.eclipse.che.api.project.shared.dto.event.FileWatcherEventType.MODIFIED; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; public class FileTreeWatcherMassiveIoOperationTest { private FileTreeWatcher fileTreeWatcher; private File testDirectory; private FileWatcherTestTree fileWatcherTestTree; @Before public void setUp() throws Exception { File targetDir = new File(Thread.currentThread().getContextClassLoader().getResource(".").getPath()).getParentFile(); testDirectory = new File(targetDir, NameGenerator.generate("watcher-", 4)); assertTrue(testDirectory.mkdir()); fileWatcherTestTree = new FileWatcherTestTree(testDirectory); } @After public void tearDown() throws Exception { if (fileTreeWatcher != null) { fileTreeWatcher.shutdown(); } IoUtil.deleteRecursive(testDirectory); } @Test public void watchesTreeCreation() throws Exception { FileWatcherNotificationHandler notificationListener = aNotificationListener(); fileTreeWatcher = new FileTreeWatcher(testDirectory, newHashSet(), notificationListener); fileTreeWatcher.startup(); Thread.sleep(500); List<String> allFilesAndDirs = fileWatcherTestTree.createTree("", 7, 5); Thread.sleep(5000); verify(notificationListener, never()).errorOccurred(eq(testDirectory), any(Throwable.class)); verify(notificationListener, never()).handleFileWatcherEvent(eq(DELETED), eq(testDirectory), anyString(), anyBoolean()); verify(notificationListener, never()).handleFileWatcherEvent(eq(MODIFIED), eq(testDirectory), anyString(), anyBoolean()); ArgumentCaptor<String> createdEvents = ArgumentCaptor.forClass(String.class); verify(notificationListener, times(allFilesAndDirs.size())).handleFileWatcherEvent(eq(CREATED), eq(testDirectory), createdEvents.capture(), anyBoolean()); assertThatCollectionsContainsSameItemsOrFailWithDiff(createdEvents.getAllValues(), allFilesAndDirs); } @Test public void watchesTreeDeletion() throws Exception { List<String> allFilesAndDirs = fileWatcherTestTree.createTree("", 7, 5); Thread.sleep(100); FileWatcherNotificationHandler notificationListener = aNotificationListener(); fileTreeWatcher = new FileTreeWatcher(testDirectory, newHashSet(), notificationListener); fileTreeWatcher.startup(); Thread.sleep(5000); assertTrue(fileWatcherTestTree.delete("")); Thread.sleep(5000); verify(notificationListener, never()).errorOccurred(eq(testDirectory), any(Throwable.class)); verify(notificationListener, never()).handleFileWatcherEvent(eq(CREATED), eq(testDirectory), anyString(), anyBoolean()); verify(notificationListener, never()).handleFileWatcherEvent(eq(MODIFIED), eq(testDirectory), anyString(), anyBoolean()); ArgumentCaptor<String> deletedEvents = ArgumentCaptor.forClass(String.class); verify(notificationListener, times(allFilesAndDirs.size())).handleFileWatcherEvent(eq(DELETED), eq(testDirectory), deletedEvents.capture(), anyBoolean()); assertThatCollectionsContainsSameItemsOrFailWithDiff(deletedEvents.getAllValues(), allFilesAndDirs); } @Test public void watchesUpdatesAllFilesInTree() throws Exception { fileWatcherTestTree.createTree("", 7, 5); Thread.sleep(100); FileWatcherNotificationHandler notificationListener = aNotificationListener(); fileTreeWatcher = new FileTreeWatcher(testDirectory, newHashSet(), notificationListener); fileTreeWatcher.startup(); Thread.sleep(5000); List<String> updated = fileWatcherTestTree.findAllFilesInTree(""); for (String file : updated) { fileWatcherTestTree.updateFile(file); } Thread.sleep(5000); verify(notificationListener, never()).errorOccurred(eq(testDirectory), any(Throwable.class)); verify(notificationListener, never()).handleFileWatcherEvent(eq(CREATED), eq(testDirectory), anyString(), anyBoolean()); verify(notificationListener, never()).handleFileWatcherEvent(eq(DELETED), eq(testDirectory), anyString(), anyBoolean()); ArgumentCaptor<String> updatedEvents = ArgumentCaptor.forClass(String.class); verify(notificationListener, times(updated.size())).handleFileWatcherEvent(eq(MODIFIED), eq(testDirectory), updatedEvents.capture(), anyBoolean()); assertThatCollectionsContainsSameItemsOrFailWithDiff(updatedEvents.getAllValues(), updated); } @Test public void watchesUpdatesFilesInTree() throws Exception { fileWatcherTestTree.createTree("", 7, 5); Thread.sleep(100); FileWatcherNotificationHandler notificationListener = aNotificationListener(); fileTreeWatcher = new FileTreeWatcher(testDirectory, newHashSet(), notificationListener); fileTreeWatcher.startup(); Thread.sleep(5000); List<String> updated = fileWatcherTestTree.findAllFilesInTree("").stream() .filter(path -> path.hashCode() % 2 == 0).collect(Collectors.toList()); for (String file : updated) { fileWatcherTestTree.updateFile(file); } Thread.sleep(5000); verify(notificationListener, never()).errorOccurred(eq(testDirectory), any(Throwable.class)); verify(notificationListener, never()).handleFileWatcherEvent(eq(CREATED), eq(testDirectory), anyString(), anyBoolean()); verify(notificationListener, never()).handleFileWatcherEvent(eq(DELETED), eq(testDirectory), anyString(), anyBoolean()); ArgumentCaptor<String> updatedEvents = ArgumentCaptor.forClass(String.class); verify(notificationListener, times(updated.size())).handleFileWatcherEvent(eq(MODIFIED), eq(testDirectory), updatedEvents.capture(), anyBoolean()); assertThatCollectionsContainsSameItemsOrFailWithDiff(updatedEvents.getAllValues(), updated); } @Test public void watchesMixedActionsInTree() throws Exception { fileWatcherTestTree.createTree("", 7, 5); Thread.sleep(100); FileWatcherNotificationHandler notificationListener = aNotificationListener(); fileTreeWatcher = new FileTreeWatcher(testDirectory, newHashSet(), notificationListener); fileTreeWatcher.startup(); Thread.sleep(5000); List<String> allFiles = fileWatcherTestTree.findAllFilesInTree(""); List<String> updated = newArrayList(allFiles.subList(0, allFiles.size() / 2)); List<String> deleted = newArrayList(allFiles.subList(allFiles.size() / 2, allFiles.size())); List<String> directories = fileWatcherTestTree.findAllDirectoriesInTree(""); List<String> created = newArrayList(); for (String directory : directories) { created.add(fileWatcherTestTree.createFile(directory)); } for (String file : deleted) { fileWatcherTestTree.delete(file); } Thread.sleep(5000); updated.addAll(created.subList(0, created.size() / 2)); for (String file : updated) { fileWatcherTestTree.updateFile(file); } Thread.sleep(5000); verify(notificationListener, never()).errorOccurred(eq(testDirectory), any(Throwable.class)); ArgumentCaptor<String> eventsCaptor = ArgumentCaptor.forClass(String.class); verify(notificationListener, times(deleted.size())).handleFileWatcherEvent(eq(DELETED), eq(testDirectory), eventsCaptor.capture(), anyBoolean()); assertThatCollectionsContainsSameItemsOrFailWithDiff(eventsCaptor.getAllValues(), deleted); eventsCaptor = ArgumentCaptor.forClass(String.class); verify(notificationListener, times(updated.size())).handleFileWatcherEvent(eq(MODIFIED), eq(testDirectory), eventsCaptor.capture(), anyBoolean()); assertThatCollectionsContainsSameItemsOrFailWithDiff(eventsCaptor.getAllValues(), updated); eventsCaptor = ArgumentCaptor.forClass(String.class); verify(notificationListener, times(created.size())).handleFileWatcherEvent(eq(CREATED), eq(testDirectory), eventsCaptor.capture(), anyBoolean()); assertThatCollectionsContainsSameItemsOrFailWithDiff(eventsCaptor.getAllValues(), created); } private FileWatcherNotificationHandler aNotificationListener() { return mock(FileWatcherNotificationHandler.class); } private void assertThatCollectionsContainsSameItemsOrFailWithDiff(Collection<String> actual, Collection<String> expected) { List<String> missed = newArrayList(expected); List<String> extra = newArrayList(actual); missed.removeAll(actual); extra.removeAll(expected); if (missed.isEmpty() && extra.isEmpty()) { return; } StringBuilder message = new StringBuilder(); if (missed.size() > 0) { message.append("\n>>> Expected items:\n") .append(missed).append('\n').append("but missed\n"); } if (extra.size() > 0) { message.append("\n>>> Items:\n") .append(extra).append('\n').append("not expected but found\n"); } fail(message.toString()); } }