├── .hgignore ├── .hgtags ├── .npmignore ├── .travis.yml ├── LICENCE ├── Makefile ├── README.md ├── docs └── manual │ ├── Makefile │ ├── make.bat │ └── source │ ├── caffeine.zip │ ├── conf.py │ ├── dev │ └── index.rst │ ├── index.rst │ ├── quickstart │ └── index.rst │ └── user │ └── index.rst ├── package.json ├── src ├── check.ls ├── data.ls ├── generating.ls ├── index.ls ├── property.ls └── random.ls ├── test ├── specs │ ├── data.ls │ ├── generating.ls │ ├── index.ls │ └── property.ls └── tap.ls └── tools └── bump-version.js /.hgignore: -------------------------------------------------------------------------------- 1 | syntax:glob 2 | # Usual Emacs backup stuff 3 | *~ 4 | \#*\# 5 | .\#* 6 | npm-debug.log 7 | .calliope 8 | 9 | # Built things that shouldn't be part of the source tree 10 | syntax: regexp 11 | ^node_modules/ 12 | ^lib/ 13 | ^build/ 14 | ^dist/ -------------------------------------------------------------------------------- /.hgtags: -------------------------------------------------------------------------------- 1 | a1d2764029ce52039d5a40f689a2b764ab9c5e49 0.4.0 2 | c7050be7d35e79d55c5a90ca2dc5431f6d066e77 0.4.2 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013-2014 Quildreen "Sorella" Motta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | bin = $(shell npm bin) 2 | lsc = $(bin)/lsc 3 | browserify = $(bin)/browserify 4 | groc = $(bin)/groc 5 | uglify = $(bin)/uglifyjs 6 | faucet = $(bin)/faucet 7 | VERSION = $(shell node -e 'console.log(require("./package.json").version)') 8 | 9 | 10 | lib: src/*.ls 11 | $(lsc) -o lib -c src/*.ls 12 | 13 | dist: 14 | mkdir -p dist 15 | 16 | dist/claire.umd.js: compile dist 17 | $(browserify) lib/index.js --standalone claire > $@ 18 | 19 | dist/claire.umd.min.js: dist/claire.umd.js 20 | $(uglify) --mangle - < $^ > $@ 21 | 22 | # ---------------------------------------------------------------------- 23 | bundle: dist/claire.umd.js 24 | 25 | minify: dist/claire.umd.min.js 26 | 27 | compile: lib 28 | 29 | documentation: 30 | cd docs/manual && make html 31 | 32 | clean: 33 | rm -rf dist build lib 34 | 35 | test: 36 | $(lsc) test/tap.ls | $(faucet) 37 | 38 | package: compile documentation bundle minify 39 | mkdir -p dist/claire-$(VERSION) 40 | cp -r docs/literate dist/claire-$(VERSION)/docs 41 | cp -r lib dist/claire-$(VERSION) 42 | cp dist/*.js dist/claire-$(VERSION) 43 | cp package.json dist/claire-$(VERSION) 44 | cp README.md dist/claire-$(VERSION) 45 | cp LICENCE dist/claire-$(VERSION) 46 | cd dist && tar -czf claire-$(VERSION).tar.gz claire-$(VERSION) 47 | 48 | publish: clean 49 | npm install 50 | npm publish 51 | 52 | bump: 53 | node tools/bump-version.js $$VERSION_BUMP 54 | 55 | bump-feature: 56 | VERSION_BUMP=FEATURE $(MAKE) bump 57 | 58 | bump-major: 59 | VERSION_BUMP=MAJOR $(MAKE) bump 60 | 61 | 62 | .PHONY: test 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Claire 2 | ====== 3 | 4 | [![Build Status](https://secure.travis-ci.org/robotlolita/claire.png?branch=master)](https://travis-ci.org/robotlolita/claire) 5 | [![NPM version](https://badge.fury.io/js/claire.png)](http://badge.fury.io/js/claire) 6 | [![Dependencies Status](https://david-dm.org/robotlolita/claire.png)](https://david-dm.org/robotlolita/claire) 7 | [![experimental](http://hughsk.github.io/stability-badges/dist/experimental.svg)](http://github.com/hughsk/stability-badges) 8 | 9 | 10 | Claire is a random testing library for both property-based testing 11 | ([QuickCheck][]-like) and random program generation ([ScalaCheck][] command's 12 | like), which allows you to express your code's behaviours and invariants 13 | in a clear way. 14 | 15 | [QuickCheck]: https://github.com/nick8325/quickcheck 16 | [ScalaCheck]: https://github.com/rickynils/scalacheck 17 | 18 | ## Example 19 | 20 | ```js 21 | var claire = require('claire') 22 | var _ = claire.data 23 | var forAll = claire.forAll 24 | 25 | var commutative_p = forAll( _.Int, _.Int ).satisfy( function(a, b) { 26 | return a + b == b + a 27 | }).asTest() 28 | // + OK passed 100 tests. 29 | 30 | 31 | var identity_p = forAll(_.Int).satisfy(function(a) { 32 | return a == a + 1 33 | }) 34 | 35 | identity_p.asTest({ verbose: true, times: 100 })() 36 | // : ! Falsified after 1 tests, 1 failed. 37 | // 38 | // : Failure #1 -------------------- 39 | // 40 | // 41 | // : The following arguments were provided: 42 | // 0 - 93 () 43 | // 44 | // (Stack trace) 45 | ``` 46 | 47 | 48 | ## Installing 49 | 50 | The easiest way is to grab it from NPM. If you're running in a Browser 51 | environment, you can use [Browserify][] 52 | 53 | $ npm install claire 54 | 55 | 56 | ### Using with CommonJS 57 | 58 | If you're not using NPM, [Download the latest release][release], and require 59 | the `claire.umd.js` file: 60 | 61 | ```js 62 | var claire = require('claire') 63 | ``` 64 | 65 | 66 | ### Using with AMD 67 | 68 | [Download the latest release][release], and require the `claire.umd.js` 69 | file: 70 | 71 | ```js 72 | require(['claire'], function(claire) { 73 | ( ... ) 74 | }) 75 | ``` 76 | 77 | 78 | ### Using without modules 79 | 80 | [Download the latest release][release], and load the `claire.umd.js` 81 | file. The properties are exposed in the global `claire` object: 82 | 83 | ```html 84 | 85 | ``` 86 | 87 | 88 | ### Compiling from source 89 | 90 | If you want to compile this library from the source, you'll need [Git][], 91 | [Make][], [Node.js][], and run the following commands: 92 | 93 | $ git clone git://github.com/robotlolita/claire.git 94 | $ cd claire 95 | $ npm install 96 | $ make bundle 97 | 98 | This will generate the `dist/claire.umd.js` file, which you can load in 99 | any JavaScript environment. 100 | 101 | 102 | ## Documentation 103 | 104 | You can [read the documentation online][docs] or build it yourself: 105 | 106 | $ git clone git://github.com/robotlolita/claire.git 107 | $ cd claire 108 | $ npm install 109 | $ make documentation 110 | 111 | Then open the file `docs/manual/build/html/index.html` in your browser. 112 | 113 | 114 | ## Platform support 115 | 116 | This library assumes an ES5 environment, but can be easily supported in ES3 117 | platforms by the use of shims. Just include [es5-shim][] :) 118 | 119 | 120 | ## Licence 121 | 122 | Copyright (c) 2013-2014 Quildreen Motta. 123 | 124 | Released under the [MIT licence](https://github.com/robotlolita/claire/blob/master/LICENCE). 125 | 126 | 127 | [Browserify]: http://browserify.org/ 128 | [Git]: http://git-scm.com/ 129 | [Make]: http://www.gnu.org/software/make/ 130 | [Node.js]: http://nodejs.org/ 131 | [es5-shim]: https://github.com/kriskowal/es5-shim 132 | [docs]: http://claire.readthedocs.org/ 133 | 134 | [release]: https://github.com/robotlolita/claire/releases/download/v1.0.0/claire-1.0.0.tar.gz 135 | 136 | -------------------------------------------------------------------------------- /docs/manual/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Claire.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Claire.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Claire" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Claire" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/manual/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Claire.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Claire.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/manual/source/caffeine.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorellabs/claire/df007a398a89100579d06be4246226c075126715/docs/manual/source/caffeine.zip -------------------------------------------------------------------------------- /docs/manual/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Claire documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Mar 10 17:26:24 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'Claire' 44 | copyright = u'2013, Quildreen "Sorella" Motta' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.3' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.3.1' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = [] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'autumn' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'caffeine' 95 | html_theme_path = ['.'] 96 | 97 | # Theme options are theme-specific and customize the look and feel of a theme 98 | # further. For a list of options available for each theme, see the 99 | # documentation. 100 | #html_theme_options = {} 101 | 102 | # Add any paths that contain custom themes here, relative to this directory. 103 | #html_theme_path = [] 104 | 105 | # The name for this set of Sphinx documents. If None, it defaults to 106 | # " v documentation". 107 | #html_title = None 108 | 109 | # A shorter title for the navigation bar. Default is the same as html_title. 110 | #html_short_title = None 111 | 112 | # The name of an image file (relative to this directory) to place at the top 113 | # of the sidebar. 114 | #html_logo = None 115 | 116 | # The name of an image file (within the static path) to use as favicon of the 117 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 118 | # pixels large. 119 | #html_favicon = None 120 | 121 | # Add any paths that contain custom static files (such as style sheets) here, 122 | # relative to this directory. They are copied after the builtin static files, 123 | # so a file named "default.css" will overwrite the builtin "default.css". 124 | html_static_path = ['_static'] 125 | 126 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 127 | # using the given strftime format. 128 | #html_last_updated_fmt = '%b %d, %Y' 129 | 130 | # If true, SmartyPants will be used to convert quotes and dashes to 131 | # typographically correct entities. 132 | #html_use_smartypants = True 133 | 134 | # Custom sidebar templates, maps document names to template names. 135 | #html_sidebars = {} 136 | html_sidebars = { "**": ['searchbox.html', 'localtoc.html'] 137 | , 'search': [] 138 | , 'genindex': [] 139 | } 140 | 141 | 142 | # Additional templates that should be rendered to pages, maps page names to 143 | # template names. 144 | #html_additional_pages = {} 145 | 146 | # If false, no module index is generated. 147 | #html_domain_indices = True 148 | 149 | # If false, no index is generated. 150 | #html_use_index = True 151 | 152 | # If true, the index is split into individual pages for each letter. 153 | #html_split_index = False 154 | 155 | # If true, links to the reST sources are added to the pages. 156 | #html_show_sourcelink = True 157 | 158 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 159 | #html_show_sphinx = True 160 | 161 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 162 | #html_show_copyright = True 163 | 164 | # If true, an OpenSearch description file will be output, and all pages will 165 | # contain a tag referring to it. The value of this option must be the 166 | # base URL from which the finished HTML is served. 167 | #html_use_opensearch = '' 168 | 169 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 170 | #html_file_suffix = None 171 | 172 | # Output file base name for HTML help builder. 173 | htmlhelp_basename = 'Clairedoc' 174 | 175 | 176 | # -- Options for LaTeX output -------------------------------------------------- 177 | 178 | latex_elements = { 179 | # The paper size ('letterpaper' or 'a4paper'). 180 | #'papersize': 'letterpaper', 181 | 182 | # The font size ('10pt', '11pt' or '12pt'). 183 | #'pointsize': '10pt', 184 | 185 | # Additional stuff for the LaTeX preamble. 186 | #'preamble': '', 187 | } 188 | 189 | # Grouping the document tree into LaTeX files. List of tuples 190 | # (source start file, target name, title, author, documentclass [howto/manual]). 191 | latex_documents = [ 192 | ('index', 'Claire.tex', u'Claire Documentation', 193 | u'Quildreen "Sorella" Motta', 'manual'), 194 | ] 195 | 196 | # The name of an image file (relative to this directory) to place at the top of 197 | # the title page. 198 | #latex_logo = None 199 | 200 | # For "manual" documents, if this is true, then toplevel headings are parts, 201 | # not chapters. 202 | #latex_use_parts = False 203 | 204 | # If true, show page references after internal links. 205 | #latex_show_pagerefs = False 206 | 207 | # If true, show URL addresses after external links. 208 | #latex_show_urls = False 209 | 210 | # Documents to append as an appendix to all manuals. 211 | #latex_appendices = [] 212 | 213 | # If false, no module index is generated. 214 | #latex_domain_indices = True 215 | 216 | 217 | # -- Options for manual page output -------------------------------------------- 218 | 219 | # One entry per manual page. List of tuples 220 | # (source start file, name, description, authors, manual section). 221 | man_pages = [ 222 | ('index', 'claire', u'Claire Documentation', 223 | [u'Quildreen "Sorella" Motta'], 1) 224 | ] 225 | 226 | # If true, show URL addresses after external links. 227 | #man_show_urls = False 228 | 229 | 230 | # -- Options for Texinfo output ------------------------------------------------ 231 | 232 | # Grouping the document tree into Texinfo files. List of tuples 233 | # (source start file, target name, title, author, 234 | # dir menu entry, description, category) 235 | texinfo_documents = [ 236 | ('index', 'Claire', u'Claire Documentation', 237 | u'Quildreen "Sorella" Motta', 'Claire', 'One line description of project.', 238 | 'Miscellaneous'), 239 | ] 240 | 241 | # Documents to append as an appendix to all manuals. 242 | #texinfo_appendices = [] 243 | 244 | # If false, no module index is generated. 245 | #texinfo_domain_indices = True 246 | 247 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 248 | #texinfo_show_urls = 'footnote' 249 | 250 | highlight_language = 'js' 251 | -------------------------------------------------------------------------------- /docs/manual/source/dev/index.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorellabs/claire/df007a398a89100579d06be4246226c075126715/docs/manual/source/dev/index.rst -------------------------------------------------------------------------------- /docs/manual/source/index.rst: -------------------------------------------------------------------------------- 1 | Claire —at a glance— 2 | ==================== 3 | 4 | Claire is a library for **property-based testing** for JavaScript 5 | applications. Just like Haskell's `QuickCheck`_ or Scala's `ScalaCheck`_, 6 | Claire works by generating random values within a given data space and 7 | verifying if the properties declared for those values hold. 8 | 9 | 10 | 11 | .. rst-class:: overview-list 12 | 13 | Guides 14 | ------ 15 | 16 | .. hlist:: 17 | :columns: 2 18 | 19 | * :doc:`Getting Started ` 20 | A lightning introduction to Claire, so you can jump straight to 21 | testing. 22 | 23 | * :doc:`Discover Claire ` 24 | A thorough tour on Claire's concepts for leeching all you can to test 25 | your code-bases better. 26 | 27 | * :doc:`Extending Claire ` 28 | Explains Claire's architecture, so you can extend Claire to support new 29 | types, combinators and test runners. 30 | 31 | * `API Reference`_ 32 | A quick reference on Claire's API, including plenty of usage examples 33 | and cross-references. 34 | 35 | 36 | .. index:: platform support 37 | 38 | Platform Support 39 | ---------------- 40 | 41 | Claire runs on all ECMAScript 5-compliant platforms without problems. It's been 42 | successfully tested in the following platforms: 43 | 44 | .. raw:: html 45 | 46 |
    47 |
  • 7.0+
  • 48 |
  • 5.1
  • 49 |
  • 15.0+
  • 50 |
  • 10.0+
  • 51 |
  • 21.0+
  • 52 |
  • 0.6+
  • 53 |
54 | 55 | For legacy, ES3 platforms, like IE's JScript, you'll have to provide support 56 | for the following methods: 57 | 58 | * Object.keys 59 | * Object.create 60 | * Object.getPrototypeOf 61 | * Object.freeze *(as an identity function)* 62 | * Array.prototype.indexOf 63 | * Array.prototype.forEach 64 | * Array.prototype.filter 65 | * Array.prototype.map 66 | * Array.prototype.reduce 67 | * Array.prototype.some 68 | * Array.prototype.every 69 | * String.prototype.trim 70 | 71 | The nice `es5-shim`_ library should take care of handling all of those for 72 | you. 73 | 74 | 75 | .. index:: support, tracker, issues 76 | 77 | Support 78 | ------- 79 | 80 | Claire uses the `Github tracker`_ for tracking bugs and new features. 81 | 82 | 83 | .. index:: licence, license 84 | 85 | Licence 86 | ------- 87 | 88 | MIT/X11. 89 | 90 | 91 | .. _Github tracker: https://github.com/killdream/claire/issues 92 | .. _es5-shim: https://github.com/kriskowal/es5-shim 93 | .. _QuickCheck: https://github.com/nick8325/quickcheck 94 | .. _ScalaCheck: https://github.com/rickynils/scalacheck 95 | .. _API Reference: _static/api/index.html 96 | -------------------------------------------------------------------------------- /docs/manual/source/quickstart/index.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Getting Started 3 | ================= 4 | 5 | This document will guide you through the basics of Claire. This is a quick 6 | introduction, but once you finish this page you'll know all you need to start 7 | writing property tests for your code-base, and even integrate them with your 8 | favourite test runner! 9 | 10 | 11 | What is Claire? 12 | =============== 13 | 14 | Claire is a tool for automatically testing your code. It uses a concept known 15 | as **property-based testing**, where you write high-level specifications of how 16 | each *unit* should behave, and the tool generates the test data for you, 17 | randomly. 18 | 19 | For example:: 20 | 21 | // Addition should be commutative 22 | var commutative_p = forAll( _.Int, _.Int ).satisfy(function(a, b) { 23 | return a + b == b + a 24 | }) 25 | 26 | 27 | Why would you use it? 28 | ===================== 29 | 30 | Since Claire generates random data, the data-space your test cases cover gets 31 | larger and larger the more you run your tests. This means that every time you 32 | run the test cases, you have more chances of catching bugs that weren't 33 | previously caught, even if you don't change anything in your code. This can 34 | highly increase the confidence you have in a code base. 35 | 36 | Claire does not prove that a code base has no bugs, instead it is a tool to 37 | increase the test space (and thus the number of bugs you can catch), and help 38 | you analyse the data that makes a particular test fail. 39 | 40 | Besides that, Claire allows you to have high-level specifications for how your 41 | program should behave (like `BDD`_), and potentially reduce the amount of lines 42 | of code spent with test cases, while increasing the overall test coverage. 43 | 44 | 45 | Writing properties 46 | ================== 47 | 48 | As said before, Claire uses **Properties** to describe the behaviour of an unit 49 | of code (a module, a function, etc.). These properties are described by 50 | JavaScript functions, and define what the expected behaviour is for a given set 51 | of data. 52 | 53 | Let's go back to the previous example of commutative property for 54 | addition. This property says that no matter which order addition is performed, 55 | the result should always be the same, and it is encoded in Claire like this:: 56 | 57 | var claire = require('claire') 58 | var _ = claire.data 59 | var forAll = claire.forAll 60 | 61 | var commutative_p = forAll( _.Int, _.Int ).satisfy( function(a, b) { 62 | return a + b == b + a 63 | }) 64 | 65 | 66 | In plain English, that means: 67 | 68 | For all pairs of integers (as ``a`` and ``b``), the sum of these integers 69 | yields the same result, regardless of the order in which it's performed. 70 | 71 | .. warning:: 72 | Properties are only considered successful if they return ``true``. Any other 73 | value will result on the property being rejected. 74 | 75 | 76 | Now, ``forAll`` is a function that takes some *data generators* and returns a 77 | ``Property`` object. This object knows how to randomly generate the test data 78 | and check if the property holds for that data. You can check if the property 79 | holds for one case by using the ``asTest`` method. 80 | 81 | .. note:: 82 | 83 | There are lots of built-in test generators for Claire. The usual data types 84 | are there: ``Int``, ``Num``, ``Bool``, ``Array(x)`` and ``Object(x)``. You 85 | can always look at the ``data.ls`` source to see all the built-in ones. 86 | 87 | 88 | The ``asTest`` returns a function that can be used within any test runner that 89 | depends on exceptions: which would likely be all of them. It logs the result if 90 | the test succeeds (in verbose mode), and throws an error if it fails. So, let's 91 | use it:: 92 | 93 | commutative_p.asTest({ verbose: true, times: 100 })() 94 | // + OK passed 100 tests. 95 | 96 | 97 | So, it tells us that Claire ran 100 tests for the property and all of them 98 | passed. But what if it had failed? Well, let's see another property:: 99 | 100 | var identity_p = forAll(_.Int).satisfy(function(a) { 101 | return a == a + 1 102 | }) 103 | 104 | identity_p.asTest({ verbose: true, times: 100 })() 105 | // : ! Falsified after 1 tests, 1 failed. 106 | // 107 | // : Failure #1 -------------------- 108 | // 109 | // 110 | // : The following arguments were provided: 111 | // 0 - 93 () 112 | // 113 | // (Stack trace) 114 | 115 | 116 | Now, it does tell us pretty loudly that the property didn't hold. And that it 117 | failed in the very first test too! It also provides information about which 118 | arguments were given to the property, and a stack trace, which we can ignore 119 | for now. 120 | 121 | So, Claire says that the ``identity_p`` property failed for the integer 122 | ``93``. If we analyse the property again, we'll see that it specifies that the 123 | the sum of any integer with 1 equals that same integer. This is clearly not the 124 | case for addition, as ``94`` does not equal ``93``. If we fix that and run the 125 | property again, we'll get a success:: 126 | 127 | var identity_p = forAll(_.Int).satisfy(function(a) { 128 | return a == a + 0 129 | }) 130 | 131 | identity_p.asTest({ verbose: true, times: 100 })() 132 | // + OK passed 100 tests. 133 | 134 | 135 | Conditional tests 136 | ================= 137 | 138 | Some properties only hold for a given data subset. For example:: 139 | 140 | var sqrt_p = forAll(_.Int).satisfy(function(a) { 141 | return Math.sqrt(a * a) == a 142 | }) 143 | 144 | sqrt_p.asTest()() 145 | // : ! Falsified after 2 tests, 1 failed. 146 | // 147 | // : Failure #1 -------------------- 148 | // 149 | // 150 | // : The following arguments were provided: 151 | // 0 - -11 () 152 | 153 | 154 | So, quite unsurprisingly, negative numbers don't work with this property, but 155 | the ``Int`` generator gives you both positive and negative numbers. So, instead 156 | of writing a new generator, Claire allows you to define a **conditional 157 | property**:: 158 | 159 | var sqrt_p = forAll(_.Int) 160 | .given( function(a){ return a > 0 }) 161 | .satisfy(function(a){ return Math.sqrt(a * a) == a }) 162 | 163 | sqrt_p.asTest({ verbose: true })() 164 | // + OK passed 100 tests. 124 (55%) tests ignored. 165 | 166 | 167 | The ``given`` method allows you to specify the subset of data that a property 168 | applies to. You get the same arguments as the ``satisfy`` method, and return a 169 | ``Boolean`` indicating whether to test the property for the generated data or 170 | not. 171 | 172 | .. note:: 173 | 174 | Claire does provide the ``Positive`` and ``Negative`` generators, which 175 | produce only positive and negative numbers, respectively. 176 | 177 | 178 | Now it tells you that the property succeeded for 100 test cases, but a large 179 | number of test cases (124, or 55%) were ignored. You can decide whether this is 180 | an indication to doubt a property or not, in which case you can try running 181 | more tests:: 182 | 183 | sqrt_p.asTest({ verbose: true, times: 1000 })() 184 | // : ? Aborted after 1956 tests. 1001 (51%) tests ignored. 185 | 186 | 187 | If too many tests are ignored, Claire might decide to stop testing so you can 188 | review the generators and conditions in a property. 189 | 190 | 191 | Analysing test results 192 | ====================== 193 | 194 | Of course, not all your properties will be as simple as the addition 195 | properties, so you need better tools to analyse the test results and decide if 196 | they are trustworthy or not, and assess why they're failing. 197 | 198 | For this, Claire allows you to *classify* the generated test cases, so you can 199 | analyse which test data has been tested by the property. This is done by the 200 | ``classify`` method:: 201 | 202 | function sorted(xs) { 203 | return xs.slice() 204 | .sort(function(a, b){ return a - b }) 205 | } 206 | 207 | var sorted_p = forAll( _.Array(_.Int) ) 208 | .satisfy(function(xs) { 209 | xs = sorted(xs) 210 | return xs.every(function(a, i) { 211 | return i == 0 212 | || a >= xs[i - 1] 213 | }) 214 | }) 215 | .classify(function(xs) { 216 | return xs.length == 0? 'empty' 217 | : xs.length == 1? 'trivial' 218 | : '> 1' 219 | }) 220 | 221 | sorted_p.asTest({ verbose: true })() 222 | // + OK passed 100 tests. 223 | // > Collected test data: 224 | // o 98% - > 1 225 | // o 1% - trivial 226 | // o 1% - empty 227 | 228 | While sorting lists with one or no elements are trivial (it's already sorted!), 229 | you can see that the majority of the data given (98%) passes the test. This is 230 | a good indication that the property is likely to be trustworthy, and you can 231 | keep running test cases to increase the confidence in the property:: 232 | 233 | sorted_p.asTest({ verbose: true, times: 10000 })() 234 | // + OK passed 10000 tests. 235 | // > Collected test data: 236 | // o 98% - > 1 237 | // o 1% - trivial 238 | // o 1% - empty 239 | 240 | 241 | Integrating with a test runner 242 | ============================== 243 | 244 | Assuming your test runner takes a function and expects that function to throw 245 | an exception if the test goes wrong, you can just use the ``asTest`` method of 246 | the ``Property`` object to integrate with the test runner. For example, this 247 | would work on Mocha:: 248 | 249 | 250 | describe('Addition', function() { 251 | it('Should be commutative', forAll(_.Int, _.Int).satisfy(function(a, b) { 252 | return a + b == b + a 253 | }).asTest()) 254 | }) 255 | 256 | 257 | Where to go from here? 258 | ====================== 259 | 260 | Now that you get the idea behind Claire, you can start writing your properties 261 | to test the behaviours in your code bases. Be sure to check the :doc:`Discover 262 | Claire <../user/index>` documentation to learn everything you can get from the 263 | library. 264 | 265 | 266 | 267 | .. _BDD: http://en.wikipedia.org/wiki/Behavior-driven_development 268 | -------------------------------------------------------------------------------- /docs/manual/source/user/index.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorellabs/claire/df007a398a89100579d06be4246226c075126715/docs/manual/source/user/index.rst -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "claire", 3 | "version": "0.4.2", 4 | "description": "Property-based testing library (à lá QuickCheck/ScalaCheck).", 5 | "main": "./lib/index.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "dependencies": { 10 | "boo": "~2.0.0", 11 | "prelude-ls": "~1.0.3", 12 | "flaw": "~0.1.0", 13 | "data.stream": "0.0.1" 14 | }, 15 | "devDependencies": { 16 | "LiveScript": "~1.2.0", 17 | "es5-shim": "~2.3.0", 18 | "browserify": "~3.19.0", 19 | "hifive-tap": "~0.1.1", 20 | "hifive": "~0.1.0", 21 | "faucet": "0.0.0" 22 | }, 23 | "scripts": { 24 | "test": "make test", 25 | "prepublish": "make compile" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/robotlolita/claire.git" 30 | }, 31 | "keywords": [ 32 | "testing", 33 | "test", 34 | "property-based testing", 35 | "random testing", 36 | "quickcheck", 37 | "smallcheck" 38 | ], 39 | "author": "Quildreen \"Sorella\" Motta ", 40 | "license": "MIT", 41 | "readmeFilename": "README.md" 42 | } 43 | -------------------------------------------------------------------------------- /src/check.ls: -------------------------------------------------------------------------------- 1 | ## Module check ######################################################## 2 | # 3 | # Tests if properties hold for N random value generations. 4 | # 5 | # 6 | # Copyright (c) 2013-2014 Quildreen "Sorella" Motta 7 | # 8 | # Permission is hereby granted, free of charge, to any person 9 | # obtaining a copy of this software and associated documentation files 10 | # (the "Software"), to deal in the Software without restriction, 11 | # including without limitation the rights to use, copy, modify, merge, 12 | # publish, distribute, sublicense, and/or sell copies of the Software, 13 | # and to permit persons to whom the Software is furnished to do so, 14 | # subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | ### -- Dependencies ---------------------------------------------------- 28 | make-error = require 'flaw' 29 | { Base } = require 'boo' 30 | { values, reduce, sort-by, map } = require 'prelude-ls' 31 | 32 | 33 | ### -- Aliases --------------------------------------------------------- 34 | frozen = Object.freeze 35 | keys = Object.keys 36 | {round} = Math 37 | 38 | 39 | ### -- Default configuration ------------------------------------------- 40 | default-config = do 41 | times : 100 42 | verbose : false 43 | 44 | 45 | ### -- Error handling -------------------------------------------------- 46 | 47 | #### λ EFailure 48 | # :internal: 49 | # Constructs a Failure error for a property. 50 | # 51 | # :: Report -> Error 52 | EFailure = make-error '' 53 | 54 | #### λ EAbandoned 55 | # :internal: 56 | # Constructs an Abandoned error for a property. 57 | # 58 | # :: Report -> Error 59 | EAbandoned = make-error '' 60 | 61 | 62 | ### -- General helpers ------------------------------------------------- 63 | 64 | #### λ percentage 65 | # :internal: 66 | # Computes the percentage of some N wrt some Total. 67 | # 68 | # :: Number, Number -> Number 69 | percentage = (num, total) -> round (num / total) * 100 70 | 71 | #### λ with-defaults 72 | # :internal: 73 | # Yields a new configuration that provides the default configuration as 74 | # fallback. 75 | # 76 | # :: { String -> a } -> { String -> a } 77 | with-defaults = (config = {}) -> ({} <<< default-config) <<< config 78 | 79 | 80 | ### -- Helpers for handling Results ------------------------------------ 81 | 82 | #### λ status 83 | # :internal: 84 | # Retrieves a normalised Status tag for the Result. 85 | # 86 | # :: Result -> ResultStatus 87 | status = (result) -> result.kind or \rejected 88 | 89 | 90 | #### λ failed-p 91 | # :internal: 92 | # Checks if a Result failed. 93 | # 94 | # :: Result -> Bool 95 | failed-p = (result) -> (status result) in <[ failed rejected ]> 96 | 97 | 98 | ### -- Helpers for presenting a Report --------------------------------- 99 | 100 | #### λ describe-verdict 101 | # :internal: 102 | # Provides a human-readable verdict of a test report. 103 | # 104 | # :: Report -> String 105 | describe-verdict = (report) -> 106 | passed = report.passed.length 107 | failed = report.failed.length 108 | all = report.all.length 109 | ignored = report.ignored.length 110 | 111 | switch report.verdict 112 | | \passed => "+ OK passed #{passed} tests." 113 | | \failed => "! Falsified after #{all - ignored} tests, #{failed} failed." 114 | | \abandoned => "? Aborted after #{all} tests." 115 | | otherwise => "/ Unknown verdict. Likely this test report lacks any data." 116 | 117 | 118 | #### λ describe-ignored 119 | # :internal: 120 | # Provides a human-readable description of the ignored tests, if they're 121 | # above a certain non-trivial threshold (50%). 122 | # 123 | # :: Report -> String 124 | describe-ignored = (report) -> 125 | ignored = report.ignored.length 126 | ignored-pct = percentage ignored, report.all.length 127 | 128 | if ignored-pct > 50 => "#ignored (#{ignored-pct}%) tests ignored." 129 | else => '' 130 | 131 | 132 | #### λ label-histogram 133 | # :internal: 134 | # Provides a human-readable histogram of the various classifications 135 | # provided for the test data. 136 | # 137 | # :: Report -> String 138 | label-histogram = (report) -> 139 | total = report.all.length 140 | labels = [[(percentage v.length, total), k] for k, v of report.labels] 141 | |> sort-by (x, y) -> x < y 142 | |> map ([p, key]) -> "o #{p}% - #{key}" 143 | 144 | if labels.length => "> Collected test data:\n #{labels.join '\n '}" 145 | else => '' 146 | 147 | 148 | #### λ describe-failures 149 | # :internal: 150 | # Provides a human-readable description of the failures that happened. 151 | # 152 | # :: Report -> String 153 | describe-failures = (report) -> 154 | label = (as) -> 155 | | as.length => "» The following labels were provided: #{JSON.stringify as}" 156 | | otherwise => '' 157 | 158 | error-for = (kind, e) -> 159 | | kind is \failed => "» Threw #{e?.stack or e}\n" 160 | | otherwise => '' 161 | 162 | arg = (a, n) -> " #n - #{JSON.stringify a.value} (#{a.generator})" 163 | 164 | rejection-for = (kind, e) -> 165 | if kind != \rejected => '' 166 | else => "» Reason: #{JSON.stringify e.value}\n" 167 | 168 | errors = report.failed.map (a, n) -> """ 169 | : Failure \##{n + 1} ----------------------------------------------------------- 170 | #{rejection-for a.kind, a} 171 | #{label a.labels} 172 | #{error-for a.kind, a.value} 173 | » The following arguments were provided: 174 | #{a.arguments.map arg .join '\n '} 175 | """ 176 | switch 177 | | errors.join '' .trim! => errors.join '\n---\n' 178 | | otherwise => '' 179 | 180 | 181 | #### λ describe-report 182 | # :internal: 183 | # Describes the report in the log if it's important to do so. 184 | # 185 | # :: Bool -> Report -> IO () 186 | describe-report = (verbose, report) --> 187 | text = (("#report".split /\n/).map (s) -> " #s").join '\n' 188 | total = report.all.length 189 | ignored = report.ignored.length 190 | ignored-pct = percentage ignored, total 191 | has-labels = !!(keys report.labels).length 192 | 193 | if verbose or (ignored-pct > 50) or has-labels => console?.log text 194 | 195 | 196 | 197 | 198 | ### -- Helper data structures ------------------------------------------ 199 | 200 | #### {} Report 201 | # 202 | # Gathers meta-data from a property test and provides ways of displaying 203 | # those data in an human-readable way. 204 | # 205 | # :: Base <| Report 206 | Report = Base.derive { 207 | 208 | ##### λ init 209 | # Initialises a Report instance. 210 | # 211 | # :: @this:Report* => Property -> this 212 | init: (@property) -> 213 | @passed = [] 214 | @failed = [] 215 | @ignored = [] 216 | @all = [] 217 | @labels = {} 218 | @verdict = null 219 | 220 | 221 | ##### λ add 222 | # Adds a single test result to the Report. 223 | # 224 | # :: @this:Report* => Result -> () 225 | add: (result) -> 226 | @all.push result 227 | result.labels.map (a) ~> @labels.[]"#a".push result 228 | switch status result 229 | | \held => @passed.push result 230 | | \failed => @failed.push result 231 | | \rejected => @failed.push result 232 | | \ignored => @ignored.push result 233 | 234 | ##### λ to-string 235 | # Provides a human-readable presentation of this Report. 236 | # 237 | # :: @this:Report* => () -> String 238 | to-string: -> 239 | """ 240 | #{describe-verdict this} #{describe-ignored this} 241 | #{label-histogram this} 242 | #{describe-failures this} 243 | """ 244 | } 245 | 246 | 247 | ### -- Checking properties --------------------------------------------- 248 | 249 | #### λ check 250 | # Runs a property repeatedly, until it holds more times than a given 251 | # threshold. 252 | # 253 | # If the Property fails to hold for certain random inputs, or if the 254 | # Property ignores too many of the inputs, the test fails immediately 255 | # and a Report describing the failure is returned. 256 | # 257 | # :: Number -> Property -> Report 258 | check = (max, property) --> 259 | report = Report.make property 260 | ignored = 0 261 | should-run = true 262 | while max and should-run 263 | result = property.run! 264 | report.add result 265 | 266 | switch status result 267 | | \held => --max 268 | | \rejected => should-run = false 269 | | \failed => should-run = false 270 | | \ignored => if ++ignored > 1000 => should-run = false 271 | 272 | report.verdict = | ignored > 1000 => \abandoned 273 | | max > 0 => \failed 274 | | otherwise => \passed 275 | frozen report 276 | 277 | 278 | #### λ test 279 | # Tests a property in a way that conforms with the standard API for test 280 | # runners. 281 | # 282 | # :: Config -> Property -> () -> IO () 283 | test = (config, property) --> do 284 | c = with-defaults config 285 | report = check c.times, property 286 | switch report.verdict 287 | | \passed => describe-report c.verbose, report 288 | | \failed => throw EFailure report 289 | | \abandoned => throw EAbandoned report 290 | 291 | 292 | 293 | 294 | ### -- Exports --------------------------------------------------------- 295 | module.exports = { check, test, Report } 296 | -------------------------------------------------------------------------------- /src/data.ls: -------------------------------------------------------------------------------- 1 | ## Module data ######################################################### 2 | # 3 | # Core data generators. 4 | # 5 | # 6 | # Copyright (c) 2013-2014 Quildreen "Sorella" Motta 7 | # 8 | # Permission is hereby granted, free of charge, to any person 9 | # obtaining a copy of this software and associated documentation files 10 | # (the "Software"), to deal in the Software without restriction, 11 | # including without limitation the rights to use, copy, modify, merge, 12 | # publish, distribute, sublicense, and/or sell copies of the Software, 13 | # and to permit persons to whom the Software is furnished to do so, 14 | # subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | 28 | ### -- Dependencies ---------------------------------------------------- 29 | { choose-int, choose } = require './random' 30 | 31 | { as-generator \ 32 | , choice, frequency, sequence \ 33 | , recursive, size, label, transform, repeat } = require './generating' 34 | 35 | 36 | ### -- Helpers --------------------------------------------------------- 37 | 38 | #### λ join 39 | # :internal: 40 | # Joins a list of things without a separator 41 | # 42 | # :: [a] -> String 43 | join = (.join '') 44 | 45 | 46 | #### λ char 47 | # :internal: 48 | # Converts some character code to a String character. 49 | # 50 | # :: Number -> String 51 | char = String.from-char-code 52 | 53 | 54 | #### λ to-integer 55 | # :internal: 56 | # Converts a Number to an Integer. 57 | # 58 | # :: Number -> Int32 59 | to-integer = (n) -> n .|. 0 60 | 61 | 62 | #### λ to-unsigned-integer 63 | # :internal: 64 | # Converts a Number to an unsigned Integer. 65 | # 66 | # :: Number -> UInt32 67 | to-unsigned-integer = (n) -> n .>>>. 0 68 | 69 | 70 | #### λ to-object 71 | # :internal: 72 | # Folds a list of pairs into an object. 73 | # 74 | # :: [String, a] -> { String -> a } 75 | to-object = (as) -> as.reduce ((r, [k,v]) -> r <<< { "#k": v }), {} 76 | 77 | 78 | ### -- Primitive data types -------------------------------------------- 79 | Null = as-generator null 80 | Undefined = as-generator void 81 | Bool = choice true, false 82 | Num = label 'num' (as-generator (s) -> choose -s, s) 83 | Byte = label 'byte' (as-generator (_) -> to-integer (choose 0, 255)) 84 | Char = label 'char' (transform char, Byte) 85 | Str = label 'str' (transform join, (repeat Char)) 86 | 87 | 88 | ### -- Specialised numeric types --------------------------------------- 89 | Int = label 'int' (transform to-integer, Num) 90 | UInt = label 'uint' (transform to-unsigned-integer, Num) 91 | Positive = label 'positive' (as-generator (s) -> choose 1, s) 92 | Negative = label 'negative' (as-generator (s) -> choose -1, -s) 93 | 94 | 95 | ### -- Specialised textual types --------------------------------------- 96 | NumChar = label 'num-char' (transform char, -> choose-int 48, 57) 97 | UpperChar = label 'upper-char' (transform char, -> choose-int 65, 90) 98 | LowerChar = label 'lower-char' (transform char, -> choose-int 97, 122) 99 | AlphaChar = frequency [1, UpperChar], [9, LowerChar] 100 | AlphaNumChar = frequency [1, NumChar], [9, AlphaChar] 101 | AlphaStr = transform join, (repeat AlphaChar) 102 | NumStr = transform join, (repeat NumChar) 103 | AlphaNumStr = transform join, (repeat AlphaNumChar) 104 | 105 | Id = do 106 | start = frequency [1, '_'], [2, '$'], [9, AlphaChar] 107 | chars = frequency [1, NumChar], [9, start] 108 | rest = transform join, (repeat chars) 109 | 110 | label 'id' (transform join, (sequence start, rest)) 111 | 112 | 113 | ### -- Container data types -------------------------------------------- 114 | List = (...as) -> repeat (choice ...as) 115 | Map = (...as) -> transform to-object, (repeat (sequence Id, (choice ...as))) 116 | 117 | 118 | ### -- Umbrella type unions -------------------------------------------- 119 | Nothing = choice Null, Undefined 120 | Falsy = choice Nothing, false, 0, '' 121 | Any = choice Nothing, Bool, Num, Str, (recursive -> (List Any)), (recursive -> (Map Any)) 122 | 123 | # TODO: Date, RegExp, Truthy 124 | 125 | 126 | ### -- Exports --------------------------------------------------------- 127 | module.exports = { 128 | # Primitives 129 | Null, Undefined, Bool, Num, Byte, Char, Str 130 | 131 | # Numbers 132 | Int, UInt, Positive, Negative 133 | 134 | # Strings 135 | NumChar, UpperChar, LowerChar, AlphaChar, AlphaNumChar, AlphaStr, 136 | NumStr, AlphaNumStr, Id 137 | 138 | # Containers 139 | Array: List, Object: Map 140 | 141 | # Umbrella 142 | Nothing, Falsy, Any 143 | } 144 | -------------------------------------------------------------------------------- /src/generating.ls: -------------------------------------------------------------------------------- 1 | ## Module generating ################################################### 2 | # 3 | # The basis for generating *everything*. 4 | # 5 | # 6 | # Copyright (c) 2013-2014 Quildreen "Sorella" Motta 7 | # 8 | # Permission is hereby granted, free of charge, to any person 9 | # obtaining a copy of this software and associated documentation files 10 | # (the "Software"), to deal in the Software without restriction, 11 | # including without limitation the rights to use, copy, modify, merge, 12 | # publish, distribute, sublicense, and/or sell copies of the Software, 13 | # and to permit persons to whom the Software is furnished to do so, 14 | # subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | 28 | ### -- Dependencies ---------------------------------------------------- 29 | { Base, derive } = require 'boo' 30 | { concat-map, replicate } = require 'prelude-ls' 31 | { pick-one, choose-int } = require './random' 32 | 33 | 34 | ### -- Aliases --------------------------------------------------------- 35 | floor = Math.floor 36 | 37 | 38 | ### -- Interfaces ------------------------------------------------------ 39 | 40 | #### type Value a 41 | # :: { "generator" -> Generator a 42 | # .. ; "value" -> a } 43 | 44 | 45 | 46 | ### -- Helpers --------------------------------------------------------- 47 | 48 | #### λ callable-p 49 | # :internal: 50 | # Checks if something is callable. 51 | # 52 | # :: a -> Bool 53 | callable-p = (a) -> typeof a is 'function' 54 | 55 | 56 | #### λ generator-p 57 | # :internal: 58 | # Checks if something is a `Generator` 59 | # 60 | # :: a -> Bool 61 | generator-p = (a) -> 'next' of (Object a) 62 | 63 | 64 | #### λ compute 65 | # :internal: 66 | # Computes a value lifted to a `Generator`. 67 | # 68 | # :: Number -> a -> Generator a -> a 69 | # :: Number -> (a -> b) -> Generator b -> b 70 | compute = (size, a, gen) -> 71 | | callable-p a => a (size ? gen.size) 72 | | otherwise => a 73 | 74 | 75 | ### -- Working with Values --------------------------------------------- 76 | 77 | #### λ make-value 78 | # Constructs a `Value` type. 79 | # 80 | # :: a -> Generator a -> Value a 81 | make-value = (value, gen) --> 82 | generator : gen 83 | value : value 84 | 85 | #### λ value 86 | # Executes a `Generator` and extracts the generated value. 87 | # 88 | # :: Number -> Generator a -> a 89 | value = (n, ctx, gen) --> ((as-generator gen).next.call ctx, n).value 90 | 91 | 92 | ### -- Core implementation --------------------------------------------- 93 | 94 | #### {} Generator 95 | # The base generator logic. 96 | # 97 | # :: Base <| Generator a 98 | Generator = Base.derive { 99 | 100 | ##### Data size 101 | # A hint for controlling the generated value's complexity. 102 | # 103 | # :: Number 104 | size: 100 105 | 106 | ##### λ next 107 | # Generates a new random value. 108 | # 109 | # :: Number -> Value a 110 | next: (n) -> ... 111 | 112 | ##### λ shrink 113 | # Continually shrinks a value into the most minimal case within the 114 | # context of this generator. 115 | # 116 | # :: a -> [a] 117 | shrink: (a) -> ... 118 | 119 | ##### λ then 120 | # Monadic bind: shoves a value from a monad into a monad-returning function. 121 | # 122 | # :: (a -> Generator b) -> Generator b 123 | then: (f) -> bind this, f 124 | 125 | ##### λ to-string 126 | # Returns a friendly representation of this generator. 127 | # 128 | # By convention (of this library), the friendly names are surrounded 129 | # by angular brackets to identify them as generators easily. 130 | # 131 | # :: () -> String 132 | to-string: -> '' 133 | } 134 | 135 | 136 | ### -- Combinators for constructing Generators ------------------------- 137 | 138 | #### λ as-generator 139 | # Lifts a regular value to a `Generator`. 140 | # 141 | # This is used by all combinators to allow users to pass regular values 142 | # as if they were proper `Generators`, making the API cleaner. 143 | # 144 | # :: Generator a -> Generator a 145 | as-generator = (a) -> 146 | | generator-p a => a 147 | | otherwise => do 148 | Generator.derive { 149 | to-string: -> "<#a>" 150 | next: (n) -> make-value (compute n, a, this), this 151 | } 152 | 153 | 154 | ##### λ bind 155 | # Monadic bind for generators 156 | # 157 | # :: Generator a -> (a -> Generator b) -> Generator b 158 | bind = (gen, f) -> do 159 | Generator.derive { 160 | next: (n) -> do 161 | v = value n, this, (as-generator gen) 162 | r = value n, this, (as-generator (f v)) 163 | make-value r, this 164 | } 165 | 166 | 167 | #### λ choice 168 | # Alternatively generate values from one of the given generators at 169 | # random. 170 | # 171 | # The values generated from `choice` generators are uniformly 172 | # distributed. You can use the `frequency` generator for weighted random 173 | # choices. 174 | # 175 | # :: Generator a... -> Generator b 176 | choice = (...as) -> do 177 | Generator.derive { 178 | to-string: -> "" 179 | next: (n) -> do 180 | gen = as-generator (pick-one as) 181 | make-value (value n, this, gen), gen 182 | } 183 | 184 | 185 | #### λ frequency 186 | # Constructs a new `Generator` that alternatively chooses between one of 187 | # the given `Generator`s using a weighted random selection. 188 | # 189 | # :: (Number, Generator a)... -> Generator b 190 | frequency = (...as) -> do 191 | gs = concat-map (([w,g]) -> replicate w, g), as 192 | representation = ([w, g]) -> w + ':' + g 193 | 194 | (choice ...gs).derive { 195 | to-string: -> "" 196 | } 197 | 198 | 199 | #### λ sequence 200 | # Constructs a new `Generator` that yields the combination of several 201 | # `Generator`s. 202 | # 203 | # :: Generator a... -> Generator b 204 | sequence = (...as) -> do 205 | Generator.derive { 206 | to-string: -> "" 207 | next: (n) -> make-value (as.map (value n, this)), this 208 | } 209 | 210 | 211 | #### λ sized 212 | # Constructs a new `Generator` with a new complexity `size` hint. 213 | # 214 | # :: (Number -> Number) -> Generator a -> Generator b 215 | sized = (f, gen) --> do 216 | g = as-generator gen 217 | g.derive { next: (n) -> g.next (f n) } 218 | 219 | 220 | #### λ recursive 221 | # Constructs a new `Generator` that takes a function or recursive 222 | # generator, and halves the size at each recursive invocation. 223 | # 224 | # The function is late bound, so you can use it for direct and indirect 225 | # recursive generators easily :3 226 | # 227 | # :: (Number -> Generator a) -> Generator b 228 | # :: Generator a -> Generator b 229 | recursive = (gen) -> do 230 | Generator.derive { 231 | to-string: -> "" 232 | next: (n) -> do 233 | n := floor ((n ? @size) / 2) 234 | g = compute n, gen, this 235 | make-value (value n, this, g), this 236 | } 237 | 238 | 239 | #### λ label 240 | # Constructs a new, custom labelled, `Generator`. 241 | # 242 | # :: String -> Generator a -> Generator a 243 | label = (name, gen) --> (as-generator gen).derive { to-string: -> "<#name>" } 244 | 245 | 246 | #### λ transform 247 | # Constructs a new `Generator` that transforms the value of another 248 | # `Generator`. 249 | # 250 | # :: (a -> b) -> Generator a -> Generator b 251 | transform = (f, gen) --> do 252 | g = as-generator gen 253 | g.derive { 254 | next: (n) -> make-value (f (value n, this, g)), this 255 | } 256 | 257 | 258 | #### λ repeat 259 | # Constructs a new `Generator` that repeats a given `Generator`. 260 | # 261 | # :: Generator a -> Generator b 262 | repeat = (gen) -> do 263 | gen := as-generator gen 264 | gen.derive { 265 | to-string: -> "" 266 | next: (n) -> do 267 | size = n ? @size 268 | range = [1 to (choose-int 0, size)] 269 | make-value (range.map ~> value size, this, gen), this 270 | } 271 | 272 | 273 | 274 | 275 | ### -- Exports --------------------------------------------------------- 276 | module.exports = { 277 | make-value 278 | value 279 | 280 | Generator 281 | 282 | as-generator 283 | bind 284 | choice 285 | frequency 286 | sequence 287 | sized 288 | recursive 289 | label 290 | transform 291 | repeat 292 | } 293 | -------------------------------------------------------------------------------- /src/index.ls: -------------------------------------------------------------------------------- 1 | ## Module index ######################################################## 2 | # 3 | # The entry-point for Claire. 4 | # 5 | # 6 | # Copyright (c) 2013-2014 Quildreen "Sorella" Motta 7 | # 8 | # Permission is hereby granted, free of charge, to any person 9 | # obtaining a copy of this software and associated documentation files 10 | # (the "Software"), to deal in the Software without restriction, 11 | # including without limitation the rights to use, copy, modify, merge, 12 | # publish, distribute, sublicense, and/or sell copies of the Software, 13 | # and to permit persons to whom the Software is furnished to do so, 14 | # subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | 28 | ### -- Helpers --------------------------------------------------------- 29 | merge = (...as) -> as.reduce (<<<) 30 | 31 | 32 | 33 | ### -- Exports --------------------------------------------------------- 34 | module.exports = merge do 35 | * { for-all } = require './property' 36 | * require './generating' 37 | * require './check' 38 | * data: require './data' 39 | -------------------------------------------------------------------------------- /src/property.ls: -------------------------------------------------------------------------------- 1 | ## Module property ##################################################### 2 | # 3 | # Defines how to generate and test properties. 4 | # 5 | # 6 | # Copyright (c) 2013-2014 Quildreen "Sorella" Motta 7 | # 8 | # Permission is hereby granted, free of charge, to any person 9 | # obtaining a copy of this software and associated documentation files 10 | # (the "Software"), to deal in the Software without restriction, 11 | # including without limitation the rights to use, copy, modify, merge, 12 | # publish, distribute, sublicense, and/or sell copies of the Software, 13 | # and to permit persons to whom the Software is furnished to do so, 14 | # subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | 28 | ### -- Dependencies ---------------------------------------------------- 29 | { Base } = require 'boo' 30 | { test } = require './check' 31 | 32 | 33 | ### -- Aliases --------------------------------------------------------- 34 | frozen = Object.freeze 35 | 36 | 37 | ### -- Property result types ------------------------------------------- 38 | 39 | #### λ make-result 40 | # Constructs a Result object. 41 | # 42 | # :: String -> [a] -> [String] -> Maybe Bool -> Result 43 | make-result = (kind, value, args, labels) --> 44 | kind : kind 45 | value : value 46 | labels : labels 47 | arguments : args 48 | 49 | 50 | #### λ invalidate 51 | # Invalidates the property for the given arguments (they're not valid). 52 | # 53 | # :: [a] -> Result 54 | invalidate = (args) --> 55 | make-result \ignored, null, args, [] 56 | 57 | 58 | #### λ hold 59 | # Constructs a Result for a successful property. 60 | # 61 | # :: [a] -> [String] -> Result 62 | hold = make-result \held, true 63 | 64 | 65 | #### λ reject 66 | # Rejects the property with the given reason. 67 | # 68 | # :: [a] -> [String] -> Result 69 | reject = make-result \rejected 70 | 71 | 72 | #### λ fail 73 | # Constructs a Result for a failed property (one that errored out). 74 | # 75 | # :: [a] -> [String] -> Result 76 | fail = make-result \failed 77 | 78 | 79 | ### -- Helpers --------------------------------------------------------- 80 | 81 | #### λ values 82 | # Extracts the values from a list of generated arguments 83 | # :: [Value a] -> [a] 84 | values = (.map (.value)) 85 | 86 | #### λ valid-p 87 | # Checks if a property is valid for the given arguments. 88 | # :: [a] -> Property -> Bool 89 | valid-p = (args, prop) --> 90 | | prop.implications.length is 0 => true 91 | | otherwise => prop.implications.every (f) -> f ...(values args) 92 | 93 | 94 | #### λ classify 95 | # Yields a list of classifications for the given arguments. 96 | # :: [a] -> Property -> [String] 97 | classify = (args, prop) --> 98 | (prop.classifiers.map (f) -> f ...(values args)).filter (!~= null) 99 | 100 | 101 | #### λ verify 102 | # Verifies if the property's invariant's hold for the arguments. 103 | # :: [a] -> [String] -> Property -> Bool 104 | verify = (args, labels, prop) --> 105 | try 106 | result = prop.invariant ...(values args) 107 | if result is true => hold args, labels 108 | else => reject result, args, labels 109 | catch e 110 | fail e, args, labels 111 | 112 | 113 | #### λ apply-property 114 | # Applies a property to some arguments. 115 | # :: [a] -> Property -> Result 116 | apply-property = (args, prop) --> 117 | | valid-p args, prop => verify args, (classify args, prop), prop 118 | | otherwise => invalidate args 119 | 120 | 121 | ### -- Core implementation --------------------------------------------- 122 | 123 | #### {} Property 124 | # Represents a property that can be verified against a list of randomly 125 | # generated arguments. 126 | # 127 | # :: Base <| Property 128 | Property = Base.derive { 129 | ##### λ init 130 | # Initialises a Property instance's for the first time. 131 | # :: @this:Property => [Gen a] -> this* 132 | init: (args) -> 133 | @arguments = frozen args or [] 134 | @classifiers = frozen [] 135 | @implications = frozen [] 136 | this 137 | 138 | ##### λ invariant 139 | # The invariant that should hold for this property. 140 | # :: (a... -> Bool) -> Bool 141 | invariant: -> ... 142 | 143 | ##### λ satisfy 144 | # Yields a new property with the given invariant. 145 | # :: @Property => (a... -> Bool) -> Property 146 | satisfy: (f) -> @derive { invariant: f } 147 | 148 | ##### λ classify 149 | # Yields a new property that provides additional classification for 150 | # the arguments. 151 | # :: @Property => (a... -> Maybe b) -> Property 152 | classify: (f) -> @derive { classifiers: @classifiers ++ [f] } 153 | 154 | ##### λ given 155 | # Yields a new property that only holds when additional implications 156 | # hold for the generated arguments. 157 | # :: @Property => (a... -> Bool) -> Property 158 | given: (f) -> @derive { implications: @implications ++ [f] } 159 | 160 | ##### λ run 161 | # Returns the `Result` of applying the property to randomly generated 162 | # arguments once. 163 | # :: @Property => () -> Result 164 | run: -> apply-property (@arguments.map (g) -> g.next!), this 165 | 166 | ##### λ as-test 167 | # Returns a function that can be given for a test runner to verify 168 | # this property. 169 | # :: @Property => Config? -> () -> () 170 | as-test: (config) -> 171 | prop = this 172 | -> test config, prop.derive { invariant: prop.invariant.bind this } 173 | } 174 | 175 | 176 | #### λ for-all 177 | # Sugar for constructing a new `Property` for a series of given argument 178 | # specifications. 179 | # :: Gen a... -> Property 180 | for-all = (...as) -> Property.make as 181 | 182 | 183 | 184 | ### -- Exports --------------------------------------------------------- 185 | module.exports = { Property, for-all } 186 | -------------------------------------------------------------------------------- /src/random.ls: -------------------------------------------------------------------------------- 1 | ## Module primitives ################################################### 2 | # 3 | # Base for pseudo-random stuff. 4 | # 5 | # 6 | # Copyright (c) 2013-2014 Quildreen "Sorella" Motta 7 | # 8 | # Permission is hereby granted, free of charge, to any person 9 | # obtaining a copy of this software and associated documentation files 10 | # (the "Software"), to deal in the Software without restriction, 11 | # including without limitation the rights to use, copy, modify, merge, 12 | # publish, distribute, sublicense, and/or sell copies of the Software, 13 | # and to permit persons to whom the Software is furnished to do so, 14 | # subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | 28 | ### -- Aliases --------------------------------------------------------- 29 | { floor, random } = Math 30 | 31 | 32 | ### -- Numeric randomness ---------------------------------------------- 33 | 34 | #### λ choose 35 | # Picks a random number between `[a, b[`. 36 | # 37 | # :: Number, Number -> Number 38 | choose = (a, b) -> (random! * (b - a)) + a 39 | 40 | #### λ choose-int 41 | # Picks a random integer between `[a, b[` 42 | # 43 | # :: Number, Number -> Integer 44 | choose-int = (a, b) -> floor (choose a, b) 45 | 46 | 47 | ### -- Collection randomness ------------------------------------------- 48 | 49 | #### λ pick-one 50 | # Picks an element of a sequence at random. 51 | # 52 | # :: [a] -> a 53 | pick-one = (as) -> as[choose-int 0, as.length] 54 | 55 | 56 | 57 | ### -- Exports --------------------------------------------------------- 58 | module.exports = { choose, choose-int, pick-one } 59 | -------------------------------------------------------------------------------- /test/specs/data.ls: -------------------------------------------------------------------------------- 1 | describe = (require 'hifive')! 2 | 3 | { keys, fold, values } = require 'prelude-ls' 4 | 5 | { sized } = require '../../lib/generating' 6 | { for-all } = require '../../lib' 7 | _ = require '../../lib/data' 8 | 9 | ### -- Helpers --------------------------------------------------------- 10 | size = (o) -> (keys o).length 11 | 12 | 13 | ### This assumes `o` has no circular references 14 | depth = (o, n = 0) -> switch typeof! o 15 | | <[ Array Object ]> => fold ((x, a) -> x >? (depth a, n+1)), (n + 1), o 16 | | otherwise => n 17 | 18 | ### -- Specification --------------------------------------------------- 19 | module.exports = describe '{M} Generators' (o, describe) -> 20 | describe '-- Primitive data types' (o) -> 21 | o 'Null' do 22 | for-all _.Null 23 | .satisfy (is null) 24 | .as-test! 25 | 26 | o 'Undefined' do 27 | for-all _.Undefined 28 | .satisfy (is void) 29 | .as-test! 30 | 31 | o 'Bool' do 32 | for-all _.Bool 33 | .satisfy -> (it is true) || (it is false) 34 | .classify -> it 35 | .as-test! 36 | 37 | o 'Num' do 38 | for-all _.Num 39 | .satisfy (is 'Number') . (typeof!) 40 | .as-test! 41 | 42 | o 'Byte' do 43 | for-all _.Byte 44 | .satisfy -> (0 <= it < 255) and ((it .|. 0) is it) 45 | .as-test! 46 | 47 | o 'Char' do 48 | for-all _.Char 49 | .satisfy -> (it.length is 1) && \ 50 | (typeof! it is 'String') 51 | .as-test! 52 | 53 | o 'Str' do 54 | for-all _.Str 55 | .satisfy (is 'String') . (typeof!) 56 | .as-test! 57 | 58 | 59 | describe '-- Specialised numeric types' (o) -> 60 | max-int = Math.pow 2, 32 61 | 62 | o 'Int' do 63 | for-all _.Int 64 | .satisfy -> -max-int <= it < max-int 65 | .as-test! 66 | 67 | o 'UInt' do 68 | for-all _.UInt 69 | .satisfy -> 0 <= it < max-int 70 | .as-test! 71 | 72 | o 'Positive' do 73 | for-all _.Positive 74 | .satisfy (> 0) 75 | .as-test! 76 | 77 | o 'Negative' do 78 | for-all _.Negative 79 | .satisfy (< 0) 80 | .as-test! 81 | 82 | 83 | describe '-- Specialised textual types' (o) -> 84 | o 'NumChar' do 85 | for-all _.NumChar 86 | .satisfy Boolean . (== /\d/) 87 | .as-test! 88 | 89 | o 'UpperChar' do 90 | for-all _.UpperChar 91 | .satisfy Boolean . (== /[A-Z]/) 92 | .as-test! 93 | 94 | o 'LowerChar' do 95 | for-all _.LowerChar 96 | .satisfy Boolean . (== /[a-z]/) 97 | .as-test! 98 | 99 | o 'AlphaChar' do 100 | for-all _.AlphaChar 101 | .satisfy Boolean . (== /[a-zA-Z]/) 102 | .as-test! 103 | 104 | o 'AlphaNumChar' do 105 | for-all _.AlphaNumChar 106 | .satisfy Boolean . (== /[a-zA-Z0-9]/) 107 | .as-test! 108 | 109 | o 'AlphaStr' do 110 | for-all _.AlphaStr 111 | .given -> it.length > 0 112 | .satisfy Boolean . (== /[a-zA-Z]+/) 113 | .as-test! 114 | 115 | o 'NumStr' do 116 | for-all _.NumStr 117 | .given -> it.length > 0 118 | .satisfy Boolean . (== /[0-9]+/) 119 | .as-test! 120 | 121 | o 'AlphaNumStr' do 122 | for-all _.AlphaNumStr 123 | .given -> it.length > 0 124 | .satisfy Boolean . (== /[a-zA-Z0-9]+/) 125 | .as-test! 126 | 127 | o 'Id' do 128 | for-all _.Id 129 | .satisfy Boolean . (== /[\$_a-zA-Z][\$_a-zA-Z0-9]*/) 130 | .classify -> | it.length is 1 => 'trivial' 131 | | it.length > 1 => 'ok' 132 | .as-test! 133 | 134 | 135 | describe '-- Container data types' (o) -> 136 | o 'Array(Byte)' do 137 | for-all (_.Array _.Byte) 138 | .satisfy -> it.every (x) -> 0 <= x < 255 139 | .as-test! 140 | 141 | o 'Array(Bool, Byte)' do 142 | for-all (_.Array _.Bool, _.Byte) 143 | .satisfy -> 144 | it.every ((x) -> 145 | | typeof x is 'number' => 0 <= x < 255 146 | | otherwise => !!x is x) 147 | .as-test! 148 | 149 | o 'Object(Bool)' do 150 | for-all (sized (-> 20), (_.Object _.Bool)) 151 | .satisfy (o) -> 152 | ((keys o).every (== /[\$_a-zA-Z][\$_a-zA-Z0-9]*/)) && \ 153 | ((values o).every -> it == !!it) 154 | .as-test! 155 | 156 | 157 | describe '-- Umbrella type unions' (o) -> 158 | o 'Nothing' do 159 | for-all _.Nothing 160 | .satisfy (~= null) 161 | .as-test! 162 | 163 | o 'Falsy' do 164 | for-all _.Falsy 165 | .satisfy -> !it 166 | .as-test! 167 | 168 | o 'Any' do 169 | for-all (sized (-> 20), _.Any) 170 | .satisfy -> switch typeof! it 171 | | \Array => ((depth it) < 5) && (it.length < 20) 172 | | \Object => ((depth it) < 5) && ((keys it).length < 20) 173 | | \String => it.length < 20 174 | | \Number => -20 <= it < 20 175 | | otherwise => true 176 | .classify -> switch typeof! it 177 | | \Array => "Array: #{depth it}" 178 | | \Object => "Object: #{depth it}" 179 | | otherwise => typeof! it 180 | .as-test! 181 | -------------------------------------------------------------------------------- /test/specs/generating.ls: -------------------------------------------------------------------------------- 1 | describe = (require 'hifive')! 2 | assert = require 'assert' 3 | 4 | { keys } = require 'prelude-ls' 5 | 6 | { as-generator, bind, Generator \ 7 | , choice, frequency, sequence, recursive, sized \ 8 | , label, transform, repeat } = require '../../lib/generating' 9 | { for-all, data } = require '../../lib' 10 | _ = require '../../lib/data' 11 | 12 | module.exports = describe '{M} Generating' (o) -> 13 | o 'λ as-generator' -> do 14 | g = (as-generator 'a') 15 | assert.equal g.to-string!, '' 16 | for-all g .satisfy (is 'a') .as-test!! 17 | 18 | 19 | o 'λ as-generator' -> do 20 | g = (as-generator Generator) 21 | assert.equal g, Generator 22 | 23 | o 'λ bind' do 24 | g = data.Int 25 | f = (v) -> (as-generator [v, v-1]) 26 | for-all (bind g, f) 27 | .satisfy -> it[1] is (it[0] - 1) 28 | .as-test! 29 | 30 | o 'Generator#then' do 31 | g = data.Int 32 | f = (v) -> (as-generator [v, v-1]) 33 | for-all (g.then f) 34 | .satisfy -> it[1] is (it[0] - 1) 35 | .as-test! 36 | 37 | o 'λ choice' do 38 | for-all (choice 'a', 'b') 39 | .satisfy -> it in <[ a b ]> 40 | .classify -> it 41 | .as-test! 42 | 43 | o 'λ frequency' do 44 | for-all (frequency [1, 'a'], [5, 'b']) 45 | .satisfy -> it in <[ a b ]> 46 | .classify -> it 47 | .as-test! 48 | 49 | o 'λ sequence' do 50 | for-all (sequence 'a', 'b') 51 | .satisfy ([a, b]) -> (a is 'a') && (b is 'b') 52 | .as-test! 53 | 54 | o 'λ recursive' do 55 | a = sequence('a', (recursive (n) -> | n == 0 => 'a' 56 | | _ => a)) 57 | for-all (sized (-> 20), a) 58 | .satisfy -> ("#it".replace /,/g, '').length is 6 59 | .as-test! 60 | 61 | o 'λ sized' do 62 | for-all (sized (-> 5), (choice _.Num, _.Str, (_.Array _.Int), (_.Object _.Int))) 63 | .satisfy -> switch typeof! it 64 | | \Number => -5 <= it < 5 65 | | \String => it.length < 5 66 | | \Array => it.length < 5 67 | | \Object => (keys it).length < 5 68 | .classify (typeof!) 69 | .as-test! 70 | 71 | o 'λ label' -> do 72 | g = (label 'a', 'b') 73 | assert.equal g.to-string!, '' 74 | for-all g .satisfy (is 'b') .as-test!! 75 | 76 | 77 | o 'λ transform' do 78 | for-all (transform (.to-upper-case!), 'a') 79 | .satisfy (is 'A') 80 | .as-test! 81 | 82 | o 'λ repeat' do 83 | for-all (repeat 'a') 84 | .given -> it.length > 0 85 | .satisfy -> it.every (is 'a') 86 | .as-test! 87 | -------------------------------------------------------------------------------- /test/specs/index.ls: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | require './data' 3 | require './generating' 4 | require './property' 5 | ] 6 | -------------------------------------------------------------------------------- /test/specs/property.ls: -------------------------------------------------------------------------------- 1 | describe = (require 'hifive')! 2 | assert = require 'assert' 3 | 4 | {Property, for-all} = require '../../lib/property' 5 | {Generator, make-value} = require '../../lib' 6 | 7 | Nat = Generator.derive { 8 | init: -> @current = 0 9 | next: -> make-value (++@current), this 10 | } 11 | 12 | module.exports = describe '{M} property' (o, describe) -> 13 | describe '{} Property' (o, describe) -> 14 | describe 'λ satisfy' (o) -> 15 | o 'Should return a new property for the given law.' -> 16 | f = (==) 17 | p = Property.make [] 18 | q = p.satisfy f 19 | (assert q.invariant is f) 20 | (assert p.invariant isnt f) 21 | 22 | describe 'λ classify' (o) -> 23 | o 'Should add a new classifier to the property.' -> 24 | f = (+); g = (-) 25 | p = (Property.make []).classify f 26 | q = p.classify g 27 | 28 | assert.deep-equal p.classifiers, [f] 29 | assert.deep-equal q.classifiers, [f, g] 30 | 31 | describe 'λ given' (o) -> 32 | o 'Should add a new implication to the property.' -> 33 | f = (==); g = (<=) 34 | p = (Property.make []).given f 35 | q = p.given g 36 | 37 | assert.deep-equal p.implications, [f] 38 | assert.deep-equal q.implications, [f, g] 39 | 40 | describe 'λ run' (o) -> 41 | o 'Should run the invariant once.' -> 42 | p = Property.make [Nat.make!] .satisfy (-> it) 43 | assert p.run!value == 1 44 | assert p.run!value == 2 45 | 46 | o 'Should reject if the assertion fails.' -> 47 | p = Property.make [Nat.make!] .satisfy (== 2) 48 | assert p.run!kind == \rejected 49 | 50 | o 'Should use the return of the invariant as the failure reason.' -> 51 | p = Property.make [Nat.make!] .satisfy (-> 'boo') 52 | assert p.run!value == 'boo' 53 | 54 | o 'Should fail when invariant throws an error.' -> 55 | p = Property.make [Nat.make!] .satisfy (-> throw new Error) 56 | assert p.run!kind == \failed 57 | 58 | o 'Should return an undecided result if any implications don`t hold.' -> 59 | p = Property.make [Nat.make!] .satisfy (-> true) .given Boolean .given (> 1) 60 | assert p.run!kind == \ignored 61 | 62 | o 'Should succeed if the assertion holds.' -> 63 | p = Property.make [Nat.make!] .satisfy (== 1) 64 | assert p.run!kind == \held 65 | 66 | o 'Should add a list of all classifiers to the Result object.' -> 67 | p = Property.make [Nat.make!] .satisfy (-> true) .classify (typeof!) .classify (> 1) 68 | assert.deep-equal p.run!labels, ['Number', false] 69 | assert.deep-equal p.run!labels, ['Number', true] 70 | 71 | o 'Should include the list of arguments in the Result object.' -> 72 | nat1 = Nat.make! 73 | nat2 = Nat.make! 74 | nat1.next! 75 | 76 | p = Property.make [nat1, nat2] .satisfy (-> true) 77 | 78 | assert.deep-equal p.run!arguments, [{value:2,generator:nat1}, {value:1,generator:nat2}] 79 | 80 | describe 'λ as-test()' (o) -> 81 | o 'Should run in the context passed to as-test.' -> 82 | ctx = {} 83 | p = Property.make [] .satisfy (-> this is ctx) 84 | p.as-test!.call ctx 85 | 86 | # Other properties to be checked in the check.ls 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /test/tap.ls: -------------------------------------------------------------------------------- 1 | require 'es5-shim' 2 | require 'es5-shim/es5-sham' 3 | 4 | hifive = require 'hifive' 5 | tap = require 'hifive-tap' 6 | specs = require './specs' 7 | 8 | (hifive.run specs, tap!).then (r) -> if r.failed.length => process?.exit 1 9 | -------------------------------------------------------------------------------- /tools/bump-version.js: -------------------------------------------------------------------------------- 1 | var pkg = require('../package.json') 2 | var fs = require('fs') 3 | 4 | function read(n) { 5 | return fs.readFileSync(n, 'utf-8') 6 | } 7 | 8 | function write(n, s) { 9 | return fs.writeFileSync(n, s, 'utf-8') 10 | } 11 | 12 | function minor(a) { 13 | return [a[0], a[1], Number(a[2]) + 1] 14 | } 15 | 16 | function feature(a) { 17 | return [a[0], Number(a[1]) + 1, 0] 18 | } 19 | 20 | function major(a) { 21 | return [Number(a[0]) + 1, 0, 0] 22 | } 23 | 24 | function bump(what, version) { 25 | return what === 'MAJOR' ? major(version) : what === 'FEATURE' ? feature(version) : /* otherwise */ 26 | minor(version) 27 | } 28 | 29 | 30 | var old_version = pkg.version 31 | 32 | pkg.version = bump(process.argv[2], pkg.version.split('.')).join('.') 33 | write('package.json', JSON.stringify(pkg, null, 2)) 34 | 35 | var readme = read('README.md').replace(/<\!-- \[release:\s*(.+?)\s*\] -->[\s\S]*?<\!-- \[\/release\] -->/, function(_, s) { 36 | return '\n' + '[release]: ' + s.replace(/\$VERSION/g, pkg.version) + '\n' + '' 37 | }) 38 | write('README.md', readme) 39 | 40 | console.log('Bumped from ' + old_version + ' to ' + pkg.version) --------------------------------------------------------------------------------