foca-integrity 0.1.8 → 0.1.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,23 +1,24 @@
1
1
  module Integrity
2
2
  class ProjectBuilder
3
- attr_reader :build_script
4
-
5
3
  def initialize(project)
4
+ @project = project
6
5
  @uri = project.uri
7
6
  @build_script = project.command
8
7
  @branch = project.branch
9
8
  @scm = SCM.new(@uri, @branch, export_directory)
10
- @build = Build.new(:project => project)
11
9
  end
12
10
 
13
11
  def build(commit)
14
- Integrity.log "Building #{commit} (#{@branch}) of #{@build.project.name} in #{export_directory} using #{scm_name}"
15
- @scm.with_revision(commit) { run_build_script }
12
+ @commit = commit
13
+ @build = commit.build
14
+ @build.start!
15
+ Integrity.log "Building #{commit.identifier} (#{@branch}) of #{@project.name} in #{export_directory} using #{@scm.name}"
16
+ @scm.with_revision(commit.identifier) { run_build_script }
16
17
  @build
17
18
  ensure
18
- @build.commit_identifier = @scm.commit_identifier(commit)
19
- @build.commit_metadata = @scm.commit_metadata(commit)
20
- @build.save
19
+ @build.complete!
20
+ @commit.update_attributes(@scm.info(commit.identifier))
21
+ send_notifications
21
22
  end
22
23
 
23
24
  def delete_code
@@ -27,18 +28,26 @@ module Integrity
27
28
  end
28
29
 
29
30
  private
30
- def export_directory
31
- Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}"
31
+ def send_notifications
32
+ @project.notifiers.each do |notifier|
33
+ begin
34
+ Integrity.log "Notifying of build #{@commit.short_identifier} using the #{notifier.name} notifier"
35
+ notifier.notify_of_build @commit
36
+ rescue Timeout::Error
37
+ Integrity.log "#{notifier.name} notifier timed out"
38
+ next
39
+ end
40
+ end
32
41
  end
33
42
 
34
- def scm_name
35
- @scm.name
43
+ def export_directory
44
+ Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}"
36
45
  end
37
46
 
38
47
  def run_build_script
39
- Integrity.log "Running `#{build_script}` in #{@scm.working_directory}"
48
+ Integrity.log "Running `#{@build_script}` in #{@scm.working_directory}"
40
49
 
41
- IO.popen "(cd #{@scm.working_directory} && #{build_script}) 2>&1", "r" do |pipe|
50
+ IO.popen "(cd #{@scm.working_directory} && #{@build_script}) 2>&1", "r" do |pipe|
42
51
  @build.output = pipe.read
43
52
  end
44
53
  @build.successful = $?.success?
@@ -9,8 +9,8 @@ module Integrity
9
9
  Git::URI.new(uri).working_tree_path
10
10
  end
11
11
 
12
- def initialize(uri, branch, working_directory)
13
- @uri = uri.to_s
12
+ def initialize(uri, branch, working_directory=nil)
13
+ @uri = uri.to_s
14
14
  @branch = branch.to_s
15
15
  @working_directory = working_directory
16
16
  end
@@ -21,27 +21,28 @@ module Integrity
21
21
  yield
22
22
  end
23
23
 
24
- def commit_identifier(sha1)
25
- `cd #{working_directory} && git show -s --pretty=format:%H #{sha1}`.chomp
24
+ def name
25
+ self.class.name.split("::").last
26
26
  end
27
27
 
28
- def commit_metadata(sha1)
29
- format = %Q(---%n:author: %an <%ae>%n:message: >-%n %s%n:date: %ci%n)
30
- YAML.load(`cd #{working_directory} && git show -s --pretty=format:"#{format}" #{sha1}`)
28
+ def head
29
+ log "Getting the HEAD of '#{uri}' at '#{branch}'"
30
+ `git ls-remote --heads #{uri} #{branch} | awk '{print $1}'`.chomp
31
31
  end
32
-
33
- def name
34
- self.class.name.split("::").last
32
+
33
+ def info(revision)
34
+ format = %Q(---%n:author: %an <%ae>%n:message: >-%n %s%n:committed_at: %ci%n)
35
+ YAML.load(`cd #{working_directory} && git show -s --pretty=format:"#{format}" #{revision}`)
35
36
  end
36
37
 
37
38
  private
38
39
 
39
40
  def fetch_code
40
- clone unless cloned?
41
+ clone unless cloned?
41
42
  checkout unless on_branch?
42
43
  pull
43
44
  end
44
-
45
+
45
46
  def clone
46
47
  log "Cloning #{uri} to #{working_directory}"
47
48
  `git clone #{uri} #{working_directory} &>/dev/null`
@@ -51,11 +52,11 @@ module Integrity
51
52
  strategy = case
52
53
  when treeish then treeish
53
54
  when local_branches.include?(branch) then branch
54
- else "-b #{branch} origin/#{branch}"
55
+ else "origin/#{branch}"
55
56
  end
56
57
 
57
58
  log "Checking-out #{strategy}"
58
- `cd #{working_directory} && git checkout #{strategy} &>/dev/null`
59
+ `cd #{working_directory} && git reset --hard #{strategy} &>/dev/null`
59
60
  end
60
61
 
61
62
  def pull
@@ -13,13 +13,13 @@ module Integrity
13
13
  # ssh://[user@]host.xz/path/to/repo.git/
14
14
  # ssh://[user@]host.xz/~user/path/to/repo.git/
15
15
  # ssh://[user@]host.xz/~/path/to/repo.git
16
- #
17
- # SSH is the default transport protocol over the network. You can optionally
18
- # specify which user to log-in as, and an alternate, scp-like syntax is also
16
+ #
17
+ # SSH is the default transport protocol over the network. You can optionally
18
+ # specify which user to log-in as, and an alternate, scp-like syntax is also
19
19
  # supported
20
20
  #
21
- # Both syntaxes support username expansion, as does the native git protocol,
22
- # but only the former supports port specification. The following three are
21
+ # Both syntaxes support username expansion, as does the native git protocol,
22
+ # but only the former supports port specification. The following three are
23
23
  # identical to the last three above, respectively:
24
24
  #
25
25
  # [user@]host.xz:/path/to/repo.git/
@@ -30,13 +30,13 @@ module Integrity
30
30
  def initialize(uri_string)
31
31
  @uri = Addressable::URI.parse(uri_string)
32
32
  end
33
-
33
+
34
34
  def working_tree_path
35
35
  strip_extension(path).gsub("/", "-")
36
36
  end
37
-
37
+
38
38
  private
39
-
39
+
40
40
  def strip_extension(string)
41
41
  uri = Pathname.new(string)
42
42
  if uri.extname.any?
@@ -46,7 +46,7 @@ module Integrity
46
46
  string
47
47
  end
48
48
  end
49
-
49
+
50
50
  def path
51
51
  path = @uri.path
52
52
  path.gsub(/\~[a-zA-Z0-9]*\//, "").gsub(/^\//, "")
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + "/helpers"
2
+
3
+ class ApiTest < Test::Unit::AcceptanceTestCase
4
+ story <<-EOF
5
+ As a project owner,
6
+ I want to be able to use GitHub as a build triggerer
7
+ So that my project is built everytime I push to the Holy Hub
8
+ EOF
9
+
10
+ def payload(after, branch="master", commits=[])
11
+ payload = { "after" => "#{after}", "ref" => "refs/heads/#{branch}" }
12
+ payload["commits"] = commits if commits.any?
13
+ payload.to_json
14
+ end
15
+
16
+ def post(path, data)
17
+ request_page(path, "post", data)
18
+ end
19
+
20
+ scenario "it only build commits for the branch being monitored" do
21
+ repo = git_repo(:my_test_project) # initial commit && successful commit
22
+ Project.gen(:my_test_project, :uri => repo.path, :branch => "my-branch")
23
+
24
+ basic_auth "admin", "test"
25
+
26
+ lambda do
27
+ post "/my-test-project/push", :payload => payload(repo.head, "master", repo.commits)
28
+ response_code.should == 422
29
+ end.should_not change(Build, :count)
30
+
31
+ visit "/my-test-project"
32
+
33
+ assert_contain("No builds for this project")
34
+ end
35
+
36
+ it "receiving a build request with build_all_commits *enabled* builds all commits, most recent first" do
37
+ Integrity.config[:build_all_commits] = true
38
+
39
+ repo = git_repo(:my_test_project) # initial commit && successful commit
40
+ 3.times do |i|
41
+ repo.add_commit("commit #{i}") do
42
+ system "echo commit_#{i} >> test-file"
43
+ system "git add test-file &>/dev/null"
44
+ end
45
+ end
46
+
47
+ Project.gen(:my_test_project, :uri => repo.path, :command => "echo successful", :branch => "master")
48
+
49
+ basic_auth "admin", "test"
50
+ post "/my-test-project/push", :payload => payload(repo.head, "master", repo.commits)
51
+
52
+ visit "/my-test-project"
53
+
54
+ assert_have_tag("h1", :content => "Built #{git_repo(:my_test_project).short_head} successfully")
55
+ assert_have_tag(".attribution", :content => "by John Doe")
56
+
57
+ assert_have_tag("#previous_builds li", :count => 4)
58
+ end
59
+
60
+ scenario "receiving a build request with build_all_commits *disabled* only builds the last commit passed" do
61
+ Integrity.config[:build_all_commits] = false
62
+
63
+ Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path)
64
+
65
+ git_repo(:my_test_project).add_failing_commit
66
+ git_repo(:my_test_project).add_successful_commit
67
+ head = git_repo(:my_test_project).head
68
+
69
+ basic_auth "admin", "test"
70
+ post "/my-test-project/push", :payload => payload(head, "master", git_repo(:my_test_project).commits)
71
+
72
+ response_code.should == 201
73
+
74
+ visit "/my-test-project"
75
+
76
+ assert_have_tag("h1", :content => "Built #{git_repo(:my_test_project).short_head} successfully")
77
+
78
+ assert_have_no_tag("#previous_builds li")
79
+ end
80
+
81
+ scenario "an unauthenticated request returns a 401" do
82
+ Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path)
83
+ head = git_repo(:my_test_project).head
84
+ post "/my-test-project/push", :payload => payload(head, "master")
85
+
86
+ response_code.should == 401
87
+ end
88
+
89
+ scenario "receiving a build request with an invalid payload returns an Invalid Request error" do
90
+ Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path)
91
+
92
+ basic_auth "admin", "test"
93
+
94
+ post "/my-test-project/push", :payload => "foo"
95
+ response_code.should == 422
96
+ end
97
+ end
@@ -0,0 +1,65 @@
1
+ require File.dirname(__FILE__) + "/helpers"
2
+
3
+ class BrowseProjectBuildsTest < Test::Unit::AcceptanceTestCase
4
+ story <<-EOS
5
+ As a user,
6
+ I want to browse the builds of a project in Integrity
7
+ So I can see the history of a project
8
+ EOS
9
+
10
+ scenario "a project with no builds should say so in a friendly manner" do
11
+ Project.gen(:integrity, :public => true, :commits => [])
12
+
13
+ visit "/integrity"
14
+
15
+ assert_have_no_tag("#last_build")
16
+ assert_have_no_tag("#previous_builds")
17
+ assert_contain("No builds for this project, buddy")
18
+ end
19
+
20
+ scenario "a user can see the last build and the list of previous builds on a project page" do
21
+ Project.gen(:integrity, :public => true, :commits => \
22
+ 3.of { Commit.gen(:successful) } +
23
+ 2.of { Commit.gen(:failed) } +
24
+ 2.of { Commit.gen(:pending) })
25
+
26
+ visit "/integrity"
27
+
28
+ assert_have_tag("#last_build")
29
+
30
+ within("ul#previous_builds") do
31
+ assert_have_tag("li.pending", :count => 2)
32
+ assert_have_tag("li.failed", :count => 2)
33
+ assert_have_tag("li.success", :count => 2)
34
+ end
35
+ end
36
+
37
+ scenario "a user can see details about the last build on the project page" do
38
+ commit = Commit.gen(:successful, :identifier => "7fee3f0014b529e2b76d591a8085d76eab0ff923",
39
+ :author => "Nicolas Sanguinetti <contacto@nicolassanguinetti.info>",
40
+ :message => "No more pending tests :)",
41
+ :committed_at => Time.mktime(2008, 12, 15, 18))
42
+ commit.build.update_attributes(:output => "This is the build output")
43
+ Project.gen(:integrity, :public => true, :commits => [commit])
44
+
45
+ visit "/integrity"
46
+
47
+ assert_have_tag("h1", :content => "Built 7fee3f0 successfully")
48
+ assert_have_tag("blockquote p", :content => "No more pending tests")
49
+ assert_have_tag("span.who", :content => "by: Nicolas Sanguinetti")
50
+ assert_have_tag("span.when", :content => "Dec 15th")
51
+ assert_have_tag("pre.output", :content => "This is the build output")
52
+ end
53
+
54
+ scenario "a user can browse to individual build pages" do
55
+ Project.gen(:integrity, :public => true, :commits => [
56
+ Commit.gen(:successful, :identifier => "7fee3f0014b529e2b76d591a8085d76eab0ff923"),
57
+ Commit.gen(:successful, :identifier => "87e673a83d273ecde121624a3fcfae57a04f2b76")
58
+ ])
59
+
60
+ visit "/integrity"
61
+ click_link(/Build 87e673a/)
62
+
63
+ assert_have_tag("h1", :content => "Built 87e673a successfully")
64
+ end
65
+ end
@@ -0,0 +1,95 @@
1
+ require File.dirname(__FILE__) + "/helpers"
2
+
3
+ class BrowsePublicProjectsTest < Test::Unit::AcceptanceTestCase
4
+ story <<-EOS
5
+ As a user,
6
+ I want to browse public projects on Integrity,
7
+ So I can follow the status of my favorite OSS projects
8
+ EOS
9
+
10
+ scenario "a user can see a public project listed on the home page" do
11
+ Project.gen(:integrity, :public => true)
12
+ Project.gen(:my_test_project, :public => true)
13
+
14
+ visit "/"
15
+
16
+ assert_have_tag("a", :content => "Integrity")
17
+ assert_have_tag("a", :content => "My Test Project")
18
+ end
19
+
20
+ scenario "a user can't see a private project listed on the home page" do
21
+ Project.gen(:my_test_project, :public => false)
22
+ Project.gen(:integrity, :public => true)
23
+
24
+ visit "/"
25
+
26
+ assert_have_no_tag("a", :content => "My Test Project")
27
+ assert_have_tag("a", :content => "Integrity")
28
+ end
29
+
30
+ scenario "a user can see the projects status on the home page" do
31
+ integrity = Project.gen(:integrity, :commits => 3.of { Commit.gen(:successful) })
32
+ test = Project.gen(:my_test_project, :commits => 2.of { Commit.gen(:failed) })
33
+ no_build = Project.gen(:public => true, :building => false)
34
+ building = Project.gen(:public => true, :building => true)
35
+
36
+ visit "/"
37
+
38
+ assert_contain("Built #{integrity.last_commit.short_identifier} successfully")
39
+ assert_contain("Built #{test.last_commit.short_identifier} and failed")
40
+ assert_contain("Never built yet")
41
+ assert_contain("Building!")
42
+ end
43
+
44
+ scenario "a user clicking through a link on the home page for a public project arrives at the project page" do
45
+ Project.gen(:my_test_project, :public => true)
46
+
47
+ visit "/"
48
+ click_link "My Test Project"
49
+
50
+ assert_have_tag("h1", :content => "My Test Project")
51
+ end
52
+
53
+ scenario "a user gets a 404 when browsing to an unexisting project" do
54
+ visit "/who-are-you"
55
+
56
+ response_code.should == 404
57
+ assert_have_tag("h1", :content => "you seem a bit lost, sir")
58
+ end
59
+
60
+ scenario "a user browsing to the url of a private project gets a 401" do
61
+ Project.gen(:my_test_project, :public => false)
62
+
63
+ visit "/my-test-project"
64
+
65
+ response_code.should == 401
66
+ assert_have_tag("h1", :content => "know the password?")
67
+ end
68
+
69
+ scenario "an admin can browse to a private project just fine" do
70
+ Project.gen(:my_test_project, :public => false)
71
+
72
+ login_as "admin", "test"
73
+
74
+ visit "/"
75
+ click_link "My Test Project"
76
+
77
+ assert_have_tag("h1", :content => "My Test Project")
78
+ end
79
+
80
+ scenario "a user browsing to a public project with no build see a friendly message" do
81
+ project = Project.gen(:my_test_project, :public => true)
82
+
83
+ visit "/my-test-project"
84
+ assert_contain("No builds for this project, buddy")
85
+ end
86
+
87
+ scenario "an admin browsing to a private project with no build see a friendly message" do
88
+ Project.gen(:my_test_project, :public => false)
89
+
90
+ login_as "admin", "test"
91
+ visit "/my-test-project"
92
+
93
+ assert_contain("No builds for this project, buddy")
94
+ end
95
+ end
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__) + "/helpers"
2
+ require File.dirname(__FILE__) + "/../helpers/acceptance/textfile_notifier"
3
+
4
+ class BuildNotificationsTest < Test::Unit::AcceptanceTestCase
5
+ story <<-EOS
6
+ As an administrator,
7
+ I want to setup notifiers on my projects
8
+ So that I get alerts with every build
9
+ EOS
10
+
11
+ before(:each) do
12
+ # This is needed before any available notifier is unset
13
+ # in the global #before
14
+ load File.dirname(__FILE__) + "/../helpers/acceptance/textfile_notifier.rb"
15
+ end
16
+
17
+ scenario "an admin sets up a notifier for a project that didn't have any" do
18
+ git_repo(:my_test_project).add_successful_commit
19
+ Project.gen(:my_test_project, :notifiers => [], :uri => git_repo(:my_test_project).path)
20
+ rm_f "/tmp/textfile_notifications.txt"
21
+
22
+ login_as "admin", "test"
23
+
24
+ visit "/my-test-project"
25
+
26
+ click_link "Edit Project"
27
+ check "enabled_notifiers_textfile"
28
+ fill_in "File", :with => "/tmp/textfile_notifications.txt"
29
+ click_button "Update Project"
30
+
31
+ click_button "manual build"
32
+
33
+ notification = File.read("/tmp/textfile_notifications.txt")
34
+ notification.should =~ /=== Built #{git_repo(:my_test_project).short_head} successfully ===/
35
+ notification.should =~ /Build #{git_repo(:my_test_project).head} was successful/
36
+ notification.should =~ %r(http://www.example.com/my-test-project/commits/#{git_repo(:my_test_project).head})
37
+ notification.should =~ /Commit Author: John Doe/
38
+ notification.should =~ /Commit Date: (.+)/
39
+ notification.should =~ /Commit Message: This commit will work/
40
+ notification.should =~ /Build Output:\n\nRunning tests...\n/
41
+ end
42
+ end