001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.ivy.core.publish;
019:
020: import java.io.File;
021: import java.io.IOException;
022: import java.net.URLDecoder;
023: import java.util.Arrays;
024: import java.util.Collection;
025: import java.util.Collections;
026: import java.util.HashMap;
027: import java.util.Iterator;
028:
029: import org.apache.ivy.Ivy;
030: import org.apache.ivy.core.IvyContext;
031: import org.apache.ivy.core.event.IvyEvent;
032: import org.apache.ivy.core.event.publish.EndArtifactPublishEvent;
033: import org.apache.ivy.core.event.publish.PublishEvent;
034: import org.apache.ivy.core.event.publish.StartArtifactPublishEvent;
035: import org.apache.ivy.core.module.descriptor.Artifact;
036: import org.apache.ivy.core.module.descriptor.MDArtifact;
037: import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
038: import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
039: import org.apache.ivy.plugins.resolver.MockResolver;
040: import org.apache.ivy.plugins.trigger.AbstractTrigger;
041:
042: import junit.framework.TestCase;
043:
044: public class PublishEventsTest extends TestCase {
045:
046: //maps ArtifactRevisionId to PublishTestCase instance.
047: private HashMap expectedPublications;
048: //expected values for the current artifact being published.
049: private PublishTestCase currentTestCase;
050: private boolean expectedOverwrite;
051:
052: //number of times PrePublishTrigger has been invoked successfully
053: private int preTriggers;
054: //number of times PostPublishTrigger has been invoked successfully
055: private int postTriggers;
056: //number of times an artifact has been successfully published by the resolver
057: private int publications;
058:
059: //dummy test data that is reused by all cases.
060: private File ivyFile;
061: private Artifact ivyArtifact;
062: private File dataFile;
063: private Artifact dataArtifact;
064:
065: private ModuleDescriptor publishModule;
066: private Collection publishSources;
067: private PublishOptions publishOptions;
068:
069: //if non-null, InstrumentedResolver will throw this exception during publish
070: private IOException publishError;
071:
072: //the ivy instance under test
073: private Ivy ivy;
074: private PublishEngine publishEngine;
075:
076: protected void setUp() throws Exception {
077: super .setUp();
078:
079: //reset test case state.
080: resetCounters();
081:
082: //this ivy settings should configure an InstrumentedResolver, PrePublishTrigger, and PostPublishTrigger
083: //(see inner classes below).
084: ivy = Ivy.newInstance();
085: ivy.configure(PublishEventsTest.class
086: .getResource("ivysettings-publisheventstest.xml"));
087: ivy.pushContext();
088: publishEngine = ivy.getPublishEngine();
089:
090: //setup dummy ivy and data files to test publishing. since we're testing the engine and not the resolver,
091: //we don't really care whether the file actually gets published. we just want to make sure
092: //that the engine calls the correct methods in the correct order, and fires required events.
093: String resourcePath = PublishEventsTest.class.getResource(
094: "ivy-1.0-dev.xml").getPath();
095: resourcePath = URLDecoder.decode(resourcePath, System
096: .getProperty("file.encoding"));
097: ivyFile = new File(resourcePath);
098: assertTrue("path to ivy file found in test environment",
099: ivyFile.exists());
100: //the contents of the data file don't matter.
101: dataFile = File.createTempFile("ivydata", ".jar");
102: dataFile.deleteOnExit();
103:
104: publishModule = XmlModuleDescriptorParser.getInstance()
105: .parseDescriptor(ivy.getSettings(), ivyFile.toURL(),
106: false);
107: //always use the same source data file, no pattern substitution is required.
108: publishSources = Collections.singleton(dataFile
109: .getAbsolutePath());
110: //always use the same ivy file, no pattern substitution is required.
111: publishOptions = new PublishOptions();
112: publishOptions.setSrcIvyPattern(ivyFile.getAbsolutePath());
113:
114: //set up our expectations for the test. these variables will
115: //be checked by the resolver and triggers during publication.
116: dataArtifact = publishModule.getAllArtifacts()[0];
117: assertEquals("sanity check", "foo", dataArtifact.getName());
118: ivyArtifact = MDArtifact.newIvyArtifact(publishModule);
119:
120: expectedPublications = new HashMap();
121: expectedPublications.put(dataArtifact.getId(),
122: new PublishTestCase(dataArtifact, dataFile, true));
123: expectedPublications.put(ivyArtifact.getId(),
124: new PublishTestCase(ivyArtifact, ivyFile, true));
125: assertEquals(
126: "hashCode sanity check: two artifacts expected during publish",
127: 2, expectedPublications.size());
128:
129: //push the TestCase instance onto the context stack, so that our
130: //triggers and resolver instances can interact with it it.
131: IvyContext.getContext().push(PublishEventsTest.class.getName(),
132: this );
133: }
134:
135: protected void tearDown() throws Exception {
136: super .tearDown();
137:
138: //reset test state.
139: resetCounters();
140:
141: //test case is finished, pop the test context off the stack.
142: IvyContext.getContext().pop(PublishEventsTest.class.getName());
143:
144: //cleanup ivy resources
145: if (ivy != null) {
146: ivy.popContext();
147: ivy = null;
148: }
149: publishEngine = null;
150: if (dataFile != null)
151: dataFile.delete();
152: dataFile = null;
153: ivyFile = null;
154: }
155:
156: protected void resetCounters() {
157: preTriggers = 0;
158: postTriggers = 0;
159: publications = 0;
160:
161: expectedPublications = null;
162: expectedOverwrite = false;
163: publishError = null;
164: currentTestCase = null;
165:
166: ivyArtifact = null;
167: dataArtifact = null;
168: }
169:
170: /**
171: * Test a simple artifact publish, without errors or overwrite settings.
172: */
173: public void testPublishNoOverwrite() throws IOException {
174: //no modifications to input required for this case -- call out to the resolver, and verify that
175: //all of our test counters have been incremented.
176: Collection missing = publishEngine.publish(publishModule
177: .getModuleRevisionId(), publishSources, "default",
178: publishOptions);
179: assertEquals("no missing artifacts", 0, missing.size());
180:
181: //if all tests passed, all of our counter variables should have been updated.
182: assertEquals("pre-publish trigger fired and passed all tests",
183: 2, preTriggers);
184: assertEquals("post-publish trigger fired and passed all tests",
185: 2, postTriggers);
186: assertEquals(
187: "resolver received a publish() call, and passed all tests",
188: 2, publications);
189: assertEquals("all expected artifacts have been published", 0,
190: expectedPublications.size());
191: }
192:
193: /**
194: * Test a simple artifact publish, with overwrite set to true.
195: */
196: public void testPublishWithOverwrite() throws IOException {
197: //we expect the overwrite settings to be passed through the event listeners and into the publisher.
198: this .expectedOverwrite = true;
199:
200: //set overwrite to true. InstrumentedResolver will verify that the correct argument value was provided.
201: publishOptions.setOverwrite(true);
202: Collection missing = publishEngine.publish(publishModule
203: .getModuleRevisionId(), publishSources, "default",
204: publishOptions);
205: assertEquals("no missing artifacts", 0, missing.size());
206:
207: //if all tests passed, all of our counter variables should have been updated.
208: assertEquals("pre-publish trigger fired and passed all tests",
209: 2, preTriggers);
210: assertEquals("post-publish trigger fired and passed all tests",
211: 2, postTriggers);
212: assertEquals(
213: "resolver received a publish() call, and passed all tests",
214: 2, publications);
215: assertEquals("all expected artifacts have been published", 0,
216: expectedPublications.size());
217: }
218:
219: /**
220: * Test an attempted publish with an invalid data file path.
221: */
222: public void testPublishMissingFile() throws IOException {
223: //delete the datafile. the publish should fail, but all events should still be fired,
224: //and the ivy artifact should still publish successfully.
225: assertTrue("datafile has been destroyed", dataFile.delete());
226: PublishTestCase dataPublish = (PublishTestCase) expectedPublications
227: .get(dataArtifact.getId());
228: dataPublish.expectedSuccess = false;
229: Collection missing = publishEngine.publish(publishModule
230: .getModuleRevisionId(), publishSources, "default",
231: publishOptions);
232: assertEquals("one missing artifact", 1, missing.size());
233: assertSameArtifact("missing artifact was returned",
234: dataArtifact, (Artifact) missing.iterator().next());
235:
236: //if all tests passed, all of our counter variables should have been updated.
237: assertEquals("pre-publish trigger fired and passed all tests",
238: 2, preTriggers);
239: assertEquals("post-publish trigger fired and passed all tests",
240: 2, postTriggers);
241: assertEquals("only the ivy file published successfully", 1,
242: publications);
243: assertEquals(
244: "publish of all expected artifacts has been attempted",
245: 0, expectedPublications.size());
246: }
247:
248: /**
249: * Test an attempted publish in which the target resolver throws an IOException.
250: */
251: public void testPublishWithException() {
252: //set an error to be thrown during publication of the data file.
253: this .publishError = new IOException("boom!");
254: //we don't care which artifact is attempted; either will fail with an IOException.
255: for (Iterator it = expectedPublications.values().iterator(); it
256: .hasNext();)
257: ((PublishTestCase) it.next()).expectedSuccess = false;
258:
259: try {
260: publishEngine.publish(publishModule.getModuleRevisionId(),
261: publishSources, "default", publishOptions);
262: fail("if the resolver throws an exception, the engine should too");
263: } catch (IOException expected) {
264: assertSame(
265: "exception thrown by the resolver should be propagated by the engine",
266: this .publishError, expected);
267: }
268:
269: //the publish engine gives up after the resolver throws an exception on the first artifact,
270: //so only one set of events should have been fired.
271: //note that the initial publish error shouldn't prevent the post-publish trigger from firing.
272: assertEquals("pre-publish trigger fired and passed all tests",
273: 1, preTriggers);
274: assertEquals("post-publish trigger fired and passed all tests",
275: 1, postTriggers);
276: assertEquals("resolver never published successfully", 0,
277: publications);
278: assertEquals("publication aborted after first failure", 1,
279: expectedPublications.size());
280: }
281:
282: /**
283: * Assert that two Artifact instances refer to the same artifact and contain the same metadata.
284: */
285: public static void assertSameArtifact(String message,
286: Artifact expected, Artifact actual) {
287: assertEquals(message + ": name", expected.getName(), actual
288: .getName());
289: assertEquals(message + ": id", expected.getId(), actual.getId());
290: assertEquals(message + ": moduleRevisionId", expected
291: .getModuleRevisionId(), actual.getModuleRevisionId());
292: assertTrue(message + ": configurations", Arrays.equals(expected
293: .getConfigurations(), actual.getConfigurations()));
294: assertEquals(message + ": type", expected.getType(), actual
295: .getType());
296: assertEquals(message + ": ext", expected.getExt(), actual
297: .getExt());
298: assertEquals(message + ": publicationDate", expected
299: .getPublicationDate(), actual.getPublicationDate());
300: assertEquals(message + ": attributes",
301: expected.getAttributes(), actual.getAttributes());
302: assertEquals(message + ": url", expected.getUrl(), actual
303: .getUrl());
304: }
305:
306: public static class PublishTestCase {
307: public Artifact expectedArtifact;
308: public File expectedData;
309: public boolean expectedSuccess;
310:
311: public boolean preTriggerFired;
312: public boolean published;
313: public boolean postTriggerFired;
314:
315: public PublishTestCase(Artifact artifact, File data,
316: boolean success) {
317: this .expectedArtifact = artifact;
318: this .expectedData = data;
319: this .expectedSuccess = success;
320: }
321: }
322:
323: /**
324: * Base class for pre- and post-publish-artifact triggers. When the trigger receives an event,
325: * the contents of the publish event are examined to make sure they match the variable settings
326: * on the calling {@link PublishEventsTest#currentTestCase} instance.
327: */
328: public static class TestPublishTrigger extends AbstractTrigger {
329:
330: public void progress(IvyEvent event) {
331: PublishEventsTest test = (PublishEventsTest) IvyContext
332: .getContext().peek(
333: PublishEventsTest.class.getName());
334: InstrumentedResolver resolver = (InstrumentedResolver) test.ivy
335: .getSettings().getResolver("default");
336:
337: assertNotNull("instrumented resolver configured", resolver);
338: assertNotNull(
339: "got a reference to the current unit test case",
340: test);
341:
342: //test the proper sequence of events by comparing the number of pre-events,
343: //post-events, and actual publications.
344: assertTrue("event is of correct base type",
345: event instanceof PublishEvent);
346:
347: PublishEvent pubEvent = (PublishEvent) event;
348: Artifact expectedArtifact = test.currentTestCase.expectedArtifact;
349: File expectedData = test.currentTestCase.expectedData;
350:
351: assertSameArtifact("event records correct artifact",
352: expectedArtifact, pubEvent.getArtifact());
353: try {
354: assertEquals("event records correct file", expectedData
355: .getCanonicalPath(), pubEvent.getData()
356: .getCanonicalPath());
357:
358: assertEquals("event records correct overwrite setting",
359: test.expectedOverwrite, pubEvent.isOverwrite());
360: assertSame("event presents correct resolver", resolver,
361: pubEvent.getResolver());
362:
363: String[] attributes = { "organisation", "module",
364: "revision", "artifact", "type", "ext",
365: "resolver", "overwrite" };
366: String[] values = { "apache", "PublishEventsTest",
367: "1.0-dev", expectedArtifact.getName(),
368: expectedArtifact.getType(),
369: expectedArtifact.getExt(), "default",
370: String.valueOf(test.expectedOverwrite) };
371:
372: for (int i = 0; i < attributes.length; ++i)
373: assertEquals("event declares correct value for "
374: + attributes[i], values[i], event
375: .getAttributes().get(attributes[i]));
376: //we test file separately, since it is hard to guaranteean exact path match, but we want
377: //to make sure that both paths point to the same canonical location on the filesystem
378: String filePath = event.getAttributes().get("file")
379: .toString();
380: assertEquals("event declares correct value for file",
381: expectedData.getCanonicalPath(), new File(
382: filePath).getCanonicalPath());
383: } catch (IOException ioe) {
384: throw new RuntimeException(ioe);
385: }
386: }
387:
388: }
389:
390: /**
391: * Extends the tests done by {@link TestPublishTrigger} to check that pre-publish events are
392: * fired before DependencyResolver.publish() is called, and before post-publish events are fired.
393: */
394: public static class PrePublishTrigger extends TestPublishTrigger {
395:
396: public void progress(IvyEvent event) {
397:
398: PublishEventsTest test = (PublishEventsTest) IvyContext
399: .getContext().peek(
400: PublishEventsTest.class.getName());
401: assertTrue("event is of correct concrete type",
402: event instanceof StartArtifactPublishEvent);
403: StartArtifactPublishEvent startEvent = (StartArtifactPublishEvent) event;
404:
405: //verify that the artifact being publish was in the expected set. set the 'currentTestCase'
406: //pointer so that the resolver and post-publish trigger can check against it.
407: Artifact artifact = startEvent.getArtifact();
408: assertNotNull("event defines artifact", artifact);
409:
410: PublishTestCase currentTestCase = (PublishTestCase) test.expectedPublications
411: .remove(artifact.getId());
412: assertNotNull("artifact " + artifact.getId()
413: + " was expected for publication", currentTestCase);
414: assertFalse("current publication has not been visited yet",
415: currentTestCase.preTriggerFired);
416: assertFalse("current publication has not been visited yet",
417: currentTestCase.published);
418: assertFalse("current publication has not been visited yet",
419: currentTestCase.postTriggerFired);
420: test.currentTestCase = currentTestCase;
421:
422: //superclass tests common attributes of publish events
423: super .progress(event);
424:
425: //increment the call counter in the test
426: currentTestCase.preTriggerFired = true;
427: ++test.preTriggers;
428: }
429:
430: }
431:
432: /**
433: * Extends the tests done by {@link TestPublishTrigger} to check that post-publish events are
434: * fired after DependencyResolver.publish() is called, and that the "status" attribute is
435: * set to the correct value.
436: */
437: public static class PostPublishTrigger extends TestPublishTrigger {
438:
439: public void progress(IvyEvent event) {
440: //superclass tests common attributes of publish events
441: super .progress(event);
442:
443: PublishEventsTest test = (PublishEventsTest) IvyContext
444: .getContext().peek(
445: PublishEventsTest.class.getName());
446:
447: //test the proper sequence of events by comparing the current count of pre-events,
448: //post-events, and actual publications.
449: assertTrue("event is of correct concrete type",
450: event instanceof EndArtifactPublishEvent);
451: assertTrue("pre-publish event has been triggered",
452: test.preTriggers > 0);
453:
454: //test sequence of events
455: assertTrue(
456: "pre-trigger event has already been fired for this artifact",
457: test.currentTestCase.preTriggerFired);
458: assertEquals("publication has been done if possible",
459: test.currentTestCase.expectedSuccess,
460: test.currentTestCase.published);
461: assertFalse(
462: "post-publish event has not yet been fired for this artifact",
463: test.currentTestCase.postTriggerFired);
464:
465: //test the "status" attribute of the post- event.
466: EndArtifactPublishEvent endEvent = (EndArtifactPublishEvent) event;
467: assertEquals("status bit is set correctly",
468: test.currentTestCase.expectedSuccess, endEvent
469: .isSuccessful());
470:
471: String expectedStatus = test.currentTestCase.expectedSuccess ? "successful"
472: : "failed";
473: assertEquals("status attribute is set to correct value",
474: expectedStatus, endEvent.getAttributes().get(
475: "status"));
476:
477: //increment the call counter in the wrapper test
478: test.currentTestCase.postTriggerFired = true;
479: ++test.postTriggers;
480: }
481:
482: }
483:
484: /**
485: * When publish() is called, verifies that a pre-publish event has been fired, and also verifies that
486: * the method arguments have the correct value. Also simulates an IOException if the current
487: * test case demands it.
488: */
489: public static class InstrumentedResolver extends MockResolver {
490:
491: public void publish(Artifact artifact, File src,
492: boolean overwrite) throws IOException {
493:
494: //verify that the data from the current test case has been handed down to us
495: PublishEventsTest test = (PublishEventsTest) IvyContext
496: .getContext().peek(
497: PublishEventsTest.class.getName());
498:
499: //test sequence of events.
500: assertNotNull(test.currentTestCase);
501: assertTrue("preTrigger has already fired",
502: test.currentTestCase.preTriggerFired);
503: assertFalse("postTrigger has not yet fired",
504: test.currentTestCase.postTriggerFired);
505: assertFalse("publish has not been called",
506: test.currentTestCase.published);
507:
508: //test event data
509: assertSameArtifact(
510: "publisher has received correct artifact",
511: test.currentTestCase.expectedArtifact, artifact);
512: assertEquals("publisher has received correct datafile",
513: test.currentTestCase.expectedData
514: .getCanonicalPath(), src.getCanonicalPath());
515: assertEquals(
516: "publisher has received correct overwrite setting",
517: test.expectedOverwrite, overwrite);
518: assertTrue(
519: "publisher only invoked when source file exists",
520: test.currentTestCase.expectedData.exists());
521:
522: //simulate a publisher error if the current test case demands it.
523: if (test.publishError != null)
524: throw test.publishError;
525:
526: //all assertions pass. increment the publication count
527: test.currentTestCase.published = true;
528: ++test.publications;
529: }
530: }
531:
532: }
|