integrity 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/README.markdown +66 -0
  2. data/Rakefile +165 -0
  3. data/VERSION.yml +4 -0
  4. data/app.rb +138 -0
  5. data/bin/integrity +4 -0
  6. data/config/config.sample.ru +31 -0
  7. data/config/config.sample.yml +38 -0
  8. data/config/thin.sample.yml +13 -0
  9. data/integrity.gemspec +76 -0
  10. data/lib/integrity.rb +80 -0
  11. data/lib/integrity/build.rb +61 -0
  12. data/lib/integrity/core_ext/object.rb +6 -0
  13. data/lib/integrity/core_ext/string.rb +5 -0
  14. data/lib/integrity/helpers.rb +16 -0
  15. data/lib/integrity/helpers/authorization.rb +33 -0
  16. data/lib/integrity/helpers/breadcrumbs.rb +20 -0
  17. data/lib/integrity/helpers/forms.rb +28 -0
  18. data/lib/integrity/helpers/pretty_output.rb +45 -0
  19. data/lib/integrity/helpers/rendering.rb +14 -0
  20. data/lib/integrity/helpers/resources.rb +13 -0
  21. data/lib/integrity/helpers/urls.rb +47 -0
  22. data/lib/integrity/installer.rb +132 -0
  23. data/lib/integrity/migrations.rb +157 -0
  24. data/lib/integrity/notifier.rb +50 -0
  25. data/lib/integrity/notifier/base.rb +55 -0
  26. data/lib/integrity/project.rb +117 -0
  27. data/lib/integrity/project_builder.rb +47 -0
  28. data/lib/integrity/scm.rb +19 -0
  29. data/lib/integrity/scm/git.rb +83 -0
  30. data/lib/integrity/scm/git/uri.rb +57 -0
  31. data/public/buttons.css +82 -0
  32. data/public/reset.css +7 -0
  33. data/public/spinner.gif +0 -0
  34. data/test/helpers.rb +47 -0
  35. data/test/helpers/acceptance.rb +127 -0
  36. data/test/helpers/acceptance/git_helper.rb +99 -0
  37. data/test/helpers/acceptance/textfile_notifier.rb +26 -0
  38. data/test/helpers/expectations.rb +5 -0
  39. data/test/helpers/expectations/be_a.rb +23 -0
  40. data/test/helpers/expectations/change.rb +90 -0
  41. data/test/helpers/expectations/have.rb +105 -0
  42. data/test/helpers/expectations/have_tag.rb +128 -0
  43. data/test/helpers/expectations/predicates.rb +37 -0
  44. data/test/helpers/fixtures.rb +83 -0
  45. data/views/_build_info.haml +18 -0
  46. data/views/build.haml +2 -0
  47. data/views/error.haml +36 -0
  48. data/views/home.haml +23 -0
  49. data/views/integrity.sass +387 -0
  50. data/views/layout.haml +28 -0
  51. data/views/new.haml +51 -0
  52. data/views/not_found.haml +31 -0
  53. data/views/notifier.haml +7 -0
  54. data/views/project.builder +21 -0
  55. data/views/project.haml +28 -0
  56. data/views/unauthorized.haml +38 -0
  57. metadata +258 -0
@@ -0,0 +1,99 @@
1
+ module GitHelper
2
+ @@_git_repositories = Hash.new {|h,k| h[k] = Repo.new(k) }
3
+
4
+ def git_repo(name)
5
+ @@_git_repositories[name]
6
+ end
7
+
8
+ def destroy_all_git_repos
9
+ @@_git_repositories.each {|n,r| r.destroy }
10
+ @@_git_repositories.clear
11
+ end
12
+
13
+ class Repo
14
+ attr_reader :path
15
+
16
+ def initialize(name)
17
+ @name = name
18
+ @path = "/tmp" / @name.to_s
19
+ create
20
+ end
21
+
22
+ def path
23
+ @path / ".git"
24
+ end
25
+
26
+ def create
27
+ destroy
28
+ FileUtils.mkdir_p @path
29
+
30
+ Dir.chdir(@path) do
31
+ system 'git init &>/dev/null'
32
+ system 'git config user.name "John Doe"'
33
+ system 'git config user.email "johndoe@example.org"'
34
+ system 'echo "just a test repo" >> README'
35
+ system 'git add README &>/dev/null'
36
+ system 'git commit -m "First commit" &>/dev/null'
37
+ end
38
+
39
+ add_successful_commit
40
+ end
41
+
42
+ def commits
43
+ Dir.chdir(@path) do
44
+ commits = `git log --pretty=oneline`.collect { |line| line.split(" ").first }
45
+ commits.inject([]) do |commits, sha1|
46
+ format = %Q(---%n:message: >-%n %s%n:timestamp: %ci%n:id: #{sha1})
47
+ commits << YAML.load(`git show -s --pretty=format:"#{format}" #{sha1}`)
48
+ end
49
+ end
50
+ end
51
+
52
+ def add_commit(message, &action)
53
+ Dir.chdir(@path) do
54
+ yield action
55
+ system %Q(git commit -m "#{message}" &>/dev/null)
56
+ end
57
+ end
58
+
59
+ def add_failing_commit
60
+ add_commit "This commit will fail" do
61
+ system %Q(echo '#{build_script(false)}' > test)
62
+ system %Q(chmod +x test)
63
+ system %Q(git add test &>/dev/null)
64
+ end
65
+ end
66
+
67
+ def add_successful_commit
68
+ add_commit "This commit will work" do
69
+ system %Q(echo '#{build_script(true)}' > test)
70
+ system %Q(chmod +x test)
71
+ system %Q(git add test &>/dev/null)
72
+ end
73
+ end
74
+
75
+ def head
76
+ Dir.chdir(@path) do
77
+ `git log --pretty=format:%H | head -1`.chomp
78
+ end
79
+ end
80
+
81
+ def short_head
82
+ head[0..6]
83
+ end
84
+
85
+ def destroy
86
+ FileUtils.rm_rf @path if File.directory?(@path)
87
+ end
88
+
89
+ protected
90
+
91
+ def build_script(successful=true)
92
+ <<-script
93
+ #!/bin/sh
94
+ echo "Running tests..."
95
+ exit #{successful ? 0 : 1}
96
+ script
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,26 @@
1
+ module Integrity
2
+ class Notifier
3
+ class Textfile < Notifier::Base
4
+ def self.to_haml
5
+ <<-haml
6
+ %p.normal
7
+ %label{ :for => "textfile_notifier_file" } File
8
+ %input.text#textfile_notifier_file{ :name => "notifiers[Textfile][file]", :type => "text", :value => config["file"] }
9
+ haml
10
+ end
11
+
12
+ def initialize(build, config={})
13
+ super
14
+ @file = @config["file"]
15
+ end
16
+
17
+ def deliver!
18
+ File.open(@file, "a") do |f|
19
+ f.puts "=== #{short_message} ==="
20
+ f.puts
21
+ f.puts full_message
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + "/expectations/be_a"
2
+ require File.dirname(__FILE__) + "/expectations/change"
3
+ require File.dirname(__FILE__) + "/expectations/have"
4
+ require File.dirname(__FILE__) + "/expectations/predicates"
5
+ require File.dirname(__FILE__) + "/expectations/have_tag"
@@ -0,0 +1,23 @@
1
+ module Matchy::Expectations
2
+ class BeAExpectation < Base
3
+ def matches?(receiver)
4
+ @receiver = receiver
5
+ @receiver.is_a?(@expected)
6
+ end
7
+
8
+ def failure_message
9
+ "Expected #{@receiver.inspect} to be a #{@expected.inspect}."
10
+ end
11
+
12
+ def negative_failure_message
13
+ "Expected #{@receiver.inspect} to not be a #{@expected.inspect}."
14
+ end
15
+ end
16
+
17
+ module TestCaseExtensions
18
+ def be_a(obj)
19
+ Matchy::Expectations::BeAExpectation.new(obj, self)
20
+ end
21
+ alias :be_an :be_a
22
+ end
23
+ end
@@ -0,0 +1,90 @@
1
+ module Matchy::Expectations
2
+ class ChangeExpectation < Base
3
+ def initialize(receiver=nil, message=nil, test_case=nil, &block)
4
+ @message = message || "result"
5
+ @value_proc = block || lambda {
6
+ receiver.__send__(message)
7
+ }
8
+ @test_case = test_case
9
+ end
10
+
11
+ def matches?(event_proc)
12
+ raise_block_syntax_error if block_given?
13
+
14
+ @before = evaluate_value_proc
15
+ event_proc.call
16
+ @after = evaluate_value_proc
17
+
18
+ return false if @from unless @from == @before
19
+ return false if @to unless @to == @after
20
+ return (@before + @amount == @after) if @amount
21
+ return ((@after - @before) >= @minimum) if @minimum
22
+ return ((@after - @before) <= @maximum) if @maximum
23
+ return @before != @after
24
+ end
25
+
26
+ def raise_block_syntax_error
27
+ raise ArgumentError, "block passed to should or should_not change must use {} instead of do/end"
28
+ end
29
+
30
+ def evaluate_value_proc
31
+ @value_proc.call
32
+ end
33
+
34
+ def failure_message
35
+ if @to
36
+ "#{@message} should have been changed to #{@to.inspect}, but is now #{@after.inspect}"
37
+ elsif @from
38
+ "#{@message} should have initially been #{@from.inspect}, but was #{@before.inspect}"
39
+ elsif @amount
40
+ "#{@message} should have been changed by #{@amount.inspect}, but was changed by #{actual_delta.inspect}"
41
+ elsif @minimum
42
+ "#{@message} should have been changed by at least #{@minimum.inspect}, but was changed by #{actual_delta.inspect}"
43
+ elsif @maximum
44
+ "#{@message} should have been changed by at most #{@maximum.inspect}, but was changed by #{actual_delta.inspect}"
45
+ else
46
+ "#{@message} should have changed, but is still #{@before.inspect}"
47
+ end
48
+ end
49
+
50
+ def actual_delta
51
+ @after - @before
52
+ end
53
+
54
+ def negative_failure_message
55
+ "#{@message} should not have changed, but did change from #{@before.inspect} to #{@after.inspect}"
56
+ end
57
+
58
+ def by(amount)
59
+ @amount = amount
60
+ self
61
+ end
62
+
63
+ def by_at_least(minimum)
64
+ @minimum = minimum
65
+ self
66
+ end
67
+
68
+ def by_at_most(maximum)
69
+ @maximum = maximum
70
+ self
71
+ end
72
+
73
+ def to(to)
74
+ @to = to
75
+ self
76
+ end
77
+
78
+ def from (from)
79
+ @from = from
80
+ self
81
+ end
82
+ end
83
+
84
+
85
+ module TestCaseExtensions
86
+ def change(receiver=nil, message=nil, &block)
87
+ Matchy::Expectations::ChangeExpectation.new(receiver, message, self, &block)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,105 @@
1
+ module Matchy::Expectations
2
+ class HaveExpectation < Base
3
+ def initialize(expected, relativity=:exactly, test_case = nil)
4
+ @expected = (expected == :no ? 0 : expected)
5
+ @relativity = relativity
6
+ @test_case = test_case
7
+ end
8
+
9
+ def relativities
10
+ @relativities ||= {
11
+ :exactly => "",
12
+ :at_least => "at least ",
13
+ :at_most => "at most "
14
+ }
15
+ end
16
+
17
+ def matches?(collection_owner)
18
+ if collection_owner.respond_to?(@collection_name)
19
+ collection = collection_owner.__send__(@collection_name, *@args, &@block)
20
+ elsif (@plural_collection_name && collection_owner.respond_to?(@plural_collection_name))
21
+ collection = collection_owner.__send__(@plural_collection_name, *@args, &@block)
22
+ elsif (collection_owner.respond_to?(:length) || collection_owner.respond_to?(:size))
23
+ collection = collection_owner
24
+ else
25
+ collection_owner.__send__(@collection_name, *@args, &@block)
26
+ end
27
+ @given = collection.size if collection.respond_to?(:size)
28
+ @given = collection.length if collection.respond_to?(:length)
29
+ raise not_a_collection if @given.nil?
30
+ return @given >= @expected if @relativity == :at_least
31
+ return @given <= @expected if @relativity == :at_most
32
+ return @given == @expected
33
+ end
34
+
35
+ def not_a_collection
36
+ "expected #{@collection_name} to be a collection but it does not respond to #length or #size"
37
+ end
38
+
39
+ def failure_message
40
+ "expected #{relative_expectation} #{@collection_name}, got #{@given}"
41
+ end
42
+
43
+ def negative_failure_message
44
+ if @relativity == :exactly
45
+ return "expected target not to have #{@expected} #{@collection_name}, got #{@given}"
46
+ elsif @relativity == :at_most
47
+ return <<-EOF
48
+ Isn't life confusing enough?
49
+ Instead of having to figure out the meaning of this:
50
+ should_not have_at_most(#{@expected}).#{@collection_name}
51
+ We recommend that you use this instead:
52
+ should have_at_least(#{@expected + 1}).#{@collection_name}
53
+ EOF
54
+ elsif @relativity == :at_least
55
+ return <<-EOF
56
+ Isn't life confusing enough?
57
+ Instead of having to figure out the meaning of this:
58
+ should_not have_at_least(#{@expected}).#{@collection_name}
59
+ We recommend that you use this instead:
60
+ should have_at_most(#{@expected - 1}).#{@collection_name}
61
+ EOF
62
+ end
63
+ end
64
+
65
+ def description
66
+ "have #{relative_expectation} #{@collection_name}"
67
+ end
68
+
69
+ def respond_to?(sym)
70
+ @expected.respond_to?(sym) || super
71
+ end
72
+
73
+ private
74
+
75
+ def method_missing(sym, *args, &block)
76
+ @collection_name = sym
77
+ if inflector = (defined?(ActiveSupport::Inflector) ? ActiveSupport::Inflector : (defined?(Inflector) ? Inflector : nil))
78
+ @plural_collection_name = inflector.pluralize(sym.to_s)
79
+ end
80
+ @args = args
81
+ @block = block
82
+ self
83
+ end
84
+
85
+ def relative_expectation
86
+ "#{relativities[@relativity]}#{@expected}"
87
+ end
88
+ end
89
+
90
+
91
+ module TestCaseExtensions
92
+ def have(n)
93
+ HaveExpectation.new(n, :exactly, self)
94
+ end
95
+ alias :have_exactly :have
96
+
97
+ def have_at_least(n)
98
+ HaveExpectation.new(n, :at_least, self)
99
+ end
100
+
101
+ def have_at_most(n)
102
+ HaveExpectation.new(n, :at_most, self)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,128 @@
1
+ require 'hpricot'
2
+
3
+ # evil hack to duck-type CgiResponse so that nested shoulds can use
4
+ # +rspec_on_rails+ matchers without remembering to call to_s on it
5
+ #
6
+ # e.g.
7
+ #
8
+ # response.should have_tag("li") do |ul|
9
+ # ul.should have_text("List Item") # with hack
10
+ # ul.to_s.should have_text("List Item") # without hack
11
+ # end
12
+ class Hpricot::Elem
13
+ alias body to_s
14
+ end
15
+
16
+ module Matchy::Expectations
17
+ class HaveTag < Base
18
+ def initialize(test_case, selector, inner_text_or_options, options, &block)
19
+ #@expected = expected
20
+ @test_case = test_case
21
+ @selector = selector
22
+
23
+ if Hash === inner_text_or_options
24
+ @inner_text = nil
25
+ @options = inner_text_or_options
26
+ else
27
+ @inner_text = inner_text_or_options
28
+ @options = options
29
+ end
30
+ end
31
+
32
+ def matches?(actual, &block)
33
+ @actual = actual
34
+ @doc = hpricot_document(@actual)
35
+
36
+ matched_elements = @doc.search(@selector)
37
+
38
+ return @options[:count] == 0 if matched_elements.empty?
39
+
40
+ matched_elements = filter_on_inner_text(matched_elements) if @inner_text
41
+ matched_elements = filter_on_nested_expectations(matched_elements, block) if block
42
+
43
+ @actual_count = matched_elements.length
44
+
45
+ return false unless acceptable_count?(@actual_count)
46
+
47
+ !matched_elements.empty?
48
+ end
49
+
50
+ def failure_message
51
+ explanation = @actual_count ? "but found #{@actual_count}" : "but did not"
52
+ "expected\n#{@doc.to_s}\nto have #{failure_count_phrase} #{failure_selector_phrase}, #{explanation}"
53
+ end
54
+
55
+ def negative_failure_message
56
+ explanation = @actual_count ? "but found #{@actual_count}" : "but did"
57
+ "expected\n#{@doc.to_s}\nnot to have #{failure_count_phrase} #{failure_selector_phrase}, #{explanation}"
58
+ end
59
+
60
+ private
61
+ def hpricot_document(input)
62
+ if Hpricot === input
63
+ input
64
+ elsif input.respond_to?(:body)
65
+ Hpricot(input.body)
66
+ else
67
+ Hpricot(input.to_s)
68
+ end
69
+ end
70
+
71
+ def filter_on_inner_text(elements)
72
+ elements.select do |element|
73
+ next(element.inner_text =~ @inner_text) if @inner_text.is_a?(Regexp)
74
+ element.inner_text == @inner_text
75
+ end
76
+ end
77
+
78
+ def filter_on_nested_expectations(elements, block)
79
+ elements.select do |el|
80
+ begin
81
+ block.call(el)
82
+ rescue NoMethodError
83
+ false
84
+ else
85
+ true
86
+ end
87
+ end
88
+ end
89
+
90
+ def acceptable_count?(actual_count)
91
+ if @options[:count]
92
+ return false unless @options[:count] === actual_count
93
+ end
94
+ if @options[:minimum]
95
+ return false unless actual_count >= @options[:minimum]
96
+ end
97
+ if @options[:maximum]
98
+ return false unless actual_count <= @options[:maximum]
99
+ end
100
+
101
+ true
102
+ end
103
+
104
+ def failure_count_phrase
105
+ if @options[:count]
106
+ "#{@options[:count]} elements matching"
107
+ elsif @options[:minimum] || @options[:maximum]
108
+ count_explanations = []
109
+ count_explanations << "at least #{@options[:minimum]}" if @options[:minimum]
110
+ count_explanations << "at most #{@options[:maximum]}" if @options[:maximum]
111
+ "#{count_explanations.join(' and ')} elements matching"
112
+ else
113
+ "an element matching"
114
+ end
115
+ end
116
+
117
+ def failure_selector_phrase
118
+ phrase = @selector.inspect
119
+ phrase << (@inner_text ? " with inner text #{@inner_text.inspect}" : "")
120
+ end
121
+ end
122
+
123
+ module TestCaseExtensions
124
+ def have_tag(selector, inner_text_or_options = nil, options = {}, &block)
125
+ Matchy::Expectations::HaveTag.new(self, selector, inner_text_or_options, options, &block)
126
+ end
127
+ end
128
+ end