/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.ui.internal.text.dart; import com.google.common.base.Joiner; import com.google.dart.engine.ast.CompilationUnit; import com.google.dart.engine.context.AnalysisContext; import com.google.dart.engine.internal.context.AnalysisContextImpl; import com.google.dart.engine.sdk.DartSdk; import com.google.dart.engine.sdk.DirectoryBasedDartSdk; import com.google.dart.engine.source.DartUriResolver; import com.google.dart.engine.source.FileUriResolver; import com.google.dart.engine.source.Source; import com.google.dart.engine.source.SourceFactory; import com.google.dart.engine.source.TestSource; import com.google.dart.tools.core.analysis.model.Project; import com.google.dart.tools.core.internal.builder.AnalysisManager; import com.google.dart.tools.core.internal.builder.AnalysisWorker; import com.google.dart.tools.core.internal.model.DartIgnoreManager; import com.google.dart.tools.core.internal.model.MockIgnoreFile; import static com.google.dart.engine.utilities.io.FileUtilities2.createFile; import junit.framework.TestCase; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.reconciler.DirtyRegion; import org.eclipse.swt.events.DisposeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class DartReconcilingStrategyTest extends TestCase { /** * AnalysisManager that does not perform background analysis. */ private final class MockAnalysisManager extends AnalysisManager { private int backgroundAnalysisCount = 0; public void assertBackgroundAnalysis(int expected) { assertEquals(expected, backgroundAnalysisCount); AnalysisWorker[] workers = getQueueWorkers(); assertEquals(expected > 0 ? 1 : 0, workers.length); if (expected > 0) { assertSame(mockContext, workers[0].getContext()); } } @Override public void startBackgroundAnalysis() { backgroundAnalysisCount++; } } /** * Analysis context that can wait for priority order and resolution. */ private class MockContext extends AnalysisContextImpl { private List<Source> priorityOrder = new ArrayList<Source>(); private int setContentsCount = 0; private int setChangedContentsCount = 0; private int changedOffset = 0; private int changedOldLength = 0; private int changedNewLength = 0; public List<Source> getPriorityOrder() { return priorityOrder; } @Override public void setAnalysisPriorityOrder(List<Source> sources) { super.setAnalysisPriorityOrder(sources); priorityOrder = sources; } @Override public void setChangedContents(Source source, String contents, int offset, int oldLength, int newLength) { if (source == mockSource) { setChangedContentsCount++; changedOffset = offset; changedOldLength = oldLength; changedNewLength = newLength; } super.setChangedContents(source, contents, offset, oldLength, newLength); } @Override public void setContents(Source source, String contents) { if (source == mockSource) { setContentsCount++; } setContentsForTest(source, contents); } void assertSetChangedContentsCount(int expectedCount, int expectedOffset, int expectedOldLength, int expectedNewLength) { assertEquals(expectedCount, setChangedContentsCount); assertEquals(expectedOffset, changedOffset); assertEquals(expectedOldLength, changedOldLength); assertEquals(expectedNewLength, changedNewLength); } void assertSetContentsCount(int expected) { assertEquals(expected, setContentsCount); } void setContentsForTest(Source source, String contents) { super.setContents(source, contents); } } /** * Mock editor for testing {@link DartReconcilingStrategy} */ private final class MockEditor implements DartReconcilingEditor { private CompilationUnit appliedUnit = null; private DisposeListener disposeListener = null; @Override public void addViewerDisposeListener(DisposeListener listener) { if (disposeListener != null) { throw new RuntimeException("dispose listener already added"); } disposeListener = listener; } @Override public void applyResolvedUnit(CompilationUnit unit) { appliedUnit = unit; } public CompilationUnit getAppliedCompilationUnit() { return appliedUnit; } public DisposeListener getDisposeListener() { return disposeListener; } @Override public AnalysisContext getInputAnalysisContext() { return mockContext; } @Override public String getInputFilePath() { // TODO(scheglov) Analysis Server throw new UnsupportedOperationException(); } @Override public Project getInputProject() { // TODO (danrubel): test null and non-null project return null; } @Override public Source getInputSource() { return mockSource; } @Override public String getTitle() { return mockSource.getShortName(); } @Override public boolean isDirty() { return true; } @Override public void setDartReconcilingStrategy(DartReconcilingStrategy dartReconcilingStrategy) { // ignored } } private final static String EOL = System.getProperty("line.separator", "\n"); private static final String INITIAL_CONTENTS = Joiner.on(EOL).join(// "library a;", "class A { foo() => this; }"); MockEditor mockEditor = new MockEditor(); Source mockSource = new TestSource(createFile("/test.dart"), INITIAL_CONTENTS); MockContext mockContext = new MockContext(); Document mockDocument = new Document(INITIAL_CONTENTS); MockAnalysisManager analysisManager = new MockAnalysisManager(); DartIgnoreManager ignoreManager = new DartIgnoreManager(new MockIgnoreFile()); DartReconcilingStrategy strategy = new DartReconcilingStrategy( mockEditor, analysisManager, ignoreManager); /** * Assert reconciler clears cached contents when disposed */ public void test_dispose() throws Exception { mockContext.setContentsForTest(mockSource, INITIAL_CONTENTS); mockContext.setAnalysisPriorityOrder(Arrays.asList(new Source[] {mockSource})); mockEditor.getDisposeListener().widgetDisposed(null); waitForBackgroundThread(); analysisManager.assertBackgroundAnalysis(1); } /** * Assert that a "." triggers immediate analysis */ public void test_docChange_period() throws Exception { String insertedText = "."; int offset = INITIAL_CONTENTS.indexOf("this") + 4; assert offset > 5; strategy.initialReconcile(); mockContext.assertSetChangedContentsCount(0, 0, 0, 0); mockContext.assertSetContentsCount(0); analysisManager.assertBackgroundAnalysis(1); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); mockDocument.replace(offset, 0, insertedText); waitForBackgroundThread(); // assert "." causes immediate update of the context assertNull(mockEditor.getAppliedCompilationUnit()); analysisManager.assertBackgroundAnalysis(2); mockContext.assertSetChangedContentsCount(1, offset, 0, 1); mockContext.assertSetContentsCount(0); } /** * Assert unit resolved, applied, and order set during initialReconcile */ public void test_initialReconcile() { strategy.initialReconcile(); mockContext.assertSetChangedContentsCount(0, 0, 0, 0); mockContext.assertSetContentsCount(0); analysisManager.assertBackgroundAnalysis(1); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); } /** * Assert unit resolved, applied, and order set during initialReconcile */ public void test_initialReconcile_cachedUnit() { analysisManager.performAnalysis(null); assertNull(mockEditor.getAppliedCompilationUnit()); strategy.initialReconcile(); mockContext.assertSetChangedContentsCount(0, 0, 0, 0); mockContext.assertSetContentsCount(0); assertNotNull(mockEditor.getAppliedCompilationUnit()); } /** * Assert ignored source is not analyzed */ public void test_initialReconcile_ignoredSource() throws Exception { ignoreManager.addToIgnores(mockSource.getFullName()); strategy.initialReconcile(); mockContext.assertSetChangedContentsCount(0, 0, 0, 0); mockContext.assertSetContentsCount(0); analysisManager.assertBackgroundAnalysis(0); analysisManager.performAnalysis(null); assertNull(mockEditor.getAppliedCompilationUnit()); } /** * Assert editor with no context does not throw exception */ public void test_initialReconcile_nullContext() { mockContext = null; assertNull(mockEditor.getInputAnalysisContext()); strategy.initialReconcile(); // test infrastructure asserts no exceptions } /** * Assert reconciler lazily sets cached contents and performs resolution */ public void test_initialState() throws Exception { assertNull(mockContext.getResolvedCompilationUnit(mockSource, mockSource)); assertEquals(0, mockContext.getPriorityOrder().size()); } /** * Assert that a document change clears the cached unit and a resolve resets it */ public void test_reconcile() throws Exception { String insertedText = "//comment\n"; strategy.initialReconcile(); mockContext.assertSetChangedContentsCount(0, 0, 0, 0); mockContext.assertSetContentsCount(0); analysisManager.assertBackgroundAnalysis(1); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); mockDocument.replace(3, 7, insertedText); assertNull(mockEditor.getAppliedCompilationUnit()); strategy.reconcile(); waitForBackgroundThread(); assertNull(mockEditor.getAppliedCompilationUnit()); analysisManager.assertBackgroundAnalysis(2); mockContext.assertSetChangedContentsCount(1, 3, 7, insertedText.length()); mockContext.assertSetContentsCount(0); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); } /** * Assert that a document change clears the cached unit and a resolve resets it */ public void test_reconcile_delete() throws Exception { strategy.initialReconcile(); mockContext.assertSetChangedContentsCount(0, 0, 0, 0); mockContext.assertSetContentsCount(0); analysisManager.assertBackgroundAnalysis(1); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); mockDocument.replace(0, 10, ""); assertNull(mockEditor.getAppliedCompilationUnit()); strategy.reconcile(); waitForBackgroundThread(); mockContext.assertSetChangedContentsCount(1, 0, 10, 0); mockContext.assertSetContentsCount(0); assertNull(mockEditor.getAppliedCompilationUnit()); analysisManager.assertBackgroundAnalysis(2); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); } /** * Assert that a document change clears the cached unit and a resolve resets it */ public void test_reconcile_ignoredSource() throws Exception { ignoreManager.addToIgnores(mockSource.getFullName()); String insertedText = "//comment\n"; strategy.initialReconcile(); mockContext.assertSetChangedContentsCount(0, 0, 0, 0); mockContext.assertSetContentsCount(0); analysisManager.assertBackgroundAnalysis(0); analysisManager.performAnalysis(null); assertNull(mockEditor.getAppliedCompilationUnit()); mockDocument.replace(3, 7, insertedText); assertNull(mockEditor.getAppliedCompilationUnit()); strategy.reconcile(); waitForBackgroundThread(); assertNull(mockEditor.getAppliedCompilationUnit()); analysisManager.assertBackgroundAnalysis(1); mockContext.assertSetChangedContentsCount(1, 3, 7, insertedText.length()); mockContext.assertSetContentsCount(0); analysisManager.performAnalysis(null); assertNull(mockEditor.getAppliedCompilationUnit()); } /** * Assert editor with no context does not throw exception */ public void test_reconcile_nullContext() throws Exception { String insertedText = "//comment\n"; mockContext = null; strategy.initialReconcile(); mockDocument.replace(0, 0, insertedText); strategy.reconcile(); analysisManager.performAnalysis(null); // test infrastructure asserts no exceptions } /** * Assert that a document change clears the cached unit and a resolve resets it */ public void test_reconcileDirtyRegionIRegion() throws Exception { String insertedText = "//comment\n"; strategy.initialReconcile(); mockContext.assertSetChangedContentsCount(0, 0, 0, 0); mockContext.assertSetContentsCount(0); analysisManager.assertBackgroundAnalysis(1); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); mockDocument.replace(0, 0, insertedText); assertNull(mockEditor.getAppliedCompilationUnit()); strategy.reconcile(new DirtyRegion(0, 0, DirtyRegion.INSERT, insertedText), new Region(0, 0)); waitForBackgroundThread(); mockContext.assertSetChangedContentsCount(1, 0, 0, insertedText.length()); mockContext.assertSetContentsCount(0); assertNull(mockEditor.getAppliedCompilationUnit()); analysisManager.assertBackgroundAnalysis(2); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); } /** * Assert that a document change clears the cached unit and a resolve resets it */ public void test_reconcileDirtyRegionIRegion_delete() throws Exception { strategy.initialReconcile(); mockContext.assertSetChangedContentsCount(0, 0, 0, 0); mockContext.assertSetContentsCount(0); analysisManager.assertBackgroundAnalysis(1); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); mockDocument.replace(0, 10, ""); assertNull(mockEditor.getAppliedCompilationUnit()); strategy.reconcile(new DirtyRegion(0, 10, DirtyRegion.REMOVE, null), new Region(0, 0)); waitForBackgroundThread(); mockContext.assertSetChangedContentsCount(1, 0, 10, 0); mockContext.assertSetContentsCount(0); assertNull(mockEditor.getAppliedCompilationUnit()); analysisManager.assertBackgroundAnalysis(2); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); } /** * Assert editor with no context does not throw exception */ public void test_reconcileDirtyRegionIRegion_nullContext() throws Exception { String insertedText = "//comment\n"; mockContext = null; strategy.initialReconcile(); mockDocument.replace(0, 0, insertedText); strategy.reconcile(new DirtyRegion(0, 0, DirtyRegion.INSERT, insertedText), new Region(0, 0)); analysisManager.performAnalysis(null); // test infrastructure asserts no exceptions } public void test_reconcileIRegion() throws Exception { String insertedText = "//comment\n"; strategy.initialReconcile(); mockContext.assertSetChangedContentsCount(0, 0, 0, 0); mockContext.assertSetContentsCount(0); analysisManager.assertBackgroundAnalysis(1); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); mockDocument.replace(0, 0, insertedText); assertNull(mockEditor.getAppliedCompilationUnit()); strategy.reconcile(new Region(0, insertedText.length())); waitForBackgroundThread(); mockContext.assertSetChangedContentsCount(1, 0, 0, insertedText.length()); mockContext.assertSetContentsCount(0); assertNull(mockEditor.getAppliedCompilationUnit()); analysisManager.assertBackgroundAnalysis(2); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); } public void test_reconcileIRegion_delete() throws Exception { strategy.initialReconcile(); mockContext.assertSetChangedContentsCount(0, 0, 0, 0); mockContext.assertSetContentsCount(0); analysisManager.assertBackgroundAnalysis(1); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); mockDocument.replace(0, 10, ""); assertNull(mockEditor.getAppliedCompilationUnit()); strategy.reconcile(new Region(0, 10)); waitForBackgroundThread(); mockContext.assertSetChangedContentsCount(1, 0, 10, 0); mockContext.assertSetContentsCount(0); assertNull(mockEditor.getAppliedCompilationUnit()); analysisManager.assertBackgroundAnalysis(2); analysisManager.performAnalysis(null); assertNotNull(mockEditor.getAppliedCompilationUnit()); } /** * Assert editor with no context does not throw exception */ public void test_reconcileIRegion_nullContext() throws Exception { String insertedText = "//comment\n"; mockContext = null; strategy.initialReconcile(); mockDocument.replace(0, 0, insertedText); strategy.reconcile(new Region(0, insertedText.length())); // test infrastructure asserts no exceptions } @Override protected void setUp() throws Exception { DartSdk sdk = DirectoryBasedDartSdk.getDefaultSdk(); assertNotNull(sdk); SourceFactory sourceFactory = new SourceFactory(new DartUriResolver(sdk), new FileUriResolver()); mockContext.setSourceFactory(sourceFactory); strategy.setDocument(mockDocument); } @Override protected void tearDown() throws Exception { // Ensure that strategy removes its AnalysisWorker listener if (strategy != null) { strategy.dispose(); } mockEditor = null; mockSource = null; mockContext = null; mockDocument = null; analysisManager = null; ignoreManager = null; strategy = null; } /** * We cannot talk to {@link AnalysisContext} on the UI thread, so we push update/analyze requests * and execute them in background. But in test we want to wait until these requests are executed. */ private void waitForBackgroundThread() { DartUpdateSourceHelper.getInstance().waitForEmptyQueue(); } }