/* * The MIT License * * Copyright (c) 2004-2010, Sun Microsystems, 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.util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import hudson.XmlFile; import hudson.matrix.MatrixRun; import hudson.model.Result; import hudson.model.Run; import java.io.File; import java.io.InputStream; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.CauseOfInterruption; import jenkins.model.InterruptedBuildAction; import junit.framework.TestCase; import org.apache.commons.io.FileUtils; import org.jvnet.hudson.test.Bug; /** * Tests for XML serialization of java objects. * @author Kohsuke Kawaguchi, Mike Dillon, Alan Harder, Richard Mortimer */ public class XStream2Test extends TestCase { public static final class Foo { Result r1,r2; } public void testMarshalValue() { Foo f = new Foo(); f.r1 = f.r2 = Result.FAILURE; String xml = Run.XSTREAM.toXML(f); // we should find two "FAILURE"s as they should be written out twice assertEquals(xml, 3, xml.split("FAILURE").length); } private static class Bar { String s; } /** * Test ability to read old XML from Hudson 1.105 or older. */ public void testXStream11Compatibility() { Bar b = (Bar)new XStream2().fromXML( "foo"); assertEquals("foo", b.s); } public static final class __Foo_Bar$Class { String under_1 = "1", under__2 = "2", _leadUnder1 = "L1", __leadUnder2 = "L2", $dollar = "D1", dollar$2 = "D2"; } /** * Test marshal/unmarshal round trip for class/field names with _ and $ characters. * (HUDSON-5768) */ public void testXmlRoundTrip() { XStream2 xs = new XStream2(); __Foo_Bar$Class b = new __Foo_Bar$Class(); String xml = xs.toXML(b); __Foo_Bar$Class b2 = (__Foo_Bar$Class)xs.fromXML(xml); assertEquals(xml, b.under_1, b2.under_1); assertEquals(xml, b.under__2, b2.under__2); assertEquals(xml, b._leadUnder1, b2._leadUnder1); assertEquals(xml, b.__leadUnder2, b2.__leadUnder2); assertEquals(xml, b.$dollar, b2.$dollar); assertEquals(xml, b.dollar$2, b2.dollar$2); } private static class Baz { private Exception myFailure; } /** * Verify RobustReflectionConverter can handle missing fields in a class extending * Throwable/Exception (default ThrowableConverter registered by XStream calls * ReflectionConverter directly, rather than our RobustReflectionConverter replacement). * (HUDSON-5769) */ public void testUnmarshalThrowableMissingField() { Level oldLevel = disableLogging(); Baz baz = new Baz(); baz.myFailure = new Exception("foo"); XStream2 xs = new XStream2(); String xml = xs.toXML(baz); baz = (Baz)xs.fromXML(xml); assertEquals("foo", baz.myFailure.getMessage()); baz = (Baz)xs.fromXML("" + "true" + "hoho" + "" + "hudson.util.XStream2Test.testUnmarshalThrowableMissingField(XStream2Test.java:97)" + "" + ""); // Object should load, despite "missingField" in XML above assertEquals("hoho", baz.myFailure.getMessage()); enableLogging(oldLevel); } private Level disableLogging() { Level oldLevel = Logger.getLogger(RobustReflectionConverter.class.getName()).getLevel(); Logger.getLogger(RobustReflectionConverter.class.getName()).setLevel(Level.OFF); return oldLevel; } private void enableLogging(Level oldLevel) { Logger.getLogger(RobustReflectionConverter.class.getName()).setLevel(oldLevel); } private static class ImmutableMapHolder { ImmutableMap m; } private static class MapHolder { Map m; } public void testImmutableMap() { XStream2 xs = new XStream2(); roundtripImmutableMap(xs, ImmutableMap.of()); roundtripImmutableMap(xs, ImmutableMap.of("abc", "xyz")); roundtripImmutableMap(xs, ImmutableMap.of("abc", "xyz", "def","ghi")); roundtripImmutableMapAsPlainMap(xs, ImmutableMap.of()); roundtripImmutableMapAsPlainMap(xs, ImmutableMap.of("abc", "xyz")); roundtripImmutableMapAsPlainMap(xs, ImmutableMap.of("abc", "xyz", "def","ghi")); } /** * Since the field type is {@link ImmutableMap}, XML shouldn't contain a reference to the type name. */ private void roundtripImmutableMap(XStream2 xs, ImmutableMap m) { ImmutableMapHolder a = new ImmutableMapHolder(); a.m = m; String xml = xs.toXML(a); //System.out.println(xml); assertFalse("shouldn't contain the class name",xml.contains("google")); assertFalse("shouldn't contain the class name",xml.contains("class")); a = (ImmutableMapHolder)xs.fromXML(xml); assertSame(m.getClass(),a.m.getClass()); // should get back the exact same type, not just a random map assertEquals(m,a.m); } private void roundtripImmutableMapAsPlainMap(XStream2 xs, ImmutableMap m) { MapHolder a = new MapHolder(); a.m = m; String xml = xs.toXML(a); //System.out.println(xml); assertTrue("XML should mention the class name",xml.contains('\"'+ImmutableMap.class.getName()+'\"')); a = (MapHolder)xs.fromXML(xml); assertSame(m.getClass(),a.m.getClass()); // should get back the exact same type, not just a random map assertEquals(m,a.m); } private static class ImmutableListHolder { ImmutableList l; } private static class ListHolder { List l; } public void testImmutableList() { XStream2 xs = new XStream2(); roundtripImmutableList(xs, ImmutableList.of()); roundtripImmutableList(xs, ImmutableList.of("abc")); roundtripImmutableList(xs, ImmutableList.of("abc", "def")); roundtripImmutableListAsPlainList(xs, ImmutableList.of()); roundtripImmutableListAsPlainList(xs, ImmutableList.of("abc")); roundtripImmutableListAsPlainList(xs, ImmutableList.of("abc", "def")); } /** * Since the field type is {@link ImmutableList}, XML shouldn't contain a reference to the type name. */ private void roundtripImmutableList(XStream2 xs, ImmutableList l) { ImmutableListHolder a = new ImmutableListHolder(); a.l = l; String xml = xs.toXML(a); //System.out.println(xml); assertFalse("shouldn't contain the class name",xml.contains("google")); assertFalse("shouldn't contain the class name",xml.contains("class")); a = (ImmutableListHolder)xs.fromXML(xml); assertSame(l.getClass(),a.l.getClass()); // should get back the exact same type, not just a random list assertEquals(l,a.l); } private void roundtripImmutableListAsPlainList(XStream2 xs, ImmutableList l) { ListHolder a = new ListHolder(); a.l = l; String xml = xs.toXML(a); //System.out.println(xml); assertTrue("XML should mention the class name",xml.contains('\"'+ImmutableList.class.getName()+'\"')); a = (ListHolder)xs.fromXML(xml); assertSame(l.getClass(),a.l.getClass()); // should get back the exact same type, not just a random list assertEquals(l,a.l); } @Bug(8006) // Previously a null entry in an array caused NPE public void testEmptyStack() { assertEquals("", Run.XSTREAM.toXML(new Object[2]).replaceAll("[ \n\r\t]+", "")); } @Bug(9843) public void testCompatibilityAlias() { XStream2 xs = new XStream2(); xs.addCompatibilityAlias("legacy.Point",Point.class); Point pt = (Point)xs.fromXML("12"); assertEquals(1,pt.x); assertEquals(2,pt.y); String xml = xs.toXML(pt); //System.out.println(xml); assertFalse("Shouldn't use the alias when writing back",xml.contains("legacy")); } public static class Point { public int x,y; } /** * Unmarshall a matrix build.xml result. */ @Bug(10903) public void testUnMarshalRunMatrix() { InputStream is = XStream2Test.class.getResourceAsStream("runMatrix.xml"); MatrixRun result = (MatrixRun) Run.XSTREAM.fromXML(is); assertNotNull(result); assertNotNull(result.getPersistentActions()); assertEquals(2, result.getPersistentActions().size()); InterruptedBuildAction action = (InterruptedBuildAction) result.getPersistentActions().get(1); assertNotNull(action.getCauses()); assertEquals(1, action.getCauses().size()); CauseOfInterruption.UserInterruption cause = (CauseOfInterruption.UserInterruption) action.getCauses().get(0); assertNotNull(cause); } public static class Foo2 { ConcurrentHashMap m = new ConcurrentHashMap(); } /** * Tests that ConcurrentHashMap is serialized into a more compact format, * but still can deserialize to older, verbose format. */ public void testConcurrentHashMapSerialization() throws Exception { Foo2 foo = new Foo2(); foo.m.put("abc","def"); foo.m.put("ghi","jkl"); File v = File.createTempFile("hashmap", "xml"); try { new XmlFile(v).write(foo); // should serialize like map String xml = FileUtils.readFileToString(v); assertFalse(xml.contains("java.util.concurrent")); //System.out.println(xml); Foo2 deserialized = (Foo2) new XStream2().fromXML(xml); assertEquals(2,deserialized.m.size()); assertEquals("def", deserialized.m.get("abc")); assertEquals("jkl", deserialized.m.get("ghi")); } finally { v.delete(); } // should be able to read in old data just fine Foo2 map = (Foo2) new XStream2().fromXML(getClass().getResourceAsStream("old-concurrentHashMap.xml")); assertEquals(1,map.m.size()); assertEquals("def",map.m.get("abc")); } }