All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redfin.fuzzy.Cases Maven / Gradle / Ivy

package com.redfin.fuzzy;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class Cases {

	/**
	 * Creates and returns a new case that is "composed" of the values returned by a collection of base cases, according
	 * to a composition function. This can be useful for quickly writing cases of complex objects that rely on
	 * various generators for their properties.
	 *
	 * 

* Use this overload only when you have more base cases than can be covered by one of the type-safe overloads. *

*

* This function works by collecting all of the subcases defined by each of the base cases and combining them in * order to produce a single "composed" subcase. The composition function is responsible for returning the result of * a single such subcase, where the values generated by each of the base cases are provided as input. *

*

* For example, consider the following composition: *

*
{@code
	 *     Case composedCase = Cases.compose(
	 *         CaseCompositionMode.PAIRWISE_PERMUTATIONS_OF_SUBCASES,
	 *         new Case[] { Any.of(2, 3), Any.of(10, 100, 1000) },
	 *         (random, values) -> (int)values[0] * (int)values[1]
	 *     );
	 *
	 *     composedCase.generateAllOnce().stream().forEach(System.out::println);
	 * }
*

* This will return a Case of Integers that is the result of multiplying one of the values from the first base case * ({@code 2} or {@code 3}) with one of the values from the second base case ({@code 10}, {@code 100}, or * {@code 1000}). The output might look like the following: *

*

	 *     20
	 *     200
	 *     2000
	 *     30
	 *     300
	 *     3000
	 * 
* * @param caseCompositionMode - the algorithm to use for deciding both how many composed subcases will be created, * as well as which specific values of the base cases to use for each composed subcase. See the descriptions * for the individual algorithms for more information. In general, you should use * {@link CaseCompositionMode#PAIRWISE_PERMUTATIONS_OF_SUBCASES} unless you experience performance issues. * Use {@link CaseCompositionMode#EACH_SUBCASE_AT_LEAST_ONCE} to limit the total number of composed subcases. * @param baseCases - the cases that the overall case is comprised of. * @param composition - a function that produces an {@code OUTPUT} value based on the specific values chosen for * each of the subcases. This function is supplied with the random number generator to use for the test as * well as an array of the values generated for each of the base cases in the {@code baseCases} array. This * array will have the same length as the {@code baseCases} array, and the value at each index {@code i} will * have been produced by a subcase of the base case at the same index. * * @param - the return type of the composed case and of the composition function. * * @return a case that can be used to generate values based on the composition function. */ public static Case compose( CaseCompositionMode caseCompositionMode, Case[] baseCases, BiFunction composition ) { FuzzyPreconditions.checkNotNull("caseCompositionMode is required.", caseCompositionMode); FuzzyPreconditions.checkNotNullAndContainsNoNulls(baseCases); FuzzyPreconditions.checkNotNull("composition function is required.", composition); Subcase[][] composedSubcases = caseCompositionMode.algorithm.apply(baseCases); Set> subcases = new HashSet<>(); for(final Subcase[] subcase : composedSubcases) { subcases.add(r -> { Object[] values = new Object[subcase.length]; for (int j = 0; j < subcase.length; j++) { @SuppressWarnings("unchecked") Subcase supplier = (Subcase) subcase[j]; values[j] = supplier.generate(r); } return composition.apply(r, values); }); } return () -> subcases; } /** * Creates and returns a new case that is "composed" of the subcases of the given base case. This can be useful for * quickly writing cases of complex objects that rely on various generators for their properties. * * @param baseCase - the case that provides the basis for the returned case. * @param compositionFunction - a function that is called once for each subcase defined by the base case. It is * provided with the random number generator to use for the test as well as a value generated by one of the * base case's subcases. * * @param the type of values generated by the base case. * @param the type of values generated by the returned case. * * @return a case defining one subcase for each subcase of the base case, whose values are defined by the * composition function. */ @SuppressWarnings("unchecked") public static Case compose( Case baseCase, BiFunction compositionFunction ) { return compose( CaseCompositionMode.EACH_SUBCASE_AT_LEAST_ONCE, new Case[] { baseCase }, (random, cases) -> compositionFunction.apply(random, (INPUT)cases[0]) ); } /** * Creates and returns a new case that is "composed" of the values returned by a collection of base cases, according * to a composition function. This can be useful for quickly writing cases of complex objects that rely on * various generators for their properties. * * @see #compose(CaseCompositionMode, Case[], BiFunction) */ @SuppressWarnings("unchecked") public static Case compose( CaseCompositionMode caseCompositionMode, Case baseCase1, Case baseCase2, TwoCaseCompositionFunction compositionFunction ) { return compose( caseCompositionMode, new Case[] { baseCase1, baseCase2 }, (random, values) -> compositionFunction.apply(random, (INPUT1)values[0], (INPUT2)values[1]) ); } public interface TwoCaseCompositionFunction { OUTPUT apply(Random random, INPUT1 input1, INPUT2 input2); } /** * Creates and returns a new case that is "composed" of the values returned by a collection of base cases, according * to a composition function. This can be useful for quickly writing cases of complex objects that rely on * various generators for their properties. * * @see #compose(CaseCompositionMode, Case[], BiFunction) */ @SuppressWarnings("unchecked") public static Case compose( CaseCompositionMode caseCompositionMode, Case baseCase1, Case baseCase2, Case baseCase3, ThreeCaseCompositionFunction compositionFunction ) { return compose( caseCompositionMode, new Case[] { baseCase1, baseCase2, baseCase3 }, (random, values) -> compositionFunction.apply(random, (INPUT1)values[0], (INPUT2)values[1], (INPUT3)values[2]) ); } public interface ThreeCaseCompositionFunction { OUTPUT apply(Random random, INPUT1 input1, INPUT2 input2, INPUT3 input3); } /** * Creates and returns a new case that is "composed" of the values returned by a collection of base cases, according * to a composition function. This can be useful for quickly writing cases of complex objects that rely on * various generators for their properties. * * @see #compose(CaseCompositionMode, Case[], BiFunction) */ @SuppressWarnings("unchecked") public static Case compose( CaseCompositionMode caseCompositionMode, Case baseCase1, Case baseCase2, Case baseCase3, Case baseCase4, FourCaseCompositionFunction compositionFunction ) { return compose( caseCompositionMode, new Case[] { baseCase1, baseCase2, baseCase3, baseCase4 }, (random, values) -> compositionFunction.apply(random, (INPUT1)values[0], (INPUT2)values[1], (INPUT3)values[2], (INPUT4)values[3]) ); } public interface FourCaseCompositionFunction { OUTPUT apply(Random random, INPUT1 input1, INPUT2 input2, INPUT3 input3, INPUT4 input4); } /** * Creates and returns a new case that is "composed" of the values returned by a collection of base cases, according * to a composition function. This can be useful for quickly writing cases of complex objects that rely on * various generators for their properties. * * @see #compose(CaseCompositionMode, Case[], BiFunction) */ @SuppressWarnings("unchecked") public static Case compose( CaseCompositionMode caseCompositionMode, Case baseCase1, Case baseCase2, Case baseCase3, Case baseCase4, Case baseCase5, FiveCaseCompositionFunction compositionFunction ) { return compose( caseCompositionMode, new Case[] { baseCase1, baseCase2, baseCase3, baseCase4, baseCase5 }, (random, values) -> compositionFunction.apply(random, (INPUT1)values[0], (INPUT2)values[1], (INPUT3)values[2], (INPUT4)values[3], (INPUT5)values[4]) ); } public interface FiveCaseCompositionFunction { OUTPUT apply(Random random, INPUT1 input1, INPUT2 input2, INPUT3 input3, INPUT4 input4, INPUT5 input5); } /** * Creates and returns a new case that is "composed" of the values returned by a collection of base cases, according * to a composition function. This can be useful for quickly writing cases of complex objects that rely on * various generators for their properties. * * @see #compose(CaseCompositionMode, Case[], BiFunction) */ @SuppressWarnings("unchecked") public static Case compose( CaseCompositionMode caseCompositionMode, Case baseCase1, Case baseCase2, Case baseCase3, Case baseCase4, Case baseCase5, Case baseCase6, SixCaseCompositionFunction compositionFunction ) { return compose( caseCompositionMode, new Case[] { baseCase1, baseCase2, baseCase3, baseCase4, baseCase5, baseCase6 }, (random, values) -> compositionFunction.apply(random, (INPUT1)values[0], (INPUT2)values[1], (INPUT3)values[2], (INPUT4)values[3], (INPUT5)values[4], (INPUT6)values[5]) ); } public interface SixCaseCompositionFunction { OUTPUT apply(Random random, INPUT1 input1, INPUT2 input2, INPUT3 input3, INPUT4 input4, INPUT5 input5, INPUT6 input6); } /** * Creates and returns a new case that is "composed" of the values returned by a collection of base cases, according * to a composition function. This can be useful for quickly writing cases of complex objects that rely on * various generators for their properties. * * @see #compose(CaseCompositionMode, Case[], BiFunction) */ @SuppressWarnings("unchecked") public static Case compose( CaseCompositionMode caseCompositionMode, Case baseCase1, Case baseCase2, Case baseCase3, Case baseCase4, Case baseCase5, Case baseCase6, Case baseCase7, SevenCaseCompositionFunction compositionFunction ) { return compose( caseCompositionMode, new Case[] { baseCase1, baseCase2, baseCase3, baseCase4, baseCase5, baseCase6, baseCase7 }, (random, values) -> compositionFunction.apply(random, (INPUT1)values[0], (INPUT2)values[1], (INPUT3)values[2], (INPUT4)values[3], (INPUT5)values[4], (INPUT6)values[5], (INPUT7)values[6]) ); } public interface SevenCaseCompositionFunction { OUTPUT apply(Random random, INPUT1 input1, INPUT2 input2, INPUT3 input3, INPUT4 input4, INPUT5 input5, INPUT6 input6, INPUT7 input7); } /** * Creates and returns a new case that is "composed" of the values returned by a collection of base cases, according * to a composition function. This can be useful for quickly writing cases of complex objects that rely on * various generators for their properties. * * @see #compose(CaseCompositionMode, Case[], BiFunction) */ @SuppressWarnings("unchecked") public static Case compose( CaseCompositionMode caseCompositionMode, Case baseCase1, Case baseCase2, Case baseCase3, Case baseCase4, Case baseCase5, Case baseCase6, Case baseCase7, Case baseCase8, EightCaseCompositionFunction compositionFunction ) { return compose( caseCompositionMode, new Case[] { baseCase1, baseCase2, baseCase3, baseCase4, baseCase5, baseCase6, baseCase7, baseCase8 }, (random, values) -> compositionFunction.apply(random, (INPUT1)values[0], (INPUT2)values[1], (INPUT3)values[2], (INPUT4)values[3], (INPUT5)values[4], (INPUT6)values[5], (INPUT7)values[6], (INPUT8)values[7]) ); } public interface EightCaseCompositionFunction { OUTPUT apply(Random random, INPUT1 input1, INPUT2 input2, INPUT3 input3, INPUT4 input4, INPUT5 input5, INPUT6 input6, INPUT7 input7, INPUT8 input8); } /** * Creates and returns a new case that is "composed" of the values returned by a collection of base cases, according * to a composition function. This can be useful for quickly writing cases of complex objects that rely on * various generators for their properties. * * @see #compose(CaseCompositionMode, Case[], BiFunction) */ @SuppressWarnings("unchecked") public static Case compose( CaseCompositionMode caseCompositionMode, Case baseCase1, Case baseCase2, Case baseCase3, Case baseCase4, Case baseCase5, Case baseCase6, Case baseCase7, Case baseCase8, Case baseCase9, NineCaseCompositionFunction compositionFunction ) { return compose( caseCompositionMode, new Case[] { baseCase1, baseCase2, baseCase3, baseCase4, baseCase5, baseCase6, baseCase7, baseCase8, baseCase9 }, (random, values) -> compositionFunction.apply(random, (INPUT1)values[0], (INPUT2)values[1], (INPUT3)values[2], (INPUT4)values[3], (INPUT5)values[4], (INPUT6)values[5], (INPUT7)values[6], (INPUT8)values[7], (INPUT9)values[8]) ); } public interface NineCaseCompositionFunction { OUTPUT apply(Random random, INPUT1 input1, INPUT2 input2, INPUT3 input3, INPUT4 input4, INPUT5 input5, INPUT6 input6, INPUT7 input7, INPUT8 input8, INPUT9 input9); } @SafeVarargs public static Case of(Subcase... subcases) { FuzzyPreconditions.checkNotNullAndContainsNoNulls(subcases); Set> subcasesSet = FuzzyUtil.setOf(subcases); return () -> subcasesSet; } @SafeVarargs public static Case of(Supplier... subcases) { FuzzyPreconditions.checkNotNullAndContainsNoNulls(subcases); Set> subcasesSet = new HashSet<>(subcases.length); for(Supplier supplier : subcases) { subcasesSet.add(r -> supplier.get()); } return () -> subcasesSet; } @SafeVarargs public static Case of(T... literalCases) { FuzzyPreconditions.checkNotNull(literalCases); Set> subcases = new HashSet<>(literalCases.length); for(T t : literalCases) { subcases.add(r -> t); } return () -> subcases; } @SafeVarargs public static Case ofDelegates(Supplier>... delegateCases) { FuzzyPreconditions.checkNotNullAndContainsNoNulls(delegateCases); Set> subcases = new HashSet<>(); for(Supplier> delegate : delegateCases) { subcases.addAll(delegate.get().getSubcases()); } return () -> subcases; } public static Case map(Case original, Function mapping) { FuzzyPreconditions.checkNotNull(original); FuzzyPreconditions.checkNotNull(mapping); return map(original, (r, t) -> mapping.apply(t)); } public static Case map(Case original, BiFunction mapping) { FuzzyPreconditions.checkNotNull(original); FuzzyPreconditions.checkNotNull(mapping); return () -> { Set> sourceSubcases = original.getSubcases(); Set> mappedSubcases = new HashSet<>(sourceSubcases.size()); for(Subcase source : sourceSubcases) { mappedSubcases.add(r -> mapping.apply(r, source.generate(r))); } return mappedSubcases; }; } }