hot-or-not 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +30 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/bin/hot-or-not +47 -0
- data/lib/hot-or-not.rb +8 -0
- data/lib/hot-or-not/announcer.rb +18 -0
- data/lib/hot-or-not/announcers/console_announcer.rb +72 -0
- data/lib/hot-or-not/compare_url.rb +43 -0
- data/lib/hot-or-not/comparison_result.rb +70 -0
- data/lib/hot-or-not/runner.rb +28 -0
- data/test/data/simple_urls.yml +9 -0
- data/test/data/simple_urls.yml.erb +7 -0
- data/test/helper.rb +46 -0
- data/test/test_compare_url.rb +71 -0
- data/test/test_comparison_result.rb +53 -0
- data/test/test_console_announcer.rb +67 -0
- data/test/test_runner.rb +102 -0
- metadata +209 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "shoulda", ">= 0"
|
10
|
+
gem "bundler", "~> 1.0.0"
|
11
|
+
gem "jeweler", "~> 1.6.0"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
gem "mocha"
|
14
|
+
end
|
15
|
+
|
16
|
+
gem 'rest-client', '~> 1.6.1'
|
17
|
+
gem 'diffy', '~> 2.0.1'
|
18
|
+
gem 'facets', '~> 2.9.1'
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diffy (2.0.1)
|
5
|
+
facets (2.9.1)
|
6
|
+
git (1.2.5)
|
7
|
+
jeweler (1.6.0)
|
8
|
+
bundler (~> 1.0.0)
|
9
|
+
git (>= 1.2.5)
|
10
|
+
rake
|
11
|
+
mime-types (1.16)
|
12
|
+
mocha (0.9.12)
|
13
|
+
rake (0.8.7)
|
14
|
+
rcov (0.9.9)
|
15
|
+
rest-client (1.6.1)
|
16
|
+
mime-types (>= 1.16)
|
17
|
+
shoulda (2.11.3)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
bundler (~> 1.0.0)
|
24
|
+
diffy (~> 2.0.1)
|
25
|
+
facets (~> 2.9.1)
|
26
|
+
jeweler (~> 1.6.0)
|
27
|
+
mocha
|
28
|
+
rcov
|
29
|
+
rest-client (~> 1.6.1)
|
30
|
+
shoulda
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Joel Friedman
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= hot-or-not
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to hot-or-not
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
+
* Fork the project
|
10
|
+
* Start a feature/bugfix branch
|
11
|
+
* Commit and push until you are happy with your contribution
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2011 Joel Friedman. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "hot-or-not"
|
18
|
+
gem.homepage = "https://github.com/joelash/hot-or-not"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{A tool for side-by-side comparsion of web apis}
|
21
|
+
gem.description = %Q{Run this with a new version of your site comparing to current version to verify no break changes are made to your api's}
|
22
|
+
gem.email = "asher.friedman@gmail.com"
|
23
|
+
gem.authors = ["Joel Friedman"]
|
24
|
+
gem.executables = ['hot-or-not']
|
25
|
+
# dependencies defined in Gemfile
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rake/testtask'
|
30
|
+
Rake::TestTask.new(:test) do |test|
|
31
|
+
test.libs << 'lib' << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
|
36
|
+
require 'rcov/rcovtask'
|
37
|
+
Rcov::RcovTask.new do |test|
|
38
|
+
test.libs << 'test'
|
39
|
+
test.pattern = 'test/**/test_*.rb'
|
40
|
+
test.verbose = true
|
41
|
+
test.rcov_opts << '--exclude "gems/*"'
|
42
|
+
end
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
require 'rake/rdoctask'
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
48
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
49
|
+
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = "hot-or-not #{version}"
|
52
|
+
rdoc.rdoc_files.include('README*')
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.3
|
data/bin/hot-or-not
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require File.dirname(__FILE__) + '/../lib/hot-or-not'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
defaults = {
|
8
|
+
:output_directory => 'test_results'
|
9
|
+
}
|
10
|
+
|
11
|
+
options = {}
|
12
|
+
parser = ::OptionParser.new do |opts|
|
13
|
+
opts.banner = "Usage: hot-or-not [options] URL_FILE"
|
14
|
+
opts.on("-o", "--output-dir", "Ouput directory. DEFAULT: #{defaults[:output_directory]}") do |d|
|
15
|
+
options[:output_directory] = d
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on_tail("--example-file", "Output contents of example file") do
|
19
|
+
puts
|
20
|
+
puts File.read(File.dirname(__FILE__) + '/../test/data/simple_urls.yml')
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
25
|
+
puts opts
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
parser.parse!
|
32
|
+
rescue OptionParser::InvalidOption => e
|
33
|
+
puts e.message
|
34
|
+
puts parser
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
options = defaults.merge options
|
39
|
+
|
40
|
+
yaml_file = ARGV[0]
|
41
|
+
unless File.exists? yaml_file
|
42
|
+
puts "hot-or-not: no such file - #{yaml_file}"
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
|
46
|
+
announcer = HotOrNot::ConsoleAnnouncer.new options[:output_directory]
|
47
|
+
HotOrNot::Runner.run_from(yaml_file, announcer).run!
|
data/lib/hot-or-not.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module HotOrNot
|
2
|
+
class ConsoleAnnouncer
|
3
|
+
include Announcer
|
4
|
+
|
5
|
+
def initialize(output_dir)
|
6
|
+
@output_dir = output_dir
|
7
|
+
@start, @results = nil, []
|
8
|
+
@output_count = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def starting
|
12
|
+
puts "Starting to compare the bodies"
|
13
|
+
@start = Time.now
|
14
|
+
end
|
15
|
+
|
16
|
+
def ending
|
17
|
+
completion_time = Time.now - @start
|
18
|
+
counts = Hash.new(0)
|
19
|
+
puts
|
20
|
+
@results.each do |result_hash|
|
21
|
+
status = result_hash[:status]
|
22
|
+
send :"output_#{status}", result_hash
|
23
|
+
counts[status] += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
puts "Finsihed in %.6f seconds." % [completion_time]
|
27
|
+
puts
|
28
|
+
puts "#{@results.count} body comparisons, #{counts[:success]} hot bodies, #{counts[:failure]} not-hot bodies, #{counts[:error]} errored bodies"
|
29
|
+
end
|
30
|
+
|
31
|
+
def announce_success(result)
|
32
|
+
@results << { :status => :success, :result => result }
|
33
|
+
print "."
|
34
|
+
end
|
35
|
+
|
36
|
+
def announce_failure(result)
|
37
|
+
@results << { :status => :failure, :result => result }
|
38
|
+
print "N"
|
39
|
+
end
|
40
|
+
|
41
|
+
def announce_error(url, error)
|
42
|
+
@results << { :status => :error, :url => url, :error => error }
|
43
|
+
print "E"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def output_success result_hash
|
48
|
+
#do nothing
|
49
|
+
end
|
50
|
+
|
51
|
+
def output_failure result_hash
|
52
|
+
result_hash[:result].output_to_files_in results_dir
|
53
|
+
to_console "Not Hot:#{$/}#{result_hash[:result].message}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def output_error result_hash
|
57
|
+
to_console "Error:#{$/}Retreiving #{result_hash[:url].url} raised error: #{result_hash[:error].message}#{$/}#{result_hash[:error].backtrace.join($/)}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_console(message)
|
61
|
+
@output_count += 1
|
62
|
+
puts " #{@output_count}) #{message}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def results_dir
|
66
|
+
@results_dir ||= @output_dir.tap do |dir|
|
67
|
+
FileUtils.rm_rf dir
|
68
|
+
FileUtils.mkdir_p dir
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module HotOrNot
|
2
|
+
class CompareUrl
|
3
|
+
|
4
|
+
attr_reader :url, :base_a, :base_b, :headers
|
5
|
+
|
6
|
+
def initialize(name, url, base_a, base_b, opts={})
|
7
|
+
@name, @url, @base_a, @base_b = name, url, base_a, base_b
|
8
|
+
@headers = opts['headers'] || opts[:headers] || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def full_name
|
12
|
+
@name
|
13
|
+
end
|
14
|
+
|
15
|
+
def short_name
|
16
|
+
@short_name ||= @name.underscore.gsub(/\s+/, '_')
|
17
|
+
end
|
18
|
+
|
19
|
+
def side_a
|
20
|
+
@side_a ||= @base_a + @url
|
21
|
+
end
|
22
|
+
|
23
|
+
def side_b
|
24
|
+
@side_b ||= @base_b + @url
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.load_from(filename)
|
28
|
+
contents = YAML.load run_erb filename
|
29
|
+
side_a, side_b = contents['side_a'], contents['side_b']
|
30
|
+
contents['comparisons'].map do |h|
|
31
|
+
name = h.delete 'name'
|
32
|
+
url = h.delete 'url'
|
33
|
+
CompareUrl.new name, url, side_a, side_b, h
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def self.run_erb(filename)
|
39
|
+
contents = IO.read filename
|
40
|
+
ERB.new(contents).result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module HotOrNot
|
2
|
+
class ComparisonResult
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def for(compare_url)
|
6
|
+
headers = compare_url.headers
|
7
|
+
new compare_url, retreive(compare_url.side_a, headers), retreive(compare_url.side_b, headers)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def retreive(url, headers)
|
12
|
+
RestClient.get(url, headers).tap { |r| raise Exception.new("Invalid response code #{r.code} for '#{url}'") unless r.code.to_s == '200' }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :message
|
17
|
+
|
18
|
+
def initialize(compare_url, side_a_results, side_b_results)
|
19
|
+
@compare_url, @side_a_results, @side_b_results = compare_url, side_a_results, side_b_results
|
20
|
+
@message, @diff = '', ''
|
21
|
+
init
|
22
|
+
end
|
23
|
+
|
24
|
+
def success?
|
25
|
+
@success
|
26
|
+
end
|
27
|
+
|
28
|
+
def side_a_body
|
29
|
+
@side_a_body ||= body_by_content_type @side_a_results
|
30
|
+
end
|
31
|
+
|
32
|
+
def side_b_body
|
33
|
+
@side_b_body ||= body_by_content_type @side_b_results
|
34
|
+
end
|
35
|
+
|
36
|
+
def diff(format = :text)
|
37
|
+
@diff.to_s format
|
38
|
+
end
|
39
|
+
|
40
|
+
def output_to_files_in(directory)
|
41
|
+
write_to directory, "side_a", side_a_body
|
42
|
+
write_to directory, "side_b", side_b_body
|
43
|
+
write_to directory, "diff", diff
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def init
|
48
|
+
return if @success = side_a_body == side_b_body
|
49
|
+
@message = "#{@compare_url.full_name}: #{@compare_url.url}: Body from #{@compare_url.base_a} did not match body from #{@compare_url.base_b}"
|
50
|
+
@diff = Diffy::Diff.new(side_a_body, side_b_body)
|
51
|
+
end
|
52
|
+
|
53
|
+
def body_by_content_type(result)
|
54
|
+
case result.headers[:content_type]
|
55
|
+
when /json/i
|
56
|
+
h = JSON.parse result.body
|
57
|
+
JSON.pretty_generate h
|
58
|
+
else
|
59
|
+
result.body
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def write_to(directory, ext, text)
|
64
|
+
filename = File.join(directory, "#{@compare_url.short_name}.#{ext}")
|
65
|
+
File.open(filename, 'w+') do |f|
|
66
|
+
f << text
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module HotOrNot
|
2
|
+
class Runner
|
3
|
+
def self.run_from(file, announcer)
|
4
|
+
new CompareUrl.load_from(file), announcer
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(urls, announcer)
|
8
|
+
@urls, @announcer = urls, announcer
|
9
|
+
end
|
10
|
+
|
11
|
+
def run!
|
12
|
+
@announcer.starting
|
13
|
+
@urls.each do |url|
|
14
|
+
begin
|
15
|
+
result = ComparisonResult.for url
|
16
|
+
if result.success?
|
17
|
+
@announcer.announce_success result
|
18
|
+
else
|
19
|
+
@announcer.announce_failure result
|
20
|
+
end
|
21
|
+
rescue StandardError, Exception => e
|
22
|
+
@announcer.announce_error url, e
|
23
|
+
end
|
24
|
+
end
|
25
|
+
@announcer.ending
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
require 'mocha'
|
13
|
+
|
14
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
15
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
16
|
+
require 'hot-or-not'
|
17
|
+
|
18
|
+
module HotOrNot
|
19
|
+
class FakeResponse
|
20
|
+
attr_reader :body, :code, :headers
|
21
|
+
def initialize(body, code = '200', headers = {})
|
22
|
+
@body, @code = body, code
|
23
|
+
@headers = { :content_type => 'html' }.merge headers
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Test::Unit::TestCase
|
29
|
+
def assert_false(truth, message=nil)
|
30
|
+
assert !truth, message
|
31
|
+
end
|
32
|
+
alias_method :assert_not, :assert_false
|
33
|
+
|
34
|
+
def assert_present(text)
|
35
|
+
assert_not_nil text
|
36
|
+
assert_not text.empty?, "#{text} was expected to not be empty"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def mock_compare_url(name, url, body_a, body_b, code='200')
|
41
|
+
HotOrNot::CompareUrl.new(name, url, 'http://side_a', 'http://side_b').tap do |compare_url|
|
42
|
+
RestClient.expects(:get).with(compare_url.side_a, {}).returns HotOrNot::FakeResponse.new(body_a, code)
|
43
|
+
RestClient.expects(:get).with(compare_url.side_b, {}).returns HotOrNot::FakeResponse.new(body_b, code) if code.to_s == '200'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module HotOrNot
|
4
|
+
class TestCompareUrl < Test::Unit::TestCase
|
5
|
+
context "load from file" do
|
6
|
+
setup do
|
7
|
+
@urls = CompareUrl.load_from File.dirname(__FILE__) + '/data/simple_urls.yml'
|
8
|
+
end
|
9
|
+
|
10
|
+
should "load right number of comparisons" do
|
11
|
+
assert_equal 2, @urls.count
|
12
|
+
end
|
13
|
+
|
14
|
+
should "load all urls" do
|
15
|
+
assert_equal 'People Test', @urls[0].full_name
|
16
|
+
assert_equal 'Names Test', @urls[1].full_name
|
17
|
+
end
|
18
|
+
|
19
|
+
should "load headers from file" do
|
20
|
+
expected_headers = { 'authorization' => 'foo123bar' }
|
21
|
+
assert_equal expected_headers, @urls[1].headers
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "load from file with erb" do
|
26
|
+
setup do
|
27
|
+
@urls = CompareUrl.load_from File.dirname(__FILE__) + '/data/simple_urls.yml.erb'
|
28
|
+
end
|
29
|
+
|
30
|
+
should "load all comparisons" do
|
31
|
+
assert_equal 2, @urls.count
|
32
|
+
end
|
33
|
+
|
34
|
+
should "run erb on the file" do
|
35
|
+
assert_equal "People Test #{Date.today}", @urls[0].full_name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "object" do
|
40
|
+
setup do
|
41
|
+
@compare_url = CompareUrl.new 'Foo Url', '/api/foo', 'http://side_a', 'http://side_b'
|
42
|
+
end
|
43
|
+
|
44
|
+
should "contain a test name" do
|
45
|
+
assert_equal "Foo Url", @compare_url.full_name
|
46
|
+
assert_equal "foo_url", @compare_url.short_name
|
47
|
+
end
|
48
|
+
|
49
|
+
should "create urls for the side_b release" do
|
50
|
+
assert_equal 'http://side_a/api/foo', @compare_url.side_a
|
51
|
+
assert_equal 'http://side_b/api/foo', @compare_url.side_b
|
52
|
+
end
|
53
|
+
|
54
|
+
should "default headers to empty hash" do
|
55
|
+
expected_headers = {}
|
56
|
+
assert_equal expected_headers, @compare_url.headers
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "object with options" do
|
61
|
+
setup do
|
62
|
+
@compare_url = CompareUrl.new 'Foo Url', '/api/foo', 'http://side_a', 'http://side_b', :headers => { :authorization => 'baz321' }
|
63
|
+
end
|
64
|
+
|
65
|
+
should "read headers form opts" do
|
66
|
+
expected_headers = { :authorization => 'baz321' }
|
67
|
+
assert_equal expected_headers, @compare_url.headers
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module HotOrNot
|
4
|
+
class TestComparisonResult < Test::Unit::TestCase
|
5
|
+
context "comparing results" do
|
6
|
+
setup do
|
7
|
+
@compare_url = CompareUrl.new 'Test People', '/api/people', 'http://side_a', 'http://side_b'
|
8
|
+
end
|
9
|
+
|
10
|
+
context "500 error" do
|
11
|
+
setup do
|
12
|
+
RestClient.expects(:get).with(@compare_url.side_a, {}).returns FakeResponse.new('foo', 500)
|
13
|
+
end
|
14
|
+
|
15
|
+
should "raise error when response code is not 200" do
|
16
|
+
assert_raises(Exception) { ComparisonResult.for @compare_url }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when results match" do
|
21
|
+
setup do
|
22
|
+
RestClient.expects(:get).with(@compare_url.side_a, {}).returns FakeResponse.new 'foo'
|
23
|
+
RestClient.expects(:get).with(@compare_url.side_b, {}).returns FakeResponse.new 'foo'
|
24
|
+
@result = ComparisonResult.for @compare_url
|
25
|
+
end
|
26
|
+
|
27
|
+
should "return success" do
|
28
|
+
assert @result.success?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when results don't match" do
|
33
|
+
setup do
|
34
|
+
RestClient.expects(:get).with(@compare_url.side_a, {}).returns FakeResponse.new 'side_a'
|
35
|
+
RestClient.expects(:get).with(@compare_url.side_b, {}).returns FakeResponse.new 'side_b'
|
36
|
+
@result = ComparisonResult.for @compare_url
|
37
|
+
end
|
38
|
+
|
39
|
+
should "not return success" do
|
40
|
+
assert_not @result.success?
|
41
|
+
end
|
42
|
+
|
43
|
+
should "have test unit style failure message" do
|
44
|
+
assert_match @result.message, /Test People:.*?\/api\/people/
|
45
|
+
end
|
46
|
+
|
47
|
+
should "provide a diff" do
|
48
|
+
assert_present @result.diff
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module HotOrNot
|
4
|
+
class TestConsoleAnnouncer < Test::Unit::TestCase
|
5
|
+
context "run" do
|
6
|
+
setup do
|
7
|
+
intercept_io
|
8
|
+
output_dir = 'test_results'
|
9
|
+
@announcer = ConsoleAnnouncer.new output_dir
|
10
|
+
end
|
11
|
+
|
12
|
+
teardown do
|
13
|
+
reset_io
|
14
|
+
end
|
15
|
+
|
16
|
+
context "the console output" do
|
17
|
+
should "print '.' for passing test" do
|
18
|
+
urls = [mock_compare_url('Foo', '/api/foo', 'foo', 'foo')]
|
19
|
+
Runner.new(urls, @announcer).run!
|
20
|
+
|
21
|
+
assert_equal '.', test_output[1].chomp
|
22
|
+
end
|
23
|
+
|
24
|
+
should "print 'N' for a failing test" do
|
25
|
+
urls = [mock_compare_url('Foo', '/api/foo', 'foo', 'bar')]
|
26
|
+
Runner.new(urls, @announcer).run!
|
27
|
+
|
28
|
+
output = test_output
|
29
|
+
assert_equal 'N', output[1].chomp
|
30
|
+
assert_equal ' 1) Not Hot:', output[2].chomp
|
31
|
+
end
|
32
|
+
|
33
|
+
should "print 'E' for a test that errors out" do
|
34
|
+
urls = [mock_compare_url('Foo', '/api/foo', 'foo', 'bar', 404)]
|
35
|
+
Runner.new(urls, @announcer).run!
|
36
|
+
|
37
|
+
o = test_output
|
38
|
+
assert_equal 'E', o[1].chomp
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def intercept_io
|
46
|
+
@output_filename = 'test_runner_tests.txt'
|
47
|
+
@output_file = File.open(@output_filename, 'w+')
|
48
|
+
@orig_stdout = STDOUT.dup
|
49
|
+
STDOUT.reopen(@output_file)
|
50
|
+
end
|
51
|
+
|
52
|
+
def reset_io
|
53
|
+
@output_file.close
|
54
|
+
FileUtils.rm_f @output_filename if leave_last_ouput?
|
55
|
+
STDOUT.reopen @orig_stdout
|
56
|
+
end
|
57
|
+
|
58
|
+
def leave_last_ouput?
|
59
|
+
ENV['HORN_io'].to_s.downcase != 'false'
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_output
|
63
|
+
STDOUT.flush
|
64
|
+
File.readlines(@output_file)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/test/test_runner.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module HotOrNot
|
4
|
+
class TestRunner < Test::Unit::TestCase
|
5
|
+
def initialize(*args)
|
6
|
+
@announcer = Class.new do
|
7
|
+
include Announcer
|
8
|
+
|
9
|
+
attr_reader :messages
|
10
|
+
def initialize
|
11
|
+
@messages = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def starting
|
15
|
+
@messages[:starting] = true
|
16
|
+
end
|
17
|
+
|
18
|
+
def ending
|
19
|
+
@messages[:ending] = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def announce_success(result)
|
23
|
+
@messages[:success] = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def announce_failure(result)
|
27
|
+
@messages[:failure] = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def announce_error(url, error)
|
31
|
+
@messages[:error] = true
|
32
|
+
end
|
33
|
+
end.new
|
34
|
+
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
context "successful comparison" do
|
39
|
+
setup do
|
40
|
+
urls = [mock_compare_url('Foo', '/api/foo', 'foo', 'foo')]
|
41
|
+
Runner.new(urls, @announcer).run!
|
42
|
+
end
|
43
|
+
|
44
|
+
should "announce starting" do
|
45
|
+
assert @announcer.messages[:starting]
|
46
|
+
end
|
47
|
+
|
48
|
+
should "announce ending" do
|
49
|
+
assert @announcer.messages[:ending]
|
50
|
+
end
|
51
|
+
|
52
|
+
should "only annouce success" do
|
53
|
+
assert @announcer.messages[:success]
|
54
|
+
assert_false @announcer.messages[:failure]
|
55
|
+
assert_false @announcer.messages[:error]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "failing comparison" do
|
60
|
+
setup do
|
61
|
+
urls = [mock_compare_url('Foo', '/api/foo', 'foo', 'bar')]
|
62
|
+
Runner.new(urls, @announcer).run!
|
63
|
+
end
|
64
|
+
|
65
|
+
should "announce starting" do
|
66
|
+
assert @announcer.messages[:starting]
|
67
|
+
end
|
68
|
+
|
69
|
+
should "announce ending" do
|
70
|
+
assert @announcer.messages[:ending]
|
71
|
+
end
|
72
|
+
|
73
|
+
should "only annouce failure" do
|
74
|
+
assert @announcer.messages[:failure]
|
75
|
+
assert_false @announcer.messages[:success]
|
76
|
+
assert_false @announcer.messages[:error]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "error during comparison" do
|
81
|
+
setup do
|
82
|
+
urls = [mock_compare_url('Foo', '/api/foo', 'foo', 'bar', 404)]
|
83
|
+
Runner.new(urls, @announcer).run!
|
84
|
+
end
|
85
|
+
|
86
|
+
should "announce starting" do
|
87
|
+
assert @announcer.messages[:starting]
|
88
|
+
end
|
89
|
+
|
90
|
+
should "announce ending" do
|
91
|
+
assert @announcer.messages[:ending]
|
92
|
+
end
|
93
|
+
|
94
|
+
should "only annouce error" do
|
95
|
+
assert @announcer.messages[:error]
|
96
|
+
assert_false @announcer.messages[:success]
|
97
|
+
assert_false @announcer.messages[:failure]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
metadata
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hot-or-not
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Joel Friedman
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-01 00:00:00 -05:00
|
19
|
+
default_executable: hot-or-not
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rest-client
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 13
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 6
|
32
|
+
- 1
|
33
|
+
version: 1.6.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: diffy
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 13
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 0
|
48
|
+
- 1
|
49
|
+
version: 2.0.1
|
50
|
+
type: :runtime
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: facets
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 41
|
61
|
+
segments:
|
62
|
+
- 2
|
63
|
+
- 9
|
64
|
+
- 1
|
65
|
+
version: 2.9.1
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: shoulda
|
71
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
type: :development
|
81
|
+
prerelease: false
|
82
|
+
version_requirements: *id004
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ~>
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 23
|
91
|
+
segments:
|
92
|
+
- 1
|
93
|
+
- 0
|
94
|
+
- 0
|
95
|
+
version: 1.0.0
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: *id005
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: jeweler
|
101
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ~>
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
hash: 15
|
107
|
+
segments:
|
108
|
+
- 1
|
109
|
+
- 6
|
110
|
+
- 0
|
111
|
+
version: 1.6.0
|
112
|
+
type: :development
|
113
|
+
prerelease: false
|
114
|
+
version_requirements: *id006
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: rcov
|
117
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
hash: 3
|
123
|
+
segments:
|
124
|
+
- 0
|
125
|
+
version: "0"
|
126
|
+
type: :development
|
127
|
+
prerelease: false
|
128
|
+
version_requirements: *id007
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: mocha
|
131
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
hash: 3
|
137
|
+
segments:
|
138
|
+
- 0
|
139
|
+
version: "0"
|
140
|
+
type: :development
|
141
|
+
prerelease: false
|
142
|
+
version_requirements: *id008
|
143
|
+
description: Run this with a new version of your site comparing to current version to verify no break changes are made to your api's
|
144
|
+
email: asher.friedman@gmail.com
|
145
|
+
executables:
|
146
|
+
- hot-or-not
|
147
|
+
extensions: []
|
148
|
+
|
149
|
+
extra_rdoc_files:
|
150
|
+
- LICENSE.txt
|
151
|
+
- README.rdoc
|
152
|
+
files:
|
153
|
+
- .document
|
154
|
+
- Gemfile
|
155
|
+
- Gemfile.lock
|
156
|
+
- LICENSE.txt
|
157
|
+
- README.rdoc
|
158
|
+
- Rakefile
|
159
|
+
- VERSION
|
160
|
+
- bin/hot-or-not
|
161
|
+
- lib/hot-or-not.rb
|
162
|
+
- lib/hot-or-not/announcer.rb
|
163
|
+
- lib/hot-or-not/announcers/console_announcer.rb
|
164
|
+
- lib/hot-or-not/compare_url.rb
|
165
|
+
- lib/hot-or-not/comparison_result.rb
|
166
|
+
- lib/hot-or-not/runner.rb
|
167
|
+
- test/data/simple_urls.yml
|
168
|
+
- test/data/simple_urls.yml.erb
|
169
|
+
- test/helper.rb
|
170
|
+
- test/test_compare_url.rb
|
171
|
+
- test/test_comparison_result.rb
|
172
|
+
- test/test_console_announcer.rb
|
173
|
+
- test/test_runner.rb
|
174
|
+
has_rdoc: true
|
175
|
+
homepage: https://github.com/joelash/hot-or-not
|
176
|
+
licenses:
|
177
|
+
- MIT
|
178
|
+
post_install_message:
|
179
|
+
rdoc_options: []
|
180
|
+
|
181
|
+
require_paths:
|
182
|
+
- lib
|
183
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
184
|
+
none: false
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
hash: 3
|
189
|
+
segments:
|
190
|
+
- 0
|
191
|
+
version: "0"
|
192
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ">="
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
hash: 3
|
198
|
+
segments:
|
199
|
+
- 0
|
200
|
+
version: "0"
|
201
|
+
requirements: []
|
202
|
+
|
203
|
+
rubyforge_project:
|
204
|
+
rubygems_version: 1.3.7
|
205
|
+
signing_key:
|
206
|
+
specification_version: 3
|
207
|
+
summary: A tool for side-by-side comparsion of web apis
|
208
|
+
test_files: []
|
209
|
+
|