/******************************************************************************* * Copyright (c) 2012, 2014 Google, Inc 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: * Sergey Prigogin (Google) - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.ui.tests.refactoring; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.InputStreamReader; import java.io.StringReader; import java.net.URI; import java.util.LinkedHashSet; import java.util.Set; import java.util.regex.Pattern; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Path; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.TextSelection; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.Refactoring; import org.eclipse.ltk.core.refactoring.RefactoringContext; import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; import org.eclipse.ltk.core.refactoring.RefactoringDescriptorProxy; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry; import org.eclipse.ltk.core.refactoring.history.RefactoringHistory; import org.eclipse.ltk.internal.core.refactoring.history.RefactoringHistoryService; import org.osgi.framework.Bundle; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.dom.IPDOMManager; import org.eclipse.cdt.core.model.CoreModel; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.core.testplugin.CProjectHelper; import org.eclipse.cdt.core.testplugin.TestScannerProvider; import org.eclipse.cdt.core.testplugin.util.BaseTestCase; import org.eclipse.cdt.core.testplugin.util.TestSourceReader; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.PreferenceConstants; import org.eclipse.cdt.ui.testplugin.CTestPlugin; import org.eclipse.cdt.internal.ui.refactoring.CRefactoring; import org.eclipse.cdt.internal.ui.refactoring.CRefactoringContext; /** * Common base for refactoring tests. */ public abstract class RefactoringTestBase extends BaseTestCase { private static final Pattern FILENAME_PATTERN = Pattern.compile("((\\w|_|-)+/)*(\\w|_|-)+\\.\\w+"); /** Allows empty files to be created during test setup. */ protected boolean createEmptyFiles = true; /** See {@link PreferenceConstants.CLASS_MEMBER_ASCENDING_VISIBILITY_ORDER} */ protected boolean ascendingVisibilityOrder; /** Expected counts of errors, warnings and info messages */ protected int expectedInitialErrors; protected int expectedInitialWarnings; protected int expectedFinalWarnings; protected int expectedFinalInfos; private boolean cpp = true; private ICProject cproject; private final Set<TestSourceFile> testFiles = new LinkedHashSet<>(); private TestSourceFile selectedFile; private TextSelection selection; private TestSourceFile historyScript; protected RefactoringTestBase() { super(); } protected RefactoringTestBase(String name) { super(name); } @Override public void setUp() throws Exception { super.setUp(); resetPreferences(); cproject = cpp ? CProjectHelper.createCCProject(getName() + System.currentTimeMillis(), "bin", IPDOMManager.ID_NO_INDEXER) : CProjectHelper.createCProject(getName() + System.currentTimeMillis(), "bin", IPDOMManager.ID_NO_INDEXER); TestScannerProvider.sLocalIncludes = new String[] { cproject.getProject().getLocation().toOSString() }; Bundle bundle = CTestPlugin.getDefault().getBundle(); CharSequence[] testData = TestSourceReader.getContentsForTest(bundle, "ui", getClass(), getName(), 0); for (CharSequence contents : testData) { TestSourceFile testFile = null; boolean firstAfterDelimiter = false; boolean expectedResult = false; BufferedReader reader = new BufferedReader(new StringReader(contents.toString())); String line; while ((line = reader.readLine()) != null) { String trimmedLine = line.trim(); if (testFile == null) { if (isResultDelimiter(trimmedLine)) { expectedResult = true; firstAfterDelimiter = true; testFile = new TestSourceFile(null); } else { assertTrue("Invalid file name \"" + trimmedLine + "\"", FILENAME_PATTERN.matcher(trimmedLine).matches()); testFile = new TestSourceFile(trimmedLine); } } else if (isResultDelimiter(trimmedLine)) { expectedResult = true; firstAfterDelimiter = true; } else if (expectedResult) { if (firstAfterDelimiter) { firstAfterDelimiter = false; if (FILENAME_PATTERN.matcher(trimmedLine).matches()) { testFile.setExpectedName(trimmedLine); continue; } } assertTrue(testFile.getExpectedName() != null); testFile.addLineToExpectedSource(line); } else { testFile.addLineToSource(line); } } reader.close(); if (testFile.getName() != null && (createEmptyFiles || !testFile.getSource().isEmpty())) { TestSourceReader.createFile(cproject.getProject(), new Path(testFile.getName()), testFile.getSource()); } testFiles.add(testFile); if (testFile.getName().endsWith(".xml")) { historyScript = testFile; } else if (selection == null) { selection = testFile.getSelection(); if (selection != null) selectedFile = testFile; } } if (selectedFile == null && !testFiles.isEmpty()) { selectedFile = testFiles.iterator().next(); } CCorePlugin.getIndexManager().setIndexerId(cproject, IPDOMManager.ID_FAST_INDEXER); waitForIndexer(cproject); } @Override public void tearDown() throws Exception { if (cproject != null) { cproject.getProject().delete(IResource.FORCE | IResource.ALWAYS_DELETE_PROJECT_CONTENT, npm()); } resetPreferences(); super.tearDown(); } protected void assertRefactoringSuccess() throws Exception { executeRefactoring(true); compareFiles(); } protected void assertRefactoringFailure() throws Exception { executeRefactoring(false); } protected void executeRefactoring(boolean expectedSuccess) throws Exception { if (ascendingVisibilityOrder) { getPreferenceStore().setValue(PreferenceConstants.CLASS_MEMBER_ASCENDING_VISIBILITY_ORDER, ascendingVisibilityOrder); } if (historyScript != null) { executeHistoryRefactoring(expectedSuccess); return; } Refactoring refactoring = createRefactoring(); RefactoringContext context; if (refactoring instanceof CRefactoring) { context = new CRefactoringContext((CRefactoring) refactoring); } else { context = new RefactoringContext(refactoring); } executeRefactoring(refactoring, context, true, expectedSuccess); } protected void executeRefactoring(Refactoring refactoring, boolean expectedSuccess) throws CoreException, Exception { executeRefactoring(refactoring, null, false, expectedSuccess); } protected void executeRefactoring(Refactoring refactoring, RefactoringContext context, boolean withUserInput, boolean expectedSuccess) throws CoreException, Exception { try { RefactoringStatus initialStatus = refactoring.checkInitialConditions(npm()); if (!expectedSuccess) { assertStatusFatalError(initialStatus); return; } if (expectedInitialErrors != 0) { assertStatusError(initialStatus, expectedInitialErrors); } else if (expectedInitialWarnings != 0) { assertStatusWarning(initialStatus, expectedInitialWarnings); } else { assertStatusOk(initialStatus); } if (withUserInput) simulateUserInput(); RefactoringStatus finalStatus = refactoring.checkFinalConditions(npm()); if (expectedFinalWarnings != 0) { assertStatusWarning(finalStatus, expectedFinalWarnings); } else if (expectedFinalInfos != 0) { assertStatusInfo(finalStatus, expectedFinalInfos); } else { assertStatusOk(finalStatus); } Change change = refactoring.createChange(npm()); change.perform(npm()); } finally { if (context != null) context.dispose(); } } private void executeHistoryRefactoring(boolean expectedSuccess) throws Exception { URI uri= URIUtil.toURI(cproject.getProject().getLocation()); String scriptSource = historyScript.getSource().replaceAll("\\$\\{projectPath\\}", uri.getPath()); RefactoringHistory history = RefactoringHistoryService.getInstance().readRefactoringHistory( new ByteArrayInputStream(scriptSource.getBytes()), 0); for (RefactoringDescriptorProxy proxy : history.getDescriptors()) { RefactoringDescriptor descriptor = proxy.requestDescriptor(npm()); RefactoringStatus status = new RefactoringStatus(); RefactoringContext context = descriptor.createRefactoringContext(status); assertTrue(status.isOK()); executeRefactoring(context.getRefactoring(), context, false, expectedSuccess); } } /** * Creates a refactoring object. */ protected abstract Refactoring createRefactoring(); /** * Subclasses can override to simulate user input. */ protected void simulateUserInput() { } protected ICProject getCProject() { return cproject; } protected IProject getProject() { return cproject.getProject(); } protected TestSourceFile getSelectedTestFile() { return selectedFile; } protected IFile getSelectedFile() { if (selectedFile == null) return null; return cproject.getProject().getFile(new Path(selectedFile.getName())); } protected ITranslationUnit getSelectedTranslationUnit() { IFile file = getSelectedFile(); if (file == null) return null; return (ITranslationUnit) CoreModel.getDefault().create(file); } protected TestSourceFile getHistoryScriptFile() { return historyScript; } protected TextSelection getSelection() { return selection; } protected boolean isCpp() { return cpp; } protected void setCpp(boolean cpp) { this.cpp = cpp; } private boolean isResultDelimiter(String str) { if (str.isEmpty()) return false; for (int i = 0; i < str.length(); i++) { if (str.charAt(i) != '=') return false; } return true; } protected void assertStatusOk(RefactoringStatus status) { if (!status.isOK()) fail("Error or warning status: " + status.getEntries()[0].getMessage()); } protected void assertStatusWarning(RefactoringStatus status, int number) { if (number > 0) { assertTrue("Warning status expected", status.hasWarning()); } RefactoringStatusEntry[] entries = status.getEntries(); int count = 0; for (RefactoringStatusEntry entry : entries) { if (entry.isWarning()) { ++count; } } assertEquals("Found " + count + " warnings instead of expected " + number, number, count); } protected void assertStatusInfo(RefactoringStatus status, int number) { if (number > 0) { assertTrue("Info status expected", status.hasInfo()); } RefactoringStatusEntry[] entries = status.getEntries(); int count = 0; for (RefactoringStatusEntry entry : entries) { if (entry.isInfo()) { ++count; } } assertEquals("Found " + count + " informational messages instead of expected " + number, number, count); } protected void assertStatusError(RefactoringStatus status, int number) { if (number > 0) { assertTrue("Error status expected", status.hasError()); } RefactoringStatusEntry[] entries = status.getEntries(); int count = 0; for (RefactoringStatusEntry entry : entries) { if (entry.isError()) { ++count; } } assertEquals("Found " + count + " errors instead of expected " + number, number, count); } protected void assertStatusFatalError(RefactoringStatus status, int number) { if (number > 0) { assertTrue("Fatal error status expected", status.hasFatalError()); } RefactoringStatusEntry[] entries = status.getEntries(); int count = 0; for (RefactoringStatusEntry entry : entries) { if (entry.isFatalError()) { ++count; } } assertEquals("Found " + count + " fatal errors instead of expected " + number, number, count); } protected void assertStatusFatalError(RefactoringStatus status) { assertTrue("Fatal error status expected", status.hasFatalError()); } protected void assertEquals(TestSourceFile testFile, IFile file) throws Exception { String actualSource = getFileContents(file); assertEquals(testFile.getExpectedSource(), actualSource); } protected void compareFiles() throws Exception { for (TestSourceFile testFile : testFiles) { String expectedSource = testFile.getExpectedSource(); IFile file = cproject.getProject().getFile(new Path(testFile.getExpectedName())); String actualSource = getFileContents(file); expectedSource= expectedSource.replace("\r\n", "\n"); actualSource= actualSource.replace("\r\n", "\n"); assertEquals(expectedSource, actualSource); } } protected String getFileContents(IFile file) throws Exception { BufferedReader reader = new BufferedReader(new InputStreamReader(file.getContents(), "UTF-8")); StringBuilder buffer = new StringBuilder(); char[] part= new char[2048]; int read= 0; while ((read= reader.read(part)) != -1) buffer.append(part, 0, read); reader.close(); return buffer.toString().replace("\r", ""); } protected void resetPreferences() { getPreferenceStore().setToDefault(PreferenceConstants.CLASS_MEMBER_ASCENDING_VISIBILITY_ORDER); } protected IPreferenceStore getPreferenceStore() { return CUIPlugin.getDefault().getPreferenceStore(); } }