@Test
public void shouldReturnLengthArrayIfTheArrayIsOneConsecutiveSequence() {
    int[] array = { 1, 3, 4, 5, 64, 4, 5, 6, 7, 8, 9, 98, -1, -2 };
    int sequenceLength = lcs.find(array);
    assertEquals(7, sequenceLength);
}

@Test
public void shouldReturnLengthArrayIfTheArrayIsOneConsecutiveSequence() {
    int[] array = { 1, 3, 4, 5, 64, 4, 5, 6, 7, 8, 9, 98, -1, -2 };
    int sequenceLength = lcs.findIterative(array);
    assertEquals(7, sequenceLength);
}

public int findIterative(int[] numbers) {
    if (numbers == null) {
        throw new IllegalArgumentException("You can't pass a null array as input.");
    }
    if (numbers.length == 0) {
        return 0;
    }
    int result = 0;
    int start = 0, end = 0;
    for (int i = 1; i < numbers.length; i++) {
        if (numbers[i - 1] < numbers[i]) {
            end = i;
        } else {
            start = i;
        }
        if (end - start > result) {
            result = end - start;
        }
    }
    return result + 1;
}

@Test
public void shouldReturnZeroIfTheArrayIsEmpty() {
    int[] array = {};
    int sequenceLength = lcs.find(array);
    assertEquals(0, sequenceLength);
}

@Test
public void shouldReturnZeroIfTheArrayIsEmpty() {
    int[] array = {};
    int sequenceLength = lcs.findIterative(array);
    assertEquals(0, sequenceLength);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullArrays() {
    lcs.find(null);
}

@Test(expected = IllegalArgumentException.class)
public void shouldNotAcceptNullArrays() {
    lcs.findIterative(null);
}

@Test
public void shouldReturnZeroIfThereIsNoAnyConsecutiveSequence() {
    int[] array = { 1, 2, 3, 4, 5, 6 };
    int sequenceLength = lcs.find(array);
    assertEquals(6, sequenceLength);
}

@Test
public void shouldReturnZeroIfThereIsNoAnyConsecutiveSequence() {
    int[] array = { 1, 2, 3, 4, 5, 6 };
    int sequenceLength = lcs.findIterative(array);
    assertEquals(6, sequenceLength);
}


@Test
public void onePlusOneEqualsTwoBasedOnCount() {
    String n1 = "01";
    String n2 = "01";
    String result = sumBinaryNumbers.sumBinaryNumbersBasedOnCount(n1, n2);
    assertEquals("010", result);
}

@Test
public void onePlusOneEqualsTwoBasedOnCount() {
    String n1 = "01";
    String n2 = "01";
    String result = sumBinaryNumbers.sumBinaryNumbersBasedOnCount(n1, n2);
    assertEquals("10", result);
}

public String sumBinaryNumbersBasedOnCount(String n1, String n2) {
    validateInput(n1, n2);
    StringBuilder result = new StringBuilder();
    int lengthVal1 = n1.length();
    int lengthVal2 = n2.length();
    int length = Math.max(lengthVal1, lengthVal2);
    int acc = 0;
    for (int i = 0; i < length; i++) {
        char c1 = i >= lengthVal1 ? '0' : n1.charAt(lengthVal1 - i - 1);
        char c2 = i >= lengthVal2 ? '0' : n2.charAt(lengthVal2 - i - 1);
        if (c1 == '0' && c2 == '0') {
            if (acc == 0) {
                result.append(0);
            } else {
                result.append(1);
            }
            acc = 0;
        } else if (c1 == '0' && c2 == '1' || c1 == '1' && c2 == '0') {
            if (acc == 0) {
                result.append(1);
                acc = 0;
            } else {
                result.append(0);
                acc = 1;
            }
        } else if (c1 == '1' && c2 == '1') {
            if (acc == 0) {
                result.append(0);
            } else {
                result.append(1);
            }
            acc = 1;
        } else {
            throw new IllegalArgumentException("Error parsing input data, review your parameters, should be a valid binary number!");
        }
    }
    result.append(acc);
    return result.reverse().toString();
}

public String sumBinaryNumbersBasedOnCount(String n1, String n2) {
    validateInput(n1, n2);
    StringBuilder result = new StringBuilder();
    int lengthVal1 = n1.length();
    int lengthVal2 = n2.length();
    int length = Math.max(lengthVal1, lengthVal2);
    int carry = 0;
    int sum = 0;
    int remainder = 0;
    for (int i = 0; i < length; i++) {
        int digit1 = i >= lengthVal1 ? 0 : n1.charAt(lengthVal1 - i - 1) - '0';
        int digit2 = i >= lengthVal2 ? 0 : n2.charAt(lengthVal2 - i - 1) - '0';
        if (digit1 < 2 && digit2 < 2) {
            sum = digit1 + digit2 + carry;
            carry = sum / 2;
            remainder = sum % 2;
            result.append((char) (remainder + '0'));
        } else {
            throw new IllegalArgumentException("Error parsing input data, review your parameters, should be a valid binary number!");
        }
    }
    if (carry != 0)
        result.append((char) (carry + '0'));
    return result.reverse().toString();
}


@Test
public void shouldReturnFalseIfInputStringIsEmpty() {
    assertFalse(uniqueChars.evaluate(""));
}

@Test
public void shouldReturnFalseIfInputStringIsEmpty() {
    assertTrue(uniqueChars.evaluate(""));
}

public boolean evaluate(String input) {
    validateInput(input);
    Map<String, Integer> chars = countCharAppearances(input);
    return containsDuplicatedChars(chars);
}

public boolean evaluate(String input) {
    validateInput(input);
    Map<String, Integer> chars = countCharAppearances(input);
    return !containsDuplicatedChars(chars);
}

@Test
public void shouldReturnFalseIfInputStringDoesNotContainDuplicatedChars() {
    assertFalse(uniqueChars.evaluate("pedro"));
}

@Test
public void shouldReturnFalseIfInputStringDoesNotContainDuplicatedChars() {
    assertTrue(uniqueChars.evaluate("pedro"));
}


@Test
public void testGetPluginArchives() {
    PluginRepository repository = new DefaultPluginRepository(pluginsPath, false);
    List<Path> pluginPaths = repository.getPluginPaths();
    assertEquals(5, pluginPaths.size());
    assertPathExists(pluginPaths, pluginsPath.resolve("plugin-1"));
    assertPathExists(pluginPaths, pluginsPath.resolve("plugin-2"));
    assertPathExists(pluginPaths, pluginsPath.resolve("plugin-3"));
    assertPathExists(pluginPaths, pluginsPath.resolve("target"));
    assertPathExists(pluginPaths, pluginsPath.resolve("build"));
}

@Test
public void testGetPluginArchives() {
    PluginRepository repository = new DefaultPluginRepository(pluginsPath, false);
    List<Path> pluginsPaths = repository.getPluginsPaths();
    assertEquals(5, pluginsPaths.size());
    assertPathExists(pluginsPaths, pluginsPath.resolve("plugin-1"));
    assertPathExists(pluginsPaths, pluginsPath.resolve("plugin-2"));
    assertPathExists(pluginsPaths, pluginsPath.resolve("plugin-3"));
    assertPathExists(pluginsPaths, pluginsPath.resolve("target"));
    assertPathExists(pluginsPaths, pluginsPath.resolve("build"));
}

@Override
public List<Path> getPluginPaths() {
    File[] pluginZips = pluginsRoot.toFile().listFiles(new ZipFileFilter());
    if ((pluginZips != null) && pluginZips.length > 0) {
        for (File pluginZip : pluginZips) {
            try {
                FileUtils.expandIfZip(pluginZip.toPath());
            } catch (IOException e) {
                log.error("Cannot expand plugin zip '{}'", pluginZip);
                log.error(e.getMessage(), e);
            }
        }
    }
    return super.getPluginPaths();
}

@Test
public void testDeletePluginPath() {
    PluginRepository repository = new DefaultPluginRepository(pluginsPath, false);
    assertTrue(Files.exists(pluginsPath.resolve("plugin-1.zip")));
    assertTrue(repository.deletePluginPath(pluginsPath.resolve("plugin-1")));
    assertFalse(Files.exists(pluginsPath.resolve("plugin-1.zip")));
    assertTrue(repository.deletePluginPath(pluginsPath.resolve("plugin-3")));
    assertFalse(repository.deletePluginPath(pluginsPath.resolve("plugin-4")));
    assertTrue(repository.deletePluginPath(pluginsPath.resolve("target")));
    assertTrue(repository.deletePluginPath(pluginsPath.resolve("build")));
    List<Path> pluginPaths = repository.getPluginPaths();
    assertEquals(1, pluginPaths.size());
    assertEquals(pluginsPath.relativize(pluginPaths.get(0)).toString(), "plugin-2");
}

@Test
public void testDeletePluginPath() {
    PluginRepository repository = new DefaultPluginRepository(pluginsPath, false);
    assertTrue(Files.exists(pluginsPath.resolve("plugin-1.zip")));
    assertTrue(repository.deletePluginPath(pluginsPath.resolve("plugin-1")));
    assertFalse(Files.exists(pluginsPath.resolve("plugin-1.zip")));
    assertTrue(repository.deletePluginPath(pluginsPath.resolve("plugin-3")));
    assertFalse(repository.deletePluginPath(pluginsPath.resolve("plugin-4")));
    assertTrue(repository.deletePluginPath(pluginsPath.resolve("target")));
    assertTrue(repository.deletePluginPath(pluginsPath.resolve("build")));
    List<Path> pluginsPaths = repository.getPluginsPaths();
    assertEquals(1, pluginsPaths.size());
    assertEquals(pluginsPath.relativize(pluginsPaths.get(0)).toString(), "plugin-2");
}


@Test
void testDeserializeYaml() {
    final String expected = "id: template-id\n" + "\n" + "info:\n" + "  name: Template Name\n" + "  author: forgedhallpass\n" + "  severity: high\n" + "  description: description\n" + "  reference: https://\n" + "  tags: tags\n" + "\n" + "requests:\n" + "- raw:\n" + "  - |-\n" + "    GET / HTTP/1.1\n" + "    Host: {{Hostname}}\n" + "    Accept: */*\n" + "    HeaderOne: {{param1}}\n" + "    HeaderTwo: {{param2}}\n" + "\n" + "  attack: clusterbomb\n" + "  payloads:\n" + "    param1:\n" + "    - headerOne\n" + "    - one\n" + "    param2:\n" + "    - headerTwo\n" + "    - two\n" + "\n" + "  matchers-condition: or\n" + "  matchers:\n" + "  - type: word\n" + "    part: all\n" + "    condition: and\n" + "    words:\n" + "    - word1\n" + "    - word2\n" + "  - type: status\n" + "    status:\n" + "    - 200\n" + "    - 500\n";
    final Template template = YamlUtil.load(expected, Template.class);
    Assertions.assertEquals(expected, Utils.normalizeTemplate(YamlUtil.dump(template)));
}

@Test
void testDeserializeYaml() {
    final String expected = "id: template-id\n" + "\n" + "info:\n" + "  name: Template Name\n" + "  author: forgedhallpass\n" + "  severity: high\n" + "  description: description\n" + "  reference: https://\n" + "  tags: tags\n" + "\n" + "requests:\n" + "- raw:\n" + "  - |-\n" + "    GET / HTTP/1.1\n" + "    Host: {{Hostname}}\n" + "    Accept: */*\n" + "    HeaderOne: {{param1}}\n" + "    HeaderTwo: {{param2}}\n" + "\n" + "  attack: clusterbomb\n" + "  payloads:\n" + "    param1:\n" + "    - headerOne\n" + "    - one\n" + "    param2:\n" + "    - headerTwo\n" + "    - two\n" + "\n" + "  matchers-condition: or\n" + "  matchers:\n" + "  - type: word\n" + "    part: all\n" + "    condition: and\n" + "    words:\n" + "    - word1\n" + "    - word2\n" + "  - type: status\n" + "    status:\n" + "    - 200\n" + "    - 500\n";
    final Template template = YamlUtil.load(expected, Template.class);
    Assertions.assertEquals(expected, TemplateUtils.normalizeTemplate(YamlUtil.dump(template)));
}

public static String normalizeTemplate(String yamlTemplate) {
    String result = yamlTemplate;
    for (String fieldName : Arrays.asList("info", "requests", "extractors")) {
        result = addNewLineBeforeProperty(result, fieldName);
    }
    result = result.contains("matchers-condition: ") ? addNewLineBeforeProperty(result, "matchers-condition", Utils.getEnumValues(Requests.MatchersCondition.class)) : addNewLineBeforeProperty(result, "matchers");
    result = result.contains("attack: ") ? addNewLineBeforeProperty(result, "attack", Utils.getEnumValues(Requests.AttackType.class)) : addNewLineBeforeProperty(result, "payloads");
    return result;
}


@Test
void testExtensiveYamlGeneration() {
    final Template template = createExtensiveTemplate();
    final String expected = "id: template-id\n" + "info:\n" + "  author: forgedhallpass\n" + "  name: Template Name\n" + "  severity: info\n" + "requests:\n" + "- matchers-condition: or\n" + "  attack: clusterbomb\n" + "  matchers:\n" + "  - part: all\n" + "    type: word\n" + "    words:\n" + "    - word1\n" + "    - word2\n" + "  - status:\n" + "    - 200\n" + "    - 500\n" + "    type: status\n" + "  payloads:\n" + "    param1:\n" + "    - headerOne\n" + "    - one\n" + "    param2:\n" + "    - headerTwo\n" + "    - two\n" + "  raw:\n" + "  - |-\n" + "    GET / HTTP/1.1\n" + "    Host: {{Hostname}}\n" + "    Accept: */*\n" + "    HeaderOne: {{param1}}\n" + "    HeaderTwo: {{param2}}\n";
    Assertions.assertEquals(expected, Utils.dumpYaml(template));
}

@Test
void testExtensiveYamlGeneration() {
    final Template template = createExtensiveTemplate();
    final String expected = "id: template-id\n" + "info:\n" + "  author: forgedhallpass\n" + "  classification:\n" + "    cvss-metrics: CVSS:3.0/\n" + "    cvss-score: 1.0\n" + "    cve-id: CVE-\n" + "    cwe-id: CWE-\n" + "  description: description\n" + "  name: Template Name\n" + "  reference: https://\n" + "  severity: info\n" + "  tags: tags\n" + "requests:\n" + "- matchers-condition: or\n" + "  attack: clusterbomb\n" + "  matchers:\n" + "  - part: all\n" + "    type: word\n" + "    words:\n" + "    - word1\n" + "    - word2\n" + "  - status:\n" + "    - 200\n" + "    - 500\n" + "    type: status\n" + "  payloads:\n" + "    param1:\n" + "    - headerOne\n" + "    - one\n" + "    param2:\n" + "    - headerTwo\n" + "    - two\n" + "  raw:\n" + "  - |-\n" + "    GET / HTTP/1.1\n" + "    Host: {{Hostname}}\n" + "    Accept: */*\n" + "    HeaderOne: {{param1}}\n" + "    HeaderTwo: {{param2}}\n";
    Assertions.assertEquals(expected, Utils.dumpYaml(template));
}

public static String dumpYaml(Object data) {
    final Representer representer = new Representer() {

        @Override
        protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
            if (Objects.isNull(propertyValue)) {
                return null;
            }
            return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
        }

        @Override
        protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
            if (!this.classTags.containsKey(javaBean.getClass())) {
                addClassTag(javaBean.getClass(), Tag.MAP);
            }
            return super.representJavaBean(properties, javaBean);
        }
    };
    final TypeDescription typeDescription = new TypeDescription(Requests.class, Tag.MAP);
    typeDescription.substituteProperty("matchers-condition", Requests.class, "getMatchersCondition", "setMatchersCondition");
    typeDescription.setExcludes("matchersCondition");
    representer.addTypeDescription(typeDescription);
    final DumperOptions options = new DumperOptions();
    options.setIndent(2);
    options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
    options.setPrettyFlow(true);
    final Yaml yaml = new Yaml(representer, options);
    return yaml.dumpAsMap(data);
}

public static String dumpYaml(Object data) {
    final Representer representer = new Representer() {

        @Override
        protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
            if (Objects.isNull(propertyValue)) {
                return null;
            }
            return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
        }

        @Override
        protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
            if (!this.classTags.containsKey(javaBean.getClass())) {
                addClassTag(javaBean.getClass(), Tag.MAP);
            }
            return super.representJavaBean(properties, javaBean);
        }
    };
    Map.of(Requests.class, List.of("matchersCondition"), Info.Classification.class, List.of("cvssMetrics", "cvssScore", "cveId", "cweId")).forEach((clazz, fields) -> {
        final TypeDescription requestsTypeDescription = new TypeDescription(clazz, Tag.MAP);
        fields.forEach(field -> requestsTypeDescription.substituteProperty(toSnakeCase(field), Info.Classification.class, createGetterMethodName(field), createSetterMethodName(field)));
        requestsTypeDescription.setExcludes(fields.toArray(String[]::new));
        representer.addTypeDescription(requestsTypeDescription);
    });
    final DumperOptions options = new DumperOptions();
    options.setIndent(2);
    options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
    options.setPrettyFlow(true);
    final Yaml yaml = new Yaml(representer, options);
    return yaml.dumpAsMap(data);
}


@Test
public void shouldIncludeSmallestFoundValueInFalsificationReport() throws Exception {
    int smallest = 101;
    try {
        testee.falisification(0, 0, smallest, Arrays.asList(0));
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + smallest);
    }
}

@Test
public void shouldIncludeSmallestFoundValueInFalsificationReport() throws Exception {
    int smallest = 101;
    try {
        testee.falisification(0, 0, smallest, Arrays.asList(0), a -> a.toString());
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + smallest);
    }
}

@Override
public void falisification(long seed, int examplesUsed, Object smallest, Throwable cause, List<Object> examples) {
    StringWriter sw = new StringWriter();
    cause.printStackTrace(new PrintWriter(sw));
    String failure = "\nCause was :-\n" + sw.toString();
    this.falsify(seed, examplesUsed, smallest, failure, examples);
}

@Override
public void falisification(long seed, int examplesUsed, Object smallest, List<Object> examples, Function<Object, String> toString) {
    this.falsify(seed, examplesUsed, smallest, "", examples, toString);
}

@Test
public void shouldIncludeNumberOfExaminedExamplesInFalsificationReport() throws Exception {
    int examples = 100;
    try {
        testee.falisification(0, examples, 0, Arrays.asList(0));
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + examples);
    }
}

@Test
public void shouldIncludeNumberOfExaminedExamplesInFalsificationReport() throws Exception {
    int examples = 100;
    try {
        testee.falisification(0, examples, 0, Arrays.asList(0), a -> a.toString());
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + examples);
    }
}

@Test
public void shouldIncludeAdditionalExamplesInFalsificationReport() throws Exception {
    try {
        testee.falisification(0, 0, 0, Arrays.asList(41, 42));
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("41", "42");
    }
}

@Test
public void shouldIncludeAdditionalExamplesInFalsificationReport() throws Exception {
    try {
        testee.falisification(0, 0, 0, Arrays.asList(41, 42), a -> a.toString());
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("41", "42");
    }
}

@Test
public void shouldIncludeOnlyFirst10ValuesInReport() throws Exception {
    try {
        List<Object> values = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
        testee.falisification(0, 0, 0, values);
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("1\n2\n3\n4\n5\n6\n7\n8\n9\n10");
        assertThat(expected.getMessage()).doesNotContain("11");
    }
}

@Test
public void shouldIncludeOnlyFirst10ValuesInReport() throws Exception {
    try {
        List<Object> values = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
        testee.falisification(0, 0, 0, values, a -> a.toString());
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("1\n2\n3\n4\n5\n6\n7\n8\n9\n10");
        assertThat(expected.getMessage()).doesNotContain("11");
    }
}

@Test
public void shouldIncludeSeedInFalsificationReport() throws Exception {
    long seed = 42;
    try {
        testee.falisification(42, 0, 0, Arrays.asList(0));
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + seed);
    }
}

@Test
public void shouldIncludeSeedInFalsificationReport() throws Exception {
    long seed = 42;
    try {
        testee.falisification(42, 0, 0, Arrays.asList(0), a -> a.toString());
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + seed);
    }
}


@Test
public void shouldPrintArrayFromAsWithPrecursorOfThreeElementsToDeepString() {
    try {
        qt().withFixedSeed(5).forAll(integers().allPositive(), integers().allPositive(), integers().allPositive()).asWithPrecursor((i, j, k) -> new Integer[] { i, j, k }).withStringFormat(i -> i.toString(), j -> j.toString(), k -> k.toString(), a -> Arrays.deepToString(a)).check((i, j, k, a) -> false);
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining("1, 1, 1, " + Arrays.deepToString(new Integer[] { 1, 1, 1 }));
    }
}

@Test
public void shouldPrintArrayFromAsWithPrecursorOfThreeElementsToDeepString() {
    try {
        qt().withFixedSeed(5).forAll(integers().allPositive(), integers().allPositive(), integers().allPositive()).asWithPrecursor((i, j, k) -> new Integer[] { i, j, k }).describedAs(i -> i.toString(), j -> j.toString(), k -> k.toString(), a -> Arrays.deepToString(a)).check((i, j, k, a) -> false);
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining("1, 1, 1, " + Arrays.deepToString(new Integer[] { 1, 1, 1 }));
    }
}

@Override
public Subject4<P, P2, P3, T> withStringFormat(Function<P, String> pToString, Function<P2, String> p2ToString, Function<P3, String> p3ToString, Function<T, String> tToString) {
    return new PrecursorTheoryBuilder3<P, P2, P3, T>(this.state, this.ps, this.assumptions, pToString, p2ToString, p3ToString, tToString);
}

@Test
public void shouldPrintArrayFromAsWithPrecursorOfFourElementsToDeepString() {
    try {
        qt().withFixedSeed(5).forAll(integers().allPositive(), integers().allPositive(), integers().allPositive(), integers().allPositive()).asWithPrecursor((i, j, k, l) -> new Integer[] { i, j, k, l }).withStringFormat(i -> i.toString(), j -> j.toString(), k -> k.toString(), l -> l.toString(), a -> Arrays.deepToString(a)).check((i, j, k, l, a) -> false);
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining("1, 1, 1, 1, " + Arrays.deepToString(new Integer[] { 1, 1, 1, 1 }));
    }
}

@Test
public void shouldPrintArrayFromAsWithPrecursorOfFourElementsToDeepString() {
    try {
        qt().withFixedSeed(5).forAll(integers().allPositive(), integers().allPositive(), integers().allPositive(), integers().allPositive()).asWithPrecursor((i, j, k, l) -> new Integer[] { i, j, k, l }).describedAs(i -> i.toString(), j -> j.toString(), k -> k.toString(), l -> l.toString(), a -> Arrays.deepToString(a)).check((i, j, k, l, a) -> false);
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining("1, 1, 1, 1, " + Arrays.deepToString(new Integer[] { 1, 1, 1, 1 }));
    }
}

@Override
public Subject5<P, P2, P3, P4, T> withStringFormat(Function<P, String> pToString, Function<P2, String> p2ToString, Function<P3, String> p3ToString, Function<P4, String> p4ToString, Function<T, String> tToString) {
    return new PrecursorTheoryBuilder4<P, P2, P3, P4, T>(this.state, this.ps, this.assumptions, pToString, p2ToString, p3ToString, p4ToString, tToString);
}

@Test
public void shouldPrintArrayFromAsOfFourElementsToDeepString() {
    try {
        qt().withFixedSeed(5).forAll(integers().allPositive(), integers().allPositive(), integers().allPositive(), integers().allPositive()).as((i, j, k, l) -> new Integer[] { i, j, k, l }).withStringFormat(a -> Arrays.deepToString(a)).check(i -> false);
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining(Arrays.deepToString(new Integer[] { 1, 1, 1, 1 }));
    }
}

@Test
public void shouldPrintArrayFromAsOfFourElementsToDeepString() {
    try {
        qt().withFixedSeed(5).forAll(integers().allPositive(), integers().allPositive(), integers().allPositive(), integers().allPositive()).as((i, j, k, l) -> new Integer[] { i, j, k, l }).describedAs(a -> Arrays.deepToString(a)).check(i -> false);
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining(Arrays.deepToString(new Integer[] { 1, 1, 1, 1 }));
    }
}

@Override
public Subject1<T> withStringFormat(Function<T, String> toString) {
    return new MappingTheoryBuilder<P, T>(this.state, this.ps, this.assumptions, this.conversion, toString);
}

@Test
public void shouldPrintArrayFromAsOfThreeElementsToDeepString() {
    try {
        qt().withFixedSeed(5).forAll(integers().allPositive(), integers().allPositive(), integers().allPositive()).as((i, j, k) -> new Integer[] { i, j, k }).withStringFormat(a -> Arrays.deepToString(a)).check(i -> false);
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining(Arrays.deepToString(new Integer[] { 1, 1, 1 }));
    }
}

@Test
public void shouldPrintArrayFromAsOfThreeElementsToDeepString() {
    try {
        qt().withFixedSeed(5).forAll(integers().allPositive(), integers().allPositive(), integers().allPositive()).as((i, j, k) -> new Integer[] { i, j, k }).describedAs(a -> Arrays.deepToString(a)).check(i -> false);
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining(Arrays.deepToString(new Integer[] { 1, 1, 1 }));
    }
}

@Test
public void shouldPrintArraysAsWithPrecursorToDeepString() {
    try {
        qt().withFixedSeed(5).forAll(arrays().ofIntegers(integers().all()).withLengthBetween(1, 100)).asWithPrecursor(a -> Arrays.asList(a)).withStringFormat(a -> Arrays.deepToString(a), l -> l.toString()).check((a, l) -> integerListIsReducedByRemovingAnItem().test(l));
        throw (new AssertionError("Test didn't fail"));
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining(Arrays.deepToString(new Integer[] { 0 }));
    }
}

@Test
public void shouldPrintArraysAsWithPrecursorToDeepString() {
    try {
        qt().withFixedSeed(5).forAll(arrays().ofIntegers(integers().all()).withLengthBetween(1, 100)).asWithPrecursor(a -> Arrays.asList(a)).describedAs(a -> Arrays.deepToString(a), l -> l.toString()).check((a, l) -> integerListIsReducedByRemovingAnItem().test(l));
        throw (new AssertionError("Test didn't fail"));
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining(Arrays.deepToString(new Integer[] { 0 }));
    }
}

@Override
public Subject2<P, T> withStringFormat(Function<P, String> pToString, Function<T, String> tToString) {
    return new PrecursorTheoryBuilder1<P, T>(this.state, this.ps, this.assumptions, pToString, tToString);
}

@Test
public void shouldUseCustomToStringForStringAndIntAsPerson() {
    try {
        qt().withFixedSeed(5).forAll(strings().basicLatinAlphabet().ofLengthBetween(3, 12), integers().between(0, 140)).assuming((s, i) -> s.codePoints().allMatch(j -> Character.isLetter(j))).as((i, j) -> new Person(i, j)).withStringFormat(p -> p.toUseToString()).check(i -> false);
        throw (new AssertionError("Test didn't fail"));
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining("Name:");
    }
}

@Test
public void shouldUseCustomToStringForStringAndIntAsPerson() {
    try {
        qt().withFixedSeed(5).forAll(strings().basicLatinAlphabet().ofLengthBetween(3, 12), integers().between(0, 140)).assuming((s, i) -> s.codePoints().allMatch(j -> Character.isLetter(j))).as((i, j) -> new Person(i, j)).describedAs(p -> p.toUseToString()).check(i -> false);
        throw (new AssertionError("Test didn't fail"));
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining("Name:");
    }
}

@Test
public void shouldUseCustomToStringOnPersonSubject() {
    try {
        forAllPeople().withStringFormat(p -> p.toUseToString()).check(i -> false);
        throw (new AssertionError("Test didn't fail"));
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining("Name:");
    }
}

@Test
public void shouldUseCustomToStringOnPersonSubject() {
    try {
        forAllPeople().describedAs(p -> p.toUseToString()).check(i -> false);
        throw (new AssertionError("Test didn't fail"));
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining("Name:");
    }
}

@Test
public void shouldUseCustomToStringForStringAndIntAsWithPrecursorPerson() {
    try {
        qt().withFixedSeed(5).forAll(strings().basicLatinAlphabet().ofLengthBetween(3, 12), integers().between(0, 140)).assuming((s, i) -> s.codePoints().allMatch(j -> Character.isLetter(j))).asWithPrecursor((i, j) -> new Person(i, j)).withStringFormat(s -> s.toString(), i -> i.toString(), p -> p.toUseToString()).check((i, s, p) -> false);
        throw (new AssertionError("Test didn't fail"));
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining("Name:");
    }
}

@Test
public void shouldUseCustomToStringForStringAndIntAsWithPrecursorPerson() {
    try {
        qt().withFixedSeed(5).forAll(strings().basicLatinAlphabet().ofLengthBetween(3, 12), integers().between(0, 140)).assuming((s, i) -> s.codePoints().allMatch(j -> Character.isLetter(j))).asWithPrecursor((i, j) -> new Person(i, j)).describedAs(s -> s.toString(), i -> i.toString(), p -> p.toUseToString()).check((i, s, p) -> false);
        throw (new AssertionError("Test didn't fail"));
    } catch (AssertionError error) {
        assertThat(error).hasMessageContaining("Name:");
    }
}

@Override
public Subject3<P, P2, T> withStringFormat(Function<P, String> pToString, Function<P2, String> p2ToString, Function<T, String> tToString) {
    return new PrecursorTheoryBuilder2<P, P2, T>(this.state, this.ps, this.assumptions, pToString, p2ToString, tToString);
}


@Test
public void shouldShrinkElementsOfFixedArrayByOneIfAllWithinRemainingCyclesOfTarget() {
    Source<String[]> testee = Arrays.arraysOf(Strings.ofBoundedLengthStrings(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT, 1, 8), String.class, 2);
    String[] input = new String[] { "\ud81b\udf33", "b" };
    String[] expected = new String[] { "\ufffd", "a" };
    assertThatSource(testee).shrinksArrayValueTo(input, expected, new ShrinkContext(0, 1000000000, Configuration.defaultPRNG(2)));
}

@Test
public void shouldShrinkElementsOfFixedArrayByOneIfAllWithinRemainingCyclesOfTarget() {
    Source<String[]> testee = Arrays.arraysOf(Strings.ofBoundedLengthStrings(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT, 1, 8), String.class, 2, 2);
    String[] input = new String[] { "\ud81b\udf33", "b" };
    String[] expected = new String[] { "\ufffd", "a" };
    assertThatSource(testee).shrinksArrayValueTo(input, expected, new ShrinkContext(0, 1000000000, Configuration.defaultPRNG(2)));
}

static <T> Source<T[]> arraysOf(Source<T> values, Class<T> c, int length) {
    return arraysOf(values, c, length, length);
}

@Test
public void shouldShrinkAFixedLengthArrayToAnArrayOfSameLength() {
    Source<Integer[]> testee = Arrays.arraysOf(Integers.range(1, Integer.MAX_VALUE), Integer.class, 4);
    Integer[] input = new Integer[] { -7, -2, 0, -4 };
    Integer[] shrunk = testee.shrink(input, new ShrinkContext(0, 100, Configuration.defaultPRNG(-2))).iterator().next();
    isArrayAfterShrinking(shrunk);
    isExpectedLength(shrunk, 4);
}

@Test
public void shouldShrinkAFixedLengthArrayToAnArrayOfSameLength() {
    Source<Integer[]> testee = Arrays.arraysOf(Integers.range(1, Integer.MAX_VALUE), Integer.class, 4, 4);
    Integer[] input = new Integer[] { -7, -2, 0, -4 };
    Integer[] shrunk = testee.shrink(input, new ShrinkContext(0, 100, Configuration.defaultPRNG(-2))).iterator().next();
    isArrayAfterShrinking(shrunk);
    isExpectedLength(shrunk, 4);
}

@Test
public void shouldShrinkNegativeIntegersByOneInAFixedLengthArrayWhereAllValuesAreWithinRemainingCyclesOfTarget() {
    Source<Integer[]> testee = Arrays.arraysOf(Integers.range(-9, 0), Integer.class, 3);
    assertThatSource(testee).shrinksArrayValueTo(new Integer[] { -6, -3, -1 }, new Integer[] { -5, -2, 0 }, new ShrinkContext(0, 100, Configuration.defaultPRNG(2)));
}

@Test
public void shouldShrinkNegativeIntegersByOneInAFixedLengthArrayWhereAllValuesAreWithinRemainingCyclesOfTarget() {
    Source<Integer[]> testee = Arrays.arraysOf(Integers.range(-9, 0), Integer.class, 3, 3);
    assertThatSource(testee).shrinksArrayValueTo(new Integer[] { -6, -3, -1 }, new Integer[] { -5, -2, 0 }, new ShrinkContext(0, 100, Configuration.defaultPRNG(2)));
}

@Test
public void shouldDescribeArrayContents() {
    Source<Integer[]> testee = Arrays.arraysOf(Integers.range(0, 1), Integer.class, 2);
    Integer[] anArray = { 1, 2, 3 };
    assertThat(testee.asString(anArray)).isEqualTo("[1, 2, 3]");
}

@Test
public void shouldDescribeArrayContents() {
    Source<Integer[]> testee = Arrays.arraysOf(Integers.range(0, 1), Integer.class, 2, 2);
    Integer[] anArray = { 1, 2, 3 };
    assertThat(testee.asString(anArray)).isEqualTo("[1, 2, 3]");
}

@Test
public void shouldGenerateAllPossibleArraysWithinDomain() {
    Source<Integer[]> testee = Arrays.arraysOf(Integers.range(1, 2), Integer.class, 2);
    assertThatSource(testee).generatesAllOf(new Integer[] { 1, 1 }, new Integer[] { 1, 2 }, new Integer[] { 2, 1 }, new Integer[] { 2, 2 });
}

@Test
public void shouldGenerateAllPossibleArraysWithinDomain() {
    Source<Integer[]> testee = Arrays.arraysOf(Integers.range(1, 2), Integer.class, 2, 2);
    assertThatSource(testee).generatesAllOf(new Integer[] { 1, 1 }, new Integer[] { 1, 2 }, new Integer[] { 2, 1 }, new Integer[] { 2, 2 });
}

@Test
public void shouldNotShrinkAnEmptyFixedSizeArray() {
    Source<Character[]> testee = Arrays.arraysOf(Characters.ofCharacters(FIRST_CODEPOINT, ASCII_LAST_CODEPOINT), Character.class, 0);
    assertThatSource(testee).cannotShrink(new Character[] {});
}

@Test
public void shouldNotShrinkAnEmptyFixedSizeArray() {
    Source<Character[]> testee = Arrays.arraysOf(Characters.ofCharacters(FIRST_CODEPOINT, ASCII_LAST_CODEPOINT), Character.class, 0, 0);
    assertThatSource(testee).cannotShrink(new Character[] {});
}


@Test
public void shouldProvideAtLeastFiveDistinctFalsifyingValuesForAFixedListWithDomainBelowZeroMarker() {
    Predicate<List<Integer>> sumLessThanMinus5000 = list -> list.stream().mapToInt(Number::intValue).sum() <= -500000;
    List<Integer> target = Arrays.asList(-3523, -3523, -3523, -3523, -3523, -3523, -3523);
    assertThatFor(listsOf(Integers.range(-23435, -3523), arrayListCollector(), 7), withShrinkCycles(3000)).check(sumLessThanMinus5000);
    atLeastFiveDistinctFalsifyingValuesAreFound();
    smallestValueIsEqualTo(target);
}

@Test
public void shouldProvideAtLeastFiveDistinctFalsifyingValuesForAFixedListWithDomainBelowZeroMarker() {
    Predicate<List<Integer>> sumLessThanMinus5000 = list -> list.stream().mapToInt(Number::intValue).sum() <= -500000;
    List<Integer> target = Arrays.asList(-3523, -3523, -3523, -3523, -3523, -3523, -3523);
    assertThatFor(listsOf(Integers.range(-23435, -3523), arrayListCollector(), 7, 7), withShrinkCycles(3000)).check(sumLessThanMinus5000);
    atLeastFiveDistinctFalsifyingValuesAreFound();
    smallestValueIsEqualTo(target);
}

static <T> Source<List<T>> listsOf(Source<T> generator, Collector<T, List<T>, List<T>> collector, int fixedSize) {
    return Source.of((prng, step) -> Collections.nCopies(fixedSize, prng).stream().map(p -> generator.next(p, step)).collect(collector)).withShrinker(shrinkFixedList(generator, collector));
}

@Test
public void shouldFindAFixedLengthListEqualToTargetWithItemsWhoseDomainIsBelowZeroMarker() {
    assertThatFor(alternatingFixedListsOf(Integers.range(-6000, -2001), 5)).check(i -> false);
    smallestValueIsEqualTo(Arrays.asList(-2001, -2001, -2001, -2001, -2001));
}

@Test
public void shouldFindAFixedLengthListEqualToTargetWithItemsWhoseDomainIsBelowZeroMarker() {
    assertThatFor(alternatingBoundedListsOf(Integers.range(-6000, -2001), 5, 5)).check(i -> false);
    smallestValueIsEqualTo(Arrays.asList(-2001, -2001, -2001, -2001, -2001));
}

static <T> Source<List<T>> alternatingFixedListsOf(Source<T> generator, int fixedSize) {
    return Compositions.interleave(listsOf(generator, arrayListCollector(), fixedSize), listsOf(generator, linkedListCollector(), fixedSize));
}

@Test
public void shouldReturnALinkedListAfterShrinkageIfOneGenerated() {
    assertThatFor(listsOf(Integers.range(-4000, 4000), linkedListCollector(), 6)).check(i -> false);
    isLinkedListAfterShrinking();
}

@Test
public void shouldReturnALinkedListAfterShrinkageIfOneGenerated() {
    assertThatFor(listsOf(Integers.range(-4000, 4000), linkedListCollector(), 6, 6)).check(i -> false);
    isLinkedListAfterShrinking();
}

@Test
public void shouldCreateAListOfFixedLengthIntegerArrayListsWithItemsOfDecreasingAbsValueOrderWhoseDomainIsBelowZeroMarker() {
    assertThatFor(listsOf(Integers.range(-4000, -1000), arrayListCollector(), 6)).check(list -> false);
    listIsInDecreasingAbsValueOrder();
}

@Test
public void shouldCreateAListOfFixedLengthIntegerArrayListsWithItemsOfDecreasingAbsValueOrderWhoseDomainIsBelowZeroMarker() {
    assertThatFor(listsOf(Integers.range(-4000, -1000), arrayListCollector(), 6, 6)).check(list -> false);
    listIsInDecreasingAbsValueOrder();
}

@Test
public void shoulCreateAListWhoseElementsAllPassTheInitialAssumptionsPredicateTest() {
    Predicate<List<Integer>> sumGreaterThanOrEqualTo50 = list -> list.stream().mapToInt(Number::intValue).sum() >= 50;
    assertThatFor(listsOf(Integers.range(-40000, 40000), linkedListCollector(), 6)).assuming(sumGreaterThanOrEqualTo50).check(list -> false);
    for (List<Integer> list : listOfShrunkenItems()) {
        assertTrue(list.stream().mapToInt(Number::intValue).sum() >= 50);
    }
}

@Test
public void shoulCreateAListWhoseElementsAllPassTheInitialAssumptionsPredicateTest() {
    Predicate<List<Integer>> sumGreaterThanOrEqualTo50 = list -> list.stream().mapToInt(Number::intValue).sum() >= 50;
    assertThatFor(listsOf(Integers.range(-40000, 40000), linkedListCollector(), 6, 6)).assuming(sumGreaterThanOrEqualTo50).check(list -> false);
    for (List<Integer> list : listOfShrunkenItems()) {
        assertTrue(list.stream().mapToInt(Number::intValue).sum() >= 50);
    }
}

@Test
public void shouldFindAFixedLengthListEqualToTargetWithItemsWhoseDomainIsWithinThePositiveIntegers() {
    assertThatFor(alternatingFixedListsOf(Integers.range(1, Integer.MAX_VALUE), 5)).check(i -> false);
    smallestValueIsEqualTo(Arrays.asList(1, 1, 1, 1, 1));
}

@Test
public void shouldFindAFixedLengthListEqualToTargetWithItemsWhoseDomainIsWithinThePositiveIntegers() {
    assertThatFor(alternatingBoundedListsOf(Integers.range(1, Integer.MAX_VALUE), 5, 5)).check(i -> false);
    smallestValueIsEqualTo(Arrays.asList(1, 1, 1, 1, 1));
}

@Test
public void shouldCreateAListOfFixedLengthIntegerLinkedListsWithItemsOfDecreasingAbsValueOrderWhoseDomainIsAcrossZeroMarker() {
    assertThatFor(listsOf(Integers.range(-40000, 40000), linkedListCollector(), 6)).check(list -> false);
    listIsInDecreasingAbsValueOrder();
}

@Test
public void shouldCreateAListOfFixedLengthIntegerLinkedListsWithItemsOfDecreasingAbsValueOrderWhoseDomainIsAcrossZeroMarker() {
    assertThatFor(listsOf(Integers.range(-40000, 40000), linkedListCollector(), 6, 6)).check(list -> false);
    listIsInDecreasingAbsValueOrder();
}

@Test
public void shouldFindAFixedLengthListEqualToTargetWithItemsWhoseDomainIsAcrossZeroMarker() {
    assertThatFor(listsOf(Integers.range(-4000, 4000), linkedListCollector(), 6)).check(i -> false);
    smallestValueIsEqualTo(new LinkedList<Integer>(Arrays.asList(0, 0, 0, 0, 0, 0)));
}

@Test
public void shouldFindAFixedLengthListEqualToTargetWithItemsWhoseDomainIsAcrossZeroMarker() {
    assertThatFor(listsOf(Integers.range(-4000, 4000), linkedListCollector(), 6, 6)).check(i -> false);
    smallestValueIsEqualTo(new LinkedList<Integer>(Arrays.asList(0, 0, 0, 0, 0, 0)));
}

@Test
public void shouldFindAFixedLengthListEqualToTargetWithItemsWhoseDomainHasUpperBoundZero() {
    assertThatFor(listsOf(Integers.range(-4000, 0), linkedListCollector(), 7)).check(i -> false);
    smallestValueIsEqualTo(new LinkedList<Integer>(Arrays.asList(0, 0, 0, 0, 0, 0, 0)));
}

@Test
public void shouldFindAFixedLengthListEqualToTargetWithItemsWhoseDomainHasUpperBoundZero() {
    assertThatFor(listsOf(Integers.range(-4000, 0), linkedListCollector(), 7, 7)).check(i -> false);
    smallestValueIsEqualTo(new LinkedList<Integer>(Arrays.asList(0, 0, 0, 0, 0, 0, 0)));
}

@Test
public void willGetStuckAtLocalMinimaWhenOneValueInListShrinksToBottomWithoutFalsifiying() {
    Predicate<List<Integer>> allEntriesOdd = list -> list.stream().filter(v -> v % 2 == 0).findFirst().isPresent();
    assertThatFor(listsOf(Integers.range(-2333, 232), linkedListCollector(), 5), withShrinkCycles(3000)).check(allEntriesOdd);
    atLeastFiveDistinctFalsifyingValuesAreFound();
}

@Test
public void willGetStuckAtLocalMinimaWhenOneValueInListShrinksToBottomWithoutFalsifiying() {
    Predicate<List<Integer>> allEntriesOdd = list -> list.stream().filter(v -> v % 2 == 0).findFirst().isPresent();
    assertThatFor(listsOf(Integers.range(-2333, 232), linkedListCollector(), 5, 5), withShrinkCycles(3000)).check(allEntriesOdd);
    atLeastFiveDistinctFalsifyingValuesAreFound();
}

@Test
public void shouldCreateAListOfFixedLengthIntegerArrayListsWithItemsOfDecreasingAbsValueOrderWhoseDomainIsAboveZeroMarker() {
    assertThatFor(listsOf(Integers.range(50, 8940), arrayListCollector(), 6)).check(list -> false);
    listIsInDecreasingAbsValueOrder();
}

@Test
public void shouldCreateAListOfFixedLengthIntegerArrayListsWithItemsOfDecreasingAbsValueOrderWhoseDomainIsAboveZeroMarker() {
    assertThatFor(listsOf(Integers.range(50, 8940), arrayListCollector(), 6, 6)).check(list -> false);
    listIsInDecreasingAbsValueOrder();
}

@Test
public void shouldReturnAnArrayListAfterShrinkageIfOneGenerated() {
    assertThatFor(alternatingFixedListsOf(Integers.range(-4000, 4000), 6)).check(i -> false);
    isArrayListAfterShrinking();
}

@Test
public void shouldReturnAnArrayListAfterShrinkageIfOneGenerated() {
    assertThatFor(alternatingBoundedListsOf(Integers.range(-4000, 4000), 6, 6)).check(i -> false);
    isArrayListAfterShrinking();
}

@Test
public void shouldFindAFixedLengthListEqualToTargetWithItemsWhoseDomainHasLowerBoundZero() {
    assertThatFor(listsOf(Integers.range(0, 6000), linkedListCollector(), 7)).check(i -> false);
    smallestValueIsEqualTo(new LinkedList<Integer>(Arrays.asList(0, 0, 0, 0, 0, 0, 0)));
}

@Test
public void shouldFindAFixedLengthListEqualToTargetWithItemsWhoseDomainHasLowerBoundZero() {
    assertThatFor(listsOf(Integers.range(0, 6000), linkedListCollector(), 7, 7)).check(i -> false);
    smallestValueIsEqualTo(new LinkedList<Integer>(Arrays.asList(0, 0, 0, 0, 0, 0, 0)));
}


@Test
public void shouldShrinkAFixedLengthArrayListToAnArrayListOfSameLength() {
    Source<List<Integer>> testee = listsOf(Integers.range(-9, 9), arrayListCollector(), 5);
    List<Integer> input = Arrays.asList(-6, -3, 0, -3, -5);
    List<Integer> shrunk = testee.shrink(input, someShrinkContext()).iterator().next();
    isExpectedLength(shrunk, 5);
    isArrayListAfterShrinking(shrunk);
}

@Test
public void shouldShrinkAFixedLengthArrayListToAnArrayListOfSameLength() {
    Source<List<Integer>> testee = listsOf(Integers.range(-9, 9), arrayListCollector(), 5, 5);
    List<Integer> input = Arrays.asList(-6, -3, 0, -3, -5);
    List<Integer> shrunk = testee.shrink(input, someShrinkContext()).iterator().next();
    isExpectedLength(shrunk, 5);
    isArrayListAfterShrinking(shrunk);
}

@Test
public void shouldShrinkFixedLengthPositivelyRangedLinkedLists() {
    Source<List<Integer>> testee = listsOf(Integers.range(1, Integer.MAX_VALUE), linkedListCollector(), 4);
    assertThatSource(testee).shrinksConformTo(new LinkedList<Integer>(Arrays.asList(4342, 5435, 757, 12)), allItemsHeadTowardsLowerAbsoluteValue(1, Integer.MAX_VALUE, new LinkedList<Integer>(Arrays.asList(4342, 5435, 757, 12))), someShrinkContext());
}

@Test
public void shouldShrinkFixedLengthPositivelyRangedLinkedLists() {
    Source<List<Integer>> testee = listsOf(Integers.range(1, Integer.MAX_VALUE), linkedListCollector(), 4, 4);
    assertThatSource(testee).shrinksConformTo(new LinkedList<Integer>(Arrays.asList(4342, 5435, 757, 12)), allItemsHeadTowardsLowerAbsoluteValue(1, Integer.MAX_VALUE, new LinkedList<Integer>(Arrays.asList(4342, 5435, 757, 12))), someShrinkContext());
}

@Test
public void shouldShrinkAFixedLengthLinkedListToALinkedListOfSameLength() {
    Source<List<Integer>> testee = listsOf(Integers.range(-9, 9), linkedListCollector(), 5);
    List<Integer> input = new LinkedList<Integer>(Arrays.asList(-6, -3, 0, -3, -5));
    List<Integer> shrunk = testee.shrink(input, someShrinkContext()).iterator().next();
    isExpectedLength(shrunk, 5);
    isLinkedListAfterShrinking(shrunk);
}

@Test
public void shouldShrinkAFixedLengthLinkedListToALinkedListOfSameLength() {
    Source<List<Integer>> testee = listsOf(Integers.range(-9, 9), linkedListCollector(), 5, 5);
    List<Integer> input = new LinkedList<Integer>(Arrays.asList(-6, -3, 0, -3, -5));
    List<Integer> shrunk = testee.shrink(input, someShrinkContext()).iterator().next();
    isExpectedLength(shrunk, 5);
    isLinkedListAfterShrinking(shrunk);
}

@Test
public void shouldShrinkAllIntegersByOneInAFixedLengthArrayListWhereAllValuesAreWithinRemainingCyclesOfTarget() {
    Source<List<Integer>> testee = alternatingFixedListsOf(Integers.range(-9, 9), 3);
    assertThatSource(testee).shrinksValueTo(Arrays.asList(-6, 3, -1), Arrays.asList(-5, 2, 0), someShrinkContext());
}

@Test
public void shouldShrinkAllIntegersByOneInAFixedLengthArrayListWhereAllValuesAreWithinRemainingCyclesOfTarget() {
    Source<List<Integer>> testee = alternatingBoundedListsOf(Integers.range(-9, 9), 3, 3);
    assertThatSource(testee).shrinksValueTo(Arrays.asList(-6, 3, -1), Arrays.asList(-5, 2, 0), someShrinkContext());
}

@Test
public void shouldNotShrinkAnEmptyFixedSizeLinkedList() {
    Source<List<Integer>> testee = listsOf(Integers.range(1, Integer.MAX_VALUE), linkedListCollector(), 0);
    assertThatSource(testee).cannotShrink(new LinkedList<Integer>());
}

@Test
public void shouldNotShrinkAnEmptyFixedSizeLinkedList() {
    Source<List<Integer>> testee = listsOf(Integers.range(1, Integer.MAX_VALUE), linkedListCollector(), 0, 0);
    assertThatSource(testee).cannotShrink(new LinkedList<Integer>());
}

@SuppressWarnings("unchecked")
@Test
public void shouldGenerateBothTypesOfList() {
    Source<List<Integer>> testee = alternatingFixedListsOf(Integers.range(1, 1), 5);
    assertThatSource(testee).generatesAllOf(Arrays.asList(1, 1, 1, 1, 1), new LinkedList<Integer>(Arrays.asList(1, 1, 1, 1, 1)));
}

@SuppressWarnings("unchecked")
@Test
public void shouldGenerateBothTypesOfList() {
    Source<List<Integer>> testee = alternatingBoundedListsOf(Integers.range(1, 1), 5, 5);
    assertThatSource(testee).generatesAllOf(Arrays.asList(1, 1, 1, 1, 1), new LinkedList<Integer>(Arrays.asList(1, 1, 1, 1, 1)));
}

@Test
public void shouldShrinkNegativeIntegersByOneInAFixedLengthLinkedListWhereAllValuesAreWithinRemainingCyclesOfTarget() {
    Source<List<Integer>> testee = listsOf(Integers.range(-9, 0), linkedListCollector(), 3);
    assertThatSource(testee).shrinksValueTo(new LinkedList<Integer>(Arrays.asList(-6, -3, -1)), new LinkedList<Integer>(Arrays.asList(-5, -2, 0)), someShrinkContext());
}

@Test
public void shouldShrinkNegativeIntegersByOneInAFixedLengthLinkedListWhereAllValuesAreWithinRemainingCyclesOfTarget() {
    Source<List<Integer>> testee = listsOf(Integers.range(-9, 0), linkedListCollector(), 3, 3);
    assertThatSource(testee).shrinksValueTo(new LinkedList<Integer>(Arrays.asList(-6, -3, -1)), new LinkedList<Integer>(Arrays.asList(-5, -2, 0)), someShrinkContext());
}

@Test
public void shouldShrinkFixedLengthIntegerArrayLists() {
    Source<List<Integer>> testee = listsOf(Integers.range(-2000, 2000), arrayListCollector(), 5);
    assertThatSource(testee).shrinksConformTo(Arrays.asList(1400, 2000, -1000, -1235, 1052), allItemsHeadTowardsLowerAbsoluteValue(-2000, 2000, Arrays.asList(1400, 2000, -1000, -1235, 1052)), someShrinkContext());
}

@Test
public void shouldShrinkFixedLengthIntegerArrayLists() {
    Source<List<Integer>> testee = listsOf(Integers.range(-2000, 2000), arrayListCollector(), 5, 5);
    assertThatSource(testee).shrinksConformTo(Arrays.asList(1400, 2000, -1000, -1235, 1052), allItemsHeadTowardsLowerAbsoluteValue(-2000, 2000, Arrays.asList(1400, 2000, -1000, -1235, 1052)), someShrinkContext());
}

@Test
public void shouldShrinkPositiveIntegersByOneInAFixedLengthArrayListWhereAllValuesAreWithinRemainingCyclesOfTarget() {
    Source<List<Integer>> testee = listsOf(Integers.range(0, 9), arrayListCollector(), 5);
    assertThatSource(testee).shrinksValueTo(Arrays.asList(1, 2, 3, 4, 5), Arrays.asList(0, 1, 2, 3, 4), someShrinkContext());
}

@Test
public void shouldShrinkPositiveIntegersByOneInAFixedLengthArrayListWhereAllValuesAreWithinRemainingCyclesOfTarget() {
    Source<List<Integer>> testee = listsOf(Integers.range(0, 9), arrayListCollector(), 5, 5);
    assertThatSource(testee).shrinksValueTo(Arrays.asList(1, 2, 3, 4, 5), Arrays.asList(0, 1, 2, 3, 4), someShrinkContext());
}

@Test
public void shouldShrinkAFixedLengthListIfOneEntryCanStillBeShrunk() {
    Source<List<Integer>> testee = listsOf(Integers.range(0, 9), arrayListCollector(), 5);
    assertThatSource(testee).shrinksValueTo(Arrays.asList(0, 0, 3, 0, 0), Arrays.asList(0, 0, 2, 0, 0), someShrinkContext());
}

@Test
public void shouldShrinkAFixedLengthListIfOneEntryCanStillBeShrunk() {
    Source<List<Integer>> testee = listsOf(Integers.range(0, 9), arrayListCollector(), 5, 5);
    assertThatSource(testee).shrinksValueTo(Arrays.asList(0, 0, 3, 0, 0), Arrays.asList(0, 0, 2, 0, 0), someShrinkContext());
}

@Test
public void shouldNotShrinkAnEmptyFixedSizeArrayList() {
    Source<List<Integer>> testee = listsOf(Integers.range(1, Integer.MAX_VALUE), arrayListCollector(), 0);
    assertThatSource(testee).cannotShrink(new ArrayList<>());
}

@Test
public void shouldNotShrinkAnEmptyFixedSizeArrayList() {
    Source<List<Integer>> testee = listsOf(Integers.range(1, Integer.MAX_VALUE), arrayListCollector(), 0, 0);
    assertThatSource(testee).cannotShrink(new ArrayList<>());
}

@Test
public void shouldShrinkFixedLengthNegativelyRangedArrayLists() {
    Source<List<Integer>> testee = alternatingFixedListsOf(Integers.range(-2000, -1000), 5);
    assertThatSource(testee).shrinksConformTo(Arrays.asList(-1400, -2000, -1000, -1235, -1052), allItemsHeadTowardsLowerAbsoluteValue(-2000, -1000, Arrays.asList(-1400, -2000, -1000, -1235, -1052)), someShrinkContext());
}

@Test
public void shouldShrinkFixedLengthNegativelyRangedArrayLists() {
    Source<List<Integer>> testee = alternatingBoundedListsOf(Integers.range(-2000, -1000), 5, 5);
    assertThatSource(testee).shrinksConformTo(Arrays.asList(-1400, -2000, -1000, -1235, -1052), allItemsHeadTowardsLowerAbsoluteValue(-2000, -1000, Arrays.asList(-1400, -2000, -1000, -1235, -1052)), someShrinkContext());
}


@Test
public void shouldShrinkCodePointToMinimumCodePointByOneInIntervalWhereDistanceToTargetIsLessThanRemainingCycles() {
    int target = 0x0021;
    assertThatFor(Longs.codePoints(0x0020, 0x007E), withShrinkCycles(18)).check(i -> false);
    listIsDecreasingByAtMostOne();
    smallestValueIsEqualTo(target);
}

@Test
public void shouldShrinkCodePointToMinimumCodePointByOneInIntervalWhereDistanceToTargetIsLessThanRemainingCycles() {
    int target = 0x0021;
    assertThatFor(CodePoints.codePoints(0x0020, 0x007E), withShrinkCycles(18)).check(i -> false);
    listIsDecreasingByAtMostOne();
    smallestValueIsEqualTo(target);
}

static Source<Long> codePoints(int startInclusive, int endInclusive) {
    return codePoints(startInclusive, endInclusive, FIRST_NON_WHITESPACE_CHARACTER_IN_BLC);
}

@Test
public void shouldShrinkCodePointToMinimumCodePointInIntervalWhereDistanceToTargetIsInitiallyGreaterThanRemainingCycles() {
    int target = 0x0021;
    assertThatFor(Longs.codePoints(0x0020, 0x007E), withShrinkCycles(6)).check(i -> false);
    listIsInDecreasingAbsValueOrder();
    smallestValueIsEqualTo(target);
}

@Test
public void shouldShrinkCodePointToMinimumCodePointInIntervalWhereDistanceToTargetIsInitiallyGreaterThanRemainingCycles() {
    int target = 0x0021;
    assertThatFor(CodePoints.codePoints(0x0020, 0x007E), withShrinkCycles(6)).check(i -> false);
    listIsInDecreasingAbsValueOrder();
    smallestValueIsEqualTo(target);
}


@Test
public void shouldShrinkCodePointsInRightDirectionWhenEndInclusiveIsLessThanTarget() {
    Source<Long> testee = Longs.codePoints(0x0000, 0x0020);
    assertThatSource(testee).shrinksConformTo(0x0017l, i -> i <= 0x0017 && i >= 0x0000, new ShrinkContext(0, 30, Configuration.defaultPRNG(0)));
}

@Test
public void shouldShrinkCodePointsInRightDirectionWhenEndInclusiveIsLessThanTarget() {
    Source<Long> testee = CodePoints.codePoints(0x0000, 0x0020);
    assertThatSource(testee).shrinksConformTo(0x0017l, i -> i <= 0x0017 && i >= 0x0000, new ShrinkContext(0, 30, Configuration.defaultPRNG(0)));
}

static Source<Long> codePoints(int startInclusive, int endInclusive) {
    long target = setTargetForCodePoints(startInclusive, endInclusive);
    return Source.of((prng, step) -> generateValidCodePoint(prng, startInclusive, endInclusive)).withShrinker(shrinkCodePoint(target));
}


@Test
public void shouldShrinkFixedSizedListsAsExpected() {
    Source<List<String>> testee = lists().allListsOf(Strings.numericStrings()).ofSize(5);
    assertThatSource(testee).shrinksValueTo(java.util.Arrays.asList("5", "8", "-45", "60", "0"), java.util.Arrays.asList("4", "7", "-44", "59", "0"));
}

@Test
public void shouldShrinkFixedSizedListsAsExpected() {
    Source<List<String>> testee = lists().allListsOf(strings().numeric()).ofSize(5);
    assertThatSource(testee).shrinksValueTo(java.util.Arrays.asList("5", "8", "-45", "60", "0"), java.util.Arrays.asList("4", "7", "-44", "59", "0"));
}

public Source<List<T>> ofSize(int size) {
    checkSizeNotNegative(size);
    return Lists.alternatingFixedListsOf(source, size);
}

public Source<List<T>> ofSize(int size) {
    return ofSizeBetween(size, size);
}


@Test
public void shouldNotShrinkToUndefinedCharacterInStringWhereRemainingCyclesGreaterThanDomainSize() {
    assertThatFor(Strings.ofFixedLengthStrings(0, 196607, 5), withShrinkCycles(196608)).check(i -> false);
    listContainsOnlyDefinedCharacters();
}

@Test
public void shouldNotShrinkToUndefinedCharacterInStringWhereRemainingCyclesGreaterThanDomainSize() {
    assertThatFor(Strings.ofBoundedLengthStrings(0, 196607, 5, 5), withShrinkCycles(196608)).check(i -> false);
    listContainsOnlyDefinedCharacters();
}

static Source<String> ofBoundedLengthStrings(int minCodePoint, int maxCodePoint, int minLength, int maxLength) {
    return Source.of((prng, step) -> generateFixedLengthString(Longs.codePoints(minCodePoint, maxCodePoint), (int) prng.generateRandomLongWithinInterval(minLength, maxLength), prng, step)).withShrinker(Strings.shrinkBoundedLengthString(minLength, minCodePoint, maxCodePoint));
}

static Source<String> ofBoundedLengthStrings(int minCodePoint, int maxCodePoint, int minLength, int maxLength) {
    return Source.of((prng, step) -> generateFixedLengthString(CodePoints.codePoints(minCodePoint, maxCodePoint), (int) prng.generateRandomLongWithinInterval(minLength, maxLength), prng, step)).withShrinker(Strings.shrinkBoundedLengthString(minLength, minCodePoint, maxCodePoint));
}

@Test
public void shouldNotShrinkToUndefinedCharacterInStringWhereRemainingCyclesEqualsTheDomainSize() {
    assertThatFor(Strings.ofFixedLengthStrings(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT, 6), withShrinkCycles(Character.MAX_CODE_POINT)).check(i -> false);
    listContainsOnlyDefinedCharacters();
}

@Test
public void shouldNotShrinkToUndefinedCharacterInStringWhereRemainingCyclesEqualsTheDomainSize() {
    assertThatFor(Strings.ofBoundedLengthStrings(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT, 6, 6), withShrinkCycles(Character.MAX_CODE_POINT)).check(i -> false);
    listContainsOnlyDefinedCharacters();
}

@Test
public void shouldOnlyGenerateCharactersWhenFixedLengthIsSetToOne() {
    assertThatFor(Strings.ofFixedLengthStrings(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT, 1)).check(i -> false);
    listContainsOnlyBMPCharacters();
}

@Test
public void shouldOnlyGenerateCharactersWhenFixedLengthIsSetToOne() {
    assertThatFor(Strings.ofBoundedLengthStrings(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT, 1, 1)).check(i -> false);
    listContainsOnlyBMPCharacters();
}

static Source<String> ofFixedLengthStrings(int minCodePoint, int maxCodePoint, int fixedLength) {
    return Source.of((prng, step) -> generateFixedLengthString(Longs.codePoints(minCodePoint, maxCodePoint), fixedLength, prng, step)).withShrinker(Strings.shrinkFixedLengthOfString(minCodePoint, maxCodePoint));
}

@Test
public void shouldFindStringEqualToZeroWhereZeroFalsifies() {
    assertThatFor(Strings.numericStrings()).check(i -> i.length() >= 10);
    smallestValueIsEqualTo("0");
}

@Test
public void shouldFindStringEqualToZeroWhereZeroFalsifies() {
    assertThatFor(Strings.boundedNumericStrings(Integer.MIN_VALUE, Integer.MAX_VALUE)).check(i -> i.length() >= 10);
    smallestValueIsEqualTo("0");
}

static Source<String> numericStrings() {
    return Integers.range(Integer.MIN_VALUE, Integer.MAX_VALUE).as(i -> i.toString(), s -> Integer.parseInt(s));
}

@Test
public void shouldFindAtLeastFiveStringsThatContainAnAmpersand() {
    assertThatFor(Strings.ofFixedLengthStrings(BASIC_LATIN_FIRST_CODEPOINT, BASIC_LATIN_LAST_CODEPOINT, 6), withShrinkCycles(10)).check(i -> i.indexOf('\u0026') < 0);
    atLeastFiveDistinctFalsifyingValuesAreFound();
}

@Test
public void shouldFindAtLeastFiveStringsThatContainAnAmpersand() {
    assertThatFor(Strings.ofBoundedLengthStrings(BASIC_LATIN_FIRST_CODEPOINT, BASIC_LATIN_LAST_CODEPOINT, 6, 6), withShrinkCycles(10)).check(i -> i.indexOf('\u0026') < 0);
    atLeastFiveDistinctFalsifyingValuesAreFound();
}

@Test
public void shouldProvideAtLeastFiveDistinctFalsifyingValuesForAllPossibleStringsWithFixedLength() {
    assertThatFor(Strings.ofFixedLengthStrings(BASIC_LATIN_FIRST_CODEPOINT, BASIC_LATIN_LAST_CODEPOINT, 8)).check(i -> i.indexOf('\u0020') >= 0);
    atLeastFiveDistinctFalsifyingValuesAreFound();
}

@Test
public void shouldProvideAtLeastFiveDistinctFalsifyingValuesForAllPossibleStringsWithFixedLength() {
    assertThatFor(Strings.ofBoundedLengthStrings(BASIC_LATIN_FIRST_CODEPOINT, BASIC_LATIN_LAST_CODEPOINT, 8, 8)).check(i -> i.indexOf('\u0020') >= 0);
    atLeastFiveDistinctFalsifyingValuesAreFound();
}

@Test
public void shouldNotShrinkToAnUndefinedCharacterInStringWhereRemainingCyclesInitiallyLessThanDomainSize() {
    assertThatFor(Strings.ofFixedLengthStrings(0, 196607, 5), withShrinkCycles(100)).check(i -> false);
    listContainsOnlyDefinedCharacters();
}

@Test
public void shouldNotShrinkToAnUndefinedCharacterInStringWhereRemainingCyclesInitiallyLessThanDomainSize() {
    assertThatFor(Strings.ofBoundedLengthStrings(0, 196607, 5, 5), withShrinkCycles(100)).check(i -> false);
    listContainsOnlyDefinedCharacters();
}


@Test
public void shouldShrinkPositiveNumericStringWhenRemainingCyclesLessThanDomainSize() {
    Source<String> testee = Strings.numericStrings();
    String original = "13253";
    Predicate<String> stringShrinksInRightDirection = (i) -> Integer.parseInt(i) <= Integer.parseInt(original) && Integer.parseInt(i) >= 0;
    assertThatSource(testee).shrinksConformTo(original, stringShrinksInRightDirection, someShrinkContext());
}

@Test
public void shouldShrinkPositiveNumericStringWhenRemainingCyclesLessThanDomainSize() {
    Source<String> testee = Strings.boundedNumericStrings(Integer.MIN_VALUE, Integer.MAX_VALUE);
    String original = "13253";
    Predicate<String> stringShrinksInRightDirection = (i) -> Integer.parseInt(i) <= Integer.parseInt(original) && Integer.parseInt(i) >= 0;
    assertThatSource(testee).shrinksConformTo(original, stringShrinksInRightDirection, someShrinkContext());
}

@Test
public void shouldShrinkAllPossibleWhenRemainingCyclesGreaterThanDomainSize() {
    Source<String> testee = Strings.ofFixedLengthStrings(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT, 5);
    assertThatSource(testee).shrinksValueTo("\ud800\udc00\u0001\ud800\udc00", "\ufffd\ufffd\u0002\ufffd\ufffd", new ShrinkContext(0, 100000000, Configuration.defaultPRNG(0)));
}

@Test
public void shouldShrinkAllPossibleWhenRemainingCyclesGreaterThanDomainSize() {
    Source<String> testee = Strings.ofBoundedLengthStrings(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT, 5, 5);
    assertThatSource(testee).shrinksValueTo("\ud800\udc00\u0001\ud800\udc00", "\ufffd\ufffd\u0002\ufffd\ufffd", new ShrinkContext(0, 100000000, Configuration.defaultPRNG(0)));
}

@Test
public void shouldNotShrinkNumericStringConsistingOfZero() {
    Source<String> testee = Strings.numericStrings();
    assertThatSource(testee).cannotShrink("0");
}

@Test
public void shouldNotShrinkNumericStringConsistingOfZero() {
    Source<String> testee = Strings.boundedNumericStrings(Integer.MIN_VALUE, Integer.MAX_VALUE);
    assertThatSource(testee).cannotShrink("0");
}

@Test
public void shouldShrinkBasicLatinAlphabetStringOfFixedLength() {
    Source<String> testee = Strings.ofFixedLengthStrings(BASIC_LATIN_FIRST_CODEPOINT, BASIC_LATIN_LAST_CODEPOINT, 8);
    assertThatSource(testee).shrinksConformTo("Agji{{&C", allLatinCharactersShrink(0x0020, "Agji{{&C"), new ShrinkContext(0, 1, Configuration.defaultPRNG(0)));
}

@Test
public void shouldShrinkBasicLatinAlphabetStringOfFixedLength() {
    Source<String> testee = Strings.ofBoundedLengthStrings(BASIC_LATIN_FIRST_CODEPOINT, BASIC_LATIN_LAST_CODEPOINT, 8, 8);
    assertThatSource(testee).shrinksConformTo("Agji{{&C", allLatinCharactersShrink(0x0020, "Agji{{&C"), new ShrinkContext(0, 1, Configuration.defaultPRNG(0)));
}

@Test
public void shouldShrinkPositiveNumericStringByOneIntegerWhenRemainingCyclesGreaterThanDomainSize() {
    Source<String> testee = Strings.numericStrings();
    assertThatSource(testee).shrinksValueTo("65", "64", someShrinkContext());
}

@Test
public void shouldShrinkPositiveNumericStringByOneIntegerWhenRemainingCyclesGreaterThanDomainSize() {
    Source<String> testee = Strings.boundedNumericStrings(Integer.MIN_VALUE, Integer.MAX_VALUE);
    assertThatSource(testee).shrinksValueTo("65", "64", someShrinkContext());
}

@Test
public void shouldNotShrinkToAnUndefinedCharacterInStringWhenRemainingCyclesGreaterThanDomainSize() {
    Source<String> testee = Strings.ofFixedLengthStrings(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT, 5);
    assertThatSource(testee).shrinksValueTo("\ud800\udc00\u0020\ud801\udca0", "\ufffd\ufffd\u0021\ud801\udc9d", new ShrinkContext(0, 100000000, Configuration.defaultPRNG(0)));
}

@Test
public void shouldNotShrinkToAnUndefinedCharacterInStringWhenRemainingCyclesGreaterThanDomainSize() {
    Source<String> testee = Strings.ofBoundedLengthStrings(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT, 5, 5);
    assertThatSource(testee).shrinksValueTo("\ud800\udc00\u0020\ud801\udca0", "\ufffd\ufffd\u0021\ud801\udc9d", new ShrinkContext(0, 100000000, Configuration.defaultPRNG(0)));
}


@SuppressWarnings("unused")
@Test
public void shouldNotCatchWhenCreatingADateWithLongOfZero() {
    try {
        Source<Date> testee = dates().withMilliSeconds(0);
    } catch (IllegalArgumentException expected) {
        fail("Threw an exception for an acceptable input!");
    }
}

@SuppressWarnings("unused")
@Test
public void shouldNotCatchWhenCreatingADateWithLongOfZero() {
    try {
        Source<Date> testee = dates().withMilliseconds(0);
    } catch (IllegalArgumentException expected) {
        fail("Threw an exception for an acceptable input!");
    }
}

public Source<Date> withMilliseconds(long millisecondsFromEpoch) {
    lowerBoundGEQZero(millisecondsFromEpoch);
    return Compositions.weightWithValues(Dates.withMilliSeconds(millisecondsFromEpoch), new Date(millisecondsFromEpoch));
}

@SuppressWarnings("unused")
@Test
public void shouldCatchWhenCreatingAnInclusiveLongIntervalForDatesWithMaxLessThanMin() {
    try {
        Source<Date> testee = dates().withMilliSecondsBetween(342, 3);
        fail("Created a Date where max long is less than min long!");
    } catch (IllegalArgumentException expected) {
    }
}

@SuppressWarnings("unused")
@Test
public void shouldCatchWhenCreatingAnInclusiveLongIntervalForDatesWithMaxLessThanMin() {
    try {
        Source<Date> testee = dates().withMillisecondsBetween(342, 3);
        fail("Created a Date where max long is less than min long!");
    } catch (IllegalArgumentException expected) {
    }
}

public Source<Date> withMillisecondsBetween(long millisecondsFromEpochStartInclusive, long millisecondsFromEpochEndInclusive) {
    lowerBoundGEQZero(millisecondsFromEpochStartInclusive);
    maxGEQMin(millisecondsFromEpochStartInclusive, millisecondsFromEpochEndInclusive);
    return Compositions.weightWithValues(Dates.withMilliSecondsBetween(millisecondsFromEpochStartInclusive, millisecondsFromEpochEndInclusive), new Date(millisecondsFromEpochEndInclusive), new Date(millisecondsFromEpochStartInclusive));
}

@SuppressWarnings("unused")
@Test
public void shouldCatchWhenCreatingADateOfNegativeLong() {
    try {
        Source<Date> testee = dates().withMilliSeconds(-234);
        fail("Created a date with a negative number of milliseconds");
    } catch (IllegalArgumentException expected) {
        assertTrue("Expected exception message to relate to negative long argument", expected.getMessage().indexOf("not an accepted number of milliseconds") >= 0);
    }
}

@SuppressWarnings("unused")
@Test
public void shouldCatchWhenCreatingADateOfNegativeLong() {
    try {
        Source<Date> testee = dates().withMilliseconds(-234);
        fail("Created a date with a negative number of milliseconds");
    } catch (IllegalArgumentException expected) {
        assertTrue("Expected exception message to relate to negative long argument", expected.getMessage().indexOf("not an accepted number of milliseconds") >= 0);
    }
}

@SuppressWarnings("unused")
@Test
public void shouldNotCatchWhenCreatingAnInclusiveLongIntervalForDatesWithMaxEqualToMin() {
    try {
        Source<Date> testee = dates().withMilliSecondsBetween(352, 352);
    } catch (IllegalArgumentException expected) {
        fail("Threw an exception for an acceptable interval!");
    }
}

@SuppressWarnings("unused")
@Test
public void shouldNotCatchWhenCreatingAnInclusiveLongIntervalForDatesWithMaxEqualToMin() {
    try {
        Source<Date> testee = dates().withMillisecondsBetween(352, 352);
    } catch (IllegalArgumentException expected) {
        fail("Threw an exception for an acceptable interval!");
    }
}

@SuppressWarnings("unused")
@Test
public void shouldNotCatchWhenCreatingAnInclusiveLongIntervalForDatesWithMinEqualToZero() {
    try {
        Source<Date> testee = dates().withMilliSecondsBetween(0, 5);
    } catch (IllegalArgumentException expected) {
        fail("Threw an exception for an acceptable interval!");
    }
}

@SuppressWarnings("unused")
@Test
public void shouldNotCatchWhenCreatingAnInclusiveLongIntervalForDatesWithMinEqualToZero() {
    try {
        Source<Date> testee = dates().withMillisecondsBetween(0, 5);
    } catch (IllegalArgumentException expected) {
        fail("Threw an exception for an acceptable interval!");
    }
}

@Test
public void shouldGenerateDateAtStartAndEndInclusive() {
    Source<Date> testee = dates().withMilliSecondsBetween(3245352, 72938572398752l);
    assertThatSource(testee).generatesAllOf(new Date(3245352), new Date(72938572398752l));
}

@Test
public void shouldGenerateDateAtStartAndEndInclusive() {
    Source<Date> testee = dates().withMillisecondsBetween(3245352, 72938572398752l);
    assertThatSource(testee).generatesAllOf(new Date(3245352), new Date(72938572398752l));
}

public Source<Date> withMilliSecondsBetween(long milliSecondsFromEpochStartInclusive, long milliSecondsFromEpochEndInclusive) {
    lowerBoundGEQZero(milliSecondsFromEpochStartInclusive);
    maxGEQMin(milliSecondsFromEpochStartInclusive, milliSecondsFromEpochEndInclusive);
    return Compositions.weightWithValues(Dates.withMilliSecondsBetween(milliSecondsFromEpochStartInclusive, milliSecondsFromEpochEndInclusive), new Date(milliSecondsFromEpochEndInclusive), new Date(milliSecondsFromEpochStartInclusive));
}

@SuppressWarnings("unused")
@Test
public void shouldCatchWhenCreatingAnInclusiveLongIntervalForDatesWithMinLessThanZero() {
    try {
        Source<Date> testee = dates().withMilliSecondsBetween(-5, 6);
        fail("Created a Date where min long is less than zero!");
    } catch (IllegalArgumentException expected) {
    }
}

@SuppressWarnings("unused")
@Test
public void shouldCatchWhenCreatingAnInclusiveLongIntervalForDatesWithMinLessThanZero() {
    try {
        Source<Date> testee = dates().withMillisecondsBetween(-5, 6);
        fail("Created a Date where min long is less than zero!");
    } catch (IllegalArgumentException expected) {
    }
}

@Test
public void shouldGenerateDateMax() {
    Source<Date> testee = dates().withMilliSeconds(7890789);
    assertThatSource(testee).generatesAllOf(new Date(7890789));
}

@Test
public void shouldGenerateDateMax() {
    Source<Date> testee = dates().withMilliseconds(7890789);
    assertThatSource(testee).generatesAllOf(new Date(7890789));
}

public Source<Date> withMilliSeconds(long milliSecondsFromEpoch) {
    lowerBoundGEQZero(milliSecondsFromEpoch);
    return Compositions.weightWithValues(Dates.withMilliSeconds(milliSecondsFromEpoch), new Date(milliSecondsFromEpoch));
}


@Test
public void shouldIncludeSmallestFoundValueInFalsificationReport() throws Exception {
    int smallest = 101;
    try {
        testee.falisification(0, 0, smallest, Arrays.asList(0));
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + smallest);
    }
}

@Test
public void shouldIncludeSmallestFoundValueInFalsificationReport() throws Exception {
    int smallest = 101;
    try {
        testee.falisification(0, 0, smallest, Arrays.asList(0), a -> a.toString());
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + smallest);
    }
}

@Override
public void falisification(long seed, int examplesUsed, Object smallest, List<Object> examples) {
    this.falsify(seed, examplesUsed, smallest, "", examples);
}

@Test
public void shouldIncludeNumberOfExaminedExamplesInFalsificationReport() throws Exception {
    int examples = 100;
    try {
        testee.falisification(0, examples, 0, Arrays.asList(0));
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + examples);
    }
}

@Test
public void shouldIncludeNumberOfExaminedExamplesInFalsificationReport() throws Exception {
    int examples = 100;
    try {
        testee.falisification(0, examples, 0, Arrays.asList(0), a -> a.toString());
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + examples);
    }
}

@Test
public void shouldIncludeAdditionalExamplesInFalsificationReport() throws Exception {
    try {
        testee.falisification(0, 0, 0, Arrays.asList(41, 42));
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("41", "42");
    }
}

@Test
public void shouldIncludeAdditionalExamplesInFalsificationReport() throws Exception {
    try {
        testee.falisification(0, 0, 0, Arrays.asList(41, 42), a -> a.toString());
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("41", "42");
    }
}

@Test
public void shouldIncludeOnlyFirst10ValuesInReport() throws Exception {
    try {
        List<Object> values = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
        testee.falisification(0, 0, 0, values);
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("1\n2\n3\n4\n5\n6\n7\n8\n9\n10");
        assertThat(expected.getMessage()).doesNotContain("11");
    }
}

@Test
public void shouldIncludeOnlyFirst10ValuesInReport() throws Exception {
    try {
        List<Object> values = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
        testee.falisification(0, 0, 0, values, a -> a.toString());
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("1\n2\n3\n4\n5\n6\n7\n8\n9\n10");
        assertThat(expected.getMessage()).doesNotContain("11");
    }
}

@Test
public void shouldIncludeSeedInFalsificationReport() throws Exception {
    long seed = 42;
    try {
        testee.falisification(42, 0, 0, Arrays.asList(0));
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + seed);
    }
}

@Test
public void shouldIncludeSeedInFalsificationReport() throws Exception {
    long seed = 42;
    try {
        testee.falisification(42, 0, 0, Arrays.asList(0), a -> a.toString());
        throw new Exception("Should not reach here");
    } catch (AssertionError expected) {
        assertThat(expected.getMessage()).contains("" + seed);
    }
}


@Test
public void shouldAllowRetentionOfPrecursorValuesWhenConvertingTypes() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).assuming(i -> i != 2).asWithPrecursor(i -> i.toString()).check((precursor, a) -> a.equals("1"));
    Pair<Integer, String> actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo(Pair.of(3, "3"));
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenConvertingTypes() {
    qt().withShrinkCycles(10).forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).assuming(i -> i != 2).asWithPrecursor(i -> i.toString()).check((precursor, a) -> a.equals("1"));
    Pair<Integer, String> actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo(Pair.of(3, "3"));
}

@CheckReturnValue
public <T> Subject2<A, T> asWithPrecursor(Function<A, T> mapping) {
    return asWithPrecursor(mapping, t -> t.toString());
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenAsserting() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).assuming(i -> i != 2).asWithPrecursor(i -> i.toString()).checkAssert((precursor, i) -> assertThat(i).isEqualTo("1"));
    verifier.isFalsifiedByException();
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenAsserting() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).assuming(i -> i != 2).asWithPrecursor(i -> i.toString()).checkAssert((precursor, i) -> assertThat(i).isEqualTo("1"));
    verifier.isFalsifiedByException();
}

@Test
public void shouldConvertAndShrinkValues() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).as(i -> i.toString()).check(a -> a.equals("1"));
    String actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo("2");
}

@Test
public void shouldConvertAndShrinkValues() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).as(i -> i.toString()).check(a -> a.equals("1"));
    String actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo("2");
}

@CheckReturnValue
public <T> Subject1<T> as(Function<A, T> mapping) {
    return new MappingTheoryBuilder<>(this.state, this.ps, mapping, t -> t.toString());
}


@Test
public void shouldFalisyWhenAssertionsThrown() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).checkAssert((a, b) -> assertThat(a + b).isEqualTo(2));
    verifier.isFalsifiedByException();
}

@Test
public void shouldFalisyWhenAssertionsThrown() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).checkAssert((a, b) -> assertThat(a + b).isEqualTo(2));
    verifier.isFalsifiedByException();
}

@Override
public final void checkAssert(final BiConsumer<A, B> property) {
    check((a, b) -> {
        property.accept(a, b);
        return true;
    });
}

@Test
public void shouldContinueToShrinkWhenLeftHandSideAtSmallestValue() {
    qt().forAll(arbitrary().constant(0), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7)).check((a, b) -> b == 5);
    verifier.<Integer, Integer>smallestValueMatches((a, b) -> a == 0 && b == 1);
}

@Test
public void shouldContinueToShrinkWhenLeftHandSideAtSmallestValue() {
    qt().forAll(arbitrary().constant(0), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).check((a, b) -> b == 5);
    verifier.<Integer, Integer>smallestValueMatches((a, b) -> a == 0 && b == 1);
}

public <T> Source<T> constant(T constant) {
    return Arbitrary.constant(constant);
}

public <T> Gen<T> constant(T constant) {
    return Generate.constant(constant);
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenConvertingTypes() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).assuming((a, b) -> a != 2 && b != 2).asWithPrecursor((a, b) -> a.toString() + ":" + b).check((a, b, value) -> value.equals("1:1"));
    Tuple3<Integer, Integer, String> actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo(Tuple3.of(3, 3, "3:3"));
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenConvertingTypes() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).assuming((a, b) -> a != 2 && b != 2).asWithPrecursor((a, b) -> a.toString() + ":" + b).check((a, b, value) -> value.equals("1:1"));
    Tuple3<Integer, Integer, String> actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo(Tuple3.of(3, 1, "3:1"));
}

@CheckReturnValue
public <T> Subject3<A, B, T> asWithPrecursor(BiFunction<A, B, T> mapping) {
    return this.asWithPrecursor(mapping, t -> t.toString());
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenAsserting() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).asWithPrecursor((a, b) -> a.toString()).checkAssert((a, b, i) -> assertThat(i).isEqualTo("1"));
    verifier.isFalsifiedByException();
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenAsserting() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).asWithPrecursor((a, b) -> a.toString()).checkAssert((a, b, i) -> assertThat(i).isEqualTo("1"));
    verifier.isFalsifiedByException();
}

@Test
public void shouldConstrainGeneratorsByAssumptions() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).assuming((a, b) -> a > 2 && b > 2).check((a, b) -> a > 2 && b > 2);
    verifier.notFalsified();
}

@Test
public void shouldConstrainGeneratorsByAssumptions() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).assuming((a, b) -> a > 2 && b > 2).check((a, b) -> a > 2 && b > 2);
    verifier.notFalsified();
}

@CheckReturnValue
public TheoryBuilder2<A, B> assuming(BiPredicate<A, B> newAssumption) {
    return new TheoryBuilder2<A, B>(this.state, this.as, this.bs, this.assumptions.and(newAssumption));
}

@Test
public void shouldContinueToShrinkWhenRightHandSideAtSmallestValue() {
    qt().forAll(arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().constant(0)).check((a, b) -> b == 5);
    verifier.<Integer, Integer>smallestValueMatches((a, b) -> a == 1 && b == 0);
}

@Test
public void shouldContinueToShrinkWhenRightHandSideAtSmallestValue() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().constant(0)).check((a, b) -> b == 5);
    verifier.<Integer, Integer>smallestValueMatches((a, b) -> a == 1 && b == 0);
}

@Test
public void shouldFalisyWhenAssertionsThrownAfterTypeConversion() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).as((a, b) -> a.toString() + b).checkAssert(a -> assertEquals("11", a));
    verifier.isFalsifiedByException();
}

@Test
public void shouldFalisyWhenAssertionsThrownAfterTypeConversion() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).as((a, b) -> a.toString() + b).checkAssert(a -> assertThat("11").isEqualTo(a));
    verifier.isFalsifiedByException();
}

@CheckReturnValue
public <T> Subject1<T> as(BiFunction<A, B, T> mapping) {
    return new MappingTheoryBuilder<>(this.state, combine(), precursor -> mapping.apply(precursor._1, precursor._2), t -> t.toString());
}

@Test
public void shouldShrinkWhenBothSidesCanBeShrunk() {
    qt().forAll(arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7)).check((a, b) -> a == 5 && b == 5);
    verifier.<Integer, Integer>smallestValueMatches((a, b) -> a == 1 && b == 1);
}

@Test
public void shouldShrinkWhenBothSidesCanBeShrunk() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).check((a, b) -> a == 5 && b == 5);
    verifier.<Integer, Integer>smallestValueMatches((a, b) -> a == 1 && b == 1);
}

@SuppressWarnings("unchecked")
public <T> Source<T> reverse(T... ts) {
    return Arbitrary.reverse(ts);
}

@Test
public void shouldConvertAndShrinkValues() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).as((a, b) -> a.toString() + ":" + b.toString()).check(a -> a.equals("1:1"));
    String actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo("2:2");
}

@Test
public void shouldConvertAndShrinkValues() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).as((a, b) -> a.toString() + ":" + b.toString()).check(a -> a.equals("1:1"));
    String actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo("2:1");
}


@Test
public void shouldContinueToShrinkWhenMiddleAtSmallestValue() {
    qt().forAll(arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().constant(0), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7)).check((a, b, c) -> c == 5);
    verifier.<Integer, Integer, Integer>smallestValueMatches((a, b, c) -> a == 1 && b == 0 && c == 1);
}

@Test
public void shouldContinueToShrinkWhenMiddleAtSmallestValue() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().constant(0), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).check((a, b, c) -> c == 5);
    verifier.<Integer, Integer, Integer>smallestValueMatches((a, b, c) -> a == 1 && b == 0 && c == 1);
}

@Test
public void shouldShrinkWhenAllSidesCanBeShrunk() {
    qt().forAll(arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7)).check((a, b, c) -> a == 5 && b == 5 && c == 5);
    verifier.<Integer, Integer, Integer>smallestValueMatches((a, b, c) -> a == 1 && b == 1 && c == 1);
}

@Test
public void shouldShrinkWhenAllSidesCanBeShrunk() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).check((a, b, c) -> a == 5 && b == 5 && c == 5);
    verifier.<Integer, Integer, Integer>smallestValueMatches((a, b, c) -> a == 1 && b == 1 && c == 1);
}

@Test
public void shouldConvertAndShrinkValues() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).as((a, b, c) -> a + ":" + b + ":" + c).check(a -> a.equals("1:1:1"));
    String actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo("2:2:2");
}

@Test
public void shouldConvertAndShrinkValues() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).as((a, b, c) -> a + ":" + b + ":" + c).check(a -> a.equals("1:1:1"));
    String actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo("1:1:2");
}

@CheckReturnValue
public <T> Subject1<T> as(Function3<A, B, C, T> mapping) {
    return new MappingTheoryBuilder<>(this.state, combine(), tuple -> mapping.apply(tuple._1, tuple._2, tuple._3), t -> t.toString());
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenAsserting() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).asWithPrecursor((a, b, c) -> a.toString()).checkAssert((a, b, c, i) -> assertThat(i).isEqualTo("1"));
    verifier.isFalsifiedByException();
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenAsserting() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).asWithPrecursor((a, b, c) -> a.toString()).checkAssert((a, b, c, i) -> assertThat(i).isEqualTo("1"));
    verifier.isFalsifiedByException();
}

@CheckReturnValue
public <T> Subject4<A, B, C, T> asWithPrecursor(Function3<A, B, C, T> mapping) {
    return this.asWithPrecursor(mapping, t -> t.toString());
}

@Test
public void shouldConstrainGeneratorsByAssumptions() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).assuming((a, b, c) -> a > 2 && b > 2 && c > 2).check((a, b, c) -> a > 2 && b > 2 && c > 2);
    verifier.notFalsified();
}

@Test
public void shouldConstrainGeneratorsByAssumptions() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).assuming((a, b, c) -> a > 2 && b > 2 && c > 2).check((a, b, c) -> a > 2 && b > 2 && c > 2);
    verifier.notFalsified();
}

@CheckReturnValue
public TheoryBuilder3<A, B, C> assuming(Predicate3<A, B, C> newAssumption) {
    return new TheoryBuilder3<A, B, C>(this.state, this.as, this.bs, this.cs, this.assumptions.and(newAssumption));
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenConvertingTypes() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).assuming((a, b, c) -> a != 2 && b != 2 && c != 2).asWithPrecursor((a, b, c) -> a + ":" + b + ":" + c).check((a, b, c, value) -> value.equals("1:1:1"));
    Tuple4<Integer, Integer, Integer, String> actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo(Tuple4.of(3, 3, 3, "3:3:3"));
}

@Test
public void shouldAllowRetentionOfPrecursorValuesWhenConvertingTypes() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).assuming((a, b, c) -> a != 2 && b != 2 && c != 2).asWithPrecursor((a, b, c) -> a + ":" + b + ":" + c).check((a, b, c, value) -> value.equals("1:1:1"));
    Tuple4<Integer, Integer, Integer, String> actual = verifier.smallestFalsifiedValue();
    assertThat(actual).isEqualTo(Tuple4.of(3, 1, 1, "3:1:1"));
}

@SuppressWarnings("unchecked")
public <T> Source<T> sequence(T... ts) {
    return sequence(java.util.Arrays.asList(ts));
}

@Test
public void shouldFalisyWhenAssertionsThrown() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).checkAssert((a, b, c) -> assertThat(a + b + c).isEqualTo(3));
    verifier.isFalsifiedByException();
}

@Test
public void shouldFalisyWhenAssertionsThrown() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).checkAssert((a, b, c) -> assertThat(a + b + c).isEqualTo(3));
    verifier.isFalsifiedByException();
}

public final void checkAssert(final TriConsumer<A, B, C> property) {
    check((a, b, c) -> {
        property.accept(a, b, c);
        return true;
    });
}

@Test
public void shouldFalisyWhenAssertionsThrownAfterTypeConversion() {
    qt().forAll(arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7), arbitrary().sequence(1, 2, 3, 4, 5, 6, 7)).as((a, b, c) -> a.toString() + b + c).checkAssert(a -> assertEquals("111", a));
    verifier.isFalsifiedByException();
}

@Test
public void shouldFalisyWhenAssertionsThrownAfterTypeConversion() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).as((a, b, c) -> a.toString() + b + c).checkAssert(a -> assertThat("111").isEqualTo(a));
    verifier.isFalsifiedByException();
}

@Test
public void shouldContinueToShrinkWhenRightHandSideAtSmallestValue() {
    qt().forAll(arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().constant(0)).check((a, b, c) -> c == 5);
    verifier.<Integer, Integer, Integer>smallestValueMatches((a, b, c) -> a == 1 && b == 1 && c == 0);
}

@Test
public void shouldContinueToShrinkWhenRightHandSideAtSmallestValue() {
    qt().forAll(arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().constant(0)).check((a, b, c) -> c == 5);
    verifier.<Integer, Integer, Integer>smallestValueMatches((a, b, c) -> a == 1 && b == 1 && c == 0);
}

@Test
public void shouldContinueToShrinkWhenLeftHandSideAtSmallestValue() {
    qt().forAll(arbitrary().constant(0), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7), arbitrary().reverse(1, 2, 3, 4, 5, 6, 7)).check((a, b, c) -> c == 5);
    verifier.<Integer, Integer, Integer>smallestValueMatches((a, b, c) -> a == 0 && b == 1 && c == 1);
}

@Test
public void shouldContinueToShrinkWhenLeftHandSideAtSmallestValue() {
    qt().forAll(arbitrary().constant(0), arbitrary().pick(1, 2, 3, 4, 5, 6, 7), arbitrary().pick(1, 2, 3, 4, 5, 6, 7)).check((a, b, c) -> c == 5);
    verifier.<Integer, Integer, Integer>smallestValueMatches((a, b, c) -> a == 0 && b == 1 && c == 1);
}