|
@@ -1,59 +1,119 @@
|
|
|
package org.cryptomator.common;
|
|
|
|
|
|
+import com.google.common.base.CharMatcher;
|
|
|
+import com.google.common.base.Splitter;
|
|
|
import org.junit.jupiter.api.Assertions;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
import org.junit.jupiter.api.DisplayName;
|
|
|
import org.junit.jupiter.api.Nested;
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
+import org.junit.jupiter.params.ParameterizedTest;
|
|
|
+import org.junit.jupiter.params.converter.ConvertWith;
|
|
|
+import org.junit.jupiter.params.converter.SimpleArgumentConverter;
|
|
|
+import org.junit.jupiter.params.provider.CsvSource;
|
|
|
+import org.mockito.Mockito;
|
|
|
|
|
|
public class ErrorCodeTest {
|
|
|
|
|
|
- private static ErrorCode codeCaughtFrom(RunnableThrowingException<RuntimeException> runnable) {
|
|
|
- try {
|
|
|
- runnable.run();
|
|
|
- throw new IllegalStateException("should not reach this point");
|
|
|
- } catch (RuntimeException e) {
|
|
|
- return ErrorCode.of(e);
|
|
|
- }
|
|
|
- }
|
|
|
+ private final StackTraceElement foo = new StackTraceElement("ErrorCodeTest", "foo", null, 0);
|
|
|
+ private final StackTraceElement bar = new StackTraceElement("ErrorCodeTest", "bar", null, 0);
|
|
|
+ private final StackTraceElement baz = new StackTraceElement("ErrorCodeTest", "baz", null, 0);
|
|
|
+ private final Exception fooException = Mockito.mock(NullPointerException.class, "fooException");
|
|
|
|
|
|
@Test
|
|
|
@DisplayName("same exception leads to same error code")
|
|
|
- public void testDifferentErrorCodes() {
|
|
|
- var code1 = codeCaughtFrom(this::throwNpe);
|
|
|
- var code2 = codeCaughtFrom(this::throwNpe);
|
|
|
+ public void testDeterministicErrorCode() {
|
|
|
+ Mockito.doReturn(new StackTraceElement[]{foo, bar, baz}).when(fooException).getStackTrace();
|
|
|
+ var code1 = ErrorCode.of(fooException);
|
|
|
+ var code2 = ErrorCode.of(fooException);
|
|
|
|
|
|
Assertions.assertEquals(code1.toString(), code2.toString());
|
|
|
}
|
|
|
|
|
|
- private void throwNpe() {
|
|
|
- throwException(new NullPointerException());
|
|
|
+ @Test
|
|
|
+ @DisplayName("three error code segments change independently")
|
|
|
+ public void testErrorCodeSegments() {
|
|
|
+ Exception fooBarException = Mockito.mock(IndexOutOfBoundsException.class, "fooBarException");
|
|
|
+ Mockito.doReturn(new StackTraceElement[]{foo, foo, foo}).when(fooBarException).getStackTrace();
|
|
|
+ Mockito.doReturn(fooException).when(fooBarException).getCause();
|
|
|
+ Mockito.doReturn(new StackTraceElement[]{bar, bar, bar, foo, foo, foo}).when(fooException).getStackTrace();
|
|
|
+
|
|
|
+ var code = ErrorCode.of(fooBarException);
|
|
|
+
|
|
|
+ Assertions.assertNotEquals(code.throwableCode(), code.rootCauseCode());
|
|
|
+ Assertions.assertNotEquals(code.rootCauseCode(), code.methodCode());
|
|
|
}
|
|
|
|
|
|
- private void throwException(RuntimeException e) throws RuntimeException {
|
|
|
- throw e;
|
|
|
+ @DisplayName("commonSuffixLength()")
|
|
|
+ @ParameterizedTest
|
|
|
+ @CsvSource({"1 2 3, 1 2 3, 3", "1 2 3, 0 2 3, 2", "1 2 3 4, 3 4, 2", "1 2 3 4, 5 6, 0", "1 2 3 4 5 6,, 0",})
|
|
|
+ public void commonSuffixLength1(@ConvertWith(IntegerArrayConverter.class) Integer[] set, @ConvertWith(IntegerArrayConverter.class) Integer[] subset, int expected) {
|
|
|
+ var result = ErrorCode.commonSuffixLength(set, subset);
|
|
|
+
|
|
|
+ Assertions.assertEquals(expected, result);
|
|
|
}
|
|
|
|
|
|
- @DisplayName("when different cause but same root cause")
|
|
|
- @Nested
|
|
|
- public class SameRootCauseDifferentCause {
|
|
|
+ @DisplayName("commonSuffixLength() with too short array")
|
|
|
+ @ParameterizedTest
|
|
|
+ @CsvSource({"1 2, 3 4 5 6", ",1 2 3 4 5 6",})
|
|
|
+ public void commonSuffixLength2(@ConvertWith(IntegerArrayConverter.class) Integer[] set, @ConvertWith(IntegerArrayConverter.class) Integer[] subset) {
|
|
|
+ Assertions.assertThrows(IllegalArgumentException.class, () -> {
|
|
|
+ ErrorCode.commonSuffixLength(set, subset);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("countTopmostFrames() with partially overlapping suffix")
|
|
|
+ public void testCountTopmostFrames1() {
|
|
|
+ var allFrames = new StackTraceElement[]{foo, bar, baz, bar, foo};
|
|
|
+ var bottomFrames = new StackTraceElement[]{baz, bar, foo};
|
|
|
|
|
|
- private final ErrorCode code1 = codeCaughtFrom(this::foo);
|
|
|
- private final ErrorCode code2 = codeCaughtFrom(this::bar);
|
|
|
+ int result = ErrorCode.countTopmostFrames(allFrames, bottomFrames);
|
|
|
|
|
|
- private void foo() throws IllegalArgumentException {
|
|
|
- try {
|
|
|
- throwNpe();
|
|
|
- } catch (NullPointerException e) {
|
|
|
- throw new IllegalArgumentException(e);
|
|
|
- }
|
|
|
- }
|
|
|
+ Assertions.assertEquals(2, result);
|
|
|
+ }
|
|
|
|
|
|
- private void bar() throws IllegalStateException {
|
|
|
- try {
|
|
|
- throwNpe();
|
|
|
- } catch (NullPointerException e) {
|
|
|
- throw new IllegalStateException(e);
|
|
|
- }
|
|
|
+ @Test
|
|
|
+ @DisplayName("countTopmostFrames() without overlapping suffix")
|
|
|
+ public void testCountTopmostFrames2() {
|
|
|
+ var allFrames = new StackTraceElement[]{foo, foo, foo};
|
|
|
+ var bottomFrames = new StackTraceElement[]{bar, bar, bar};
|
|
|
+
|
|
|
+ int result = ErrorCode.countTopmostFrames(allFrames, bottomFrames);
|
|
|
+
|
|
|
+ Assertions.assertEquals(3, result);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("countUniqueFrames() fully overlapping")
|
|
|
+ public void testCountUniqueFrames3() {
|
|
|
+ var allFrames = new StackTraceElement[]{foo, bar, baz};
|
|
|
+ var bottomFrames = new StackTraceElement[]{foo, bar, baz};
|
|
|
+
|
|
|
+ int result = ErrorCode.countTopmostFrames(allFrames, bottomFrames);
|
|
|
+
|
|
|
+ Assertions.assertEquals(0, result);
|
|
|
+ }
|
|
|
+
|
|
|
+ @DisplayName("when different exception with same root cause")
|
|
|
+ @Nested
|
|
|
+ public class DifferentExceptionWithSameRootCause {
|
|
|
+
|
|
|
+ private final Exception fooBarException = Mockito.mock(IllegalArgumentException.class, "fooBarException");
|
|
|
+ private final Exception fooBazException = Mockito.mock(IndexOutOfBoundsException.class, "fooBazException");
|
|
|
+
|
|
|
+ private ErrorCode code1;
|
|
|
+ private ErrorCode code2;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ private void setup() {
|
|
|
+ Mockito.doReturn(new StackTraceElement[]{baz, bar, foo}).when(fooException).getStackTrace();
|
|
|
+ Mockito.doReturn(new StackTraceElement[]{foo}).when(fooBarException).getStackTrace();
|
|
|
+ Mockito.doReturn(new StackTraceElement[]{foo}).when(fooBazException).getStackTrace();
|
|
|
+ Mockito.doReturn(fooException).when(fooBarException).getCause();
|
|
|
+ Mockito.doReturn(fooException).when(fooBazException).getCause();
|
|
|
+ this.code1 = ErrorCode.of(fooBarException);
|
|
|
+ this.code2 = ErrorCode.of(fooBazException);
|
|
|
}
|
|
|
|
|
|
@Test
|
|
@@ -82,23 +142,21 @@ public class ErrorCodeTest {
|
|
|
|
|
|
}
|
|
|
|
|
|
- @DisplayName("when same cause but different call stack")
|
|
|
+ @DisplayName("when same exception with different call stacks")
|
|
|
@Nested
|
|
|
- public class SameCauseDifferentCallStack {
|
|
|
+ public class SameExceptionDifferentCallStack {
|
|
|
|
|
|
- private final ErrorCode code1 = codeCaughtFrom(this::foo);
|
|
|
- private final ErrorCode code2 = codeCaughtFrom(this::bar);
|
|
|
+ private final Exception barException = Mockito.mock(NullPointerException.class, "barException");
|
|
|
|
|
|
- private void foo() throws NullPointerException {
|
|
|
- try {
|
|
|
- throwNpe();
|
|
|
- } catch (NullPointerException e) {
|
|
|
- throw new IllegalArgumentException(e);
|
|
|
- }
|
|
|
- }
|
|
|
+ private ErrorCode code1;
|
|
|
+ private ErrorCode code2;
|
|
|
|
|
|
- private void bar() throws NullPointerException {
|
|
|
- foo();
|
|
|
+ @BeforeEach
|
|
|
+ private void setup() {
|
|
|
+ Mockito.doReturn(new StackTraceElement[]{foo, bar, baz}).when(fooException).getStackTrace();
|
|
|
+ Mockito.doReturn(new StackTraceElement[]{foo, baz, bar}).when(barException).getStackTrace();
|
|
|
+ this.code1 = ErrorCode.of(fooException);
|
|
|
+ this.code2 = ErrorCode.of(barException);
|
|
|
}
|
|
|
|
|
|
@Test
|
|
@@ -114,9 +172,9 @@ public class ErrorCodeTest {
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- @DisplayName("rootCauseCodes are equal")
|
|
|
+ @DisplayName("rootCauseCodes are different")
|
|
|
public void testSameRootCauseCodes() {
|
|
|
- Assertions.assertEquals(code1.rootCauseCode(), code2.rootCauseCode());
|
|
|
+ Assertions.assertNotEquals(code1.rootCauseCode(), code2.rootCauseCode());
|
|
|
}
|
|
|
|
|
|
@Test
|
|
@@ -127,4 +185,19 @@ public class ErrorCodeTest {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ public static class IntegerArrayConverter extends SimpleArgumentConverter {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected Integer[] convert(Object source, Class<?> targetType) {
|
|
|
+ if (source == null) {
|
|
|
+ return new Integer[0];
|
|
|
+ } else if (source instanceof String s && Integer[].class.isAssignableFrom(targetType)) {
|
|
|
+ return Splitter.on(CharMatcher.inRange('0', '9').negate()).splitToStream(s).map(Integer::valueOf).toArray(Integer[]::new);
|
|
|
+ } else {
|
|
|
+ throw new IllegalArgumentException("Conversion from " + source.getClass() + " to " + targetType + " not supported.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
}
|