spring-jruby 1.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +364 -0
  4. data/bin/spring +49 -0
  5. data/lib/spring-jruby/application.rb +283 -0
  6. data/lib/spring-jruby/application/boot.rb +19 -0
  7. data/lib/spring-jruby/binstub.rb +13 -0
  8. data/lib/spring-jruby/boot.rb +9 -0
  9. data/lib/spring-jruby/client.rb +46 -0
  10. data/lib/spring-jruby/client/binstub.rb +188 -0
  11. data/lib/spring-jruby/client/command.rb +18 -0
  12. data/lib/spring-jruby/client/help.rb +62 -0
  13. data/lib/spring-jruby/client/rails.rb +34 -0
  14. data/lib/spring-jruby/client/run.rb +167 -0
  15. data/lib/spring-jruby/client/status.rb +30 -0
  16. data/lib/spring-jruby/client/stop.rb +22 -0
  17. data/lib/spring-jruby/client/version.rb +11 -0
  18. data/lib/spring-jruby/command_wrapper.rb +82 -0
  19. data/lib/spring-jruby/commands.rb +51 -0
  20. data/lib/spring-jruby/commands/rails.rb +112 -0
  21. data/lib/spring-jruby/commands/rake.rb +30 -0
  22. data/lib/spring-jruby/configuration.rb +60 -0
  23. data/lib/spring-jruby/env.rb +109 -0
  24. data/lib/spring-jruby/errors.rb +36 -0
  25. data/lib/spring-jruby/impl/application.rb +7 -0
  26. data/lib/spring-jruby/impl/application_manager.rb +7 -0
  27. data/lib/spring-jruby/impl/fork/application.rb +69 -0
  28. data/lib/spring-jruby/impl/fork/application_manager.rb +137 -0
  29. data/lib/spring-jruby/impl/fork/run.rb +47 -0
  30. data/lib/spring-jruby/impl/pool/application.rb +47 -0
  31. data/lib/spring-jruby/impl/pool/application_manager.rb +226 -0
  32. data/lib/spring-jruby/impl/pool/run.rb +27 -0
  33. data/lib/spring-jruby/impl/run.rb +7 -0
  34. data/lib/spring-jruby/io_helpers.rb +92 -0
  35. data/lib/spring-jruby/json.rb +626 -0
  36. data/lib/spring-jruby/platform.rb +23 -0
  37. data/lib/spring-jruby/process_title_updater.rb +65 -0
  38. data/lib/spring-jruby/server.rb +130 -0
  39. data/lib/spring-jruby/sid.rb +42 -0
  40. data/lib/spring-jruby/test.rb +18 -0
  41. data/lib/spring-jruby/test/acceptance_test.rb +371 -0
  42. data/lib/spring-jruby/test/application.rb +217 -0
  43. data/lib/spring-jruby/test/application_generator.rb +134 -0
  44. data/lib/spring-jruby/test/rails_version.rb +40 -0
  45. data/lib/spring-jruby/test/watcher_test.rb +167 -0
  46. data/lib/spring-jruby/version.rb +3 -0
  47. data/lib/spring-jruby/watcher.rb +30 -0
  48. data/lib/spring-jruby/watcher/abstract.rb +86 -0
  49. data/lib/spring-jruby/watcher/polling.rb +61 -0
  50. metadata +137 -0
@@ -0,0 +1,217 @@
1
+ require "spring-jruby/env"
2
+
3
+ module Spring
4
+ module Test
5
+ class Application
6
+ DEFAULT_TIMEOUT = ENV['CI'] ? 30 : 10
7
+
8
+ attr_reader :root, :spring_env
9
+
10
+ def initialize(root)
11
+ @root = Pathname.new(root)
12
+ @spring_env = Spring::Env.new(root)
13
+ end
14
+
15
+ def exists?
16
+ root.exist?
17
+ end
18
+
19
+ def stdout
20
+ @stdout ||= IO.pipe
21
+ end
22
+
23
+ def stderr
24
+ @stderr ||= IO.pipe
25
+ end
26
+
27
+ def log_file
28
+ @log_file ||= path("tmp/spring.log").open("w+")
29
+ end
30
+
31
+ def env
32
+ @env ||= {
33
+ "GEM_HOME" => gem_home.to_s,
34
+ "GEM_PATH" => gem_home.to_s,
35
+ "HOME" => user_home.to_s,
36
+ "RAILS_ENV" => nil,
37
+ "RACK_ENV" => nil,
38
+ "SPRING_LOG" => log_file.path
39
+ }
40
+ end
41
+
42
+ def path(addition)
43
+ root.join addition
44
+ end
45
+
46
+ def gemfile
47
+ path "Gemfile"
48
+ end
49
+
50
+ def gem_home
51
+ path "../gems/#{RUBY_VERSION}"
52
+ end
53
+
54
+ def user_home
55
+ path "user_home"
56
+ end
57
+
58
+ def spring
59
+ gem_home.join "bin/spring"
60
+ end
61
+
62
+ def rails_version
63
+ @rails_version ||= RailsVersion.new(gemfile.read.match(/gem 'rails', '(.*)'/)[1])
64
+ end
65
+
66
+ def spring_test_command
67
+ "#{rails_version.test_command} #{test}"
68
+ end
69
+
70
+ def stop_spring
71
+ run "#{spring} stop"
72
+ rescue Errno::ENOENT
73
+ end
74
+
75
+ def test
76
+ path "test/#{rails_version.controller_tests_dir}/posts_controller_test.rb"
77
+ end
78
+
79
+ def controller
80
+ path "app/controllers/posts_controller.rb"
81
+ end
82
+
83
+ def application_config
84
+ path "config/application.rb"
85
+ end
86
+
87
+ def spring_config
88
+ path "config/spring.rb"
89
+ end
90
+
91
+ def run(command, opts = {})
92
+ start_time = Time.now
93
+
94
+ Bundler.with_clean_env do
95
+ Process.spawn(
96
+ env,
97
+ command.to_s,
98
+ out: stdout.last,
99
+ err: stderr.last,
100
+ in: :close,
101
+ chdir: root.to_s,
102
+ )
103
+ end
104
+
105
+ _, status = Timeout.timeout(opts.fetch(:timeout, DEFAULT_TIMEOUT)) { Process.wait2 }
106
+
107
+ if pid = spring_env.pid
108
+ @server_pid = pid
109
+ lines = `ps -A -o ppid= -o pid= | egrep '^\\s*#{@server_pid}'`.lines
110
+ @application_pids = lines.map { |l| l.split.last.to_i }
111
+ end
112
+
113
+ output = read_streams
114
+ puts dump_streams(command, output) if ENV["SPRING_DEBUG"]
115
+
116
+ @times << (Time.now - start_time) if @times
117
+
118
+ output.merge(status: status, command: command)
119
+ rescue Timeout::Error => e
120
+ raise e, "Output:\n\n#{dump_streams(command, read_streams)}"
121
+ end
122
+
123
+ def with_timing
124
+ @times = []
125
+ yield
126
+ ensure
127
+ @times = nil
128
+ end
129
+
130
+ def last_time
131
+ @times.last
132
+ end
133
+
134
+ def first_time
135
+ @times.first
136
+ end
137
+
138
+ def timing_ratio
139
+ last_time / first_time
140
+ end
141
+
142
+ def read_streams
143
+ {
144
+ stdout: read_stream(stdout.first),
145
+ stderr: read_stream(stderr.first),
146
+ log: read_stream(log_file)
147
+ }
148
+ end
149
+
150
+ def read_stream(stream)
151
+ output = ""
152
+ while IO.select([stream], [], [], 0.5) && !stream.eof?
153
+ output << stream.readpartial(10240)
154
+ end
155
+ output
156
+ end
157
+
158
+ def dump_streams(command, streams)
159
+ output = "$ #{command}\n"
160
+
161
+ streams.each do |name, stream|
162
+ unless stream.chomp.empty?
163
+ output << "--- #{name} ---\n"
164
+ output << "#{stream.chomp}\n"
165
+ end
166
+ end
167
+
168
+ output << "\n"
169
+ output
170
+ end
171
+
172
+ def debug(artifacts)
173
+ artifacts = artifacts.dup
174
+ artifacts.delete :status
175
+ dump_streams(artifacts.delete(:command), artifacts)
176
+ end
177
+
178
+ def await_reload
179
+ raise "no pid" if @application_pids.nil? || @application_pids.empty?
180
+
181
+ Timeout.timeout(DEFAULT_TIMEOUT) do
182
+ sleep 0.1 while @application_pids.any? { |p| process_alive?(p) }
183
+ end
184
+ end
185
+
186
+ def run!(command, options = {})
187
+ attempts = (options.delete(:retry) || 0) + 1
188
+ artifacts = nil
189
+
190
+ until attempts == 0 || artifacts && artifacts[:status].success?
191
+ artifacts = run(command, options)
192
+ attempts -= 1
193
+ end
194
+
195
+ if artifacts[:status].success?
196
+ artifacts
197
+ else
198
+ raise "command failed\n\n#{debug(artifacts)}"
199
+ end
200
+ end
201
+
202
+ def bundle
203
+ run! "(gem list bundler | grep bundler) || gem install bundler", timeout: nil, retry: 2
204
+ run! "bundle check || bundle update --retry=2", timeout: nil
205
+ end
206
+
207
+ private
208
+
209
+ def process_alive?(pid)
210
+ Process.kill 0, pid
211
+ true
212
+ rescue Errno::ESRCH
213
+ false
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,134 @@
1
+ module Spring
2
+ module Test
3
+ class ApplicationGenerator
4
+ attr_reader :version_constraint, :version, :application
5
+
6
+ def initialize(version_constraint)
7
+ @version_constraint = version_constraint
8
+ @version = RailsVersion.new(version_constraint.split(' ').last)
9
+ @application = Application.new(root)
10
+ @bundled = false
11
+ end
12
+
13
+ def test_root
14
+ Pathname.new Spring::Test.root
15
+ end
16
+
17
+ def root
18
+ test_root.join("apps/rails-#{version.major}-#{version.minor}-spring-#{Spring::VERSION}")
19
+ end
20
+
21
+ def system(command)
22
+ if ENV["SPRING_DEBUG"]
23
+ puts "$ #{command}\n"
24
+ else
25
+ command = "(#{command}) > /dev/null"
26
+ end
27
+
28
+ Kernel.system(command) or raise "command failed: #{command}"
29
+ puts if ENV["SPRING_DEBUG"]
30
+ end
31
+
32
+ def generate
33
+ Bundler.with_clean_env { generate_files }
34
+ install_spring
35
+ generate_scaffold
36
+ end
37
+
38
+ # Sporadic SSL errors keep causing test failures so there are anti-SSL workarounds here
39
+ def generate_files
40
+ system("gem list '^rails$' --installed --version '#{version_constraint}' || " \
41
+ "gem install rails --clear-sources --source http://rubygems.org --version '#{version_constraint}'")
42
+
43
+ @version = RailsVersion.new(`ruby -e 'puts Gem::Specification.find_by_name("rails", "#{version_constraint}").version'`.chomp)
44
+
45
+ skips = %w(--skip-bundle --skip-javascript --skip-sprockets)
46
+ skips << "--skip-spring" if version.bundles_spring?
47
+
48
+ system("rails _#{version}_ new #{application.root} #{skips.join(' ')}")
49
+ raise "application generation failed" unless application.exists?
50
+
51
+ FileUtils.mkdir_p(application.gem_home)
52
+ FileUtils.mkdir_p(application.user_home)
53
+ FileUtils.rm_rf(application.path("test/performance"))
54
+
55
+ append_to_file(application.gemfile, "gem 'spring', '#{Spring::VERSION}'")
56
+
57
+ if version.needs_testunit?
58
+ append_to_file(application.gemfile, "gem 'spring-commands-testunit'")
59
+ end
60
+
61
+ rewrite_file(application.gemfile) do |c|
62
+ c.sub!("https://rubygems.org", "http://rubygems.org")
63
+ c.gsub!(/(gem '(byebug|web-console|sdoc|jbuilder)')/, "# \\1")
64
+ c
65
+ end
66
+
67
+
68
+ if application.path("bin").exist?
69
+ FileUtils.cp_r(application.path("bin"), application.path("bin_original"))
70
+ end
71
+ end
72
+
73
+ def rewrite_file(file)
74
+ File.write(file, yield(file.read))
75
+ end
76
+
77
+ def append_to_file(file, add)
78
+ rewrite_file(file) { |c| c << "#{add}\n" }
79
+ end
80
+
81
+ def generate_if_missing
82
+ generate unless application.exists?
83
+ end
84
+
85
+ def install_spring
86
+ return if @installed
87
+
88
+ build_and_install_gems
89
+
90
+ application.bundle
91
+
92
+ FileUtils.rm_rf application.path("bin")
93
+
94
+ if application.path("bin_original").exist?
95
+ FileUtils.cp_r application.path("bin_original"), application.path("bin")
96
+ end
97
+
98
+ application.run! "#{application.spring} binstub --all"
99
+ @installed = true
100
+ end
101
+
102
+ def manually_built_gems
103
+ %w(spring)
104
+ end
105
+
106
+ def build_and_install_gems
107
+ manually_built_gems.each do |name|
108
+ spec = Gem::Specification.find_by_name(name)
109
+
110
+ FileUtils.cd(spec.gem_dir) do
111
+ FileUtils.rm(Dir.glob("#{name}-*.gem"))
112
+ system("gem build #{name}.gemspec 2>&1")
113
+ end
114
+
115
+ application.run! "gem install #{spec.gem_dir}/#{name}-*.gem", timeout: nil
116
+ end
117
+ end
118
+
119
+ def copy_to(path)
120
+ system("rm -rf #{path}")
121
+ system("cp -r #{application.root} #{path}")
122
+ end
123
+
124
+ def generate_scaffold
125
+ application.run! "bundle exec rails g scaffold post title:string"
126
+ application.run! "bundle exec rake db:migrate db:test:clone"
127
+ end
128
+
129
+ def gemspec(name)
130
+ "#{Gem::Specification.find_by_name(name).gem_dir}/#{name}.gemspec"
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,40 @@
1
+ module Spring
2
+ module Test
3
+ class RailsVersion
4
+ attr_reader :version
5
+
6
+ def initialize(string)
7
+ @version = Gem::Version.new(string)
8
+ end
9
+
10
+ def rails_3?
11
+ version < Gem::Version.new("4.0.0")
12
+ end
13
+ alias needs_testunit? rails_3?
14
+
15
+ def test_command
16
+ needs_testunit? ? 'bin/testunit' : 'bin/rake test'
17
+ end
18
+
19
+ def controller_tests_dir
20
+ rails_3? ? 'functional' : 'controllers'
21
+ end
22
+
23
+ def bundles_spring?
24
+ version.segments.take(2) == [4, 1] || version > Gem::Version.new("4.1")
25
+ end
26
+
27
+ def major
28
+ version.segments[0]
29
+ end
30
+
31
+ def minor
32
+ version.segments[1]
33
+ end
34
+
35
+ def to_s
36
+ version.to_s
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,167 @@
1
+ require "tmpdir"
2
+ require "fileutils"
3
+ require "timeout"
4
+ require "active_support/core_ext/numeric/time"
5
+
6
+ module Spring
7
+ module Test
8
+ class WatcherTest < ActiveSupport::TestCase
9
+ runnables.delete self # prevent Minitest running this class
10
+
11
+ LATENCY = 0.001
12
+ TIMEOUT = 1
13
+
14
+ attr_accessor :dir
15
+
16
+ def watcher_class
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def watcher
21
+ @watcher ||= watcher_class.new(dir, LATENCY)
22
+ end
23
+
24
+ def setup
25
+ @dir = File.realpath(Dir.mktmpdir)
26
+ end
27
+
28
+ def teardown
29
+ FileUtils.remove_entry_secure @dir
30
+ watcher.stop
31
+ end
32
+
33
+ def touch(file, mtime = nil)
34
+ options = {}
35
+ options[:mtime] = mtime if mtime
36
+ FileUtils.touch(file, options)
37
+ end
38
+
39
+ def assert_stale
40
+ timeout = Time.now + TIMEOUT
41
+ sleep LATENCY until watcher.stale? || Time.now > timeout
42
+ assert watcher.stale?
43
+ end
44
+
45
+ def assert_not_stale
46
+ sleep LATENCY * 10
47
+ assert !watcher.stale?
48
+ end
49
+
50
+ test "starting with no file" do
51
+ file = "#{@dir}/omg"
52
+ touch file, Time.now - 2.seconds
53
+
54
+ watcher.start
55
+ watcher.add file
56
+
57
+ assert_not_stale
58
+ touch file, Time.now
59
+ assert_stale
60
+ end
61
+
62
+ test "is stale when a watched file is updated" do
63
+ file = "#{@dir}/omg"
64
+ touch file, Time.now - 2.seconds
65
+
66
+ watcher.add file
67
+ watcher.start
68
+
69
+ assert_not_stale
70
+ touch file, Time.now
71
+ assert_stale
72
+ end
73
+
74
+ test "is stale when removing files" do
75
+ file = "#{@dir}/omg"
76
+ touch file, Time.now
77
+
78
+ watcher.add file
79
+ watcher.start
80
+
81
+ assert_not_stale
82
+ FileUtils.rm(file)
83
+ assert_stale
84
+ end
85
+
86
+ test "is stale when files are added to a watched directory" do
87
+ subdir = "#{@dir}/subdir"
88
+ FileUtils.mkdir(subdir)
89
+
90
+ watcher.add subdir
91
+ watcher.start
92
+
93
+ assert_not_stale
94
+ touch "#{subdir}/foo", Time.now - 1.minute
95
+ assert_stale
96
+ end
97
+
98
+ test "is stale when a file is changed in a watched directory" do
99
+ subdir = "#{@dir}/subdir"
100
+ FileUtils.mkdir(subdir)
101
+ touch "#{subdir}/foo", Time.now - 1.minute
102
+
103
+ watcher.add subdir
104
+ watcher.start
105
+
106
+ assert_not_stale
107
+ touch "#{subdir}/foo", Time.now
108
+ assert_stale
109
+ end
110
+
111
+ test "adding doesn't wipe stale state" do
112
+ file = "#{@dir}/omg"
113
+ file2 = "#{@dir}/foo"
114
+ touch file, Time.now - 2.seconds
115
+ touch file2, Time.now - 2.seconds
116
+
117
+ watcher.add file
118
+ watcher.start
119
+
120
+ assert_not_stale
121
+
122
+ touch file, Time.now
123
+ watcher.add file2
124
+
125
+ assert_stale
126
+ end
127
+
128
+ test "on stale" do
129
+ file = "#{@dir}/omg"
130
+ touch file, Time.now - 2.seconds
131
+
132
+ stale = false
133
+ watcher.on_stale { stale = true }
134
+
135
+ watcher.add file
136
+ watcher.start
137
+
138
+ touch file, Time.now
139
+
140
+ Timeout.timeout(1) { sleep 0.01 until stale }
141
+ assert stale
142
+
143
+ # Check that we only get notified once
144
+ stale = false
145
+ sleep LATENCY * 3
146
+ assert !stale
147
+ end
148
+
149
+ test "add relative path" do
150
+ File.write("#{dir}/foo", "foo")
151
+ watcher.add "foo"
152
+ assert_equal ["#{dir}/foo"], watcher.files.to_a
153
+ end
154
+
155
+ test "add dot relative path" do
156
+ File.write("#{dir}/foo", "foo")
157
+ watcher.add "./foo"
158
+ assert_equal ["#{dir}/foo"], watcher.files.to_a
159
+ end
160
+
161
+ test "add non existent file" do
162
+ watcher.add './foobar'
163
+ assert watcher.files.empty?
164
+ end
165
+ end
166
+ end
167
+ end