• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
getTestDataRelPath()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
getTestResource(String relativePath, boolean expectExists)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
setUp()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      */
testCaseNeedsUniqueProject()153     protected boolean testCaseNeedsUniqueProject() {
154         return false;
155     }
156 
testNeedsUniqueProject()157     protected boolean testNeedsUniqueProject() {
158         return false;
159     }
160 
161     @Override
validateSdk(IAndroidTarget target)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      */
getProjectName()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 
getProject()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 
getTestDataFile(IProject project, String name)203     protected IFile getTestDataFile(IProject project, String name) throws Exception {
204         return getTestDataFile(project, name, name);
205     }
206 
getLayoutFile(IProject project, String name)207     protected IFile getLayoutFile(IProject project, String name) throws Exception {
208         return getTestDataFile(project, name, FD_RES + "/" + FD_RES_LAYOUT + "/" + name);
209     }
210 
getValueFile(IProject project, String name)211     protected IFile getValueFile(IProject project, String name) throws Exception {
212         return getTestDataFile(project, name, FD_RES + "/" + FD_RES_VALUES + "/" + name);
213     }
214 
getTestDataFile(IProject project, String sourceName, String destPath)215     protected IFile getTestDataFile(IProject project, String sourceName,
216             String destPath) throws Exception {
217         return getTestDataFile(project, sourceName, destPath, false);
218     }
219 
getTestDataFile(IProject project, String sourceName, String destPath, boolean overwrite)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 
createProject(String name)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 
getMinSdk()303     protected int getMinSdk() {
304         return -1;
305     }
306 
createTestProject()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 
validateProjectExists(String name)320     protected 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 
getProject(String name)327     private static IProject getProject(String name) {
328         IProject iproject = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
329         return iproject;
330     }
331 
getCaretOffset(IFile file, String caretLocation)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      */
updateCaret(ISourceViewer viewer, String caretLocation)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 
addSelection(String newFileContents, Point selectedRange)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
removeSessionData(String data)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 
createDesc(String name, String fqn, boolean hasChildren)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 
createNode(UiViewElementNode parent, String fqn, boolean hasChildren)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 
createNode(String fqn, boolean hasChildren)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 
TestLayoutEditorDelegate( IFile file, IStructuredDocument structuredDocument, UiDocumentNode uiRootNode)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 
TestAndroidXmlCommonEditor( IFile file, IStructuredDocument structuredDocument, UiDocumentNode uiRootNode)441             TestAndroidXmlCommonEditor(
442                     IFile file,
443                     IStructuredDocument structuredDocument,
444                     UiDocumentNode uiRootNode) {
445                 mFile = file;
446                 mStructuredDocument = structuredDocument;
447                 mUiRootNode = uiRootNode;
448             }
449 
450             @Override
getInputFile()451             public IFile getInputFile() {
452                 return mFile;
453             }
454 
455             @Override
getProject()456             public IProject getProject() {
457                 return mFile.getProject();
458             }
459 
460             @Override
getStructuredDocument()461             public IStructuredDocument getStructuredDocument() {
462                 return mStructuredDocument;
463             }
464 
465             @Override
getUiRootNode()466             public UiDocumentNode getUiRootNode() {
467                 return mUiRootNode;
468             }
469 
470             @Override
editorDirtyStateChanged()471             public void editorDirtyStateChanged() {
472             }
473 
474             @Override
getModelForRead()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 
testDummy()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