Skip to content
Browse files

Moving existing code from plugin here. Add some specs, and more pendi…

…ngs.
  • Loading branch information...
1 parent 29a1dea commit 05e9d941910431c5a933258113967e752634361b @MGPalmer MGPalmer committed Jan 28, 2011
Showing with 354 additions and 7 deletions.
  1. +2 −5 Gemfile
  2. +30 −0 Gemfile.lock
  3. +94 −0 lib/schemer.rb
  4. +24 −0 lib/schemer/define_rules.rb
  5. +36 −0 lib/schemer/document.rb
  6. +99 −0 lib/schemer/rule.rb
  7. +9 −0 lib/unit_test_extensions.rb
  8. +60 −2 spec/schemer_spec.rb
View
7 Gemfile
@@ -1,10 +1,7 @@
source "http://rubygems.org"
-# Add dependencies required to use your gem here.
-# Example:
-# gem "activesupport", ">= 2.3.5"
-# Add dependencies to develop your gem here.
-# Include everything needed to run rake, tests, features, etc.
+gem "activesupport", '~>2.3.5'
+
group :development do
gem "rspec", "~> 2.3.0"
gem "bundler", "~> 1.0.0"
View
30 Gemfile.lock
@@ -0,0 +1,30 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ activesupport (2.3.10)
+ diff-lcs (1.1.2)
+ git (1.2.5)
+ jeweler (1.5.2)
+ bundler (~> 1.0.0)
+ git (>= 1.2.5)
+ rake
+ rake (0.8.7)
+ rcov (0.9.9)
+ rspec (2.3.0)
+ rspec-core (~> 2.3.0)
+ rspec-expectations (~> 2.3.0)
+ rspec-mocks (~> 2.3.0)
+ rspec-core (2.3.1)
+ rspec-expectations (2.3.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.3.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ activesupport (~> 2.3.5)
+ bundler (~> 1.0.0)
+ jeweler (~> 1.5.2)
+ rcov
+ rspec (~> 2.3.0)
View
94 lib/schemer.rb
@@ -0,0 +1,94 @@
+require 'active_support'
+require 'active_support/version'
+if ActiveSupport::VERSION::STRING >= "3.0.0"
+ require 'active_support/core_ext'
+end
+require 'test/unit/assertions'
+require 'schemer'
+require 'schemer/rule'
+require 'schemer/document'
+require 'schemer/define_rules'
+require 'unit_test_extensions'
+module Schemer
+
+ class FailedError < Test::Unit::AssertionFailedError
+ attr_accessor :printed
+ end
+
+ class << self
+
+ def compare(options = {:scheme => {}, :to => {}})
+ scheme = options[:scheme]
+ testee = options[:to]
+ raise ArgumentError, ":to needs to be a hash !" unless testee.is_a?(Hash)
+ raise ArgumentError, ":scheme needs to be a Schemer::Document !" unless scheme.is_a?(Document)
+ compare_hash(scheme, testee)
+ true
+ end
+
+ private
+
+ def compare_value(template, value, path)
+ if template.is_a?(Rule)
+ template.schemer_compare(value, path)
+ else
+ Rule.check_class_equality!(template, value, path)
+ end
+ end
+
+ def compare_hash(template, testee, trail = ["root"])
+ template.stringify_keys!
+ testee.stringify_keys!
+ raise FailedError, "keys don't match in #{format_path(trail)}:\nexpected:\t#{template.keys.sort.join(',')}\nreceived:\t#{testee.keys.sort.join(',')}" if template.keys.sort != testee.keys.sort
+ template.each do |t_key, t_value|
+ current_path = trail + [t_key]
+ value = testee[t_key]
+ compare_value(t_value, value, format_path(current_path))
+ if value && t_value.is_a?(Array)
+ # all array elements need to be of the same type as the first value in the template
+ elements_template = t_value.first
+ value.each_with_index do |elements_value, index|
+ array_path = current_path + [index]
+ compare_value(elements_template, elements_value, format_path(array_path))
+ if elements_value.is_a?(Hash)
+ compare_hash(elements_template, elements_value, array_path)
+ end
+ end
+ end
+ if value.is_a?(Array) && t_value.is_a?(Rule) && t_value.options['compare_each']
+ value.each_with_index do |elements_value, index|
+ elements_template = t_value.example_value[index]
+ array_path = current_path + [index]
+ compare_value(elements_template, elements_value, format_path(array_path))
+ if elements_value.is_a?(Hash)
+ compare_hash(elements_template, elements_value, array_path)
+ end
+ end
+ end
+ if value.is_a?(Hash)
+ if t_value.is_a?(Schemer::Rule)
+ compare_value(t_value, value, current_path)
+ compare_hash(t_value.example_value, value, current_path)
+ else
+ compare_hash(t_value, value, current_path)
+ end
+ end
+ end
+
+ rescue Schemer::FailedError => e
+ raise e if e.printed
+
+ error = Schemer::FailedError.new
+ error.printed = true
+
+ expected = PP.pp(template, '')
+ outcome = PP.pp(testee, '')
+
+ raise error, "#{e.message}\n\nExpected:\n#{expected}\n\nbut was:\n#{outcome}", caller
+ end
+
+ def format_path(trail)
+ "'" + trail.join("'=>'") + "'"
+ end
+ end
+end
View
24 lib/schemer/define_rules.rb
@@ -0,0 +1,24 @@
+module Schemer
+
+ module DefineRules
+
+ # This turns the supplied +example_value+ (any object) into an object that carries rules about itself with it.
+ # The rules will be applied when a template is compared with assert_schemer. Rules are:
+ # (default): This always applies - the value must be of the same class as the +example_value+
+ # 'allow_nil': This allows the value to be nil (breaking the first rule)
+ # 'included_in': Pass an array of values - the value must be one of these
+ # 'matches': Pass a regexp - the value must match it, and be a String
+ def rule(example_value, options = {})
+ Rule.new(example_value, options)
+ end
+
+ #for iterating over each example in an array intead of using only the first to compare the data array with
+ def iterating_rule(example_value, options = {})
+ if example_value
+ Rule.new(example_value, :compare_each => true)
+ end
+ end
+
+ end
+
+end
View
36 lib/schemer/document.rb
@@ -0,0 +1,36 @@
+module Schemer
+ class Document < Hash
+
+ def initialize(hash = {})
+ hash.each do |k, v|
+ self[k] = v
+ end
+ end
+
+ # Turns a Schemer::Document into a plain Hash - this removes all special
+ # objects like Schemer::Rule and replaces them with their example values, so
+ # the result can be used as documentation.
+ def to_hash
+ result = {}
+ each do |k, v|
+ result[k] = self.class.derulerize(v)
+ end
+ result
+ end
+
+ private
+
+ def self.derulerize(object)
+ case object
+ when Hash
+ new(object).to_hash
+ when Array
+ object.map { |e| derulerize(e) }
+ when Schemer::Rule
+ derulerize(object.example_value)
+ else
+ object
+ end
+ end
+ end
+end
View
99 lib/schemer/rule.rb
@@ -0,0 +1,99 @@
+module Schemer
+ class Rule
+
+ OPTIONS = ["allow_nil", "compare_each", "included_in", "matches"]
+
+ attr_accessor :options, :example_value
+
+ def initialize(example, options = {})
+ options.stringify_keys!
+ options['allow_nil'] ||= false
+ options['compare_each'] ||= false
+ options["included_in"] ||= false
+ options["matches"] ||= false
+ raise ArgumentError, "options can be #{OPTIONS.join(',')}, not #{options.keys.inspect}" unless options.keys.sort == OPTIONS.sort
+ self.example_value = example
+ self.options = options
+ self.schemer_compare(example, "initialization of rule")
+ end
+
+ def schemer_compare(value, path)
+# puts "#{path} #{@schemer_rule_options.inspect}"
+# puts self.inspect
+# puts value.inspect
+# puts @schemer_rule_options["included_in"].inspect
+# puts !@schemer_rule_options["included_in"].include?(value) if @schemer_rule_options["included_in"]
+ return true if schemer_check_allowed_nil!(value, path)
+ return true if schemer_check_included_in!(value, path)
+ return true if schemer_check_matches!(value, path)
+ return true if schemer_check_class_equality!(value, path)
+ end
+
+ def self.check_class_equality!(template, value, path)
+
+ value_klass = case value
+ when Document
+ Hash
+ when Rule
+ value.example_value.class
+ else
+ value.class
+ end
+ template_klass = case template
+ when Document
+ Hash
+ when Rule
+ template.example_value.class
+ else
+ template.class
+ end
+ if template_klass != value_klass
+ raise FailedError, "value at #{path} (#{value_klass}) is not a #{template_klass} !"
+ else
+ true
+ end
+ end
+
+ private
+
+ def schemer_check_class_equality!(value, path)
+ self.class.check_class_equality!(self, value, path)
+ end
+
+ def schemer_check_allowed_nil!(value, path)
+ if options['allow_nil']
+ if value.nil?
+ true
+ else
+ false
+ end
+ else
+ false
+ end
+ end
+
+ def schemer_check_included_in!(value, path)
+ if options["included_in"]
+ unless options["included_in"].include?(value)
+ raise Schemer::FailedError, "value at #{path} #{value.inspect} (#{value.class}) is not one of #{options["included_in"].inspect} !"
+ else
+ true
+ end
+ else
+ false
+ end
+ end
+
+ def schemer_check_matches!(value, path)
+ if options["matches"]
+ if value !~ options["matches"]
+ raise Schemer::FailedError, "value at #{path} #{value.inspect} (#{value.class}) does not match #{options["matches"].inspect} !"
+ else
+ true
+ end
+ else
+ false
+ end
+ end
+ end
+end
View
9 lib/unit_test_extensions.rb
@@ -0,0 +1,9 @@
+module Test
+ module Unit
+ module Assertions
+ def assert_schemer(scheme, compare_to)
+ Schemer.compare(:scheme => scheme, :to => compare_to)
+ end
+ end
+ end
+end
View
62 spec/schemer_spec.rb
@@ -1,7 +1,65 @@
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
+include Schemer::DefineRules
+
describe "Schemer" do
- it "fails" do
- fail "hey buddy, you should probably rename this file and start specing for real"
+ before(:each) do
+ @scheme = Schemer::Document.new({
+ "ship" => {
+ "parts" => [
+ {
+ "name" => "Mast",
+ "length" => rule(12.3, :allow_nil => true),
+ "material" => rule("wood", :included_in => ['wood', 'steel', 'human'])
+ },
+ {
+ "name" => "Rudder",
+ "length" => nil,
+ "material" => "steel"
+ }
+ ]
+ }
+ })
+ end
+
+ describe "Testing with #compare" do
+
+ it "returns true for a valid document, treating symbols and strings alike" do
+ Schemer.compare(
+ :scheme => @scheme,
+ :to => {
+ :ship => {
+ :parts => [
+ :name => "Thingy",
+ :length => 1.0,
+ :material => "human"
+ ]
+ }
+ }
+ ).should be_true
+ end
+ it "complains if a key is missing" do
+ lambda do
+ Schemer.compare(
+ :scheme => @scheme,
+ :to => {
+ :tank => {}
+ }
+ )
+ end.should raise_error(Schemer::FailedError, /expected: ship*\n*received: tank/)
+ end
+ it "complains if not given a Schemer::Document"
+ it "complains if there are extra keys"
+ it "complains if a value is of the wrong class"
+ it "complains if a value is nil"
+ it "does not complain if a value is nil and the rule allows nil"
+ it "complains if a value does not match the regexp rule"
+ it "complains if a value is not included in the rule list"
+ it "checks all values of value arrays, but only against the first array value of the scheme"
+ it "checks all array values one-to-one if the compare_each rule is used"
end
+
+ describe "Converting into plain example hashes"
+ it "checks that the examples of rules obey the rules"
+ it "has a unit test extension method"
end

0 comments on commit 05e9d94

Please sign in to comment.
Something went wrong with that request. Please try again.