You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by kw...@apache.org on 2024/08/05 14:36:07 UTC

(jackrabbit) 01/01: JCR-5095 Improve exception handling in TokenBasedLoginTest

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch bugfix/improve-error-handing-in-tokenbasedlogintest
in repository https://gitbox.apache.org/repos/asf/jackrabbit.git

commit 12e9c981d51703f66db9dc80f302b612f0279cea
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Mon Aug 5 16:35:56 2024 +0200

    JCR-5095 Improve exception handling in TokenBasedLoginTest
---
 .../authentication/token/TokenBasedLoginTest.java  | 268 ++++++++++-----------
 1 file changed, 132 insertions(+), 136 deletions(-)

diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedLoginTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedLoginTest.java
index 78d8194e3..4c973b869 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedLoginTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedLoginTest.java
@@ -16,6 +16,23 @@
  */
 package org.apache.jackrabbit.core.security.authentication.token;
 
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.jcr.LoginException;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
 import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
 import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
@@ -26,18 +43,6 @@ import org.apache.jackrabbit.test.AbstractJCRTest;
 import org.apache.jackrabbit.test.NotExecutableException;
 import org.apache.jackrabbit.test.RepositoryStub;
 
-import javax.jcr.LoginException;
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
-
 /**
  * <code>TokenBasedLoginTest</code>...
  */
@@ -230,120 +235,77 @@ public class TokenBasedLoginTest extends AbstractJCRTest {
      * Tests concurrent login on the Repository including token creation.
      * Test copied and slightly adjusted from org.apache.jackrabbit.core.ConcurrentLoginTest
      */
-    public void testConcurrentLogin() throws RepositoryException, NotExecutableException {
-        final Exception[] exception = new Exception[1];
-        List<Thread> testRunner = new ArrayList<Thread>();
-        for (int i = 0; i < 10; i++) {
-            testRunner.add(new Thread(new Runnable() {
-                public void run() {
-                    for (int i = 0; i < 100; i++) {
+    public void testConcurrentLogin() throws Throwable {
+        assertParallelExecutionSucceeds(10, new JcrRunnable() {
+            public void run() {
+                for (int i = 0; i < 100; i++) {
+                    try {
+                        SimpleCredentials sc = new SimpleCredentials(testuser.getID(), testuser.getID().toCharArray());
+                        sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+
+                        Session s = getHelper().getRepository().login(sc);
                         try {
-                            SimpleCredentials sc = new SimpleCredentials(testuser.getID(), testuser.getID().toCharArray());
-                            sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
-
-                            Session s = getHelper().getRepository().login(sc);
-                            try {
-                                Set<TokenCredentials> tcs = ((SessionImpl) s).getSubject().getPublicCredentials(TokenCredentials.class);
-                                assertFalse(tcs.isEmpty());
-                            } finally {
-                                s.logout();
-                            }
-                        } catch (Exception e) {
-                            exception[0] = e;
-                            break;
+                            Set<TokenCredentials> tcs = ((SessionImpl) s).getSubject().getPublicCredentials(TokenCredentials.class);
+                            assertFalse(tcs.isEmpty());
+                        } finally {
+                            s.logout();
                         }
+                    } catch (RepositoryException e) {
+                        throw new IllegalStateException("Repository exception", e);
                     }
                 }
-            }));
-        }
-
-        // start threads
-        for (Object aTestRunner : testRunner) {
-            ((Thread) aTestRunner).start();
-        }
-
-        // join threads
-        for (Object aTestRunner : testRunner) {
-            try {
-                ((Thread) aTestRunner).join();
-            } catch (InterruptedException e) {
-                fail(e.toString());
             }
-        }
+        });
 
-        if (exception[0] != null) {
-            fail(exception[0].toString());
-        }
     }
 
     /**
      * Tests concurrent login of 3 different users on the Repository including
      * token creation.
      * Test copied and slightly adjusted from org.apache.jackrabbit.core.ConcurrentLoginTest
+     * @throws InterruptedException 
      */
-    public void testConcurrentLoginOfDifferentUsers() throws RepositoryException, NotExecutableException {
-        final Exception[] exception = new Exception[1];
-        List<Thread> testRunner = new ArrayList<Thread>();
-        for (int i = 0; i < 10; i++) {
-            testRunner.add(new Thread(new Runnable() {
-                public void run() {
-                    for (int i = 0; i < 100; i++) {
+    public void testConcurrentLoginOfDifferentUsers() throws InterruptedException {
+        assertParallelExecutionSucceeds(10, new JcrRunnable() {
+            public void run() {
+                for (int i = 0; i < 100; i++) {
+                    try {
+                        SimpleCredentials c;
+                        double rand = 3 * Math.random();
+                        int index = (int) Math.floor(rand);
+                        switch (index) {
+                            case 0:
+                                c = new SimpleCredentials(testuser.getID(), testuser.getID().toCharArray());
+                                break;
+                            case 1:
+                                c = new SimpleCredentials(getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_SUPERUSER_NAME), getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_SUPERUSER_PWD).toCharArray());
+                                break;
+                            default:
+                                c = new SimpleCredentials(getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_READONLY_NAME), getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_READONLY_PWD).toCharArray());
+                                break;
+                        }
+                        c.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+                        Session s = getHelper().getRepository().login(c);
                         try {
-                            SimpleCredentials c;
-                            double rand = 3 * Math.random();
-                            int index = (int) Math.floor(rand);
-                            switch (index) {
-                                case 0:
-                                    c = new SimpleCredentials(testuser.getID(), testuser.getID().toCharArray());
-                                    break;
-                                case 1:
-                                    c = new SimpleCredentials(getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_SUPERUSER_NAME), getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_SUPERUSER_PWD).toCharArray());
-                                    break;
-                                default:
-                                    c = new SimpleCredentials(getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_READONLY_NAME), getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_READONLY_PWD).toCharArray());
-                                    break;
-                            }
-                            c.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
-                            Session s = getHelper().getRepository().login(c);
-                            try {
-                                Set<TokenCredentials> tcs = ((SessionImpl) s).getSubject().getPublicCredentials(TokenCredentials.class);
-                                assertFalse(tcs.isEmpty());
-                            } finally {
-                                s.logout();
-                            }
-                        } catch (Exception e) {
-                            exception[0] = e;
-                            break;
+                            Set<TokenCredentials> tcs = ((SessionImpl) s).getSubject().getPublicCredentials(TokenCredentials.class);
+                            assertFalse(tcs.isEmpty());
+                        } finally {
+                            s.logout();
                         }
+                    } catch (RepositoryException e) {
+                        throw new IllegalStateException("Repository exception", e);
                     }
                 }
-            }));
-        }
-
-        // start threads
-        for (Object aTestRunner : testRunner) {
-            ((Thread) aTestRunner).start();
-        }
-
-        // join threads
-        for (Object aTestRunner : testRunner) {
-            try {
-                ((Thread) aTestRunner).join();
-            } catch (InterruptedException e) {
-                fail(e.toString());
             }
-        }
-
-        if (exception[0] != null) {
-            fail(exception[0].toString());
-        }
+        });
     }
 
     /**
      * Tests concurrent login on the Repository including token creation.
      * Test copied and slightly adjusted from org.apache.jackrabbit.core.ConcurrentLoginTest
+     * @throws InterruptedException 
      */
-    public void testConcurrentLoginDifferentWorkspaces() throws RepositoryException, NotExecutableException {
+    public void testConcurrentLoginDifferentWorkspaces() throws RepositoryException, NotExecutableException, InterruptedException {
         final String testID = testuser.getID();
 
         // check if test is executable
@@ -367,53 +329,87 @@ public class TokenBasedLoginTest extends AbstractJCRTest {
             }
         }
 
-        final Exception[] exception = new Exception[1];
+        assertParallelExecutionSucceeds(10, new JcrRunnable() {
+            public void run() {
+                for (int i = 0; i < 100; i++) {
+                    try {
+                        double rand = wspNames.size() * Math.random();
+                        int index = (int) Math.floor(rand);
+                        String wspName = wspNames.get(index);
+
+                        SimpleCredentials sc = new SimpleCredentials(testID, testID.toCharArray());
+                        sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+
+                        Session s = getHelper().getRepository().login(sc, wspName);
+                        try {
+                            Set<TokenCredentials> tcs = ((SessionImpl) s).getSubject().getPublicCredentials(TokenCredentials.class);
+                            assertFalse(tcs.isEmpty());
+                        } finally {
+                            s.logout();
+                        }
+
+                    } catch (RepositoryException e) {
+                        throw new IllegalStateException("Repository exception", e);
+                    }
+                }
+            }
+        });
+    }
+
+    static class UncheckedRepositoryException extends RuntimeException {
+        private static final long serialVersionUID = 1L;
+
+        public UncheckedRepositoryException(RepositoryException e) {
+            super(e);
+        }
+    }
+
+    static interface JcrRunnable {
+        public void run() throws RepositoryException;
+    }
+
+    private static void assertParallelExecutionSucceeds(int numThreads, JcrRunnable runnable) throws InterruptedException {
         List<Thread> testRunner = new ArrayList<Thread>();
-        for (int i = 0; i < 10; i++) {
+        for (int i = 0; i < numThreads; i++) {
             testRunner.add(new Thread(new Runnable() {
                 public void run() {
-                    for (int i = 0; i < 100; i++) {
-                        try {
-                            double rand = wspNames.size() * Math.random();
-                            int index = (int) Math.floor(rand);
-                            String wspName = wspNames.get(index);
-
-                            SimpleCredentials sc = new SimpleCredentials(testID, testID.toCharArray());
-                            sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
-
-                            Session s = getHelper().getRepository().login(sc, wspName);
-                            try {
-                                Set<TokenCredentials> tcs = ((SessionImpl) s).getSubject().getPublicCredentials(TokenCredentials.class);
-                                assertFalse(tcs.isEmpty());
-                            } finally {
-                                s.logout();
-                            }
-
-                        } catch (Exception e) {
-                            exception[0] = e;
-                            break;
-                        }
+                    try {
+                        runnable.run();
+                    } catch (RepositoryException e) {
+                        throw new UncheckedRepositoryException(e);
                     }
                 }
             }));
         }
-
+        assertThreadExecutionSucceeds(testRunner);
+    }
+    /**
+     * Executes all given threads and waits for them to finish. Exception in any of the thread will make the calling thread die with an exception as well
+     * @param testRunner the threads to execute
+     * @throws InterruptedException
+     */
+    private static void assertThreadExecutionSucceeds(Collection<Thread> testRunner) throws InterruptedException {
+        Map<String, Throwable> threadExceptions = new ConcurrentHashMap<>();
         // start threads
-        for (Object aTestRunner : testRunner) {
-            ((Thread) aTestRunner).start();
+        for (Thread aTestRunner : testRunner) {
+            aTestRunner.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
+                public void uncaughtException(Thread t, Throwable e) {
+                    threadExceptions.put(t.getName() + " (id " + t.getId() +")", e);
+                }
+            } );
+            aTestRunner.start();
         }
 
         // join threads
-        for (Object aTestRunner : testRunner) {
-            try {
-                ((Thread) aTestRunner).join();
-            } catch (InterruptedException e) {
-                fail(e.toString());
-            }
+        for (Thread aTestRunner : testRunner) {
+            aTestRunner.join();
         }
 
-        if (exception[0] != null) {
-            fail(exception[0].toString());
+        // only rethrow one exception(put the other ones as suppressed)
+        IllegalStateException mainException = threadExceptions.entrySet().stream().findFirst().map(e -> new IllegalStateException("Thread " + e.getKey() + " failed", e.getValue())).orElse(null);
+        if (mainException != null) {
+            threadExceptions.entrySet().stream().skip(1).forEach(e -> mainException.addSuppressed(new IllegalStateException("Thread " + e.getKey() + " failed", e.getValue())));
+            throw mainException;
         }
     }
 }
\ No newline at end of file