Home | History | Annotate | Download | only in refactoring
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
     17 
     18 import static com.android.SdkConstants.FD_RES;
     19 import static com.android.SdkConstants.FD_RES_LAYOUT;
     20 import static com.android.SdkConstants.FD_RES_VALUES;
     21 
     22 import com.android.ide.common.sdk.LoadStatus;
     23 import com.android.ide.eclipse.adt.AdtPlugin;
     24 import com.android.ide.eclipse.adt.AdtUtils;
     25 import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
     26 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
     27 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
     28 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
     29 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
     30 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
     31 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
     32 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
     33 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     34 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     35 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     36 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     37 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreator;
     38 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState;
     39 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
     40 import com.android.ide.eclipse.tests.SdkLoadingTestCase;
     41 import com.android.sdklib.IAndroidTarget;
     42 
     43 import org.eclipse.core.resources.IContainer;
     44 import org.eclipse.core.resources.IFile;
     45 import org.eclipse.core.resources.IFolder;
     46 import org.eclipse.core.resources.IProject;
     47 import org.eclipse.core.resources.ResourcesPlugin;
     48 import org.eclipse.core.runtime.NullProgressMonitor;
     49 import org.eclipse.core.runtime.Path;
     50 import org.eclipse.jdt.core.IJavaProject;
     51 import org.eclipse.jface.operation.IRunnableContext;
     52 import org.eclipse.jface.operation.IRunnableWithProgress;
     53 import org.eclipse.jface.text.source.ISourceViewer;
     54 import org.eclipse.swt.graphics.Point;
     55 import org.eclipse.wst.sse.core.StructuredModelManager;
     56 import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
     57 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
     58 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
     59 
     60 import java.io.ByteArrayInputStream;
     61 import java.io.File;
     62 import java.io.InputStream;
     63 import java.lang.reflect.InvocationTargetException;
     64 import java.util.HashMap;
     65 import java.util.List;
     66 import java.util.Map;
     67 
     68 @SuppressWarnings({"restriction", "javadoc"})
     69 public abstract class AdtProjectTest extends SdkLoadingTestCase {
     70     private static final int TARGET_API_LEVEL = 16;
     71     public static final String TEST_PROJECT_PACKAGE = "com.android.eclipse.tests"; //$NON-NLS-1$
     72     private static final long TESTS_START_TIME = System.currentTimeMillis();
     73     private static final String PROJECTNAME_PREFIX = "testproject-";
     74 
     75     /**
     76      * We don't stash the project used by each test case as a field such that test cases
     77      * can share a single project instance (which is typically much faster).
     78      * However, see {@link #getProjectName()} for exceptions to this sharing scheme.
     79      */
     80     private static Map<String, IProject> sProjectMap = new HashMap<String, IProject>();
     81 
     82     @Override
     83     protected String getTestDataRelPath() {
     84         return "eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/"
     85                 + "internal/editors/layout/refactoring/testdata";
     86     }
     87 
     88     @Override
     89     protected InputStream getTestResource(String relativePath, boolean expectExists) {
     90         String path = "testdata" + File.separator + relativePath; //$NON-NLS-1$
     91         InputStream stream =
     92             AdtProjectTest.class.getResourceAsStream(path);
     93         if (!expectExists && stream == null) {
     94             return null;
     95         }
     96         return stream;
     97     }
     98 
     99     @Override
    100     protected void setUp() throws Exception {
    101         super.setUp();
    102 
    103         // Prevent preview icon computation during plugin test to make test faster
    104         if (AdtPlugin.getDefault() == null) {
    105             fail("This test must be run as an Eclipse plugin test, not a plain JUnit test!");
    106         }
    107         AdtPrefs.getPrefs().setPaletteModes("ICON_TEXT"); //$NON-NLS-1$
    108 
    109         getProject();
    110 
    111         Sdk current = Sdk.getCurrent();
    112         assertNotNull(current);
    113         LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus();
    114         assertSame(LoadStatus.LOADED, sdkStatus);
    115         IAndroidTarget target = current.getTarget(getProject());
    116         IJavaProject javaProject = BaseProjectHelper.getJavaProject(getProject());
    117         assertNotNull(javaProject);
    118         int iterations = 0;
    119         while (true) {
    120             if (iterations == 100) {
    121                 fail("Couldn't load target; ran out of time");
    122             }
    123             LoadStatus status = current.checkAndLoadTargetData(target, javaProject);
    124             if (status == LoadStatus.FAILED) {
    125                 fail("Couldn't load target " + target);
    126             }
    127             if (status != LoadStatus.LOADING) {
    128                 break;
    129             }
    130             Thread.sleep(250);
    131             iterations++;
    132         }
    133         AndroidTargetData targetData = current.getTargetData(target);
    134         assertNotNull(targetData);
    135         LayoutDescriptors layoutDescriptors = targetData.getLayoutDescriptors();
    136         assertNotNull(layoutDescriptors);
    137         List<ViewElementDescriptor> viewDescriptors = layoutDescriptors.getViewDescriptors();
    138         assertNotNull(viewDescriptors);
    139         assertTrue(viewDescriptors.size() > 0);
    140         List<ViewElementDescriptor> layoutParamDescriptors =
    141                 layoutDescriptors.getLayoutDescriptors();
    142         assertNotNull(layoutParamDescriptors);
    143         assertTrue(layoutParamDescriptors.size() > 0);
    144     }
    145 
    146     /** Set to true if the subclass test case should use a per-instance project rather
    147      * than a shared project. This is needed by projects which modify the project in such
    148      * a way that it affects what other tests see (for example, the quickfix resource creation
    149      * tests will add in new resources, which the code completion tests will then list as
    150      * possible matches if the code completion test is run after the quickfix test.)
    151      * @return true to create a per-instance project instead of the default shared project
    152      */
    153     protected boolean testCaseNeedsUniqueProject() {
    154         return false;
    155     }
    156 
    157     protected boolean testNeedsUniqueProject() {
    158         return false;
    159     }
    160 
    161     @Override
    162     protected boolean validateSdk(IAndroidTarget target) {
    163         // Not quite working yet. When enabled will make tests run faster.
    164         //if (target.getVersion().getApiLevel() < TARGET_API_LEVEL) {
    165         //    return false;
    166         //}
    167 
    168         return true;
    169     }
    170 
    171     /** Returns a name to use for the project used in this test. Subclasses do not need to
    172      * override this if they can share a project with others - which is the case if they do
    173      * not modify the project in a way that does not affect other tests. For example
    174      * the resource quickfix test will create new resources which affect what shows up
    175      * in the code completion results, so the quickfix tests will override this method
    176      * to produce a unique project for its own tests.
    177      */
    178     private String getProjectName() {
    179         if (testNeedsUniqueProject()) {
    180             return PROJECTNAME_PREFIX + getClass().getSimpleName() + "-" + getName();
    181         } else if (testCaseNeedsUniqueProject()) {
    182             return PROJECTNAME_PREFIX + getClass().getSimpleName();
    183         } else {
    184             return PROJECTNAME_PREFIX + TESTS_START_TIME;
    185         }
    186     }
    187 
    188     protected IProject getProject() {
    189         String projectName = getProjectName();
    190         IProject project = sProjectMap.get(projectName);
    191         if (project == null) {
    192             project = createProject(projectName);
    193             assertNotNull(project);
    194             sProjectMap.put(projectName, project);
    195         }
    196         if (!testCaseNeedsUniqueProject() && !testNeedsUniqueProject()) {
    197             addCleanupDir(AdtUtils.getAbsolutePath(project).toFile());
    198         }
    199         addCleanupDir(project.getFullPath().toFile());
    200         return project;
    201     }
    202 
    203     protected IFile getTestDataFile(IProject project, String name) throws Exception {
    204         return getTestDataFile(project, name, name);
    205     }
    206 
    207     protected IFile getLayoutFile(IProject project, String name) throws Exception {
    208         return getTestDataFile(project, name, FD_RES + "/" + FD_RES_LAYOUT + "/" + name);
    209     }
    210 
    211     protected IFile getValueFile(IProject project, String name) throws Exception {
    212         return getTestDataFile(project, name, FD_RES + "/" + FD_RES_VALUES + "/" + name);
    213     }
    214 
    215     protected IFile getTestDataFile(IProject project, String sourceName,
    216             String destPath) throws Exception {
    217         return getTestDataFile(project, sourceName, destPath, false);
    218     }
    219 
    220     protected IFile getTestDataFile(IProject project, String sourceName,
    221             String destPath, boolean overwrite) throws Exception {
    222         String[] split = destPath.split("/"); //$NON-NLS-1$
    223         IContainer parent;
    224         String name;
    225         if (split.length == 1) {
    226             parent = project;
    227             name = destPath;
    228         } else {
    229             IFolder folder = project.getFolder(split[0]);
    230             NullProgressMonitor monitor = new NullProgressMonitor();
    231             if (!folder.exists()) {
    232                 folder.create(true /* force */, true /* local */, monitor);
    233             }
    234             for (int i = 1, n = split.length; i < n -1; i++) {
    235                 IFolder subFolder = folder.getFolder(split[i]);
    236                 if (!subFolder.exists()) {
    237                     subFolder.create(true /* force */, true /* local */, monitor);
    238                 }
    239                 folder = subFolder;
    240             }
    241             name = split[split.length - 1];
    242             parent = folder;
    243         }
    244         IFile file = parent.getFile(new Path(name));
    245         if (overwrite && file.exists()) {
    246             String currentContents = AdtPlugin.readFile(file);
    247             String newContents = readTestFile(sourceName, true);
    248             if (currentContents == null || !currentContents.equals(newContents)) {
    249                 file.delete(true, new NullProgressMonitor());
    250             } else {
    251                 return file;
    252             }
    253         }
    254         if (!file.exists()) {
    255             String xml = readTestFile(sourceName, true);
    256             InputStream bstream = new ByteArrayInputStream(xml.getBytes("UTF-8")); //$NON-NLS-1$
    257             NullProgressMonitor monitor = new NullProgressMonitor();
    258             file.create(bstream, false /* force */, monitor);
    259         }
    260 
    261         return file;
    262     }
    263 
    264     protected IProject createProject(String name) {
    265         IAndroidTarget target = null;
    266 
    267         IAndroidTarget[] targets = getSdk().getTargets();
    268         for (IAndroidTarget t : targets) {
    269             if (!t.isPlatform()) {
    270                 continue;
    271             }
    272             if (t.getVersion().getApiLevel() >= TARGET_API_LEVEL) {
    273                 target = t;
    274                 break;
    275             }
    276         }
    277         assertNotNull(target);
    278 
    279         IRunnableContext context = new IRunnableContext() {
    280             @Override
    281             public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
    282                     throws InvocationTargetException, InterruptedException {
    283                 runnable.run(new NullProgressMonitor());
    284             }
    285         };
    286         NewProjectWizardState state = new NewProjectWizardState(Mode.ANY);
    287         state.projectName = name;
    288         state.target = target;
    289         state.packageName = TEST_PROJECT_PACKAGE;
    290         state.activityName = name;
    291         state.applicationName = name;
    292         state.createActivity = false;
    293         state.useDefaultLocation = true;
    294         if (getMinSdk() != -1) {
    295             state.minSdk = Integer.toString(getMinSdk());
    296         }
    297 
    298         NewProjectCreator creator = new NewProjectCreator(state, context);
    299         creator.createAndroidProjects();
    300         return validateProjectExists(name);
    301     }
    302 
    303     protected int getMinSdk() {
    304         return -1;
    305     }
    306 
    307     public void createTestProject() {
    308         IAndroidTarget target = null;
    309 
    310         IAndroidTarget[] targets = getSdk().getTargets();
    311         for (IAndroidTarget t : targets) {
    312             if (t.getVersion().getApiLevel() >= TARGET_API_LEVEL) {
    313                 target = t;
    314                 break;
    315             }
    316         }
    317         assertNotNull(target);
    318     }
    319 
    320     private static IProject validateProjectExists(String name) {
    321         IProject iproject = getProject(name);
    322         assertTrue(String.format("%s project not created", name), iproject.exists());
    323         assertTrue(String.format("%s project not opened", name), iproject.isOpen());
    324         return iproject;
    325     }
    326 
    327     private static IProject getProject(String name) {
    328         IProject iproject = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
    329         return iproject;
    330     }
    331 
    332     protected int getCaretOffset(IFile file, String caretLocation) {
    333         assertTrue(caretLocation, caretLocation.contains("^"));
    334 
    335         String fileContent = AdtPlugin.readFile(file);
    336         return getCaretOffset(fileContent, caretLocation);
    337     }
    338 
    339     /**
    340      * If the given caret location string contains a selection range, select that range in
    341      * the given viewer
    342      *
    343      * @param viewer the viewer to contain the selection
    344      * @param caretLocation the location string
    345      */
    346     protected int updateCaret(ISourceViewer viewer, String caretLocation) {
    347         assertTrue(caretLocation, caretLocation.contains("^")); //$NON-NLS-1$
    348 
    349         int caretDelta = caretLocation.indexOf("^"); //$NON-NLS-1$
    350         assertTrue(caretLocation, caretDelta != -1);
    351         String text = viewer.getTextWidget().getText();
    352 
    353         int length = 0;
    354 
    355         // String around caret/range without the range and caret marker characters
    356         String caretContext;
    357 
    358         if (caretLocation.contains("[^")) { //$NON-NLS-1$
    359             caretDelta--;
    360             assertTrue(caretLocation, caretLocation.startsWith("[^", caretDelta)); //$NON-NLS-1$
    361 
    362             int caretRangeEnd = caretLocation.indexOf(']', caretDelta + 2);
    363             assertTrue(caretLocation, caretRangeEnd != -1);
    364             length = caretRangeEnd - caretDelta - 2;
    365             assertTrue(length > 0);
    366             caretContext = caretLocation.substring(0, caretDelta)
    367                     + caretLocation.substring(caretDelta + 2, caretRangeEnd)
    368                     + caretLocation.substring(caretRangeEnd + 1);
    369         } else {
    370             caretContext = caretLocation.substring(0, caretDelta)
    371                     + caretLocation.substring(caretDelta + 1); // +1: skip "^"
    372         }
    373 
    374         int caretContextIndex = text.indexOf(caretContext);
    375 
    376         assertTrue("Caret content " + caretContext + " not found in file",
    377                 caretContextIndex != -1);
    378 
    379         int offset = caretContextIndex + caretDelta;
    380         viewer.setSelectedRange(offset, length);
    381 
    382         return offset;
    383     }
    384 
    385     protected String addSelection(String newFileContents, Point selectedRange) {
    386         int selectionBegin = selectedRange.x;
    387         int selectionEnd = selectionBegin + selectedRange.y;
    388         return addSelection(newFileContents, selectionBegin, selectionEnd);
    389     }
    390 
    391     @Override
    392     protected String removeSessionData(String data) {
    393         data = super.removeSessionData(data);
    394         if (getProject() != null) {
    395             data = data.replace(getProject().getName(), "PROJECTNAME");
    396         }
    397 
    398         return data;
    399     }
    400 
    401     public static ViewElementDescriptor createDesc(String name, String fqn, boolean hasChildren) {
    402         if (hasChildren) {
    403             return new ViewElementDescriptor(name, name, fqn, "", "", new AttributeDescriptor[0],
    404                     new AttributeDescriptor[0], new ElementDescriptor[1], false);
    405         } else {
    406             return new ViewElementDescriptor(name, fqn);
    407         }
    408     }
    409 
    410     public static UiViewElementNode createNode(UiViewElementNode parent, String fqn,
    411             boolean hasChildren) {
    412         String name = fqn.substring(fqn.lastIndexOf('.') + 1);
    413         ViewElementDescriptor descriptor = createDesc(name, fqn, hasChildren);
    414         if (parent == null) {
    415             // All node hierarchies should be wrapped inside a document node at the root
    416             parent = new UiViewElementNode(createDesc("doc", "doc", true));
    417         }
    418         return (UiViewElementNode) parent.appendNewUiChild(descriptor);
    419     }
    420 
    421     public static UiViewElementNode createNode(String fqn, boolean hasChildren) {
    422         return createNode(null, fqn, hasChildren);
    423     }
    424 
    425     /** Special editor context set on the model to be rendered */
    426     protected static class TestLayoutEditorDelegate extends LayoutEditorDelegate {
    427 
    428         public TestLayoutEditorDelegate(
    429                 IFile file,
    430                 IStructuredDocument structuredDocument,
    431                 UiDocumentNode uiRootNode) {
    432             super(new TestAndroidXmlCommonEditor(file, structuredDocument, uiRootNode));
    433         }
    434 
    435         static class TestAndroidXmlCommonEditor extends CommonXmlEditor {
    436 
    437             private final IFile mFile;
    438             private final IStructuredDocument mStructuredDocument;
    439             private UiDocumentNode mUiRootNode;
    440 
    441             TestAndroidXmlCommonEditor(
    442                     IFile file,
    443                     IStructuredDocument structuredDocument,
    444                     UiDocumentNode uiRootNode) {
    445                 mFile = file;
    446                 mStructuredDocument = structuredDocument;
    447                 mUiRootNode = uiRootNode;
    448             }
    449 
    450             @Override
    451             public IFile getInputFile() {
    452                 return mFile;
    453             }
    454 
    455             @Override
    456             public IProject getProject() {
    457                 return mFile.getProject();
    458             }
    459 
    460             @Override
    461             public IStructuredDocument getStructuredDocument() {
    462                 return mStructuredDocument;
    463             }
    464 
    465             @Override
    466             public UiDocumentNode getUiRootNode() {
    467                 return mUiRootNode;
    468             }
    469 
    470             @Override
    471             public void editorDirtyStateChanged() {
    472             }
    473 
    474             @Override
    475             public IStructuredModel getModelForRead() {
    476                 IModelManager mm = StructuredModelManager.getModelManager();
    477                 if (mm != null) {
    478                     try {
    479                         return mm.getModelForRead(mFile);
    480                     } catch (Exception e) {
    481                         fail(e.toString());
    482                     }
    483                 }
    484 
    485                 return null;
    486             }
    487         }
    488     }
    489 
    490     public void testDummy() {
    491         // This class contains shared test functionality for testcase subclasses,
    492         // but without an actual test in the class JUnit complains (even if we make
    493         // it abstract)
    494     }
    495 }
    496