gforces-integrity 0.1.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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