/* * The MIT License * * Copyright (c) 2010, Yahoo!, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.plugins.labeledgroupedtests; import com.thoughtworks.xstream.XStream; import hudson.Functions; import hudson.XmlFile; import hudson.model.AbstractBuild; import hudson.model.BuildListener; import hudson.tasks.junit.CaseResult; import hudson.tasks.junit.SuiteResult; import hudson.tasks.junit.TestResult; import hudson.tasks.test.AbstractTestResultAction; import hudson.tasks.test.TestObject; import hudson.util.HeapSpaceStringConverter; import hudson.util.XStream2; import org.kohsuke.stapler.StaplerProxy; import javax.rmi.CORBA.Util; import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.logging.Level; import java.util.logging.Logger; /** * User: Benjamin Shine bshine * Date: Oct 21, 2009 * Time: 2:39:57 PM */ public class MetaLabeledTestResultGroupAction extends AbstractTestResultAction<MetaLabeledTestResultGroupAction> implements StaplerProxy { static public final String RESULT_DATA_FILENAME = "testResultGroups.xml"; private int failCount; private int skipCount; private Integer totalCount; // TODO: can we make this just a normal int, and find another way to check // whether we're populated yet? (This technique is borrowed from hudson core TestResultAction.) /** * @deprecated use resultGroupReference instead. */ protected MetaLabeledTestResultGroup resultGroup; /** * Store the result group itself in a separate file so we don't eat up * too much memory. */ private transient WeakReference<MetaLabeledTestResultGroup> resultGroupReference; public MetaLabeledTestResultGroupAction(AbstractBuild owner, MetaLabeledTestResultGroup r, BuildListener listener) { super(owner); setResult(r, listener); } /** * Store the data to a separate file, and update our cached values. */ public synchronized void setResult(MetaLabeledTestResultGroup r, BuildListener listener) { r.setParentAction(this); totalCount = r.getTotalCount(); failCount = r.getFailCount(); skipCount = r.getSkipCount(); // persist the data try { getDataFile().write(r); } catch (IOException e) { e.printStackTrace(listener.fatalError("Failed to save the labeled test groups publisher's test result")); } this.resultGroupReference = new WeakReference<MetaLabeledTestResultGroup>(r); } private XmlFile getDataFile() { return new XmlFile(XSTREAM, new File(owner.getRootDir(), RESULT_DATA_FILENAME)); } public Object getTarget() { return getResult(); } /** * Gets the number of failed tests. */ public int getFailCount() { if (totalCount == null) getResult(); // this will load the result from disk if necessary return failCount; } /** * Gets the total number of skipped tests * * @return */ public int getSkipCount() { if (totalCount == null) getResult(); // this will load the result from disk if necessary return skipCount; } /** * Gets the total number of tests. */ public int getTotalCount() { if (totalCount == null) getResult(); // this will load the result from disk if necessary return totalCount; } /** * Get the result that this action represents. If necessary, the result will be * loaded from disk. * * @return */ public synchronized MetaLabeledTestResultGroup getResult() { // If we've got a legacy data structure, inline, then just return it, // no fancy loading-on-demand. if (this.resultGroup != null) return this.resultGroup; MetaLabeledTestResultGroup r; if (resultGroupReference == null) { r = load(); resultGroupReference = new WeakReference<MetaLabeledTestResultGroup>(r); } else { r = resultGroupReference.get(); } if (r == null) { r = load(); resultGroupReference = new WeakReference<MetaLabeledTestResultGroup>(r); } if (r == null) { logger.severe("Couldn't get result for MetaLabeledTestResultGroup " + this); return null; } if (totalCount == null) { totalCount = r.getTotalCount(); failCount = r.getFailCount(); skipCount = r.getSkipCount(); } return r; } /** * Loads a {@link MetaLabeledTestResultGroup} from disk. */ private MetaLabeledTestResultGroup load() { MetaLabeledTestResultGroup r; try { r = (MetaLabeledTestResultGroup) getDataFile().read(); } catch (IOException e) { logger.log(Level.WARNING, "Failed to load " + getDataFile(), e); r = new MetaLabeledTestResultGroup(); // return a dummy } r.setParentAction(this); return r; } /** * This convenience method is what getResult() should have been, * but with a specified return type. * * @return */ public MetaLabeledTestResultGroup getResultAsTestResultGroup() { return getResult(); } public LabeledTestResultGroup getLabeledTestResultGroup(String label) { return getResult().getGroupByLabel(label); } public String getDescription(TestObject testObject) { return getResult().getDescription(); } public void setDescription(TestObject testObject, String s) { getResult().setDescription(s); } public String getDisplayName() { return "Test Results"; } /** * Bring this object into an internally-consistent state after deserializing it. * For a MetaLabeledTestResultGroupAction , we don't have to do anything, * because the WeakReference handles loading the actual test result data * from disk when it is requested. * The only case where we have something to do here is if this object was * serialized with the test result data inline, rather than in a separate file. * If the data was inline, we do a little dance to move the data into a separate * file. * * @return */ public Object readResolve() { // This method is called when an instance of this object is loaded from // persistent storage into memory. We use this opportunity to detect // and convert from storing the test results in the same file as the // build.xml to storing the test results in a separate file. if (this.resultGroup != null) { // If there was a non-null resultGroup stored in the same file as this action, // tell this result group to get into a valid state, with this object // as the parent action. this.resultGroup.setParentAction(this); this.resultGroup.tally(); totalCount = this.resultGroup.getTotalCount(); failCount = this.resultGroup.getFailCount(); skipCount = this.resultGroup.getSkipCount(); // ****************************************************************** // Slightly riskier: in-memory modifications of legacy data // // Free up memory at startup when loading builds with inline test results. // Save the the results out to a separate file that can be re-loaded // on demand, and release the memory used by the test results, // but do not modify the original build.xml // // MetaLabeledTestResultGroup tmp = this.resultGroup; // Save the result with the new separate-file/weak reference system // setResult(tmp, null); // // Null out this field so that its memory can be reclaimed. // this.resultGroup = null; // ****************************************************************** // ****************************************************************** // Seriously risky: on-disk modification of legacy data. // // This code will re-save the build file without the inline test result data, // but I'm leaving it commented out because we risk data loss or corruption // if we get synchronization wrong. // // MetaLabeledTestResultGroup tmp = this.resultGroup; // Save the result with the new separate-file/weak reference system // setResult(tmp, null); // // // Null out this field so that its memory can be reclaimed. // this.resultGroup = null; // // // Save the build, so that it no longer stores the whole test data inside of itself. // try { // this.owner.save(); // } catch (IOException e) { // logger.warning("Couldn't rewrite build.xml from MetaLabeledTestResultGroupAction: " // + Functions.printThrowable(e)); // } // ****************************************************************** } return this; } private static final Logger logger = Logger.getLogger(MetaLabeledTestResultGroupAction.class.getName()); private static final XStream XSTREAM = new XStream2(); }