├── .gitignore ├── .hgignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── build.local.xml ├── build.properties ├── build.xml ├── composer.json ├── package.xml ├── phpunit.xml └── src ├── .empty ├── README.txt ├── bin └── .empty ├── data └── .empty ├── docs └── .empty ├── php ├── .empty └── Phix_Project │ └── ContractLib2 │ ├── Contract.php │ ├── ContractInvariant.php │ ├── E5xx │ └── ContractFailedException.php │ └── OldValues.php ├── tests ├── .empty ├── functional-tests │ └── .empty ├── integration-tests │ └── .empty └── unit-tests │ ├── .empty │ ├── bin │ └── .empty │ ├── bootstrap.php │ ├── php │ ├── .empty │ └── Phix_Project │ │ └── ContractLib2 │ │ ├── ContractTest.php │ │ └── OldValuesTest.php │ └── www │ └── .empty └── www └── .empty /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | composer.phar 3 | dist 4 | .tmp 5 | nbproject 6 | review 7 | vendor 8 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | .build 4 | .dist 5 | nbproject 6 | review 7 | tmp 8 | vendor 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.3 4 | - 5.4 5 | before_script: 6 | - composer install --dev -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | New BSD License 2 | =============== 3 | 4 | Copyright (c) 2011, Stuart Herbert 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the names of the copyright holders nor the names of its 16 | contributors may be used to endorse or promote products derived from this 17 | software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ContractLib 2 | =========== 3 | 4 | **ContractLib** is a simple-to-use PHP component for easily enforcing programming contracts throughout your PHP components. These programming contracts can go a long way to helping you, and the users of your components, develop more robust code. 5 | 6 | ContractLib is loosely inspired by Microsoft Research's work on the 7 | [Code Contracts Library for .NET](http://research.microsoft.com/en-us/projects/contracts/userdoc.pdf) 8 | 9 | System-Wide Installation 10 | ------------------------ 11 | 12 | ContractLib should be installed using the [PEAR Installer](http://pear.php.net). This installer is the PHP community's de-facto standard for installing PHP components. 13 | 14 | sudo pear channel-discover pear.phix-project.org 15 | sudo pear install --alldeps phix/ContractLib 16 | 17 | As A Dependency On Your Component 18 | --------------------------------- 19 | 20 | If you are creating a component that relies on ContractLib, please make sure that you add LicenseLib to your component's package.xml file: 21 | 22 | ```xml 23 | 24 | 25 | 26 | ContractLib 27 | pear.phix-project.org 28 | 1.0.0 29 | 1.999.9999 30 | 31 | 32 | 33 | ``` 34 | 35 | Usage 36 | ----- 37 | 38 | The best documentation for ContractLib are the unit tests, which are shipped in the package. You will find them installed into your PEAR repository, which on Linux systems is normally /usr/share/php/test. 39 | 40 | You can find them online on GitHub: http://github.com/stuartherbert/ContractLib/ 41 | 42 | Development Environment 43 | ----------------------- 44 | 45 | If you want to patch or enhance this component, you will need to create a suitable development environment, by [installing phix](http://phix-project.org#install). 46 | 47 | You can then clone the git repository: 48 | 49 | # ContractLib 50 | git clone git@github.com:stuartherbert/ContractLib.git 51 | 52 | Then, install a local copy of this component's dependencies to complete the development environment: 53 | 54 | # build vendor/ folder 55 | phing build-vendor 56 | 57 | To make life easier for you, common tasks (such as running unit tests, generating code review analytics, and creating the PEAR package) have been automated using [phing](http://phing.info). You'll find the automated steps inside the build.xml file that ships with the component. 58 | 59 | Run the command 'phing' in the component's top-level folder to see the full list of available automated tasks. 60 | 61 | License 62 | ------- 63 | 64 | See LICENSE.txt for full license details. 65 | -------------------------------------------------------------------------------- /build.local.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /build.properties: -------------------------------------------------------------------------------- 1 | project.name=ContractLib2 2 | project.channel=pear.phix-project.org 3 | project.majorVersion=2 4 | project.minorVersion=1 5 | project.patchLevel=4 6 | project.snapshot=false 7 | 8 | checkstyle.standard=Zend 9 | 10 | component.type=php-library 11 | component.version=12 12 | 13 | pear.local=/var/www/${project.channel} 14 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | The code coverage report is in file://${project.review.codecoveragedir} 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | Populating vendor/ with dependencies 280 | 281 | 282 | 283 | Making sure vendor/ does not contain ${project.name} 284 | Removing ${project.name}'s code from vendor/ 285 | 286 | 287 | 288 | 289 | Removing ${project.name}'s docs from vendor/ 290 | 291 | 292 | 293 | 294 | 295 | 296 | Removing ${project.name}'s unit tests from vendor/ 297 | 298 | 299 | 300 | 301 | 302 | Your vendor/ folder has been built. 303 | You only need to run 'phing build-vendor' again if you change the 304 | dependencies listed in your package.xml file. 305 | 306 | 307 | 308 | 309 | 310 | 311 | Creating vendor/ as a sandboxed PEAR install folder 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | Building release directory 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | Creating ${project.tarfile} PEAR package 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | project.lastBuiltTarfile=${project.tarfile} 403 | 404 | 405 | Your PEAR package is in ${project.tarfile} 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | Cannot find PEAR package file ${project.lastBuiltTarfile} 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | Please run 'phing pear-package' first, then try again. 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | Cannot find PEAR package file ${project.lastBuiltTarfile} 460 | Run 'phing pear-package' to create a new PEAR package, then try again 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | Please run 'phing pear-package' first, then try again. 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | Cannot find PEAR package file ${project.lastBuiltTarfile} 491 | Run 'phing pear-package' to create a new PEAR package, then try again 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phix/contractlib", 3 | "description": "ContractLib is a lightweight PHP library designed to help you write more robust PHP components by helping your components enforce programming contracts throughout your code", 4 | "homepage": "https://github.com/stuartherbert/ContractLib/", 5 | "license": "BSD-3-Clause", 6 | "authors": [ 7 | { 8 | "name": "Stuart Herbert", 9 | "email": "stuart@stuartherbert.com", 10 | "homepage": "http://www.stuartherbert.com", 11 | "role": "Developer" 12 | } 13 | ], 14 | "support": { 15 | "issues": "https://github.com/stuartherbert/ContractLib/issues", 16 | "source": "https://github.com/stuartherbert/ContractLib/" 17 | }, 18 | "require": { 19 | "phix/exceptionslib": "1.*" 20 | }, 21 | "require-dev": { 22 | "phix/autoloader": "4.*" 23 | }, 24 | "autoload": { 25 | "psr-0": { "" : "src/php/" } 26 | } 27 | } -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ${project.name} 4 | ${project.channel} 5 | An easy-to-use PHP library for helping with Programming By Contract in PHP 6 | 7 | ContractLib is a lightweight PHP library designed to help you write more robust PHP components by helping your components enforce programming contracts throughout your code. 8 | 9 | 10 | Stuart Herbert 11 | stuartherbert 12 | stuart@stuartherbert.com 13 | yes 14 | 15 | ${build.date} 16 | 17 | 18 | ${project.version} 19 | ${project.majorVersion}.${project.minorVersion} 20 | 21 | 22 | ${project.stability} 23 | stable 24 | 25 | New BSD license 26 | 27 | No notes. 28 | 29 | 30 | 31 | ${contents} 32 | 33 | 34 | 35 | 36 | 37 | 5.3.0 38 | 39 | 40 | 1.9.4 41 | 42 | 43 | Autoloader4 44 | pear.phix-project.org 45 | 4.0.0 46 | 4.999.9999 47 | 48 | 49 | ExceptionsLib1 50 | pear.phix-project.org 51 | 1.0.0 52 | 1.999.9999 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 1.0.0 61 | 1.0 62 | 63 | 64 | stable 65 | stable 66 | 67 | Your release date 68 | New BSD license 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src/tests/unit-tests 6 | 7 | 8 | 9 | 10 | vendor 11 | src/tests 12 | 13 | 14 | src/bin 15 | src/php 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/.empty -------------------------------------------------------------------------------- /src/README.txt: -------------------------------------------------------------------------------- 1 | Your src/ folder 2 | ================ 3 | 4 | This src/ folder is where you put all of your code for release. There's 5 | a folder for each type of file that the PEAR Installer supports. You can 6 | find out more about these file types online at: 7 | 8 | http://blog.stuartherbert.com/php/2011/04/04/explaining-file-roles/ 9 | 10 | * bin/ 11 | 12 | If you're creating any command-line tools, this is where you'd put 13 | them. Files in here get installed into /usr/bin on Linux et al. 14 | 15 | There is more information available here: http://blog.stuartherbert.com/php/2011/04/06/php-components-shipping-a-command-line-program/ 16 | 17 | You can find an example here: https://github.com/stuartherbert/phix/tree/master/src/bin 18 | 19 | * data/ 20 | 21 | If you have any data files (any files that aren't PHP code, and which 22 | don't belong in the www/ folder), this is the folder to put them in. 23 | 24 | There is more information available here: http://blog.stuartherbert.com/php/2011/04/11/php-components-shipping-data-files-with-your-components/ 25 | 26 | You can find an example here: https://github.com/stuartherbert/ComponentManagerPhpLibrary/tree/master/src/data 27 | 28 | * php/ 29 | 30 | This is where your component's PHP code belongs. Everything that goes 31 | into this folder must be PSR0-compliant, so that it works with the 32 | supplied autoloader. 33 | 34 | There is more information available here: http://blog.stuartherbert.com/php/2011/04/05/php-components-shipping-reusable-php-code/ 35 | 36 | You can find an example here: https://github.com/stuartherbert/ContractLib/tree/master/src/php 37 | 38 | * tests/functional-tests/ 39 | 40 | Right now, this folder is just a placeholder for future functionality. 41 | You're welcome to make use of it yourself. 42 | 43 | * tests/integration-tests/ 44 | 45 | Right now, this folder is just a placeholder for future functionality. 46 | You're welcome to make use of it yourself. 47 | 48 | * tests/unit-tests/ 49 | 50 | This is where all of your PHPUnit tests go. 51 | 52 | It needs to contain _exactly_ the same folder structure as the src/php/ 53 | folder. For each of your PHP classes in src/php/, there should be a 54 | corresponding test file in test/unit-tests. 55 | 56 | There is more information available here: http://blog.stuartherbert.com/php/2011/08/15/php-components-shipping-unit-tests-with-your-component/ 57 | 58 | You can find an example here: https://github.com/stuartherbert/ContractLib/tree/master/test/unit-tests 59 | 60 | * www/ 61 | 62 | This folder is for any files that should be published in a web server's 63 | DocRoot folder. 64 | 65 | It's quite unusual for components to put anything in this folder, but 66 | it is there just in case. 67 | 68 | There is more information available here: http://blog.stuartherbert.com/php/2011/08/16/php-components-shipping-web-pages-with-your-components/ 69 | -------------------------------------------------------------------------------- /src/bin/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/bin/.empty -------------------------------------------------------------------------------- /src/data/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/data/.empty -------------------------------------------------------------------------------- /src/docs/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/docs/.empty -------------------------------------------------------------------------------- /src/php/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/php/.empty -------------------------------------------------------------------------------- /src/php/Phix_Project/ContractLib2/Contract.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 2011-present Stuart Herbert 40 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License 41 | * @link http://phix-project.org 42 | * @version @@PACKAGE_VERSION@@ 43 | */ 44 | 45 | namespace Phix_Project\ContractLib2; 46 | 47 | class Contract 48 | { 49 | /** 50 | * Are we currently enforcing any contracts passed to 51 | * self::Enforce()? 52 | * 53 | * By default, we do not! 54 | * 55 | * @var boolean 56 | */ 57 | static protected $enforcing = false; 58 | 59 | /** 60 | * A set of OldValues objects 61 | * 62 | * You can get the right object for your code's current scope by 63 | * calling Contract::OldValues() 64 | * 65 | * Objects in this array that are no longer required are nuked by 66 | * the Contract::Postconditions() wrapper 67 | * 68 | * @var array(OldValues) 69 | */ 70 | static protected $oldValues = array(); 71 | 72 | /** 73 | * Global library; cannot instantiate 74 | * 75 | * You can't instantiate this library because it needs to preserve 76 | * state even as the execution scope in your code changes :( 77 | * 78 | * @codeCoverageIgnore 79 | */ 80 | protected function __construct() 81 | { 82 | // do nothing 83 | } 84 | 85 | /** 86 | * Precondition: is the expression $expr true? 87 | * 88 | * Use this method at the start of your method to make sure you're 89 | * happy with the data that you have been passed, and with the 90 | * current state of your object 91 | * 92 | * Throws an E5xx_ContractPreconditionException if the parameter 93 | * passed in is false 94 | * 95 | * @throw E5xx_ContractPreconditionException 96 | * @param boolean $expr 97 | * @param string $reason error message to show on failure 98 | * @return boolean true on success 99 | */ 100 | static public function Requires($expr, $reason = null) 101 | { 102 | if (!$expr) 103 | { 104 | throw new E5xx_ContractFailedException('Requires', $reason); 105 | } 106 | 107 | return true; 108 | } 109 | 110 | /** 111 | * Precondition: is the expression $expr true? 112 | * 113 | * Use this method at the start of your method to make sure you're 114 | * happy with the data that you have been passed, and with the 115 | * current state of your object 116 | * 117 | * Throws an E5xx_ContractPreconditionException if $expr is false, 118 | * and adds $value to the exception's error message so that you 119 | * can see which value failed the test 120 | * 121 | * @param mixed $value 122 | * @param boolean $expr 123 | * @param string $reason error message to show on failure 124 | * @return boolean true on success 125 | */ 126 | static public function RequiresValue($value, $expr, $reason = null) 127 | { 128 | if (!$expr) 129 | { 130 | throw new E5xx_ContractFailedException('RequiresValue', $reason, true, $value); 131 | } 132 | 133 | return true; 134 | } 135 | 136 | /** 137 | * Postcondition: is the expression $expr true? 138 | * 139 | * Use this method at the end of your method to make sure you're 140 | * happy with the results before your method returns to the caller 141 | * 142 | * Throws an E5xx_ContractPostconditionException if $expr is false. 143 | * 144 | * @param boolean $expr 145 | * @param string $reason error message to show on failure 146 | * @return boolean true on success 147 | */ 148 | static public function Ensures($expr, $reason = null) 149 | { 150 | if (!$expr) 151 | { 152 | throw new E5xx_ContractFailedException('Ensures', $reason); 153 | } 154 | 155 | return true; 156 | } 157 | 158 | /** 159 | * Postcondition: is the expression $expr true? 160 | * 161 | * Use this method at the end of your method to make sure you're 162 | * happy with the results before your method returns to the caller 163 | * 164 | * Throws an E5xx_ContractPostConditionException if $expr is false, 165 | * and adds $value to the exception's error message so that you 166 | * can see which value failed the test 167 | * 168 | * @param mixed $value 169 | * @param boolean $expr 170 | * @param string $reason error message to show on failure 171 | * @return boolean true on success 172 | */ 173 | static public function EnsuresValue($value, $expr, $reason = null) 174 | { 175 | if (!$expr) 176 | { 177 | throw new E5xx_ContractFailedException('EnsuresValue', $reason, true, $value); 178 | } 179 | 180 | return true; 181 | } 182 | 183 | /** 184 | * Condition: is the expr $expr true? 185 | * 186 | * Use this method in the middle of your method, to check the 187 | * workings of your method before continuing. 188 | * 189 | * Throws an E5xx_ContractConditionException if $expr is false. 190 | * 191 | * @param boolean $expr 192 | * @param string $reason error message to show on failure 193 | * @return boolean true on success 194 | */ 195 | static public function Asserts($expr, $reason = null) 196 | { 197 | if (!$expr) 198 | { 199 | throw new E5xx_ContractFailedException('Asserts', $reason); 200 | } 201 | 202 | return true; 203 | } 204 | 205 | /** 206 | * Condition: is the expr $expr true? 207 | * 208 | * Use this method in the middle of your method, to check the 209 | * workings of your method before continuing. 210 | * 211 | * Throws an E5xx_ContractConditionException if $expr is false, 212 | * and adds $value to the exception's error message so that you 213 | * can see which value failed the test 214 | * 215 | * @param mixed $value 216 | * @param boolean $expr 217 | * @param string $reason error message to show on failure 218 | * @return boolean true on success 219 | */ 220 | static public function AssertsValue($value, $expr, $reason = null) 221 | { 222 | if (!$expr) 223 | { 224 | throw new E5xx_ContractFailedException('AssertsValue', $reason, true, $value); 225 | } 226 | 227 | return true; 228 | } 229 | 230 | /** 231 | * Apply the same condition (or set of conditions) to the values 232 | * in an array 233 | * 234 | * @param array $values 235 | * @param callback $callback 236 | * @param boolean true on success 237 | */ 238 | static public function ForAll($values, $callback) 239 | { 240 | array_walk($values, $callback); 241 | 242 | return true; 243 | } 244 | 245 | // ================================================================ 246 | // 247 | // Old values support 248 | // 249 | // ---------------------------------------------------------------- 250 | 251 | /** 252 | * Obtain a value, suitable for use as an array key, based on the 253 | * current execution scope in an app 254 | * 255 | * @return array(string, string) 256 | * The caller, plus the scope 257 | */ 258 | static protected function determineScope() 259 | { 260 | // the scope comes from interpreting the current 261 | // execution stack 262 | // 263 | // we are looking for the function or method that has 264 | // called either Contract::Preconditions or 265 | // Contract::Postconditions 266 | // 267 | // this algorithm is annoyingly expensive, but it should 268 | // ensure that there are no problems with actions in one 269 | // scope ever affecting the old values remembered in any 270 | // other scope 271 | 272 | $debug_backtrace = debug_backtrace(); 273 | 274 | $caller = null; 275 | $maxIndex = count($debug_backtrace); 276 | $i = 0; 277 | 278 | while ($caller == null && $i < $maxIndex) 279 | { 280 | // var_dump(substr($debug_backtrace[$i]['function'], -10, 10)); 281 | 282 | if (isset($debug_backtrace[$i]['class']) 283 | && $debug_backtrace[$i]['class'] == 'Phix_Project\ContractLib2\Contract' 284 | && substr($debug_backtrace[$i]['function'], -10, 10) == 'conditions' 285 | ) 286 | { 287 | $caller = $debug_backtrace[$i]['function']; 288 | } 289 | $i++; 290 | } 291 | 292 | // did we find what we are looking for? 293 | if ($caller == null) 294 | { 295 | // no - throw an exception 296 | throw new \RuntimeException('You can only use ContractLib2 Old Values support inside Preconditions or Postconditions'); 297 | } 298 | 299 | // at this point, $debug_backtrace[0] points at the caller 300 | // to the precondition or postcondition 301 | // 302 | // we want to remember the file and function, but not the 303 | // specific line number 304 | // 305 | // this makes sure that we return the same result when 306 | // called in both the preconditions and postconditions 307 | // in the same function, with the same callstack 308 | if (isset($debug_backtrace[$i]['line'])) 309 | { 310 | // this never seems to get called, but I assume 311 | // that one day the backtrace might get 'fixed' 312 | // @codeCoverageIgnoreStart 313 | unset($debug_backtrace[$i]['line']); 314 | // @codeCoverageIgnoreEnd 315 | } 316 | 317 | // now, let's build up this scope 318 | $scope = ''; 319 | for (;$i < $maxIndex; $i++) 320 | { 321 | foreach (array('file', 'line', 'function') as $key) 322 | { 323 | if (isset($debug_backtrace[$i][$key])) 324 | { 325 | $scope .= $debug_backtrace[$i][$key]; 326 | } 327 | } 328 | } 329 | 330 | // var_dump($scope); 331 | 332 | return array($caller, $scope); 333 | } 334 | 335 | /** 336 | * Obtain the old value of an argument 337 | * 338 | * @param string $argName 339 | * @return mixed 340 | */ 341 | static public function OldValue($argName) 342 | { 343 | // work out the current scope 344 | list($caller, $scope) = self::determineScope(); 345 | 346 | // do we have an existing OldValues object for this scope? 347 | if (!isset(self::$oldValues[$scope])) 348 | { 349 | return null; 350 | } 351 | 352 | // do we have a stashed value for this argument name? 353 | if (self::$oldValues[$scope]->hasStashed($argName)) 354 | { 355 | // yes we do 356 | return self::$oldValues[$scope]->unpack($argName); 357 | } 358 | 359 | // no we don't 360 | return null; 361 | } 362 | 363 | /** 364 | * Remember an old value for future comparisons 365 | * 366 | * @param string $argName 367 | * The name of the value to remember 368 | * @param mixed $argValue 369 | * The value to remember 370 | */ 371 | static public function RememberOldValue($argName, $argValue) 372 | { 373 | // work out the current scope 374 | list($caller, $scope) = self::determineScope(); 375 | 376 | // we must have been called from Preconditions 377 | if ($caller !== 'Preconditions') 378 | { 379 | throw new \RuntimeException('You can only remember old values inside Contract::Preconditions'); 380 | } 381 | 382 | // do we have an existing OldValues object for this scope? 383 | if (!isset(self::$oldValues[$scope])) 384 | { 385 | // no - create one! 386 | self::$oldValues[$scope] = new OldValues(); 387 | } 388 | 389 | // stash the value 390 | self::$oldValues[$scope]->stash($argName, $argValue); 391 | } 392 | 393 | /** 394 | * Free up memory by forgetting the old values we may have 395 | * remembered in the current scope 396 | */ 397 | static public function ForgetOldValues() 398 | { 399 | // work out the current scope 400 | list($caller, $scope) = self::determineScope(); 401 | 402 | // release an object, if we have one 403 | if (isset(self::$oldValues[$scope])) 404 | { 405 | unset(self::$oldValues[$scope]); 406 | } 407 | } 408 | 409 | /** 410 | * Get the internal list of scopes that are remembering old 411 | * values 412 | * 413 | * This method exists only to help with debugging 414 | * 415 | * @return array 416 | */ 417 | static public function _rememberedScopes() 418 | { 419 | return self::$oldValues; 420 | } 421 | 422 | // ================================================================ 423 | // 424 | // Unreachable code support 425 | // 426 | // ---------------------------------------------------------------- 427 | 428 | static public function Unreachable($file, $line) 429 | { 430 | throw new E5xx_ContractFailedException('Unreachable', "Unreachable code in file $file at line $line has somehow been reached. Go figure!"); 431 | } 432 | 433 | // ================================================================ 434 | // 435 | // Wrapped contract support 436 | // 437 | // ---------------------------------------------------------------- 438 | 439 | /** 440 | * Tell us to enforce contracts passed to self::Enforce() 441 | */ 442 | static public function EnforceWrappedContracts() 443 | { 444 | self::$enforcing = true; 445 | } 446 | 447 | /** 448 | * Tell us to enforce only calls made directly to the individual 449 | * contract conditions: Requires, Assert, Ensures et al 450 | */ 451 | static public function EnforceOnlyDirectContracts() 452 | { 453 | self::$enforcing = false; 454 | } 455 | 456 | /** 457 | * Check a set of preconditions *if* we are enforcing wrapped 458 | * contracts. 459 | * 460 | * This exists as a performance boost, allowing us to leave 461 | * contracts in the code even in production environments 462 | * 463 | * @param callback $callback 464 | * @param array $params 465 | * @return boolean true on success 466 | */ 467 | static public function Preconditions($callback, $params = array()) 468 | { 469 | if (self::$enforcing) 470 | { 471 | call_user_func_array($callback, $params); 472 | } 473 | 474 | return true; 475 | } 476 | 477 | /** 478 | * Check a set of postconditions *if* we are enforcing wrapped 479 | * contracts. 480 | * 481 | * This exists as a performance boost, allowing us to leave 482 | * contracts in the code even in production environments 483 | * 484 | * @param callback $callback 485 | * @param array $params 486 | * @return boolean true on success 487 | */ 488 | static public function Postconditions($callback, $params = array()) 489 | { 490 | if (self::$enforcing) 491 | { 492 | call_user_func_array($callback, $params); 493 | } 494 | 495 | // success! 496 | return true; 497 | } 498 | 499 | /** 500 | * Check a set of conditions mid-method *if* we are enforcing 501 | * wrapped contracts. 502 | * 503 | * This exists as a performance boost, allowing us to leave 504 | * contracts in the code even in production environments 505 | * 506 | * @param callback $callback 507 | * @param array $params 508 | * @return boolean true on success 509 | */ 510 | static public function Conditionals($callback, $params = array()) 511 | { 512 | if (self::$enforcing) 513 | { 514 | call_user_func_array($callback, $params); 515 | } 516 | 517 | return true; 518 | } 519 | } -------------------------------------------------------------------------------- /src/php/Phix_Project/ContractLib2/ContractInvariant.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 2011-present Stuart Herbert 40 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License 41 | * @link http://phix-project.org 42 | * @version @@PACKAGE_VERSION@@ 43 | */ 44 | 45 | namespace Phix_Project\ContractLib2; 46 | 47 | interface ContractInvariant 48 | { 49 | /** 50 | * A test method that can be called at any time during the life 51 | * of an object, which will run a series of assert()s to prove 52 | * that the object is in a sane state 53 | */ 54 | public function objectInvariant(); 55 | } -------------------------------------------------------------------------------- /src/php/Phix_Project/ContractLib2/E5xx/ContractFailedException.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 2011-present Stuart Herbert 40 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License 41 | * @link http://phix-project.org 42 | * @version @@PACKAGE_VERSION@@ 43 | */ 44 | 45 | namespace Phix_Project\ContractLib2; 46 | 47 | use Phix_Project\ExceptionsLib1\E5xx_InternalServerErrorException; 48 | 49 | /** 50 | * This exception is thrown when an assert() statement fails 51 | */ 52 | class E5xx_ContractFailedException extends E5xx_InternalServerErrorException 53 | { 54 | public function __construct($contract, $reason, $hasValue = false, $value = false) 55 | { 56 | $message = 'Contract::' . $contract . '() failed'; 57 | 58 | if ($hasValue) 59 | { 60 | $message .= "; value tested was '" . print_r($value, true) . "'"; 61 | } 62 | 63 | if ($reason !== null) 64 | { 65 | $message .= '; reason for failure was: ' . $reason; 66 | } 67 | 68 | parent::__construct($message); 69 | } 70 | } -------------------------------------------------------------------------------- /src/php/Phix_Project/ContractLib2/OldValues.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 2011-present Stuart Herbert 40 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License 41 | * @link http://phix-project.org 42 | * @version @@PACKAGE_VERSION@@ 43 | */ 44 | 45 | namespace Phix_Project\ContractLib2; 46 | 47 | /** 48 | * Helper class for remember a set of named values 49 | */ 50 | class OldValues 51 | { 52 | /** 53 | * Add a value to the list 54 | * 55 | * @param string $name 56 | * @param mixed $value 57 | */ 58 | public function stash($name, $value) 59 | { 60 | $this->values[$name] = serialize($value); 61 | } 62 | 63 | /** 64 | * Do we have a named value in the list? 65 | * 66 | * @param string $name 67 | * @return boolean 68 | */ 69 | public function hasStashed($name) 70 | { 71 | return isset($this->values[$name]); 72 | } 73 | 74 | /** 75 | * Retrieve a value from the list 76 | * 77 | * @param string $name 78 | * @return mixed 79 | */ 80 | public function unpack($name) 81 | { 82 | return unserialize($this->values[$name]); 83 | } 84 | } -------------------------------------------------------------------------------- /src/tests/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/tests/.empty -------------------------------------------------------------------------------- /src/tests/functional-tests/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/tests/functional-tests/.empty -------------------------------------------------------------------------------- /src/tests/integration-tests/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/tests/integration-tests/.empty -------------------------------------------------------------------------------- /src/tests/unit-tests/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/tests/unit-tests/.empty -------------------------------------------------------------------------------- /src/tests/unit-tests/bin/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/tests/unit-tests/bin/.empty -------------------------------------------------------------------------------- /src/tests/unit-tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 2011-present Stuart Herbert 40 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License 41 | * @link http://phix-project.org 42 | * @version @@PACKAGE_VERSION@@ 43 | */ 44 | 45 | 46 | namespace Phix_Project\ContractLib2; 47 | 48 | use Exception; 49 | use ReflectionClass; 50 | use PHPUnit_Framework_TestCase; 51 | 52 | class ContractTest extends PHPUnit_Framework_TestCase 53 | { 54 | public function testCannotInstantiate() 55 | { 56 | $refClass = new ReflectionClass('Phix_Project\ContractLib2\Contract'); 57 | $refMethod = $refClass->getMethod('__construct'); 58 | $this->assertFalse($refMethod->isPublic()); 59 | } 60 | 61 | public function testPreconditionsMustBeTrue() 62 | { 63 | // prove that the precondition checks do not throw an 64 | // exception when they are passed the value of TRUE 65 | $this->assertTrue(Contract::Requires(true)); 66 | $this->assertTrue(Contract::RequiresValue(0, true)); 67 | 68 | // prove that the precondition checks do throw an exception 69 | // when they are passed the value of FALSE 70 | $caughtException = false; 71 | try 72 | { 73 | Contract::Requires(false); 74 | } 75 | catch (E5xx_ContractFailedException $e) 76 | { 77 | $caughtException = true; 78 | } 79 | $this->assertTrue($caughtException); 80 | 81 | // repeat the check with another of the precondition 82 | // check methods 83 | $caughtException = false; 84 | try 85 | { 86 | Contract::RequiresValue(10, false); 87 | } 88 | catch (E5xx_ContractFailedException $e) 89 | { 90 | $caughtException = true; 91 | } 92 | $this->assertTrue($caughtException); 93 | } 94 | 95 | public function testPostconditionsMustBeTrue() 96 | { 97 | // prove that the postcondition checks do not throw an 98 | // exception when they are passed the value of TRUE 99 | $this->assertTrue(Contract::Ensures(true)); 100 | $this->assertTrue(Contract::EnsuresValue(0, true)); 101 | 102 | // prove that the postcondition checks do throw an 103 | // exception when they are passed the value of FALSE 104 | $caughtException = false; 105 | try 106 | { 107 | Contract::Ensures(false); 108 | } 109 | catch (E5xx_ContractFailedException $e) 110 | { 111 | $caughtException = true; 112 | } 113 | $this->assertTrue($caughtException); 114 | 115 | // repeat the test for another of the postcondition 116 | // check methods 117 | $caughtException = false; 118 | try 119 | { 120 | Contract::EnsuresValue(10, false); 121 | } 122 | catch (E5xx_ContractFailedException $e) 123 | { 124 | $caughtException = true; 125 | } 126 | $this->assertTrue($caughtException); 127 | } 128 | 129 | public function testMidConditionsMustBeTrue() 130 | { 131 | // prove that the condition checks do not throw an 132 | // exception when they are passed the value of TRUE 133 | $this->assertTrue(Contract::Asserts(true)); 134 | $this->assertTrue(Contract::AssertsValue(0, true)); 135 | 136 | // prove that the condition checks do throw an exception 137 | // when they are passed the value of FALSE 138 | $caughtException = false; 139 | try 140 | { 141 | Contract::Asserts(false); 142 | } 143 | catch (E5xx_ContractFailedException $e) 144 | { 145 | $caughtException = true; 146 | } 147 | $this->assertTrue($caughtException); 148 | 149 | // repeat the test with another of the condition check 150 | // methods 151 | $caughtException = false; 152 | try 153 | { 154 | Contract::AssertsValue(10, false); 155 | } 156 | catch (E5xx_ContractFailedException $e) 157 | { 158 | $caughtException = true; 159 | } 160 | $this->assertTrue($caughtException); 161 | } 162 | 163 | public function testCanApplyConditionsToArrays() 164 | { 165 | $testData1 = array (1,2,3,4,5); 166 | $testData2 = array (6,7,8,9,10); 167 | 168 | // these contracts are satisfied 169 | Contract::ForAll($testData1, function($value) { Contract::Requires($value < 6); }); 170 | $this->assertTrue(true); 171 | Contract::ForAll($testData2, function($value) { Contract::Requires($value > 5); }); 172 | $this->assertTrue(true); 173 | 174 | // these contracts are not satisfied 175 | $caughtException = false; 176 | try 177 | { 178 | Contract::ForAll($testData1, function($value) { Contract::Requires($value > 5); }); 179 | } 180 | catch (E5xx_ContractFailedException $e) 181 | { 182 | $caughtException = true; 183 | } 184 | $this->assertTrue($caughtException); 185 | 186 | // these contracts are not satisfied 187 | $caughtException = false; 188 | try 189 | { 190 | Contract::ForAll($testData2, function($value) { Contract::Requires($value < 6); }); 191 | } 192 | catch (E5xx_ContractFailedException $e) 193 | { 194 | $caughtException = true; 195 | } 196 | $this->assertTrue($caughtException); 197 | } 198 | 199 | public function testCanSeeTheValueThatFailedThePrecondition() 200 | { 201 | $caughtException = false; 202 | try 203 | { 204 | Contract::RequiresValue(5, false); 205 | } 206 | catch (E5xx_ContractFailedException $e) 207 | { 208 | $caughtException = $e->getMessage(); 209 | } 210 | 211 | // did we catch the exception? 212 | $this->assertTrue($caughtException !== false); 213 | 214 | // did we get the message we expect? 215 | $expected = "Internal server error: Contract::RequiresValue() failed; value tested was '5'"; 216 | $this->assertEquals($expected, $caughtException); 217 | } 218 | 219 | public function testCanSeeTheValueThatFailedThePostcondition() 220 | { 221 | $caughtException = false; 222 | try 223 | { 224 | Contract::EnsuresValue(5, false); 225 | } 226 | catch (E5xx_ContractFailedException $e) 227 | { 228 | $caughtException = $e->getMessage(); 229 | } 230 | 231 | // did we catch the exception? 232 | $this->assertTrue($caughtException !== false); 233 | 234 | // did we get the message we expect? 235 | $expected = "Internal server error: Contract::EnsuresValue() failed; value tested was '5'"; 236 | $this->assertEquals($expected, $caughtException); 237 | } 238 | 239 | public function testCanSeeTheValueThatFailedTheMidCondition() 240 | { 241 | $caughtException = false; 242 | try 243 | { 244 | Contract::AssertsValue(5, false); 245 | } 246 | catch (E5xx_ContractFailedException $e) 247 | { 248 | $caughtException = $e->getMessage(); 249 | } 250 | 251 | // did we catch the exception? 252 | $this->assertTrue($caughtException !== false); 253 | 254 | // did we get the message we expect? 255 | $expected = "Internal server error: Contract::AssertsValue() failed; value tested was '5'"; 256 | $this->assertEquals($expected, $caughtException); 257 | } 258 | 259 | public function testCanSeeTheReasonWhenThePreconditionFailed() 260 | { 261 | $caughtException = false; 262 | try 263 | { 264 | Contract::Requires(false, "my reason"); 265 | } 266 | catch (E5xx_ContractFailedException $e) 267 | { 268 | $caughtException = $e->getMessage(); 269 | } 270 | 271 | // did we catch the exception? 272 | $this->assertTrue($caughtException !== false); 273 | 274 | // did we get the message we expect? 275 | $expected = "Internal server error: Contract::Requires() failed; reason for failure was: my reason"; 276 | $this->assertEquals($expected, $caughtException); 277 | 278 | $caughtException = false; 279 | try 280 | { 281 | Contract::RequiresValue(5, false, "my reason"); 282 | } 283 | catch (E5xx_ContractFailedException $e) 284 | { 285 | $caughtException = $e->getMessage(); 286 | } 287 | 288 | // did we catch the exception? 289 | $this->assertTrue($caughtException !== false); 290 | 291 | // did we get the message we expect? 292 | $expected = "Internal server error: Contract::RequiresValue() failed; value tested was '5'; reason for failure was: my reason"; 293 | $this->assertEquals($expected, $caughtException); 294 | } 295 | 296 | public function testCanSeeTheReasonWhenThePostconditionFailed() 297 | { 298 | $caughtException = false; 299 | try 300 | { 301 | Contract::Ensures(false, 'my reason'); 302 | } 303 | catch (E5xx_ContractFailedException $e) 304 | { 305 | $caughtException = $e->getMessage(); 306 | } 307 | 308 | // did we catch the exception? 309 | $this->assertTrue($caughtException !== false); 310 | 311 | // did we get the message we expect? 312 | $expected = "Internal server error: Contract::Ensures() failed; reason for failure was: my reason"; 313 | $this->assertEquals($expected, $caughtException); 314 | 315 | $caughtException = false; 316 | try 317 | { 318 | Contract::EnsuresValue(5, false, 'my reason'); 319 | } 320 | catch (E5xx_ContractFailedException $e) 321 | { 322 | $caughtException = $e->getMessage(); 323 | } 324 | 325 | // did we catch the exception? 326 | $this->assertTrue($caughtException !== false); 327 | 328 | // did we get the message we expect? 329 | $expected = "Internal server error: Contract::EnsuresValue() failed; value tested was '5'; reason for failure was: my reason"; 330 | $this->assertEquals($expected, $caughtException); 331 | } 332 | 333 | public function testCanSeeTheReasonTheMidConditionFailed() 334 | { 335 | $caughtException = false; 336 | try 337 | { 338 | Contract::Asserts(false, 'my reason'); 339 | } 340 | catch (E5xx_ContractFailedException $e) 341 | { 342 | $caughtException = $e->getMessage(); 343 | } 344 | 345 | // did we catch the exception? 346 | $this->assertTrue($caughtException !== false); 347 | 348 | // did we get the message we expect? 349 | $expected = "Internal server error: Contract::Asserts() failed; reason for failure was: my reason"; 350 | $this->assertEquals($expected, $caughtException); 351 | 352 | $caughtException = false; 353 | try 354 | { 355 | Contract::AssertsValue(5, false, 'my reason'); 356 | } 357 | catch (E5xx_ContractFailedException $e) 358 | { 359 | $caughtException = $e->getMessage(); 360 | } 361 | 362 | // did we catch the exception? 363 | $this->assertTrue($caughtException !== false); 364 | 365 | // did we get the message we expect? 366 | $expected = "Internal server error: Contract::AssertsValue() failed; value tested was '5'; reason for failure was: my reason"; 367 | $this->assertEquals($expected, $caughtException); 368 | } 369 | 370 | public function testWrappedContractsCanBeDisabled() 371 | { 372 | // ensure wrapped contracts are switched off 373 | Contract::EnforceOnlyDirectContracts(); 374 | 375 | // some data to test 376 | $x = 1; 377 | $y = 2; 378 | $z = 3; 379 | 380 | // check wrapped preconditions 381 | $executed = false; 382 | Contract::Preconditions(function($x, $y, $z) use (&$executed) { 383 | Contract::Requires($x < $y); 384 | Contract::Requires($y < $z); 385 | $executed = true; 386 | }, array($x, $y, $z)); 387 | $this->assertFalse($executed); 388 | 389 | // check wrapped mid-conditions 390 | $executed = false; 391 | Contract::Conditionals(function() use (&$executed) { 392 | Contract::Asserts(2 > 1); 393 | Contract::Asserts(5 > 4); 394 | $executed = true; 395 | }); 396 | $this->assertFalse($executed); 397 | 398 | // check wrapped postconditions 399 | $executed = false; 400 | Contract::Postconditions(function($x, $y, $z) use (&$executed) { 401 | Contract::Ensures($x < $z); 402 | Contract::Ensures($z > $x); 403 | $executed = true; 404 | }, array($x, $y, $z)); 405 | $this->assertFalse($executed); 406 | } 407 | 408 | public function testCanWrapContractsForPeformance() 409 | { 410 | // enable wrapped contracts 411 | Contract::EnforceWrappedContracts(); 412 | 413 | // some data to test 414 | $x = 1; 415 | $y = 2; 416 | $z = 3; 417 | 418 | // check wrapped preconditions 419 | $executed = false; 420 | Contract::Preconditions(function($x, $y, $z) use (&$executed) { 421 | Contract::Requires($x < $y); 422 | Contract::Requires($y < $z); 423 | $executed = true; 424 | }, array($x, $y, $z)); 425 | $this->assertTrue($executed); 426 | 427 | // check wrapped mid-conditions 428 | $executed = false; 429 | Contract::Conditionals(function() use (&$executed) { 430 | Contract::Asserts(2 > 1); 431 | Contract::Asserts(5 > 4); 432 | $executed = true; 433 | }); 434 | $this->assertTrue($executed); 435 | 436 | // check wrapped postconditions 437 | $executed = false; 438 | Contract::Postconditions(function($x, $y, $z) use (&$executed) { 439 | Contract::Ensures($x < $z); 440 | Contract::Ensures($z > $x); 441 | $executed = true; 442 | }, array($x, $y, $z)); 443 | $this->assertTrue($executed); 444 | } 445 | 446 | public function testCanDisabledWrappedContracts() 447 | { 448 | // enable wrapped contracts 449 | Contract::EnforceWrappedContracts(); 450 | 451 | // execute a wrapped contract 452 | $executed = false; 453 | Contract::Preconditions(function() use (&$executed) 454 | { 455 | $executed = true; 456 | }); 457 | $this->assertTrue($executed); 458 | 459 | // now, disable wrapped contracts 460 | Contract::EnforceOnlyDirectContracts(); 461 | 462 | // repeat the test 463 | $executed = false; 464 | Contract::Preconditions(function() use (&$executed) 465 | { 466 | $executed = true; 467 | }); 468 | $this->assertFalse($executed); 469 | } 470 | 471 | public function testCanTrackUnreachableCode() 472 | { 473 | $caughtException = false; 474 | $expectedMessage = 475 | $actualMessage = "Internal server error: Contract::Unreachable() failed; reason for failure was: " 476 | . 'Unreachable code in file ' . __FILE__ 477 | . ' at line ' . (__LINE__ + 5) 478 | . ' has somehow been reached. Go figure!'; 479 | 480 | try 481 | { 482 | Contract::Unreachable(__FILE__, __LINE__); 483 | } 484 | catch (E5xx_ContractFailedException $e) 485 | { 486 | $caughtException = true; 487 | $actualMessage = $e->getMessage(); 488 | } 489 | 490 | // what happened? 491 | $this->assertTrue($caughtException); 492 | $this->assertEquals($expectedMessage, $actualMessage); 493 | } 494 | } -------------------------------------------------------------------------------- /src/tests/unit-tests/php/Phix_Project/ContractLib2/OldValuesTest.php: -------------------------------------------------------------------------------- 1 | 39 | * @copyright 2011-present Stuart Herbert 40 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License 41 | * @link http://phix-project.org 42 | * @version @@PACKAGE_VERSION@@ 43 | */ 44 | 45 | 46 | namespace Phix_Project\ContractLib2; 47 | 48 | use Exception; 49 | use ReflectionClass; 50 | use PHPUnit_Framework_TestCase; 51 | 52 | use Phix_Project\ContractLib2\Contract; 53 | 54 | class OldValuesTest extends PHPUnit_Framework_TestCase 55 | { 56 | public function testCanInstantiate() 57 | { 58 | // you *can* create an OldValues object directly yourself, 59 | // but tbh it isn't much use 60 | // 61 | // the right way to use this object is by inference, 62 | // through the OldValue methods on Contract:: 63 | 64 | $obj = new OldValues(); 65 | $this->assertTrue($obj instanceof OldValues); 66 | } 67 | 68 | public function testOldValuesCanBeRememberedInPreconditions() 69 | { 70 | // make sure wrapped contracts are enabled 71 | Contract::EnforceWrappedContracts(); 72 | 73 | // some test data 74 | $arg1 = __LINE__; 75 | 76 | // stash the value 77 | Contract::Preconditions(function() use ($arg1) 78 | { 79 | Contract::RememberOldValue('arg1', $arg1); 80 | }); 81 | 82 | // if we get here, the test has passed 83 | $this->assertTrue(true); 84 | 85 | // let's forget that value now :) 86 | Contract::Postconditions(function() 87 | { 88 | Contract::ForgetOldValues(); 89 | }); 90 | } 91 | 92 | public function testCannotBeRememberedInPostconditions() 93 | { 94 | // tell PHPUnit that this test causes an exception 95 | $this->setExpectedException('RuntimeException'); 96 | 97 | // make sure wrapped contracts are enabled 98 | Contract::EnforceWrappedContracts(); 99 | 100 | // some test data 101 | $arg1 = __LINE__; 102 | 103 | // stash the value 104 | Contract::Postconditions(function() use ($arg1) 105 | { 106 | Contract::RememberOldValue('arg1', $arg1); 107 | }); 108 | 109 | // this should never be reached, but just in case ... 110 | $this->assertTrue(false); 111 | } 112 | 113 | public function testCannotBeRememberedOutsideWrappedContracts() 114 | { 115 | // tell PHPUnit that this test causes an exception 116 | $this->setExpectedException('RuntimeException'); 117 | 118 | // some test data 119 | $arg1 = __LINE__; 120 | 121 | // stash the value 122 | Contract::RememberOldValue('arg1', $arg1); 123 | 124 | // this should never be reached, but just in case ... 125 | $this->assertTrue(false); 126 | } 127 | 128 | public function testCanStashValues() 129 | { 130 | // make sure wrapped contracts are enabled 131 | Contract::EnforceWrappedContracts(); 132 | 133 | // some test data 134 | $origArg1 = __LINE__; 135 | $origArg2 = __LINE__; 136 | 137 | // take a copy ... because we need to change these variables 138 | // in a bit to prove that we're remembering the original 139 | // value 140 | $arg1 = $origArg1; 141 | $arg2 = $origArg2; 142 | 143 | // stash some values 144 | Contract::Preconditions(function() use ($arg1, $arg2) 145 | { 146 | Contract::RememberOldValue('arg1', $arg1); 147 | Contract::RememberOldValue('arg2', $arg2); 148 | }); 149 | 150 | // change variables in this scope 151 | $arg1 = __LINE__; 152 | $arg2 = __LINE__; 153 | 154 | // now, did we get the values? 155 | // 156 | // for this to work propery, we have to test it inside 157 | // the postconditions wrapper 158 | Contract::PostConditions(function($obj) use($origArg1, $origArg2, $arg1, $arg2) 159 | { 160 | // the remembered values should be the originals 161 | $obj->assertEquals($origArg1, Contract::OldValue('arg1')); 162 | $obj->assertEquals($origArg2, Contract::OldValue('arg2')); 163 | 164 | // the remembered values should not be the same 165 | // values that our changed variables now have 166 | $obj->assertNotEquals($arg1, Contract::OldValue('arg1')); 167 | $obj->assertNotEquals($arg2, Contract::OldValue('arg2')); 168 | 169 | // release the memory 170 | Contract::ForgetOldValues(); 171 | }, array($this)); 172 | } 173 | 174 | public function testReturnsNullWhenNoOldValuesHaveBeenRemembered() 175 | { 176 | // make sure wrapped contracts are enabled 177 | Contract::EnforceWrappedContracts(); 178 | 179 | // do the test 180 | // we never remembered any values in this scope!! 181 | Contract::PostConditions(function($obj) 182 | { 183 | $obj->assertNull(Contract::OldValue('arg1')); 184 | }, array($this)); 185 | } 186 | 187 | public function testReturnsNullForOldValuesThatHaveNotBeenRemembered() 188 | { 189 | // make sure wrapped contracts are enabled 190 | Contract::EnforceWrappedContracts(); 191 | 192 | $arg1 = __LINE__; 193 | 194 | Contract::Preconditions(function() use($arg1) 195 | { 196 | Contract::RememberOldValue('arg1', $arg1); 197 | }); 198 | 199 | // do the test 200 | // we never remembered a value for arg2 201 | Contract::PostConditions(function($obj) use($arg1) 202 | { 203 | $obj->assertEquals($arg1, Contract::OldValue('arg1')); 204 | $obj->assertNull(Contract::OldValue('arg2')); 205 | 206 | // remember to forget the remembered values! 207 | Contract::ForgetOldValues(); 208 | }, array($this)); 209 | } 210 | 211 | public function testCanForgetValuesToSameMemory() 212 | { 213 | // make sure wrapped contracts are enabled 214 | Contract::EnforceWrappedContracts(); 215 | 216 | // make sure there are no remembered scopes atm 217 | $this->assertEquals(0, count(Contract::_rememberedScopes())); 218 | 219 | // some test data 220 | $arg1 = 'fred'; 221 | $arg2 = 'alice'; 222 | 223 | // stash some values 224 | Contract::Preconditions(function() use ($arg1, $arg2) 225 | { 226 | Contract::RememberOldValue('arg1', $arg1); 227 | Contract::RememberOldValue('arg2', $arg2); 228 | }); 229 | 230 | // make sure we have remembered the scope 231 | $this->assertEquals(1, count(Contract::_rememberedScopes())); 232 | 233 | // now, did we get the values? 234 | // 235 | // for this to work propery, we have to test it inside 236 | // the postconditions wrapper 237 | Contract::PostConditions(function($obj) use($arg1, $arg2) 238 | { 239 | $obj->assertEquals($arg1, Contract::OldValue('arg1')); 240 | $obj->assertEquals($arg2, Contract::OldValue('arg2')); 241 | 242 | // forget these values, freeing up the memory 243 | Contract::ForgetOldValues(); 244 | }, array($this)); 245 | 246 | // make sure we have forgotten the scope 247 | $this->assertEquals(0, count(Contract::_rememberedScopes())); 248 | } 249 | 250 | public function testScopeIsUniqueToCaller() 251 | { 252 | // how many scopes have previous tests left in memory? 253 | $currentScopeCount = count(Contract::_rememberedScopes()); 254 | 255 | // remember some values, in their own unique scope 256 | // doing so increases the number of scopes that Contract:: 257 | // now remembers 258 | $this->rememberSomeValues(); 259 | $this->assertEquals($currentScopeCount + 1, count(Contract::_rememberedScopes())); 260 | 261 | // try to recall those values 262 | // this does not affect the number of scopes that Contact:: 263 | // remembers 264 | $this->recallSomeValues(); 265 | $this->assertEquals($currentScopeCount + 1, count(Contract::_rememberedScopes())); 266 | 267 | // let's remember those values now 268 | $this->rememberSomeValues(true); 269 | $this->assertEquals($currentScopeCount, count(Contract::_rememberedScopes())); 270 | } 271 | 272 | protected function rememberSomeValues($check = false) 273 | { 274 | if (!$check) 275 | { 276 | Contract::Preconditions(function() 277 | { 278 | // the scope for these values is unique 279 | Contract::RememberOldValue('arg1', __LINE__); 280 | Contract::RememberOldValue('arg2', __LINE__); 281 | }); 282 | } 283 | 284 | if ($check) 285 | { 286 | Contract::Postconditions(function($obj) 287 | { 288 | $obj->assertNotNull(Contract::OldValue('arg1')); 289 | $obj->assertNotNull(Contract::OldValue('arg2')); 290 | 291 | // clean up after ourselves 292 | Contract::ForgetOldValues(); 293 | }, array($this)); 294 | } 295 | } 296 | 297 | protected function recallSomeValues() 298 | { 299 | Contract::PostConditions(function($obj) 300 | { 301 | // these values will be null, because they were 302 | // never ever set in this scope 303 | $obj->assertNull(Contract::OldValue('arg1')); 304 | $obj->assertNull(Contract::OldValue('arg2')); 305 | }, array($this)); 306 | } 307 | } -------------------------------------------------------------------------------- /src/tests/unit-tests/www/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/tests/unit-tests/www/.empty -------------------------------------------------------------------------------- /src/www/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartherbert/ContractLib/52fcad32b3f42bf862b08f47b502858274b98c88/src/www/.empty --------------------------------------------------------------------------------