mr-sparkle 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ### 0.0.2
2
+
3
+ * Messages from mr-sparkle now go on stderr, not stdout, just like unicorn's log messages
4
+
5
+ ### 0.0.1
6
+
7
+ * Initial release
data/Rakefile CHANGED
@@ -1 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+ Rake::TestTask.new(:spec) do |t|
4
+ t.test_files = FileList['spec/*_spec.rb']
5
+ t.verbose = true
6
+ end
7
+
8
+ task :default => [:spec]
@@ -16,4 +16,4 @@ OptionParser.new do |opts|
16
16
  end
17
17
  end.parse!
18
18
 
19
- Mr::Sparkle::Daemon.new(options, ARGV).run
19
+ Mr::Sparkle::Daemon.new.run(options, ARGV)
@@ -10,27 +10,22 @@ module Mr
10
10
 
11
11
  class Daemon
12
12
 
13
- def initialize(options, unicorn_args)
14
- @reload_pattern = options[:pattern] || DEFAULT_RELOAD_PATTERN
15
- @full_reload_pattern = options[:full] || DEFAULT_FULL_RELOAD_PATTERN
16
- @unicorn_args = unicorn_args
17
- end
18
-
19
13
  def start_unicorn
20
- Kernel.spawn('unicorn', '-c',
14
+ @unicorn_pid = Kernel.spawn('unicorn', '-c',
21
15
  File.expand_path('mr-sparkle/unicorn.conf.rb',File.dirname(__FILE__)),
22
16
  *@unicorn_args)
23
17
  end
24
18
 
25
- def run()
26
- puts "Reload pattern: #{@reload_pattern}"
27
- @unicorn_pid = start_unicorn
19
+ def run(options, unicorn_args)
20
+ reload_pattern = options[:pattern] || DEFAULT_RELOAD_PATTERN
21
+ full_reload_pattern = options[:full] || DEFAULT_FULL_RELOAD_PATTERN
22
+ @unicorn_args = unicorn_args
28
23
  listener = Listen.to('.', :relative_paths=>true)
29
- listener.filter(@full_reload_pattern)
30
- listener.filter(@reload_pattern)
24
+ listener.filter(full_reload_pattern)
25
+ listener.filter(reload_pattern)
31
26
  listener.change do |modified, added, removed|
32
- puts "File change event detected: #{[modified, added, removed].inspect}"
33
- if (modified + added + removed).index {|f| f =~ @full_reload_pattern}
27
+ $stderr.puts "File change event detected: #{{modified: modified, added: added, removed: removed}.inspect}"
28
+ if (modified + added + removed).index {|f| f =~ full_reload_pattern}
34
29
  # Reload everything. Perhaps this could use the "procedure to
35
30
  # replace a running unicorn executable" described at:
36
31
  # http://unicorn.bogomips.org/SIGNALS.html
@@ -38,7 +33,7 @@ module Mr
38
33
  # and this is just way simpler for now.
39
34
  Process.kill(:QUIT, @unicorn_pid)
40
35
  Process.wait(@unicorn_pid)
41
- @unicorn_pid = start_unicorn.call
36
+ start_unicorn
42
37
  else
43
38
  # Send a HUP to unicorn to tell it to gracefully shut down its
44
39
  # workers
@@ -61,6 +56,13 @@ module Mr
61
56
  # So we need to start it in the background, then keep this thread
62
57
  # alive just so it can wait to be interrupted.
63
58
  listener.start(false)
59
+ # Theoretically, we could have problems if a file changed RIGHT AT
60
+ # THIS POINT, between the time we started the listener and the time
61
+ # we started the unicorn process. But this is just for development,
62
+ # so we're just not going to worry about that.
63
+ start_unicorn
64
+
65
+ # And now we just want to keep the thread alive--we're just waiting around to get interrupted at this point.
64
66
  sleep(99999) while true
65
67
  end
66
68
 
@@ -1,5 +1,5 @@
1
1
  module Mr
2
2
  module Sparkle
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
@@ -25,4 +25,10 @@ Gem::Specification.new do |gem|
25
25
  # https://github.com/guard/guard#efficient-filesystem-handling
26
26
  gem.add_dependency('rb-inotify', '~>0.8.8')
27
27
  gem.add_dependency('rb-fsevent', '~>0.9.2')
28
+
29
+ gem.add_development_dependency('minitest', '~>4.3.3')
30
+ gem.add_development_dependency('minitest-reporters', '~>0.13.1')
31
+ gem.add_development_dependency('minitest-around', '~>0.0.1')
32
+ gem.add_development_dependency('rack', '~>1.4.1')
33
+ gem.add_development_dependency('rake', '~>10.0.3')
28
34
  end
@@ -0,0 +1,5 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gem 'rack', '~>1.4.1'
5
+ gem 'full_reload_test_gem', :path=>'vendor/full_reload_test_gem'
@@ -0,0 +1,5 @@
1
+ require 'bundler/setup'
2
+ require 'full_reload_test_gem'
3
+ # This is loaded just once--subsequent changes to the file won't be reflected...
4
+ string_from_file = IO.read(File.expand_path("file.notwatched",File.dirname(__FILE__)))
5
+ run lambda {|env| [200, {'Content-Type' => 'text/plain'}, [string_from_file+FullReloadTestGem::THINGY]] }
@@ -0,0 +1 @@
1
+ This file was not watched.
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "full_reload_test_gem"
7
+ gem.version = '0.0.1'
8
+ gem.authors = ["Micah Chalmer"]
9
+ gem.email = ["micah@micahchalmer.net"]
10
+ gem.description = %q{This is only here as part of a test fixture}
11
+ gem.summary = %q{This is only here as part of a test fixture}
12
+ gem.homepage = ""
13
+
14
+ gem.files = ['lib/full_reload_test_gem.rb']
15
+ gem.executables = []
16
+ gem.test_files = []
17
+ gem.require_paths = ["lib"]
18
+ end
@@ -0,0 +1,3 @@
1
+ module FullReloadTestGem
2
+ THINGY = "This string is from a gem."
3
+ end
@@ -0,0 +1,4 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gem 'rack', '~>1.4.1'
@@ -0,0 +1 @@
1
+ run lambda {|env| [200, {'Content-Type' => 'text/plain'}, ["Hello, world!"]] }
@@ -0,0 +1,41 @@
1
+ require_relative 'spec_helper'
2
+
3
+ VENDOR_GEM_CODE_PATH = 'vendor/full_reload_test_gem/lib/full_reload_test_gem.rb'
4
+ INITIAL_STRING = "This file was not watched.\nThis string is from a gem."
5
+
6
+ describe "App Runner: Full Reloading" do
7
+ before do
8
+ self.app_template_path = app_template_fixture("full_reload")
9
+ end
10
+
11
+ it "does not reload when a file that isn't watched is changed" do
12
+ start_app
13
+ app_request('/').body.must_equal(INITIAL_STRING)
14
+ change_file('file.notwatched', /not watched/, 'changed')
15
+ # The whole point of this is that a change is NOT fired off--so we
16
+ # can't wait for that. Sleep to give it time to wrongly trigger,
17
+ # if it were going to.
18
+ sleep(3)
19
+ app_request('/').body.must_match(/^This file was not watched/)
20
+ end
21
+
22
+ it "reloads the app when config.ru changes, but not the gem" do
23
+ start_app
24
+ app_request('/').body.must_equal(INITIAL_STRING)
25
+ change_file('file.notwatched', /not watched/, 'changed')
26
+ change_file(VENDOR_GEM_CODE_PATH, /from a gem/, 'also changed but will not be reflected')
27
+ change_file('config.ru', /be reflected.../, 'comment changed just to trigger')
28
+ watch_until_change_detected
29
+ app_request('/').body.must_equal("This file was changed.\nThis string is from a gem.")
30
+ end
31
+
32
+ it "reloads the entire app, including gems, when Gemfile is changed" do
33
+ start_app
34
+ app_request('/').body.must_equal(INITIAL_STRING)
35
+ change_file('file.notwatched', /not watched/, 'changed')
36
+ change_file(VENDOR_GEM_CODE_PATH, /from a gem/, 'from a gem and this time it will show changes')
37
+ change_file('Gemfile', /sample Gemfile/, 'sample changed Gemfile')
38
+ watch_until_change_detected
39
+ app_request('/').body.must_equal("This file was changed.\nThis string is from a gem and this time it will show changes.")
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe "App Runner: Normal Operation" do
4
+ before do
5
+ self.app_template_path = app_template_fixture('hello')
6
+ end
7
+
8
+ it "runs the application" do
9
+ start_app
10
+ app_request('/').body.must_match(/^Hello, world!/)
11
+ end
12
+
13
+ it "reloads the application when a ruby file changes" do
14
+ start_app
15
+ app_request('/').body.must_match(/^Hello, world!/)
16
+ change_file('config.ru', /world/, "Dolly")
17
+ watch_until_change_detected
18
+ app_request('/').body.must_match(/^Hello, Dolly!/)
19
+ end
20
+ end
@@ -0,0 +1,126 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/reporters'
3
+ require 'minitest/around'
4
+ require 'net/http'
5
+ require 'tmpdir'
6
+ require 'timeout'
7
+ require 'bundler'
8
+
9
+ MiniTest::Reporters.use! MiniTest::Reporters::SpecReporter.new
10
+
11
+ # Max # of seconds to wait before declaring the test failed
12
+ WAIT_TIMEOUT=10
13
+
14
+ class ServerAppTest < MiniTest::Spec
15
+
16
+ attr_accessor :app_template_path # Path to the test fixture application to run
17
+
18
+ # Use this to get paths for app_template_path
19
+ def app_template_fixture(name)
20
+ File.expand_path("fixtures/#{name}",File.dirname(__FILE__))
21
+ end
22
+
23
+ attr_reader :running_app_dir
24
+ attr_reader :app_pid
25
+ attr_reader :app_stdout
26
+ attr_reader :app_stderr
27
+
28
+ attr_writer :app_command
29
+ def app_command
30
+ @app_command ||= File.expand_path('../bin/mr-sparkle',File.dirname(__FILE__))
31
+ end
32
+
33
+ attr_writer :app_args
34
+ def app_args
35
+ # We want unicorn not to listen on non-local ports. We also use a different port
36
+ # than the usual test server...
37
+ @app_args || []
38
+ end
39
+
40
+ def start_app
41
+ stop_app
42
+ @app_socket_path = File.expand_path("main_unicorn_socket", running_app_dir)
43
+ Dir.mkdir(File.expand_path('log', running_app_dir))
44
+ @app_stdout, app_stdout_w = IO.pipe
45
+ @app_stderr, app_stderr_w = IO.pipe
46
+ Bundler.with_clean_env do
47
+ @app_pid = Kernel.spawn(
48
+ app_command, *app_args, '--', '-l', @app_socket_path,
49
+ {pgroup: true, chdir: running_app_dir,
50
+ in: '/dev/null', out: app_stdout_w, err: app_stderr_w, pgroup: true})
51
+ end
52
+
53
+ @old_handler = Signal.trap(:EXIT) do
54
+ stop_app
55
+ @old_handler.call if @old_handler
56
+ end
57
+ watch_until {|line| File.exists?(@app_socket_path)}
58
+ end
59
+
60
+ def watch_until(stream=@app_stderr, &block)
61
+ Timeout.timeout(WAIT_TIMEOUT) do
62
+ stream.each_line do |line|
63
+ return true if block.call(line)
64
+ end
65
+ end
66
+ end
67
+
68
+ def app_request(path)
69
+ # Hat tip to:
70
+ # http://code.google.com/p/semicomplete/source/browse/codesamples/ruby-supervisorctl.rb?spec=svn3046&r=3046
71
+ # for how this works
72
+ sock = Net::BufferedIO.new(UNIXSocket.new(@app_socket_path))
73
+ req = Net::HTTP::Get.new(path)
74
+ req.exec(sock, "1.1", path)
75
+ begin
76
+ response = Net::HTTPResponse.read_new(sock)
77
+ end while response.kind_of?(Net::HTTPContinue)
78
+ response.reading_body(sock, req.response_body_permitted?) { }
79
+
80
+ response
81
+ end
82
+
83
+ def stop_app
84
+ if @app_pid
85
+ Process.kill(:TERM, -@app_pid) # Negate the pid to kill the whole process group
86
+ Process.wait(@app_pid)
87
+ @app_pid = nil
88
+ end
89
+
90
+ Signal.trap(:EXIT, @old_handler) if @old_handler
91
+ @old_handler = nil
92
+ end
93
+
94
+ def change_file(file_name, pattern, replacement)
95
+ full_file_name = File.expand_path(file_name,@running_app_dir)
96
+ contents = IO.read(full_file_name)
97
+ IO.write(full_file_name, contents.gsub(pattern,replacement))
98
+ end
99
+
100
+ def watch_until_change_detected(&block)
101
+ watch_until do |line|
102
+ /^File change event detected/.match(line) && (block.nil? || block.call(line))
103
+ end
104
+ # It won't really serve the new version until it starts its new worker process
105
+ watch_until {|line| /worker=\d+ ready/.match(line)}
106
+ end
107
+
108
+ def around
109
+ raise "Must set @app_template_path" if @app_template_path.nil?
110
+ Dir.mktmpdir do |dir|
111
+ FileUtils.cp_r("#{app_template_path}/.", dir)
112
+ @running_app_dir = dir
113
+ begin
114
+ yield
115
+ rescue Exception=>e
116
+ puts e.inspect
117
+ raise
118
+ ensure
119
+ stop_app
120
+ end
121
+ end
122
+ end
123
+
124
+ end
125
+
126
+ MiniTest::Spec.register_spec_type(/^App Runner:/, ServerAppTest)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mr-sparkle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-23 00:00:00.000000000Z
12
+ date: 2012-12-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: unicorn
16
- requirement: &77182970 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: 4.5.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *77182970
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 4.5.0
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: listen
27
- requirement: &77182700 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ~>
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: 0.6.0
33
38
  type: :runtime
34
39
  prerelease: false
35
- version_requirements: *77182700
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.6.0
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: rb-inotify
38
- requirement: &77182400 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ~>
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: 0.8.8
44
54
  type: :runtime
45
55
  prerelease: false
46
- version_requirements: *77182400
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.8.8
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: rb-fsevent
49
- requirement: &77182140 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ~>
@@ -54,7 +69,92 @@ dependencies:
54
69
  version: 0.9.2
55
70
  type: :runtime
56
71
  prerelease: false
57
- version_requirements: *77182140
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 0.9.2
78
+ - !ruby/object:Gem::Dependency
79
+ name: minitest
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 4.3.3
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 4.3.3
94
+ - !ruby/object:Gem::Dependency
95
+ name: minitest-reporters
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 0.13.1
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 0.13.1
110
+ - !ruby/object:Gem::Dependency
111
+ name: minitest-around
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 0.0.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 0.0.1
126
+ - !ruby/object:Gem::Dependency
127
+ name: rack
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: 1.4.1
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: 1.4.1
142
+ - !ruby/object:Gem::Dependency
143
+ name: rake
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ version: 10.0.3
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: 10.0.3
58
158
  description: This gem contains a script to start a Unicorn-based server for your Rack
59
159
  application that reloads your automatically when they are changed, but doesn't incur
60
160
  the penalty of reloading all the gem dependencies. It's based on Jonathan D. Stott's
@@ -67,6 +167,7 @@ extensions: []
67
167
  extra_rdoc_files: []
68
168
  files:
69
169
  - .gitignore
170
+ - CHANGES.md
70
171
  - Gemfile
71
172
  - LICENSE.txt
72
173
  - README.md
@@ -76,6 +177,16 @@ files:
76
177
  - lib/mr-sparkle/unicorn.conf.rb
77
178
  - lib/mr-sparkle/version.rb
78
179
  - mr-sparkle.gemspec
180
+ - spec/fixtures/full_reload/Gemfile
181
+ - spec/fixtures/full_reload/config.ru
182
+ - spec/fixtures/full_reload/file.notwatched
183
+ - spec/fixtures/full_reload/vendor/full_reload_test_gem/full_reload_test_gem.gemspec
184
+ - spec/fixtures/full_reload/vendor/full_reload_test_gem/lib/full_reload_test_gem.rb
185
+ - spec/fixtures/hello/Gemfile
186
+ - spec/fixtures/hello/config.ru
187
+ - spec/full_reload_spec.rb
188
+ - spec/normal_running_spec.rb
189
+ - spec/spec_helper.rb
79
190
  homepage: http://github.com/MicahChalmer/mr-sparkle
80
191
  licenses: []
81
192
  post_install_message:
@@ -96,8 +207,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
207
  version: '0'
97
208
  requirements: []
98
209
  rubyforge_project:
99
- rubygems_version: 1.8.17
210
+ rubygems_version: 1.8.23
100
211
  signing_key:
101
212
  specification_version: 3
102
213
  summary: Runs Unicorn, automatically reloading the application, but not bundled gems.
103
- test_files: []
214
+ test_files:
215
+ - spec/fixtures/full_reload/Gemfile
216
+ - spec/fixtures/full_reload/config.ru
217
+ - spec/fixtures/full_reload/file.notwatched
218
+ - spec/fixtures/full_reload/vendor/full_reload_test_gem/full_reload_test_gem.gemspec
219
+ - spec/fixtures/full_reload/vendor/full_reload_test_gem/lib/full_reload_test_gem.rb
220
+ - spec/fixtures/hello/Gemfile
221
+ - spec/fixtures/hello/config.ru
222
+ - spec/full_reload_spec.rb
223
+ - spec/normal_running_spec.rb
224
+ - spec/spec_helper.rb