Browse files

initial commit

  • Loading branch information...
0 parents commit 03bd64a7dd6e7a0b578c2367404febe2e309ac6e @svenfuchs committed Dec 1, 2008
Showing with 570 additions and 0 deletions.
  1. +11 −0 lib/with.rb
  2. +49 −0 lib/with/context.rb
  3. +36 −0 lib/with/dsl.rb
  4. +59 −0 lib/with/group.rb
  5. +15 −0 lib/with/named_block.rb
  6. +123 −0 test/demo.rb
  7. +36 −0 test/testunit_with_test.rb
  8. +241 −0 test/with_test.rb
11 lib/with.rb
@@ -0,0 +1,11 @@
+require 'with/context'
+require 'with/group'
+require 'with/named_block'
+
+module With
+ def describe(name, &block)
+ group = Group.new name, &block
+ group.compile(self)
+ group
+ end
+end
49 lib/with/context.rb
@@ -0,0 +1,49 @@
+require 'with/dsl'
+
+module With
+ class Context
+ include Dsl
+
+ attr_reader :name
+
+ def initialize(name, action, preconditions, assertions, &block)
+ @name, @action, @preconditions, @assertions = name, action, [], []
+ @children = []
+ instance_eval &block if block
+ @preconditions += preconditions
+ @assertions += assertions
+ end
+
+ def add_children(*children)
+ children.each do |child|
+ child.parent = self
+ @children << child
+ end
+ end
+
+ def find_action
+ @action || parent && parent.find_action || raise("could not find action")
+ end
+
+ def collect_preconditions
+ (parent ? parent.collect_preconditions : []) + @preconditions
+ end
+
+ def collect_assertions
+ (parent ? parent.collect_assertions : []) + @assertions
+ end
+
+ def self_and_parents
+ (parent ? parent.self_and_parents : []) + [self]
+ end
+
+ def leafs
+ return self if children.empty?
+ children.map { |child| child.leafs }.flatten
+ end
+
+ def calls
+ [find_action, collect_preconditions, collect_assertions]
+ end
+ end
+end
36 lib/with/dsl.rb
@@ -0,0 +1,36 @@
+module With
+ module Dsl
+ attr_reader :children
+ attr_accessor :parent
+
+ attr_reader :preconditions
+ attr_reader :assertions
+
+ def with(*names, &block)
+ names << { :parent => self }
+ Group.new(*names, &block)
+ end
+
+ def before(name = nil, &block)
+ name ||= "before #{block.inspect}"
+ @preconditions << NamedBlock.new(name, &block)
+ end
+
+ def action(name = nil, &block)
+ name ||= "action #{block.inspect}"
+ @action = NamedBlock.new(name, &block)
+ end
+
+ def assertion(name = nil, options = {}, &block)
+ group = options[:with] ? with(*options[:with]) : self
+ group.assertions << NamedBlock.new(name, &block)
+ end
+ alias :it :assertion
+
+ def share(*blocks, &block)
+ name = blocks.shift
+ blocks << block if block
+ @shared[name] = blocks
+ end
+ end
+end
59 lib/with/group.rb
@@ -0,0 +1,59 @@
+require 'with/dsl'
+
+module With
+ class Group
+ include Dsl
+
+ attr_reader :names
+
+ def initialize(*args, &block)
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ @parent = options[:parent]
+ @parent.children << self if @parent
+
+ @names = args
+ @children, @assertions, @preconditions, @shared = [], [], [], {}
+
+ instance_eval &block if block
+ end
+
+ def compile(target)
+ expand.first.leafs.each { |leaf| define_test_method(target, leaf) }
+ end
+
+ protected
+
+ def expand
+ names.map do |name|
+ shared_blocks = find_shared(name) || [nil]
+ shared_blocks.map do |shared|
+ context = Context.new(name, @action, preconditions.dup, assertions.dup, &shared)
+ children.each { |child| context.add_children *child.expand }
+ context
+ end
+ end.flatten
+ end
+
+ def find_shared(name)
+ @shared[name] || parent && parent.find_shared(name)
+ end
+
+ def define_test_method(target, context)
+ action, preconditions, assertions = *context.calls
+ method_name = generate_test_method_name(context)
+
+ target.send :define_method, method_name, &lambda {
+ preconditions.map { |precondition| instance_eval &precondition.block }
+ instance_eval &action.block
+ assertions.map { |assertion| instance_eval &assertion.block }
+ }
+ end
+
+ def generate_test_method_name(context)
+ contexts = context.self_and_parents
+ name = "test_<#{context.object_id}>_#{contexts.shift.name}_"
+ name += contexts.map { |c| "with_#{c.name}" }.join('_and_')
+ name.gsub(/[\W ]/, '_').gsub('__', '_')
+ end
+ end
+end
15 lib/with/named_block.rb
@@ -0,0 +1,15 @@
+module With
+ class NamedBlock
+ attr_reader :name, :block
+
+ def initialize(name, &block)
+ @name = name
+ @block = block or raise "need to provide a block for an assertion"
+ end
+
+ def call
+ @block.call
+ name
+ end
+ end
+end
123 test/demo.rb
@@ -0,0 +1,123 @@
+$:.unshift File.dirname(__FILE__) + '/../lib/'
+require 'rubygems'
+require 'actionpack'
+require 'action_controller'
+require 'action_controller/test_process'
+require 'active_support'
+require 'with'
+
+# setup some fakes so the demo can run
+
+class Article
+ attr_reader :errors
+
+ def initialize(attributes)
+ @attributes = attributes
+ end
+
+ def save
+ @errors = ['title', 'body'] - @attributes.keys
+ @errors.empty?
+ end
+end
+
+class User
+ def initialize(admin = false)
+ @admin = admin
+ end
+
+ def admin?
+ @admin
+ end
+end
+
+class ArticlesController < ActionController::Base
+ attr_accessor :current_user
+ before_filter :require_admin
+
+ def create
+ @article = Article.new params
+ if @article.save
+ redirect_to '/articles/1'
+ else
+ flash[:error] = "missing: #{@article.errors.join(', ')}"
+ render :text => "can't fake a real template easily?"
+ end
+ end
+
+ protected
+
+ def require_admin
+ redirect_to '/login' unless current_user && current_user.admin?
+ end
+end
+
+ActionController::Routing.module_eval do
+ set = ActionController::Routing::RouteSet.new
+ set.draw {|map| map.articles 'articles', :controller => 'articles', :action => 'create'}
+ remove_const :Routes
+ const_set :Routes, set
+end
+
+# set up some macros
+
+class ActionController::TestCase
+ def it_redirects_to(path)
+ assert_redirected_to path
+ end
+
+ def it_assigns_flash(key, pattern)
+ assert flash[:error] =~ pattern
+ end
+end
+
+# TODO figure out how to reduce this
+module With::Dsl
+ def it_redirects_to(path)
+ assertion { assert_redirected_to path }
+ end
+end
+
+# and now the fun starts ...
+
+class ArticlesControllerTest < ActionController::TestCase
+ extend With
+
+ def valid_article_params
+ { :title => 'an article title', :body => 'an article body' }
+ end
+
+ describe 'POST to :create' do
+ action { post :create, @params }
+
+ with :login_as_admin do
+ it "succeeds", :with => :valid_article_params do
+ it_redirects_to 'articles/1'
+ end
+
+ it "fails", :with => :invalid_article_params do
+ it_assigns_flash :error, /missing: (body|title)/
+ end
+ end
+
+ with :login_as_user, :no_login do
+ it_redirects_to '/login'
+ end
+
+ # TODO figure out how to move these out of here ...
+ share :login_as_admin do
+ before { @controller.current_user = User.new(true) }
+ end
+
+ share :valid_article_params do
+ before { @params = valid_article_params }
+ end
+
+ # TODO figure out how to reduce this syntax
+ share :invalid_article_params,
+ lambda { before { @params = valid_article_params.except(:title) } },
+ lambda { before { @params = valid_article_params.except(:body) } }
+ end
+
+ puts "tests defined: \n " + instance_methods.grep(/^test_/).join(", \n ")
+end
36 test/testunit_with_test.rb
@@ -0,0 +1,36 @@
+$:.unshift File.dirname(__FILE__) + '/../lib/'
+require 'with'
+
+class TestUnitWithTest < Test::Unit::TestCase
+ extend With
+
+ describe 'foo' do
+ action { :'called action!' }
+
+ with :'context 1', :'context 2' do
+ it :'assertion 1', :with => :'context 3' do
+ :'called assertion 1'
+ end
+ end
+
+ share :'context 1' do
+ before :'precondition 1' do
+ :'called precondition 1'
+ end
+ end
+
+ share :'context 2' do
+ before :'precondition 2' do
+ :'called precondition 2'
+ end
+ end
+ end
+
+ @@tests_defined = instance_methods.grep(/^test_/)
+
+ def test_with_defined_two_tests
+ names = [ "test_foo_with_context_1_and_with_context_3",
+ "test_foo_with_context_2_and_with_context_3" ]
+ assert_equal names, @@tests_defined
+ end
+end
241 test/with_test.rb
@@ -0,0 +1,241 @@
+$:.unshift File.dirname(__FILE__) + '/../lib/'
+require 'with'
+
+# when expanded it sets up the following context tree:
+#
+# context 1.1
+# shared assertion 1.1.1
+# shared assertion 1.1.2
+# context 2
+# shared assertion 2
+# assertion 2.3
+# context 3.1
+# shared assertion 3.1
+# assertion 2.1
+# context 3.2
+# shared assertion 3.2
+# assertion 2.1
+# context 4
+# shared assertion 4.1
+# assertion 2.2
+# context 4
+# shared assertion 4.2
+# assertion 2.2
+# context 1.2
+# shared assertion 1.2
+# context 2
+# shared assertion 2
+# assertion 2.3
+# context 3.1
+# shared assertion 3.1
+# assertion 2.1
+# context 3.2
+# shared assertion 3.2
+# assertion 2.1
+# context 4
+# shared assertion 4.1
+# assertion 2.2
+# context 4
+# shared assertion 4.2
+# assertion 2.2
+
+class WithTest < Test::Unit::TestCase
+ include With
+
+ def setup
+ @group = describe 'main' do
+ action { :'called action!' }
+
+ with :'context 1.1', :'context 1.2' do
+ with :'context 2' do
+ it :'assertion 2.1', :with => [:'context 3.1', :'context 3.2'] do
+ :'called assertion 2.1'
+ end
+
+ it :'assertion 2.2', :with => :'context 4' do
+ :'called assertion 2.2'
+ end
+
+ it :'assertion 2.3' do
+ :'called assertion 2.3'
+ end
+ end
+ end
+
+ share :'context 1.1' do
+ before :'precondition 1.1' do
+ :'called precondition 1.1'
+ end
+
+ it :'shared assertion 1.1.1' do
+ :'called shared assertion 1.1.1'
+ end
+
+ it :'shared assertion 1.1.2' do
+ :'called shared assertion 1.1.2'
+ end
+ end
+
+ share :'context 1.2' do
+ before :'precondition 1.2' do
+ :'called precondition 1.2'
+ end
+
+ it :'shared assertion 1.2' do
+ :'called shared assertion 1.2'
+ end
+ end
+
+ share :'context 2' do
+ before :'precondition 2' do
+ :'called precondition 2'
+ end
+
+ it :'shared assertion 2' do
+ :'called shared assertion 2'
+ end
+ end
+
+ share :'context 3.1' do
+ before :'precondition 3.1' do
+ :'called precondition 3.1'
+ end
+
+ it :'shared assertion 3.1' do
+ :'called shared assertion 3.1'
+ end
+ end
+
+ share :'context 3.2' do
+ before :'precondition 3.2' do
+ :'called precondition 3.2'
+ end
+
+ it :'shared assertion 3.2' do
+ :'called shared assertion 3.2'
+ end
+ end
+
+ share :'context 4',
+ lambda {
+ before :'precondition 4.1' do
+ :'called precondition 4.1'
+ end
+
+ it :'shared assertion 4.1' do
+ :'called shared assertion 4.1'
+ end
+ },
+ lambda {
+ before :'precondition 4.2' do
+ :'called precondition 4.2'
+ end
+
+ it :'shared assertion 4.2' do
+ :'called shared assertion 4.2'
+ end
+ }
+ end
+ end
+
+ def test_context_group_tree
+ assert_equal ['main'], @group.names
+ assert_equal [:'context 1.1', :'context 1.2'], @group.children[0].names
+ assert_equal [:'context 2'], @group.children[0].children[0].names
+ assert_equal [:'context 3.1', :'context 3.2'], @group.children[0].children[0].children[0].names
+ assert_equal [:'context 4'], @group.children[0].children[0].children[1].names
+ assert_equal :'assertion 2.1', @group.children[0].children[0].children[0].assertions.first.name
+ assert_equal :'assertion 2.2', @group.children[0].children[0].children[1].assertions.first.name
+ assert_equal :'assertion 2.3', @group.children[0].children[0].assertions.first.name
+ end
+
+ def test_context_group_exapand
+ root = @group.expand.first
+
+ expected = [:'context 1.1', :'context 1.2']
+ assert_equal expected, root.children.map {|child| child.name }
+
+ expected = [:'context 2']
+ assert_equal expected, root.children[0].children.map {|child| child.name }
+
+ expected = [:"context 3.1", :"context 3.2", :"context 4", :"context 4"]
+ assert_equal expected, root.children[0].children[0].children.map {|child| child.name }
+
+ expected = [:'context 2']
+ assert_equal expected, root.children[1].children.map {|child| child.name }
+
+ expected = [:"context 3.1", :"context 3.2", :"context 4", :"context 4"]
+ assert_equal expected, root.children[1].children[0].children.map {|child| child.name }
+ end
+
+ def test_context_group_exapanded_collect_assertations
+ root = @group.expand.first
+
+ expected = [:'shared assertion 1.1.1', :'shared assertion 1.1.2']
+ assert_equal expected, root.children[0].collect_assertions.map {|a| a.name }
+
+ expected = [:"shared assertion 1.1.1", :"shared assertion 1.1.2",
+ :"shared assertion 2", :"assertion 2.3"]
+ assert_equal expected, root.children[0].children[0].collect_assertions.map {|a| a.name }
+
+ expected = [:"shared assertion 1.1.1", :"shared assertion 1.1.2",
+ :"shared assertion 2", :"assertion 2.3",
+ :"shared assertion 3.1", :"assertion 2.1"]
+ assert_equal expected, root.children[0].children[0].children[0].collect_assertions.map {|a| a.name }
+
+ expected = [:"shared assertion 1.1.1", :"shared assertion 1.1.2",
+ :"shared assertion 2", :"assertion 2.3",
+ :"shared assertion 3.2", :"assertion 2.1"]
+ assert_equal expected, root.children[0].children[0].children[1].collect_assertions.map {|a| a.name }
+
+ expected = [:"shared assertion 1.1.1", :"shared assertion 1.1.2",
+ :"shared assertion 2", :"assertion 2.3",
+ :"shared assertion 4.1", :"assertion 2.2"]
+ assert_equal expected, root.children[0].children[0].children[2].collect_assertions.map {|a| a.name }
+
+ expected = [:"shared assertion 1.2", :"shared assertion 2",
+ :"assertion 2.3", :"shared assertion 4.1",
+ :"assertion 2.2"]
+ assert_equal expected, root.children[1].children[0].children[2].collect_assertions.map {|a| a.name }
+
+ expected = [:"shared assertion 1.2", :"shared assertion 2",
+ :"assertion 2.3", :"shared assertion 4.2",
+ :"assertion 2.2"]
+ assert_equal expected, root.children[1].children[0].children[3].collect_assertions.map {|a| a.name }
+ end
+
+ def test_leafs
+ root = @group.expand.first
+ expected = [ :"context 3.1", :"context 3.2", :"context 4", :"context 4",
+ :"context 3.1", :"context 3.2", :"context 4", :"context 4"]
+ assert_equal expected, root.leafs.map {|c| c.name }
+ end
+
+ def test_compile
+ root = @group.expand.first
+ method = root.leafs.first.compile
+ expected = [ "precondition 1.1",
+ "precondition 2",
+ "precondition 3.1",
+ "action",
+ "shared assertion 1.1.1",
+ "shared assertion 1.1.2",
+ "shared assertion 2",
+ "assertion 2.3",
+ "shared assertion 3.1",
+ "assertion 2.1" ]
+ assert_equal expected, method.call.map {|result| result.to_s.gsub(/ #<.*>/, '') }
+
+ method = root.leafs.last.compile
+ expected = [ "precondition 1.2",
+ "precondition 2",
+ "precondition 4.2",
+ "action",
+ "shared assertion 1.2",
+ "shared assertion 2",
+ "assertion 2.3",
+ "shared assertion 4.2",
+ "assertion 2.2" ]
+ assert_equal expected, method.call.map {|result| result.to_s.gsub(/ #<.*>/, '') }
+ end
+end

0 comments on commit 03bd64a

Please sign in to comment.