foca-integrity 0.1.4 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/README.markdown +19 -106
  2. data/Rakefile +53 -25
  3. data/VERSION.yml +1 -1
  4. data/app.rb +24 -153
  5. data/bin/integrity +2 -80
  6. data/config/config.sample.ru +2 -1
  7. data/config/config.sample.yml +4 -0
  8. data/integrity.gemspec +16 -11
  9. data/lib/integrity/build.rb +10 -5
  10. data/lib/integrity/helpers/authorization.rb +33 -0
  11. data/lib/integrity/helpers/breadcrumbs.rb +20 -0
  12. data/lib/integrity/helpers/forms.rb +28 -0
  13. data/lib/integrity/helpers/pretty_output.rb +45 -0
  14. data/lib/integrity/helpers/rendering.rb +14 -0
  15. data/lib/integrity/helpers/resources.rb +13 -0
  16. data/lib/integrity/helpers/urls.rb +47 -0
  17. data/lib/integrity/helpers.rb +16 -0
  18. data/lib/integrity/installer.rb +133 -0
  19. data/lib/integrity/migrations.rb +50 -0
  20. data/lib/integrity/notifier/base.rb +1 -1
  21. data/lib/integrity/notifier.rb +2 -2
  22. data/lib/integrity/project.rb +40 -13
  23. data/lib/integrity/{builder.rb → project_builder.rb} +1 -3
  24. data/lib/integrity/scm/git.rb +4 -4
  25. data/lib/integrity/scm.rb +5 -8
  26. data/lib/integrity.rb +37 -26
  27. data/test/helpers/acceptance/git_helper.rb +99 -0
  28. data/test/helpers/acceptance/textfile_notifier.rb +26 -0
  29. data/test/helpers/acceptance.rb +126 -0
  30. data/test/helpers/expectations/be_a.rb +23 -0
  31. data/test/helpers/expectations/change.rb +90 -0
  32. data/test/helpers/expectations/have.rb +105 -0
  33. data/test/helpers/expectations/have_tag.rb +128 -0
  34. data/test/helpers/expectations/predicates.rb +37 -0
  35. data/test/helpers/expectations.rb +5 -0
  36. data/test/helpers/fixtures.rb +83 -0
  37. data/test/helpers.rb +48 -0
  38. data/views/_build_info.haml +18 -0
  39. data/views/build.haml +1 -1
  40. data/views/error.haml +10 -3
  41. data/views/home.haml +3 -2
  42. data/views/integrity.sass +1 -1
  43. data/views/layout.haml +3 -0
  44. data/views/new.haml +10 -13
  45. data/views/notifier.haml +1 -1
  46. data/views/project.builder +21 -0
  47. data/views/project.haml +9 -13
  48. metadata +38 -11
  49. data/lib/integrity/core_ext/time.rb +0 -13
  50. data/spec/form_field_matchers.rb +0 -91
  51. data/spec/spec_helper.rb +0 -135
  52. data/vendor/sinatra-hacks/lib/hacks.rb +0 -49
  53. data/views/build_info.haml +0 -22
@@ -1,7 +1,5 @@
1
- require 'fileutils'
2
-
3
1
  module Integrity
4
- class Builder
2
+ class ProjectBuilder
5
3
  attr_reader :build_script
6
4
 
7
5
  def initialize(project)
@@ -44,7 +44,7 @@ module Integrity
44
44
 
45
45
  def clone
46
46
  log "Cloning #{uri} to #{working_directory}"
47
- `git clone #{uri} #{working_directory}`
47
+ `git clone #{uri} #{working_directory} &>/dev/null`
48
48
  end
49
49
 
50
50
  def checkout(treeish=nil)
@@ -55,12 +55,12 @@ module Integrity
55
55
  end
56
56
 
57
57
  log "Checking-out #{strategy}"
58
- `cd #{working_directory} && git checkout #{strategy}`
58
+ `cd #{working_directory} && git checkout #{strategy} &>/dev/null`
59
59
  end
60
60
 
61
61
  def pull
62
62
  log "Pull-ing in #{working_directory}"
63
- `cd #{working_directory} && git pull`
63
+ `cd #{working_directory} && git pull &>/dev/null`
64
64
  end
65
65
 
66
66
  def local_branches
@@ -72,7 +72,7 @@ module Integrity
72
72
  end
73
73
 
74
74
  def on_branch?
75
- File.basename(`cd #{working_directory} && git symbolic-ref HEAD`).chomp == branch
75
+ File.basename(`cd #{working_directory} && git symbolic-ref HEAD &>/dev/null`).chomp == branch
76
76
  end
77
77
 
78
78
  def log(message)
data/lib/integrity/scm.rb CHANGED
@@ -5,18 +5,15 @@ module Integrity
5
5
  def self.new(uri, *args)
6
6
  scm_class_for(uri).new(uri, *args)
7
7
  end
8
-
8
+
9
9
  def self.working_tree_path(uri)
10
10
  scm_class_for(uri).working_tree_path(uri)
11
11
  end
12
-
12
+
13
13
  private
14
-
15
- def self.scm_class_for(string)
16
- case string.to_s
17
- when /\.git\/?/ then Git
18
- else raise SCMUnknownError, "could not find any SCM based on string '#{string}'"
19
- end
14
+ def self.scm_class_for(uri)
15
+ return Git if uri.scheme == "git" || uri.path =~ /\.git\/?/
16
+ raise SCMUnknownError, "could not find any SCM based on URI '#{uri.to_s}'"
20
17
  end
21
18
  end
22
19
  end
data/lib/integrity.rb CHANGED
@@ -1,32 +1,35 @@
1
1
  __DIR__ = File.dirname(__FILE__)
2
2
  $:.unshift "#{__DIR__}/integrity", *Dir["#{__DIR__}/../vendor/**/lib"].to_a
3
3
 
4
- require 'rubygems'
5
- require 'json'
6
- require 'dm-core'
7
- require 'dm-validations'
8
- require 'dm-types'
9
- require 'dm-timestamps'
10
- require 'dm-aggregates'
4
+ require "rubygems"
5
+ require "json"
6
+ require "dm-core"
7
+ require "dm-validations"
8
+ require "dm-types"
9
+ require "dm-timestamps"
10
+ require "dm-aggregates"
11
11
 
12
- require 'yaml'
13
- require 'logger'
14
- require 'digest/sha1'
12
+ require "yaml"
13
+ require "logger"
14
+ require "digest/sha1"
15
+ require "timeout"
16
+ require "ostruct"
17
+ require "fileutils"
15
18
 
16
- require 'core_ext/object'
17
- require 'core_ext/string'
18
- require 'core_ext/time'
19
+ require "core_ext/object"
20
+ require "core_ext/string"
19
21
 
20
- require 'project'
21
- require 'build'
22
- require 'builder'
23
- require 'scm'
24
- require 'scm/git'
25
- require 'notifier'
22
+ require "project"
23
+ require "build"
24
+ require "project_builder"
25
+ require "scm"
26
+ require "scm/git"
27
+ require "notifier"
26
28
 
27
29
  module Integrity
28
30
  def self.new(config_file = nil)
29
31
  self.config = config_file unless config_file.nil?
32
+ DataMapper.logger = self.logger if config[:log_debug_info]
30
33
  DataMapper.setup(:default, config[:database_uri])
31
34
  end
32
35
 
@@ -35,12 +38,13 @@ module Integrity
35
38
  end
36
39
 
37
40
  def self.default_configuration
38
- @defaults ||= { :database_uri => 'sqlite3::memory:',
39
- :export_directory => root / 'exports',
41
+ @defaults ||= { :database_uri => "sqlite3::memory:",
42
+ :export_directory => root / "exports",
40
43
  :log => STDOUT,
41
- :base_uri => 'http://localhost:8910',
44
+ :base_uri => "http://localhost:8910",
42
45
  :use_basic_auth => false,
43
- :build_all_commits => true}
46
+ :build_all_commits => true,
47
+ :log_debug_info => false }
44
48
  end
45
49
 
46
50
  def self.config
@@ -50,7 +54,7 @@ module Integrity
50
54
  def self.config=(file)
51
55
  @config = default_configuration.merge(YAML.load_file(file))
52
56
  end
53
-
57
+
54
58
  def self.log(message, &block)
55
59
  logger.info(message, &block)
56
60
  end
@@ -60,9 +64,16 @@ module Integrity
60
64
  logger.formatter = LogFormatter.new
61
65
  end
62
66
  end
63
-
67
+
68
+ def self.version
69
+ @version ||= begin
70
+ file = YAML.load_file(Integrity.root / "VERSION.yml")
71
+ "#{file['major']}.#{file['minor']}.#{file['patch']}"
72
+ end
73
+ end
74
+
64
75
  private
65
-
76
+
66
77
  class LogFormatter < Logger::Formatter
67
78
  def call(severity, time, progname, msg)
68
79
  time.strftime("[%H:%M:%S] ") + msg2str(msg) + "\n"
@@ -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,126 @@
1
+ require 'webrat/rack'
2
+ require 'sinatra'
3
+ require 'sinatra/test'
4
+
5
+ disable :run
6
+ disable :reload
7
+
8
+ Webrat.configuration.instance_variable_set("@mode", :sinatra)
9
+
10
+ module Webrat
11
+ class SinatraSession < Session
12
+ DEFAULT_DOMAIN = "integrity.example.org"
13
+
14
+ def initialize(context = nil)
15
+ super(context)
16
+ @sinatra_test = Sinatra::TestHarness.new
17
+ end
18
+
19
+ %w(get head post put delete).each do |verb|
20
+ class_eval <<-METHOD
21
+ def #{verb}(path, data, headers = {})
22
+ params = data.inject({}) do |data, (key,value)|
23
+ data[key] = Rack::Utils.unescape(value)
24
+ data
25
+ end
26
+ headers['HTTP_HOST'] = DEFAULT_DOMAIN
27
+ @sinatra_test.#{verb}(path, params, headers)
28
+ end
29
+ METHOD
30
+ end
31
+
32
+ def response_body
33
+ @sinatra_test.body
34
+ end
35
+
36
+ def response_code
37
+ @sinatra_test.status
38
+ end
39
+
40
+ private
41
+
42
+ def response
43
+ @sinatra_test.response
44
+ end
45
+
46
+ def current_host
47
+ URI.parse(current_url).host || DEFAULT_DOMAIN
48
+ end
49
+
50
+ def response_location_host
51
+ URI.parse(response_location).host || DEFAULT_DOMAIN
52
+ end
53
+ end
54
+ end
55
+
56
+ require Integrity.root / "app"
57
+ require File.dirname(__FILE__) / "acceptance/git_helper"
58
+
59
+ module AcceptanceHelper
60
+ include FileUtils
61
+
62
+ def export_directory
63
+ Integrity.root / "exports"
64
+ end
65
+
66
+ def enable_auth!
67
+ Integrity.config[:use_basic_auth] = true
68
+ Integrity.config[:admin_username] = "admin"
69
+ Integrity.config[:admin_password] = "test"
70
+ Integrity.config[:hash_admin_password] = false
71
+ end
72
+
73
+ def login_as(user, password)
74
+ def AcceptanceHelper.logged_in; true; end
75
+ basic_auth user, password
76
+ visit "/login"
77
+ Sinatra::Application.before { login_required if AcceptanceHelper.logged_in }
78
+ end
79
+
80
+ def log_out
81
+ def AcceptanceHelper.logged_in; false; end
82
+ @_webrat_session = Webrat::SinatraSession.new(self)
83
+ end
84
+
85
+ def disable_auth!
86
+ Integrity.config[:use_basic_auth] = false
87
+ end
88
+
89
+ def set_and_create_export_directory!
90
+ FileUtils.rm_r(export_directory) if File.directory?(export_directory)
91
+ FileUtils.mkdir(export_directory)
92
+ Integrity.config[:export_directory] = export_directory
93
+ end
94
+
95
+ def setup_log!
96
+ pathname = Integrity.root / "integrity.log"
97
+ FileUtils.rm pathname if File.exists?(pathname)
98
+ Integrity.config[:log] = pathname
99
+ end
100
+ end
101
+
102
+ class Test::Unit::AcceptanceTestCase < Test::Unit::TestCase
103
+ include AcceptanceHelper
104
+ include Test::Storyteller
105
+ include GitHelper
106
+ include Webrat::Methods
107
+ Webrat::Methods.delegate_to_session :response_code
108
+
109
+ before(:all) do
110
+ Integrity.config[:base_uri] = "http://#{Webrat::SinatraSession::DEFAULT_DOMAIN}"
111
+ end
112
+
113
+ before(:each) do
114
+ # ensure each scenario is run in a clean sandbox
115
+ setup_and_reset_database!
116
+ enable_auth!
117
+ setup_log!
118
+ set_and_create_export_directory!
119
+ log_out
120
+ end
121
+
122
+ after(:each) do
123
+ destroy_all_git_repos
124
+ rm_r export_directory if File.directory?(export_directory)
125
+ end
126
+ end
@@ -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