foca-integrity 0.1.8 → 0.1.9.0

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 (75) hide show
  1. data/README.markdown +7 -0
  2. data/Rakefile +89 -81
  3. data/config/config.sample.ru +11 -21
  4. data/config/config.sample.yml +15 -12
  5. data/lib/integrity.rb +21 -23
  6. data/lib/integrity/app.rb +138 -0
  7. data/lib/integrity/author.rb +39 -0
  8. data/lib/integrity/build.rb +54 -31
  9. data/lib/integrity/commit.rb +71 -0
  10. data/lib/integrity/helpers.rb +3 -3
  11. data/lib/integrity/helpers/authorization.rb +2 -2
  12. data/lib/integrity/helpers/forms.rb +3 -3
  13. data/lib/integrity/helpers/pretty_output.rb +1 -1
  14. data/lib/integrity/helpers/rendering.rb +6 -1
  15. data/lib/integrity/helpers/resources.rb +9 -3
  16. data/lib/integrity/helpers/urls.rb +15 -13
  17. data/lib/integrity/installer.rb +43 -60
  18. data/lib/integrity/migrations.rb +31 -48
  19. data/lib/integrity/notifier.rb +14 -16
  20. data/lib/integrity/notifier/base.rb +29 -19
  21. data/lib/integrity/notifier/test_helpers.rb +100 -0
  22. data/lib/integrity/project.rb +69 -33
  23. data/lib/integrity/project_builder.rb +23 -14
  24. data/lib/integrity/scm/git.rb +15 -14
  25. data/lib/integrity/scm/git/uri.rb +9 -9
  26. data/test/acceptance/api_test.rb +97 -0
  27. data/test/acceptance/browse_project_builds_test.rb +65 -0
  28. data/test/acceptance/browse_project_test.rb +95 -0
  29. data/test/acceptance/build_notifications_test.rb +42 -0
  30. data/test/acceptance/create_project_test.rb +97 -0
  31. data/test/acceptance/delete_project_test.rb +53 -0
  32. data/test/acceptance/edit_project_test.rb +117 -0
  33. data/test/acceptance/error_page_test.rb +18 -0
  34. data/test/acceptance/helpers.rb +2 -0
  35. data/test/acceptance/installer_test.rb +62 -0
  36. data/test/acceptance/manual_build_project_test.rb +82 -0
  37. data/test/acceptance/notifier_test.rb +109 -0
  38. data/test/acceptance/project_syndication_test.rb +30 -0
  39. data/test/acceptance/stylesheet_test.rb +18 -0
  40. data/test/helpers.rb +59 -27
  41. data/test/helpers/acceptance.rb +19 -64
  42. data/test/helpers/acceptance/email_notifier.rb +55 -0
  43. data/test/helpers/acceptance/git_helper.rb +15 -15
  44. data/test/helpers/acceptance/textfile_notifier.rb +3 -3
  45. data/test/helpers/expectations.rb +0 -1
  46. data/test/helpers/expectations/be_a.rb +4 -4
  47. data/test/helpers/expectations/change.rb +5 -5
  48. data/test/helpers/expectations/have.rb +4 -4
  49. data/test/helpers/expectations/predicates.rb +4 -4
  50. data/test/helpers/fixtures.rb +44 -18
  51. data/test/helpers/initial_migration_fixture.sql +44 -0
  52. data/test/unit/build_test.rb +51 -0
  53. data/test/unit/commit_test.rb +83 -0
  54. data/test/unit/helpers_test.rb +56 -0
  55. data/test/unit/integrity_test.rb +18 -0
  56. data/test/unit/migrations_test.rb +56 -0
  57. data/test/unit/notifier_test.rb +123 -0
  58. data/test/unit/project_builder_test.rb +108 -0
  59. data/test/unit/project_test.rb +282 -0
  60. data/test/unit/scm_test.rb +54 -0
  61. data/views/_commit_info.haml +24 -0
  62. data/views/build.haml +2 -2
  63. data/views/error.haml +4 -3
  64. data/views/home.haml +3 -5
  65. data/views/integrity.sass +19 -6
  66. data/views/new.haml +6 -6
  67. data/views/project.builder +9 -9
  68. data/views/project.haml +14 -12
  69. metadata +98 -116
  70. data/VERSION.yml +0 -4
  71. data/app.rb +0 -137
  72. data/integrity.gemspec +0 -76
  73. data/lib/integrity/core_ext/string.rb +0 -5
  74. data/test/helpers/expectations/have_tag.rb +0 -128
  75. data/views/_build_info.haml +0 -18
@@ -2,13 +2,38 @@ require "dm-migrations"
2
2
  require "migration_runner"
3
3
 
4
4
  module Integrity
5
- class Migrations
6
- include DataMapper::Types
5
+ def self.migrate_db
6
+ setup_initial_migration if pre_migrations?
7
+ Integrity::Migrations.migrate_up!
8
+ end
9
+
10
+ def self.setup_initial_migration
11
+ database_adapter.execute %q(CREATE TABLE "migration_info" ("migration_name" VARCHAR(255));)
12
+ database_adapter.execute %q(INSERT INTO "migration_info" ("migration_name") VALUES ("initial"))
13
+ end
14
+
15
+ def self.pre_migrations?
16
+ !table_exists?("migration_info") &&
17
+ ( table_exists?("integrity_projects") &&
18
+ table_exists?("integrity_builds") &&
19
+ table_exists?("integrity_notifiers") )
20
+ end
21
+
22
+ def self.table_exists?(table_name)
23
+ database_adapter.storage_exists?(table_name)
24
+ end
25
+
26
+ def self.database_adapter
27
+ DataMapper.repository(:default).adapter
28
+ end
29
+
30
+ module Migrations
31
+ # This is what is actually happening:
32
+ # include DataMapper::MigrationRunner
7
33
 
8
- # not strictly necessary, but it makes it clear what is going on.
9
- include DataMapper::MigrationRunner
34
+ include DataMapper::Types
10
35
 
11
- migration 1, :initial, :verbose => false do
36
+ migration 1, :initial, :verbose => true do
12
37
  up do
13
38
  create_table :integrity_projects do
14
39
  column :id, Integer, :serial => true
@@ -46,15 +71,9 @@ module Integrity
46
71
  column :project_id, Integer
47
72
  end
48
73
  end
49
-
50
- down do
51
- drop_table :integrity_notifiers
52
- drop_table :integrity_projects
53
- drop_table :integrity_builds
54
- end
55
74
  end
56
75
 
57
- migration 2, :add_commits, :verbose => false do
76
+ migration 2, :add_commits, :verbose => true do
58
77
  up do
59
78
  class ::Integrity::Build
60
79
  property :commit_identifier, String
@@ -116,42 +135,6 @@ module Integrity
116
135
  :output => build.output)
117
136
  end
118
137
  end
119
-
120
- down do
121
- modify_table :integrity_builds do
122
- add_column :commit_identifier, String, :nullable => false
123
- add_column :commit_metadata, Yaml, :nullable => false
124
- add_column :project_id, Integer
125
- end
126
-
127
- # sqlite hodgepockery
128
- all_builds = Build.all.map {|b| b.freeze }
129
- drop_table :integrity_builds
130
- create_table :integrity_builds do
131
- column :id, Integer, :serial => true
132
- column :output, Text, :nullable => false, :default => ""
133
- column :successful, Boolean, :nullable => false, :default => false
134
- column :commit_identifier, String, :nullable => false
135
- column :commit_metadata, Yaml, :nullable => false
136
- column :created_at, DateTime
137
- column :updated_at, DateTime
138
- column :project_id, Integer
139
- end
140
-
141
- all_builds.each do |build|
142
- Build.create(:project_id => build.commit.project_id,
143
- :output => build.output,
144
- :successful => build.successful,
145
- :commit_identifier => build.commit.identifier,
146
- :commit_metadata => {
147
- :message => build.commit.message,
148
- :author => build.commit.author.full,
149
- :date => commit.committed_at
150
- }.to_yaml)
151
- end
152
-
153
- drop_table :commits
154
- end
155
138
  end
156
139
  end
157
140
  end
@@ -1,38 +1,40 @@
1
+ require File.dirname(__FILE__) + "/notifier/base"
2
+
1
3
  module Integrity
2
4
  class Notifier
3
5
  include DataMapper::Resource
4
-
5
- property :id, Serial
6
+
7
+ property :id, Integer, :serial => true
6
8
  property :name, String, :nullable => false
7
9
  property :config, Yaml, :nullable => false, :lazy => false
8
-
10
+
9
11
  belongs_to :project, :class_name => "Integrity::Project"
10
-
12
+
11
13
  validates_is_unique :name, :scope => :project_id
12
14
  validates_present :project_id
13
-
15
+
14
16
  def self.available
15
- @available ||= constants.map { |name| const_get(name) }.select { |notifier| valid_notifier?(notifier) }
17
+ constants.map { |name| const_get(name) }.select { |notifier| valid_notifier?(notifier) }
16
18
  end
17
-
19
+
18
20
  def self.enable_notifiers(project, enabled, config={})
19
21
  all(:project_id => project).destroy!
20
22
  list_of_enabled_notifiers(enabled).each do |name|
21
23
  create! :project_id => project, :name => name, :config => config[name]
22
24
  end
23
-
25
+
24
26
  end
25
-
27
+
26
28
  def notify_of_build(build)
27
29
  to_const.notify_of_build(build, config)
28
30
  end
29
-
31
+
30
32
  private
31
-
33
+
32
34
  def to_const
33
35
  self.class.module_eval(name)
34
36
  end
35
-
37
+
36
38
  def self.list_of_enabled_notifiers(names)
37
39
  [*names].reject { |n| n.nil? }
38
40
  end
@@ -44,7 +46,3 @@ module Integrity
44
46
  private_class_method :valid_notifier?
45
47
  end
46
48
  end
47
-
48
- require File.dirname(__FILE__) / "notifier" / "base"
49
-
50
- Dir["#{File.dirname(__FILE__)}/notifier/*.rb"].each &method(:require)
@@ -8,47 +8,57 @@ module Integrity
8
8
  def self.to_haml
9
9
  raise NoMethodError, "you need to implement this method in your notifier"
10
10
  end
11
-
12
- attr_reader :build
13
-
14
- def initialize(build, config)
15
- @build = build
11
+
12
+ attr_reader :commit
13
+
14
+ def initialize(commit, config)
15
+ @commit = commit
16
16
  @config = config
17
17
  end
18
-
18
+
19
+ def build
20
+ warn "Notifier::Base#build is deprecated, use Notifier::Base#commit instead"
21
+ commit
22
+ end
23
+
19
24
  def deliver!
20
25
  raise NoMethodError, "you need to implement this method in your notifier"
21
26
  end
22
-
27
+
23
28
  def short_message
24
- "Build #{build.short_commit_identifier} #{build.successful? ? "was successful" : "failed"}"
29
+ commit.human_readable_status
25
30
  end
26
-
31
+
27
32
  def full_message
28
33
  <<-EOM
29
- "Build #{build.commit_identifier} #{build.successful? ? "was successful" : "failed"}"
34
+ "Build #{commit.identifier} #{commit.successful? ? "was successful" : "failed"}"
30
35
 
31
- Commit Message: #{build.commit_message}
32
- Commit Date: #{build.commited_at}
33
- Commit Author: #{build.commit_author.name}
36
+ Commit Message: #{commit.message}
37
+ Commit Date: #{commit.committed_at}
38
+ Commit Author: #{commit.author.name}
34
39
 
35
- Link: #{build_url}
40
+ Link: #{commit_url}
36
41
 
37
42
  Build Output:
38
43
 
39
- #{stripped_build_output}
44
+ #{stripped_commit_output}
40
45
  EOM
41
46
  end
42
-
43
- def build_url
47
+
48
+ def commit_url
44
49
  raise if Integrity.config[:base_uri].nil?
45
- Integrity.config[:base_uri] / build.project.permalink / "builds" / build.commit_identifier
50
+ Integrity.config[:base_uri] / commit.project.permalink / "commits" / commit.identifier
46
51
  end
47
52
 
48
53
  private
49
54
 
55
+ def stripped_commit_output
56
+ commit.output.gsub("\e[0m", "").gsub(/\e\[3[1-7]m/, "")
57
+ end
58
+
50
59
  def stripped_build_output
51
- build.output.gsub("\e[0m", "").gsub(/\e\[3[1-7]m/, "")
60
+ warn "Notifier::Base#stripped_build_output is deprecated, use Notifier::base#stripped_commit_output instead"
61
+ stripped_commit_output
52
62
  end
53
63
  end
54
64
  end
@@ -0,0 +1,100 @@
1
+ require "hpricot"
2
+ require "haml"
3
+ require File.dirname(__FILE__) + "/../../integrity"
4
+
5
+ module Integrity
6
+ class Notifier
7
+ module TestHelpers
8
+ module HpricotAssertions
9
+ # Thanks Harry! http://gist.github.com/39960
10
+
11
+ class HpricotMatcher
12
+ def initialize(html)
13
+ @doc = Hpricot(html)
14
+ end
15
+
16
+ # elements('h1') returns a Hpricot::Elements object with all h1-tags.
17
+ def elements(selector)
18
+ @doc.search(selector)
19
+ end
20
+
21
+ # element('h1') returns Hpricot::Elem with first h1-tag, or nil if
22
+ # none exist.
23
+ def element(selector)
24
+ @doc.at(selector)
25
+ end
26
+
27
+ # tags('h1') returns the inner HTML of all matched elements mathed.
28
+ def tags(selector)
29
+ e = elements(selector)
30
+ e.map {|x| x.inner_html}
31
+ end
32
+
33
+ # tag('h1') returns the inner HTML of the first mached element, or
34
+ # nil if none matched.
35
+ def tag(selector)
36
+ e = element(selector)
37
+ e && e.inner_html
38
+ end
39
+ end
40
+
41
+ def assert_have_tag(html, selector, content=nil)
42
+ matcher = HpricotMatcher.new(html)
43
+ assert_equal content, matcher.tag(selector) if content
44
+ assert matcher.tag(selector)
45
+ end
46
+ end
47
+
48
+ module NotifierFormHelpers
49
+ include HpricotAssertions
50
+
51
+ def form(config={})
52
+ Haml::Engine.new(notifier_class.to_haml).
53
+ render(OpenStruct.new(:config => config))
54
+ end
55
+
56
+ def assert_form_have_tag(selector, options={})
57
+ content = options.delete(:content)
58
+ assert_have_tag(form(options), selector, content)
59
+ end
60
+
61
+ def assert_form_have_option(option, value=nil)
62
+ selector = "input##{notifier.downcase}_notifier_#{option}"
63
+ selector << "[@name='notifiers[#{notifier}][#{option}]']"
64
+ selector << "[@value='#{value}']" if value
65
+
66
+ assert_form_have_tag(selector, option => value)
67
+ end
68
+
69
+ def assert_form_have_options(*options)
70
+ options.each { |option| assert_form_have_option(option) }
71
+ end
72
+ end
73
+
74
+ include NotifierFormHelpers
75
+
76
+ def build
77
+ @build ||= Integrity::Build.gen(:successful)
78
+ end
79
+
80
+ def commit
81
+ @commit ||= build.commit
82
+ end
83
+
84
+ def notifier_class
85
+ Integrity::Notifier.const_get(notifier)
86
+ end
87
+
88
+ def notification
89
+ notifier_class.new(commit).body
90
+ end
91
+
92
+ def setup_database
93
+ DataMapper.setup(:default, "sqlite3::memory:")
94
+ DataMapper.auto_migrate!
95
+
96
+ require File.dirname(__FILE__) + "/../../../test/helpers/fixtures"
97
+ end
98
+ end
99
+ end
100
+ end
@@ -2,7 +2,7 @@ module Integrity
2
2
  class Project
3
3
  include DataMapper::Resource
4
4
 
5
- property :id, Serial
5
+ property :id, Integer, :serial => true
6
6
  property :name, String, :nullable => false
7
7
  property :permalink, String
8
8
  property :uri, URI, :nullable => false, :length => 255
@@ -13,7 +13,7 @@ module Integrity
13
13
  property :created_at, DateTime
14
14
  property :updated_at, DateTime
15
15
 
16
- has n, :builds, :class_name => "Integrity::Build"
16
+ has n, :commits, :class_name => "Integrity::Commit"
17
17
  has n, :notifiers, :class_name => "Integrity::Notifier"
18
18
 
19
19
  before :save, :set_permalink
@@ -30,36 +30,52 @@ module Integrity
30
30
  end
31
31
 
32
32
  def build(commit_identifier="HEAD")
33
- return if building?
34
- update_attributes(:building => true)
35
- ProjectBuilder.new(self).build(commit_identifier)
36
- ensure
37
- update_attributes(:building => false)
38
- send_notifications
33
+ commit_identifier = head_of_remote_repo if commit_identifier == "HEAD"
34
+ commit = find_or_create_commit_with_identifier(commit_identifier)
35
+ commit.queue_build
39
36
  end
40
37
 
41
38
  def push(payload)
42
- payload = JSON.parse(payload || "")
43
-
44
- if Integrity.config[:build_all_commits]
45
- payload["commits"].sort_by { |commit| Time.parse(commit["timestamp"]) }.each do |commit|
46
- build(commit["id"]) if payload["ref"] =~ /#{branch}/
39
+ payload = parse_payload(payload)
40
+ raise ArgumentError unless valid_payload?(payload)
41
+
42
+ commits =
43
+ if Integrity.config[:build_all_commits]
44
+ payload["commits"]
45
+ else
46
+ [ payload["commits"].first ]
47
47
  end
48
- else
49
- build(payload["after"]) if payload["ref"] =~ /#{branch}/
48
+
49
+ commits.each do |commit_data|
50
+ create_commit_from(commit_data)
51
+ build(commit_data["id"])
50
52
  end
51
53
  end
52
54
 
55
+ def last_commit
56
+ commits.first(:project_id => id, :order => [:committed_at.desc])
57
+ end
58
+
53
59
  def last_build
54
- all_builds.first
60
+ warn "Project#last_build is deprecated, use Project#last_commit"
61
+ last_commit
62
+ end
63
+
64
+ def previous_commits
65
+ commits.all(:project_id => id, :order => [:committed_at.desc]).tap {|commits| commits.shift }
55
66
  end
56
67
 
57
68
  def previous_builds
58
- all_builds.tap {|builds| builds.shift }
69
+ warn "Project#previous_builds is deprecated, use Project#previous_commits"
70
+ previous_commits
59
71
  end
60
72
 
61
73
  def status
62
- last_build && last_build.status
74
+ last_commit && last_commit.status
75
+ end
76
+
77
+ def human_readable_status
78
+ last_commit && last_commit.human_readable_status
63
79
  end
64
80
 
65
81
  def public=(flag)
@@ -70,12 +86,12 @@ module Integrity
70
86
  end
71
87
 
72
88
  def config_for(notifier)
73
- notifier = notifiers.first(:name => notifier.to_s.split(/::/).last)
89
+ notifier = notifiers.first(:name => notifier.to_s.split(/::/).last, :project_id => id)
74
90
  notifier.blank? ? {} : notifier.config
75
91
  end
76
92
 
77
93
  def notifies?(notifier)
78
- !notifiers.first(:name => notifier.to_s.split(/::/).last).blank?
94
+ !notifiers.first(:name => notifier.to_s.split(/::/).last, :project_id => id).blank?
79
95
  end
80
96
 
81
97
  def enable_notifiers(*args)
@@ -83,6 +99,30 @@ module Integrity
83
99
  end
84
100
 
85
101
  private
102
+ def find_or_create_commit_with_identifier(commit_identifier)
103
+ # We abuse +committed_at+ here setting it to Time.now because we use it
104
+ # to sort (for last_commit and previous_commits). I don't like this
105
+ # very much, but for now it's the only solution I can find.
106
+ #
107
+ # This also creates a dependency, as now we *always* have to update the
108
+ # +committed_at+ field after building to ensure the date is correct :(
109
+ #
110
+ # This might also make your commit listings a little jumpy, if some
111
+ # commits change place every time a build finishes =\
112
+ commits.first_or_create({ :identifier => commit_identifier, :project_id => id }, :committed_at => Time.now)
113
+ end
114
+
115
+ def head_of_remote_repo
116
+ SCM.new(uri, branch).head
117
+ end
118
+
119
+ def create_commit_from(data)
120
+ commits.create(:identifier => data["id"],
121
+ :author => "#{data["author"]["name"]} <#{data["author"]["email"]}>",
122
+ :message => data["message"],
123
+ :committed_at => data["timestamp"])
124
+ end
125
+
86
126
  def set_permalink
87
127
  self.permalink = (name || "").downcase.
88
128
  gsub(/'s/, "s").
@@ -92,26 +132,22 @@ module Integrity
92
132
  end
93
133
 
94
134
  def delete_code
95
- builds.destroy!
135
+ commits.all(:project_id => id).destroy!
96
136
  ProjectBuilder.new(self).delete_code
97
137
  rescue SCM::SCMUnknownError => error
98
138
  Integrity.log "Problem while trying to deleting code: #{error}"
99
139
  end
100
140
 
101
- def send_notifications
102
- notifiers.each do |notifier|
103
- begin
104
- Integrity.log "Notifying of build #{last_build.short_commit_identifier} using the #{notifier.name} notifier"
105
- notifier.notify_of_build last_build
106
- rescue Timeout::Error
107
- Integrity.log "#{notifier.name} notifier timed out"
108
- next
109
- end
110
- end
141
+ def valid_payload?(payload)
142
+ payload && payload["ref"].to_s.include?(branch) &&
143
+ !payload["commits"].nil? &&
144
+ !payload["commits"].to_a.empty?
111
145
  end
112
146
 
113
- def all_builds
114
- builds.all.sort_by {|b| b.commited_at }.reverse
147
+ def parse_payload(payload)
148
+ JSON.parse(payload.to_s)
149
+ rescue JSON::ParserError
150
+ false
115
151
  end
116
152
  end
117
153
  end