ardekantur-shnork 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +31 -0
- data/lib/rspec_shnork_matchers.rb +59 -0
- data/lib/shnork.rb +104 -0
- data/readme.md +64 -0
- data/spec/shnork_spec.rb +52 -0
- data/spec/site.rb +40 -0
- data/spec/spec_helper.rb +6 -0
- metadata +67 -0
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rubygems/specification'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
desc "Run all specs"
|
9
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
10
|
+
t.spec_files = FileList['spec/*.rb'] - ['spec/site.rb']
|
11
|
+
# t.spec_opts = ['--color']
|
12
|
+
end
|
13
|
+
|
14
|
+
spec = Gem::Specification.load('shnork.gemspec')
|
15
|
+
|
16
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
17
|
+
pkg.gem_spec = spec
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "run rspec + rcov"
|
21
|
+
Spec::Rake::SpecTask.new("spec:rcov") do |t|
|
22
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
23
|
+
t.rcov_opts = ['--exclude', "spec/,rcov.rb,rspec.rb,spec*,gems*"]
|
24
|
+
t.rcov = true
|
25
|
+
t.rcov_dir = 'doc/coverage'
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "install the gem locally"
|
29
|
+
task :install => [:package] do
|
30
|
+
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
|
31
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module RspecShnork
|
2
|
+
|
3
|
+
def shnork thresholds = {404 => 0, 500 => 0}
|
4
|
+
RspecShnork.new(thresholds, '/')
|
5
|
+
end
|
6
|
+
|
7
|
+
def shnork_at path, thresholds = {404 => 0, 500 => 0}
|
8
|
+
RspecSnork.new(thresholds, path)
|
9
|
+
end
|
10
|
+
|
11
|
+
class RspecShnork
|
12
|
+
|
13
|
+
def initialize thresholds, path = nil
|
14
|
+
@path = path || '/'
|
15
|
+
@thresholds = thresholds
|
16
|
+
end
|
17
|
+
|
18
|
+
def matches?(target)
|
19
|
+
@results = Shnork::Hunt(@path)
|
20
|
+
messages_for(404) <= @thresholds[404] and messages_for(500) <= @thresholds[500]
|
21
|
+
end
|
22
|
+
|
23
|
+
def failure_message
|
24
|
+
max_messages = 5
|
25
|
+
msg = <<-EOM
|
26
|
+
Shnork found #{messages_to_string(404)} and #{messages_to_string(500)}.
|
27
|
+
One or both of these are over the accepted thresholds (404 -> #{@thresholds[404]}, 500 -> #{@thresholds[500]}).
|
28
|
+
EOM
|
29
|
+
[404, 500].each do |error|
|
30
|
+
i = 0
|
31
|
+
if messages_for(error) > 0 then
|
32
|
+
show_errors = messages_for(error) < max_messages ? messages_for(error) : max_messages
|
33
|
+
msg += " First #{show_errors} discovered #{error} error#{ show_errors == 1 ? '' : 's'}:\n"
|
34
|
+
while i < max_messages and i < messages_for(error)
|
35
|
+
msg += " * " + @results.codes[error][i] + "\n"
|
36
|
+
i += 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
return msg
|
41
|
+
end
|
42
|
+
|
43
|
+
def messages_for(error)
|
44
|
+
return 0 unless @results.codes.has_key? error
|
45
|
+
return @results.codes[error].size
|
46
|
+
end
|
47
|
+
|
48
|
+
def messages_to_string(error)
|
49
|
+
err = (messages_for(error) == 1) ? "error" : "errors"
|
50
|
+
"#{messages_for(error)} #{error} #{err}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def negative_failure_message
|
54
|
+
"expected shnork not to pass"
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/lib/shnork.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift( File.dirname(__FILE__) )
|
5
|
+
require 'rspec_shnork_matchers'
|
6
|
+
|
7
|
+
module Shnork
|
8
|
+
|
9
|
+
def self.Hunt page
|
10
|
+
return Shnork.new().analyze(page)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.new
|
14
|
+
return Shnork.new()
|
15
|
+
end
|
16
|
+
|
17
|
+
class Shnork
|
18
|
+
|
19
|
+
include Rack::Utils
|
20
|
+
|
21
|
+
attr_reader :page, :reached
|
22
|
+
attr_accessor :depth
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
set :env, :production
|
26
|
+
set :run, false
|
27
|
+
@app = Sinatra::application
|
28
|
+
@request = Rack::MockRequest.new(Sinatra.build_application)
|
29
|
+
@reached = []
|
30
|
+
@found = []
|
31
|
+
|
32
|
+
@internal_depth = {}
|
33
|
+
@internal_pred = {}
|
34
|
+
@depth = :all
|
35
|
+
|
36
|
+
@results = ShnorkResults.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def analyze page
|
40
|
+
@found << [page, nil]
|
41
|
+
while @found.size != 0
|
42
|
+
link = @found.shift
|
43
|
+
next if @reached.include? link.first
|
44
|
+
retrieve link
|
45
|
+
end
|
46
|
+
|
47
|
+
return @results
|
48
|
+
end
|
49
|
+
|
50
|
+
def record page
|
51
|
+
@reached = @reached | [page]
|
52
|
+
end
|
53
|
+
|
54
|
+
def less_than_max_depth d
|
55
|
+
return true if @depth == :all
|
56
|
+
d <= @depth
|
57
|
+
end
|
58
|
+
|
59
|
+
def retrieve page_from
|
60
|
+
page = page_from.first
|
61
|
+
from = page_from.last
|
62
|
+
@internal_depth[page] = from ? @internal_depth[from] + 1 : 0
|
63
|
+
@internal_pred[page] = from
|
64
|
+
return unless less_than_max_depth @internal_depth[page]
|
65
|
+
@page = @request.get(page)
|
66
|
+
@page = @request.get(@page.headers["Location"]) if @page.status >= 300 and @page.status < 400
|
67
|
+
@results.add @page.status, page
|
68
|
+
@found = @found | links(page)
|
69
|
+
record page
|
70
|
+
end
|
71
|
+
|
72
|
+
def links current_page
|
73
|
+
return [] unless @page
|
74
|
+
Nokogiri::HTML(@page.body).xpath('//a').select { |link| link.attributes["href"] =~ /^\// }.map { |link| [link.attributes["href"], current_page] }
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def status
|
80
|
+
puts "DEPTH: #{@internal_depth.inspect}"
|
81
|
+
puts "PREDS: #{@internal_pred.inspect}"
|
82
|
+
puts "FOUND: #{@found.inspect}"
|
83
|
+
puts "REACH: #{@reached.inspect}"
|
84
|
+
puts "----------------------"
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
class ShnorkResults
|
90
|
+
|
91
|
+
attr_reader :codes
|
92
|
+
|
93
|
+
def initialize
|
94
|
+
@codes = {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def add return_code, page
|
98
|
+
@codes[return_code] ||= []
|
99
|
+
@codes[return_code] << page
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# shnork
|
2
|
+
|
3
|
+
shnork is [dwemthy's][] lil' cousin. he's kinda headstrong but he doesn't take any guff. he explores your website like a dungeon, making sure it's structurally sound, all cavernous.
|
4
|
+
|
5
|
+
shnork is link testing for sinatra. lookatitgo!
|
6
|
+
|
7
|
+
## features
|
8
|
+
|
9
|
+
* specify the depth you want to allow shnork to descend
|
10
|
+
* provide thresholds: allow _x_ number of `404`s or _y_ number of `500`s
|
11
|
+
|
12
|
+
## syntax
|
13
|
+
|
14
|
+
describe "My awesome Sinatra web application" do
|
15
|
+
include RspecShnork
|
16
|
+
|
17
|
+
it "should be a perfect website" do
|
18
|
+
Sinatra::application.should shnork
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not be broken beyond four 404 errors and 9 500 errors" do
|
22
|
+
Sinatra::application.should shnork({404 => 4, 500 => 9})
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be a perfect from the members section in" do
|
26
|
+
Sinatra::application.should shnork_at '/members'
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
## the results are in
|
32
|
+
|
33
|
+
Actual code results! **Exciting**.
|
34
|
+
|
35
|
+
1)
|
36
|
+
'Shnork RSpec matchers should work' FAILED
|
37
|
+
Shnork found 6 404 errors and 6 500 errors.
|
38
|
+
One or both of these are over the accepted thresholds (404 -> 0, 500 -> 0).
|
39
|
+
First 5 discovered 404 errors:
|
40
|
+
* /broken-link/1
|
41
|
+
* /broken-link/2
|
42
|
+
* /broken-link/3
|
43
|
+
* /broken-link/4
|
44
|
+
* /broken-link/5
|
45
|
+
First 2 discovered 500 errors:
|
46
|
+
* /error/1
|
47
|
+
* /error/2
|
48
|
+
* /error/3
|
49
|
+
./spec/shnork_rspec_spec.rb:8:
|
50
|
+
|
51
|
+
Wootabega.
|
52
|
+
|
53
|
+
## helping out
|
54
|
+
|
55
|
+
this code is in its infancy. please feel free to contribute suggestions as to what features you'd like. features have a greater chance of being implemented if you write a spec, or if you fork and write how you think it should work!
|
56
|
+
|
57
|
+
here's [where you can file bugs][].
|
58
|
+
|
59
|
+
### misc
|
60
|
+
|
61
|
+
licensed under the gpl. enjoy.
|
62
|
+
|
63
|
+
[dwemthy's]: http://poignantguide.net/dwemthy/
|
64
|
+
[where you can file bugs]: http://ardekantur.lighthouseapp.com/projects/20004-shnork/overview
|
data/spec/shnork_spec.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "Shnork" do
|
4
|
+
|
5
|
+
setup do
|
6
|
+
Sinatra.application = nil
|
7
|
+
|
8
|
+
page = Proc.new { [
|
9
|
+
"<h1>My Web Page</h1>",
|
10
|
+
"This is my unsightly webpage! It's not even valid markup!",
|
11
|
+
"<a href='http://www.google.com'>Google</a>",
|
12
|
+
"<hr />",
|
13
|
+
'<a href="/login">Login</a>',
|
14
|
+
"<a href='/about'>About this page</a>"
|
15
|
+
].join }
|
16
|
+
get '/', &page
|
17
|
+
|
18
|
+
about = Proc.new { "<a href='/second/layer/down'>Second layer down link</a>" }
|
19
|
+
get '/about', &about
|
20
|
+
|
21
|
+
second_layer_down = Proc.new { "You made it here!" }
|
22
|
+
get '/second/layer/down', &second_layer_down
|
23
|
+
|
24
|
+
@shallow_hunt = Shnork.new()
|
25
|
+
@shallow_hunt.depth = 0
|
26
|
+
@shallow_results = @shallow_hunt.analyze('/')
|
27
|
+
|
28
|
+
@deep_hunt = Shnork.new()
|
29
|
+
@deep_results = @deep_hunt.analyze('/')
|
30
|
+
end
|
31
|
+
|
32
|
+
teardown do
|
33
|
+
Sinatra.application = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should record the initial link as reached" do
|
37
|
+
@shallow_hunt.reached.should == ['/']
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should provide information about the initial link in results" do
|
41
|
+
@shallow_results.codes.size.should == 1
|
42
|
+
@shallow_results.codes.should == { 200 => ['/'] }
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should find links the second level down" do
|
46
|
+
@deep_results.codes[200].should include('/second/layer/down')
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should appropriately handle redirects"
|
50
|
+
it "should should only descend to the desired depth"
|
51
|
+
|
52
|
+
end
|
data/spec/site.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sinatra'
|
3
|
+
|
4
|
+
get '/' do
|
5
|
+
<<-EOM
|
6
|
+
Hello World!
|
7
|
+
<a href="/">Home</a>
|
8
|
+
<a href="/working-link">Working Link</a>
|
9
|
+
<a href="/working-link/1">Working Link</a>
|
10
|
+
<a href="/working-link/2">Working Link</a>
|
11
|
+
<a href="/working-link/3">Working Link</a>
|
12
|
+
<a href="/working-link/4">Working Link</a>
|
13
|
+
<a href="/working-link/5">Working Link</a>
|
14
|
+
<a href="/working-link/6">Working Link</a>
|
15
|
+
EOM
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/working-link' do
|
19
|
+
<<-EOM
|
20
|
+
This is a working page!
|
21
|
+
EOM
|
22
|
+
end
|
23
|
+
|
24
|
+
get '/working-link/:number' do
|
25
|
+
<<-EOM
|
26
|
+
This is a working page!
|
27
|
+
Here is another: <a href='/working-link'>Working Link</a>
|
28
|
+
This is a broken link: <a href='/broken-link/#{params[:number]}'>Broken Link #{params[:number]}</a>
|
29
|
+
This link will cause a 500 error: <a href='/error/#{params[:number]}'>500 Error link #{params[:number]}</a>
|
30
|
+
EOM
|
31
|
+
end
|
32
|
+
|
33
|
+
error do
|
34
|
+
"<h1>500</h1>"
|
35
|
+
end
|
36
|
+
|
37
|
+
get '/error/:number' do
|
38
|
+
throw :halt, [500, 'error']
|
39
|
+
end
|
40
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ardekantur-shnork
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ardekantur
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-14 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: nokogiri
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.2
|
23
|
+
version:
|
24
|
+
description: make sure your sinatra websites are logically sound
|
25
|
+
email: greystone@ardekantur.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files: []
|
31
|
+
|
32
|
+
files:
|
33
|
+
- readme.md
|
34
|
+
- Rakefile
|
35
|
+
- lib/shnork.rb
|
36
|
+
- lib/rspec_shnork_matchers.rb
|
37
|
+
- spec/shnork_spec.rb
|
38
|
+
- spec/spec_helper.rb
|
39
|
+
- spec/site.rb
|
40
|
+
has_rdoc: false
|
41
|
+
homepage: http://github.com/ardekantur/shnork
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.2.0
|
63
|
+
signing_key:
|
64
|
+
specification_version: 2
|
65
|
+
summary: make sure your sinatra websites are logically sound
|
66
|
+
test_files:
|
67
|
+
- spec/shnork_spec.rb
|