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

com.github.dakusui.thincrest.metamor.MetamorphicTestCaseFactory Maven / Gradle / Ivy

The newest version!
package com.github.dakusui.thincrest.metamor;

import com.github.dakusui.thincrest.metamor.internals.InternalUtils;
import com.github.dakusui.thincrest_pcond.forms.Printables;

import java.text.MessageFormat;
import java.util.function.*;

import static com.github.dakusui.thincrest_pcond.forms.Predicates.transform;
import static java.util.Objects.requireNonNull;

/**
 * An interface of a factory for a metamorphic test case.
 *
 * @param  Type of "source value".
 * @param  Input type of the function under test.
 * @param  Output type of function under test.
 * @param  Input type of metamorphic relation.
 */
public interface MetamorphicTestCaseFactory {
  /**
   * Returns a function under test.
   *
   * @return a function under test.
   */
  Function fut();

  InputResolver.Sequence.Factory inputResolverSequenceFactory();

  Function>, R> metamorphicTransformer();

  Predicate metamorphicChecker();

  default Predicate>> metamorphicRelation() {
    return transform(metamorphicTransformer()).check(metamorphicChecker());
  }

  /**
   * Returns a function that executes the FUT for each element in `Dataset>`.
   *
   * @return A function that executes the FUT for each element in `Dataset>`.
   */
  default Function>, Dataset>> metamorphicExecutor() {
    return InternalUtils.createObservableProcessingPipeline("fut", this.metamorphicMapper(), this.inputResolverSequenceFactory().count(), inputVariableNameFormatter(), ioVariableName());
  }

  /**
   * A name of input variable.
   * This will be printed in the test report.
   *
   * @return A name of input variable.
   */
  String inputVariableName();

  /**
   * In metamorphic testing context, the function under test is executed multiple times with different input values.
   * The returned function renders input variable names so that they can be identified each other when an index is given.
   * By default, it returns a function that appends the given index.
   *
   * @return A function to render an input variable name corresponding to a given index.
   */
  default IntFunction inputVariableNameFormatter() {
    return i -> this.inputVariableName() + "[" + i + "]";
  }

  default Function, IoPair>, Function, IoPair>> metamorphicMapper() {
    return Printables.function(
        () -> "  " + fut(),
        ioContext -> Printables.function(
            () -> "input:" + ioContext.output(),
            inputResolver -> {
              I in = inputResolver.apply(ioContext.output());
              return IoPair.create(in, fut().apply(in));
            }));
  }

  /**
   * A builder method that returns a printable predicate that examines the function under test.
   *
   * @return A printable predicate that examines FUT with a given metamorphic relation.
   */
  default Predicate toMetamorphicTestPredicate() {
    return transform(this.inputResolverSequenceFactory().andThen(this.metamorphicExecutor())).check(this.metamorphicRelation());
  }

  String ioVariableName();


  static  Builder forFunctionUnderTest(String name, Function fut) {
    return forFunctionUnderTest(Printables.function(name, fut));
  }

  static  Builder forFunctionUnderTest(Function fut) {
    return new Builder().fut(fut);
  }

  class Impl implements MetamorphicTestCaseFactory {

    private final Function fut;
    private final InputResolver.Sequence.Factory inputResolverSequenceFactory;
    private final Function>, R> metamorphicTransformer;
    private final Predicate metamorphicChecker;
    private final String inputVariableName;
    private final String ioVariableName;

    public Impl(Function fut, InputResolver.Sequence.Factory inputResolverSequenceFactory, Function>, R> metamorphicTransformer, Predicate metamorphicChecker, String inputVariableName, String ioVariableName) {
      this.fut = fut;
      this.inputResolverSequenceFactory = inputResolverSequenceFactory;
      this.metamorphicTransformer = metamorphicTransformer;
      this.metamorphicChecker = metamorphicChecker;
      this.inputVariableName = inputVariableName;
      this.ioVariableName = ioVariableName;
    }

    @Override
    public Function fut() {
      return this.fut;
    }

    @Override
    public InputResolver.Sequence.Factory inputResolverSequenceFactory() {
      return this.inputResolverSequenceFactory;
    }

    @Override
    public Function>, R> metamorphicTransformer() {
      return this.metamorphicTransformer;
    }

    @Override
    public Predicate metamorphicChecker() {
      return this.metamorphicChecker;
    }

    @Override
    public String inputVariableName() {
      return this.inputVariableName;
    }

    @Override
    public String ioVariableName() {
      return this.ioVariableName;
    }

  }

  abstract class BuilderBase, X, I, O, R> {
    abstract static class InputResolverSequenceFactoryProvider implements Supplier> {
      final BuilderBase parent;

      protected InputResolverSequenceFactoryProvider(BuilderBase parent) {
        this.parent = parent;
      }

      abstract void add(Function formatter, Function function);

      abstract int count();
    }

    protected Function fut;
    protected InputResolverSequenceFactoryProvider inputResolverSequenceFactoryProvider;
    protected Predicate checker;
    protected String sourceVariableName;
    protected String inputVariableName;
    protected String ioVariableName;
    protected String outputVariableName;

    protected BuilderBase() {
      this.sourceVariableName("x")
          .inputVariableName("input")
          .ioVariableName("io")
          .outputVariableName("out");
    }

    protected , XX, RR> BB newBuilder(Supplier constructor) {
      return constructor.get()
          .fut(this.fut)
          .sourceVariableName(this.sourceVariableName)
          .inputVariableName(this.inputVariableName)
          .ioVariableName(this.ioVariableName)
          .outputVariableName(this.outputVariableName);
    }

    protected , RR> BB newBuilderWithSpecifiedRelationType(Supplier constructor) {
      return newBuilder(constructor)
          .inputResolverSequenceFactoryProvider(this.inputResolverSequenceFactoryProvider);
    }

    protected , XX> BB newBuilderWithSpecifiedSourceType(Supplier constructor) {
      return this.newBuilder(constructor);
    }

    @SuppressWarnings("unchecked")
    public B sourceVariableName(String sourceVariableName) {
      this.sourceVariableName = sourceVariableName;
      return (B) this;
    }

    @SuppressWarnings("unchecked")
    public B inputVariableName(String inputVariableName) {
      this.inputVariableName = inputVariableName;
      return (B) this;
    }

    @SuppressWarnings("unchecked")
    public B outputVariableName(String outputVariableName) {
      this.outputVariableName = requireNonNull(outputVariableName);
      return (B) this;
    }

    @SuppressWarnings("unchecked")
    public B ioVariableName(String ioVariableName) {
      this.ioVariableName = requireNonNull(ioVariableName);
      return (B) this;
    }

    @SuppressWarnings("unchecked")
    B inputResolverSequenceFactoryProvider(InputResolverSequenceFactoryProvider inputResolverSequenceFactory) {
      this.inputResolverSequenceFactoryProvider = requireNonNull(inputResolverSequenceFactory);
      return (B) this;
    }

    public B inputResolverSequenceFactory(InputResolver.Sequence.Factory inputResolverSequenceFactory) {
      Utils.requireState(this.inputResolverSequenceFactoryProvider == null, "Input Resolver Sequence Factory is already set.");
      return this.inputResolverSequenceFactoryProvider(new InputResolverSequenceFactoryProvider(this) {

        @Override
        public InputResolver.Sequence.Factory get() {
          return inputResolverSequenceFactory;
        }

        @Override
        void add(Function formatter, Function function) {
          throw new IllegalStateException();
        }

        @Override
        int count() {
          return inputResolverSequenceFactory.count();
        }
      });
    }

    public B addInputResolvers(Function, InputResolver.Sequence.Factory> b) {
      return this.addInputResolvers(this.sourceVariableName, b);
    }

    public B addInputResolvers(String variableName, Function, InputResolver.Sequence.Factory> b) {
      InputResolver.Sequence.Factory.Builder ib = new InputResolver.Sequence.Factory.Builder<>(this.inputVariableName, variableName);
      return this.inputResolverSequenceFactory(b.apply(ib));
    }

    @SuppressWarnings("unchecked")
    public B addInputResolver(Function formatter, Function f) {
      requireNonNull(formatter);
      requireNonNull(f);
      if (this.inputResolverSequenceFactoryProvider == null) {
        this.inputResolverSequenceFactoryProvider = new InputResolverSequenceFactoryProvider(this) {
          int count = 0;
          Consumer> inputResolverAdder = b -> {
          };

          @Override
          void add(Function formatter, Function f) {
            inputResolverAdder = inputResolverAdder.andThen(b -> b.function(formatter, f));
            count++;
          }

          @Override
          int count() {
            return count;
          }

          @Override
          public InputResolver.Sequence.Factory get() {
            InputResolver.Sequence.Factory.Builder b = new InputResolver.Sequence.Factory.Builder<>(BuilderBase.this.inputVariableName, BuilderBase.this.sourceVariableName);
            this.inputResolverAdder.accept(b);
            return b.build();
          }
        };
      }
      this.inputResolverSequenceFactoryProvider.add(formatter, f);
      return (B) this;
    }

    public abstract , XX> BB sourceValueType(XX sourceType);

    /**
     * Let this object know the source type.
     *
     * @param sourceType The type of the source value.x
     * @param        The type of this object.
     * @param        The type of the input value.
     * @return This object
     */
    @SuppressWarnings("unused")
    public , XX> BB sourceValueType(Class sourceType) {
      return this.sourceValueType((XX) null);
    }

    /**
     * Let this factory know that the source value and the input values are the same type.
     *
     * @param  The type of this builder.
     * @return This object
     */
    @SuppressWarnings("unchecked")
    public > BB makeInputResolversEndomorphic() {
      return (BB) this.sourceValueType((I) null)
          .addInputResolver(x -> String.format("%s", x), Function.identity());
    }


    /**
     * Specifies a function under test.
     *
     * @param fut A function under test
     * @return This builder object
     */
    @SuppressWarnings("unchecked")
    public B fut(Function fut) {
      this.fut = requireNonNull(fut);
      return (B) this;
    }

    public 

MetamorphicTestCaseFactoryWithPreformer.Builder withPreformer() { return this.newBuilderWithSpecifiedRelationType(MetamorphicTestCaseFactoryWithPreformer.Builder::new); } public Builder skipPreformer() { return this.newBuilderWithSpecifiedRelationType(Builder::new); } public abstract

MetamorphicTestCaseFactoryWithPreformer.Builder preformer(Function, P> preformer); public

MetamorphicTestCaseFactoryWithPreformer.Builder preformer(String preformerName, Function, P> preformer) { return this.preformer(Printables.function(preformerName, preformer)); } public MetamorphicTestCaseFactoryWithPreformer.Builder outputOnly() { return this.preformer("outputOnly", IoPair::output); } @SuppressWarnings("unchecked") public B checker(Predicate checker) { this.checker = requireNonNull(checker); return (B) this; } public MetamorphicTestCaseFactory check(String name, Predicate checker) { return this.check(Printables.predicate(name, checker)); } public MetamorphicTestCaseFactory check(Predicate checker) { return checker(checker).build(); } public abstract MetamorphicTestCaseFactory build(); } class Builder extends BuilderBase, X, I, O, R> { private Function>, R> transformer; public Builder() { } @SuppressWarnings("unchecked") @Override public , XX> BB sourceValueType(XX sourceType) { return (BB) this., XX>newBuilderWithSpecifiedSourceType(Builder::new); } public Builder propositionFactory(Function>, Proposition> pf) { return this., Proposition>newBuilderWithSpecifiedRelationType(Builder::new) .transformer(pf) .checker(PropositionPredicate.INSTANCE); } public MetamorphicTestCaseFactory proposition(Function formatter, Predicate>> p) { return this.propositionFactory( Proposition.Factory.create( p, formatter, i -> ioVariableName + "[" + i + "]", inputResolverSequenceFactoryProvider.count())) .build(); } public MetamorphicTestCaseFactory proposition(String propositionName, Predicate>> p) { return this.proposition(args -> MessageFormat.format(propositionName, args), p); } public

MetamorphicTestCaseFactoryWithPreformer.Builder preformer(Function, P> preformer) { return this.

withPreformer().preformer(preformer); } public Builder transformer(Function>, R> transformer) { this.transformer = requireNonNull(transformer); return this; } @Override public MetamorphicTestCaseFactory build() { return new Impl<>(this.fut, this.inputResolverSequenceFactoryProvider.get(), this.transformer, this.checker, this.inputVariableName, this.ioVariableName); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy