Index: doc/configuration/triggers.html =================================================================== --- doc/configuration/triggers.html (revision 599855) +++ doc/configuration/triggers.html (working copy) @@ -120,6 +120,41 @@ Fired after an artifact has been downloaded from a repository to the cache + pre-publish-artifact
since 2.0 + + + + Fired before an artifact is published into a repository + + post-publish-artifact
since 2.0 + + + + Fired after an artifact is published into a repository. Note that this event is fired + whether or not the publication succeeded. The "status" property can be checked to + verify success. + Index: src/java/org/apache/ivy/core/event/publish/StartArtifactPublishEvent.java =================================================================== --- src/java/org/apache/ivy/core/event/publish/StartArtifactPublishEvent.java (revision 0) +++ src/java/org/apache/ivy/core/event/publish/StartArtifactPublishEvent.java (revision 0) @@ -0,0 +1,25 @@ +package org.apache.ivy.core.event.publish; + +import java.io.File; + +import org.apache.ivy.core.module.descriptor.Artifact; +import org.apache.ivy.plugins.resolver.DependencyResolver; + +/** + * Event fired just before an artifact is published into a resolver. Triggers registered + * on {@link #NAME} will be notified of these events. + * + * @see DependencyResolver#publish(Artifact, File, boolean) + * @author Jason.Trump + */ +public class StartArtifactPublishEvent extends PublishEvent { + + private static final long serialVersionUID = -1134274781039590219L; + + public static final String NAME = "pre-publish-artifact"; + + public StartArtifactPublishEvent(DependencyResolver resolver, Artifact artifact, File data, boolean overwrite) { + super(NAME, resolver, artifact, data, overwrite); + } + +} \ No newline at end of file Index: src/java/org/apache/ivy/core/event/publish/PublishEvent.java =================================================================== --- src/java/org/apache/ivy/core/event/publish/PublishEvent.java (revision 0) +++ src/java/org/apache/ivy/core/event/publish/PublishEvent.java (revision 0) @@ -0,0 +1,57 @@ +package org.apache.ivy.core.event.publish; + +import java.io.File; + +import org.apache.ivy.core.event.IvyEvent; +import org.apache.ivy.core.module.descriptor.Artifact; +import org.apache.ivy.plugins.resolver.DependencyResolver; + +/** + * Base class for events fired during {@link DependencyResolver#publish(Artifact, File, boolean)}. + * + * @see StartArtifactPublishEvent + * @see EndArtifactPublishEvent + * @author Jason.Trump + */ +public abstract class PublishEvent extends IvyEvent { + + private final DependencyResolver resolver; + private final Artifact artifact; + private final File data; + private final boolean overwrite; + + protected PublishEvent(String name, DependencyResolver resolver, Artifact artifact, File data, boolean overwrite) { + super(name); + this.resolver = resolver; + this.artifact = artifact; + this.data = data; + this.overwrite = overwrite; + + addMridAttributes(artifact.getModuleRevisionId()); + addAttributes(artifact.getAttributes()); + addAttribute("resolver", resolver.getName()); + addAttribute("file", data.getAbsolutePath()); + addAttribute("overwrite", String.valueOf(overwrite)); + } + + /** @return the resolver into which the artifact is being published */ + public DependencyResolver getResolver() { + return resolver; + } + + /** @return a local file containing the artifact data */ + public File getData() { + return data; + } + + /** @return metadata about the artifact being published */ + public Artifact getArtifact() { + return artifact; + } + + /** @return true iff this event overwrites existing resolver data for this artifact */ + public boolean isOverwrite() { + return overwrite; + } + +} Index: src/java/org/apache/ivy/core/event/publish/EndArtifactPublishEvent.java =================================================================== --- src/java/org/apache/ivy/core/event/publish/EndArtifactPublishEvent.java (revision 0) +++ src/java/org/apache/ivy/core/event/publish/EndArtifactPublishEvent.java (revision 0) @@ -0,0 +1,38 @@ +package org.apache.ivy.core.event.publish; + +import java.io.File; + +import org.apache.ivy.core.module.descriptor.Artifact; +import org.apache.ivy.plugins.resolver.DependencyResolver; + +/** + * Event fired after artifact publication has finished (possibly in error). Triggers + * registered on {@link #NAME} will be notified of these events. + * + * @see DependencyResolver#publish(Artifact, File, boolean) + * @author Jason.Trump + */ +public class EndArtifactPublishEvent extends PublishEvent { + + private static final long serialVersionUID = -65690169431499422L; + + public static final String NAME = "post-publish-artifact"; + + public static final String STATUS_SUCCESSFUL = "successful"; + public static final String STATUS_FAILED = "failed"; + + private final boolean successful; + + public EndArtifactPublishEvent(DependencyResolver resolver, Artifact artifact, File data, boolean overwrite, boolean successful) { + super(NAME, resolver, artifact, data, overwrite); + this.successful = successful; + addAttribute("status", isSuccessful() ? STATUS_SUCCESSFUL : STATUS_FAILED); + } + + /** + * @return true iff no errors were encountered during the publication + */ + public boolean isSuccessful() { + return successful; + } +} \ No newline at end of file Index: src/java/org/apache/ivy/core/publish/PublishEngine.java =================================================================== --- src/java/org/apache/ivy/core/publish/PublishEngine.java (revision 599855) +++ src/java/org/apache/ivy/core/publish/PublishEngine.java (working copy) @@ -35,6 +35,9 @@ import org.apache.ivy.core.IvyPatternHelper; import org.apache.ivy.core.cache.CacheManager; import org.apache.ivy.core.cache.ResolutionCacheManager; +import org.apache.ivy.core.event.EventManager; +import org.apache.ivy.core.event.publish.EndArtifactPublishEvent; +import org.apache.ivy.core.event.publish.StartArtifactPublishEvent; import org.apache.ivy.core.module.descriptor.Artifact; import org.apache.ivy.core.module.descriptor.DefaultArtifact; import org.apache.ivy.core.module.descriptor.MDArtifact; @@ -49,9 +52,11 @@ public class PublishEngine { private PublishEngineSettings settings; + private EventManager eventManager; - public PublishEngine(PublishEngineSettings settings) { + public PublishEngine(PublishEngineSettings settings, EventManager eventManager) { this.settings = settings; + this.eventManager = eventManager; } /** @@ -228,11 +233,19 @@ DependencyResolver resolver, boolean overwrite) throws IOException { IvyContext.getContext().checkInterrupted(); File src = new File(IvyPatternHelper.substitute(srcArtifactPattern, artifact)); - if (src.exists()) { - resolver.publish(artifact, src, overwrite); - return true; - } else { - return false; + + //notify triggers that an artifact is about to be published + eventManager.fireIvyEvent(new StartArtifactPublishEvent(resolver, artifact, src, overwrite)); + boolean successful = false; //set to true once the publish succeeds + try { + if (src.exists()) { + resolver.publish(artifact, src, overwrite); + successful = true; + } + return successful; + } finally { + //notify triggers that the publish is finished, successfully or not. + eventManager.fireIvyEvent(new EndArtifactPublishEvent(resolver, artifact, src, overwrite, successful)); } } Index: src/java/org/apache/ivy/Ivy.java =================================================================== --- src/java/org/apache/ivy/Ivy.java (revision 599855) +++ src/java/org/apache/ivy/Ivy.java (working copy) @@ -284,7 +284,7 @@ deliverEngine = new DeliverEngine(settings); } if (publishEngine == null) { - publishEngine = new PublishEngine(settings); + publishEngine = new PublishEngine(settings, eventManager); } if (installEngine == null) { installEngine = new InstallEngine( Index: test/java/org/apache/ivy/core/publish/ivy-1.0-dev.xml =================================================================== --- test/java/org/apache/ivy/core/publish/ivy-1.0-dev.xml (revision 0) +++ test/java/org/apache/ivy/core/publish/ivy-1.0-dev.xml (revision 0) @@ -0,0 +1,25 @@ + + + + + + + + Index: test/java/org/apache/ivy/core/publish/ivysettings-publisheventstest.xml =================================================================== --- test/java/org/apache/ivy/core/publish/ivysettings-publisheventstest.xml (revision 0) +++ test/java/org/apache/ivy/core/publish/ivysettings-publisheventstest.xml (revision 0) @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + Index: test/java/org/apache/ivy/core/publish/PublishEngineTest.java =================================================================== --- test/java/org/apache/ivy/core/publish/PublishEngineTest.java (revision 599855) +++ test/java/org/apache/ivy/core/publish/PublishEngineTest.java (working copy) @@ -50,7 +50,7 @@ public void testAtomicity() throws Exception { IvySettings settings = new IvySettings(); - final PublishEngine engine = new PublishEngine(settings); + final PublishEngine engine = new PublishEngine(settings, new EventManager()); final int[] counter = new int[] {0}; final DefaultModuleDescriptor md = DefaultModuleDescriptor Index: test/java/org/apache/ivy/core/publish/PublishEventsTest.java =================================================================== --- test/java/org/apache/ivy/core/publish/PublishEventsTest.java (revision 0) +++ test/java/org/apache/ivy/core/publish/PublishEventsTest.java (revision 0) @@ -0,0 +1,448 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ivy.core.publish; + +import java.io.File; +import java.io.IOException; +import java.net.URLDecoder; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; + +import org.apache.ivy.Ivy; +import org.apache.ivy.core.IvyContext; +import org.apache.ivy.core.event.IvyEvent; +import org.apache.ivy.core.event.publish.EndArtifactPublishEvent; +import org.apache.ivy.core.event.publish.PublishEvent; +import org.apache.ivy.core.event.publish.StartArtifactPublishEvent; +import org.apache.ivy.core.module.descriptor.Artifact; +import org.apache.ivy.core.module.descriptor.MDArtifact; +import org.apache.ivy.core.module.descriptor.ModuleDescriptor; +import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser; +import org.apache.ivy.plugins.resolver.MockResolver; +import org.apache.ivy.plugins.trigger.AbstractTrigger; + +import junit.framework.TestCase; + +public class PublishEventsTest extends TestCase { + + //maps ArtifactRevisionId to PublishTestCase instance. + private HashMap expectedPublications; + //expected values for the current artifact being published. + private PublishTestCase currentTestCase; + private boolean expectedOverwrite; + + //number of times PrePublishTrigger has been invoked successfully + private int preTriggers; + //number of times PostPublishTrigger has been invoked successfully + private int postTriggers; + //number of times an artifact has been successfully published by the resolver + private int publications; + + //dummy test data that is reused by all cases. + private File ivyFile; + private Artifact ivyArtifact; + private File dataFile; + private Artifact dataArtifact; + + private ModuleDescriptor publishModule; + private Collection publishSources; + private PublishOptions publishOptions; + + //if non-null, InstrumentedResolver will throw this exception during publish + private IOException publishError; + + //the ivy instance under test + private Ivy ivy; + private PublishEngine publishEngine; + + protected void setUp() throws Exception { + super.setUp(); + + //reset test case state. + resetCounters(); + + //this ivy settings should configure an InstrumentedResolver, PrePublishTrigger, and PostPublishTrigger + //(see inner classes below). + ivy = Ivy.newInstance(); + ivy.configure(PublishEventsTest.class.getResource("ivysettings-publisheventstest.xml")); + ivy.pushContext(); + publishEngine = ivy.getPublishEngine(); + + //setup dummy ivy and data files to test publishing. since we're testing the engine and not the resolver, + //we don't really care whether the file actually gets published. we just want to make sure + //that the engine calls the correct methods in the correct order, and fires required events. + String resourcePath = PublishEventsTest.class.getResource("ivy-1.0-dev.xml").getPath(); + resourcePath = URLDecoder.decode(resourcePath, System.getProperty("file.encoding")); + ivyFile = new File(resourcePath); + assertTrue("path to ivy file found in test environment", ivyFile.exists()); + //the contents of the data file don't matter. + dataFile = File.createTempFile("ivydata", ".jar"); + dataFile.deleteOnExit(); + + publishModule = XmlModuleDescriptorParser.getInstance().parseDescriptor(ivy.getSettings(), ivyFile.toURL(), false); + //always use the same source data file, no pattern substitution is required. + publishSources = Collections.singleton(dataFile.getAbsolutePath()); + //always use the same ivy file, no pattern substitution is required. + publishOptions = new PublishOptions(); + publishOptions.setSrcIvyPattern(ivyFile.getAbsolutePath()); + + //set up our expectations for the test. these variables will + //be checked by the resolver and triggers during publication. + dataArtifact = publishModule.getAllArtifacts()[0]; + assertEquals("sanity check", "foo", dataArtifact.getName()); + ivyArtifact = MDArtifact.newIvyArtifact(publishModule); + + expectedPublications = new HashMap(); + expectedPublications.put(dataArtifact.getId(), new PublishTestCase(dataArtifact, dataFile, true)); + expectedPublications.put(ivyArtifact.getId(), new PublishTestCase(ivyArtifact, ivyFile, true)); + assertEquals("hashCode sanity check: two artifacts expected during publish", 2, expectedPublications.size()); + + //push the TestCase instance onto the context stack, so that our + //triggers and resolver instances can interact with it it. + IvyContext.getContext().push(PublishEventsTest.class.getName(), this); + } + + protected void tearDown() throws Exception { + super.tearDown(); + + //reset test state. + resetCounters(); + + //test case is finished, pop the test context off the stack. + IvyContext.getContext().pop(PublishEventsTest.class.getName()); + + //cleanup ivy resources + if (ivy != null) { + ivy.popContext(); + ivy = null; + } + publishEngine = null; + if (dataFile != null) + dataFile.delete(); + dataFile = null; + ivyFile = null; + } + + protected void resetCounters() { + preTriggers = 0; + postTriggers = 0; + publications = 0; + + expectedPublications = null; + expectedOverwrite = false; + publishError = null; + currentTestCase = null; + + ivyArtifact = null; + dataArtifact = null; + } + + /** + * Test a simple artifact publish, without errors or overwrite settings. + */ + public void testPublishNoOverwrite() throws IOException { + //no modifications to input required for this case -- call out to the resolver, and verify that + //all of our test counters have been incremented. + Collection missing = publishEngine.publish(publishModule.getModuleRevisionId(), publishSources, "default", publishOptions); + assertEquals("no missing artifacts", 0, missing.size()); + + //if all tests passed, all of our counter variables should have been updated. + assertEquals("pre-publish trigger fired and passed all tests", 2, preTriggers); + assertEquals("post-publish trigger fired and passed all tests", 2, postTriggers); + assertEquals("resolver received a publish() call, and passed all tests", 2, publications); + assertEquals("all expected artifacts have been published", 0, expectedPublications.size()); + } + + /** + * Test a simple artifact publish, with overwrite set to true. + */ + public void testPublishWithOverwrite() throws IOException { + //we expect the overwrite settings to be passed through the event listeners and into the publisher. + this.expectedOverwrite = true; + + //set overwrite to true. InstrumentedResolver will verify that the correct argument value was provided. + publishOptions.setOverwrite(true); + Collection missing = publishEngine.publish(publishModule.getModuleRevisionId(), publishSources, "default", publishOptions); + assertEquals("no missing artifacts", 0, missing.size()); + + //if all tests passed, all of our counter variables should have been updated. + assertEquals("pre-publish trigger fired and passed all tests", 2, preTriggers); + assertEquals("post-publish trigger fired and passed all tests", 2, postTriggers); + assertEquals("resolver received a publish() call, and passed all tests", 2, publications); + assertEquals("all expected artifacts have been published", 0, expectedPublications.size()); + } + + /** + * Test an attempted publish with an invalid data file path. + */ + public void testPublishMissingFile() throws IOException { + //delete the datafile. the publish should fail, but all events should still be fired, + //and the ivy artifact should still publish successfully. + assertTrue("datafile has been destroyed", dataFile.delete()); + PublishTestCase dataPublish = (PublishTestCase)expectedPublications.get(dataArtifact.getId()); + dataPublish.expectedSuccess = false; + Collection missing = publishEngine.publish(publishModule.getModuleRevisionId(), publishSources, "default", publishOptions); + assertEquals("one missing artifact", 1, missing.size()); + assertSameArtifact("missing artifact was returned", dataArtifact, (Artifact)missing.iterator().next()); + + //if all tests passed, all of our counter variables should have been updated. + assertEquals("pre-publish trigger fired and passed all tests", 2, preTriggers); + assertEquals("post-publish trigger fired and passed all tests", 2, postTriggers); + assertEquals("only the ivy file published successfully", 1, publications); + assertEquals("publish of all expected artifacts has been attempted", 0, expectedPublications.size()); + } + + /** + * Test an attempted publish in which the target resolver throws an IOException. + */ + public void testPublishWithException() { + //set an error to be thrown during publication of the data file. + this.publishError = new IOException("boom!"); + //we don't care which artifact is attempted; either will fail with an IOException. + for (Iterator it = expectedPublications.values().iterator(); it.hasNext(); ) + ((PublishTestCase)it.next()).expectedSuccess = false; + + try { + publishEngine.publish(publishModule.getModuleRevisionId(), publishSources, "default", publishOptions); + fail("if the resolver throws an exception, the engine should too"); + } catch (IOException expected) { + assertSame("exception thrown by the resolver should be propagated by the engine", + this.publishError, expected); + } + + //the publish engine gives up after the resolver throws an exception on the first artifact, + //so only one set of events should have been fired. + //note that the initial publish error shouldn't prevent the post-publish trigger from firing. + assertEquals("pre-publish trigger fired and passed all tests", 1, preTriggers); + assertEquals("post-publish trigger fired and passed all tests", 1, postTriggers); + assertEquals("resolver never published successfully", 0, publications); + assertEquals("publication aborted after first failure", 1, expectedPublications.size()); + } + + /** + * Assert that two Artifact instances refer to the same artifact and contain the same metadata. + */ + public static void assertSameArtifact(String message, Artifact expected, Artifact actual) { + assertEquals(message + ": name", expected.getName(), actual.getName()); + assertEquals(message + ": id", expected.getId(), actual.getId()); + assertEquals(message + ": moduleRevisionId", expected.getModuleRevisionId(), actual.getModuleRevisionId()); + assertTrue(message + ": configurations", Arrays.equals(expected.getConfigurations(), actual.getConfigurations())); + assertEquals(message + ": type", expected.getType(), actual.getType()); + assertEquals(message + ": ext", expected.getExt(), actual.getExt()); + assertEquals(message + ": publicationDate", expected.getPublicationDate(), actual.getPublicationDate()); + assertEquals(message + ": attributes", expected.getAttributes(), actual.getAttributes()); + assertEquals(message + ": url", expected.getUrl(), actual.getUrl()); + } + + public static class PublishTestCase { + public Artifact expectedArtifact; + public File expectedData; + public boolean expectedSuccess; + + public boolean preTriggerFired; + public boolean published; + public boolean postTriggerFired; + + public PublishTestCase(Artifact artifact, File data, boolean success) { + this.expectedArtifact = artifact; + this.expectedData = data; + this.expectedSuccess = success; + } + } + + /** + * Base class for pre- and post-publish-artifact triggers. When the trigger receives an event, + * the contents of the publish event are examined to make sure they match the variable settings + * on the calling {@link PublishEventsTest#currentTestCase} instance. + * + * @author Jason.Trump + */ + public static class TestPublishTrigger extends AbstractTrigger { + + public void progress(IvyEvent event) { + PublishEventsTest test = (PublishEventsTest)IvyContext.getContext().peek(PublishEventsTest.class.getName()); + InstrumentedResolver resolver = (InstrumentedResolver)test.ivy.getSettings().getResolver("default"); + + assertNotNull("instrumented resolver configured", resolver); + assertNotNull("got a reference to the current unit test case", test); + + //test the proper sequence of events by comparing the number of pre-events, + //post-events, and actual publications. + assertTrue("event is of correct base type", + event instanceof PublishEvent); + + PublishEvent pubEvent = (PublishEvent)event; + Artifact expectedArtifact = test.currentTestCase.expectedArtifact; + File expectedData = test.currentTestCase.expectedData; + + assertSameArtifact("event records correct artifact", + expectedArtifact, pubEvent.getArtifact()); + try { + assertEquals("event records correct file", + expectedData.getCanonicalPath(), pubEvent.getData().getCanonicalPath()); + + assertEquals("event records correct overwrite setting", test.expectedOverwrite, pubEvent.isOverwrite()); + assertSame("event presents correct resolver", resolver, pubEvent.getResolver()); + + String[] attributes = { + "organisation", "module", "revision", "artifact", "type", "ext", "resolver", "overwrite" + }; + String[] values = { + "apache", "PublishEventsTest", "1.0-dev", expectedArtifact.getName(), expectedArtifact.getType(), expectedArtifact.getExt(), "default", String.valueOf(test.expectedOverwrite) + }; + + for (int i = 0; i < attributes.length; ++i) + assertEquals("event declares correct value for " + attributes[i], + values[i], event.getAttributes().get(attributes[i])); + //we test file separately, since it is hard to guaranteean exact path match, but we want + //to make sure that both paths point to the same canonical location on the filesystem + String filePath = event.getAttributes().get("file").toString(); + assertEquals("event declares correct value for file", + expectedData.getCanonicalPath(), new File(filePath).getCanonicalPath()); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + } + + /** + * Extends the tests done by {@link TestPublishTrigger} to check that pre-publish events are + * fired before DependencyResolver.publish() is called, and before post-publish events are fired. + * + * @author Jason.Trump + */ + public static class PrePublishTrigger extends TestPublishTrigger { + + public void progress(IvyEvent event) { + + PublishEventsTest test = (PublishEventsTest)IvyContext.getContext().peek(PublishEventsTest.class.getName()); + assertTrue("event is of correct concrete type", + event instanceof StartArtifactPublishEvent); + StartArtifactPublishEvent startEvent = (StartArtifactPublishEvent)event; + + //verify that the artifact being publish was in the expected set. set the 'currentTestCase' + //pointer so that the resolver and post-publish trigger can check against it. + Artifact artifact = startEvent.getArtifact(); + assertNotNull("event defines artifact", artifact); + + PublishTestCase currentTestCase = (PublishTestCase)test.expectedPublications.remove(artifact.getId()); + assertNotNull("artifact " + artifact.getId() + " was expected for publication", currentTestCase); + assertFalse("current publication has not been visited yet", currentTestCase.preTriggerFired); + assertFalse("current publication has not been visited yet", currentTestCase.published); + assertFalse("current publication has not been visited yet", currentTestCase.postTriggerFired); + test.currentTestCase = currentTestCase; + + //superclass tests common attributes of publish events + super.progress(event); + + //increment the call counter in the test + currentTestCase.preTriggerFired = true; + ++test.preTriggers; + } + + } + + /** + * Extends the tests done by {@link TestPublishTrigger} to check that post-publish events are + * fired after DependencyResolver.publish() is called, and that the "status" attribute is + * set to the correct value. + * + * @author Jason.Trump + */ + public static class PostPublishTrigger extends TestPublishTrigger { + + public void progress(IvyEvent event) { + //superclass tests common attributes of publish events + super.progress(event); + + PublishEventsTest test = (PublishEventsTest)IvyContext.getContext().peek(PublishEventsTest.class.getName()); + + //test the proper sequence of events by comparing the current count of pre-events, + //post-events, and actual publications. + assertTrue("event is of correct concrete type", + event instanceof EndArtifactPublishEvent); + assertTrue("pre-publish event has been triggered", test.preTriggers > 0); + + //test sequence of events + assertTrue("pre-trigger event has already been fired for this artifact", + test.currentTestCase.preTriggerFired); + assertEquals("publication has been done if possible", + test.currentTestCase.expectedSuccess, test.currentTestCase.published); + assertFalse("post-publish event has not yet been fired for this artifact", + test.currentTestCase.postTriggerFired); + + //test the "status" attribute of the post- event. + EndArtifactPublishEvent endEvent = (EndArtifactPublishEvent)event; + assertEquals("status bit is set correctly", + test.currentTestCase.expectedSuccess, endEvent.isSuccessful()); + + String expectedStatus = test.currentTestCase.expectedSuccess ? "successful" : "failed"; + assertEquals("status attribute is set to correct value", + expectedStatus, endEvent.getAttributes().get("status")); + + //increment the call counter in the wrapper test + test.currentTestCase.postTriggerFired = true; + ++test.postTriggers; + } + + } + + /** + * When publish() is called, verifies that a pre-publish event has been fired, and also verifies that + * the method arguments have the correct value. Also simulates an IOException if the current + * test case demands it. + * + * @author Jason.Trump + */ + public static class InstrumentedResolver extends MockResolver { + + public void publish(Artifact artifact, File src, boolean overwrite) throws IOException { + + //verify that the data from the current test case has been handed down to us + PublishEventsTest test = (PublishEventsTest)IvyContext.getContext().peek(PublishEventsTest.class.getName()); + + //test sequence of events. + assertNotNull(test.currentTestCase); + assertTrue("preTrigger has already fired", test.currentTestCase.preTriggerFired); + assertFalse("postTrigger has not yet fired", test.currentTestCase.postTriggerFired); + assertFalse("publish has not been called", test.currentTestCase.published); + + //test event data + assertSameArtifact("publisher has received correct artifact", + test.currentTestCase.expectedArtifact, artifact); + assertEquals("publisher has received correct datafile", + test.currentTestCase.expectedData.getCanonicalPath(), src.getCanonicalPath()); + assertEquals("publisher has received correct overwrite setting", + test.expectedOverwrite, overwrite); + assertTrue("publisher only invoked when source file exists", test.currentTestCase.expectedData.exists()); + + //simulate a publisher error if the current test case demands it. + if (test.publishError != null) + throw test.publishError; + + //all assertions pass. increment the publication count + test.currentTestCase.published = true; + ++test.publications; + } + } + +}