com.github.dakusui.crest.core.Session Maven / Gradle / Ivy
package com.github.dakusui.crest.core;
import com.github.dakusui.crest.functions.TransformingPredicate;
import com.github.dakusui.crest.utils.printable.TrivialFunction;
import junit.framework.AssertionFailedError;
import junit.framework.ComparisonFailure;
import java.util.*;
import java.util.function.*;
import static com.github.dakusui.crest.utils.InternalUtils.*;
import static java.lang.String.format;
import static java.util.Arrays.asList;
public interface Session {
static void perform(String message, T value, Matcher super T> matcher, ExceptionFactory exceptionFactory) {
Report report = perform(value, matcher);
if (!report.isSuccessful()) {
if (report.exceptions().isEmpty()) {
Throwable exception = exceptionFactory.create(message, report, report.exceptions());
if (exception instanceof RuntimeException)
throw (RuntimeException) exception;
if (exception instanceof Error)
throw (Error) exception;
throw new RuntimeException(exception);
}
if (report.exceptions().get(0) instanceof AssertionFailedError)
throw new ComparisonFailure(
report.exceptions().get(0).getMessage(),
report.expectation(),
report.mismatch()
);
throw new ExecutionFailure(message, report.expectation(), report.mismatch(), report.exceptions());
}
}
static Report perform(T value, Matcher matcher) {
return perform(value, matcher, create());
}
static Report perform(T value, Matcher matcher, Session session) {
if (matcher.matches(value, session, new LinkedList<>())) {
session.matched(true);
} else {
matcher.describeExpectation(session.matched(false));
matcher.describeMismatch(value, session);
}
return session.report();
}
Report report();
void describeExpectation(Matcher.Composite matcher);
void describeExpectation(Matcher.Leaf matcher);
void describeMismatch(T value, Matcher.Composite matcher);
void describeMismatch(T value, Matcher.Leaf matcher);
@SuppressWarnings("unchecked")
default boolean matches(Matcher.Leaf leaf, T value, Consumer listener) {
if (this instanceof Impl)
((Impl) this).snapshot(value, null, value);
try {
return this.test(
(Predicate) leaf.p(),
this.apply(
(Function) leaf.func(),
value
));
} catch (RuntimeException | Error exception) {
listener.accept(exception);
addException(exception);
return false;
}
}
O apply(Function func, I value);
boolean test(Predicate pred, I value);
static Session create() {
return new Impl<>();
}
Session addException(Throwable exception);
Session matched(boolean b);
@FunctionalInterface
interface ExceptionFactory {
Throwable create(String message, Report report, List causes);
}
class Impl implements Session {
private boolean variableExplained = false;
static class Writer {
private int level = 0;
private List buffer = new LinkedList<>();
Impl.Writer enter() {
level++;
return this;
}
Impl.Writer leave() {
level--;
return this;
}
Impl.Writer appendLine(String format, Object... args) {
buffer.add(String.format(indent(this.level) + format, args));
return this;
}
String write() {
return String.join("\n", this.buffer);
}
private String indent(int level) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < level; i++) {
builder.append(" ");
}
return builder.toString();
}
}
private static final String VARIABLE_NAME = "x";
private static final String TRANSFORMED_VARIABLE_NAME = "y";
private Map, Function, ?>> memoizationMapForFunctions = new HashMap<>();
private Map, Predicate>> memoizationMapForPredicates = new HashMap<>();
private Map, String> snapshots = new HashMap<>();
private HashSet> explained = new HashSet<>();
Impl.Writer expectationWriter = new Impl.Writer();
Impl.Writer mismatchWriter = new Impl.Writer();
private boolean result;
private List exceptions = new LinkedList<>();
@Override
public Report report() {
if (!Impl.this.exceptions.isEmpty())
mismatchWriter.appendLine("FAILED");
return new Report() {
private List exceptions = Collections.unmodifiableList(Impl.this.exceptions);
private boolean result = Impl.this.result;
private String mismatch = mismatchWriter.write();
private String expectation = expectationWriter.write();
@Override
public String expectation() {
return expectation;
}
@Override
public String mismatch() {
return mismatch;
}
@Override
public List exceptions() {
return this.exceptions;
}
@Override
public boolean isSuccessful() {
return result && exceptions().isEmpty();
}
};
}
@Override
public void describeExpectation(Matcher.Composite matcher) {
beginExpectation(matcher);
try {
matcher.children().forEach(each -> each.describeExpectation(this));
} finally {
endExpectation(matcher);
}
}
void beginExpectation(Matcher.Composite matcher) {
expectationWriter.appendLine(format("%s:[", matcher.name())).enter();
}
@SuppressWarnings("unused")
void endExpectation(Matcher.Composite matcher) {
expectationWriter.leave().appendLine("]");
}
@Override
public void describeMismatch(T value, Matcher.Composite matcher) {
beginMismatch(value, matcher);
try {
matcher.children().forEach(each -> each.describeMismatch(value, this));
} finally {
endMismatch(value, matcher);
}
}
@Override
public void describeExpectation(Matcher.Leaf matcher) {
describeExpectationTo(expectationWriter, matcher);
}
@SuppressWarnings("unchecked")
@Override
public void describeMismatch(T value, Matcher.Leaf matcher) {
if (this.matches(matcher, value, NOP)) {
describeExpectationTo(mismatchWriter, matcher);
return;
}
Function func = matcher.func();
Predicate> p = matcher.p();
appendMismatchSummary(value, func, p);
// if p is plain predicate
// p(func(x)) == true
// -> In this case, no additional information can be printed for p
// if p is transforming predicate
// p(y) == true
// y = f(func(x))
// -> In this case, how p worked can be broken down into p(y) side and
// f(func(x)) side.
if (!variableExplained) {
this.mismatchWriter.enter();
this.mismatchWriter.appendLine("%s=%s", VARIABLE_NAME, snapshotOf(null, value));
this.mismatchWriter.leave();
this.variableExplained = true;
}
if (p instanceof TransformingPredicate && !fails(func, value)) {
this.mismatchWriter
.enter()
.appendLine(
"%s=%s%s",
TRANSFORMED_VARIABLE_NAME,
VARIABLE_NAME,
func);
try {
// This doesn't give additional information if func isn't a chained function
// but still makes easier to read the output.
explainFunction(value, func, VARIABLE_NAME, this.mismatchWriter);
} finally {
this.mismatchWriter.leave();
}
TransformingPredicate, ?> pp = (TransformingPredicate, ?>) p;
this.mismatchWriter
.enter()
.appendLine(
"%s%s %s",
TRANSFORMED_VARIABLE_NAME,
pp.function(),
pp.predicate())
.leave();
explainFunction(
(T) apply(func, value),
(Function) pp.function(),
TRANSFORMED_VARIABLE_NAME, this.mismatchWriter);
} else {
if (func instanceof ChainedFunction)
this.mismatchWriter
.enter()
.appendLine("%s%s %s", VARIABLE_NAME, func, p)
.leave();
explainFunction(value, func, VARIABLE_NAME, this.mismatchWriter);
}
}
private void appendMismatchSummary(T value, Function func, Predicate> p) {
String formattedExpectation = formatExpectation(p, func);
String formattedFunctionOutput = this.snapshotOf(func, value);
if (fails(func, value)) {
this.mismatchWriter.appendLine(
"%s failed with %s",
formattedExpectation,
formattedFunctionOutput
);
} else if (fails(p, this.apply(func, value))) {
this.mismatchWriter.appendLine(
"%s failed with %s",
formattedExpectation,
this.snapshotOf(p, this.apply(func, value))
);
} else {
this.mismatchWriter.appendLine("%s was not met", formattedExpectation);
}
}
void describeExpectationTo(Impl.Writer writer, Matcher.Leaf matcher) {
writer.appendLine("%s", formatExpectation(matcher.p(), matcher.func()));
}
void beginMismatch(T value, Matcher.Composite matcher) {
if (matcher.isTopLevel()) {
this.mismatchWriter.appendLine("when %s=%s; then %s:[", VARIABLE_NAME, summarizeValue(value), matcher.name());
} else
this.mismatchWriter.appendLine("%s:[", matcher.name());
mismatchWriter.enter();
}
void endMismatch(T value, Matcher.Composite matcher) {
this.mismatchWriter.leave().appendLine("]->%s", matcher.matches(value, this, new LinkedList<>()));
}
@Override
public Session addException(Throwable exception) {
this.exceptions.add(exception);
return this;
}
@SuppressWarnings("unchecked")
private boolean fails(Function, ?> func, Object value) {
try {
this.apply((Function
© 2015 - 2025 Weber Informatics LLC | Privacy Policy