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