approvals 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/License.txt +22 -0
- data/README.md +133 -0
- data/Rakefile +1 -0
- data/TODO +2 -0
- data/approvals.gemspec +23 -0
- data/bin/approvals +9 -0
- data/lib/approvals.rb +24 -0
- data/lib/approvals/approval.rb +97 -0
- data/lib/approvals/configuration.rb +24 -0
- data/lib/approvals/dsl.rb +7 -0
- data/lib/approvals/error.rb +5 -0
- data/lib/approvals/extensions/rspec.rb +11 -0
- data/lib/approvals/extensions/rspec/dsl.rb +9 -0
- data/lib/approvals/extensions/rspec/example.rb +17 -0
- data/lib/approvals/extensions/rspec/example_group.rb +15 -0
- data/lib/approvals/namers/default_namer.rb +20 -0
- data/lib/approvals/namers/rspec_namer.rb +27 -0
- data/lib/approvals/reporters.rb +3 -0
- data/lib/approvals/reporters/first_working_reporter.rb +21 -0
- data/lib/approvals/reporters/image_reporter.rb +16 -0
- data/lib/approvals/reporters/image_reporter/html_image_reporter.rb +35 -0
- data/lib/approvals/reporters/image_reporter/image_magick_reporter.rb +20 -0
- data/lib/approvals/utilities.rb +4 -0
- data/lib/approvals/utilities/cli.rb +28 -0
- data/lib/approvals/utilities/dotfile.rb +38 -0
- data/lib/approvals/utilities/executable.rb +14 -0
- data/lib/approvals/utilities/scrubber.rb +43 -0
- data/lib/approvals/utilities/system_command.rb +13 -0
- data/lib/approvals/writer.rb +26 -0
- data/lib/approvals/writers.rb +5 -0
- data/lib/approvals/writers/array_writer.rb +15 -0
- data/lib/approvals/writers/html_writer.rb +15 -0
- data/lib/approvals/writers/json_writer.rb +17 -0
- data/lib/approvals/writers/text_writer.rb +22 -0
- data/lib/approvals/writers/xml_writer.rb +15 -0
- data/spec/approvals_spec.rb +64 -0
- data/spec/configuration_spec.rb +27 -0
- data/spec/extensions/rspec_approvals_spec.rb +49 -0
- data/spec/fixtures/approvals/approvals_verifies_a_complex_object.approved.txt +1 -0
- data/spec/fixtures/approvals/approvals_verifies_a_string.approved.txt +1 -0
- data/spec/fixtures/approvals/approvals_verifies_an_array.approved.txt +4 -0
- data/spec/fixtures/approvals/approvals_verifies_an_executable.approved.txt +1 -0
- data/spec/fixtures/approvals/approvals_verifies_html.approved.html +11 -0
- data/spec/fixtures/approvals/approvals_verifies_json.approved.json +7 -0
- data/spec/fixtures/approvals/approvals_verifies_xml.approved.xml +9 -0
- data/spec/fixtures/approvals/verifications_a_string.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_a_complex_object.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_a_string.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_an_array.approved.txt +4 -0
- data/spec/fixtures/approvals/verifies_an_executable.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_html.approved.html +11 -0
- data/spec/fixtures/approvals/verifies_json.approved.json +7 -0
- data/spec/fixtures/approvals/verifies_xml.approved.xml +9 -0
- data/spec/fixtures/one.png +0 -0
- data/spec/fixtures/two.png +0 -0
- data/spec/namers/default_namer_spec.rb +35 -0
- data/spec/namers/rspec_namer_spec.rb +31 -0
- data/spec/namers_spec.rb +16 -0
- data/spec/reporters/first_working_reporter_spec.rb +29 -0
- data/spec/reporters/html_image_reporter_spec.rb +22 -0
- data/spec/reporters/image_magick_reporter_spec.rb +16 -0
- data/spec/utilities/dotfile_spec.rb +22 -0
- data/spec/utilities/executable_spec.rb +15 -0
- data/spec/utilities/scrubber_spec.rb +24 -0
- data/spec/utilities/system_command_spec.rb +13 -0
- metadata +147 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
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
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,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,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)
|