killbill-memoizeit

payment: refactor TestPluginOperation#testWithAccountLock *

2/2/2016 11:25:27 AM

Details

diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
index 650bd00..d328ffc 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestPluginOperation.java
@@ -1,8 +1,8 @@
 /*
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Groupon licenses this file to you under the Apache License, version 2.0
+ * The Billing Project 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:
  *
@@ -21,6 +21,7 @@ import java.math.BigDecimal;
 import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -45,8 +46,6 @@ import org.killbill.billing.util.config.PaymentConfig;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.commons.locker.memory.MemoryGlobalLocker;
 import org.mockito.Mockito;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -56,13 +55,13 @@ import com.jayway.awaitility.Awaitility;
 
 public class TestPluginOperation extends PaymentTestSuiteNoDB {
 
-    private final static String PLUGIN_NAME_PLACEHOLDER = "pluginName";
+    private static final String PLUGIN_NAME_PLACEHOLDER = "pluginName";
+
+    private static final int TIMEOUT = 3;
 
     private final GlobalLocker locker = new MemoryGlobalLocker();
     private final Account account = Mockito.mock(Account.class);
 
-    private static final Logger logger = LoggerFactory.getLogger(TestPluginOperation.class);
-
     @BeforeMethod(groups = "fast")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
@@ -70,11 +69,10 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testWithAccountLock() throws Exception {
-        testLocking(true);
+    public void testAccountLock() throws Exception {
+        testLocking();
     }
 
-
     @Test(groups = "fast")
     public void testOperationThrowsPaymentApiException() throws Exception {
         final CallbackTest callback = new CallbackTest(new PaymentApiException(ErrorCode.__UNKNOWN_ERROR_CODE));
@@ -103,30 +101,31 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
         }
     }
 
-    private void testLocking(final boolean withAccountLock) throws Exception {
+    private void testLocking() throws Exception {
         final Semaphore available = new Semaphore(1, true);
         final CallbackTest callback = new CallbackTest(available);
-        final PaymentOperation pluginOperation = getPluginOperation(withAccountLock);
+        final PaymentOperation pluginOperation = getPluginOperation(true);
 
         // Take the only permit
         available.acquire();
 
         // Start the plugin operation in the background (will block)
         runPluginOperationInBackground(pluginOperation, callback, false);
+        Awaitility.await()
+                  .until(new Callable<Boolean>() {
+                      @Override
+                      public Boolean call() throws Exception {
+                          return callback.getStartCount() == 1;
+                      }
+                  });
 
         // The operation should be blocked here because we have the semaphore
-        Assert.assertEquals(available.getQueueLength(), 1);
         Assert.assertEquals(callback.getRunCount(), 0);
 
-        if (withAccountLock) {
-            // If the account is locked, trying to run the operation again will throw LockFailedException
-            runPluginOperationInBackground(pluginOperation, callback, true);
-        } else {
-            // If the account is not locked, it will just block
-            runPluginOperationInBackground(pluginOperation, callback, false);
-            Assert.assertEquals(available.getQueueLength(), 2);
-            Assert.assertEquals(callback.getRunCount(), 0);
-        }
+        // Trying to run the operation again will throw LockFailedException
+        Awaitility.await().atMost(TIMEOUT + 1, TimeUnit.SECONDS).untilTrue(runPluginOperationInBackground(pluginOperation, callback, true));
+
+        Assert.assertEquals(callback.getRunCount(), 0);
 
         // Release the semaphore
         available.release();
@@ -136,37 +135,41 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
                   .until(new Callable<Boolean>() {
                       @Override
                       public Boolean call() throws Exception {
-                          return callback.getRunCount() == (withAccountLock ? 1 : 2);
+                          return callback.getRunCount() == 1;
                       }
                   });
 
         // Verify the final state
-        Assert.assertEquals(available.getQueueLength(), 0);
-        Assert.assertEquals(callback.getRunCount(), withAccountLock ? 1 : 2);
+        Assert.assertEquals(available.availablePermits(), 1);
     }
 
-    private void runPluginOperationInBackground(final PaymentOperation pluginOperation, final CallbackTest callback, final boolean shouldFailBecauseOfLockFailure) throws Exception {
-        final AtomicBoolean threadStarted = new AtomicBoolean(false);
+    private AtomicBoolean runPluginOperationInBackground(final PaymentOperation pluginOperation, final CallbackTest callback, final boolean shouldFailBecauseOfLockFailure) throws Exception {
+        final AtomicBoolean threadRunning = new AtomicBoolean(false);
+        final AtomicBoolean threadHasRun = new AtomicBoolean(false);
         final Thread t1 = new Thread(new Runnable() {
             @Override
             public void run() {
-                threadStarted.set(true);
-
-                if (shouldFailBecauseOfLockFailure) {
-                    try {
-                        pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
-                        Assert.fail();
-                    } catch (final OperationException e) {
-                        Assert.assertTrue(e.getCause() instanceof PaymentApiException);
-                        // No better error code for lock failures...
-                        Assert.assertEquals(((PaymentApiException) e.getCause()).getCode(), ErrorCode.PAYMENT_INTERNAL_ERROR.getCode());
-                    }
-                } else {
-                    try {
-                        pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
-                    } catch (final OperationException e) {
-                        Assert.fail(e.getMessage());
+                threadRunning.set(true);
+
+                try {
+                    if (shouldFailBecauseOfLockFailure) {
+                        try {
+                            pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
+                            Assert.fail();
+                        } catch (final OperationException e) {
+                            Assert.assertTrue(e.getCause() instanceof PaymentApiException);
+                            // No better error code for lock failures...
+                            Assert.assertEquals(((PaymentApiException) e.getCause()).getCode(), ErrorCode.PAYMENT_INTERNAL_ERROR.getCode());
+                        }
+                    } else {
+                        try {
+                            pluginOperation.dispatchWithAccountLockAndTimeout(PLUGIN_NAME_PLACEHOLDER, callback);
+                        } catch (final OperationException e) {
+                            Assert.fail(e.getMessage());
+                        }
                     }
+                } finally {
+                    threadHasRun.set(true);
                 }
             }
         });
@@ -174,7 +177,9 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
         t1.start();
 
         // Make sure the thread has started
-        Awaitility.await().untilTrue(threadStarted);
+        Awaitility.await().untilTrue(threadRunning);
+
+        return threadRunning;
     }
 
     private PaymentOperation getPluginOperation() throws PaymentApiException {
@@ -182,7 +187,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
     }
 
     private PaymentOperation getPluginOperation(final boolean shouldLockAccount) throws PaymentApiException {
-        return getPluginOperation(shouldLockAccount, Integer.MAX_VALUE);
+        return getPluginOperation(shouldLockAccount, TIMEOUT);
     }
 
     private PaymentOperation getPluginOperation(final boolean shouldLockAccount, final int timeoutSeconds) throws PaymentApiException {
@@ -209,6 +214,7 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
 
     private static final class CallbackTest implements DispatcherCallback<PluginDispatcherReturnType<OperationResult>, PaymentApiException> {
 
+        private final AtomicInteger startCount = new AtomicInteger(0);
         private final AtomicInteger runCount = new AtomicInteger(0);
 
         private final Semaphore available;
@@ -242,9 +248,11 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
 
         @Override
         public PluginDispatcherReturnType<OperationResult> doOperation() throws PaymentApiException {
+            startCount.incrementAndGet();
+
             try {
                 if (available != null) {
-                    available.acquire();
+                    available.acquireUninterruptibly();
                 }
 
                 if (sleepTimeMillis != null) {
@@ -272,6 +280,10 @@ public class TestPluginOperation extends PaymentTestSuiteNoDB {
         public int getRunCount() {
             return runCount.get();
         }
+
+        public int getStartCount() {
+            return startCount.get();
+        }
     }
 
     private static final class PluginOperationTest extends PaymentOperation {