approvals 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +4 -0
  3. data/Gemfile +4 -0
  4. data/License.txt +22 -0
  5. data/README.md +133 -0
  6. data/Rakefile +1 -0
  7. data/TODO +2 -0
  8. data/approvals.gemspec +23 -0
  9. data/bin/approvals +9 -0
  10. data/lib/approvals.rb +24 -0
  11. data/lib/approvals/approval.rb +97 -0
  12. data/lib/approvals/configuration.rb +24 -0
  13. data/lib/approvals/dsl.rb +7 -0
  14. data/lib/approvals/error.rb +5 -0
  15. data/lib/approvals/extensions/rspec.rb +11 -0
  16. data/lib/approvals/extensions/rspec/dsl.rb +9 -0
  17. data/lib/approvals/extensions/rspec/example.rb +17 -0
  18. data/lib/approvals/extensions/rspec/example_group.rb +15 -0
  19. data/lib/approvals/namers/default_namer.rb +20 -0
  20. data/lib/approvals/namers/rspec_namer.rb +27 -0
  21. data/lib/approvals/reporters.rb +3 -0
  22. data/lib/approvals/reporters/first_working_reporter.rb +21 -0
  23. data/lib/approvals/reporters/image_reporter.rb +16 -0
  24. data/lib/approvals/reporters/image_reporter/html_image_reporter.rb +35 -0
  25. data/lib/approvals/reporters/image_reporter/image_magick_reporter.rb +20 -0
  26. data/lib/approvals/utilities.rb +4 -0
  27. data/lib/approvals/utilities/cli.rb +28 -0
  28. data/lib/approvals/utilities/dotfile.rb +38 -0
  29. data/lib/approvals/utilities/executable.rb +14 -0
  30. data/lib/approvals/utilities/scrubber.rb +43 -0
  31. data/lib/approvals/utilities/system_command.rb +13 -0
  32. data/lib/approvals/writer.rb +26 -0
  33. data/lib/approvals/writers.rb +5 -0
  34. data/lib/approvals/writers/array_writer.rb +15 -0
  35. data/lib/approvals/writers/html_writer.rb +15 -0
  36. data/lib/approvals/writers/json_writer.rb +17 -0
  37. data/lib/approvals/writers/text_writer.rb +22 -0
  38. data/lib/approvals/writers/xml_writer.rb +15 -0
  39. data/spec/approvals_spec.rb +64 -0
  40. data/spec/configuration_spec.rb +27 -0
  41. data/spec/extensions/rspec_approvals_spec.rb +49 -0
  42. data/spec/fixtures/approvals/approvals_verifies_a_complex_object.approved.txt +1 -0
  43. data/spec/fixtures/approvals/approvals_verifies_a_string.approved.txt +1 -0
  44. data/spec/fixtures/approvals/approvals_verifies_an_array.approved.txt +4 -0
  45. data/spec/fixtures/approvals/approvals_verifies_an_executable.approved.txt +1 -0
  46. data/spec/fixtures/approvals/approvals_verifies_html.approved.html +11 -0
  47. data/spec/fixtures/approvals/approvals_verifies_json.approved.json +7 -0
  48. data/spec/fixtures/approvals/approvals_verifies_xml.approved.xml +9 -0
  49. data/spec/fixtures/approvals/verifications_a_string.approved.txt +1 -0
  50. data/spec/fixtures/approvals/verifies_a_complex_object.approved.txt +1 -0
  51. data/spec/fixtures/approvals/verifies_a_string.approved.txt +1 -0
  52. data/spec/fixtures/approvals/verifies_an_array.approved.txt +4 -0
  53. data/spec/fixtures/approvals/verifies_an_executable.approved.txt +1 -0
  54. data/spec/fixtures/approvals/verifies_html.approved.html +11 -0
  55. data/spec/fixtures/approvals/verifies_json.approved.json +7 -0
  56. data/spec/fixtures/approvals/verifies_xml.approved.xml +9 -0
  57. data/spec/fixtures/one.png +0 -0
  58. data/spec/fixtures/two.png +0 -0
  59. data/spec/namers/default_namer_spec.rb +35 -0
  60. data/spec/namers/rspec_namer_spec.rb +31 -0
  61. data/spec/namers_spec.rb +16 -0
  62. data/spec/reporters/first_working_reporter_spec.rb +29 -0
  63. data/spec/reporters/html_image_reporter_spec.rb +22 -0
  64. data/spec/reporters/image_magick_reporter_spec.rb +16 -0
  65. data/spec/utilities/dotfile_spec.rb +22 -0
  66. data/spec/utilities/executable_spec.rb +15 -0
  67. data/spec/utilities/scrubber_spec.rb +24 -0
  68. data/spec/utilities/system_command_spec.rb +13 -0
  69. metadata +147 -0
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.received.txt
6
+ .approvals
7
+ tmp/
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ script: bundle exec rspec spec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rspec-approvals.gemspec
4
+ gemspec
data/License.txt ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2011 Katrina Owen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # Approvals
2
+
3
+ Approvals are based on the idea of the *_golden master_*.
4
+
5
+ You take a snapshot of an object, and then compare all future
6
+ versions of the object to the snapshot.
7
+
8
+ Big hat tip to Llewellyn Falco who developed the approvals concept, as
9
+ well as the original approvals libraries (.NET, Java, Ruby, PHP,
10
+ probably others).
11
+
12
+ See [ApprovalTests](http://www.approvaltests.com) for videos and additional documentation about the general concept.
13
+
14
+ Also, check out Herding Code's [podcast #117](http://t.co/GLn88R5) in
15
+ which Llewellyn Falco is interviewed about approvals.
16
+
17
+ [![Build Status](https://secure.travis-ci.org/kytrinyx/rspec-approvals.png?branch=master)](http://travis-ci.org/kytrinyx/rspec-approvals)
18
+
19
+ ## Configuration
20
+
21
+ Approvals.configure do |c|
22
+ c.approvals_path = 'output/goes/here/'
23
+ end
24
+
25
+ The default location for the output files is
26
+
27
+ approvals/
28
+
29
+ ## Usage
30
+
31
+ Approvals.verify(your_subject, :format => :json)
32
+
33
+ This will raise an `ApprovalError` in the case of a failure.
34
+
35
+ The `:inspect` method on the subject will be used to generate the output for
36
+ the `received` file. For custom complex objects you will need to override
37
+ `:to_s` or `:inspect` to get helpful output, rather than the default:
38
+
39
+ #<Object:0x0000010105ea40> # or whatever the object id is
40
+
41
+ Note: `:inspect` uses `:to_s` unless it has been specifically defined to do something else.
42
+
43
+ The first time the approval is run, a file will be created with the contents of the subject of your approval:
44
+
45
+ the_name_of_the_approval.received.txt # or .json, .html, .xml as appropriate
46
+
47
+ Since you have not yet approved anything, the `*.approved` file does not exist, and the comparison will fail.
48
+
49
+ ### RSpec
50
+
51
+ For the moment the only direct integration is with RSpec.
52
+
53
+ The default directory for output files when using RSpec is
54
+
55
+ spec/fixtures/approvals/
56
+
57
+ You can override this:
58
+
59
+ RSpec.configure do |c|
60
+ c.approvals_path = 'some/other/path'
61
+ end
62
+
63
+ The basic format of the approval is modeled after RSpec's `it`:
64
+
65
+ verify "something" do
66
+ "this is the the thing you want to verify"
67
+ end
68
+
69
+ ### Naming
70
+
71
+ Currently there is only an RSpecNamer that takes an example (or any object that responds to `:full_description`).
72
+
73
+ namer = Approvals::Namers::RSpecNamer.new(example)
74
+
75
+ Approvals.verify(thing, :namer => namer, :format => :html)
76
+
77
+ When using RSpec, the namer is set for you.
78
+
79
+ ### Formatting
80
+
81
+ You can pass a format for your output before it gets written to the file.
82
+ At the moment, only xml, html, and json are supported.
83
+
84
+ Simply add a `:format => :xml`, `:format => :html`, or `:format => :json` option to the example:
85
+
86
+ verify "some html", :format => :html do
87
+ "<html><head></head><body><h1>ZOMG</h1></body></html>"
88
+ end
89
+
90
+ verify "some json", :format => :json do
91
+ "{\"beverage\":\"coffee\"}"
92
+ end
93
+
94
+ ### Approving a spec
95
+
96
+ If the contents of the received file is to your liking, you can approve
97
+ the file by renaming it.
98
+
99
+ For an example who's full description is `My Spec`:
100
+
101
+ mv my_spec.received.txt my_spec.approved.txt
102
+
103
+ When you rerun the approval, it should now pass.
104
+
105
+ ### Expensive computations
106
+
107
+ The Executable class allows you to perform expensive operations only when the command to execute it changes.
108
+
109
+ For example, if you have a SQL query that is very slow, you can create an executable with the actual SQL to be performed.
110
+
111
+ The first time the spec runs, it will fail, allowing you to inspect the results.
112
+ If this output looks right, approve the query. The next time the spec is run, it will compare only the actual SQL.
113
+
114
+ If someone changes the query, then the comparison will fail. Both the previously approved command and the received command will be executed so that you can inspect the difference between the results of the two.
115
+
116
+ executable = Approvals::Executable.new(subject.slow_sql) do |output|
117
+ # do something on failure
118
+ end
119
+
120
+ Approvals.verify(executable, :options => :here)
121
+
122
+ ### RSpec executable
123
+
124
+ There is a convenience wrapper for RSpec that looks like so:
125
+
126
+ verify "an executable" do
127
+ executable(subject.slow_sql) do |command|
128
+ result = ActiveRecord::Base.connection.execute(command)
129
+ # do something to display the result
130
+ end
131
+ end
132
+
133
+ Copyright (c) 2011 Katrina Owen, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ [ ] add default namer
2
+ Approvals.verify(something, :name => 'the name of this test')
data/approvals.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "approvals"
6
+ s.version = "0.0.1"
7
+ s.authors = ["Katrina Owen"]
8
+ s.email = ["katrina.owen@gmail.com"]
9
+ s.homepage = ""
10
+ s.summary = %q{Approval Tests for Ruby}
11
+ s.description = %q{Approval Tests for Ruby}
12
+
13
+ s.rubyforge_project = "approvals"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency 'rspec', '~> 2.7'
21
+ s.add_dependency 'thor'
22
+ s.add_dependency 'nokogiri'
23
+ end
data/bin/approvals ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'thor'
5
+ require 'approvals'
6
+ require 'approvals/utilities/cli'
7
+
8
+ Approvals::CLI.start
9
+
data/lib/approvals.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'nokogiri'
2
+ require 'approvals/configuration'
3
+ require 'approvals/approval'
4
+ require 'approvals/dsl'
5
+ require 'approvals/error'
6
+ require 'approvals/utilities'
7
+ require 'approvals/reporters'
8
+ require 'approvals/writer'
9
+ require 'approvals/namers/default_namer'
10
+
11
+ require 'approvals/extensions/rspec'
12
+
13
+
14
+ module Approvals
15
+ extend DSL
16
+
17
+ class << self
18
+ def reset
19
+ Dotfile.reset
20
+ end
21
+ end
22
+ end
23
+
24
+ Approvals.reset
@@ -0,0 +1,97 @@
1
+ module Approvals
2
+ class Approval
3
+ class << self
4
+ attr_accessor :namer
5
+ end
6
+
7
+ attr_reader :subject, :namer, :failure
8
+ def initialize(subject, options = {})
9
+ @subject = subject
10
+ @namer = options[:namer] || default_namer(options[:name])
11
+ @format = options[:format] || identify_format
12
+ end
13
+
14
+ def default_namer(name)
15
+ Namers::DefaultNamer.new(name)
16
+ end
17
+
18
+ def identify_format
19
+ return :hash if subject.respond_to? :each_pair
20
+ return :array if subject.respond_to? :each_with_index
21
+ return :txt
22
+ end
23
+
24
+ def writer
25
+ @writer ||= Writer.for(@format)
26
+ end
27
+
28
+ def verify
29
+ unless File.exists?(namer.output_dir)
30
+ FileUtils.mkdir_p(namer.output_dir)
31
+ end
32
+
33
+ writer.write(subject, received_path)
34
+
35
+ unless approved?
36
+ fail_with "Approval file \"#{approved_path}\" not found."
37
+ end
38
+
39
+ unless received_matches?
40
+ fail_with "Received file \"#{received_path}\" does not match approved \"#{approved_path}\"."
41
+ end
42
+
43
+ success!
44
+ end
45
+
46
+ def success!
47
+ File.delete received_path
48
+ end
49
+
50
+ def approved?
51
+ File.exists? approved_path
52
+ end
53
+
54
+ def received_matches?
55
+ FileUtils.cmp received_path, approved_path
56
+ end
57
+
58
+ def fail_with(message)
59
+ if subject.respond_to?(:on_failure)
60
+ subject.on_failure.call(approved_text) if approved?
61
+ subject.on_failure.call(received_text)
62
+ end
63
+
64
+ Dotfile.append(diff_path)
65
+
66
+ raise ApprovalError.new("Approval Error: #{message}")
67
+ end
68
+
69
+ def diff_path
70
+ "#{received_path} #{approved_path}"
71
+ end
72
+
73
+ def full_path(state)
74
+ "#{namer.output_dir}#{namer.name}.#{state}.#{writer.extension}"
75
+ end
76
+
77
+ def name
78
+ namer.name
79
+ end
80
+
81
+ def approved_path
82
+ full_path('approved')
83
+ end
84
+
85
+ def received_path
86
+ full_path('received')
87
+ end
88
+
89
+ def approved_text
90
+ File.read approved_path
91
+ end
92
+
93
+ def received_text
94
+ File.read received_path
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,24 @@
1
+ require 'singleton'
2
+
3
+ module Approvals
4
+
5
+ class << self
6
+ def configure(&block)
7
+ block.call Approvals::Configuration.instance
8
+ end
9
+
10
+ def configuration
11
+ Approvals::Configuration.instance
12
+ end
13
+ end
14
+
15
+ class Configuration
16
+ include Singleton
17
+
18
+ attr_writer :approvals_path
19
+
20
+ def approvals_path
21
+ @approvals_path ||= 'approvals/'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ module Approvals
2
+ module DSL
3
+ def verify(object, options = {})
4
+ Approval.new(object, options).verify
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Approvals
2
+ class ApprovalError < Exception
3
+ attr_accessor :received_path, :approved_path
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ if defined? RSpec
2
+ require 'approvals/extensions/rspec/dsl'
3
+ require 'approvals/extensions/rspec/example_group'
4
+ require 'approvals/extensions/rspec/example'
5
+ require 'approvals/namers/rspec_namer'
6
+
7
+ RSpec.configure do |c|
8
+ c.extend Approvals::RSpec::DSL
9
+ c.add_setting :approvals_path, :default => 'spec/fixtures/approvals/'
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Approvals
2
+ module RSpec
3
+ module DSL
4
+ def executable(command, &block)
5
+ Approvals::Executable.new(command, &block)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ module Approvals
2
+ module RSpec
3
+ class Example < ::RSpec::Core::Example
4
+
5
+ attr_reader :received
6
+ def initialize(example_group, description, options, &block)
7
+ super(example_group, description, options, block)
8
+
9
+ namer = Approvals::Namers::RSpecNamer.new(self)
10
+ approval = Approval.new(@example_block.call, options.merge(:namer => namer))
11
+ @example_block = Proc.new { approval.verify }
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+
@@ -0,0 +1,15 @@
1
+ module Approvals
2
+ module RSpec
3
+ module ExampleGroup
4
+
5
+ def verify(desc=nil, *args, &block)
6
+ options = build_metadata_hash_from(args)
7
+ examples << Approvals::RSpec::Example.new(self, desc, options, &block)
8
+ examples.last
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+
15
+ RSpec::Core::ExampleGroup.send(:extend, Approvals::RSpec::ExampleGroup)