<commit_before>package org.innovateuk.ifs.async.controller;

import org.junit.Test;
import org.springframework.ui.Model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Consumer;

import static java.util.Collections.singletonList;
import static org.innovateuk.ifs.util.MapFunctions.asMap;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;

/**
 * Test for the thread safety of a {@link org.springframework.ui.Model} wrapped in a {@link ThreadsafeModel} wrapper.
 */
public class ThreadsafeModelTest {

    @Test
    public void testCallsToContainsAttributeDoNotLockOtherReadOperations() throws ExecutionException, InterruptedException {

        assertFirstReadOperationDoesNotBlockSecondReadOperation(
                model -> model.containsAttribute("read1"),
                model -> model.containsAttribute("read2"));

        assertFirstReadOperationDoesNotBlockSecondReadOperation(
                model -> model.containsAttribute("read"), Model::asMap);
    }

    @Test
    public void testCallsToAsMapDoNotLockOtherReadOperations() throws ExecutionException, InterruptedException {

        assertFirstReadOperationDoesNotBlockSecondReadOperation(
                Model::asMap, model -> model.containsAttribute("read"));
    }

    @Test
    public void testCallsToContainsAttributeLocksWriteAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.containsAttribute("read"),
                model -> model.addAttribute("write"));
    }

    @Test
    public void testCallsToAsMapLocksWriteAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                Model::asMap,
                model -> model.addAllAttributes(Collections.singleton("write")));

        assertFirstOperationBlocksSecondOperationUntilComplete(
                Model::asMap,
                model -> model.mergeAttributes(asMap(1, 2)));
    }

    @Test
    public void testCallsToAddAttributeLocksReadAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.addAttribute("write"),
                model -> model.containsAttribute("read"));
    }

    @Test
    public void testCallsToAddAllAttributesLocksReadAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.addAllAttributes(singletonList("write")),
                Model::asMap);
    }

    @Test
    public void testCallsToMergeAttributesLocksReadAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.mergeAttributes(asMap(1, 2)),
                Model::asMap);
    }

    @Test
    public void testCallsToAddAttributeLocksWriteAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.addAttribute("write1"),
                model -> model.addAttribute("write2"));
    }

    @Test
    public void testCallsToAddAllAttributesLocksWriteAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.addAllAttributes(singletonList("write")),
                model -> model.mergeAttributes(asMap(1, 2)));
    }

    @Test
    public void testCallsToMergeAttributesLocksWriteAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.mergeAttributes(asMap(1, 2)),
                model -> model.addAllAttributes(singletonList("write")));
    }

    /**
     * This test tests that we can have as many concurrent reads as we like whilst maintaing thread safety.
     *
     * This test issues 2 calls to {@link ThreadsafeModel#containsAttribute(String)}, the first of which blocks *within*
     * the bounds of the threadsafe locking mechanism inside ThreadsafeModel until the 2nd call is executed, upon which
     * the first call is unblocked.  The test performs this sequence in such a way so as to allow the 2nd call to write
     * its value to the "operationValues" list before the 1st call does, and we can assert that the 2nd value always makes it
     * into the list before the first does.
     *
     * This test proves therefore that a 2nd "read" operation can occur whilst a first "read" operation is in progress
     * (simulated by blocking the 1st call on the countDownLatch until the 2nd one is ready to release it).
     */
    private void assertFirstReadOperationDoesNotBlockSecondReadOperation(Consumer<Model> read1Operation, Consumer<Model> read2Operation) throws InterruptedException, ExecutionException {

        CountDownLatch countDownLatch = new CountDownLatch(1);

        Model wrappedModel = mock(Model.class);

        ThreadsafeModel threadsafeModel = new ThreadsafeModel(wrappedModel);

        List<String> operationValues = new ArrayList<>();

        Model read1WhenAnswer = doAnswer(invocation -> {
            countDownLatch.await();<commit_msg>Added a test to test against false positives in the ThreadModel testing mechanism<commit_after>package org.innovateuk.ifs.async.controller;

import org.junit.Test;
import org.springframework.ui.Model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Consumer;

import static java.util.Collections.singletonList;
import static org.innovateuk.ifs.util.MapFunctions.asMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;

/**
 * Test for the thread safety of a {@link org.springframework.ui.Model} wrapped in a {@link ThreadsafeModel} wrapper.
 */
public class ThreadsafeModelTest {

    @Test
    public void testCallsToContainsAttributeDoNotLockOtherReadOperations() throws ExecutionException, InterruptedException {

        assertFirstReadOperationDoesNotBlockSecondReadOperation(
                model -> model.containsAttribute("read1"),
                model -> model.containsAttribute("read2"));

        assertFirstReadOperationDoesNotBlockSecondReadOperation(
                model -> model.containsAttribute("read"), Model::asMap);
    }

    @Test
    public void testCallsToAsMapDoNotLockOtherReadOperations() throws ExecutionException, InterruptedException {

        assertFirstReadOperationDoesNotBlockSecondReadOperation(
                Model::asMap, model -> model.containsAttribute("read"));
    }

    @Test
    public void testCallsToContainsAttributeLocksWriteAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.containsAttribute("read"),
                model -> model.addAttribute("write"));
    }

    @Test
    public void testCallsToAsMapLocksWriteAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                Model::asMap,
                model -> model.addAllAttributes(Collections.singleton("write")));

        assertFirstOperationBlocksSecondOperationUntilComplete(
                Model::asMap,
                model -> model.mergeAttributes(asMap(1, 2)));
    }

    @Test
    public void testCallsToAddAttributeLocksReadAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.addAttribute("write"),
                model -> model.containsAttribute("read"));
    }

    @Test
    public void testCallsToAddAllAttributesLocksReadAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.addAllAttributes(singletonList("write")),
                Model::asMap);
    }

    @Test
    public void testCallsToMergeAttributesLocksReadAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.mergeAttributes(asMap(1, 2)),
                Model::asMap);
    }

    @Test
    public void testCallsToAddAttributeLocksWriteAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.addAttribute("write1"),
                model -> model.addAttribute("write2"));
    }

    @Test
    public void testCallsToAddAllAttributesLocksWriteAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.addAllAttributes(singletonList("write")),
                model -> model.mergeAttributes(asMap(1, 2)));
    }

    @Test
    public void testCallsToMergeAttributesLocksWriteAccessUntilComplete() throws ExecutionException, InterruptedException {

        assertFirstOperationBlocksSecondOperationUntilComplete(
                model -> model.mergeAttributes(asMap(1, 2)),
                model -> model.addAllAttributes(singletonList("write")));
    }

    /**
     * A non-standard thing to test, but as the mechanics of this test are quite tricky to implement, we want to
     * ensure that it is not providing false-positives
     */
    @Test
    public void testThatTheTestMechanismWorks() throws ExecutionException, InterruptedException {

        try {
            assertFirstOperationBlocksSecondOperationUntilComplete(
                    model -> model.containsAttribute("read1"),
                    model -> model.containsAttribute("read2"));

            fail("2 read operations should not block each other and therefore there must be a problem with the test mechanism");

        } catch (AssertionError e) {

            // expected behaviour
        }
    }

    /**
     * This test tests that we can have as many concurrent reads as we like whilst maintaing thread safety.
     *
     * This test issues 2 calls to {@link ThreadsafeModel#containsAttribute(String)}, the first of which blocks *within*
     * the bounds of the threadsafe locking mechanism inside ThreadsafeModel until the 2nd call is executed, upon which
     * the first call is unblocked.  The test performs this sequence in such a way so as to allow the 2nd call to write
     * its value to the "operationValues" list before the 1st call does, and we can assert that the 2nd value always makes it
     * into the list before the first does.
     *
     * This test proves therefore that a 2nd "read" operation can occur whilst a first "read" operation is in progress
     * (simulated by blocking the 1st call on the countDownLatch until the 2nd one is ready to release it).
     */
    private void assertFirstReadOperationDoesNotBlockSecondReadOperation(Consumer<Model> read1Operation, Consumer<Model> read2Operation) throws InterruptedException, ExecutionException {

        CountDownLatch countDownLatch = new CountDownLatch(1);

        Model wrappedModel = mock(Model.class);

        ThreadsafeModel threadsafeModel = new ThreadsafeModel(wrappedModel);

        List<String> operationValues = new ArrayList<>();

        Model read1WhenAnswer = doAnswer(invocation -> {
            countDownLatch.await();
{'fraction_non_alphanumeric': 0.07284712245415363, 'fraction_numerical': 0.004309980562832756, 'mean_word_length': 3.458929917106255, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 51, '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': '1469979', 'n_tokens_mistral': 2887, 'n_tokens_neox': 2749, 'n_words': 758}