| /* |
| * 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.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. |
| ivyFile = new File("test/java/org/apache/ivy/core/publish/ivy-1.0-dev.xml"); |
| assertTrue("path to ivy file not 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.toURI().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 |
| // 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", 1, preTriggers); |
| assertEquals("post-publish trigger fired and passed all tests", 1, postTriggers); |
| assertEquals("only the ivy file published successfully", 1, publications); |
| assertEquals("publish of all expected artifacts has been attempted", 1, |
| 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. |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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; |
| } |
| } |
| |
| } |