Skip to content

Commit

Permalink
initial working version
Browse files Browse the repository at this point in the history
  • Loading branch information
glenjamin committed Jan 24, 2015
1 parent dcc8368 commit 67bbad6
Show file tree
Hide file tree
Showing 13 changed files with 314 additions and 1 deletion.
19 changes: 19 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"env": {
"node": true
},
"rules": {
"strict": true,
"quotes": false,
"no-use-before-define": "func",
"no-unused-vars": [2, "all"],
"no-mixed-requires": [1, true],
"max-depth": [1, 5],
"max-len": [1, 80, 4],
"eqeqeq": false,
"no-path-concat": false,
"no-else-return": true,
"no-eq-null": true,
"no-lonely-if": true
}
}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules/
out/
checkers.js
checkers.js.map
.repl/
target/
5 changes: 5 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
out/
checkers.js.map
.repl/
target/
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,69 @@
# checkers
ClojureScript's test.check packaged up for JavaScript

Property-based testing for JavaScript via ClojureScript's [test.check](https://github.com/clojure/test.check).

test.check is a Clojure property-based testing tool inspired by [QuickCheck](http://www.quviq.com/products/erlang-quickcheck/). The core idea of test.check is that instead of enumerating expected input and output for unit tests, you write properties about your function that should hold true for all inputs. This lets you write concise, powerful tests.

Checkers brings the power of test.check to plain ol' JavaScript.

# Install

npm install checkers --save

# Usage

```js
var checkers = require('checkers');
var gen = checkers.gen;

// Property is incorrect
checkers.forAll(
[gen.int],
function(i) {
return i * i > i;
}
).check(1000);

// Property is now correct
checkers.forAll(
[gen.int],
function(i) {
return i * i >= i;
}
).check(1000);

// Check property with a particular seed
checkers.forAll(
[gen.int],
function(i) {
return i * i >= i;
}
).check(1000, {seed: 1422111938215});
```

## Documentation

More coming soon!

## TODO

* Generator tests
* Generator docs
* Tutorial
* Better examples

## Development

See `npm run` or `package.json` for a list of available scripts.

You will need [leiningen](http://leiningen.org/) in order to build locally.

## License

Distributed under the Eclipse Public License.

checkers is Copyright © 2015 Glen Mailer and contributors.

[test.check](https://github.com/clojure/test.check/) is Copyright
Rich Hickey, Reid Draper and contributors.

103 changes: 103 additions & 0 deletions cljs/checkers.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
(ns checkers
(:require [cljs.test.check :as tc]
[cljs.test.check.properties :as prop]
[cljs.test.check.generators :as gen]
[goog.object]))

;; Interop utils
(def format (aget (js/require "util") "format"))

(defn- obj-seq
"Seq from enumerable keys of a JS Object"
[obj]
(for [k (goog.object/getKeys obj)]
[k (aget obj k)]))

(defn- arrayify
"Force a generator's output to be a JS array"
[generator]
(fn [& args] (gen/fmap into-array (apply generator args))))

(defn- obj-assoc [obj k v] (doto obj (aset (clj->js k) v)))
(defn- into-object [associative] (reduce-kv obj-assoc #js {} associative))
(defn- objectify
"Force a generator's output to be a JS object"
[generator]
(fn [& args] (gen/fmap into-object (apply generator args))))

;; Check API

(defn format-args [arglist]
(.join (into-array (map #(format "%j" %) arglist)) ","))

(defn generate-message
[{:keys [num-tests fail seed]
{:keys [smallest]} :shrunk}]
(format "Failed after %d test(s)\nInput: %s\nShrunk to: %s\nSeed: %s"
num-tests (format-args fail) (format-args smallest) seed))

(defn check
"Wrap up quick-check to take options as a JS object and throw on failure"
[property n & [opts]]
(let [opts (apply concat (js->clj opts :keywordize-keys true))
{:keys [result] :as r} (apply tc/quick-check n property opts)]
(if-not result
(let [ex (js/Error. (generate-message r))]
(aset ex "result" (clj->js r))
(throw ex)))))

(aset js/exports "forAll"
(fn [& args]
(let [p (apply prop/for-all* args)]
(aset p "check" #(check p %1 %2))
p)))

(aset js/exports "sample" (comp into-array gen/sample))

;; Generator API

(aset js/exports "gen" #js {})


(aset js/exports "gen" "fmap" gen/fmap)
(aset js/exports "gen" "return" gen/return)
(aset js/exports "gen" "bind" gen/bind)

; Combinators & Helpers
(aset js/exports "gen" "resize" gen/resize)
(aset js/exports "gen" "choose" gen/choose)
(aset js/exports "gen" "oneOf" gen/one-of)
(aset js/exports "gen" "frequency" gen/frequency)
(aset js/exports "gen" "elements" gen/elements)
(aset js/exports "gen" "suchThat" gen/such-that)
(aset js/exports "gen" "notEmpty" gen/not-empty)
(aset js/exports "gen" "noShrink" gen/no-shrink)
(aset js/exports "gen" "shrink2" gen/shrink-2)

; Data Types
(aset js/exports "gen" "boolean" gen/boolean)
(aset js/exports "gen" "tuple" (arrayify gen/tuple))

(aset js/exports "gen" "int" gen/int)
(aset js/exports "gen" "nat" gen/nat)
(aset js/exports "gen" "posInt" gen/pos-int)
(aset js/exports "gen" "negInt" gen/neg-int)
(aset js/exports "gen" "sPosInt" gen/s-pos-int)
(aset js/exports "gen" "sNegInt" gen/s-neg-int)

(aset js/exports "gen" "array" (arrayify gen/vector))
(aset js/exports "gen" "shuffle" (arrayify gen/shuffle))

(aset js/exports "gen" "obj" (objectify gen/map))
(def ^:private gen-hash-map (objectify gen/hash-map))
(aset js/exports "gen" "object"
(fn [obj] (apply gen-hash-map (apply concat (obj-seq obj)))))

(aset js/exports "gen" "char" gen/char)
(aset js/exports "gen" "charAscii" gen/char-ascii)
(aset js/exports "gen" "charAlphanum" gen/char-alphanumeric)
(aset js/exports "gen" "charAlpha" gen/char-alpha)

(aset js/exports "gen" "string" gen/string)
(aset js/exports "gen" "stringAscii" gen/string-ascii)
(aset js/exports "gen" "stringAlphanum" gen/string-alphanumeric)
1 change: 1 addition & 0 deletions cljs/notice.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// This file was generated by the ClojureScript compiler.
36 changes: 36 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "checkers",
"version": "0.9.0",
"description": "Property-based testing for JavaScript via ClojureScript's test.check",
"main": "checkers.js",
"scripts": {
"clean": "lein clean",
"build": "lein cljsbuild once release",
"build-dev": "lein cljsbuild once dev",
"dev": "lein cljsbuild auto dev",
"test": "mocha",
"repl": "./scripts/repl",
"prepublish": "npm run clean && npm run build"
},
"repository": {
"type": "git",
"url": "https://github.com/glenjamin/checkers.git"
},
"keywords": [
"test",
"testing",
"property-based",
"quickcheck"
],
"author": "Glen Mailer <[email protected]>",
"license": "EPL",
"bugs": {
"url": "https://github.com/glenjamin/checkers/issues"
},
"homepage": "https://github.com/glenjamin/checkers",
"devDependencies": {
"lodash": "^2.4.1",
"mocha": "^2.1.0",
"source-map-support": "^0.2.9"
}
}
36 changes: 36 additions & 0 deletions project.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
(defproject checkers "0.9.0-SNAPSHOT"
:description "Property-based testing for JavaScript via ClojureScript's test.check"
:url "https://github.com/glenjamin/checkers"

:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/clojurescript "0.0-2665"]
[org.clojure/test.check "0.7.0"]]

:plugins [[lein-cljsbuild "1.0.4"]]

:source-paths ["cljs"]

:clean-targets ["out" "checkers.js" "checkers.js.map"]

:cljsbuild {
:builds [{:id "dev"
:source-paths ["cljs"]
:compiler {:output-to "checkers.js"
:output-dir "out/dev"
:preamble ["notice.txt"]
:optimizations :simple
:pretty-print true
:cache-analysis true
:source-map "checkers.js.map"
:language-in :ecmascript5
:language-out :ecmascript5}}
{:id "release"
:source-paths ["cljs"]
:compiler {:output-to "checkers.js"
:output-dir "out/release"
:preamble ["notice.txt"]
:optimizations :advanced
:pretty-print true
:cache-analysis true
:language-in :ecmascript5
:language-out :ecmascript5}}]})
2 changes: 2 additions & 0 deletions scripts/repl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
rlwrap lein trampoline run -m clojure.main scripts/repl.clj
9 changes: 9 additions & 0 deletions scripts/repl.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(require
'[cljs.repl :as repl]
'[cljs.repl.node :as node])

(repl/repl* (node/repl-env)
{:output-dir "out/repl"
:optimizations :none
:cache-analysis true
:source-map true})
27 changes: 27 additions & 0 deletions test/basic.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*eslint-env mocha */
var _ = require('lodash');

var checkers = require('..');
var gen = checkers.gen;

describe("checkers", function() {
it("checks that squaring makes things bigger or the same", function() {
checkers.forAll(
[gen.int],
function(i) {
return i * i >= i;
}
).check(1000);
});
it("checks Array.sort is the same as _.sortBy on strings", function() {
checkers.forAll(
[gen.array(gen.int)],
function(arr) {
var lodash = _.sortBy(arr, function(x) { return '' + x; });
var stdlib = arr.slice();
stdlib.sort();
return _.isEqual(stdlib, lodash);
}
).check(100);
});
});
1 change: 1 addition & 0 deletions test/mocha.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require test/source-map-support
1 change: 1 addition & 0 deletions test/source-map-support.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('source-map-support').install();

0 comments on commit 67bbad6

Please sign in to comment.