foca-integrity 0.1.4 → 0.1.6

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 (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