/******************************************************************************* * Copyright (c) 2006, 2016 Wind River Systems, Inc. and others. * 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 * * Contributors: * Markus Schorn - initial API and implementation * Andrew Ferguson (Symbian) * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.core.testplugin.util; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.dom.ILinkage; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.index.IIndex; import org.eclipse.cdt.core.index.IIndexFile; import org.eclipse.cdt.core.index.IIndexFileLocation; import org.eclipse.cdt.core.index.IndexLocationFactory; import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.core.testplugin.CTestPlugin; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.FileLocator; 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.Status; import org.junit.Assert; import org.osgi.framework.Bundle; /** * Utilities for reading test source code from plug-in .java sources */ public class TestSourceReader { private final Bundle bundle; private final String srcRoot; private final Class clazz; private final int numSections; /** * @param bundle the bundle containing the source, if {@code null} can try to load using * classpath (source folder has to be in the classpath for this to work) * @param srcRoot the directory inside the bundle containing the packages * @param clazz the name of the class containing the test */ public TestSourceReader(Bundle bundle, String srcRoot, Class clazz) { this(bundle, srcRoot, clazz, 0); } /** * @param bundle the bundle containing the source, if {@code null} can try to load using * classpath (source folder has to be in the classpath for this to work) * @param srcRoot the directory inside the bundle containing the packages * @param clazz the name of the class containing the test * @param numSections the number of comment sections preceding the named test to return. * Pass zero to get all available sections. */ public TestSourceReader(Bundle bundle, String srcRoot, Class clazz, int numSections) { this.bundle = bundle; this.srcRoot = srcRoot; this.clazz = clazz; this.numSections = numSections; } public StringBuilder[] getContentsForTest(final String testName) throws IOException { return getContentsForTest(bundle, srcRoot, clazz, testName, numSections); } public String readTaggedComment(String tag) throws IOException { return readTaggedComment(bundle, tag, clazz, tag); } /** * Returns an array of StringBuilder objects for each comment section found preceding the named * test in the source code. * * @param bundle the bundle containing the source, if {@code null} can try to load using * classpath (source folder has to be in the classpath for this to work) * @param srcRoot the directory inside the bundle containing the packages * @param clazz the name of the class containing the test * @param testName the name of the test * @param numSections the number of comment sections preceding the named test to return. * Pass zero to get all available sections. * @return an array of StringBuilder objects for each comment section found preceding the named * test in the source code. * @throws IOException */ public static StringBuilder[] getContentsForTest(Bundle bundle, String srcRoot, Class clazz, final String testName, int numSections) throws IOException { // Walk up the class inheritance chain until we find the test method. try { while (clazz.getMethod(testName).getDeclaringClass() != clazz) { clazz = clazz.getSuperclass(); } } catch (SecurityException e) { Assert.fail(e.getMessage()); } catch (NoSuchMethodException e) { Assert.fail(e.getMessage()); } while (true) { // Find and open the .java file for the class clazz. String fqn = clazz.getName().replace('.', '/'); fqn = fqn.indexOf("$") == -1 ? fqn : fqn.substring(0, fqn.indexOf("$")); String classFile = fqn + ".java"; IPath filePath= new Path(srcRoot + '/' + classFile); InputStream in; Class superclass = clazz.getSuperclass(); try { if (bundle != null) { in = FileLocator.openStream(bundle, filePath, false); } else { in = clazz.getResourceAsStream('/' + classFile); } } catch (IOException e) { if (superclass == null || !superclass.getPackage().equals(clazz.getPackage())) { throw e; } clazz = superclass; continue; } BufferedReader br = new BufferedReader(new InputStreamReader(in)); try { // Read the java file collecting comments until we encounter the test method. List<StringBuilder> contents = new ArrayList<StringBuilder>(); StringBuilder content = new StringBuilder(); for (String line = br.readLine(); line != null; line = br.readLine()) { line = line.replaceFirst("^\\s*", ""); // Replace leading whitespace, preserve trailing if (line.startsWith("//")) { content.append(line.substring(2)).append('\n'); } else { if (!line.startsWith("@") && content.length() > 0) { contents.add(content); if (numSections > 0 && contents.size() == numSections + 1) contents.remove(0); content = new StringBuilder(); } if (line.length() > 0 && !contents.isEmpty()) { int idx= line.indexOf(testName); if (idx != -1 && !Character.isJavaIdentifierPart(line.charAt(idx + testName.length()))) { return contents.toArray(new StringBuilder[contents.size()]); } if (!line.startsWith("@")) { contents.clear(); } } } } } finally { br.close(); } if (superclass == null || !superclass.getPackage().equals(clazz.getPackage())) { throw new IOException("Test data not found for " + clazz.getName() + "." + testName); } clazz = superclass; } } /** * Searches for the offset of the first occurrence of a string in a workspace file. * @param lookfor string to be searched for * @param fullPath full path of the workspace file * @return the offset or -1 * @throws Exception * @throws UnsupportedEncodingException * @since 4.0 */ public static int indexOfInFile(String lookfor, Path fullPath) throws Exception { IFile file= ResourcesPlugin.getWorkspace().getRoot().getFile(fullPath); Reader reader= new BufferedReader(new InputStreamReader(file.getContents(), file.getCharset())); Assert.assertTrue(lookfor.indexOf('\n') == -1); try { int c= 0; int offset= 0; StringBuilder buf= new StringBuilder(); while ((c = reader.read()) >= 0) { buf.append((char) c); if (c == '\n') { int idx= buf.indexOf(lookfor); if (idx >= 0) { return idx + offset; } offset += buf.length(); buf.setLength(0); } } int idx= buf.indexOf(lookfor); if (idx >= 0) { return idx + offset; } return -1; } finally { reader.close(); } } public static int getLineNumber(int offset, Path fullPath) throws Exception { IFile file= ResourcesPlugin.getWorkspace().getRoot().getFile(fullPath); Reader reader= new BufferedReader(new InputStreamReader(file.getContents(), file.getCharset())); try { int line = 1; for (int i = 0; i < offset; i++) { int c= reader.read(); Assert.assertTrue(c >= 0); if (c == '\n') line++; } return line; } finally { reader.close(); } } /** * Reads a section in comments form the source of the given class. The section * is started with '// {tag}' and ends with the first line not started by '//' * @since 4.0 */ public static String readTaggedComment(Bundle bundle, String srcRoot, Class clazz, final String tag) throws IOException { IPath filePath= new Path(srcRoot + '/' + clazz.getName().replace('.', '/') + ".java"); InputStream in= FileLocator.openStream(bundle, filePath, false); LineNumberReader reader= new LineNumberReader(new InputStreamReader(in)); boolean found= false; final StringBuilder content= new StringBuilder(); try { String line= reader.readLine(); while (line != null) { line= line.trim(); if (line.startsWith("//")) { line= line.substring(2); if (found) { content.append(line); content.append('\n'); } else { line= line.trim(); if (line.startsWith("{" + tag)) { if (line.length() == tag.length() + 1 || !Character.isJavaIdentifierPart(line.charAt(tag.length() + 1))) { found= true; } } } } else if (found) { break; } line= reader.readLine(); } } finally { reader.close(); } Assert.assertTrue("Tag '" + tag + "' is not defined inside of '" + filePath + "'.", found); return content.toString(); } /** * Creates a file with content at the given path inside the given container. * If the file exists its content is replaced. * @param container a container to create the file in * @param filePath the path relative to the container to create the file at * @param contents the content for the file * @return a file object. * @throws CoreException * @since 4.0 */ public static IFile createFile(final IContainer container, final IPath filePath, final CharSequence contents) throws CoreException { final IWorkspace ws = ResourcesPlugin.getWorkspace(); final IFile result[] = new IFile[1]; ws.run(new IWorkspaceRunnable() { @Override public void run(IProgressMonitor monitor) throws CoreException { // Obtain file handle IFile file = container.getFile(filePath); InputStream stream; try { stream = new ByteArrayInputStream(contents.toString().getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { throw new CoreException(new Status(IStatus.ERROR, CTestPlugin.PLUGIN_ID, null, e)); } // Create file input stream if (file.exists()) { long timestamp= file.getLocalTimeStamp(); file.setContents(stream, false, false, new NullProgressMonitor()); if (file.getLocalTimeStamp() == timestamp) { file.setLocalTimeStamp(timestamp + 1000); } } else { createFolders(file); file.create(stream, true, new NullProgressMonitor()); } result[0]= file; } private void createFolders(IResource res) throws CoreException { IContainer container= res.getParent(); if (!container.exists() && container instanceof IFolder) { createFolders(container); ((IFolder) container).create(true, true, new NullProgressMonitor()); } } }, null); return result[0]; } /** * Creates a file with content at the given path inside the given container. * If the file exists its content is replaced. * @param container a container to create the file in * @param filePath the path relative to the container to create the file at * @param contents the content for the file * @return a file object. * @since 4.0 */ public static IFile createFile(IContainer container, String filePath, String contents) throws CoreException { return createFile(container, new Path(filePath), contents); } /** * Waits until the given file is indexed. Fails if this does not happen within the * given time. * @param file * @param maxmillis * @throws Exception * @since 4.0 */ public static void waitUntilFileIsIndexed(IIndex index, IFile file, int maxmillis) throws Exception { long fileTimestamp = file.getLocalTimeStamp(); IIndexFileLocation indexFileLocation = IndexLocationFactory.getWorkspaceIFL(file); long endTime= System.currentTimeMillis() + maxmillis; int timeLeft= maxmillis; while (timeLeft >= 0) { Assert.assertTrue(CCorePlugin.getIndexManager().joinIndexer(timeLeft, new NullProgressMonitor())); index.acquireReadLock(); try { IIndexFile[] files= index.getFiles(ILinkage.CPP_LINKAGE_ID, indexFileLocation); if (files.length > 0 && areAllFilesNotOlderThan(files, fileTimestamp)) { Assert.assertTrue(CCorePlugin.getIndexManager().joinIndexer(timeLeft, new NullProgressMonitor())); return; } files= index.getFiles(ILinkage.C_LINKAGE_ID, indexFileLocation); if (files.length > 0 && areAllFilesNotOlderThan(files, fileTimestamp)) { Assert.assertTrue(CCorePlugin.getIndexManager().joinIndexer(timeLeft, new NullProgressMonitor())); return; } } finally { index.releaseReadLock(); } Thread.sleep(50); timeLeft= (int) (endTime - System.currentTimeMillis()); } Assert.fail("Indexing of " + file.getFullPath() + " did not complete in " + maxmillis / 1000. + " sec"); } private static boolean areAllFilesNotOlderThan(IIndexFile[] files, long timestamp) throws CoreException { for (IIndexFile file : files) { if (file.getTimestamp() < timestamp) { return false; } } return true; } public static IASTTranslationUnit createIndexBasedAST(IIndex index, ICProject project, IFile file) throws CModelException, CoreException { ICElement elem= project.findElement(file.getFullPath()); if (elem instanceof ITranslationUnit) { ITranslationUnit tu= (ITranslationUnit) elem; return tu.getAST(index, ITranslationUnit.AST_SKIP_INDEXED_HEADERS); } Assert.fail("Could not create AST for " + file.getFullPath()); return null; } }