<commit_before>package kbasesearchengine.test.events.exceptions;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.junit.Test;

import com.google.common.base.Optional;

import kbasesearchengine.events.StatusEvent;
import kbasesearchengine.events.StatusEventID;
import kbasesearchengine.events.StatusEventType;
import kbasesearchengine.events.StatusEventWithID;
import kbasesearchengine.events.exceptions.FatalIndexingException;
import kbasesearchengine.events.exceptions.FatalRetriableIndexingException;
import kbasesearchengine.events.exceptions.IndexingException;
import kbasesearchengine.events.exceptions.RetriableIndexingException;
import kbasesearchengine.events.exceptions.Retrier;
import kbasesearchengine.events.exceptions.RetriesExceededIndexingException;
import kbasesearchengine.events.exceptions.RetryConsumer;
import kbasesearchengine.events.exceptions.RetryFunction;
import kbasesearchengine.events.exceptions.RetryLogger;
import kbasesearchengine.system.StorageObjectType;
import kbasesearchengine.test.common.TestCommon;

public class RetrierTest {
    
    private class LogEvent {
        private final Instant time;
        private final int retryCount;
        private final Optional<StatusEventWithID> event;
        private final RetriableIndexingException exception;
        
        private LogEvent(
                final Instant time,
                final int retryCount,
                final Optional<StatusEventWithID> optional,
                final RetriableIndexingException exception) {
            this.time = time;
            this.retryCount = retryCount;
            this.event = optional;
            this.exception = exception;
        }
    }
    
    private class CollectingLogger implements RetryLogger {

        private final List<LogEvent> events = new LinkedList<>();
        
        @Override
        public void log(int retryCount, Optional<StatusEventWithID> optional,
                RetriableIndexingException e) {
            events.add(new LogEvent(Instant.now(), retryCount, optional, e));
        }
        
    }

    @Test
    public void construct() throws Exception {
        final CollectingLogger log = new CollectingLogger();
        final Retrier ret = new Retrier(2, 10, Arrays.asList(4, 5, 6), log);
        
        assertThat("incorrect retries", ret.getRetryCount(), is(2));
        assertThat("incorrect delay", ret.getDelayMS(), is(10));
        assertThat("incorrect fatal delays", ret.getFatalRetryBackoffsMS(),
                is(Arrays.asList(4, 5, 6)));
        assertThat("incorrect logger", ret.getLogger(), is(log));
    }
    
    @Test
    public void constructFail() throws Exception {
        final CollectingLogger log = new CollectingLogger();
        failConstruct(0, 1, Collections.emptyList(), log,
                new IllegalArgumentException("retryCount must be at least 1"));
        failConstruct(1, 0, Collections.emptyList(), log,
                new IllegalArgumentException("delayMS must be at least 1"));
        failConstruct(1, 1, null, log, new NullPointerException("fatalRetryBackoffsMS"));
        failConstruct(1, 1, Arrays.asList(1, null), log,
                new IllegalArgumentException("Illegal value in fatalRetryBackoffsMS: null"));
        failConstruct(1, 1, Arrays.asList(1, 0), log,
                new IllegalArgumentException("Illegal value in fatalRetryBackoffsMS: 0"));
        failConstruct(1, 1, Collections.emptyList(), null, new NullPointerException("logger"));
    }

    private void failConstruct(
            final int retries,
            final int retryDelay,
            final List<Integer> fatalRetryDelay,
            final RetryLogger logger,
            final Exception expected) {
        try {
            new Retrier(retries, retryDelay, fatalRetryDelay, logger);
        } catch (Exception got) {
            TestCommon.assertExceptionCorrect(got, expected);
        }
    }
    
    @Test
    public void immutableFatalDelayList() {
        final List<Integer> delays = new ArrayList<>();
        delays.add(1);
        delays.add(2);
        delays.add(3);
        final Retrier ret = new Retrier(2, 10, delays, new CollectingLogger());
        try {
            ret.getFatalRetryBackoffsMS().set(2, 42);
        } catch (UnsupportedOperationException e) {
            // test passed
        }
    }
    
    private class TestConsumer<T> implements RetryConsumer<T> {

        private final T input;
        private final int retries;
        private int count = 0;
        private final boolean fatal;
        
        private TestConsumer(T input, int retries) {
            this.input = input;
            this.retries = retries;
            fatal = false;
        }
        
        private TestConsumer(T input, int retries, boolean fatal) {
            this.input = input;
            this.retries = retries;
            this.fatal = fatal;
        }

        @Override
        public void accept(final T t)
                throws IndexingException, RetriableIndexingException, InterruptedException {
            assertThat("incorrect input", t, is(input));
            if (count == retries) {
                return;
            } else {
                count++;
                if (fatal) {
                    throw new FatalRetriableIndexingException("game over man");
                } else {
                    throw new RetriableIndexingException("bar");
                }
            }
        }
        
    }
    
    @Test
    public void consumer1RetrySuccessNoEvent() throws Exception {
        final CollectingLogger collog = new CollectingLogger();
        final Retrier ret = new Retrier(1, 50, Collections.emptyList(), collog);
        final Instant start = Instant.now();
        ret.retryCons(new TestConsumer<>("foo", 1), "foo", null);
        final Instant end = Instant.now();
        
        assertThat("incorrect retries", collog.events.size(), is(1));
        final LogEvent le = collog.events.get(0);
        assertThat("incorrect retry count", le.retryCount, is(1));
        assertThat("incorrect event", le.event, is(Optional.absent()));
        TestCommon.assertExceptionCorrect(le.exception, new RetriableIndexingException("bar"));
        assertCloseMS(start, le.time, 0, 15);
        assertCloseMS(start, end, 50, 15);
    }
    
    @Test
    public void consumer2RetrySuccessWithEvent() throws Exception {
        final CollectingLogger collog = new CollectingLogger();
        final Retrier ret = new Retrier(2, 50, Collections.emptyList(), collog);
        final StatusEventWithID ev = new StatusEventWithID(StatusEvent.getBuilder(
                new StorageObjectType("foo", "whee"), Instant.now(), StatusEventType.DELETED)
                .withNullableAccessGroupID(23)
                .withNullableObjectID("bar")
                .withNullableVersion(6)<commit_msg>adjust time assertion in retrier test<commit_after>package kbasesearchengine.test.events.exceptions;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.junit.Test;

import com.google.common.base.Optional;

import kbasesearchengine.events.StatusEvent;
import kbasesearchengine.events.StatusEventID;
import kbasesearchengine.events.StatusEventType;
import kbasesearchengine.events.StatusEventWithID;
import kbasesearchengine.events.exceptions.FatalIndexingException;
import kbasesearchengine.events.exceptions.FatalRetriableIndexingException;
import kbasesearchengine.events.exceptions.IndexingException;
import kbasesearchengine.events.exceptions.RetriableIndexingException;
import kbasesearchengine.events.exceptions.Retrier;
import kbasesearchengine.events.exceptions.RetriesExceededIndexingException;
import kbasesearchengine.events.exceptions.RetryConsumer;
import kbasesearchengine.events.exceptions.RetryFunction;
import kbasesearchengine.events.exceptions.RetryLogger;
import kbasesearchengine.system.StorageObjectType;
import kbasesearchengine.test.common.TestCommon;

public class RetrierTest {
    
    private class LogEvent {
        private final Instant time;
        private final int retryCount;
        private final Optional<StatusEventWithID> event;
        private final RetriableIndexingException exception;
        
        private LogEvent(
                final Instant time,
                final int retryCount,
                final Optional<StatusEventWithID> optional,
                final RetriableIndexingException exception) {
            this.time = time;
            this.retryCount = retryCount;
            this.event = optional;
            this.exception = exception;
        }
    }
    
    private class CollectingLogger implements RetryLogger {

        private final List<LogEvent> events = new LinkedList<>();
        
        @Override
        public void log(int retryCount, Optional<StatusEventWithID> optional,
                RetriableIndexingException e) {
            events.add(new LogEvent(Instant.now(), retryCount, optional, e));
        }
        
    }

    @Test
    public void construct() throws Exception {
        final CollectingLogger log = new CollectingLogger();
        final Retrier ret = new Retrier(2, 10, Arrays.asList(4, 5, 6), log);
        
        assertThat("incorrect retries", ret.getRetryCount(), is(2));
        assertThat("incorrect delay", ret.getDelayMS(), is(10));
        assertThat("incorrect fatal delays", ret.getFatalRetryBackoffsMS(),
                is(Arrays.asList(4, 5, 6)));
        assertThat("incorrect logger", ret.getLogger(), is(log));
    }
    
    @Test
    public void constructFail() throws Exception {
        final CollectingLogger log = new CollectingLogger();
        failConstruct(0, 1, Collections.emptyList(), log,
                new IllegalArgumentException("retryCount must be at least 1"));
        failConstruct(1, 0, Collections.emptyList(), log,
                new IllegalArgumentException("delayMS must be at least 1"));
        failConstruct(1, 1, null, log, new NullPointerException("fatalRetryBackoffsMS"));
        failConstruct(1, 1, Arrays.asList(1, null), log,
                new IllegalArgumentException("Illegal value in fatalRetryBackoffsMS: null"));
        failConstruct(1, 1, Arrays.asList(1, 0), log,
                new IllegalArgumentException("Illegal value in fatalRetryBackoffsMS: 0"));
        failConstruct(1, 1, Collections.emptyList(), null, new NullPointerException("logger"));
    }

    private void failConstruct(
            final int retries,
            final int retryDelay,
            final List<Integer> fatalRetryDelay,
            final RetryLogger logger,
            final Exception expected) {
        try {
            new Retrier(retries, retryDelay, fatalRetryDelay, logger);
        } catch (Exception got) {
            TestCommon.assertExceptionCorrect(got, expected);
        }
    }
    
    @Test
    public void immutableFatalDelayList() {
        final List<Integer> delays = new ArrayList<>();
        delays.add(1);
        delays.add(2);
        delays.add(3);
        final Retrier ret = new Retrier(2, 10, delays, new CollectingLogger());
        try {
            ret.getFatalRetryBackoffsMS().set(2, 42);
        } catch (UnsupportedOperationException e) {
            // test passed
        }
    }
    
    private class TestConsumer<T> implements RetryConsumer<T> {

        private final T input;
        private final int retries;
        private int count = 0;
        private final boolean fatal;
        
        private TestConsumer(T input, int retries) {
            this.input = input;
            this.retries = retries;
            fatal = false;
        }
        
        private TestConsumer(T input, int retries, boolean fatal) {
            this.input = input;
            this.retries = retries;
            this.fatal = fatal;
        }

        @Override
        public void accept(final T t)
                throws IndexingException, RetriableIndexingException, InterruptedException {
            assertThat("incorrect input", t, is(input));
            if (count == retries) {
                return;
            } else {
                count++;
                if (fatal) {
                    throw new FatalRetriableIndexingException("game over man");
                } else {
                    throw new RetriableIndexingException("bar");
                }
            }
        }
        
    }
    
    @Test
    public void consumer1RetrySuccessNoEvent() throws Exception {
        final CollectingLogger collog = new CollectingLogger();
        final Retrier ret = new Retrier(1, 50, Collections.emptyList(), collog);
        final Instant start = Instant.now();
        ret.retryCons(new TestConsumer<>("foo", 1), "foo", null);
        final Instant end = Instant.now();
        
        assertThat("incorrect retries", collog.events.size(), is(1));
        final LogEvent le = collog.events.get(0);
        assertThat("incorrect retry count", le.retryCount, is(1));
        assertThat("incorrect event", le.event, is(Optional.absent()));
        TestCommon.assertExceptionCorrect(le.exception, new RetriableIndexingException("bar"));
        assertCloseMS(start, le.time, 0, 30);
        assertCloseMS(start, end, 50, 30);
    }
    
    @Test
    public void consumer2RetrySuccessWithEvent() throws Exception {
        final CollectingLogger collog = new CollectingLogger();
        final Retrier ret = new Retrier(2, 50, Collections.emptyList(), collog);
        final StatusEventWithID ev = new StatusEventWithID(StatusEvent.getBuilder(
                new StorageObjectType("foo", "whee"), Instant.now(), StatusEventType.DELETED)
                .withNullableAccessGroupID(23)
                .withNullableObjectID("bar")
                .withNullableVersion(6)
{'fraction_non_alphanumeric': 0.09053672316384181, 'fraction_numerical': 0.008757062146892656, 'mean_word_length': 2.5742049469964665, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3523029', 'n_tokens_mistral': 3888, 'n_tokens_neox': 3743, 'n_words': 916}