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.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertNotNull;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023
024import java.io.IOException;
025import java.util.Objects;
026import org.apache.hadoop.hbase.testclassification.MasterTests;
027import org.apache.hadoop.hbase.testclassification.SmallTests;
028import org.junit.jupiter.api.Tag;
029import org.junit.jupiter.api.Test;
030
031/**
032 * Test that we correctly serialize exceptions from a remote source
033 */
034@Tag(MasterTests.TAG)
035@Tag(SmallTests.TAG)
036public class TestForeignExceptionSerialization {
037
038  private static final String srcName = "someNode";
039
040  /**
041   * Verify that we get back similar stack trace information before an after serialization.
042   */
043  @Test
044  public void testSimpleException() throws IOException {
045    String data = "some bytes";
046    ForeignException in = new ForeignException("SRC", new IllegalArgumentException(data));
047    // check that we get the data back out
048    ForeignException e = ForeignException.deserialize(ForeignException.serialize(srcName, in));
049    assertNotNull(e);
050
051    // now check that we get the right stack trace
052    StackTraceElement elem = new StackTraceElement(this.getClass().toString(), "method", "file", 1);
053    in.setStackTrace(new StackTraceElement[] { elem });
054    e = ForeignException.deserialize(ForeignException.serialize(srcName, in));
055
056    assertNotNull(e);
057    assertEquals(elem, e.getCause().getStackTrace()[0], "Stack trace got corrupted");
058    assertEquals(1, e.getCause().getStackTrace().length, "Got an unexpectedly long stack trace");
059  }
060
061  /**
062   * Compare that a generic exception's stack trace has the same stack trace elements after
063   * serialization and deserialization
064   */
065  @Test
066  public void testRemoteFromLocal() throws IOException {
067    String errorMsg = "some message";
068    Exception generic = new Exception(errorMsg);
069    generic.printStackTrace();
070    assertTrue(generic.getMessage().contains(errorMsg));
071
072    ForeignException e = ForeignException.deserialize(ForeignException.serialize(srcName, generic));
073
074    // Workaround for java 11 - replaced assertArrayEquals with individual elements comparison
075    // using custom comparison helper method
076    assertEquals(generic.getStackTrace().length, e.getCause().getStackTrace().length,
077      "Stacktrace lengths don't match");
078    for (int i = 0; i < generic.getStackTrace().length; i++) {
079      assertTrue(
080        compareStackTraceElement(generic.getStackTrace()[i], e.getCause().getStackTrace()[i]),
081        "Local stack trace got corrupted at " + i + "th index");
082    }
083
084    e.printStackTrace(); // should have ForeignException and source node in it.
085    assertTrue(e.getCause().getCause() == null);
086
087    // verify that original error message is present in Foreign exception message
088    assertTrue(e.getCause().getMessage().contains(errorMsg));
089  }
090
091  // Helper method to compare two stackTraceElements
092  private boolean compareStackTraceElement(StackTraceElement obj1, StackTraceElement obj2) {
093    return obj1.getClassName().equals(obj2.getClassName())
094      && obj1.getLineNumber() == obj2.getLineNumber()
095      && Objects.equals(obj1.getMethodName(), obj2.getMethodName())
096      && Objects.equals(obj1.getFileName(), obj2.getFileName());
097  }
098}