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.AndroidConstants.FD_RES_LAYOUT; 19 import static com.android.AndroidConstants.FD_RES_VALUES; 20 import static com.android.sdklib.SdkConstants.FD_RES; 21 22 import com.android.ide.eclipse.adt.AdtPlugin; 23 import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; 24 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; 25 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; 26 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; 27 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; 28 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; 29 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 30 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreator; 31 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState; 32 import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode; 33 import com.android.ide.eclipse.tests.SdkTestCase; 34 import com.android.sdklib.IAndroidTarget; 35 import com.android.sdklib.SdkConstants; 36 37 import org.eclipse.core.resources.IContainer; 38 import org.eclipse.core.resources.IFile; 39 import org.eclipse.core.resources.IFolder; 40 import org.eclipse.core.resources.IProject; 41 import org.eclipse.core.resources.ResourcesPlugin; 42 import org.eclipse.core.runtime.NullProgressMonitor; 43 import org.eclipse.core.runtime.Path; 44 import org.eclipse.jface.operation.IRunnableContext; 45 import org.eclipse.jface.operation.IRunnableWithProgress; 46 import org.eclipse.jface.text.source.ISourceViewer; 47 import org.eclipse.swt.graphics.Point; 48 import org.eclipse.wst.sse.core.StructuredModelManager; 49 import org.eclipse.wst.sse.core.internal.provisional.IModelManager; 50 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; 51 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; 52 53 import java.io.BufferedReader; 54 import java.io.ByteArrayInputStream; 55 import java.io.File; 56 import java.io.InputStream; 57 import java.io.InputStreamReader; 58 import java.lang.reflect.InvocationTargetException; 59 import java.util.Calendar; 60 import java.util.HashMap; 61 import java.util.Map; 62 63 @SuppressWarnings("restriction") 64 public class AdtProjectTest extends SdkTestCase { 65 private static final int TARGET_API_LEVEL = 12; 66 public static final String TEST_PROJECT_PACKAGE = "com.android.eclipse.tests"; //$NON-NLS-1$ 67 68 /** Update golden files if different from the actual results */ 69 private static final boolean UPDATE_DIFFERENT_FILES = false; 70 /** Create golden files if missing */ 71 private static final boolean UPDATE_MISSING_FILES = true; 72 private static final String TEST_DATA_REL_PATH = 73 "eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/" 74 + "internal/editors/layout/refactoring/testdata"; 75 private static final String PROJECTNAME_PREFIX = "testproject-"; 76 private static final long TESTS_START_TIME = System.currentTimeMillis(); 77 private static File sTempDir = null; 78 79 /** 80 * We don't stash the project used by each test case as a field such that test cases 81 * can share a single project instance (which is typically much faster). 82 * However, see {@link #getProjectName()} for exceptions to this sharing scheme. 83 */ 84 private static Map<String, IProject> sProjectMap = new HashMap<String, IProject>(); 85 86 @Override 87 protected void setUp() throws Exception { 88 super.setUp(); 89 90 // Prevent preview icon computation during plugin test to make test faster 91 if (AdtPlugin.getDefault() == null) { 92 fail("This test must be run as an Eclipse plugin test, not a plain JUnit test!"); 93 } 94 AdtPrefs.getPrefs().setPaletteModes("ICON_TEXT"); //$NON-NLS-1$ 95 96 getProject(); 97 } 98 99 /** Set to true if the subclass test case should use a per-instance project rather 100 * than a shared project. This is needed by projects which modify the project in such 101 * a way that it affects what other tests see (for example, the quickfix resource creation 102 * tests will add in new resources, which the code completion tests will then list as 103 * possible matches if the code completion test is run after the quickfix test.) 104 * @return true to create a per-instance project instead of the default shared project 105 */ 106 protected boolean testCaseNeedsUniqueProject() { 107 return false; 108 } 109 110 protected boolean testNeedsUniqueProject() { 111 return false; 112 } 113 114 @Override 115 protected boolean validateSdk(IAndroidTarget target) { 116 // Not quite working yet. When enabled will make tests run faster. 117 //if (target.getVersion().getApiLevel() < TARGET_API_LEVEL) { 118 // return false; 119 //} 120 121 return true; 122 } 123 124 /** Returns a name to use for the project used in this test. Subclasses do not need to 125 * override this if they can share a project with others - which is the case if they do 126 * not modify the project in a way that does not affect other tests. For example 127 * the resource quickfix test will create new resources which affect what shows up 128 * in the code completion results, so the quickfix tests will override this method 129 * to produce a unique project for its own tests. 130 */ 131 private String getProjectName() { 132 if (testNeedsUniqueProject()) { 133 return PROJECTNAME_PREFIX + getClass().getSimpleName() + "-" + getName(); 134 } else if (testCaseNeedsUniqueProject()) { 135 return PROJECTNAME_PREFIX + getClass().getSimpleName(); 136 } else { 137 return PROJECTNAME_PREFIX + TESTS_START_TIME; 138 } 139 } 140 141 protected IProject getProject() { 142 String projectName = getProjectName(); 143 IProject project = sProjectMap.get(projectName); 144 if (project == null) { 145 project = createProject(projectName); 146 assertNotNull(project); 147 sProjectMap.put(projectName, project); 148 } 149 150 return project; 151 } 152 153 protected IFile getTestDataFile(IProject project, String name) throws Exception { 154 return getTestDataFile(project, name, name); 155 } 156 157 protected IFile getLayoutFile(IProject project, String name) throws Exception { 158 return getTestDataFile(project, name, FD_RES + "/" + FD_RES_LAYOUT + "/" + name); 159 } 160 161 protected IFile getValueFile(IProject project, String name) throws Exception { 162 return getTestDataFile(project, name, FD_RES + "/" + FD_RES_VALUES + "/" + name); 163 } 164 165 protected IFile getTestDataFile(IProject project, String sourceName, 166 String destPath) throws Exception { 167 return getTestDataFile(project, sourceName, destPath, false); 168 } 169 170 protected IFile getTestDataFile(IProject project, String sourceName, 171 String destPath, boolean overwrite) throws Exception { 172 String[] split = destPath.split("/"); //$NON-NLS-1$ 173 IContainer parent; 174 String name; 175 if (split.length == 1) { 176 parent = project; 177 name = destPath; 178 } else { 179 IFolder folder = project.getFolder(split[0]); 180 NullProgressMonitor monitor = new NullProgressMonitor(); 181 if (!folder.exists()) { 182 folder.create(true /* force */, true /* local */, monitor); 183 } 184 for (int i = 1, n = split.length; i < n -1; i++) { 185 IFolder subFolder = folder.getFolder(split[i]); 186 if (!subFolder.exists()) { 187 subFolder.create(true /* force */, true /* local */, monitor); 188 } 189 folder = subFolder; 190 } 191 name = split[split.length - 1]; 192 parent = folder; 193 } 194 IFile file = parent.getFile(new Path(name)); 195 if (overwrite && file.exists()) { 196 String currentContents = AdtPlugin.readFile(file); 197 String newContents = readTestFile(sourceName, true); 198 if (currentContents == null || !currentContents.equals(newContents)) { 199 file.delete(true, new NullProgressMonitor()); 200 } else { 201 return file; 202 } 203 } 204 if (!file.exists()) { 205 String xml = readTestFile(sourceName, true); 206 InputStream bstream = new ByteArrayInputStream(xml.getBytes("UTF-8")); //$NON-NLS-1$ 207 NullProgressMonitor monitor = new NullProgressMonitor(); 208 file.create(bstream, false /* force */, monitor); 209 } 210 211 return file; 212 } 213 214 protected IProject createProject(String name) { 215 IAndroidTarget target = null; 216 217 IAndroidTarget[] targets = getSdk().getTargets(); 218 for (IAndroidTarget t : targets) { 219 if (t.getVersion().getApiLevel() >= TARGET_API_LEVEL) { 220 target = t; 221 break; 222 } 223 } 224 assertNotNull(target); 225 226 227 IRunnableContext context = new IRunnableContext() { 228 public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable) 229 throws InvocationTargetException, InterruptedException { 230 runnable.run(new NullProgressMonitor()); 231 } 232 }; 233 NewProjectWizardState state = new NewProjectWizardState(Mode.ANY); 234 state.projectName = name; 235 state.target = target; 236 state.packageName = TEST_PROJECT_PACKAGE; 237 state.activityName = name; 238 state.applicationName = name; 239 state.createActivity = false; 240 state.useDefaultLocation = true; 241 242 NewProjectCreator creator = new NewProjectCreator(state, context); 243 creator.createAndroidProjects(); 244 return validateProjectExists(name); 245 } 246 247 public void createTestProject() { 248 IAndroidTarget target = null; 249 250 IAndroidTarget[] targets = getSdk().getTargets(); 251 for (IAndroidTarget t : targets) { 252 if (t.getVersion().getApiLevel() >= TARGET_API_LEVEL) { 253 target = t; 254 break; 255 } 256 } 257 assertNotNull(target); 258 } 259 260 private static IProject validateProjectExists(String name) { 261 IProject iproject = getProject(name); 262 assertTrue(String.format("%s project not created", name), iproject.exists()); 263 assertTrue(String.format("%s project not opened", name), iproject.isOpen()); 264 return iproject; 265 } 266 267 private static IProject getProject(String name) { 268 IProject iproject = ResourcesPlugin.getWorkspace().getRoot() 269 .getProject(name); 270 return iproject; 271 } 272 273 protected int getCaretOffset(IFile file, String caretLocation) { 274 assertTrue(caretLocation, caretLocation.contains("^")); 275 276 String fileContent = AdtPlugin.readFile(file); 277 return getCaretOffset(fileContent, caretLocation); 278 } 279 280 protected int getCaretOffset(String fileContent, String caretLocation) { 281 assertTrue(caretLocation, caretLocation.contains("^")); //$NON-NLS-1$ 282 283 int caretDelta = caretLocation.indexOf("^"); //$NON-NLS-1$ 284 assertTrue(caretLocation, caretDelta != -1); 285 286 // String around caret/range without the range and caret marker characters 287 String caretContext; 288 if (caretLocation.contains("[^")) { //$NON-NLS-1$ 289 caretDelta--; 290 assertTrue(caretLocation, caretLocation.startsWith("[^", caretDelta)); //$NON-NLS-1$ 291 int caretRangeEnd = caretLocation.indexOf(']', caretDelta + 2); 292 assertTrue(caretLocation, caretRangeEnd != -1); 293 caretContext = caretLocation.substring(0, caretDelta) 294 + caretLocation.substring(caretDelta + 2, caretRangeEnd) 295 + caretLocation.substring(caretRangeEnd + 1); 296 } else { 297 caretContext = caretLocation.substring(0, caretDelta) 298 + caretLocation.substring(caretDelta + 1); // +1: skip "^" 299 } 300 301 int caretContextIndex = fileContent.indexOf(caretContext); 302 assertTrue("Caret content " + caretContext + " not found in file", 303 caretContextIndex != -1); 304 return caretContextIndex + caretDelta; 305 } 306 307 /** 308 * If the given caret location string contains a selection range, select that range in 309 * the given viewer 310 * 311 * @param viewer the viewer to contain the selection 312 * @param caretLocation the location string 313 */ 314 protected int updateCaret(ISourceViewer viewer, String caretLocation) { 315 assertTrue(caretLocation, caretLocation.contains("^")); //$NON-NLS-1$ 316 317 int caretDelta = caretLocation.indexOf("^"); //$NON-NLS-1$ 318 assertTrue(caretLocation, caretDelta != -1); 319 String text = viewer.getTextWidget().getText(); 320 321 int length = 0; 322 323 // String around caret/range without the range and caret marker characters 324 String caretContext; 325 326 if (caretLocation.contains("[^")) { //$NON-NLS-1$ 327 caretDelta--; 328 assertTrue(caretLocation, caretLocation.startsWith("[^", caretDelta)); //$NON-NLS-1$ 329 330 int caretRangeEnd = caretLocation.indexOf(']', caretDelta + 2); 331 assertTrue(caretLocation, caretRangeEnd != -1); 332 length = caretRangeEnd - caretDelta - 2; 333 assertTrue(length > 0); 334 caretContext = caretLocation.substring(0, caretDelta) 335 + caretLocation.substring(caretDelta + 2, caretRangeEnd) 336 + caretLocation.substring(caretRangeEnd + 1); 337 } else { 338 caretContext = caretLocation.substring(0, caretDelta) 339 + caretLocation.substring(caretDelta + 1); // +1: skip "^" 340 } 341 342 int caretContextIndex = text.indexOf(caretContext); 343 344 assertTrue("Caret content " + caretContext + " not found in file", 345 caretContextIndex != -1); 346 347 int offset = caretContextIndex + caretDelta; 348 viewer.setSelectedRange(offset, length); 349 350 return offset; 351 } 352 353 protected String addSelection(String newFileContents, Point selectedRange) { 354 int selectionBegin = selectedRange.x; 355 int selectionEnd = selectionBegin + selectedRange.y; 356 return addSelection(newFileContents, selectionBegin, selectionEnd); 357 } 358 359 protected String addSelection(String newFileContents, int selectionBegin, int selectionEnd) { 360 // Insert selection markers -- [ ] for the selection range, ^ for the caret 361 String newFileWithCaret; 362 if (selectionBegin < selectionEnd) { 363 newFileWithCaret = newFileContents.substring(0, selectionBegin) + "[^" 364 + newFileContents.substring(selectionBegin, selectionEnd) + "]" 365 + newFileContents.substring(selectionEnd); 366 } else { 367 // Selected range 368 newFileWithCaret = newFileContents.substring(0, selectionBegin) + "^" 369 + newFileContents.substring(selectionBegin); 370 } 371 372 return newFileWithCaret; 373 } 374 375 protected String getCaretContext(String file, int offset) { 376 int windowSize = 20; 377 int begin = Math.max(0, offset - windowSize / 2); 378 int end = Math.min(file.length(), offset + windowSize / 2); 379 380 return "..." + file.substring(begin, offset) + "^" + file.substring(offset, end) + "..."; 381 } 382 383 /** 384 * Very primitive line differ, intended for files where there are very minor changes 385 * (such as code completion apply-tests) 386 */ 387 protected String getDiff(String before, String after) { 388 // Do line by line analysis 389 String[] beforeLines = before.split("\n"); 390 String[] afterLines = after.split("\n"); 391 392 int firstDelta = 0; 393 for (; firstDelta < Math.min(beforeLines.length, afterLines.length); firstDelta++) { 394 if (!beforeLines[firstDelta].equals(afterLines[firstDelta])) { 395 break; 396 } 397 } 398 399 if (firstDelta == beforeLines.length && firstDelta == afterLines.length) { 400 return ""; 401 } 402 403 // Counts from the end of both arrays 404 int lastDelta = 0; 405 for (; lastDelta < Math.min(beforeLines.length, afterLines.length); lastDelta++) { 406 if (!beforeLines[beforeLines.length - 1 - lastDelta].equals( 407 afterLines[afterLines.length - 1 - lastDelta])) { 408 break; 409 } 410 } 411 412 413 boolean showBeforeWindow = firstDelta >= beforeLines.length - lastDelta; 414 boolean showAfterWindow = firstDelta >= afterLines.length - lastDelta; 415 416 StringBuilder sb = new StringBuilder(); 417 if (showAfterWindow && firstDelta > 0) { 418 sb.append(" "); 419 sb.append(afterLines[firstDelta - 1]); 420 sb.append('\n'); 421 } 422 for (int i = firstDelta; i < beforeLines.length - lastDelta; i++) { 423 sb.append("<"); 424 if (beforeLines[i].length() > 0) { 425 sb.append(" "); 426 } 427 sb.append(beforeLines[i]); 428 sb.append('\n'); 429 } 430 if (showAfterWindow && lastDelta < afterLines.length - 1) { 431 sb.append(" "); 432 sb.append(afterLines[afterLines.length - (lastDelta -1)]); 433 sb.append('\n'); 434 } 435 436 sb.append("---\n"); 437 438 if (showBeforeWindow && firstDelta > 0) { 439 sb.append(" "); 440 sb.append(beforeLines[firstDelta - 1]); 441 sb.append('\n'); 442 } 443 for (int i = firstDelta; i < afterLines.length - lastDelta; i++) { 444 sb.append(">"); 445 if (afterLines[i].length() > 0) { 446 sb.append(" "); 447 } 448 sb.append(afterLines[i]); 449 sb.append('\n'); 450 } 451 if (showBeforeWindow && lastDelta < beforeLines.length - 1) { 452 sb.append(" "); 453 sb.append(beforeLines[beforeLines.length - (lastDelta -1)]); 454 sb.append('\n'); 455 } 456 457 return sb.toString(); 458 } 459 460 protected String removeSessionData(String data) { 461 if (getProject() != null) { 462 data = data.replace(getProject().getName(), "PROJECTNAME"); 463 } 464 465 return data; 466 } 467 468 public static ViewElementDescriptor createDesc(String name, String fqn, boolean hasChildren) { 469 if (hasChildren) { 470 return new ViewElementDescriptor(name, name, fqn, "", "", new AttributeDescriptor[0], 471 new AttributeDescriptor[0], new ElementDescriptor[1], false); 472 } else { 473 return new ViewElementDescriptor(name, fqn); 474 } 475 } 476 477 public static UiViewElementNode createNode(UiViewElementNode parent, String fqn, 478 boolean hasChildren) { 479 String name = fqn.substring(fqn.lastIndexOf('.') + 1); 480 ViewElementDescriptor descriptor = createDesc(name, fqn, hasChildren); 481 if (parent == null) { 482 // All node hierarchies should be wrapped inside a document node at the root 483 parent = new UiViewElementNode(createDesc("doc", "doc", true)); 484 } 485 return (UiViewElementNode) parent.appendNewUiChild(descriptor); 486 } 487 488 public static UiViewElementNode createNode(String fqn, boolean hasChildren) { 489 return createNode(null, fqn, hasChildren); 490 } 491 492 protected String readTestFile(String relativePath, boolean expectExists) { 493 String path = "testdata" + File.separator + relativePath; //$NON-NLS-1$ 494 InputStream stream = 495 AdtProjectTest.class.getResourceAsStream(path); 496 if (!expectExists && stream == null) { 497 return null; 498 } 499 500 assertNotNull(relativePath + " does not exist", stream); 501 502 BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); 503 String xml = AdtPlugin.readFile(reader); 504 assertNotNull(xml); 505 assertTrue(xml.length() > 0); 506 507 // Remove any references to the project name such that we are isolated from 508 // that in golden file. 509 // Appears in strings.xml etc. 510 xml = removeSessionData(xml); 511 512 return xml; 513 } 514 515 protected void assertEqualsGolden(String basename, String actual) { 516 assertEqualsGolden(basename, actual, basename.substring(basename.lastIndexOf('.') + 1)); 517 } 518 519 protected void assertEqualsGolden(String basename, String actual, String newExtension) { 520 String testName = getName(); 521 if (testName.startsWith("test")) { 522 testName = testName.substring(4); 523 if (Character.isUpperCase(testName.charAt(0))) { 524 testName = Character.toLowerCase(testName.charAt(0)) + testName.substring(1); 525 } 526 } 527 String expectedName; 528 String extension = basename.substring(basename.lastIndexOf('.') + 1); 529 if (newExtension == null) { 530 newExtension = extension; 531 } 532 expectedName = basename.substring(0, basename.indexOf('.')) 533 + "-expected-" + testName + '.' + newExtension; 534 String expected = readTestFile(expectedName, false); 535 if (expected == null) { 536 File expectedPath = new File( 537 UPDATE_MISSING_FILES ? getTargetDir() : getTempDir(), expectedName); 538 AdtPlugin.writeFile(expectedPath, actual); 539 System.out.println("Expected - written to " + expectedPath + ":\n"); 540 System.out.println(actual); 541 fail("Did not find golden file (" + expectedName + "): Wrote contents as " 542 + expectedPath); 543 } else { 544 if (!expected.equals(actual)) { 545 File expectedPath = new File(getTempDir(), expectedName); 546 File actualPath = new File(getTempDir(), 547 expectedName.replace("expected", "actual")); 548 AdtPlugin.writeFile(expectedPath, expected); 549 AdtPlugin.writeFile(actualPath, actual); 550 // Also update data dir with the current value 551 if (UPDATE_DIFFERENT_FILES) { 552 AdtPlugin.writeFile( new File(getTargetDir(), expectedName), actual); 553 } 554 System.out.println("The files differ: diff " + expectedPath + " " 555 + actualPath); 556 assertEquals("The files differ - see " + expectedPath + " versus " + actualPath, 557 expected, actual); 558 } 559 } 560 } 561 562 /** Get the location to write missing golden files to */ 563 protected File getTargetDir() { 564 // Set $ADT_SDK_SOURCE_PATH to point to your git "sdk" directory; if done, then 565 // if you run a unit test which refers to a golden file which does not exist, it 566 // will be created directly into the test data directory and you can rerun the 567 // test 568 // and it should pass (after you verify that the golden file contains the correct 569 // result of course). 570 String sdk = System.getenv("ADT_SDK_SOURCE_PATH"); 571 if (sdk != null) { 572 File sdkPath = new File(sdk); 573 if (sdkPath.exists()) { 574 File testData = new File(sdkPath, TEST_DATA_REL_PATH.replace('/', 575 File.separatorChar)); 576 if (testData.exists()) { 577 return testData; 578 } 579 } 580 } 581 return getTempDir(); 582 } 583 584 protected File getTempDir() { 585 if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) { 586 return new File("/tmp"); //$NON-NLS-1$ 587 } 588 589 if (sTempDir == null) { 590 // On Windows, we don't want to pollute the temp folder (which is generally 591 // already incredibly busy). So let's create a temp folder for the results. 592 593 File base = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$ 594 595 Calendar c = Calendar.getInstance(); 596 String name = String.format("adtTests_%1$tF_%1$tT", c).replace(':', '-'); //$NON-NLS-1$ 597 File tmpDir = new File(base, name); 598 if (!tmpDir.exists() && tmpDir.mkdir()) { 599 sTempDir = tmpDir; 600 } else { 601 sTempDir = base; 602 } 603 } 604 605 return sTempDir; 606 } 607 608 /** Special editor context set on the model to be rendered */ 609 protected static class TestLayoutEditor extends LayoutEditor { 610 private final IFile mFile; 611 private final IStructuredDocument mStructuredDocument; 612 private UiDocumentNode mUiRootNode; 613 614 public TestLayoutEditor(IFile file, IStructuredDocument structuredDocument, 615 UiDocumentNode uiRootNode) { 616 mFile = file; 617 mStructuredDocument = structuredDocument; 618 mUiRootNode = uiRootNode; 619 } 620 621 @Override 622 public IFile getInputFile() { 623 return mFile; 624 } 625 626 @Override 627 public IProject getProject() { 628 return mFile.getProject(); 629 } 630 631 @Override 632 public IStructuredDocument getStructuredDocument() { 633 return mStructuredDocument; 634 } 635 636 @Override 637 public UiDocumentNode getUiRootNode() { 638 return mUiRootNode; 639 } 640 641 @Override 642 public void editorDirtyStateChanged() { 643 } 644 645 @Override 646 public IStructuredModel getModelForRead() { 647 IModelManager mm = StructuredModelManager.getModelManager(); 648 if (mm != null) { 649 try { 650 return mm.getModelForRead(mFile); 651 } catch (Exception e) { 652 fail(e.toString()); 653 } 654 } 655 656 return null; 657 } 658 } 659 660 public void testDummy() { 661 // This class contains shared test functionality for testcase subclasses, 662 // but without an actual test in the class JUnit complains (even if we make 663 // it abstract) 664 } 665 } 666