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

com.github.mperry.fg.test.Specification.groovy Maven / Gradle / Ivy

There is a newer version: 0.8
Show newest version
package com.github.mperry.fg.test

import fj.F
import fj.F2
import fj.F3
import fj.F4
import fj.F5
import fj.F6
import fj.F7
import fj.F8
import fj.P2
import fj.data.Option
import fj.data.Validation
import fj.test.Arbitrary
import fj.test.Bool
import fj.test.CheckResult
import fj.test.Property
import groovy.transform.TypeChecked
import groovy.transform.TypeCheckingMode
import org.codehaus.groovy.runtime.NullObject

/**
 * Created with IntelliJ IDEA.
 * User: MarkPerry
 * Date: 30/11/13
 * Time: 3:58 PM
 * To change this template use File | Settings | File Templates.
 */
@TypeChecked
class Specification {

	static final int MAX_ARGS = 5
	static final Map> FUNC_TYPES = [1: F, 2: F2, 3: F3, 4: F4, 5: F5, 6: F6, 7: F7, 8: F8]

    @TypeChecked(TypeCheckingMode.SKIP)
	static Property createProp(Map, Arbitrary> map, Option> pre, Closure c, F, Boolean> validation) {
		def list = c.getParameterTypes()
		def listOptArbs = list.collect { Class it -> map.containsKey(it) ? Option.some(map[it]) : Option.none() }
		def allMapped = listOptArbs.forAll { Option it -> it.isSome() }
		if (!allMapped) {
			throw new Exception("Not all function parameter types were found: ${list.findAll { !map.containsKey(it)}}")
		}
        def listArb = listOptArbs.collect { Option it -> it.some() }
		dynamicCreateProp(listArb, pre, c, validation)
	}

	@TypeChecked(TypeCheckingMode.SKIP)
	static Property dynamicCreateProp(List list, Option> pre, Closure c, F, Boolean> validate) {
		this."createProp${list.size()}"(list, pre, c, validate)
	}

	static Property showAllWithMap(Boolean truth, Map, Arbitrary> map, Option> pre, Closure c, F, Boolean> validate) {
		if (c.getMaximumNumberOfParameters() > MAX_ARGS) {
			throw new Exception("Testing does not support ${c.getMaximumNumberOfParameters()}, maximum supported is $MAX_ARGS")
		}
		def p = createProp(map, pre, c, validate)
		p
//		def cr = p.check()
//		p.checkBooleanWithNullableSummary(truth)
	}

	/**
	 *
	 * @param map Override the default map
	 * @param c
	 */
	@TypeChecked(TypeCheckingMode.SKIP)
	static Property spec(Map, Arbitrary> map, Closure c) {
		def p = showAllWithMap(true, Model.DEFAULT_MAP + map, Option.none(), c, Model.DEFAULT_VALIDATOR)
		p
//		check(p, true)

	}

	static Property spec(Model config) {
		def p = showAllWithMap(config.truth, config.map, config.pre, config.function, config.validator)
		p
//		check(p, config.truth)
	}

	@TypeChecked(TypeCheckingMode.SKIP)
	static Property spec(Closure c) {
		def p = showAllWithMap(true, Model.DEFAULT_MAP, Option.none(), c, Model.DEFAULT_VALIDATOR)
		p
//		check(p, true)
	}

	@TypeChecked(TypeCheckingMode.SKIP)
	static CheckResult specAssert(Map, Arbitrary> map, Closure c) {
		check(spec(map, c), true)
	}

	static CheckResult specAssert(Model config) {
		check(spec(config), config.truth)
	}

	@TypeChecked(TypeCheckingMode.SKIP)
	static CheckResult specAssert(Closure c) {
//		def p = showAllWithMap(true, Model.DEFAULT_MAP, Option.none(), c, Model.DEFAULT_VALIDATOR)
		check(spec(c), true)
	}

	static CheckResult specAssert(Property p, boolean truth) {
		check(p, truth)
	}

	static CheckResult specAssert(Property p) {
		check(p, true)
	}

	static CheckResult check(Property p, Boolean truth) {
//		def cr = p.check()
		p.checkBooleanWithNullableSummary(truth)
	}

	static Property implies(Boolean pre, Boolean result) {
		Bool.bool(pre).implies(result)
	}

	static Validation perform(Closure c, List args) {
		try {
			Validation.success(c.call(args))
		} catch (Throwable t) {
			Validation.fail(t)
		}
	}

	static void checkTypes(List argList, Closure func) {
		def objectTypes = argList.collect { it.getClass() }
		def closureTypes = func.getParameterTypes().toList()
		def typesOk = objectTypes.zip(closureTypes).inject(true) { Boolean result, P2 p ->
			result && ((p._1() == NullObject.class) ? true : p._2().isAssignableFrom(p._1()))
		}
		if (!typesOk || objectTypes.size() != closureTypes.size()) {
			throw new Exception("Cannot call func with value types $objectTypes.  Closure requires types $closureTypes")
		}
	}

	static Property callCommon(List argList, Option> pre, Closure func, F, Boolean> validate) {
		checkTypes(argList, func)
		pre.map { Closure c ->
	 		checkTypes(argList, c)
		}
		def preOk = pre.map { Closure it -> it.call(argList) }.orSome(true)
		def result = !preOk ? true : validate.f(perform(func, argList))
		implies(preOk, result)
	}

	static Class funcType(List list) {
		FUNC_TYPES[list.size()]
	}

	static Property createProp0(List> list, Option> pre, Closure func, F, Boolean> validate) {
		callCommon([], pre, func, validate)
	}

	static Property createProp1(List> list, Option> pre, Closure func, F, Boolean> validate) {
		Property.property(list[0], { a ->
			callCommon([a], pre, func, validate)
//		}.asType(funcType(list)))
		} as F)
	}

	static Property createProp2(List> list, Option> pre, Closure func, F, Boolean> validate) {
		Property.property(list[0], list[1], { Object a, Object b ->
			callCommon([a, b], pre, func, validate)
		} as F2)
	}

	static Property createProp3(List> list, Option> pre, Closure func, F, Boolean> validate) {
		Property.property(list[0], list[1], list[2], { a, b, c ->
			callCommon([a, b, c], pre, func, validate)
		} as F3)
	}

	static Property createProp4(List> list, Option> pre, Closure func, F, Boolean> validate) {
		Property.property(list[0], list[1], list[2], list[3], { a, b, c, d ->
			callCommon([a, b, c, d], pre, func, validate)
		} as F4)
	}

	static Property createProp5(List> list, Option> pre, Closure func, F, Boolean> validate) {
		Property.property(list[0], list[1], list[2], list[3], list[4], { a, b, c, d, e ->
			callCommon([a, b, c, d, e], pre, func, validate)
		} as F5)
	}

}