gforces-integrity 0.1.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/.gitignore +12 -0
  2. data/CHANGES +42 -0
  3. data/README.md +82 -0
  4. data/Rakefile +57 -0
  5. data/bin/integrity +4 -0
  6. data/config/config.sample.ru +21 -0
  7. data/config/config.sample.yml +41 -0
  8. data/config/heroku/.gems +1 -0
  9. data/config/heroku/Rakefile +6 -0
  10. data/config/heroku/config.ru +7 -0
  11. data/config/heroku/integrity-config.rb +14 -0
  12. data/config/thin.sample.yml +13 -0
  13. data/integrity.gemspec +138 -0
  14. data/lib/integrity.rb +78 -0
  15. data/lib/integrity/app.rb +138 -0
  16. data/lib/integrity/author.rb +39 -0
  17. data/lib/integrity/build.rb +52 -0
  18. data/lib/integrity/commit.rb +60 -0
  19. data/lib/integrity/core_ext/object.rb +6 -0
  20. data/lib/integrity/helpers.rb +16 -0
  21. data/lib/integrity/helpers/authorization.rb +33 -0
  22. data/lib/integrity/helpers/breadcrumbs.rb +20 -0
  23. data/lib/integrity/helpers/forms.rb +29 -0
  24. data/lib/integrity/helpers/pretty_output.rb +45 -0
  25. data/lib/integrity/helpers/rendering.rb +25 -0
  26. data/lib/integrity/helpers/resources.rb +19 -0
  27. data/lib/integrity/helpers/urls.rb +59 -0
  28. data/lib/integrity/installer.rb +138 -0
  29. data/lib/integrity/migrations.rb +151 -0
  30. data/lib/integrity/notifier.rb +44 -0
  31. data/lib/integrity/notifier/base.rb +74 -0
  32. data/lib/integrity/notifier/test.rb +59 -0
  33. data/lib/integrity/notifier/test/fixtures.rb +108 -0
  34. data/lib/integrity/notifier/test/hpricot_matcher.rb +38 -0
  35. data/lib/integrity/project.rb +100 -0
  36. data/lib/integrity/project/notifiers.rb +33 -0
  37. data/lib/integrity/project/push.rb +44 -0
  38. data/lib/integrity/project_builder.rb +56 -0
  39. data/lib/integrity/scm.rb +21 -0
  40. data/lib/integrity/scm/git.rb +89 -0
  41. data/lib/integrity/scm/git/uri.rb +57 -0
  42. data/public/buttons.css +82 -0
  43. data/public/reset.css +7 -0
  44. data/public/spinner.gif +0 -0
  45. data/test/acceptance/api_test.rb +97 -0
  46. data/test/acceptance/browse_project_builds_test.rb +65 -0
  47. data/test/acceptance/browse_project_test.rb +99 -0
  48. data/test/acceptance/build_notifications_test.rb +95 -0
  49. data/test/acceptance/create_project_test.rb +97 -0
  50. data/test/acceptance/delete_project_test.rb +53 -0
  51. data/test/acceptance/edit_project_test.rb +117 -0
  52. data/test/acceptance/error_page_test.rb +18 -0
  53. data/test/acceptance/installer_test.rb +79 -0
  54. data/test/acceptance/manual_build_project_test.rb +82 -0
  55. data/test/acceptance/not_found_page_test.rb +29 -0
  56. data/test/acceptance/project_syndication_test.rb +30 -0
  57. data/test/acceptance/stylesheet_test.rb +26 -0
  58. data/test/acceptance/unauthorized_page_test.rb +20 -0
  59. data/test/helpers.rb +82 -0
  60. data/test/helpers/acceptance.rb +83 -0
  61. data/test/helpers/acceptance/email_notifier.rb +55 -0
  62. data/test/helpers/acceptance/git_helper.rb +99 -0
  63. data/test/helpers/acceptance/notifier_helper.rb +47 -0
  64. data/test/helpers/acceptance/textfile_notifier.rb +26 -0
  65. data/test/helpers/expectations.rb +4 -0
  66. data/test/helpers/expectations/be_a.rb +23 -0
  67. data/test/helpers/expectations/change.rb +90 -0
  68. data/test/helpers/expectations/have.rb +105 -0
  69. data/test/helpers/expectations/predicates.rb +37 -0
  70. data/test/helpers/initial_migration_fixture.sql +44 -0
  71. data/test/unit/build_test.rb +72 -0
  72. data/test/unit/commit_test.rb +66 -0
  73. data/test/unit/helpers_test.rb +103 -0
  74. data/test/unit/integrity_test.rb +35 -0
  75. data/test/unit/migrations_test.rb +57 -0
  76. data/test/unit/notifier/base_test.rb +43 -0
  77. data/test/unit/notifier/test_test.rb +29 -0
  78. data/test/unit/notifier_test.rb +97 -0
  79. data/test/unit/project_builder_test.rb +118 -0
  80. data/test/unit/project_test.rb +363 -0
  81. data/test/unit/scm_test.rb +54 -0
  82. data/views/_commit_info.haml +24 -0
  83. data/views/build.haml +2 -0
  84. data/views/error.haml +37 -0
  85. data/views/home.haml +21 -0
  86. data/views/integrity.sass +400 -0
  87. data/views/layout.haml +29 -0
  88. data/views/new.haml +50 -0
  89. data/views/not_found.haml +31 -0
  90. data/views/notifier.haml +7 -0
  91. data/views/project.builder +21 -0
  92. data/views/project.haml +30 -0
  93. data/views/unauthorized.haml +38 -0
  94. metadata +325 -0
@@ -0,0 +1,44 @@
1
+ require File.dirname(__FILE__) + "/notifier/base"
2
+
3
+ module Integrity
4
+ class Notifier
5
+ include DataMapper::Resource
6
+
7
+ property :id, Integer, :serial => true
8
+ property :name, String, :nullable => false
9
+ property :enabled, Boolean, :nullable => false, :default => false
10
+ property :config, Yaml, :nullable => false, :lazy => false
11
+
12
+ belongs_to :project, :class_name => "Integrity::Project",
13
+ :child_key => [:project_id]
14
+
15
+ validates_is_unique :name, :scope => :project_id
16
+ validates_present :project_id
17
+
18
+ def self.available
19
+ @@_notifiers ||= {}
20
+ @@_notifiers
21
+ end
22
+
23
+ def self.register(klass)
24
+ raise ArgumentError unless valid?(klass)
25
+
26
+ available[klass.to_s.split(":").last] = klass
27
+ end
28
+
29
+ def self.valid?(notifier)
30
+ notifier.respond_to?(:to_haml) && notifier.respond_to?(:notify_of_build) &&
31
+ notifier != Notifier::Base
32
+ end
33
+ private_class_method :valid?
34
+
35
+ def notify_of_build(build)
36
+ to_const.notify_of_build(build, config)
37
+ end
38
+
39
+ private
40
+ def to_const
41
+ self.class.available[name]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,74 @@
1
+ module Integrity
2
+ class Notifier
3
+ class Base
4
+ def self.notify_of_build(build, config)
5
+ Integrity.log "Notifying of build #{build.commit.short_identifier} using the #{self.class} notifier"
6
+ Timeout.timeout(8) { new(build.commit, config).deliver! }
7
+ rescue Timeout::Error
8
+ Integrity.log "#{notifier.name} notifier timed out"
9
+ false
10
+ end
11
+
12
+ def self.to_haml
13
+ raise NotImplementedError, "you need to implement this method in your notifier"
14
+ end
15
+
16
+ attr_reader :commit
17
+
18
+ def initialize(commit, config)
19
+ @commit = commit
20
+ @config = config
21
+ end
22
+
23
+ def build
24
+ warn "Notifier::Base#build is deprecated, use Notifier::Base#commit instead (#{caller[0]})"
25
+ commit
26
+ end
27
+
28
+ def deliver!
29
+ raise NotImplementedError, "you need to implement this method in your notifier"
30
+ end
31
+
32
+ def short_message
33
+ commit.human_readable_status
34
+ end
35
+
36
+ def full_message
37
+ <<-EOM
38
+ "Build #{commit.identifier} #{commit.successful? ? "was successful" : "failed"}"
39
+
40
+ Commit Message: #{commit.message}
41
+ Commit Date: #{commit.committed_at}
42
+ Commit Author: #{commit.author.name}
43
+
44
+ Link: #{commit_url}
45
+
46
+ Build Output:
47
+
48
+ #{stripped_commit_output}
49
+ EOM
50
+ end
51
+
52
+ def commit_url
53
+ raise if Integrity.config[:base_uri].nil?
54
+ Integrity.config[:base_uri] / commit.project.permalink / "commits" / commit.identifier
55
+ end
56
+
57
+ def build_url
58
+ warn "Notifier::Base#build_url is deprecated, use Notifier::Base#commit_url instead (#{caller[0]})"
59
+ commit_url
60
+ end
61
+
62
+ private
63
+
64
+ def stripped_commit_output
65
+ commit.output.gsub("\e[0m", "").gsub(/\e\[3[1-7]m/, "")
66
+ end
67
+
68
+ def stripped_build_output
69
+ warn "Notifier::Base#stripped_build_output is deprecated, use Notifier::base#stripped_commit_output instead (#{caller[0]})"
70
+ stripped_commit_output
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + "/../../integrity"
2
+
3
+ require "integrity/notifier/test/hpricot_matcher"
4
+ require "integrity/notifier/test/fixtures"
5
+
6
+ module Integrity
7
+ class Notifier
8
+ module Test
9
+ def setup_database
10
+ DataMapper.setup(:default, "sqlite3::memory:")
11
+ DataMapper.auto_migrate!
12
+ end
13
+
14
+ def notifier_class
15
+ Integrity::Notifier.const_get(notifier)
16
+ end
17
+
18
+ def notification(commit)
19
+ notifier_class.new(commit).full_message
20
+ end
21
+
22
+ def notification_successful
23
+ notification(Integrity::Commit.gen(:successful))
24
+ end
25
+
26
+ def notification_failed
27
+ notification(Integrity::Commit.gen(:failed))
28
+ end
29
+
30
+ def assert_form_have_option(option, value=nil)
31
+ selector = "input##{notifier.downcase}_notifier_#{option}"
32
+ selector << "[@name='notifiers[#{notifier}][#{option}]']"
33
+ selector << "[@value='#{value}']" if value
34
+
35
+ assert_form_have_tag(selector, option => value)
36
+ end
37
+
38
+ def assert_form_have_options(*options)
39
+ options.each { |option| assert_form_have_option(option) }
40
+ end
41
+
42
+ def assert_form_have_tag(selector, options={})
43
+ content = options.delete(:content)
44
+ assert_have_tag(form(options), selector, content)
45
+ end
46
+
47
+ def assert_have_tag(html, selector, content=nil)
48
+ matcher = HpricotMatcher.new(html)
49
+ assert_equal content, matcher.tag(selector) if content
50
+ assert matcher.tag(selector)
51
+ end
52
+
53
+ def form(config={})
54
+ Haml::Engine.new(notifier_class.to_haml).
55
+ render(OpenStruct.new(:config => config))
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,108 @@
1
+ require "dm-sweatshop"
2
+
3
+ include DataMapper::Sweatshop::Unique
4
+
5
+ class Array
6
+ def pick
7
+ self[rand(self.length)]
8
+ end
9
+ end
10
+
11
+ def create_notifier!(name)
12
+ klass = Class.new(Integrity::Notifier::Base) do
13
+ def self.to_haml; ""; end
14
+ def deliver!; nil; end
15
+ end
16
+
17
+ unless Integrity::Notifier.const_defined?(name)
18
+ Integrity::Notifier.const_set(name, klass)
19
+ end
20
+ end
21
+
22
+ Integrity::Project.fixture do
23
+ { :name => (name = unique { /\w+/.gen }),
24
+ :uri => "git://github.com/#{/\w+/.gen}/#{name}.git",
25
+ :branch => ["master", "test-refactoring", "lh-34"].pick,
26
+ :command => ["rake", "make", "ant -buildfile test.xml"].pick,
27
+ :public => [true, false].pick,
28
+ :building => [true, false].pick }
29
+ end
30
+
31
+ Integrity::Project.fixture(:integrity) do
32
+ { :name => "Integrity",
33
+ :uri => "git://github.com/foca/integrity.git",
34
+ :branch => "master",
35
+ :command => "rake",
36
+ :public => true,
37
+ :building => false }
38
+ end
39
+
40
+ Integrity::Project.fixture(:my_test_project) do
41
+ { :name => "My Test Project",
42
+ :uri => File.dirname(__FILE__) + "/../../",
43
+ :branch => "master",
44
+ :command => "./test",
45
+ :public => true,
46
+ :building => false }
47
+ end
48
+
49
+ Integrity::Commit.fixture do
50
+ project = Integrity::Project.first || Integrity::Project.gen
51
+
52
+ { :identifier => Digest::SHA1.hexdigest(/[:paragraph:]/.gen),
53
+ :message => /[:sentence:]/.gen,
54
+ :author => /\w+ \w+ <\w+@example.org>/.gen,
55
+ :committed_at => unique {|i| Time.mktime(2008, 12, 15, 18, (59 - i) % 60) },
56
+ :project_id => project.id }
57
+ end
58
+
59
+ Integrity::Commit.fixture(:successful) do
60
+ Integrity::Commit.generate_attributes.update(:build => Integrity::Build.gen(:successful))
61
+ end
62
+
63
+ Integrity::Commit.fixture(:failed) do
64
+ Integrity::Commit.generate_attributes.update(:build => Integrity::Build.gen(:failed))
65
+ end
66
+
67
+ Integrity::Commit.fixture(:pending) do
68
+ Integrity::Commit.generate_attributes.update(:build => Integrity::Build.gen(:pending))
69
+ end
70
+
71
+ Integrity::Build.fixture do
72
+ commit = Integrity::Commit.first || Integrity::Commit.gen
73
+
74
+ { :output => /[:paragraph:]/.gen,
75
+ :successful => true,
76
+ :started_at => unique {|i| Time.mktime(2008, 12, 15, 18, i % 60) },
77
+ :created_at => unique {|i| Time.mktime(2008, 12, 15, 18, i % 60) },
78
+ :completed_at => unique {|i| Time.mktime(2008, 12, 15, 18, i % 60) },
79
+ :commit_id => commit.id }
80
+ end
81
+
82
+ Integrity::Build.fixture(:successful) do
83
+ Integrity::Build.generate_attributes.update(:successful => true)
84
+ end
85
+
86
+ Integrity::Build.fixture(:failed) do
87
+ Integrity::Build.generate_attributes.update(:successful => false)
88
+ end
89
+
90
+ Integrity::Build.fixture(:pending) do
91
+ Integrity::Build.generate_attributes.update(:successful => nil, :started_at => nil, :completed_at => nil)
92
+ end
93
+
94
+ Integrity::Notifier.fixture(:irc) do
95
+ create_notifier! "IRC"
96
+
97
+ { :project => Integrity::Project.generate,
98
+ :name => "IRC",
99
+ :config => { :uri => "irc://irc.freenode.net/integrity" }}
100
+ end
101
+
102
+ Integrity::Notifier.fixture(:twitter) do
103
+ create_notifier! "Twitter"
104
+
105
+ { :project => Integrity::Project.generate,
106
+ :name => "Twitter",
107
+ :config => { :email => "foo@example.org", :pass => "secret" }}
108
+ end
@@ -0,0 +1,38 @@
1
+ require "hpricot"
2
+
3
+ module Integrity
4
+ class Notifier
5
+ module Test
6
+ # Thanks Harry! http://gist.github.com/39960
7
+ class HpricotMatcher
8
+ def initialize(html)
9
+ @doc = Hpricot(html)
10
+ end
11
+
12
+ # elements('h1') returns a Hpricot::Elements object with all h1-tags.
13
+ def elements(selector)
14
+ @doc.search(selector)
15
+ end
16
+
17
+ # element('h1') returns Hpricot::Elem with first h1-tag, or nil if
18
+ # none exist.
19
+ def element(selector)
20
+ @doc.at(selector)
21
+ end
22
+
23
+ # tags('h1') returns the inner HTML of all matched elements mathed.
24
+ def tags(selector)
25
+ e = elements(selector)
26
+ e.map {|x| x.inner_html}
27
+ end
28
+
29
+ # tag('h1') returns the inner HTML of the first mached element, or
30
+ # nil if none matched.
31
+ def tag(selector)
32
+ e = element(selector)
33
+ e && e.inner_html
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,100 @@
1
+ require "integrity/project/notifiers"
2
+ require "integrity/project/push"
3
+
4
+ module Integrity
5
+ class Project
6
+ include DataMapper::Resource
7
+
8
+ include Helpers::Notifiers
9
+ include Helpers::Push
10
+
11
+ property :id, Integer, :serial => true
12
+ property :name, String, :nullable => false
13
+ property :permalink, String
14
+ property :uri, URI, :nullable => false, :length => 255
15
+ property :branch, String, :nullable => false, :default => "master"
16
+ property :command, String, :nullable => false, :length => 255, :default => "rake"
17
+ property :public, Boolean, :default => true
18
+ property :building, Boolean, :default => false
19
+ property :created_at, DateTime
20
+ property :updated_at, DateTime
21
+
22
+ has n, :commits, :class_name => "Integrity::Commit"
23
+ has n, :notifiers, :class_name => "Integrity::Notifier"
24
+
25
+ before :save, :set_permalink
26
+ before :destroy, :delete_working_directory
27
+
28
+ validates_is_unique :name
29
+
30
+ def self.only_public_unless(condition)
31
+ if condition
32
+ all
33
+ else
34
+ all(:public => true)
35
+ end
36
+ end
37
+
38
+ def build(commit_identifier="HEAD")
39
+ commit_identifier = head_of_remote_repo if commit_identifier == "HEAD"
40
+ commit = find_or_create_commit_with_identifier(commit_identifier)
41
+ Build.queue(commit)
42
+ end
43
+
44
+ def last_commit
45
+ commits.first(:project_id => id, :order => [:committed_at.desc])
46
+ end
47
+
48
+ def previous_commits
49
+ commits.all(:project_id => id, :order => [:committed_at.desc]).tap {|commits| commits.shift }
50
+ end
51
+
52
+ def status
53
+ last_commit && last_commit.status
54
+ end
55
+
56
+ def human_readable_status
57
+ last_commit && last_commit.human_readable_status
58
+ end
59
+
60
+ def public=(flag)
61
+ attribute_set(:public, case flag
62
+ when "1", "0" then flag == "1"
63
+ else !!flag
64
+ end)
65
+ end
66
+
67
+ private
68
+ def find_or_create_commit_with_identifier(commit_identifier)
69
+ # We abuse +committed_at+ here setting it to Time.now because we use it
70
+ # to sort (for last_commit and previous_commits). I don't like this
71
+ # very much, but for now it's the only solution I can find.
72
+ #
73
+ # This also creates a dependency, as now we *always* have to update the
74
+ # +committed_at+ field after building to ensure the date is correct :(
75
+ #
76
+ # This might also make your commit listings a little jumpy, if some
77
+ # commits change place every time a build finishes =\
78
+ commits.first_or_create({ :identifier => commit_identifier, :project_id => id }, :committed_at => Time.now)
79
+ end
80
+
81
+ def head_of_remote_repo
82
+ SCM.new(uri, branch).head
83
+ end
84
+
85
+ def set_permalink
86
+ self.permalink = (name || "").downcase.
87
+ gsub(/'s/, "s").
88
+ gsub(/&/, "and").
89
+ gsub(/[^a-z0-9]+/, "-").
90
+ gsub(/-*$/, "")
91
+ end
92
+
93
+ def delete_working_directory
94
+ commits.all(:project_id => id).destroy!
95
+ ProjectBuilder.delete_working_directory(self)
96
+ rescue SCM::SCMUnknownError => error
97
+ Integrity.log "Problem while trying to deleting code: #{error}"
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,33 @@
1
+ module Integrity
2
+ class Project
3
+ module Helpers
4
+ module Notifiers
5
+ def notifies?(notifier)
6
+ return false unless notifier = notifiers.first(:name => notifier)
7
+
8
+ notifier.enabled?
9
+ end
10
+
11
+ def enabled_notifiers
12
+ notifiers.all(:enabled => true)
13
+ end
14
+
15
+ def config_for(notifier)
16
+ notifier = notifiers.first(:name => notifier)
17
+ notifier ? notifier.config : {}
18
+ end
19
+
20
+ def update_notifiers(to_enable, config)
21
+ config.each_pair { |name, config|
22
+ notifier = notifiers.first(:name => name)
23
+ notifier ||= notifiers.new(:name => name)
24
+
25
+ notifier.enabled = to_enable.include?(name)
26
+ notifier.config = config
27
+ notifier.save
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end