slyphon-zookeeper 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,12 @@
1
+ v0.9.3 Event thread shutdown fix, Windows compatibility fix
2
+
3
+ * Use a 'shutdown thread' to coordinate cleanup if close is called from the
4
+ event thread (prevents deadlock)
5
+
6
+ * Default Logger now uses $stderr instead of opening /dev/null [#16]
7
+
8
+ * Gemfile/gemspec/Rakefile refactoring.
9
+
1
10
  v0.9.2 More efficient and simpler wrappers for GIL release
2
11
 
3
12
  * After a code review by Andrew Wason (rectalogic), use a much simpler
data/Gemfile CHANGED
@@ -2,9 +2,16 @@ source :rubygems
2
2
 
3
3
  gemspec
4
4
 
5
- platforms :mri_18 do
6
- gem 'ruby-debug'
5
+ group :test do
6
+ gem "rspec", "~> 2.8.0"
7
+ gem 'flexmock', '~> 0.8.11'
8
+ gem 'eventmachine', '1.0.0.beta.4'
9
+ gem 'evented-spec', '~> 0.9.0'
7
10
  end
8
11
 
12
+ group :development do
13
+ gem 'rake', '~> 0.9.0'
14
+ gem 'pry'
15
+ end
9
16
 
10
17
  # vim:ft=ruby
data/README.markdown CHANGED
@@ -1,21 +1,22 @@
1
- zookeeper
1
+ # zookeeper #
2
2
 
3
- An interface to the Zookeeper distributed configuration server.
3
+ An interface to the Zookeeper cluster coordination server.
4
4
 
5
- For a higher-level interface with a slightly more convenient API and features
6
- such as locks, have a look at [ZK](https://github.com/slyphon/zk) (also
7
- available is [ZK-EventMachine](https://github.com/slyphon/zk-eventmachine) for
8
- those who prefer async).
5
+ For a higher-level interface with a more convenient API and features such as locks, have a look at [ZK](https://github.com/slyphon/zk) (also available is [ZK-EventMachine](https://github.com/slyphon/zk-eventmachine) for those who prefer async).
9
6
 
10
- Unfortunately, since this is a fork of twitter/zookeeper, we don't have our own
11
- Issues tracker. Issues should be filed under [ZK](https://github.com/slyphon/zk/issues).
7
+ ## Big Plans for 1.0 ##
8
+
9
+ The 1.0 release will feature a reorganization of the heirarchy. There will be a single top-level `Zookeeper` namespace (as opposed to the current layout, with 5-6 different top-level constants), and for the next several releases, there will be a backwards compatible require for users that still need to use the old names.
12
10
 
13
11
  ## License
14
12
 
15
- Copyright 2008 Phillip Pearson, and 2010 Twitter, Inc. Licensed under the
16
- MIT License. See the included LICENSE file. Portions copyright 2008-2010
17
- the Apache Software Foundation, licensed under the Apache 2 license, and
18
- used with permission.
13
+ Copyright 2008 Phillip Pearson, and 2010 Twitter, Inc.
14
+ Licensed under the MIT License. See the included LICENSE file.
15
+
16
+ Portions copyright 2008-2010 the Apache Software Foundation, licensed under the
17
+ Apache 2 license, and used with permission.
18
+
19
+ Portions contributed to the open source community by HPDC, L.P.
19
20
 
20
21
  ## Install
21
22
 
@@ -42,10 +43,17 @@ The following methods are initially supported:
42
43
  * get\_acl
43
44
  * set\_acl
44
45
 
45
- All support async callbacks. get, get\_children and stat support both
46
- watchers and callbacks.
46
+ All support async callbacks. get, get\_children and stat support both watchers and callbacks.
47
+
48
+ Calls take a dictionary of parameters. With the exception of set\_acl, the only required parameter is :path. Each call returns a dictionary with at minimum two keys :req\_id and :rc.
49
+
50
+ ### A Bit about this repository ###
51
+
52
+ Twitter's open source office was kind enough to transfer this repository to facilitate development and administration of this repository. The `zookeeper` gem's last three releases were recorded in branches `v0.4.2`, `v0.4.3` and `v0.4.4`. Releases of the `slyphon-zookeeper` gem were cut off of the fork, and unfortunately (due to an oversight on my part) were tagged with unrelated versions. Those were tagged with names `release/0.9.2`.
53
+
54
+ The plan is to keep the `slyphon-zookeeper` tags, and to tag the `zookeeper` releases `twitter/release/0.4.x`.
55
+
56
+ Further work will be carried out on this repository. The `0.9.3` release of the zookeeper gem will be released under the 'zookeeper' name, and will bring the two divergent (conceptual) branches of development together.
57
+
47
58
 
48
- Calls take a dictionary of parameters. With the exception of set\_acl, the
49
- only required parameter is :path. Each call returns a dictionary with at
50
- minimum two keys :req\_id and :rc.
51
59
 
data/Rakefile CHANGED
@@ -2,17 +2,19 @@
2
2
  # ENV.fetch('GEM_HOME').split('@').last
3
3
  # end
4
4
 
5
- GEM_FILES = FileList['slyphon-zookeeper-*.gem']
5
+ GEM_FILES = FileList['*zookeeper-*.gem']
6
6
 
7
7
  namespace :mb do
8
8
  namespace :gems do
9
9
  task :build do
10
- sh "rvm 1.8.7 do gem build slyphon-zookeeper.gemspec"
10
+ sh "rvm 1.8.7 do gem build zookeeper.gemspec"
11
11
  ENV['JAVA_GEM'] = '1'
12
- sh "rvm 1.8.7 do gem build slyphon-zookeeper.gemspec"
12
+ sh "rvm 1.8.7 do gem build zookeeper.gemspec"
13
13
  end
14
14
 
15
- task :push do
15
+ task :push => :build do
16
+ raise "No gemfiles to push!" if GEM_FILES.empty?
17
+
16
18
  GEM_FILES.each do |gem|
17
19
  sh "gem push #{gem}"
18
20
  end
@@ -28,16 +30,8 @@ end
28
30
 
29
31
  gemset_name = 'zookeeper'
30
32
 
31
- directory 'tmp'
32
-
33
33
  # this nonsense w/ tmp and the Gemfile is a bundler optimization
34
34
 
35
- GEMSPEC_LINK = 'tmp/slyphon-zookeeper.gemspec'
36
-
37
- file GEMSPEC_LINK => 'tmp' do
38
- ln_s '../slyphon-zookeeper.gemspec', GEMSPEC_LINK
39
- end
40
-
41
35
  %w[1.8.7 1.9.2 jruby rbx 1.9.3].each do |ns_name|
42
36
  rvm_ruby = (ns_name == 'rbx') ? "rbx-2.0.testing" : ns_name
43
37
 
@@ -50,11 +44,16 @@ end
50
44
  bundle_task_name = "mb:#{ns_name}:bundle_install"
51
45
  rspec_task_name = "mb:#{ns_name}:run_rspec"
52
46
 
53
- phony_gemfile_link_name = File.expand_path("tmp/Gemfile.#{ns_name}")
47
+ phony_gemfile_link_name = "Gemfile.#{ns_name}"
48
+ phony_gemfile_lock_name = "#{phony_gemfile_link_name}.lock"
54
49
 
55
- file phony_gemfile_link_name => GEMSPEC_LINK do
50
+ file phony_gemfile_link_name do
56
51
  # apparently, rake doesn't deal with symlinks intelligently :P
57
- ln_s('../Gemfile', phony_gemfile_link_name) unless File.exists?(phony_gemfile_link_name)
52
+ ln_s('Gemfile', phony_gemfile_link_name) unless File.symlink?(phony_gemfile_link_name)
53
+ end
54
+
55
+ task :clean do
56
+ rm_rf [phony_gemfile_lock_name, phony_gemfile_lock_name]
58
57
  end
59
58
 
60
59
  task create_gemset_name do
@@ -95,12 +94,21 @@ end
95
94
 
96
95
  task "mb:#{ns_name}" => rspec_task_name
97
96
 
98
- task "mb:test_all" => rspec_task_name
97
+ task "mb:test_all_rubies" => rspec_task_name
98
+ end
99
+
100
+ task "mb:test_all" do
101
+ require 'benchmark'
102
+ t = Benchmark.realtime do
103
+ Rake::Task['mb:test_all_rubies'].invoke
104
+ end
105
+
106
+ $stderr.puts "Test run took: #{t} s"
99
107
  end
100
108
 
101
109
  task :default => 'mb:1.9.3'
102
110
 
103
- task :clean do
111
+ task :clobber do
104
112
  rm_rf 'tmp'
105
113
  end
106
114
 
data/ext/extconf.rb CHANGED
@@ -50,7 +50,7 @@ end
50
50
 
51
51
  Dir.chdir(HERE) do
52
52
  if File.exist?("lib")
53
- puts "Zkc already built; run 'rake clean' first if you need to rebuild."
53
+ puts "Zkc already built; run 'rake clobber' in ext/ first if you need to rebuild."
54
54
  else
55
55
  puts "Building zkc."
56
56
 
@@ -104,10 +104,14 @@ class ZookeeperBase
104
104
  end
105
105
 
106
106
  def close
107
- @mutex.synchronize do
108
- stop_dispatch_thread!
109
- @czk.close
107
+ shutdown_thread = Thread.new do
108
+ @mutex.synchronize do
109
+ stop_dispatch_thread!
110
+ @czk.close
111
+ end
110
112
  end
113
+
114
+ shutdown_thread.join unless event_dispatch_thread?
111
115
  end
112
116
 
113
117
  # the C lib doesn't strip the chroot path off of returned path values, which
@@ -222,14 +222,19 @@ class ZookeeperBase
222
222
  end
223
223
 
224
224
  def close
225
- @mutex.synchronize do
226
- return if @_closed
227
- @_closed = true # these are probably unnecessary
228
- @_running = false
229
-
230
- stop_dispatch_thread!
231
- @jzk.close if @jzk
225
+ shutdown_thread = Thread.new do
226
+ @mutex.synchronize do
227
+ unless @_closed
228
+ @_closed = true # these are probably unnecessary
229
+ @_running = false
230
+
231
+ stop_dispatch_thread!
232
+ @jzk.close if @jzk
233
+ end
234
+ end
232
235
  end
236
+
237
+ shutdown_thread.join unless event_dispatch_thread?
233
238
  end
234
239
 
235
240
  def state
data/lib/zookeeper.rb CHANGED
@@ -23,7 +23,7 @@ require 'zookeeper_base'
23
23
 
24
24
  class Zookeeper < ZookeeperBase
25
25
  unless defined?(@@logger)
26
- @@logger = Logger.new('/dev/null').tap { |l| l.level = Logger::FATAL } # UNIX: FOR GREAT JUSTICE !!
26
+ @@logger = Logger.new($stderr).tap { |l| l.level = Logger::ERROR }
27
27
  end
28
28
 
29
29
  def self.logger
@@ -90,9 +90,15 @@ protected
90
90
 
91
91
  # we now release the mutex so that dispatch_next_callback can grab it
92
92
  # to do what it needs to do while delivering events
93
- @dispatch_shutdown_cond.wait
94
-
95
- @dispatcher.join
93
+ #
94
+ # wait for a maximum of 2 sec for dispatcher to signal exit (should be
95
+ # fast)
96
+ @dispatch_shutdown_cond.wait(2)
97
+
98
+ # wait for another 2 sec for the thread to join
99
+ unless @dispatcher.join(2)
100
+ logger.error { "Dispatch thread did not join cleanly, continuing" }
101
+ end
96
102
  @dispatcher = nil
97
103
  end
98
104
  end
@@ -0,0 +1,6 @@
1
+ # this "namespacing" is retarded. fix this in 1.0
2
+ # @private
3
+ module ZookeeperVersion
4
+ VERSION = '0.9.3'
5
+ DRIVER_VERSION = '3.3.5'
6
+ end
@@ -16,52 +16,10 @@ describe 'Zookeeper chrooted' do
16
16
  @zk and @zk.close
17
17
  end
18
18
 
19
-
20
19
  def zk
21
20
  @zk
22
21
  end
23
22
 
24
- def with_open_zk(host='localhost:2181')
25
- z = Zookeeper.new(host)
26
- yield z
27
- ensure
28
- if z
29
- z.close
30
-
31
- wait_until do
32
- begin
33
- !z.connected?
34
- rescue RuntimeError
35
- true
36
- end
37
- end
38
- end
39
- end
40
-
41
- # this is not as safe as the one in ZK, just to be used to clean up
42
- # when we're the only one adjusting a particular path
43
- def rm_rf(z, path)
44
- z.get_children(:path => path).tap do |h|
45
- if h[:rc].zero?
46
- h[:children].each do |child|
47
- rm_rf(z, File.join(path, child))
48
- end
49
- elsif h[:rc] == ZookeeperExceptions::ZNONODE
50
- # no-op
51
- else
52
- raise "Oh noes! unexpected return value! #{h.inspect}"
53
- end
54
- end
55
-
56
- rv = z.delete(:path => path)
57
-
58
- unless (rv[:rc].zero? or rv[:rc] == ZookeeperExceptions::ZNONODE)
59
- raise "oh noes! failed to delete #{path}"
60
- end
61
-
62
- path
63
- end
64
-
65
23
  describe 'non-existent' do
66
24
  describe 'with existing parent' do
67
25
  let(:chroot_path) { '/one-level' }
@@ -1,27 +1,21 @@
1
1
  require 'shared/all_success_return_values'
2
2
 
3
3
  shared_examples_for "connection" do
4
- def ensure_node(path, data)
5
- if zk.stat(:path => path)[:stat].exists?
6
- zk.set(:path => path, :data => data)
7
- else
8
- zk.create(:path => path, :data => data)
9
- end
10
- end
11
4
 
12
5
  before :each do
13
- ensure_node(path, data)
6
+ ensure_node(zk, path, data)
14
7
  end
15
8
 
16
9
  after :each do
17
- ensure_node(path, data)
10
+ ensure_node(zk, path, data)
18
11
  end
19
12
 
20
13
  after :all do
21
14
  Zookeeper.logger.warn "running shared examples after :all"
22
- z = Zookeeper.new("localhost:2181")
23
- z.delete(:path => path)
24
- z.close
15
+
16
+ with_open_zk(connection_string) do |z|
17
+ rm_rf(z, path)
18
+ end
25
19
  end
26
20
 
27
21
  # unfortunately, we can't test w/o exercising other parts of the driver, so
@@ -1003,5 +997,22 @@ shared_examples_for "connection" do
1003
997
  zk.event_dispatch_thread?.should_not be_true
1004
998
  end
1005
999
  end
1000
+
1001
+ describe :close do
1002
+ describe 'from the event dispatch thread' do
1003
+ it %[should not deadlock] do
1004
+
1005
+ evil_cb = lambda do |*|
1006
+ logger.debug { "calling close event_dispatch_thread? #{zk.event_dispatch_thread?}" }
1007
+ zk.close
1008
+ end
1009
+
1010
+ zk.stat(:path => path, :callback => evil_cb)
1011
+
1012
+ wait_until { zk.closed? }
1013
+ zk.should be_closed
1014
+ end
1015
+ end
1016
+ end
1006
1017
  end
1007
1018
 
data/spec/spec_helper.rb CHANGED
@@ -25,6 +25,7 @@ if ENV['ZKRB_NOLOG']
25
25
  Zookeeper.set_debug_level(0)
26
26
  end
27
27
 
28
+
28
29
  module ZookeeperSpecHeleprs
29
30
  class TimeoutError < StandardError; end
30
31
 
@@ -32,6 +33,59 @@ module ZookeeperSpecHeleprs
32
33
  Zookeeper.logger
33
34
  end
34
35
 
36
+ def ensure_node(zk, path, data)
37
+ return if zk.closed?
38
+ if zk.stat(:path => path)[:stat].exists?
39
+ zk.set(:path => path, :data => data)
40
+ else
41
+ zk.create(:path => path, :data => data)
42
+ end
43
+ end
44
+
45
+ def with_open_zk(host='localhost:2181')
46
+ z = Zookeeper.new(host)
47
+ yield z
48
+ ensure
49
+ if z
50
+ unless z.closed?
51
+ z.close
52
+
53
+ wait_until do
54
+ begin
55
+ !z.connected?
56
+ rescue RuntimeError
57
+ true
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # this is not as safe as the one in ZK, just to be used to clean up
65
+ # when we're the only one adjusting a particular path
66
+ def rm_rf(z, path)
67
+ z.get_children(:path => path).tap do |h|
68
+ if h[:rc].zero?
69
+ h[:children].each do |child|
70
+ rm_rf(z, File.join(path, child))
71
+ end
72
+ elsif h[:rc] == ZookeeperExceptions::ZNONODE
73
+ # no-op
74
+ else
75
+ raise "Oh noes! unexpected return value! #{h.inspect}"
76
+ end
77
+ end
78
+
79
+ rv = z.delete(:path => path)
80
+
81
+ unless (rv[:rc].zero? or rv[:rc] == ZookeeperExceptions::ZNONODE)
82
+ raise "oh noes! failed to delete #{path}"
83
+ end
84
+
85
+ path
86
+ end
87
+
88
+
35
89
  # method to wait until block passed returns true or timeout (default is 10 seconds) is reached
36
90
  # raises TiemoutError on timeout
37
91
  def wait_until(timeout=10)
@@ -5,10 +5,10 @@ require 'shared/connection_examples'
5
5
  describe 'Zookeeper' do
6
6
  let(:path) { "/_zktest_" }
7
7
  let(:data) { "underpants" }
8
- let(:zk_host) { 'localhost:2181' }
8
+ let(:connection_string) { 'localhost:2181' }
9
9
 
10
10
  before do
11
- @zk = Zookeeper.new(zk_host)
11
+ @zk = Zookeeper.new(connection_string)
12
12
  end
13
13
 
14
14
  after do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slyphon-zookeeper
3
3
  version: !ruby/object:Gem::Version
4
- hash: 63
4
+ hash: 61
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 2
10
- version: 0.9.2
9
+ - 3
10
+ version: 0.9.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Phillip Pearson
@@ -20,105 +20,21 @@ autorequire:
20
20
  bindir: bin
21
21
  cert_chain: []
22
22
 
23
- date: 2012-04-27 00:00:00 Z
24
- dependencies:
25
- - !ruby/object:Gem::Dependency
26
- name: rspec
27
- prerelease: false
28
- requirement: &id001 !ruby/object:Gem::Requirement
29
- none: false
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- hash: 15
34
- segments:
35
- - 2
36
- - 0
37
- - 0
38
- version: 2.0.0
39
- type: :development
40
- version_requirements: *id001
41
- - !ruby/object:Gem::Dependency
42
- name: flexmock
43
- prerelease: false
44
- requirement: &id002 !ruby/object:Gem::Requirement
45
- none: false
46
- requirements:
47
- - - ~>
48
- - !ruby/object:Gem::Version
49
- hash: 41
50
- segments:
51
- - 0
52
- - 8
53
- - 11
54
- version: 0.8.11
55
- type: :development
56
- version_requirements: *id002
57
- - !ruby/object:Gem::Dependency
58
- name: eventmachine
59
- prerelease: false
60
- requirement: &id003 !ruby/object:Gem::Requirement
61
- none: false
62
- requirements:
63
- - - "="
64
- - !ruby/object:Gem::Version
65
- hash: 2025692189
66
- segments:
67
- - 1
68
- - 0
69
- - 0
70
- - beta
71
- - 4
72
- version: 1.0.0.beta.4
73
- type: :development
74
- version_requirements: *id003
75
- - !ruby/object:Gem::Dependency
76
- name: evented-spec
77
- prerelease: false
78
- requirement: &id004 !ruby/object:Gem::Requirement
79
- none: false
80
- requirements:
81
- - - ~>
82
- - !ruby/object:Gem::Version
83
- hash: 59
84
- segments:
85
- - 0
86
- - 9
87
- - 0
88
- version: 0.9.0
89
- type: :development
90
- version_requirements: *id004
91
- - !ruby/object:Gem::Dependency
92
- name: rake
93
- prerelease: false
94
- requirement: &id005 !ruby/object:Gem::Requirement
95
- none: false
96
- requirements:
97
- - - ~>
98
- - !ruby/object:Gem::Version
99
- hash: 59
100
- segments:
101
- - 0
102
- - 9
103
- - 0
104
- version: 0.9.0
105
- type: :development
106
- version_requirements: *id005
107
- - !ruby/object:Gem::Dependency
108
- name: pry
109
- prerelease: false
110
- requirement: &id006 !ruby/object:Gem::Requirement
111
- none: false
112
- requirements:
113
- - - ">="
114
- - !ruby/object:Gem::Version
115
- hash: 3
116
- segments:
117
- - 0
118
- version: "0"
119
- type: :development
120
- version_requirements: *id006
121
- description: twitter's zookeeper client
23
+ date: 2012-05-03 00:00:00 Z
24
+ dependencies: []
25
+
26
+ description: |+
27
+ A low-level multi-Ruby wrapper around the ZooKeeper API bindings.
28
+ For a friendlier interface, see http://github.com/slyphon/zk
29
+
30
+ Currently supported:
31
+
32
+ MRI: 1.8.7, 1.9.2, 1.9.3
33
+ JRuby: ~> 1.6.7
34
+ Rubinius: 2.0.testing
35
+
36
+ This library uses version 3.3.5 of zookeeper bindings.
37
+
122
38
  email:
123
39
  - slyphon@gmail.com
124
40
  executables: []
@@ -162,8 +78,8 @@ files:
162
78
  - lib/zookeeper/em_client.rb
163
79
  - lib/zookeeper/exceptions.rb
164
80
  - lib/zookeeper/stat.rb
81
+ - lib/zookeeper/version.rb
165
82
  - notes.txt
166
- - slyphon-zookeeper.gemspec
167
83
  - spec/c_zookeeper_spec.rb
168
84
  - spec/chrooted_connection_spec.rb
169
85
  - spec/default_watcher_spec.rb
@@ -180,6 +96,7 @@ files:
180
96
  - test/test_esoteric.rb
181
97
  - test/test_watcher1.rb
182
98
  - test/test_watcher2.rb
99
+ - zookeeper.gemspec
183
100
  homepage: https://github.com/slyphon/zookeeper
184
101
  licenses: []
185
102
 
@@ -213,7 +130,7 @@ rubyforge_project:
213
130
  rubygems_version: 1.8.15
214
131
  signing_key:
215
132
  specification_version: 3
216
- summary: twitter's zookeeper client
133
+ summary: Low level zookeeper client
217
134
  test_files:
218
135
  - spec/c_zookeeper_spec.rb
219
136
  - spec/chrooted_connection_spec.rb
@@ -1,36 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
-
4
- Gem::Specification.new do |s|
5
- s.name = "slyphon-zookeeper"
6
- s.version = '0.9.2'
7
-
8
- s.authors = ["Phillip Pearson", "Eric Maland", "Evan Weaver", "Brian Wickman", "Neil Conway", "Jonathan D. Simms"]
9
- s.email = ["slyphon@gmail.com"]
10
- s.summary = %q{twitter's zookeeper client}
11
- s.description = s.summary
12
- s.homepage = 'https://github.com/slyphon/zookeeper'
13
-
14
- s.add_development_dependency "rspec", ">= 2.0.0"
15
- s.add_development_dependency 'flexmock', '~> 0.8.11'
16
- s.add_development_dependency 'eventmachine', '1.0.0.beta.4'
17
- s.add_development_dependency 'evented-spec', '~> 0.9.0'
18
- s.add_development_dependency 'rake', '~> 0.9.0'
19
- s.add_development_dependency 'pry'
20
-
21
- s.files = `git ls-files`.split("\n")
22
- s.require_paths = ["lib"]
23
-
24
- if ENV['JAVA_GEM'] or defined?(::JRUBY_VERSION)
25
- s.platform = 'java'
26
- s.add_runtime_dependency('slyphon-log4j', '= 1.2.15')
27
- s.add_runtime_dependency('slyphon-zookeeper_jar', '= 3.3.5')
28
- s.require_paths += %w[java]
29
- else
30
- s.require_paths += %w[ext]
31
- s.extensions = 'ext/extconf.rb'
32
- end
33
-
34
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
35
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
36
- end