| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.testFramework; |
| |
| import com.intellij.codeInsight.generation.actions.CommentByLineCommentAction; |
| import com.intellij.ide.DataManager; |
| import com.intellij.injected.editor.DocumentWindow; |
| import com.intellij.injected.editor.EditorWindow; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.Result; |
| import com.intellij.openapi.application.ex.PathManagerEx; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.command.WriteCommandAction; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.*; |
| import com.intellij.openapi.editor.actionSystem.EditorActionHandler; |
| import com.intellij.openapi.editor.actionSystem.EditorActionManager; |
| import com.intellij.openapi.editor.ex.util.EditorUtil; |
| import com.intellij.openapi.editor.impl.DocumentImpl; |
| import com.intellij.openapi.editor.impl.EditorImpl; |
| import com.intellij.openapi.editor.impl.TrailingSpacesStripper; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.fileEditor.OpenFileDescriptor; |
| import com.intellij.openapi.project.ProjectManager; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.ThrowableComputable; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.io.FileUtilRt; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.CharsetToolkit; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VfsUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.vfs.encoding.EncodingProjectManager; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.impl.source.PostprocessReformattingAspect; |
| import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; |
| import com.intellij.rt.execution.junit.FileComparisonFailure; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.runners.Parameterized; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| public abstract class LightPlatformCodeInsightTestCase extends LightPlatformTestCase { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.testFramework.LightCodeInsightTestCase"); |
| |
| protected static Editor myEditor; |
| protected static PsiFile myFile; |
| protected static VirtualFile myVFile; |
| |
| @Override |
| protected void runTest() throws Throwable { |
| if (isRunInWriteAction()) { |
| WriteCommandAction.runWriteCommandAction(getProject(), new ThrowableComputable<Void, Throwable>() { |
| @Override |
| public Void compute() throws Throwable { |
| doRunTest(); |
| return null; |
| } |
| }); |
| } |
| else { |
| new WriteCommandAction.Simple(getProject()){ |
| @Override |
| protected void run() throws Throwable { |
| doRunTest(); |
| } |
| }.performCommand(); |
| } |
| } |
| |
| protected void doRunTest() throws Throwable { |
| LightPlatformCodeInsightTestCase.super.runTest(); |
| } |
| |
| protected boolean isRunInWriteAction() { |
| return true; |
| } |
| |
| /** |
| * Configure test from data file. Data file is usual java, xml or whatever file that needs to be tested except it |
| * has <caret> marker where caret should be placed when file is loaded in editor and <selection></selection> |
| * denoting selection bounds. |
| * @param filePath - relative path from %IDEA_INSTALLATION_HOME%/testData/ |
| */ |
| protected void configureByFile(@TestDataFile @NonNls @NotNull String filePath) { |
| try { |
| final File ioFile = new File(getTestDataPath() + filePath); |
| String fileText = FileUtilRt.loadFile(ioFile, CharsetToolkit.UTF8, true); |
| configureFromFileText(ioFile.getName(), fileText); |
| } |
| catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @NonNls |
| @NotNull |
| protected String getTestDataPath() { |
| if (myTestDataPath != null) { |
| return myTestDataPath; |
| } |
| return PathManagerEx.getTestDataPath(); |
| } |
| |
| protected VirtualFile getVirtualFile(@NonNls String filePath) { |
| String fullPath = getTestDataPath() + filePath; |
| |
| final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); |
| assertNotNull("file " + fullPath + " not found", vFile); |
| return vFile; |
| } |
| |
| /** |
| * Same as configureByFile but text is provided directly. |
| * @param fileName - name of the file. |
| * @param fileText - data file text. |
| * @throws java.io.IOException |
| */ |
| @NotNull |
| protected static Document configureFromFileText(@NonNls @NotNull final String fileName, @NonNls @NotNull final String fileText) { |
| return new WriteCommandAction<Document>(null) { |
| @Override |
| protected void run(@NotNull Result<Document> result) throws Throwable { |
| if (myVFile != null) { |
| // avoid messing with invalid files, in case someone calls configureXXX() several times |
| PsiDocumentManager.getInstance(ourProject).commitAllDocuments(); |
| FileEditorManager.getInstance(ourProject).closeFile(myVFile); |
| try { |
| myVFile.delete(this); |
| } |
| catch (IOException e) { |
| LOG.error(e); |
| } |
| myVFile = null; |
| } |
| final Document fakeDocument = new DocumentImpl(fileText); |
| |
| EditorTestUtil.CaretAndSelectionState caretsState = EditorTestUtil.extractCaretAndSelectionMarkers(fakeDocument); |
| |
| String newFileText = fakeDocument.getText(); |
| Document document; |
| try { |
| document = setupFileEditorAndDocument(fileName, newFileText); |
| } |
| catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| EditorTestUtil.setCaretsAndSelection(myEditor, caretsState); |
| setupEditorForInjectedLanguage(); |
| result.setResult(document); |
| } |
| }.execute().getResultObject(); |
| } |
| |
| protected static Editor createEditor(@NotNull VirtualFile file) { |
| Editor editor = FileEditorManager.getInstance(getProject()).openTextEditor(new OpenFileDescriptor(getProject(), file, 0), false); |
| ((EditorImpl)editor).setCaretActive(); |
| return editor; |
| } |
| |
| @NotNull |
| private static Document setupFileEditorAndDocument(@NotNull String fileName, @NotNull String fileText) throws IOException { |
| EncodingProjectManager.getInstance(getProject()).setEncoding(null, CharsetToolkit.UTF8_CHARSET); |
| EncodingProjectManager.getInstance(ProjectManager.getInstance().getDefaultProject()).setEncoding(null, CharsetToolkit.UTF8_CHARSET); |
| PostprocessReformattingAspect.getInstance(ourProject).doPostponedFormatting(); |
| deleteVFile(); |
| myVFile = getSourceRoot().createChildData(null, fileName); |
| VfsUtil.saveText(myVFile, fileText); |
| final FileDocumentManager manager = FileDocumentManager.getInstance(); |
| final Document document = manager.getDocument(myVFile); |
| assertNotNull("Can't create document for '" + fileName + "'", document); |
| manager.reloadFromDisk(document); |
| document.insertString(0, " "); |
| document.deleteString(0, 1); |
| myFile = getPsiManager().findFile(myVFile); |
| assertNotNull("Can't create PsiFile for '" + fileName + "'. Unknown file type most probably.", myFile); |
| assertTrue(myFile.isPhysical()); |
| myEditor = createEditor(myVFile); |
| myVFile.setCharset(CharsetToolkit.UTF8_CHARSET); |
| |
| PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); |
| return document; |
| } |
| |
| private static void setupEditorForInjectedLanguage() { |
| if (myEditor != null) { |
| final Ref<EditorWindow> editorWindowRef = new Ref<EditorWindow>(); |
| myEditor.getCaretModel().runForEachCaret(new CaretAction() { |
| @Override |
| public void perform(Caret caret) { |
| Editor editor = InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(myEditor, myFile); |
| if (caret == myEditor.getCaretModel().getPrimaryCaret() && editor instanceof EditorWindow) { |
| editorWindowRef.set((EditorWindow)editor); |
| } |
| } |
| }); |
| if (!editorWindowRef.isNull()) { |
| myEditor = editorWindowRef.get(); |
| myFile = editorWindowRef.get().getInjectedFile(); |
| } |
| } |
| } |
| |
| private static void deleteVFile() throws IOException { |
| if (myVFile != null) { |
| ApplicationManager.getApplication().runWriteAction(new ThrowableComputable<Void, IOException>() { |
| @Override |
| public Void compute() throws IOException { |
| myVFile.delete(this); |
| return null; |
| } |
| }); |
| } |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| FileEditorManager editorManager = FileEditorManager.getInstance(getProject()); |
| VirtualFile[] openFiles = editorManager.getOpenFiles(); |
| for (VirtualFile openFile : openFiles) { |
| editorManager.closeFile(openFile); |
| } |
| deleteVFile(); |
| myEditor = null; |
| myFile = null; |
| myVFile = null; |
| super.tearDown(); |
| } |
| |
| /** |
| * Validates that content of the editor as well as caret and selection matches one specified in data file that |
| * should be formed with the same format as one used in configureByFile |
| * @param filePath - relative path from %IDEA_INSTALLATION_HOME%/testData/ |
| */ |
| protected void checkResultByFile(@TestDataFile @NonNls @NotNull String filePath) { |
| checkResultByFile(null, filePath, false); |
| } |
| |
| /** |
| * Validates that content of the editor as well as caret and selection matches one specified in data file that |
| * should be formed with the same format as one used in configureByFile |
| * @param message - this check specific message. Added to text, caret position, selection checking. May be null |
| * @param filePath - relative path from %IDEA_INSTALLATION_HOME%/testData/ |
| * @param ignoreTrailingSpaces - whether trailing spaces in editor in data file should be stripped prior to comparing. |
| */ |
| protected void checkResultByFile(@Nullable String message, @TestDataFile @NotNull String filePath, final boolean ignoreTrailingSpaces) { |
| bringRealEditorBack(); |
| |
| getProject().getComponent(PostprocessReformattingAspect.class).doPostponedFormatting(); |
| if (ignoreTrailingSpaces) { |
| final Editor editor = myEditor; |
| TrailingSpacesStripper.stripIfNotCurrentLine(editor.getDocument(), false); |
| EditorUtil.fillVirtualSpaceUntilCaret(editor); |
| } |
| |
| PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); |
| |
| String fullPath = getTestDataPath() + filePath; |
| |
| File ioFile = new File(fullPath); |
| |
| assertTrue(getMessage("Cannot find file " + fullPath, message), ioFile.exists()); |
| String fileText = null; |
| try { |
| fileText = FileUtil.loadFile(ioFile, CharsetToolkit.UTF8); |
| } catch (IOException e) { |
| LOG.error(e); |
| } |
| checkResultByText(message, StringUtil.convertLineSeparators(fileText), ignoreTrailingSpaces, getTestDataPath() + "/" + filePath); |
| } |
| |
| /** |
| * Same as checkResultByFile but text is provided directly. |
| */ |
| protected void checkResultByText(@NonNls @NotNull String fileText) { |
| checkResultByText(null, fileText, false, null); |
| } |
| |
| /** |
| * Same as checkResultByFile but text is provided directly. |
| * @param message - this check specific message. Added to text, caret position, selection checking. May be null |
| * @param ignoreTrailingSpaces - whether trailing spaces in editor in data file should be stripped prior to comparing. |
| */ |
| protected void checkResultByText(final String message, @NotNull String fileText, final boolean ignoreTrailingSpaces) { |
| checkResultByText(message, fileText, ignoreTrailingSpaces, null); |
| } |
| |
| /** |
| * Same as checkResultByFile but text is provided directly. |
| * @param message - this check specific message. Added to text, caret position, selection checking. May be null |
| * @param ignoreTrailingSpaces - whether trailing spaces in editor in data file should be stripped prior to comparing. |
| */ |
| protected void checkResultByText(final String message, @NotNull final String fileText, final boolean ignoreTrailingSpaces, final String filePath) { |
| bringRealEditorBack(); |
| PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| final Document document = EditorFactory.getInstance().createDocument(fileText); |
| |
| if (ignoreTrailingSpaces) { |
| ((DocumentImpl)document).stripTrailingSpaces(getProject()); |
| } |
| |
| EditorTestUtil.CaretAndSelectionState carets = EditorTestUtil.extractCaretAndSelectionMarkers(document); |
| |
| PostprocessReformattingAspect.getInstance(getProject()).doPostponedFormatting(); |
| String newFileText = document.getText(); |
| |
| PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); |
| String fileText = myFile.getText(); |
| String failMessage = getMessage("Text mismatch", message); |
| if (filePath != null && !newFileText.equals(fileText)) { |
| throw new FileComparisonFailure(failMessage, newFileText, fileText, filePath); |
| } |
| assertEquals(failMessage, newFileText, fileText); |
| |
| EditorTestUtil.verifyCaretAndSelectionState(myEditor, carets, message); |
| } |
| }); |
| } |
| |
| private static String getMessage(@NonNls String engineMessage, String userMessage) { |
| if (userMessage == null) return engineMessage; |
| return userMessage + " [" + engineMessage + "]"; |
| } |
| |
| /** |
| * @return Editor used in test. |
| */ |
| protected static Editor getEditor() { |
| return myEditor; |
| } |
| |
| /** |
| * @return PsiFile opened in editor used in test |
| */ |
| protected static PsiFile getFile() { |
| return myFile; |
| } |
| |
| protected static VirtualFile getVFile() { |
| return myVFile; |
| } |
| |
| protected static void bringRealEditorBack() { |
| PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); |
| if (myEditor instanceof EditorWindow) { |
| Document document = ((DocumentWindow)myEditor.getDocument()).getDelegate(); |
| myFile = PsiDocumentManager.getInstance(getProject()).getPsiFile(document); |
| myEditor = ((EditorWindow)myEditor).getDelegate(); |
| myVFile = myFile.getVirtualFile(); |
| } |
| } |
| |
| protected void caretUp() { |
| EditorActionManager actionManager = EditorActionManager.getInstance(); |
| EditorActionHandler action = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP); |
| action.execute(getEditor(), DataManager.getInstance().getDataContext()); |
| } |
| protected void deleteLine() { |
| EditorActionManager actionManager = EditorActionManager.getInstance(); |
| EditorActionHandler action = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_DELETE_LINE); |
| action.execute(getEditor(), DataManager.getInstance().getDataContext()); |
| } |
| protected static void type(char c) { |
| EditorActionManager actionManager = EditorActionManager.getInstance(); |
| final DataContext dataContext = DataManager.getInstance().getDataContext(); |
| if (c == '\n') { |
| actionManager.getActionHandler(IdeActions.ACTION_EDITOR_ENTER).execute(getEditor(), dataContext); |
| } |
| else if (c == '\b') { |
| actionManager.getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE).execute(getEditor(), dataContext); |
| } |
| else { |
| // typed action is always executed in host editor context |
| actionManager.getTypedAction().actionPerformed(InjectedLanguageUtil.getTopLevelEditor(getEditor()), c, dataContext); |
| } |
| } |
| |
| protected static void type(@NonNls String s) { |
| for (char c : s.toCharArray()) { |
| type(c); |
| } |
| } |
| protected static void backspace() { |
| executeAction(IdeActions.ACTION_EDITOR_BACKSPACE); |
| } |
| protected static void delete() { |
| executeAction(IdeActions.ACTION_EDITOR_DELETE); |
| } |
| |
| protected static void home() { |
| executeAction(IdeActions.ACTION_EDITOR_MOVE_LINE_START); |
| } |
| |
| protected static void end() { |
| executeAction(IdeActions.ACTION_EDITOR_MOVE_LINE_END); |
| } |
| |
| protected static void copy() { |
| executeAction(IdeActions.ACTION_EDITOR_COPY); |
| } |
| |
| protected static void paste() { |
| executeAction(IdeActions.ACTION_EDITOR_PASTE); |
| } |
| |
| protected static void moveCaretToPreviousWordWithSelection() { |
| executeAction("EditorPreviousWordWithSelection"); |
| } |
| |
| protected static void moveCaretToNextWordWithSelection() { |
| executeAction("EditorNextWordWithSelection"); |
| } |
| |
| protected static void cutLineBackward() { |
| executeAction("EditorCutLineBackward"); |
| } |
| |
| protected static void cutToLineEnd() { |
| executeAction("EditorCutLineEnd"); |
| } |
| |
| protected static void deleteToLineStart() { |
| executeAction("EditorDeleteToLineStart"); |
| } |
| |
| protected static void deleteToLineEnd() { |
| executeAction("EditorDeleteToLineEnd"); |
| } |
| |
| protected static void killToWordStart() { |
| executeAction("EditorKillToWordStart"); |
| } |
| |
| protected static void killToWordEnd() { |
| executeAction("EditorKillToWordEnd"); |
| } |
| |
| protected static void killRegion() { |
| executeAction("EditorKillRegion"); |
| } |
| |
| protected static void killRingSave() { |
| executeAction("EditorKillRingSave"); |
| } |
| |
| protected static void unindent() { |
| executeAction("EditorUnindentSelection"); |
| } |
| |
| protected static void selectLine() { |
| executeAction("EditorSelectLine"); |
| } |
| |
| protected static void lineComment() { |
| new CommentByLineCommentAction().actionPerformedImpl(getProject(), getEditor()); |
| } |
| |
| protected static void executeAction(@NonNls @NotNull final String actionId) { |
| CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() { |
| @Override |
| public void run() { |
| EditorTestUtil.executeAction(getEditor(), actionId); |
| } |
| }, "", null); |
| } |
| |
| protected static DataContext getCurrentEditorDataContext() { |
| final DataContext defaultContext = DataManager.getInstance().getDataContext(); |
| return new DataContext() { |
| @Override |
| @Nullable |
| public Object getData(@NonNls String dataId) { |
| if (CommonDataKeys.EDITOR.is(dataId)) { |
| return getEditor(); |
| } |
| if (CommonDataKeys.PROJECT.is(dataId)) { |
| return getProject(); |
| } |
| if (CommonDataKeys.PSI_FILE.is(dataId)) { |
| return getFile(); |
| } |
| if (CommonDataKeys.PSI_ELEMENT.is(dataId)) { |
| PsiFile file = getFile(); |
| if (file == null) return null; |
| Editor editor = getEditor(); |
| if (editor == null) return null; |
| return file.findElementAt(editor.getCaretModel().getOffset()); |
| } |
| return defaultContext.getData(dataId); |
| } |
| }; |
| } |
| |
| /** |
| * file parameterized tests support |
| * @see FileBasedTestCaseHelperEx |
| */ |
| |
| /** |
| * @Parameterized.Parameter fields are injected on parameterized test creation. |
| */ |
| @Parameterized.Parameter(0) |
| public String myFileSuffix; |
| |
| /** |
| * path to the root of test data in case of com.intellij.testFramework.FileBasedTestCaseHelperEx |
| * or |
| * path to the directory with current test data in case of @TestDataPath |
| */ |
| @Parameterized.Parameter(1) |
| public String myTestDataPath; |
| |
| @Parameterized.Parameters(name = "{0}") |
| public static List<Object[]> params() throws Throwable { |
| return Collections.emptyList(); |
| } |
| |
| @com.intellij.testFramework.Parameterized.Parameters(name = "{0}") |
| public static List<Object[]> params(Class<?> klass) throws Throwable{ |
| final LightPlatformCodeInsightTestCase testCase = (LightPlatformCodeInsightTestCase)klass.newInstance(); |
| if (!(testCase instanceof FileBasedTestCaseHelper)) { |
| fail("Parameterized test should implement FileBasedTestCaseHelper"); |
| } |
| |
| try { |
| PathManagerEx.replaceLookupStrategy(klass, com.intellij.testFramework.Parameterized.class); |
| } |
| catch (IllegalArgumentException ignore) { |
| //allow to run out of idea project |
| } |
| |
| final FileBasedTestCaseHelper fileBasedTestCase = (FileBasedTestCaseHelper)testCase; |
| String testDataPath = testCase.getTestDataPath(); |
| |
| File testDir = null; |
| if (fileBasedTestCase instanceof FileBasedTestCaseHelperEx) { |
| testDir = new File(testDataPath, ((FileBasedTestCaseHelperEx)fileBasedTestCase).getRelativeBasePath()); |
| } else { |
| final TestDataPath annotation = klass.getAnnotation(TestDataPath.class); |
| if (annotation == null) { |
| fail("TestCase should implement com.intellij.testFramework.FileBasedTestCaseHelperEx or be annotated with com.intellij.testFramework.TestDataPath"); |
| } else { |
| final String trimmedRoot = StringUtil.trimStart(StringUtil.trimStart(annotation.value(), "$CONTENT_ROOT"), "$PROJECT_ROOT"); |
| final String lastPathComponent = new File(testDataPath).getName(); |
| final int idx = trimmedRoot.indexOf(lastPathComponent); |
| testDataPath = testDataPath.replace(File.separatorChar, '/') + (idx > 0 ? trimmedRoot.substring(idx + lastPathComponent.length()) : trimmedRoot); |
| testDir = new File(testDataPath); |
| } |
| } |
| |
| final File[] files = testDir.listFiles(); |
| |
| if (files == null) { |
| fail("Test files not found in " + testDir.getPath()); |
| } |
| |
| final List<Object[]> result = new ArrayList<Object[]>(); |
| for (File file : files) { |
| final String fileSuffix = fileBasedTestCase.getFileSuffix(file.getName()); |
| if (fileSuffix != null) { |
| result.add(new Object[] {fileSuffix, testDataPath}); |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public String getName() { |
| if (myFileSuffix != null) { |
| return "test" + myFileSuffix; |
| } |
| return super.getName(); |
| } |
| |
| @Before |
| public void before() throws Throwable { |
| final Throwable[] throwables = new Throwable[1]; |
| |
| invokeTestRunnable(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| setUp(); |
| } |
| catch (Throwable e) { |
| throwables[0] = e; |
| } |
| } |
| }); |
| |
| if (throwables[0] != null) { |
| throw throwables[0]; |
| } |
| } |
| |
| @After |
| public void after() throws Throwable { |
| final Throwable[] throwables = new Throwable[1]; |
| |
| invokeTestRunnable(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| tearDown(); |
| } |
| catch (Throwable e) { |
| throwables[0] = e; |
| } |
| } |
| }); |
| if (throwables[0] != null) { |
| throw throwables[0]; |
| } |
| } |
| |
| protected void runSingleTest(final Runnable testRunnable) throws Throwable { |
| final Throwable[] throwables = new Throwable[1]; |
| |
| Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| try { |
| testRunnable.run(); |
| } |
| catch (Throwable e) { |
| throwables[0] = e; |
| } |
| } |
| }; |
| |
| invokeTestRunnable(runnable); |
| |
| if (throwables[0] != null) { |
| throw throwables[0]; |
| } |
| |
| } |
| |
| } |