/******************************************************************************* * Copyright (c) 2008 Mountainminds GmbH & Co. KG * This software is provided under the terms of the Eclipse Public License v1.0 * See http://www.eclipse.org/legal/epl-v10.html. * * $Id: EMMAAnalyzer.java 1593 2010-10-18 17:48:01Z dcarver $ ******************************************************************************/ package org.eclemma.runtime.equinox.internal; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import org.eclemma.runtime.equinox.ICoverageAnalyzer; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import com.vladium.emma.EMMAProperties; import com.vladium.emma.data.CoverageOptions; import com.vladium.emma.data.CoverageOptionsFactory; import com.vladium.emma.data.DataFactory; import com.vladium.emma.data.ICoverageData; import com.vladium.emma.data.IMetaData; import com.vladium.emma.data.SessionData; import com.vladium.emma.filter.IInclExclFilter; import com.vladium.emma.instr.InstrVisitor; import com.vladium.emma.rt.RT; import com.vladium.emma.rt.RTSettings; import com.vladium.jcd.cls.ClassDef; import com.vladium.jcd.compiler.ClassWriter; import com.vladium.jcd.parser.ClassDefParser; /** * This EMMA based coverage analyzer dumps a file in system property * emma.session.out.file or if that is not defined it will create a coverage.es * file in user.dir. * * @author Marc R. Hoffmann, Mikkel T Andersen */ public class EMMAAnalyzer implements ICoverageAnalyzer { private static final String PREFIX = "emma."; private static final String SESSION_OUT_FILE = PREFIX + EMMAProperties.PROPERTY_SESSION_DATA_OUT_FILE; private static final String SESSION_OUT_MERGE = PREFIX + EMMAProperties.PROPERTY_SESSION_DATA_OUT_MERGE; private static final String INCL_EXCL_FILTER = PREFIX + "filter"; private static final String SHOW_HELP = "eclemma.help"; private static final String INSTRUMENT_BUNDLES = "eclemma.instrument.bundles"; private BundleContext bundleContext; private CoverageOptions options; private IMetaData metadata; private boolean started; private List bundlesToInstrument; private IInclExclFilter filter; public void start(BundleContext bundleContext) { this.bundleContext = bundleContext; System.out.println("Running Equinox with emma code coverage. (Add -D" + SHOW_HELP + " for more information)"); printHelpOptions(); RTSettings.setStandaloneMode(false); RT.reset(true, false); options = CoverageOptionsFactory.create(System.getProperties()); filter = IInclExclFilter.Factory.create(PropertyUtils.toArray(System .getProperty(INCL_EXCL_FILTER))); metadata = DataFactory.newMetaData(options); final String instrumentBundles = System.getProperty(INSTRUMENT_BUNDLES); bundlesToInstrument = PropertyUtils.toList(instrumentBundles); System.out .println("Covering the bundles with symbolic name(s): " + (instrumentBundles != null ? instrumentBundles : " no bundles specified (for example: -D" + INSTRUMENT_BUNDLES + "=org.eclipse.swt). So instrumenting all then")); started = true; } public void stop() { collectMetaData(); started = false; final ICoverageData coveragedata = RT.getCoverageData(); try { writeSessionData(metadata, coveragedata); } catch (IOException e) { System.err.println("Error while writing the session file"); e.printStackTrace(); } } public byte[] instrument(final String bundleid, final String classname, final byte[] bytes) { if (started) { try { final ClassDef classdef = ClassDefParser.parseClass(bytes); final InstrVisitor.InstrResult result = process(classdef, true); if (result.m_instrumented) { ByteArrayOutputStream out = new ByteArrayOutputStream( bytes.length * 2); ClassWriter.writeClassTable(classdef, out); return out.toByteArray(); } } catch (Exception ex) { System.err.println("Error while instrumenting " + classname + " in bundle " + bundleid); ex.printStackTrace(); } } return null; } public String getRuntimePackages() { return "com.vladium.emma.rt"; } public boolean shouldInstrumentClassesInBundle(String symbolicName) { return getBundlesToInstrument().contains(symbolicName) || getBundlesToInstrument().isEmpty(); } /** * Collect Meta data of all classes that have not yet been loaded. */ private void collectMetaData() { final Bundle[] allBundles = bundleContext.getBundles(); List instrumentBundles = new ArrayList(getBundlesToInstrument()); for (int i = 0; i < allBundles.length; i++) { final Bundle bundle = allBundles[i]; if (shouldInstrumentClassesInBundle(bundle.getSymbolicName())) { instrumentBundles.remove(bundle.getSymbolicName()); collectMetaData(bundle); } } if (!instrumentBundles.isEmpty()) { System.err .println("Could not instrument all bundles as they were not in the bundle context: " + PropertyUtils.listToString(instrumentBundles)); throw new RuntimeException( "Could not instrument all bundles as they were not in the bundle context: " + PropertyUtils.listToString(instrumentBundles)); } } /** * Processes all Java *.class files contained in the given bundle and * extracts coverage Meta data information. For accurate statistics this is * required to obtain information about classes that have not been loaded at * all. * * TODO: Respect bundle class path and also process included JARs. * * @param bundle * bundle to collect coverage Meta data for */ private void collectMetaData(final Bundle bundle) { System.out.println("Collecting coverage Meta data for bundle " + bundle.getSymbolicName()); final Enumeration entries = bundle.findEntries("/", "*.class", true); while (entries != null && entries.hasMoreElements()) { final URL url = (URL) entries.nextElement(); try { final ClassDef classdef = ClassDefParser.parseClass(url .openStream()); if (!metadata.hasDescriptor(classdef.getName())) { process(classdef, false); } } catch (final IOException e) { System.err.println("Error while opening resource " + url + " in bundle " + bundle.getSymbolicName()); e.printStackTrace(); } } } /** * Applies EMMA's instrumentation process to the given class definition. * * @param classdef * class definition * @param instrument * flag whether the class should actually become instrumented, * otherwise only Meta data is collected * @return instrumentation result */ private InstrVisitor.InstrResult process(final ClassDef classdef, final boolean instrument) { final InstrVisitor.InstrResult result = new InstrVisitor.InstrResult(); if ((filter == null) || filter.included(classdef.getName().replace('/', '.'))) { final InstrVisitor visitor = new InstrVisitor(options); visitor.process(classdef, false, instrument, true, result); if (result.m_descriptor != null) { metadata.add(result.m_descriptor, true); } } return result; } /** * Writing the combined metadata and coveragedata into a session file. * * @param metadata * metadata for the session * @param coveragedata * coveragedata for the session * @throws IOException * if it could not persist it will throw an IOException. */ private void writeSessionData(IMetaData metadata, ICoverageData coveragedata) throws IOException { String fileName = System.getProperty(SESSION_OUT_FILE, EMMAProperties.DEFAULT_SESSION_DATA_OUT_FILE); System.out.println("Saving session data to: " + fileName); File file = new File(fileName); new File(file.getParent()).mkdirs(); DataFactory.persist(new SessionData(metadata, coveragedata), file, shouldMerge()); } /** * Checks to see if it should merge. Reads the System property * emma.session.out.merge. If it is not set the default value is true, like * in the emma documentation. * * @see {@link http://emma.sourceforge.net/reference/ch03.html} * * @return */ private boolean shouldMerge() { return new Boolean(System.getProperty(SESSION_OUT_MERGE, "true")) .booleanValue(); } /** * @return a list of symbolic names for bundles it needs to instrument. */ private List getBundlesToInstrument() { return bundlesToInstrument; } private void printHelpOptions() { if (System.getProperty(SHOW_HELP) != null) { System.out .println("---------------------------------------------------------------------------------------------------------------------------------------"); System.out.println("Options: " + INSTRUMENT_BUNDLES + ", " + SESSION_OUT_FILE + ", " + SESSION_OUT_MERGE + ", " + INCL_EXCL_FILTER); printOption( INSTRUMENT_BUNDLES, "list all symbolic names of bundles you want coverered, separated with , (comma) (if " + INSTRUMENT_BUNDLES + " is not specified all bundles will be instrumented)"); printOption(SESSION_OUT_FILE, "the file to put the output of the session in (c:\\myCoverage\\coverage.es)"); printOption( SESSION_OUT_MERGE, "true if it should merge and false if it should not merge with existing session.out.file (default is true)"); printOption( INCL_EXCL_FILTER, "filter when covering files, see emma documentation for details. (remember - for exclude and + for include)"); System.out.println(" Example 1: -D" + INSTRUMENT_BUNDLES + "=org.eclipse.swt -D" + SESSION_OUT_MERGE + "=false"); System.out.println(" Example 2: -D" + INSTRUMENT_BUNDLES + "=org.eclipse.swt,org.eclipse.jface -D" + SESSION_OUT_FILE + "=c:/swt-jface-coverage.es"); System.out.println(" Example 2: -D" + INSTRUMENT_BUNDLES + "=org.eclipse.swt,org.eclipse.jface -D" + SESSION_OUT_FILE + "=c:/swt-jface-coverage.es -D+" + INCL_EXCL_FILTER + "=-*Test*"); System.out .println("---------------------------------------------------------------------------------------------------------------------------------------"); } } private void printOption(String property, String description) { System.out.println(" - " + property + ": " + description); } }