Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.dakusui.pcond.provider.impls.BaseAssertionProvider Maven / Gradle / Ivy
package com.github.dakusui.pcond.provider.impls;
import com.github.dakusui.pcond.core.Evaluable;
import com.github.dakusui.pcond.core.Evaluator;
import com.github.dakusui.pcond.internals.InternalUtils;
import com.github.dakusui.pcond.provider.ApplicationException;
import com.github.dakusui.pcond.provider.AssertionProviderBase;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static com.github.dakusui.pcond.core.Evaluator.Entry.Type.*;
import static com.github.dakusui.pcond.internals.InternalUtils.executionFailure;
import static com.github.dakusui.pcond.internals.InternalUtils.formatObject;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
public abstract class BaseAssertionProvider implements AssertionProviderBase {
private final boolean useEvaluator;
private final Configuration configuration;
public BaseAssertionProvider(Properties properties) {
this.useEvaluator = useEvaluator(this.getClass(), properties);
this.configuration = new Configuration() {
@Override
public int summarizedStringLength() {
return Configuration.super.summarizedStringLength();
}
@Override
public ApplicationException throwApplicationException(String message) {
return new ApplicationException(message);
}
};
}
@Override
public String composeMessageForPrecondition(T value, Predicate super T> predicate) {
return format("value:<%s> violated precondition:value %s", formatObject(value), predicate);
}
@Override
public String composeMessageForPostcondition(T value, Predicate super T> predicate) {
return format("value:<%s> violated postcondition:value %s", formatObject(value), predicate);
}
@Override
public String composeMessageForAssertion(T t, Predicate super T> predicate) {
return "Value:" + formatObject(t) + " violated: " + predicate.toString();
}
@Override
public String composeMessageForValidation(T t, Predicate super T> predicate) {
return "Value:" + formatObject(t) + " violated: " + predicate.toString();
}
@Override
public ApplicationException applicationException(String message) {
return new ApplicationException(message);
}
@Override
public T testFailedException(String message) {
throw testFailedException(Explanation.fromMessage(message));
}
@Override
public Configuration configuration() {
return this.configuration;
}
@FunctionalInterface
interface ReflectiveExceptionFactory {
T create(Class c, Explanation explanation) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException;
default T apply(Class c, Explanation explanation) {
try {
return create(c, explanation);
} catch (InvocationTargetException | InstantiationException |
IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException("FAILED TO INSTANTIATE EXCEPTION: '" + c.getCanonicalName() + "'", e);
}
}
}
@SuppressWarnings("unchecked")
T createException(String className, Explanation explanation, ReflectiveExceptionFactory reflectiveExceptionFactory) {
try {
return reflectiveExceptionFactory.apply((Class) Class.forName(className), explanation);
} catch (ClassNotFoundException e) {
throw new RuntimeException("FAILED TO INSTANTIATE EXCEPTION: '" + className + "' (NOT FOUND)", e);
}
}
@SuppressWarnings("unchecked")
@Override
public T checkValueAndThrowIfFails(T value, Predicate super T> cond, BiFunction, String> messageComposer, ExceptionComposer exceptionComposer) throws E {
if (useEvaluator && cond instanceof Evaluable) {
Evaluator evaluator = Evaluator.create();
try {
((Evaluable) cond).accept(value, evaluator);
} catch (Error error) {
throw error;
} catch (Throwable t) {
String message = format("An exception(%s) was thrown during evaluation of value: %s: %s", t, value, cond);
throw executionFailure(composeExplanation(message, evaluator.resultEntries(), t), t);
}
Result result = new Result(evaluator.resultValue(), evaluator.resultEntries());
if (result.result())
return value;
throw exceptionComposer.apply(composeExplanation(messageComposer.apply(value, cond), result.entries, null));
} else {
if (!cond.test(value))
throw exceptionComposer.apply(composeExplanation(messageComposer.apply(value, cond), emptyList(), null));
return value;
}
}
@Override
public T checkValue(T value, Predicate super T> cond, BiFunction, String> messageComposer, Function exceptionComposer) throws E {
return checkValueAndThrowIfFails(value, cond, messageComposer, explanation -> exceptionComposer.apply(explanation.toString()));
}
private static Explanation composeExplanation(String message, List result, Throwable t) {
List expectationDetails = new LinkedList<>();
List actualResultDetails = new LinkedList<>();
String expectation = composeExplanationForExpectations(result, t, expectationDetails);
String actualResult = composeExplanationForActualResults(result, t, actualResultDetails);
// assert expectationDetails.size() == actualResultDetails.size();
return new Explanation(message, composeReport(expectation, expectationDetails, new AtomicInteger(0)), composeReport(actualResult, actualResultDetails, new AtomicInteger(0)));
}
public static String composeReport(String summary, List details, AtomicInteger index) {
if (summary == null && details == null)
return null;
String ret = summary;
ret += format("%n");
if (details != null && !details.isEmpty()) {
ret += format("%n");
ret += details.stream()
.map(Objects::toString)
.map(each -> format(".Detail of failure [%s]%n", index.getAndIncrement())
+ format("----%n")
+ each + format("%n")
+ format("----%n"))
.collect(joining(format("%n")));
}
return ret;
}
private static String composeExplanationForActualResults(List result, Throwable t, List actualInputDetails) {
return composeExplanation(result.stream()
.peek((Evaluator.Entry each) -> {
if (each.hasActualInputDetail())
actualInputDetails.add(each.actualInputDetail());
})
.map((Evaluator.Entry each) -> evaluatorEntryToFormattedEntry(
each,
() -> each.hasOutput() ?
InternalUtils.formatObject(each.output()) :
InternalUtils.formatObject(t)))
.collect(toList()));
}
private static String composeExplanationForExpectations(List result, Throwable t, List expectationDetails) {
return composeExplanation(result.stream()
.map((Evaluator.Entry each) -> evaluatorEntryToFormattedEntry(
each,
() -> (each.hasOutput() ?
InternalUtils.formatObject(each.output() instanceof Boolean ? each.expectedBooleanValue() : each.output()) :
InternalUtils.formatObject(t))))
.peek((FormattedEntry each) -> {
Optional formSnapshot = each.mismatchExplanation();
formSnapshot.ifPresent(expectationDetails::add);
})
.collect(toList()));
}
private static String composeExplanation(List formattedEntries) {
AtomicInteger mismatchExplanationCount = new AtomicInteger(0);
boolean mismatchExplanationFound = formattedEntries
.stream()
.anyMatch(e -> e.mismatchExplanation().isPresent());
return evaluatorEntriesToString(
formattedEntries,
columnLengths -> formattedEntryToString(
columnLengths[0], columnLengths[1], columnLengths[2],
mismatchExplanationCount, mismatchExplanationFound));
}
private static boolean useEvaluator(Class> myClass, Properties properties) {
return Boolean.parseBoolean(properties.getProperty(myClass.getName() + ".useEvaluator", "true"));
}
private static FormattedEntry evaluatorEntryToFormattedEntry(Evaluator.Entry entry, Supplier outputFormatter) {
return new FormattedEntry(
InternalUtils.formatObject(entry.input()),
entry.formName(),
entry.level() == 0 ?
"" :
format("%" + (entry.level() * 2) + "s", ""),
!asList(LEAF, AND, OR, NOT, FUNCTION).contains(entry.type()) ?
null :
outputFormatter.get(),
entry.hasExpectationDetail() ? entry.expectationDetail() : null);
}
private static Function formattedEntryToString(
int inputColumnWidth, int formNameColumnLength, int outputColumnLength,
AtomicInteger i, boolean mismatchExplanationFound) {
return (FormattedEntry formattedEntry) ->
(mismatchExplanationFound ?
format("%-4s", formattedEntry.mismatchExplanation().isPresent() ?
"[" + i.getAndIncrement() + "]" : "") :
"") +
format("%-" + Math.max(2, inputColumnWidth) + "s" +
"%-" + (formNameColumnLength + 2) + "s" +
"%-" + Math.max(2, outputColumnLength) + "s",
formattedEntry.input().orElse(""),
formattedEntry.input().map(v -> "->").orElse(" ") + formattedEntry.indent() + formattedEntry.formName(),
formattedEntry.output().map(v -> "->" + v).orElse(""));
}
private static String evaluatorEntriesToString(List formattedEntries, Function> formatterFactory) {
int maxInputLength = 0, maxIndentAndFormNameLength = 0, maxOutputLength = 0;
for (FormattedEntry eachEntry : formattedEntries) {
int inputLength = eachEntry.input().map(String::length).orElse(0);
if (inputLength > maxInputLength)
maxInputLength = inputLength;
int inputAndFormNameLength = eachEntry.indent().length() + eachEntry.formName().length();
if (inputAndFormNameLength > maxIndentAndFormNameLength)
maxIndentAndFormNameLength = inputAndFormNameLength;
int outputLength = eachEntry.output().map(String::length).orElse(0);
if (outputLength > maxOutputLength)
maxOutputLength = outputLength;
}
Function formatter = formatterFactory.apply(new int[] { maxInputLength, maxIndentAndFormNameLength, maxOutputLength });
AtomicReference lastFormattedEntry = new AtomicReference<>();
return formattedEntries
.stream()
.map((FormattedEntry eachEntry) -> {
FormattedEntry lastEntry = lastFormattedEntry.get();
lastFormattedEntry.set(eachEntry);
if (lastEntry == null)
return eachEntry;
if (Objects.equals(lastEntry.input, eachEntry.input))
return new FormattedEntry(null, eachEntry.formName, eachEntry.indent(), eachEntry.output, eachEntry.mismatchExplanation);
return eachEntry;
})
.map(formatter)
.map(s -> ("+" + s).trim().substring(1))
.collect(joining(format("%n")));
}
static class FormattedEntry {
private final String input;
private final String formName;
private final String indent;
private final String output;
private final Object mismatchExplanation;
FormattedEntry(String input, String formName, String indent, String output, Object mismatchExplanation) {
this.input = input;
this.formName = formName;
this.indent = indent;
this.output = output;
this.mismatchExplanation = mismatchExplanation;
}
Optional input() {
return Optional.ofNullable(this.input);
}
String indent() {
return this.indent;
}
String formName() {
return this.formName;
}
Optional mismatchExplanation() {
return Optional.ofNullable(this.mismatchExplanation);
}
Optional output() {
return Optional.ofNullable(this.output);
}
}
public static class Result {
final boolean result;
final List entries;
public Result(boolean result, List entries) {
this.result = result;
this.entries = entries;
}
public boolean result() {
return this.result;
}
}
}