lax 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 Feivel Jellyfish
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ lax
2
+ ===
3
+ A bright smidgen of a testing framework that is not the boss of you.
4
+ ```ruby
5
+ Lax.test { |assert|
6
+ assert.calling(:/).on(1).with(0).raises ZeroDivisionError
7
+
8
+ assert.satisfies(->(n) { n == 1 }) { |returns_one|
9
+ returns_one.calling(:+).on(0).with 1
10
+
11
+ returns_one.on(1) { |id_on_one|
12
+ id_on_one.calling(:+).with 0
13
+ id_on_one.calling(:*).with 1
14
+
15
+ 0.upto(10) { |n|
16
+ id_on_one.calling(:**).with n
17
+ }
18
+ }
19
+ }
20
+ }
21
+ Lax.go
22
+ ```
23
+ yes but why
24
+ -----------
25
+ * Everything about a test is independently scopeable - methods, arguments, receivers, blocks, expectations, hooks, and any metadata you might care to attach. Testing that one method call satisfies three conditions is as natural as testing that one condition is satisfied by three different method calls. Write tests in whatever way makes sense.
26
+ * No hardcoded constraints on terminal output, handling of failed tests, w/e - it's all done with user-configurable hooks.
27
+ * Support for concurrent testing (via threads - not currently threadsafe in JRuby).
28
+ * Code footprint so small, it's hardly there at all (< 200 SLOC).
29
+ * Does not pollute your toplevel namespace or infect the entire Ruby object hierarchy with its code.
30
+
31
+ make it do it
32
+ -------------
33
+ ```shell
34
+ cd my/project/root
35
+ mkdir -p lax/test
36
+ echo "Lax.test {|that| that.calling(:+).on(1).with(99).returns 100}" > lax/test/test.rb
37
+ echo "require 'lax'; Lax::RakeTask.new(:dir=>'lax')" >> rakefile
38
+ rake lax
39
+ ```
40
+
41
+ license
42
+ -------
43
+ X11. See LICENSE for details.
44
+
data/lax.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ $LOAD_PATH << File.expand_path('../lib', __FILE__)
2
+ require 'rake'
3
+ require 'lax/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lax'
7
+ spec.version = Lax::VERSION
8
+ spec.author = 'feivel jellyfish'
9
+ spec.email = 'feivel@sdf.org'
10
+ spec.files = FileList['lax.gemspec','lib/**/*.rb','README.md','LICENSE']
11
+ spec.test_files = FileList['rakefile','test/**/*.rb']
12
+ spec.license = 'MIT/X11'
13
+ spec.homepage = 'http://github.com/gwentacle/lax'
14
+ spec.summary = 'A more forgiving testing framework.'
15
+ spec.description = 'A testing framework that tries to stay out of your way.'
16
+ end
data/lib/lax/cb.rb ADDED
@@ -0,0 +1,28 @@
1
+ module Lax
2
+ class CB < Proc
3
+ def <<(cb); CB.new {|e| self[cb[e]]} end
4
+ def +(cb); CB.new {|e| self[e]; cb[e]} end
5
+
6
+ StartTime = CB.new { @start = Time.now }
7
+ StopTime = CB.new { @stop = Time.now }
8
+
9
+ SimpleOut = CB.new do |tc|
10
+ $stdout.write(tc[:pass] ? "\x1b[32m=\x1b[0m" : "\x1b[31m#\x1b[0m")
11
+ end
12
+
13
+ Summary = CB.new do |rn|
14
+ puts "\nFinished #{(cs=rn.cases).size} tests" <<
15
+ " in #{(@stop - @start).round 10} seconds" <<
16
+ " with #{(fs=cs.select{|c|!c[:pass]}).size} failures"
17
+ end
18
+
19
+ FailList = CB.new do |rn|
20
+ fs = rn.cases.select{|c|!c[:pass]}
21
+ fs.each do |f|
22
+ puts " #{Module===f[:obj] ? "#{f[:obj]}::" : "#{f[:obj].class}#"}#{f[:msg]}" <<
23
+ "#{?(+[*f[:args]]*', '+?) if f[:args]} " << (f.has_key?(:value) ? "#=> #{f[:value]}" : "raised unhandled #{f[:xptn].class}")
24
+ end
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,38 @@
1
+ module Lax
2
+ class RakeTask
3
+ include Rake::DSL
4
+ def initialize(opts = {})
5
+ dir = opts.delete(:dir) || :test
6
+ runner_opts = {
7
+ start: CB::StartTime,
8
+ after: CB::SimpleOut,
9
+ finish: CB::StopTime + CB::Summary + CB::FailList
10
+ }.merge opts
11
+ make_tasks dir, runner_opts
12
+ end
13
+
14
+ private
15
+ def make_tasks(dir, runner_opts)
16
+ namespace dir do
17
+ desc "[Lax] load all test files"
18
+ task load: make_test_groups(dir)
19
+ desc "[Lax] run all loaded tests"
20
+ task(:run) { Lax.go runner_opts }
21
+ end
22
+ desc "[Lax] load and run all tests"
23
+ task dir => ["#{dir}:load","#{dir}:run"]
24
+ end
25
+
26
+ def make_test_groups(dir)
27
+ FileList["#{dir}/**/*"].select {|f| File.directory? f}.map do |group|
28
+ name = group.sub(/^#{dir}\//,'').gsub(/\//,?:)
29
+ desc "[Lax] load files in #{group}"
30
+ task name do
31
+ Dir["#{group}/*.rb"].each {|file| load file }
32
+ end
33
+ "#{dir}:#{name}"
34
+ end
35
+ end
36
+ end
37
+ end
38
+
data/lib/lax/runner.rb ADDED
@@ -0,0 +1,45 @@
1
+ module Lax
2
+ # Runner for test cases. Handles callbacks, concurrency, etc.
3
+ # TODO: DRb support!
4
+ class Runner
5
+ attr_reader :cases
6
+ # Takes an array of test cases and an optional hash of options.
7
+ def initialize(cases, opts={})
8
+ @cases, @opts = cases, {threads: 1}.merge(opts)
9
+ end
10
+
11
+ def go
12
+ (start=@opts[:start]) and start[self]
13
+ todo = @cases.dup
14
+ (1..@opts[:threads]).map do
15
+ Thread.new {after run before todo.shift while todo.any?}
16
+ end.each &:join
17
+ (finish=@opts[:finish]) and finish[self]
18
+ self
19
+ end
20
+
21
+ private
22
+ def run(c)
23
+ begin
24
+ c[:pass] = c[:cond][c[:value]=c[:obj].__send__(c[:msg],*c[:args],&c[:blk])]
25
+ rescue c[:xptn] => e
26
+ c[:pass] = true
27
+ rescue => e
28
+ c[:pass] = false
29
+ c[:xptn] = e
30
+ end
31
+ c
32
+ end
33
+
34
+ def before(c)
35
+ [@opts,c].each {|h| h[:before] and h[:before][c]}
36
+ c
37
+ end
38
+
39
+ def after(c)
40
+ [c,@opts].each {|h| h[:after] and h[:after][c]}
41
+ c
42
+ end
43
+ end
44
+ end
45
+
data/lib/lax/tree.rb ADDED
@@ -0,0 +1,60 @@
1
+ module Lax
2
+ class Tree < Array
3
+ attr_reader :tc
4
+ def initialize(tc={})
5
+ @tc=tc
6
+ end
7
+
8
+ def on(obj,&b)
9
+ where({obj: obj},&b)
10
+ end
11
+
12
+
13
+ def calling(msg,&b)
14
+ where({msg: msg},&b)
15
+ end
16
+
17
+ def with(*args,&b)
18
+ where({args: args},&b)
19
+ end
20
+
21
+ def with_block(blk=nil,&b)
22
+ blk ? where({blk: blk},&b) : where(blk: b)
23
+ end
24
+
25
+ def satisfies(cond=nil,&b)
26
+ cond ? where({cond: cond},&b) : where(cond: b)
27
+ end
28
+
29
+ def before(bef=nil,&b)
30
+ bef ? where({before: bef},&b) : where(before: b)
31
+ end
32
+
33
+ def after(aft=nil,&b)
34
+ aft ? where({after: aft},&b) : where(after: b)
35
+ end
36
+
37
+ def raises(xptn=StandardError,&b)
38
+ where({xptn: xptn},&b)
39
+ end
40
+
41
+ def it
42
+ calling(:tap).with_block {}
43
+ end
44
+
45
+ def returns(v,&b)
46
+ satisfies ->(e){e==v}, &b
47
+ end
48
+
49
+ def where(h)
50
+ g=Tree.new tc.merge h
51
+ yield g if block_given?
52
+ push(g).last
53
+ end
54
+
55
+ def cases
56
+ any?? map {|n| n.any? ? n.cases : n.tc}.flatten : [tc]
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,3 @@
1
+ module Lax
2
+ VERSION = '0.0.1'
3
+ end
data/lib/lax.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'lax/version'
2
+ module Lax
3
+ autoload :Tree, 'lax/tree'
4
+ autoload :RakeTask, 'lax/rake_task'
5
+ autoload :Runner, 'lax/runner'
6
+ autoload :CB, 'lax/cb'
7
+ class << self
8
+ @@cases = []
9
+ def test(c={})
10
+ yield(group = Tree.new(Hash===c ? c : {obj: c}))
11
+ @@cases += group.cases
12
+ end
13
+
14
+ def go(runner_opts={})
15
+ Runner.new(@@cases.shift(@@cases.size), runner_opts).go
16
+ end
17
+ end
18
+ end
19
+
data/rakefile ADDED
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH << File.expand_path('../lib', __FILE__)
2
+ require 'rake'
3
+ require 'lax'
4
+
5
+ Lax::RakeTask.new
6
+ task default: :test
7
+
@@ -0,0 +1,14 @@
1
+ class TestCases
2
+ def a_number
3
+ rand(0..100)
4
+ end
5
+ def an_exception
6
+ QWERTY.uiop[]
7
+ end
8
+ end
9
+
10
+ Lax.test(TestCases.new) {|assert|
11
+ assert.calling(:a_number).satisfies {|n| Fixnum===n}
12
+ assert.calling(:an_exception).raises
13
+ }
14
+
@@ -0,0 +1,35 @@
1
+ Lax.test(1) {|that|
2
+ that.calling(:/).with(0).raises ZeroDivisionError
3
+ that.calling(:**) {|exponentiation|
4
+ exponentiation.with(1).satisfies {|n|n==1}
5
+ exponentiation.with(2).satisfies ->(n){n==1}
6
+ }
7
+ }
8
+
9
+ Lax.test { |assert|
10
+ assert.calling(:/).on(1).with(0).raises ZeroDivisionError
11
+ assert.returns(1) { |assert_equal_to_one|
12
+ assert_equal_to_one.on(0).calling(:+).with 1
13
+ assert_equal_to_one.on(1) {|identity_on_one|
14
+ identity_on_one.calling(:*).with 1
15
+ identity_on_one.calling(:**).with 2
16
+ identity_on_one.calling(:+).with 0
17
+ }
18
+ }
19
+ }
20
+
21
+ Lax.test {|claim|
22
+ claim.calling(:object_id).satisfies(->(v){Fixnum===v}) {|_|
23
+ _.on 1
24
+ _.on 'asdf'
25
+ _.on String
26
+ _.on Lax
27
+ }
28
+
29
+ claim.calling(:size).on([1,2,3]).satisfies {|n|n==3}
30
+ }
31
+
32
+ Lax.test(222) {|that|
33
+ that.it.returns 222
34
+ }
35
+
@@ -0,0 +1,11 @@
1
+ @array = []
2
+
3
+ Lax.test(@array) {|that|
4
+ that.before {@array << 1}.calling(:size) {|_|
5
+ _.returns 1
6
+ _.returns 2
7
+ _.after {@array.shift}.returns 3
8
+ _.returns 3
9
+ }
10
+ }
11
+
data/test/unit/test.rb ADDED
@@ -0,0 +1,9 @@
1
+ Lax.test {|assert|
2
+ assert.calling(:upcase).on('asdf').returns('ASDF')
3
+ }
4
+
5
+ Lax.test('asdf') {|test_that|
6
+ test_that.satisfies {|n| n=='ASDF'}.calling(:upcase)
7
+ }
8
+
9
+
@@ -0,0 +1,14 @@
1
+ before = proc {sleep 0.01}
2
+ cases = Lax::Tree.new.tap {|tree|
3
+ 1.upto(100) do |n|
4
+ tree.on(n).it.returns n
5
+ end
6
+ }.cases
7
+
8
+ Lax.test(Lax::Runner.new(cases, threads: 100, before: before)) {|that|
9
+ that.calling(:go).satisfies {|runner|
10
+ runner.cases.size == 100 and
11
+ runner.cases.all? {|c| c[:pass]}
12
+ }
13
+ }
14
+
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lax
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - feivel jellyfish
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-04 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: A testing framework that tries to stay out of your way.
15
+ email: feivel@sdf.org
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lax.gemspec
21
+ - lib/lax.rb
22
+ - lib/lax/rake_task.rb
23
+ - lib/lax/version.rb
24
+ - lib/lax/runner.rb
25
+ - lib/lax/cb.rb
26
+ - lib/lax/tree.rb
27
+ - README.md
28
+ - LICENSE
29
+ - rakefile
30
+ - test/case/control.rb
31
+ - test/case/cases.rb
32
+ - test/unit/callbacks.rb
33
+ - test/unit/test.rb
34
+ - test/unit/thread.rb
35
+ homepage: http://github.com/gwentacle/lax
36
+ licenses:
37
+ - MIT/X11
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 1.8.23
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: A more forgiving testing framework.
60
+ test_files:
61
+ - rakefile
62
+ - test/case/control.rb
63
+ - test/case/cases.rb
64
+ - test/unit/callbacks.rb
65
+ - test/unit/test.rb
66
+ - test/unit/thread.rb