/******************************************************************************* * Copyright (c) 2009, 2010 Sven Kiera * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.phpsrc.eclipse.pti.tools.phpunit.core; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InvalidClassException; import java.io.InvalidObjectException; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.dltk.compiler.problem.IProblem; import org.eclipse.dltk.compiler.problem.ProblemSeverities; import org.eclipse.dltk.core.IField; import org.eclipse.dltk.core.IMethod; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.IType; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.search.SearchMatch; import org.eclipse.ui.progress.UIJob; import org.phpsrc.eclipse.pti.core.PHPToolkitUtil; import org.phpsrc.eclipse.pti.core.compiler.problem.FileProblem; import org.phpsrc.eclipse.pti.core.launching.OperatingSystem; import org.phpsrc.eclipse.pti.core.launching.PHPToolLauncher; import org.phpsrc.eclipse.pti.core.php.inifile.INIFileEntry; import org.phpsrc.eclipse.pti.core.php.inifile.INIFileUtil; import org.phpsrc.eclipse.pti.core.php.source.PHPClassSourceModifier; import org.phpsrc.eclipse.pti.core.php.source.PHPSourceFile; import org.phpsrc.eclipse.pti.core.search.PHPSearchEngine; import org.phpsrc.eclipse.pti.core.tools.AbstractPHPTool; import org.phpsrc.eclipse.pti.tools.phpunit.PHPUnitPlugin; import org.phpsrc.eclipse.pti.tools.phpunit.core.codecoverage.CloverCodeCoverageHandler; import org.phpsrc.eclipse.pti.tools.phpunit.core.model.PHPUnitModel; import org.phpsrc.eclipse.pti.tools.phpunit.core.model.TestRunSession; import org.phpsrc.eclipse.pti.tools.phpunit.core.preferences.PHPUnitPreferences; import org.phpsrc.eclipse.pti.tools.phpunit.core.preferences.PHPUnitPreferencesFactory; import org.phpsrc.eclipse.pti.ui.Logger; public class PHPUnit extends AbstractPHPTool { private interface IMethodFilter { public boolean accept(IMethod method); } public final static QualifiedName QUALIFIED_NAME = new QualifiedName( PHPUnitPlugin.PLUGIN_ID, "PHPUnit"); //$NON-NLS-1$ public final static String PHPUNIT_TEST_SUITE_CLASS = "PHPUnit_Framework_TestSuite"; //$NON-NLS-1$ public final static String PHPUNIT_TEST_CASE_CLASS = "PHPUnit_Framework_TestCase"; //$NON-NLS-1$ private final static String PHPUNIT_SKELETON_OPTION_CLASS = "skeleton-class"; //$NON-NLS-1$ private final static String PHPUNIT_SKELETON_OPTION_TEST = "skeleton-test"; //$NON-NLS-1$ private final static String PHPUNIT_SUMMARY_FILE = "phpunit.xml"; //$NON-NLS-1$ private final static Pattern PHPUNIT_TESTCASE_PATTERN = Pattern .compile("PHPUnit_.+_[A-Za-z]*TestCase"); private static PHPUnit instance; private IMethodFilter testMethodFilter; protected PHPUnit() { testMethodFilter = new IMethodFilter() { public boolean accept(IMethod method) { return method != null && method.getElementName().startsWith("test"); //$NON-NLS-1$ } }; } public static PHPUnit getInstance() { if (instance == null) instance = new PHPUnit(); return instance; } public void createPHPClassSkeleton(String className, IFile classFile, String testClassName, String testClassFilePath) throws InvalidObjectException, CoreException, InvalidClassException, PHPUnitException { createPHPClassSkeleton(className, classFile, testClassName, testClassFilePath, null); } public void createPHPClassSkeleton(String className, IFile classFile, String phpClassName, String classFilePath, String superClass) throws InvalidObjectException, CoreException, InvalidClassException, PHPUnitException { createSkeleton(className, classFile, phpClassName, classFilePath, superClass, PHPUNIT_SKELETON_OPTION_CLASS); } public void createTestSkeleton(String className, IFile classFile, String testClassName, String testClassFilePath) throws InvalidObjectException, CoreException, InvalidClassException, PHPUnitException { createTestSkeleton(className, classFile, testClassName, testClassFilePath, null); } public void createTestSkeleton(String className, IFile classFile, String testClassName, String testClassFilePath, String testSuperClass) throws InvalidObjectException, CoreException, InvalidClassException, PHPUnitException { if (testSuperClass == null || "".equals(testSuperClass)) testSuperClass = PHPUNIT_TEST_CASE_CLASS; createSkeleton(className, classFile, testClassName, testClassFilePath, testSuperClass, PHPUNIT_SKELETON_OPTION_TEST, testMethodFilter); } private void createSkeleton(String className, IFile classFile, String targetClassName, String classFilePath, String targetSuperClass, String skeletonOption) throws InvalidObjectException, CoreException, InvalidClassException, PHPUnitException { createSkeleton(className, classFile, targetClassName, classFilePath, targetSuperClass, skeletonOption, null); } private void createSkeleton(String className, IFile classFile, String targetClassName, String classFilePath, String targetSuperClass, String skeletonOption, IMethodFilter methodFilter) throws InvalidObjectException, CoreException, InvalidClassException, PHPUnitException { Path path = new Path(classFilePath); IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path); IProject project = file.getProject(); if (project == null) throw new InvalidObjectException("no project found"); IFolder folder = (IFolder) file.getParent(); createFolder(folder); String testClassLocation = file.getLocation().toOSString(); PHPClassSourceModifier modifier = null; if (file.exists()) { ISourceModule oldModule = PHPToolkitUtil.getSourceModule(file); IType oldClass = oldModule.getAllTypes()[0]; modifier = new PHPClassSourceModifier(oldModule, oldClass.getElementName()); } String cmdLineArgs = "--" + skeletonOption + " " + className; cmdLineArgs += " " + OperatingSystem.escapeShellFileArg(classFile.getLocation() .toOSString()); cmdLineArgs += " " + targetClassName; cmdLineArgs += " " + OperatingSystem.escapeShellFileArg(testClassLocation); PHPToolLauncher launcher = getProjectPHPToolLauncher(project, cmdLineArgs, classFile.getParent().getLocation()); String output = launcher.launch(project); boolean ok = (output.indexOf("Wrote skeleton for ") >= 0 ? true : false); if (ok) { folder.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); ISourceModule newModule = PHPToolkitUtil.getSourceModule(file); newModule.reconcile(false, null, new NullProgressMonitor()); IType newClass = newModule.getAllTypes()[0]; String newTestCaseSource = null; if (modifier != null) { for (IMethod method : newClass.getMethods()) { if (methodFilter == null || methodFilter.accept(method)) { modifier.addMethod(method); } } newTestCaseSource = modifier.getSource(); } else { String[] superClasses = newClass.getSuperClasses(); if (superClasses != null && superClasses.length > 0 && !superClasses[0].equals(targetSuperClass)) { modifier = new PHPClassSourceModifier(newModule, newClass.getElementName()); modifier.setSuperClass(targetSuperClass); newTestCaseSource = modifier.getSource(); } } if (newTestCaseSource != null) { try { FileWriter writer = new FileWriter(file.getLocation() .toOSString()); writer.write(newTestCaseSource); writer.close(); file.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); } catch (IOException e) { } } } else { int pos = output.indexOf("Sebastian Bergmann."); throw new PHPUnitException(output.substring(pos + 20).trim()); } } public IProblem[] runTestCase(final IFile testFile) { return runTestCase(testFile, false); } public IProblem[] runTestCase(final IFile testFile, boolean forceCodeCoverage) { try { PHPUnitPreferences prefs = PHPUnitPreferencesFactory .factory(testFile); ISourceModule module = PHPToolkitUtil.getSourceModule(testFile); IType[] types = module.getAllTypes(); for (IType type : types) { String cmdLineArgs = "--log-json php://stdout"; File coverageFile = null; if (forceCodeCoverage || prefs.generateCodeCoverage()) { coverageFile = new File( PHPUnitPlugin.getCodeCoverageDirectory(), testFile.getName() + ".xml"); cmdLineArgs += " --coverage-clover " + OperatingSystem.escapeShellFileArg(coverageFile .toString()); } cmdLineArgs += " " + PHPToolkitUtil.getClassNameWithNamespace(type .getSourceModule()); cmdLineArgs += " " + OperatingSystem.escapeShellFileArg(testFile .getLocation().toOSString()); PHPToolLauncher launcher = getProjectPHPToolLauncher( testFile.getProject(), cmdLineArgs, testFile .getParent().getLocation()); TestRunSession session = new TestRunSession(launcher, "TestRunTest", testFile); addTestRunSession(session); String output = launcher.launch(testFile.getProject()); IProblem[] problems = parseOutput(testFile.getProject(), output); if (coverageFile != null && coverageFile.exists()) { CloverCodeCoverageHandler.importXml(coverageFile); coverageFile.delete(); } return problems; } } catch (ModelException e) { Logger.logException(e); } return new IProblem[0]; } private File createTempSummaryFile(String fileName) throws IOException { File tempDir = createTempDir("pti_phpunit"); //$NON-NLS-1$ return createTempFile(tempDir, fileName); } private void addTestRunSession(final TestRunSession session) { UIJob job = new UIJob("Update Test Runner") { public IStatus runInUIThread(IProgressMonitor monitor) { PHPUnitPlugin.getModel().addTestRunSession(session); notifyResultListener(session); return Status.OK_STATUS; } }; job.schedule(); } private void importTestRunSession(final File summaryFile) { UIJob job = new UIJob("Update Test Runner") { public IStatus runInUIThread(IProgressMonitor monitor) { if (summaryFile.exists() && summaryFile.length() > 0) { try { TestRunSession session = PHPUnitModel .importTestRunSession(summaryFile); notifyResultListener(session); } catch (CoreException e) { Logger.logException(e); } } return Status.OK_STATUS; } }; job.schedule(); } public IProblem[] runAllTestsInFolder(IFolder folder) { return runAllTestsInFolder(folder, false); } public IProblem[] runAllTestsInFolder(IFolder folder, boolean forceCodeCoverage) { PHPUnitPreferences prefs = PHPUnitPreferencesFactory.factory(folder); String cmdLineArgs = OperatingSystem.escapeShellFileArg(folder .getLocation().toOSString()); File coverageFile = null; if (forceCodeCoverage || prefs.generateCodeCoverage()) { coverageFile = new File(PHPUnitPlugin.getCodeCoverageDirectory(), folder.getName() + ".xml"); cmdLineArgs = " --coverage-clover " + OperatingSystem.escapeShellFileArg(coverageFile .toString()) + " " + cmdLineArgs; } cmdLineArgs = "--log-json php://stdout " + cmdLineArgs; PHPToolLauncher launcher = getProjectPHPToolLauncher( folder.getProject(), cmdLineArgs, folder.getLocation()); TestRunSession session = new TestRunSession(launcher, "TestRunTest", folder.getProject()); addTestRunSession(session); IProblem[] problems = parseOutput(folder.getProject(), launcher.launch(folder.getProject())); if (coverageFile != null && coverageFile.exists()) { CloverCodeCoverageHandler.importXml(coverageFile); coverageFile.delete(); } return problems; } public IProblem[] runTestSuite(IFile file) { return runTestSuite(file, false); } public IProblem[] runTestSuite(IFile file, boolean forceCodeCoverage) { PHPUnitPreferences prefs = PHPUnitPreferencesFactory.factory(file); String cmdLineArgs = OperatingSystem.escapeShellFileArg(file .getLocation().toOSString()); cmdLineArgs = "--log-json php://stdout " + cmdLineArgs; File coverageFile = null; if (forceCodeCoverage || prefs.generateCodeCoverage()) { coverageFile = new File(PHPUnitPlugin.getCodeCoverageDirectory(), file.getName() + ".xml"); cmdLineArgs = " --coverage-clover " + OperatingSystem.escapeShellFileArg(coverageFile .toString()) + " " + cmdLineArgs; } PHPToolLauncher launcher = getProjectPHPToolLauncher(file.getProject(), cmdLineArgs, file.getLocation()); TestRunSession session = new TestRunSession(launcher, "TestRunTest", file); addTestRunSession(session); IProblem[] problems = parseOutput(file.getProject(), launcher.launch(file.getProject())); if (coverageFile != null && coverageFile.exists()) { CloverCodeCoverageHandler.importXml(coverageFile); coverageFile.delete(); } return problems; } protected IProblem[] parseOutput(IProject project, String output) { ArrayList<IProblem> problems = new ArrayList<IProblem>(); String projectLocation = project.getLocation().toOSString(); if (output != null && output.length() > 0) { Pattern pFailed = Pattern.compile("[0-9]+\\) .*"); Pattern pFileAndLine = Pattern.compile(".*:[0-9]+"); String[] lines = output.split("\n"); Matcher m = null; for (int i = 0; i < lines.length; ++i) { m = pFailed.matcher(lines[i].trim()); if (m.matches()) { ++i; String msg = lines[i].trim(); ++i; while (i < lines.length && !"".equals(lines[i].trim())) { msg += "\n" + lines[i].trim(); ++i; } Matcher mf = null; while (i < lines.length && (mf == null || !mf.matches())) { mf = pFileAndLine.matcher(lines[i].trim()); if (!mf.matches()) ++i; } String lineFailureLocation = null; for (int x = 1; x >= 0; --x) { if (lines.length > i + x && lines[i + x].lastIndexOf(':') != -1) { lineFailureLocation = lines[i + x]; break; } } if (lineFailureLocation != null) { String file = lineFailureLocation.substring(0, lineFailureLocation.lastIndexOf(":")); IResource testFile = project.findMember(file .substring(projectLocation.length())); if (testFile != null) { PHPSourceFile sourceFile; try { sourceFile = new PHPSourceFile((IFile) testFile); int lineNumber = Integer .parseInt(lineFailureLocation .substring(lineFailureLocation .lastIndexOf(":") + 1)); problems.add(new FileProblem((IFile) testFile, msg, IProblem.Task, new String[0], ProblemSeverities.Error, sourceFile .lineStart(lineNumber), sourceFile.lineEnd(lineNumber), lineNumber)); ++i; } catch (CoreException e) { Logger.logException(e); } catch (IOException e) { Logger.logException(e); } } } } } } return problems.toArray(new IProblem[0]); } private void createFolder(IFolder folder) throws CoreException { IContainer parent = folder.getParent(); if (parent instanceof IFolder) { createFolder((IFolder) parent); } if (!folder.exists()) { folder.create(true, true, new NullProgressMonitor()); } } private PHPToolLauncher getProjectPHPToolLauncher(IProject project, String cmdLineArgs, IPath fileIncludePath) { PHPUnitPreferences prefs = PHPUnitPreferencesFactory.factory(project); String bootstrap = prefs.getBootstrap(); if (bootstrap != null && bootstrap.length() > 0) { IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); if (root != null) { IResource resource = root.findMember(bootstrap); if (resource != null && resource.exists() && resource.getLocation() != null) { cmdLineArgs = "--bootstrap " + OperatingSystem.escapeShellFileArg(resource .getLocation().toOSString()) + " " + cmdLineArgs; } } } PHPToolLauncher launcher = new PHPToolLauncher(QUALIFIED_NAME, getPHPExecutable(prefs.getPhpExecutable()), getScriptFile(), cmdLineArgs, getPHPINIEntries(project, fileIncludePath)); launcher.setPrintOuput(prefs.isPrintOutput()); return launcher; } private INIFileEntry[] getPHPINIEntries(IProject project) { IPath[] includePaths = PHPUnitPlugin.getDefault() .getPluginIncludePaths(project); return getPHPINIEntries(includePaths); } private INIFileEntry[] getPHPINIEntries(IProject project, IPath fileIncludePath) { IPath[] pluginIncludePaths = PHPUnitPlugin.getDefault() .getPluginIncludePaths(project); IPath[] includePaths = new IPath[pluginIncludePaths.length + 1]; System.arraycopy(pluginIncludePaths, 0, includePaths, 0, pluginIncludePaths.length); includePaths[includePaths.length - 1] = fileIncludePath; return getPHPINIEntries(includePaths); } private INIFileEntry[] getPHPINIEntries(IPath[] includePaths) { INIFileEntry[] entries; if (includePaths.length > 0) { entries = new INIFileEntry[] { INIFileUtil .createIncludePathEntry(includePaths) }; } else { entries = new INIFileEntry[0]; } return entries; } public static IPath getScriptFile() { return PHPUnitPlugin.getDefault().resolvePluginResource( "/php/tools/phpunit.php"); } static public IFile searchTestCase(IFile file) { if (isTestCase(file)) return file; PHPUnitPreferences prefs = PHPUnitPreferencesFactory.factory(file); ISourceModule module = PHPToolkitUtil.getSourceModule(file); if (module != null) { String className = PHPToolkitUtil.getClassName(module); String namespace = PHPToolkitUtil.getNamespace(module); if (className != null) { return searchClass(namespace, className + "Test", file.getProject(), prefs.noNamespaceCheck()); } } return null; } static public IFile searchTestElement(IFile testCase) { if (!isTestCase(testCase)) return testCase; PHPUnitPreferences prefs = PHPUnitPreferencesFactory.factory(testCase); ISourceModule module = PHPToolkitUtil.getSourceModule(testCase); if (module != null) { String className = PHPToolkitUtil.getClassName(module); String namespace = PHPToolkitUtil.getNamespace(module); if (className != null) { return searchClass(namespace, className.substring(0, className.length() - 4), testCase.getProject(), prefs.noNamespaceCheck()); } } return null; } static private IFile searchClass(String namespace, String className, IProject project, boolean noNamespaceCheck) { SearchMatch[] matches = PHPSearchEngine.findClass(className, PHPSearchEngine.createProjectScope(project)); for (SearchMatch match : matches) { if (noNamespaceCheck) { return (IFile) match.getResource(); } else { String matchNamespace = PHPToolkitUtil .getNamespace(((IType) match.getElement()) .getSourceModule()); if (namespace == null && matchNamespace == null) { return (IFile) match.getResource(); } else if (namespace != null && namespace.equals(matchNamespace)) { return (IFile) match.getResource(); } } } return null; } static public boolean isTestCase(IFile file) { return PHPToolkitUtil.hasSuperClass(file, PHPUNIT_TESTCASE_PATTERN); } static public boolean isTestSuite(IFile file) { ISourceModule module = PHPToolkitUtil.getSourceModule(file); if (PHPToolkitUtil.hasSuperClass(module, PHPUNIT_TEST_SUITE_CLASS)) return true; try { IMethod method = PHPToolkitUtil.getClassMethod(module, "suite"); if (method != null) { if (method.getSource().contains(PHPUNIT_TEST_SUITE_CLASS)) { return true; } Pattern p = Pattern.compile(".*new ([a-zA-Z0-9_]+).*"); IModelElement[] elements = method.getChildren(); for (IModelElement e : elements) { if (e instanceof IField) { IField f = (IField) e; Matcher m = p.matcher(f.getSource()); if (m.matches()) { SearchMatch[] classes = PHPSearchEngine.findClass(m .group(1), PHPSearchEngine .createProjectScope(file.getProject())); for (SearchMatch c : classes) { if (PHPToolkitUtil.hasSuperClass( c.getResource(), PHPUNIT_TEST_SUITE_CLASS)) return true; } } } } } } catch (ModelException e) { } return false; } }