From: Michael Wallner Date: Fri, 8 Mar 2013 14:56:03 +0000 (+0100) Subject: Merge branch 'with_results' X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fascertain;a=commitdiff_plain;h=fe749a704d38b3e7d13195f09cfdd91b02d70617;hp=caddc1fbc70d19f84a3c37321ff22d0c9ed63adb Merge branch 'with_results' --- diff --git a/lib/ascertain/Assert.php b/lib/ascertain/Assert.php new file mode 100644 index 0000000..6759986 --- /dev/null +++ b/lib/ascertain/Assert.php @@ -0,0 +1,341 @@ + + * $testable->assert() + * ->that("name")->isLen(3, 40, "must be between 3 and 40 characters long") + * ->that("email")->isEmail("is not valid"); + * + */ +class Assert +{ + /** + * Testable properties + * @var array + */ + private $properties; + + /** + * The name of the currently inspected property + * @var string + */ + private $inspectedProperty; + + /** + * Whether the argument to when() was true + * @var bool + */ + private $inspectCondition = true; + + /** + * Whether assertions cause exceptions of this type + * @var string + */ + private $exceptionClass; + + /** + * Failed assertions + * @var array + */ + private $validationErrors = array(); + + /** + * Succeeded assertions + * @var array + */ + private $validationResults = array(); + + /** + * @param \ascertain\Testable $testable + * @param string $exceptionClass + */ + function __construct(Testable $testable, $exceptionClass = null) { + $this->properties = $testable->export(); + $this->exceptionClass = $exceptionClass; + } + + /** + * @return int count of failed assertions + */ + function hasErrors() { + return count($this->validationErrors); + } + + /** + * @return array of failed assertions + */ + function getErrors() { + return $this->validationErrors; + } + + /** + * Reset assertions failures/results + * @return \ascertain\Assert + */ + function resetErrors() { + $this->validationResults = array(); + $this->validationErrors = array(); + return $this; + } + + /** + * Set the currently inspected property + * @param string $property + * @return \ascertain\Assert + */ + function that($property) { + $this->inspectCondition = true; + $this->inspectedProperty = $property; + return $this; + } + + /** + * The following assertions are only tested if the argument is true + * @param bool $condition + * @return \ascertain\Assert + */ + function when($condition) { + $this->inspectCondition = $condition; + return $this; + } + + /** + * !strcmp($v, $c) + * @param mixed &$r + * @param string $v + * @param string $c + * @return bool + */ + function test(&$r, $v, $c) { + $r = !strcmp($v, $c); + return $r; + } + + /** + * !strlen($v) + * @param mixed &$r + * @param string $v + * @return bool + */ + function testNothing(&$r, $v) { + $r = !strlen($v); + return $r; + } + + /** + * is_numeric($v) + * @param mixed &$r + * @param string $v + * @return bool + */ + function testNumeric(&$r, $v) { + $r = is_numeric($v); + return $r; + } + + /** + * Test whether the argument is scalar + * @param mixed &$r + * @param mixed $v + * @param bool $strictNulls + * @return bool + */ + function testScalar(&$r, $v, $strictNulls = false) { + $r = is_scalar($v) && (!$strictNulls || !isset($v)); + return $r; + } + + /** + * Test wheter the argument constists only of printable characters + * @param mixed &$r + * @param string $v + * @return bool + */ + function testPrintable(&$r, $v) { + return preg_match("/^[[:print:]\\P{Cc}]*\$/u", $v, $r) > 0; + } + + /** + * Test wheter the string length is between a certain range + * @param mixed &$r + * @param string $v + * @param int $min + * @param int $max + * @return bool + */ + function testLen(&$r, $v, $min, $max = PHP_INT_MAX) { + return $this->testRange($r, function_exists("mb_strlen") ? mb_strlen($v) : strlen($v), $min, $max); + } + + /** + * Test wheter a value is between a certain range + * @param mixed &$r + * @param mixed $v + * @param mixed $min + * @param mixed $max + * @return bool + */ + function testRange(&$r, $v, $min, $max) { + $r = (($v >= $min) && ($v <= $max)); + return $r; + } + + /** + * Test for a valid integer with FILTER_VALIDATE_INT + * @param mixed &$r + * @param mixed $v + * @param array $options + * @return bool + */ + function testInteger(&$r, $v, array $options = null) { + $r = filter_var($v, FILTER_VALIDATE_INT, $options); + return $r !== false; + } + + /** + * Test for a valid boolean with FILTER_VALIDATE_BOOLEAN + * @param mixed &$r + * @param type $v + * @param array $options + * @return type + */ + function testBoolean(&$r, $v, array $options = null) { + $options["flags"] = isset($options["flags"]) ? $options["flags"]|FILTER_NULL_ON_FAILURE : FILTER_NULL_ON_FAILURE; + $r = filter_var($v, FILTER_VALIDATE_BOOLEAN, $options); + return isset($r); + } + + /** + * Test for a valid float with FILTER_VALIDATE_FLOAT + * @param mixed &$r + * @param mixed $v + * @param array $options + * @return bool + */ + function testFloat(&$r, $v, array $options = null) { + $r = filter_var($v, FILTER_VALIDATE_FLOAT, $options); + return $r !== false; + } + + /** + * Test for a valid URL with FILTER_VALIDATE_URL + * @param mixed &$r + * @param string $v + * @param array $options + * @return bool + */ + function testUrl(&$r, $v, array $options = null) { + $r = filter_var($v, FILTER_VALIDATE_URL, $options); + return $r !== false; + } + + /** + * Test for a valid email address with FILTER_VALIDATE_EMAIL + * @param mixed &$r + * @param string $v + * @param array $options + * @return bool + */ + function testEmail(&$r, $v, array $options = null) { + $r = filter_var($v, FILTER_VALIDATE_EMAIL, $options); + return $r !== false; + } + + /** + * Test for a valid IP address with FILTER_VALIDATE_IP + * @param mixed &$r + * @param string $v + * @param array $options + * @return bool + */ + function testIp(&$r, $v, array $options = null) { + $r = filter_var($v, FILTER_VALIDATE_IP, $options); + return $r !== false; + } + + /** + * Test whether a string contains another string or an array contains a key + * @param mixed &$r + * @param mixed $v haystack + * @param string $n needle + * @param bool $cs case-sensitive + * @return bool + */ + function testContaining(&$r, $v, $n, $cs = true) { + if (is_array($v)) { + if (!$cs) { + $v = array_change_key_case($v); + $n = strtolower($n); + } + if (array_key_exists($n, $v)) { + $r = $v[$n]; + return true; + } + return $r = false; + } else { + if ($cs) { + $r = strstr($v, (string) $n); + } else { + $r = stristr($v, (string) $n); + } + return $r !== false; + } + } + + /** + * Thest + * @param mixed &$r + * @param mixed $v + * @param array $a + * @param bool $strict + * @return bool + */ + function testAny(&$r, $v, $a, $strict = false) { + return $r = in_array($v, $a, $strict); + } + + /** + * Thest if a regular expression matches + * @param mixed &$r + * @param string $v + * @param stirng $regex + * @param int $flags + * @return bool + */ + function testMatching(&$r, $v, $regex, $flags = 0) { + return preg_match($regex, $v, $r, $flags) > 0; + } + + /** + * Semantic is(Not) wrapper to the assertions + * @param string $method + * @param array $args + * @return \ascertain\Assert + * @throws InvalidArgumentException (or rahter the configured exception) + */ + function __call($method, $args) { + $match = null; + if ($this->inspectCondition && preg_match("/^is(Not(?!hing))?(.*)\$/i", $method, $match)) { + list(, $not, $test) = $match; + + $result = null; + $error = array_pop($args); + array_unshift($args, $this->properties[$this->inspectedProperty]); + array_unshift($args, null); + $args[0] = &$result; + $valid = call_user_func_array(array($this, "test$test"), $args); + if ($valid === !!$not) { + $this->validationErrors[$this->inspectedProperty][] = $error; + if (($exception = $this->exceptionClass)) { + throw new $exception("$this->inspectedProperty $error"); + } + } + $this->validationResults[$this->inspectedProperty][] = $args[0]; + } + return $this; + } + +} diff --git a/lib/ascertain/Testable.php b/lib/ascertain/Testable.php new file mode 100644 index 0000000..869d468 --- /dev/null +++ b/lib/ascertain/Testable.php @@ -0,0 +1,13 @@ + - * $testable->assert() - * ->that("name")->isLen(3, 40, "must be between 3 and 40 characters long") - * ->that("email")->isEmail("is not valid"); - * - */ -class Assert -{ - /** - * Testable properties - * @var array - */ - private $properties; - - /** - * The name of the currently inspected property - * @var string - */ - private $inspectedProperty; - - /** - * Whether the argument to when() was true - * @var bool - */ - private $inspectCondition = true; - - /** - * Whether assertions cause exceptions of this type - * @var string - */ - private $exceptionClass; - - /** - * Failed assertions - * @var array - */ - private $validationErrors = array(); - - /** - * @param \ascertain\Testable $testable - * @param string $exceptionClass - */ - function __construct(Testable $testable, $exceptionClass = null) { - $this->properties = $testable->export(); - $this->exceptionClass = $exceptionClass; - } - - /** - * @return int count of failed assertions - */ - function hasErrors() { - return count($this->validationErrors); - } - - /** - * @return array of failed assertions - */ - function getErrors() { - return $this->validationErrors; - } - - /** - * Reset failed assertions - * @return \ascertain\Assert - */ - function resetErrors() { - $this->validationErrors = array(); - return $this; - } - - /** - * Set the currently inspected property - * @param string $property - * @return \ascertain\Assert - */ - function that($property) { - $this->inspectCondition = true; - $this->inspectedProperty = $property; - return $this; - } - - /** - * The following assertions are only tested if the argument is true - * @param bool $condition - * @return \ascertain\Assert - */ - function when($condition) { - $this->inspectCondition = $condition; - return $this; - } - - /** - * !strcmp($v, $c) - * @param string $v - * @param string $c - * @return bool - */ - function test($v, $c) { - return !strcmp($v, $c); - } - - /** - * !strlen($v) - * @param string $v - * @return bool - */ - function testNothing($v) { - return !strlen($v); - } - - /** - * is_numeric($v) - * @param string $v - * @return bool - */ - function testNumeric($v) { - return is_numeric($v); - } - - /** - * Test wheter string representations of original and the int-cast equal - * @param mixed $v - * @return bool - */ - function testInteger($v) { - return ((string) $v) === ((string)(int) $v); - } - - /** - * Test whether the argument is scalar - * @param mixed $v - * @param bool $strictNulls - * @return bool - */ - function testScalar($v, $strictNulls = false) { - return is_scalar($v) && (!$strictNulls || !isset($v)); - } - - /** - * Test wheter the argument constists only of printable characters - * @param string $v - * @return bool - */ - function testPrintable($v) { - return preg_match("/^[[:print:]\\P{Cc}]*\$/u", $v) > 0; - } - - /** - * Test wheter the string length is between a certain range - * @param string $v - * @param int $min - * @param int $max - * @return bool - */ - function testLen($v, $min, $max) { - return $this->testRange(function_exists("mb_strlen") ? mb_strlen($v) : strlen($v), $min, $max); - } - - /** - * Test wheter a value is between a certain range - * @param mixed $v - * @param mixed $min - * @param mixed $max - * @return bool - */ - function testRange($v, $min, $max) { - return $v >= $min && $v <= $max; - } - - /** - * Test for a valid email address with FILTER_VALIDATE_EMAIL - * @param stting $v - * @param int $options - * @return bool - */ - function testEmail($v, $options = null) { - return filter_var($v, FILTER_VALIDATE_EMAIL, $options) !== false; - } - - /** - * Test for a valid URL with FILTER_VALIDATE_URL - * @param string $v - * @param int $options - * @return bool - */ - function testUrl($v, $options = null) { - return filter_var($v, FILTER_VALIDATE_URL, $options) !== false; - } - - /** - * Test whether a string contains another string - * @param type $v haystack - * @param type $n needle - * @param bool $ci case-sensitive - * @return bool - */ - function testContains($v, $n, $ci = true) { - return ($ci ? strpos($v, $n) : stripos($v, $n)) !== false; - } - - /** - * Thest if a regular expression matches - * @param string $v - * @param stirng $regex - * @param int $flags - * @return int - */ - function testRegex($v, $regex, $flags = 0) { - return preg_match($regex, $v, null, $flags); - } - - /** - * Semantic is(Not) wrapper to the assertions - * @param string $method - * @param array $args - * @return \ascertain\Assert - * @throws InvalidArgumentException (or rahter the configured exception) - */ - function __call($method, $args) { - if ($this->inspectCondition && preg_match("/^is(Not)?(.*)\$/i", $method, $match)) { - list(, $not, $test) = $match; - - $error = array_pop($args); - array_unshift($args, $this->properties[$this->inspectedProperty]); - if (call_user_func_array(array($this, "test$test"), $args) == !!$not) { - if (($exception = $this->exceptionClass)) { - throw new $exception("$this->inspectedProperty $error"); - } - $this->validationErrors[$this->inspectedProperty][] = $error; - } - } - return $this; - } - -} diff --git a/src/Testable.php b/src/Testable.php deleted file mode 100644 index 869d468..0000000 --- a/src/Testable.php +++ /dev/null @@ -1,13 +0,0 @@ -good = $good; + } + + function export() { + return array_map(function($v) { + return $v[(int)$this->good]; + }, array( + "any" => [0,1], + "boolean" => ["nay", true], + "containing" => ["im 1", "im 2"], + "containing2" => [[1], [1,2]], + "email" => ["@nirvana", "mike@php.net"], + "float" => ["foo", 123.123], + "integer" => [123.1, 123], + "ip" => ["543.234.123.000", "123.234.98.0"], + "len" => ["foo","foobar"], + "matching" => ["foo","foo"], + "nothing" => [0, ""], + "numeric" => ["123foo123", "123.123"], + "printable" => ["\r\n", "easy test"], + "scalar" => [null, 1], + "url" => ["this-::is a h#rd one", "http://because/probably?everything=valid#here"], + )); + } +} + +class ValidatorTest extends \PHPUnit_Framework_TestCase { + + /** + * @var \ascertain\Test + */ + protected $good; + + /** + * @var \ascertain\Test + */ + protected $bad; + + protected function setUp() { + $this->good = new Test(true); + $this->bad = new Test(false); + } + + public function testTestNothing() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("nothing")->isNothing("error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("nothing")->isNotNothing("error"); + $this->assertEquals(1, $good->hasErrors()); + $this->assertSame(array("nothing"=>array("error")), $good->getErrors()); + $good->resetErrors(); + $this->assertSame(array(), $good->getErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("nothing")->isNotNothing("error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("nothing")->isNothing("error"); + $this->assertEquals(1, $bad->hasErrors()); + $this->assertSame(array("nothing"=>array("error")), $bad->getErrors()); + $bad->resetErrors(); + $this->assertSame(array(), $bad->getErrors()); + } + + public function testTestNumeric() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("numeric")->isNumeric("error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("numeric")->isNotNumeric("error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("numeric")->isNotNumeric("error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("numeric")->isNumeric("error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestScalar() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("scalar")->isScalar("error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("scalar")->isNotScalar("error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("scalar")->isNotScalar("error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("scalar")->isScalar("error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestPrintable() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("printable")->isPrintable("error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("printable")->isNotPrintable("error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("printable")->isNotPrintable("error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("printable")->isPrintable("error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestLen() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("len")->isLen(4, "error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("len")->isNotLen(4, "error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("len")->isNotLen(4, "error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("len")->isLen(4, "error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestInteger() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("integer")->isInteger("error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("integer")->isNotInteger("error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("integer")->isNotInteger("error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("integer")->isInteger("error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestBoolean() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("boolean")->isBoolean("error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("boolean")->isNotBoolean("error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("boolean")->isNotBoolean("error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("boolean")->isBoolean("error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestFloat() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("float")->isFloat("error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("float")->isNotFloat("error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("float")->isNotFloat("error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("float")->isFloat("error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestUrl() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("url")->isUrl("error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("url")->isNotUrl("error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("url")->isNotUrl("error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("url")->isUrl("error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestEmail() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("email")->isEmail("error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("email")->isNotEmail("error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("email")->isNotEmail("error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("email")->isEmail("error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestIp() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("ip")->isIp("error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("ip")->isNotIp("error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("ip")->isNotIp("error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("ip")->isIp("error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestContaining() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("containing")->isContaining(2, "error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("containing")->isNotContaining(2, "error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("containing")->isNotContaining(2, "error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("containing")->isContaining(2, "error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestContaining2() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("containing2")->isContaining(1, "error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("containing2")->isNotContaining(1, "error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("containing2")->isNotContaining(1, "error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("containing2")->isContaining(1, "error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestMatching() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("matching")->isMatching("/^\w+\$/", "error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("matching")->isNotMatching("/^\w+\$/", "error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("matching")->isNotMatching("/^\$/", "error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("matching")->isMatching("/^\$/", "error"); + $this->assertEquals(1, $bad->hasErrors()); + } + + public function testTestAny() { + $good = $this->good->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $good); + $good->that("any")->isAny(array(1,2), "error"); + $this->assertEquals(0, $good->hasErrors()); + $good->that("any")->isNotAny(array(1,2), "error"); + $this->assertEquals(1, $good->hasErrors()); + + $bad = $this->bad->assert(false); + $this->assertInstanceOf("\\ascertain\\Assert", $bad); + $bad->that("any")->isNotAny(array(1,2), "error"); + $this->assertEquals(0, $bad->hasErrors()); + $bad->that("any")->isAny(array(1,2), "error"); + $this->assertEquals(1, $bad->hasErrors()); + } +} diff --git a/tests/setup.inc b/tests/setup.inc new file mode 100644 index 0000000..8459513 --- /dev/null +++ b/tests/setup.inc @@ -0,0 +1,5 @@ +