package com.netflix.astyanax.contrib.valve;

import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import org.junit.Assert;
import org.junit.Test;

public class RollingTimeWindowValveTest {

    @Test
    public void testSingleThreadSingleBucket() throws Exception {
     
        final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 1);
        
        Long counter = new Long(0L);
        
        for (int i=0; i<100000; i++) {
            boolean success = valve.decrementAndCheckQuota();
            if (success) {
                counter++;
            }
        }
        
        Assert.assertTrue("Success: " + counter, counter == 10000);
    }
    
    @Test
    public void testSingleThreadMultipleBucketsSingleSecond() throws Exception {

        final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 10);
        runMultiThreadTest(valve, 1, 750, 8000);
    }

    @Test
    public void testSingleThreadMultipleBucketsMultipleSeconds() throws Exception {

        final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 10);
        runMultiThreadTest(valve, 1, 1750, 18000);
    }

    @Test
    public void testMultipleThreadsSingleBucketSingleSecond() throws Exception {
     
        final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 1);
        runMultiThreadTest(valve, 8, 800, 10000);
    }
    
    @Test
    public void testMultipleThreadsSingleBucketMultipleSeconds() throws Exception {
     
        final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 1);
        runMultiThreadTest(valve, 8, 1700, 20000);
    }

    @Test
    public void testMultipleThreadsMultipleBucketsMultipleSeconds() throws Exception {
     
        final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 10);
        runMultiThreadTest(valve, 8, 1750, 18000);
    }

    private void runMultiThreadTest(final RollingTimeWindowValve valve, int numThreads, int sleepMillis, long expectedSuccesses) throws Exception {
        
        final AtomicLong successCount = new AtomicLong(0L);
        final MultiThreadTestControl testControl = new MultiThreadTestControl(numThreads);
        
        testControl.runTest(new Callable<Void>() {

            @Override
            public Void call() throws Exception {
                boolean success = valve.decrementAndCheckQuota();
                if (success) {
                    successCount.incrementAndGet();
                }
                return null;
            }
        });

        Thread.sleep(sleepMillis);
        
        testControl.stopTest();
        
        long delta = Math.abs(expectedSuccesses-successCount.get());
        int percentageDiff = (int) (delta*100/expectedSuccesses);
        
        Assert.assertTrue("Success: " + successCount.get() + ", expected: " + expectedSuccesses + ", percentageDiff: " + percentageDiff, percentageDiff < 10);
        //System.out.println("Success: " + successCount.get() + ", expected: " + expectedSuccesses + ", percentageDiff: " + percentageDiff);
    }
    
    @Test
    public void testChangeInRate() throws Exception {
        
        final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 10);
        
        final AtomicReference<PauseTest> pause = new AtomicReference<PauseTest>(new PauseTest(8)); 
        
        final AtomicLong successCount = new AtomicLong(0L);
        final MultiThreadTestControl testControl = new MultiThreadTestControl(8);
        
        testControl.runTest(new Callable<Void>() {

            @Override
            public Void call() throws Exception {
                
                if (pause.get().shouldPause()) {
                    pause.get().waitOnResume();
                }
                
                boolean success = valve.decrementAndCheckQuota();
                if (success) {
                    successCount.incrementAndGet();
                }
                return null;
            }
        });

        Thread.sleep(1450);
        
        pause.get().pauseTest();
        valve.setRatePerSecond(20000L);
        pause.get().resumeTest();
        
        Thread.sleep(970);
        
        pause.set(new PauseTest(8));
        pause.get().pauseTest();
        valve.setRatePerSecond(5000L);
        pause.get().resumeTest();
        
        Thread.sleep(1000);

        testControl.stopTest();
        
        long expectedSuccesses = 40000;  // 10000 for the 1st 1000 ms. and then 20000 for the next 1000 ms and 5000 for the last 1000 ms
        long delta = Math.abs(expectedSuccesses-successCount.get());
        int percentageDiff = (int) (delta*100/expectedSuccesses);
        
        //Assert.assertTrue("Success: " + successCount.get() + ", expected: " + expectedSuccesses + ", percentageDiff: " + percentageDiff, percentageDiff < 10);
        //System.out.println("Success: " + successCount.get() + ", expected: " + expectedSuccesses + ", percentageDiff: " + percentageDiff);
    }
    
    private class PauseTest {
        
        private final CountDownLatch waitLatch = new CountDownLatch(1);
        
        private final AtomicBoolean pauseEnabled = new AtomicBoolean(false);
        
        private PauseTest(int numWorkersToPause) {
        }
        
        private void pauseTest() {
            pauseEnabled.set(true);
        }
        
        private void resumeTest() {
            pauseEnabled.set(false);
            waitLatch.countDown();
        }
        
        private boolean shouldPause() {
            return pauseEnabled.get();
        }
        
        private void waitOnResume() {
            try {
                waitLatch.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
}

@@ -363,25 +363,17 @@
 est %7B%0A%0A 
-   @Test
 %0A
+
     publ
@@ -837,34 +837,25 @@
 %0A    %7D%0A    %0A
-    @Test
 %0A
+
     public v
@@ -1061,32 +1061,24 @@
 0);%0A    %7D%0A%0A 
-   @Test
 %0A    public 
@@ -1289,34 +1289,25 @@
 00);%0A    %7D%0A%0A
-    @Test
 %0A
+
     public v
@@ -1521,34 +1521,25 @@
 %0A    %7D%0A    %0A
-    @Test
 %0A
+
     public v
@@ -1757,26 +1757,17 @@
 %0A    %7D%0A%0A
-    @Test
 %0A
+
     publ
@@ -3201,17 +3201,9 @@
    %0A
+
  
-   @Test
 %0A   

Remove tests on unused code