001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.errorhandling;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertNotNull;
022import static org.junit.Assert.assertTrue;
023
024import java.io.IOException;
025import java.util.Objects;
026
027import org.apache.hadoop.hbase.HBaseClassTestRule;
028import org.apache.hadoop.hbase.testclassification.MasterTests;
029import org.apache.hadoop.hbase.testclassification.SmallTests;
030import org.junit.ClassRule;
031import org.junit.Test;
032import org.junit.experimental.categories.Category;
033
034/**
035 * Test that we correctly serialize exceptions from a remote source
036 */
037@Category({MasterTests.class, SmallTests.class})
038public class TestForeignExceptionSerialization {
039
040  @ClassRule
041  public static final HBaseClassTestRule CLASS_RULE =
042      HBaseClassTestRule.forClass(TestForeignExceptionSerialization.class);
043
044  private static final String srcName = "someNode";
045
046  /**
047   * Verify that we get back similar stack trace information before an after serialization.
048   */
049  @Test
050  public void testSimpleException() throws IOException {
051    String data = "some bytes";
052    ForeignException in = new ForeignException("SRC", new IllegalArgumentException(data));
053    // check that we get the data back out
054    ForeignException e = ForeignException.deserialize(ForeignException.serialize(srcName, in));
055    assertNotNull(e);
056
057    // now check that we get the right stack trace
058    StackTraceElement elem = new StackTraceElement(this.getClass().toString(), "method", "file", 1);
059    in.setStackTrace(new StackTraceElement[] { elem });
060    e = ForeignException.deserialize(ForeignException.serialize(srcName, in));
061
062    assertNotNull(e);
063    assertEquals("Stack trace got corrupted", elem, e.getCause().getStackTrace()[0]);
064    assertEquals("Got an unexpectedly long stack trace", 1, e.getCause().getStackTrace().length);
065  }
066
067  /**
068   * Compare that a generic exception's stack trace has the same stack trace elements after
069   * serialization and deserialization
070   */
071  @Test
072  public void testRemoteFromLocal() throws IOException {
073    String errorMsg = "some message";
074    Exception generic = new Exception(errorMsg);
075    generic.printStackTrace();
076    assertTrue(generic.getMessage().contains(errorMsg));
077
078    ForeignException e = ForeignException.deserialize(ForeignException.serialize(srcName, generic));
079
080    // Workaround for java 11 - replaced assertArrayEquals with individual elements comparison
081    // using custom comparison helper method
082    assertEquals("Stacktrace lengths don't match", generic.getStackTrace().length,
083        e.getCause().getStackTrace().length);
084    for (int i = 0; i < generic.getStackTrace().length; i++) {
085      assertTrue("Local stack trace got corrupted at " + i + "th index",
086          compareStackTraceElement(generic.getStackTrace()[i], e.getCause().getStackTrace()[i]));
087    }
088
089    e.printStackTrace(); // should have ForeignException and source node in it.
090    assertTrue(e.getCause().getCause() == null);
091
092    // verify that original error message is present in Foreign exception message
093    assertTrue(e.getCause().getMessage().contains(errorMsg));
094  }
095
096  // Helper method to compare two stackTraceElements
097  private boolean compareStackTraceElement(StackTraceElement obj1, StackTraceElement obj2) {
098    return obj1.getClassName().equals(obj2.getClassName()) && obj1.getLineNumber() == obj2
099        .getLineNumber() && Objects.equals(obj1.getMethodName(), obj2.getMethodName()) && Objects
100        .equals(obj1.getFileName(), obj2.getFileName());
101  }
102}