/*
* 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.flink.runtime.util;
import org.apache.flink.core.memory.MemoryUtils;
import org.apache.flink.core.testutils.CommonTestUtils;
import org.apache.flink.util.ExceptionUtils;
import org.apache.flink.util.InstantiationUtil;
import org.junit.Test;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import static org.junit.Assert.*;
public class SerializedThrowableTest {
@Test
public void testIdenticalMessageAndStack() {
try {
IllegalArgumentException original = new IllegalArgumentException("test message");
SerializedThrowable serialized = new SerializedThrowable(original);
assertEquals(original.getMessage(), serialized.getMessage());
assertEquals(original.toString(), serialized.toString());
assertEquals(ExceptionUtils.stringifyException(original),
ExceptionUtils.stringifyException(serialized));
assertArrayEquals(original.getStackTrace(), serialized.getStackTrace());
}
catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
@Test
public void testSerialization() {
try {
// We need an exception whose class is not in the core class loader
// we solve that by defining an exception class dynamically
// an exception class, as bytes
final byte[] classData = {
-54, -2, -70, -66, 0, 0, 0, 51, 0, 21, 10, 0, 3, 0, 18, 7, 0, 19, 7, 0, 20, 1,
0, 16, 115, 101, 114, 105, 97, 108, 86, 101, 114, 115, 105, 111, 110, 85, 73,
68, 1, 0, 1, 74, 1, 0, 13, 67, 111, 110, 115, 116, 97, 110, 116, 86, 97, 108,
117, 101, 5, -103, -52, 22, -41, -23, -36, -25, 47, 1, 0, 6, 60, 105, 110, 105,
116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105,
110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111,
99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4,
116, 104, 105, 115, 1, 0, 61, 76, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101,
47, 102, 108, 105, 110, 107, 47, 114, 117, 110, 116, 105, 109, 101, 47, 117,
116, 105, 108, 47, 84, 101, 115, 116, 69, 120, 99, 101, 112, 116, 105, 111,
110, 70, 111, 114, 83, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111,
110, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 34, 84,
101, 115, 116, 69, 120, 99, 101, 112, 116, 105, 111, 110, 70, 111, 114, 83,
101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 46, 106, 97, 118, 97,
12, 0, 9, 0, 10, 1, 0, 59, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47,
102, 108, 105, 110, 107, 47, 114, 117, 110, 116, 105, 109, 101, 47, 117, 116,
105, 108, 47, 84, 101, 115, 116, 69, 120, 99, 101, 112, 116, 105, 111, 110, 70,
111, 114, 83, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 1, 0,
19, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 69, 120, 99, 101, 112, 116,
105, 111, 110, 0, 33, 0, 2, 0, 3, 0, 0, 0, 1, 0, 26, 0, 4, 0, 5, 0, 1, 0, 6, 0,
0, 0, 2, 0, 7, 0, 1, 0, 1, 0, 9, 0, 10, 0, 1, 0, 11, 0, 0, 0, 47, 0, 1, 0, 1, 0,
0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 2, 0, 12, 0, 0, 0, 6, 0, 1, 0, 0, 0, 21,
0, 13, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 14, 0, 15, 0, 0, 0, 1, 0, 16, 0, 0, 0,
2, 0, 17};
// dummy class loader that has no access to any classes
ClassLoader loader = new URLClassLoader(new URL[0]);
// define a class into the classloader
Class<?> clazz = MemoryUtils.UNSAFE.defineClass(
"org.apache.flink.runtime.util.TestExceptionForSerialization",
classData, 0, classData.length,
loader,
new ProtectionDomain(new CodeSource(null, (Certificate[]) null), new Permissions()));
// create an instance of the exception (no message, no cause)
Exception userException = clazz.asSubclass(Exception.class).newInstance();
// check that we cannot simply copy the exception
try {
byte[] serialized = InstantiationUtil.serializeObject(userException);
InstantiationUtil.deserializeObject(serialized, getClass().getClassLoader());
fail("should fail with a class not found exception");
}
catch (ClassNotFoundException e) {
// as we want it
}
// validate that the SerializedThrowable mimics the original exception
SerializedThrowable serialized = new SerializedThrowable(userException);
assertEquals(userException.getMessage(), serialized.getMessage());
assertEquals(userException.toString(), serialized.toString());
assertEquals(ExceptionUtils.stringifyException(userException),
ExceptionUtils.stringifyException(serialized));
assertArrayEquals(userException.getStackTrace(), serialized.getStackTrace());
// copy the serialized throwable and make sure everything still works
SerializedThrowable copy = CommonTestUtils.createCopySerializable(serialized);
assertEquals(userException.getMessage(), copy.getMessage());
assertEquals(userException.toString(), copy.toString());
assertEquals(ExceptionUtils.stringifyException(userException),
ExceptionUtils.stringifyException(copy));
assertArrayEquals(userException.getStackTrace(), copy.getStackTrace());
// deserialize the proper exception
Throwable deserialized = copy.deserializeError(loader);
assertEquals(clazz, deserialized.getClass());
// deserialization with the wrong classloader does not lead to a failure
Throwable wronglyDeserialized = copy.deserializeError(getClass().getClassLoader());
assertEquals(ExceptionUtils.stringifyException(userException),
ExceptionUtils.stringifyException(wronglyDeserialized));
}
catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
@Test
public void testCauseChaining() {
Exception cause2 = new Exception("level2");
Exception cause1 = new Exception("level1", cause2);
Exception root = new Exception("level0", cause1);
SerializedThrowable st = new SerializedThrowable(root);
assertEquals("level0", st.getMessage());
assertNotNull(st.getCause());
assertEquals("level1", st.getCause().getMessage());
assertNotNull(st.getCause().getCause());
assertEquals("level2", st.getCause().getCause().getMessage());
}
@Test
public void testCyclicCauseChaining() {
Exception cause3 = new Exception("level3");
Exception cause2 = new Exception("level2", cause3);
Exception cause1 = new Exception("level1", cause2);
Exception root = new Exception("level0", cause1);
// introduce a cyclic reference
cause3.initCause(cause1);
SerializedThrowable st = new SerializedThrowable(root);
assertArrayEquals(root.getStackTrace(), st.getStackTrace());
assertEquals(ExceptionUtils.stringifyException(root), ExceptionUtils.stringifyException(st));
}
@Test
public void testCopyPreservesCause() {
Exception original = new Exception("original message");
Exception parent = new Exception("parent message", original);
SerializedThrowable serialized = new SerializedThrowable(parent);
assertNotNull(serialized.getCause());
SerializedThrowable copy = new SerializedThrowable(serialized);
assertEquals("parent message", copy.getMessage());
assertNotNull(copy.getCause());
assertEquals("original message", copy.getCause().getMessage());
}
}