slugforge 4.0.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 (72) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +316 -0
  3. data/bin/slugforge +9 -0
  4. data/lib/slugforge.rb +19 -0
  5. data/lib/slugforge/build.rb +4 -0
  6. data/lib/slugforge/build/build_project.rb +31 -0
  7. data/lib/slugforge/build/export_upstart.rb +85 -0
  8. data/lib/slugforge/build/package.rb +63 -0
  9. data/lib/slugforge/cli.rb +125 -0
  10. data/lib/slugforge/commands.rb +130 -0
  11. data/lib/slugforge/commands/build.rb +20 -0
  12. data/lib/slugforge/commands/config.rb +24 -0
  13. data/lib/slugforge/commands/deploy.rb +383 -0
  14. data/lib/slugforge/commands/project.rb +21 -0
  15. data/lib/slugforge/commands/tag.rb +148 -0
  16. data/lib/slugforge/commands/wrangler.rb +142 -0
  17. data/lib/slugforge/configuration.rb +125 -0
  18. data/lib/slugforge/helper.rb +186 -0
  19. data/lib/slugforge/helper/build.rb +46 -0
  20. data/lib/slugforge/helper/config.rb +37 -0
  21. data/lib/slugforge/helper/enumerable.rb +46 -0
  22. data/lib/slugforge/helper/fog.rb +90 -0
  23. data/lib/slugforge/helper/git.rb +89 -0
  24. data/lib/slugforge/helper/path.rb +76 -0
  25. data/lib/slugforge/helper/project.rb +86 -0
  26. data/lib/slugforge/models/host.rb +233 -0
  27. data/lib/slugforge/models/host/fog_host.rb +33 -0
  28. data/lib/slugforge/models/host/hostname_host.rb +9 -0
  29. data/lib/slugforge/models/host/ip_address_host.rb +9 -0
  30. data/lib/slugforge/models/host_group.rb +65 -0
  31. data/lib/slugforge/models/host_group/aws_tag_group.rb +22 -0
  32. data/lib/slugforge/models/host_group/ec2_instance_group.rb +21 -0
  33. data/lib/slugforge/models/host_group/hostname_group.rb +16 -0
  34. data/lib/slugforge/models/host_group/ip_address_group.rb +16 -0
  35. data/lib/slugforge/models/host_group/security_group_group.rb +20 -0
  36. data/lib/slugforge/models/logger.rb +36 -0
  37. data/lib/slugforge/models/tag_manager.rb +125 -0
  38. data/lib/slugforge/slugins.rb +125 -0
  39. data/lib/slugforge/version.rb +9 -0
  40. data/scripts/post-install.sh +143 -0
  41. data/scripts/unicorn-shepherd.sh +305 -0
  42. data/spec/fixtures/array.yaml +3 -0
  43. data/spec/fixtures/fog_credentials.yaml +4 -0
  44. data/spec/fixtures/invalid_syntax.yaml +1 -0
  45. data/spec/fixtures/one.yaml +3 -0
  46. data/spec/fixtures/two.yaml +3 -0
  47. data/spec/fixtures/valid.yaml +4 -0
  48. data/spec/slugforge/commands/deploy_spec.rb +72 -0
  49. data/spec/slugforge/commands_spec.rb +33 -0
  50. data/spec/slugforge/configuration_spec.rb +200 -0
  51. data/spec/slugforge/helper/fog_spec.rb +81 -0
  52. data/spec/slugforge/helper/git_spec.rb +152 -0
  53. data/spec/slugforge/models/host_group/aws_tag_group_spec.rb +54 -0
  54. data/spec/slugforge/models/host_group/ec2_instance_group_spec.rb +51 -0
  55. data/spec/slugforge/models/host_group/hostname_group_spec.rb +20 -0
  56. data/spec/slugforge/models/host_group/ip_address_group_spec.rb +54 -0
  57. data/spec/slugforge/models/host_group/security_group_group_spec.rb +52 -0
  58. data/spec/slugforge/models/tag_manager_spec.rb +75 -0
  59. data/spec/spec_helper.rb +37 -0
  60. data/spec/support/env.rb +3 -0
  61. data/spec/support/example_groups/configuration_writer.rb +24 -0
  62. data/spec/support/example_groups/helper_provider.rb +10 -0
  63. data/spec/support/factories.rb +18 -0
  64. data/spec/support/fog.rb +15 -0
  65. data/spec/support/helpers.rb +18 -0
  66. data/spec/support/mock_logger.rb +6 -0
  67. data/spec/support/ssh.rb +8 -0
  68. data/spec/support/streams.rb +13 -0
  69. data/templates/foreman/master.conf.erb +21 -0
  70. data/templates/foreman/process-master.conf.erb +2 -0
  71. data/templates/foreman/process.conf.erb +19 -0
  72. metadata +344 -0
@@ -0,0 +1,3 @@
1
+ - a
2
+ - b
3
+
@@ -0,0 +1,4 @@
1
+ default:
2
+ aws_access_key_id: 'aws-access-key-id'
3
+ aws_secret_access_key: 'aws-secret-access-key'
4
+ region: 'us-east-1'
@@ -0,0 +1 @@
1
+ foo: -
@@ -0,0 +1,3 @@
1
+ test_option: 1
2
+ foo:
3
+ bar: "baz"
@@ -0,0 +1,3 @@
1
+ test_option: 2
2
+ foo:
3
+ bar: "baz"
@@ -0,0 +1,4 @@
1
+ test: foo
2
+ foo:
3
+ bar: baz
4
+
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'json'
3
+
4
+ describe Slugforge::Commands::Deploy, :config => true do
5
+
6
+ describe "deployment via" do
7
+ let(:project) { "zany" }
8
+ let(:host) { "example.com" }
9
+ let(:opts) { %w(--force --json) }
10
+ let(:config) { %w() }
11
+ let(:cmd) { ["deploy", operation, target, "--project", project, host, opts, config].flatten }
12
+ let(:stdout) { StringIO.new }
13
+ let(:stderr) { StringIO.new }
14
+ let(:output) { capture(:stdout, stdout) { capture(:stderr, stderr) { Slugforge::Cli.start(cmd) } } }
15
+ let(:result) { JSON.parse(output) }
16
+ let(:s3_root) { "s3://#{helpers.config.values[:slug_bucket]}/#{project}" }
17
+
18
+ shared_examples_for "a deployment method" do
19
+ it "should succeed" do
20
+ begin
21
+ result["success"].should == true
22
+ rescue SystemExit
23
+ fail stderr.string
24
+ end
25
+ end
26
+
27
+ it "has a full slug path" do
28
+ Slugforge::Commands::Deploy.any_instance.should_receive(:deploy).with(anything, anything, hash_including(locator))
29
+ output
30
+ end
31
+ end
32
+
33
+ describe "#file" do
34
+ let(:operation) { "file" }
35
+ let(:target) { "artifact.slug" }
36
+ let(:locator) { {:filename => target} }
37
+
38
+ it_should_behave_like "a deployment method"
39
+ end
40
+
41
+ describe "#tag" do
42
+ let(:operation) { "tag" }
43
+ let(:target) { "testing" }
44
+ let(:slug_name) { "123.slug" }
45
+ let(:locator) { {:s3_url => "#{s3_root}/#{slug_name}"} }
46
+
47
+ context "with existing tagged slug" do
48
+ before(:each) do
49
+ create_slug project, slug_name
50
+ create_tag project, target, slug_name
51
+ end
52
+
53
+ it_should_behave_like "a deployment method"
54
+ end
55
+ end
56
+
57
+ describe "#name" do
58
+ let(:operation) { "name" }
59
+ let(:target) { "123456" }
60
+ let(:locator) { {:s3_url => "#{s3_root}/#{target}.slug"} }
61
+
62
+ context "with existing named slug" do
63
+ before(:each) do
64
+ create_slug project, "#{target}"
65
+ end
66
+
67
+ it_should_behave_like "a deployment method"
68
+ end
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slugforge::Command do
4
+ let(:command_class) { Class.new(Slugforge::Command) }
5
+ let(:stdout) { StringIO.new }
6
+ let(:stderr) { StringIO.new }
7
+ let(:args) { [] }
8
+ let(:command) { capture(:stdout, stdout) { capture(:stderr, stderr) { command_class.start(args) } } }
9
+
10
+ describe "slugin loading" do
11
+ context "in a command" do
12
+ it "should only activate slugins once" do
13
+ Slugforge::SluginManager.any_instance.should_receive(:activate_slugins).once
14
+ command
15
+ end
16
+ end
17
+
18
+ context "in a subcommand" do
19
+ let(:subcommand_class) { Class.new(Slugforge::SubCommand) }
20
+ let(:args) { [ 'sub' ] }
21
+
22
+ before(:each) do
23
+ command_class.desc 'sub [THING]', 'test subcommand'
24
+ command_class.subcommand 'sub', subcommand_class
25
+ end
26
+
27
+ it "should only activate slugins once" do
28
+ Slugforge::SluginManager.any_instance.should_receive(:activate_slugins).once
29
+ command
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,200 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slugforge::Configuration do
4
+
5
+ # Use a subclass that can be safely modified without destroying other specs
6
+ let(:described_class) do
7
+ Class.new(Slugforge::Configuration) do
8
+ self.configuration_files = []
9
+ end
10
+ end
11
+
12
+ subject { described_class.new }
13
+
14
+ before(:each) do
15
+ # Ensure we have a clean configuration object for each test
16
+ described_class.instance_variable_set(:@options, {})
17
+ end
18
+
19
+ describe '.option' do
20
+ it 'defines an instance method with the option name' do
21
+ described_class.option(:test_option, {})
22
+ subject.should respond_to(:test_option)
23
+ end
24
+
25
+ it 'raies an error if a duplicate option is defined' do
26
+ described_class.option(:test_option, {})
27
+ expect { described_class.option(:test_option, {}) }.to raise_error
28
+ end
29
+ end
30
+
31
+ describe 'programatically defined methods' do
32
+ describe 'on the instance' do
33
+ it 'returns the options value' do
34
+ described_class.option(:test_option, {})
35
+ subject.instance_variable_set(:@values, {:test_option => 'test'})
36
+ subject.test_option.should == 'test'
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "loading config" do
42
+ let!(:option) { described_class.option(:test_option, {:default => 'test' }) }
43
+
44
+ it 'applies default values' do
45
+ subject.test_option.should == 'test'
46
+ end
47
+
48
+ context 'from conf files' do
49
+ before(:each) do
50
+ FakeFS.deactivate!
51
+ end
52
+
53
+ it "should fail with invalid YAML" do
54
+ described_class.configuration_files = [ fixture_file("invalid_syntax.yaml") ]
55
+ expect { subject }.to raise_error
56
+ end
57
+
58
+ it "should gracefully handle non-hash contents" do
59
+ described_class.configuration_files = [ fixture_file("array.yaml") ]
60
+ expect { subject }.to_not raise_error
61
+ end
62
+
63
+ context "with a missing configuration file" do
64
+ before(:each) do
65
+ described_class.configuration_files = [ "xxxxx.yaml" ]
66
+ end
67
+
68
+ it "applies the defaults" do
69
+ subject.test_option.should == "test"
70
+ end
71
+ end
72
+
73
+ context "with single configuration file" do
74
+ let!(:option) { described_class.option(:test_option, {:default => 'test', :key => 'test'}) }
75
+
76
+ before(:each) do
77
+ described_class.configuration_files = [ fixture_file("valid.yaml") ]
78
+ end
79
+
80
+ it "should load values mapped by their configuration key" do
81
+ subject.test_option.should == 'foo'
82
+ end
83
+
84
+ context "and a dotted conf key" do
85
+ let!(:option) { described_class.option(:test_option, {:key => 'foo.bar'}) }
86
+
87
+ it "should load nested value" do
88
+ subject.test_option.should == 'baz'
89
+ end
90
+ end
91
+ end
92
+
93
+ context "with multiple configuration files" do
94
+ let!(:option) { described_class.option(:test_option, {:key => 'test_option'}) }
95
+
96
+ before(:each) do
97
+ described_class.configuration_files = [ fixture_file("one.yaml"), fixture_file("two.yaml") ]
98
+ end
99
+
100
+ it "should override the values in the first file with values from the second" do
101
+ subject.test_option.should == 2
102
+ end
103
+
104
+ context "and a dotted conf key" do
105
+ let!(:option) { described_class.option(:test_option, {:key => 'foo.bar'}) }
106
+
107
+ it "should load and override nested values" do
108
+ subject.test_option.should == "baz"
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ context "from ENV" do
115
+ let!(:option) { described_class.option(:test_option, {:default => 'test', :key => 'test_option', :env => 'TEST_OPTION'}) }
116
+
117
+ around(:each) do |example|
118
+ with_env('TEST_OPTION' => 'foo') { example.run }
119
+ end
120
+
121
+ it 'should assign values from mapped variables' do
122
+ subject.test_option.should == 'foo'
123
+ end
124
+
125
+ context "with conf file" do
126
+ before(:each) do
127
+ described_class.configuration_files = [ fixture_file("one.yaml") ]
128
+ end
129
+
130
+ it "should prefer ENV" do
131
+ subject.test_option.should == 'foo'
132
+ end
133
+ end
134
+ end
135
+
136
+ context "from CLI options" do
137
+ let!(:option) { described_class.option(:test_option, {:default => 'test', :option => :'test-option'}) }
138
+
139
+ subject { described_class.new :'test-option' => 'foo' }
140
+
141
+ it "should load value from mapped option" do
142
+ subject.test_option.should == 'foo'
143
+ end
144
+
145
+ context "with ENV present" do
146
+ let!(:option) { described_class.option(:test_option, {:default => 'test', :option => :'test-option', :env => 'TEST_OPTION'}) }
147
+
148
+ around(:each) do |example|
149
+ with_env('TEST_OPTION' => 'bar') { example.run }
150
+ end
151
+
152
+ it 'should prefer setting from CLI' do
153
+ subject.test_option.should == 'foo'
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ describe '#update_with' do
160
+ it 'yields for every option' do
161
+ options = {
162
+ :a => {:key => 'a'},
163
+ :b => {:key => 'b'}
164
+ }
165
+
166
+ described_class.option(:a, options[:a])
167
+ described_class.option(:b, options[:b])
168
+ expect { |b| subject.send(:update_with, &b) }.to yield_successive_args(options[:a], options[:b])
169
+ end
170
+
171
+ context 'when the block returns a value' do
172
+ it 'updates the options value' do
173
+ described_class.option(:a, {})
174
+ subject.instance_variable_set(:@values, {:a => 'false'} )
175
+ blk = proc { |c| 'true' }
176
+ subject.send(:update_with, &blk)
177
+ subject.a.should == 'true'
178
+ end
179
+
180
+ it 'same for "falsey" but not nil' do
181
+ described_class.option(:a, {})
182
+ subject.instance_variable_set(:@values, {:a => 'false'} )
183
+ blk = proc { |c| false }
184
+ subject.send(:update_with, &blk)
185
+ subject.a.should == false
186
+ end
187
+ end
188
+
189
+ context 'when the block does not return a value' do
190
+ it 'leaves the options value as-is' do
191
+ described_class.option(:a, {})
192
+ subject.instance_variable_set(:@values, {:a => 'false'} )
193
+ blk = proc { |c| nil }
194
+ subject.send(:update_with, &blk)
195
+ subject.a.should == 'false'
196
+ end
197
+ end
198
+ end
199
+ end
200
+
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slugforge::Helper::Fog, :config => false do
4
+ let(:command_class) do
5
+ Class.new(Slugforge::Command) do
6
+ include Slugforge::Helper::Config
7
+ include Slugforge::Helper::Fog
8
+
9
+ desc 'test', 'An example command'
10
+ def test; end
11
+ end
12
+ end
13
+
14
+ let(:options) {{ 'aws-access-key-id' => 'abc', 'aws-region' => 'north-pole-9', 'aws-secret-key' => '123', 'slug-bucket' => 'bucket' }}
15
+
16
+ let(:command) { command_class.new [], options }
17
+
18
+ context "#aws_credentials" do
19
+ subject(:aws_credentials) { command.aws_credentials }
20
+
21
+ it "should error without access key" do
22
+ options.delete('aws-access-key-id')
23
+ expect { aws_credentials }.to raise_error(/access key/)
24
+ end
25
+
26
+ it "should error without secret key" do
27
+ options.delete('aws-secret-key')
28
+ expect { aws_credentials }.to raise_error(/secret key/)
29
+ end
30
+
31
+ it "should not error without session token" do
32
+ expect { aws_credentials }.to_not raise_error
33
+ end
34
+
35
+ context "response hash" do
36
+ it "should include key and secret" do
37
+ expect(aws_credentials).to eq({:aws_access_key_id => 'abc', :aws_secret_access_key => '123'})
38
+ end
39
+
40
+ context "when session token is set" do
41
+ let(:options) {{ 'aws-access-key-id' => 'abc', 'aws-secret-key' => '123', 'aws-session-token' => 'xyz' }}
42
+
43
+ it "should include session token" do
44
+ expect(aws_credentials).to eq({
45
+ :aws_session_token => 'xyz',
46
+ :aws_access_key_id => 'abc',
47
+ :aws_secret_access_key => '123'})
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ context "#aws_session" do
54
+ # Fog doesn't have an STS mock yet
55
+ let(:response) { build_sts_response }
56
+ let(:session) { response.body }
57
+ let(:username) { 'user name' }
58
+ let(:sts) { double(::Fog::AWS::STS, :get_federation_token => response) }
59
+
60
+ before(:each) do
61
+ ::Fog::AWS::STS.stub(:new) { sts }
62
+ end
63
+
64
+ it "should exchange aws credentials for a federation session token" do
65
+ command.stub(:username).and_return(username)
66
+ ::Fog::AWS::STS.should_receive(:new).with(command.aws_credentials) { sts }
67
+ sts.should_receive(:get_federation_token).with(username, instance_of(Hash), instance_of(Fixnum))
68
+
69
+ command.aws_session
70
+ end
71
+
72
+ it "should return a session hash matching what fog expects for credentials" do
73
+ expect(command.aws_session).to eq({
74
+ :aws_access_key_id => session['AccessKeyId'],
75
+ :aws_region => options['aws-region'],
76
+ :aws_secret_access_key => session['SecretAccessKey'],
77
+ :aws_session_token => session['SessionToken']
78
+ })
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slugforge::Helper::Git do
4
+ class DummyClass
5
+ attr_accessor :options
6
+ end
7
+
8
+ let(:dummy_class) { DummyClass.new }
9
+ let(:git_account) { 'GitAccount' }
10
+ let(:git_repository) { 'repository' }
11
+ let(:git_branch) { 'master' }
12
+ let(:git_remote) { 'remote' }
13
+ let(:git_url) { "git@github.com:#{git_account}/#{git_repository}.git" }
14
+ let(:git_sha) { '0123456789abcdef0123456789abcdef01234567' }
15
+
16
+ before(:each) do
17
+ dummy_class.extend(subject)
18
+ dummy_class.stub(:error_class).and_return(Thor::Error)
19
+ end
20
+
21
+ context 'not inside work tree' do
22
+ before(:each) { dummy_class.stub(:git_inside_work_tree?).and_return(false) }
23
+
24
+ it '#git_account returns nil' do
25
+ dummy_class.git_account.should be_nil
26
+ end
27
+ it '#git_repository returns nil' do
28
+ dummy_class.git_repository.should be_nil
29
+ end
30
+ it '#git_branch returns nil' do
31
+ dummy_class.git_branch.should be_nil
32
+ end
33
+ it '#git_remote returns nil' do
34
+ dummy_class.git_remote.should be_nil
35
+ end
36
+ it '#git_remote_sha returns nil' do
37
+ dummy_class.git_remote_sha.should be_nil
38
+ end
39
+ it '#git_url returns empty' do
40
+ dummy_class.git_url.should be_empty
41
+ end
42
+ it '#git_sha raises' do
43
+ expect { dummy_class.git_sha }.to raise_error(Thor::Error)
44
+ end
45
+ end
46
+
47
+ context 'inside work tree' do
48
+ before(:each) { dummy_class.stub(:git_inside_work_tree?).and_return(true) }
49
+ context 'git_url is empty' do
50
+ before(:each) { dummy_class.stub(:git_url).and_return('') }
51
+
52
+ it '#git_account returns nil' do
53
+ dummy_class.git_account.should be_nil
54
+ end
55
+ it '#git_repository returns nil' do
56
+ dummy_class.git_repository.should be_nil
57
+ end
58
+ end
59
+
60
+ context 'git_url is valid (SSH)' do
61
+ before(:each) { dummy_class.stub(:git_url).and_return(git_url) }
62
+
63
+ it '#git_accout returns account' do
64
+ dummy_class.git_account.should == git_account
65
+ end
66
+ it '#git_repository returns repository' do
67
+ dummy_class.git_repository.should == git_repository
68
+ end
69
+ end
70
+
71
+ context 'git_url is valid (HTTPS)' do
72
+ let(:git_url) { "https://github.com/#{git_account}/#{git_repository}" }
73
+
74
+ before(:each) { dummy_class.stub(:git_url).and_return(git_url) }
75
+
76
+ it '#git_accout returns account' do
77
+ dummy_class.git_account.should == git_account
78
+ end
79
+ it '#git_repository returns repository' do
80
+ dummy_class.git_repository.should == git_repository
81
+ end
82
+ end
83
+
84
+ describe '#git_remote_sha' do
85
+ before(:each) do
86
+ dummy_class.stub(:git_command).and_return(git_sha)
87
+ dummy_class.stub(:git_url).and_return(git_url)
88
+ dummy_class.stub(:git_branch).and_return(git_branch)
89
+ end
90
+
91
+ context 'no options specified' do
92
+ it 'returns first 10 characters of SHA' do
93
+ dummy_class.git_remote_sha.should == git_sha.slice(0...10)
94
+ end
95
+ end
96
+
97
+ context 'sha_length specified' do
98
+ it 'returns first character when sha_length=1' do
99
+ dummy_class.git_remote_sha(:sha_length => 1).should == git_sha.slice(0...1)
100
+ end
101
+
102
+ it 'returns all characters when sha_length=40' do
103
+ dummy_class.git_remote_sha(:sha_length => 40).should == git_sha.slice(0...40)
104
+ end
105
+
106
+ it 'returns all characters when sha_length>40' do
107
+ dummy_class.git_remote_sha(:sha_length => 50).should == git_sha.slice(0...40)
108
+ end
109
+ end
110
+
111
+ context 'url specified' do
112
+ it 'git_command receives url' do
113
+ test_url = 'test_url'
114
+ dummy_class.git_remote_sha(:url => test_url)
115
+ dummy_class.should have_received(:git_command).with("ls-remote #{test_url} #{git_branch}")
116
+ end
117
+ end
118
+
119
+ context 'branch specified' do
120
+ it 'git_command receives branch' do
121
+ test_branch = 'test_branch'
122
+ dummy_class.git_remote_sha(:branch => test_branch)
123
+ dummy_class.should have_received(:git_command).with("ls-remote #{git_url} #{test_branch}")
124
+ end
125
+ end
126
+ end
127
+
128
+ describe '#git_sha' do
129
+ before(:each) { dummy_class.stub(:git_command).and_return(git_sha) }
130
+
131
+ context 'no options specified' do
132
+ it 'returns first 10 characters of SHA' do
133
+ dummy_class.git_sha.should == git_sha.slice(0...10)
134
+ end
135
+ end
136
+
137
+ context 'sha_length specified' do
138
+ it 'returns first character when sha_length=1' do
139
+ dummy_class.git_sha(:sha_length => 1).should == git_sha.slice(0...1)
140
+ end
141
+
142
+ it 'returns all characters when sha_length=40' do
143
+ dummy_class.git_sha(:sha_length => 40).should == git_sha.slice(0...40)
144
+ end
145
+
146
+ it 'returns all characters when sha_length>40' do
147
+ dummy_class.git_sha(:sha_length => 50).should == git_sha.slice(0...40)
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end