/** * 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.hadoop.io; import org.apache.commons.lang.RandomStringUtils; import org.junit.Test; import java.io.DataOutput; import java.io.IOException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class TestCompatibleWritable { private static final String TEST_LONG_STRING = RandomStringUtils.random(2 * 1024, false, true); @Test(timeout = 1000) public void test0to0() throws Exception { TypeVersion0 a = new TypeVersion0(); TypeVersion0 b = new TypeVersion0(); checkedCloneInto(a, b); assertFalse(a.getBufferCalled); assertFalse(b.getBufferCalled); } @Test(timeout = 1000) public void test0to1() throws Exception { TypeVersion0 a = new TypeVersion0(); TypeVersion1 b = new TypeVersion1(); checkedCloneInto(a, b); assertTrue(b.assignedDefaults1); assertFalse(a.getBufferCalled); assertFalse(b.getBufferCalled); } @Test(timeout = 1000) public void test0to2() throws Exception { TypeVersion0 a = new TypeVersion0(); TypeVersion2 b = new TypeVersion2(); checkedCloneInto(a, b); assertTrue(b.assignedDefaults1); assertTrue(b.assignedDefaults2); assertFalse(a.getBufferCalled); assertFalse(b.getBufferCalled); } @Test(timeout = 1000) public void test1to0() throws Exception { TypeVersion1 a = new TypeVersion1(); TypeVersion0 b = new TypeVersion0(); checkedCloneInto(a, b); assertFalse(a.assignedDefaults1); assertTrue(a.getBufferCalled); assertTrue(b.getBufferCalled); } @Test(timeout = 1000) public void test1to1() throws Exception { TypeVersion1 a = new TypeVersion1(); TypeVersion1 b = new TypeVersion1(); checkedCloneInto(a, b); assertFalse(a.assignedDefaults1); assertFalse(b.assignedDefaults1); assertTrue(a.getBufferCalled); assertFalse(b.getBufferCalled); } @Test(timeout = 1000) public void test1to2() throws Exception { TypeVersion1 a = new TypeVersion1(); TypeVersion2 b = new TypeVersion2(); checkedCloneInto(a, b); assertFalse(a.assignedDefaults1); assertFalse(b.assignedDefaults1); assertTrue(b.assignedDefaults2); assertTrue(a.getBufferCalled); assertFalse(b.getBufferCalled); } @Test(timeout = 1000) public void test2to0() throws Exception { TypeVersion2 a = new TypeVersion2(); TypeVersion0 b = new TypeVersion0(); checkedCloneInto(a, b); assertFalse(a.assignedDefaults1); assertFalse(a.assignedDefaults2); assertTrue(a.getBufferCalled); assertTrue(b.getBufferCalled); assertTrue(a.getBufferCalled); assertTrue(b.getBufferCalled); } @Test(timeout = 1000) public void test2to1() throws Exception { TypeVersion2 a = new TypeVersion2(); TypeVersion1 b = new TypeVersion1(); checkedCloneInto(a, b); assertFalse(a.assignedDefaults1); assertFalse(a.assignedDefaults2); assertFalse(b.assignedDefaults1); assertTrue(a.getBufferCalled); assertTrue(b.getBufferCalled); } @Test(timeout = 1000) public void test2to2() throws Exception { TypeVersion2 a = new TypeVersion2(); TypeVersion2 b = new TypeVersion2(); checkedCloneInto(a, b); assertFalse(a.assignedDefaults1); assertFalse(a.assignedDefaults2); assertFalse(b.assignedDefaults1); assertFalse(b.assignedDefaults2); assertTrue(a.getBufferCalled); assertFalse(b.getBufferCalled); } @Test public void test3to3() throws Exception { TypeVersion0Enclosing a = new TypeVersion0Enclosing(); TypeVersion0Enclosing b = new TypeVersion0Enclosing(); checkedCloneInto(a, b); } private void checkedCloneInto(Writable src, Writable dst) throws IOException { DataOutputBuffer outBuffer = new DataOutputBuffer(); DataInputBuffer inBuffer = new DataInputBuffer(); outBuffer.reset(); src.write(outBuffer); inBuffer.reset(outBuffer.getData(), outBuffer.getLength()); dst.readFields(inBuffer); assertEquals(inBuffer.getLength(), inBuffer.getPosition()); assertFalse(inBuffer.canReadNextVersion()); } //////////////////////////////////////////////// // Example versioned classes //////////////////////////////////////////////// /** This is an initial version - a base class. */ private static class TypeVersion0 extends CompatibleWritable { public final String FIELDS0 = "0123"; /** Not serialized */ public boolean getBufferCalled = false; @Override protected int getVersion() { return INITIAL_VERSION; } @Override protected byte[] getBuffer(int size) { getBufferCalled = true; return super.getBuffer(size); } @Override protected void writeCompatible(DataOutput out) throws IOException { Text.writeStringOpt(out, FIELDS0); } @Override protected void readCompatible(CompatibleDataInput in) throws IOException { assertEquals(FIELDS0, Text.readStringOpt(in)); } } /** This is an extended version, which adds a few fields. */ private static class TypeVersion1 extends TypeVersion0 { public final String FIELDS1 = "45678"; public final String FIELDS1extra = "fawefsd"; public boolean assignedDefaults1 = false; @Override protected int getVersion() { // Consecutive versions must increment this by one. // This could be super.getVersion() + 1, but it's not for efficiency reasons. return INITIAL_VERSION + 1; } @Override protected void writeCompatible(DataOutput out) throws IOException { // Just like in {@link Writable}, first serialize base class (previous versions). super.writeCompatible(out); Text.writeStringOpt(out, FIELDS1); Text.writeStringOpt(out, FIELDS1extra); } @Override protected void readCompatible(CompatibleDataInput in) throws IOException { super.readCompatible(in); // This call is not a pure function (predicate in this case), // call only once per version increment. if (in.canReadNextVersion()) { assertEquals(FIELDS1, Text.readStringOpt(in)); assertEquals(FIELDS1extra, Text.readStringOpt(in)); } else { assignedDefaults1 = true; } } } private static class TypeVersion2 extends TypeVersion1 { public final String FIELDS2 = "9AB"; public TypeVersion0Enclosing enclosing = new TypeVersion0Enclosing(); public boolean assignedDefaults2 = false; @Override protected int getVersion() { return INITIAL_VERSION + 2; } @Override protected void writeCompatible(DataOutput out) throws IOException { super.writeCompatible(out); Text.writeStringOpt(out, FIELDS2); enclosing.write(out); } @Override protected void readCompatible(CompatibleDataInput in) throws IOException { super.readCompatible(in); if (in.canReadNextVersion()) { assertEquals(FIELDS2, Text.readStringOpt(in)); enclosing.readFields(in); } else { assignedDefaults2 = true; } } } private static class TypeVersion0Enclosing extends CompatibleWritable { public final String FIELDS = "zxcvcvx"; @Override protected int getVersion() { return INITIAL_VERSION; } @Override protected void writeCompatible(DataOutput out) throws IOException { Text.writeStringOpt(out, FIELDS); } @Override protected void readCompatible(CompatibleDataInput in) throws IOException { assertEquals(FIELDS, Text.readStringOpt(in)); assertFalse(in.canReadNextVersion()); } } }