padrino-core 0.17.0 → 0.18.1
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.
- checksums.yaml +4 -4
- data/lib/padrino-core/application/application_setup.rb +18 -1
- data/lib/padrino-core/application/routing.rb +25 -8
- data/lib/padrino-core/application.rb +13 -6
- data/lib/padrino-core/cli/binstub.rb +2 -1
- data/lib/padrino-core/module.rb +7 -4
- data/lib/padrino-core/path_router/compiler.rb +8 -2
- data/lib/padrino-core/reloader/storage.rb +19 -0
- data/lib/padrino-core/reloader.rb +12 -0
- data/lib/padrino-core/server.rb +2 -0
- data/lib/padrino-core/version.rb +1 -1
- data/test/fixtures/apps/concerned/models/catalog_item.rb +7 -0
- data/test/fixtures/apps/concerned/models/filtered_item.rb +5 -0
- data/test/fixtures/apps/concerned/models/mixins/filterable.rb +13 -0
- data/test/fixtures/apps/concerned/models/mixins/sortable.rb +13 -0
- data/test/fixtures/apps/helpers/class_methods_helpers.rb +1 -1
- data/test/fixtures/apps/helpers/instance_methods_helpers.rb +1 -1
- data/test/helper.rb +3 -0
- data/test/test_binstub.rb +29 -3
- data/test/test_host_authorization.rb +117 -0
- data/test/test_mounter.rb +87 -0
- data/test/test_reloader_complex.rb +0 -2
- data/test/test_reloader_external.rb +0 -2
- data/test/test_reloader_simple.rb +0 -2
- data/test/test_reloader_storage.rb +3 -4
- data/test/test_reloader_system.rb +104 -8
- data/test/test_routing.rb +98 -0
- data/test/test_server.rb +58 -0
- metadata +11 -109
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8eef091fc9fe0d2d4b9ffdf24e35d6626f8f1f335ab766ae76e3baff1af3263a
|
|
4
|
+
data.tar.gz: ecf826091bcdb34a1d67c79f333627d57ea367ba97c48c1a926937be198fb703
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 601689861cc5ecada66374c61811e158127ed481d118e19518e2d41ed396e394e40d8cf003b380d67cd7123479ac68b0dea20413af3160b2a4af227bbaf41608
|
|
7
|
+
data.tar.gz: 81d7b0f8fc4f2c054a944a7c34b112836276ae1cd1c59f33de261ccbd31b101685443edc0b476a6efe42bc7c85c2bbdd075364022b2ce5be42a0f76b862276b4
|
|
@@ -60,7 +60,10 @@ module Padrino
|
|
|
60
60
|
set :views, proc { File.join(root, 'views') }
|
|
61
61
|
|
|
62
62
|
set :uri_root, '/'
|
|
63
|
-
set :public_folder, proc {
|
|
63
|
+
set :public_folder, proc {
|
|
64
|
+
app_public = Padrino.root('public', uri_root)
|
|
65
|
+
File.exist?(app_public) ? app_public : Padrino.root('public')
|
|
66
|
+
}
|
|
64
67
|
set :images_path, proc { File.join(public_folder, 'images') }
|
|
65
68
|
set :base_url, 'http://localhost'
|
|
66
69
|
end
|
|
@@ -111,10 +114,24 @@ module Padrino
|
|
|
111
114
|
builder.use Rack::Head
|
|
112
115
|
register Padrino::Flash
|
|
113
116
|
setup_protection builder
|
|
117
|
+
setup_host_authorization builder if host_authorization_set?
|
|
114
118
|
setup_csrf_protection builder
|
|
115
119
|
setup_application!
|
|
116
120
|
end
|
|
117
121
|
|
|
122
|
+
##
|
|
123
|
+
# Sinatra 4.1+ checks the Host header against its host_authorization
|
|
124
|
+
# setting. Its default permits only local hosts in development, so
|
|
125
|
+
# Padrino applies the setting only when it is set to a Hash on the app,
|
|
126
|
+
# a Padrino application it inherits from or in Padrino.configure_apps,
|
|
127
|
+
# and apps that don't set it keep answering any host.
|
|
128
|
+
#
|
|
129
|
+
def host_authorization_set?
|
|
130
|
+
respond_to?(:setup_host_authorization, true) &&
|
|
131
|
+
method(:host_authorization).owner != Sinatra::Base.singleton_class &&
|
|
132
|
+
host_authorization.is_a?(Hash)
|
|
133
|
+
end
|
|
134
|
+
|
|
118
135
|
##
|
|
119
136
|
# We need to add almost __sinatra__ images.
|
|
120
137
|
#
|
|
@@ -381,6 +381,20 @@ module Padrino
|
|
|
381
381
|
end
|
|
382
382
|
end
|
|
383
383
|
|
|
384
|
+
##
|
|
385
|
+
# The project's shared public/ for an app mounted under a sub-path whose
|
|
386
|
+
# public folder is the default public/<uri_root>. Static files and asset
|
|
387
|
+
# stamps fall back to it for files public/<uri_root> doesn't have. Nil for
|
|
388
|
+
# standalone Sinatra apps that only register Padrino::Routing.
|
|
389
|
+
#
|
|
390
|
+
# @api private
|
|
391
|
+
def shared_public_folder
|
|
392
|
+
return unless respond_to?(:uri_root) && Padrino.respond_to?(:root)
|
|
393
|
+
return if uri_root.nil? || uri_root == '/' || !public_folder
|
|
394
|
+
|
|
395
|
+
Padrino.root('public') if File.expand_path(public_folder) == Padrino.root('public', uri_root)
|
|
396
|
+
end
|
|
397
|
+
|
|
384
398
|
##
|
|
385
399
|
# Processes the existing path and prepends the 'parent' parameters onto the route
|
|
386
400
|
# Used for calculating path in route method.
|
|
@@ -857,17 +871,20 @@ module Padrino
|
|
|
857
871
|
|
|
858
872
|
##
|
|
859
873
|
# This is mostly just a helper so request.path_info isn't changed when
|
|
860
|
-
# serving files from the public directory.
|
|
874
|
+
# serving files from the public directory. An app mounted under a sub-path
|
|
875
|
+
# falls back to the shared public/ for files missing from public/<uri_root>.
|
|
861
876
|
#
|
|
862
877
|
def static_file?(path_info)
|
|
863
878
|
return unless (public_dir = settings.public_folder)
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
879
|
+
# Decode the path the way Sinatra's static! and the router do: %XX only, so '+' stays a plus.
|
|
880
|
+
file = Rack::Utils.unescape_path(path_info.b).force_encoding(Encoding::UTF_8)
|
|
881
|
+
[File.expand_path(public_dir), settings.shared_public_folder].compact.each do |dir|
|
|
882
|
+
path = "#{dir}#{file}"
|
|
883
|
+
next unless Rack::Utils.valid_path?(path)
|
|
884
|
+
path = File.expand_path(path)
|
|
885
|
+
return path if path.start_with?("#{dir}/") && File.file?(path)
|
|
886
|
+
end
|
|
887
|
+
nil
|
|
871
888
|
end
|
|
872
889
|
|
|
873
890
|
#
|
|
@@ -119,15 +119,22 @@ module Padrino
|
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
##
|
|
122
|
-
# Returns
|
|
123
|
-
#
|
|
122
|
+
# Returns the files to load as dependencies of your Application: those matching
|
|
123
|
+
# the default globs below, under the application's root. The list is built again
|
|
124
|
+
# on each call, so appending to it has no effect; to load more files, override
|
|
125
|
+
# the method and add them to +super+. Add file paths (expand globs with Dir.glob)
|
|
126
|
+
# under Padrino.root: the reloader watches these files and reloads the application
|
|
127
|
+
# when one changes.
|
|
124
128
|
#
|
|
125
|
-
# @return [Array]
|
|
126
|
-
#
|
|
129
|
+
# @return [Array<String>]
|
|
130
|
+
# paths of the files to load as dependencies
|
|
127
131
|
#
|
|
128
132
|
# @example
|
|
129
|
-
# MyApp
|
|
130
|
-
#
|
|
133
|
+
# class MyApp < Padrino::Application
|
|
134
|
+
# def self.dependencies
|
|
135
|
+
# super + Dir.glob(Padrino.root('uploaders', '**', '*.rb'))
|
|
136
|
+
# end
|
|
137
|
+
# end
|
|
131
138
|
#
|
|
132
139
|
def dependencies
|
|
133
140
|
[
|
|
@@ -21,7 +21,8 @@ module Padrino
|
|
|
21
21
|
if File.file?(binstub)
|
|
22
22
|
exec Gem.ruby, binstub, *ARGV
|
|
23
23
|
else
|
|
24
|
-
|
|
24
|
+
# exit! skips the flush of buffered output, so write to the unbuffered stderr. Not warn: -W0 silences it.
|
|
25
|
+
$stderr.puts 'Please run `bundle install && bundle binstubs --all` from your project root to generate bundle-specific executables'
|
|
25
26
|
exit!
|
|
26
27
|
end
|
|
27
28
|
end
|
data/lib/padrino-core/module.rb
CHANGED
|
@@ -34,8 +34,9 @@ module Padrino
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
##
|
|
37
|
-
# Returns the
|
|
38
|
-
#
|
|
37
|
+
# Returns the path globs of the module's files to load as dependencies. The list
|
|
38
|
+
# is built again on each call, so appending to it has no effect; to load more
|
|
39
|
+
# files, override the method and add globs to +super+.
|
|
39
40
|
#
|
|
40
41
|
# @return [Array<String>]
|
|
41
42
|
# The dependency paths.
|
|
@@ -44,9 +45,11 @@ module Padrino
|
|
|
44
45
|
# module MyModule
|
|
45
46
|
# extend Padrino::Module
|
|
46
47
|
# gem! 'my_gem'
|
|
47
|
-
# end
|
|
48
48
|
#
|
|
49
|
-
#
|
|
49
|
+
# def self.dependency_paths
|
|
50
|
+
# super + ["#{root}/uploaders/*.rb"]
|
|
51
|
+
# end
|
|
52
|
+
# end
|
|
50
53
|
#
|
|
51
54
|
def dependency_paths
|
|
52
55
|
[
|
|
@@ -70,6 +70,8 @@ module Padrino
|
|
|
70
70
|
# Returns a instance of PathRouter::Route if path is matched with current regexp
|
|
71
71
|
#
|
|
72
72
|
def match?(offset, path)
|
|
73
|
+
return unless path
|
|
74
|
+
|
|
73
75
|
@routes[offset..].find do |route|
|
|
74
76
|
route.regexp === path || (path.end_with?('/') && route.regexp === path[0..-2])
|
|
75
77
|
end
|
|
@@ -88,10 +90,14 @@ module Padrino
|
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
##
|
|
91
|
-
# Decode env["PATH_INFO"]
|
|
93
|
+
# Decode env["PATH_INFO"]. Returns nil when the path doesn't decode to valid text
|
|
94
|
+
# (a malformed or non-UTF-8 escape such as "/%ff"), so it matches no route.
|
|
92
95
|
#
|
|
93
96
|
def decode_pattern(pattern)
|
|
94
|
-
decode_uri(encode_default_external(pattern))
|
|
97
|
+
decoded = decode_uri(encode_default_external(pattern))
|
|
98
|
+
decoded if decoded.valid_encoding?
|
|
99
|
+
rescue ArgumentError, EncodingError
|
|
100
|
+
nil
|
|
95
101
|
end
|
|
96
102
|
|
|
97
103
|
##
|
|
@@ -20,6 +20,7 @@ module Padrino
|
|
|
20
20
|
|
|
21
21
|
def prepare(name)
|
|
22
22
|
file = remove(name)
|
|
23
|
+
reset_concerns(name)
|
|
23
24
|
@old_entries ||= {}
|
|
24
25
|
@old_entries[name] = {
|
|
25
26
|
constants: object_classes,
|
|
@@ -57,6 +58,24 @@ module Padrino
|
|
|
57
58
|
@files ||= {}
|
|
58
59
|
end
|
|
59
60
|
|
|
61
|
+
##
|
|
62
|
+
# Resets the ActiveSupport::Concern modules first defined in the file. Unlike classes
|
|
63
|
+
# they are kept on reload, so that classes including them keep working and namespaces
|
|
64
|
+
# keep their constants, but their state is reset so the file can define them again.
|
|
65
|
+
#
|
|
66
|
+
def reset_concerns(file)
|
|
67
|
+
return unless defined?(::ActiveSupport::Concern)
|
|
68
|
+
|
|
69
|
+
ObjectSpace.each_object(::Module) do |mod|
|
|
70
|
+
# Module#=== doesn't call mod, which can be a DeprecatedConstantProxy that warns on any call.
|
|
71
|
+
next unless ::ActiveSupport::Concern === mod && mod.name
|
|
72
|
+
next unless Object.const_source_location(mod.name)&.first == file
|
|
73
|
+
Reloader.reset_concern(mod) if Object.const_get(mod.name).equal?(mod)
|
|
74
|
+
rescue NameError
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
60
79
|
##
|
|
61
80
|
# Returns all the classes in the object space.
|
|
62
81
|
#
|
|
@@ -153,6 +153,18 @@ module Padrino
|
|
|
153
153
|
# no const to remove
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
##
|
|
157
|
+
# Clears the included and prepended blocks of an ActiveSupport::Concern, so that its file
|
|
158
|
+
# can define them again. Its dependencies are reset by +extend ActiveSupport::Concern+.
|
|
159
|
+
#
|
|
160
|
+
def reset_concern(concern)
|
|
161
|
+
return if constant_excluded?(concern)
|
|
162
|
+
%i[@_included_block @_prepended_block].each do |ivar|
|
|
163
|
+
concern.remove_instance_variable(ivar) if concern.instance_variable_defined?(ivar)
|
|
164
|
+
end
|
|
165
|
+
logger.devel "Reset concern #{concern}"
|
|
166
|
+
end
|
|
167
|
+
|
|
156
168
|
##
|
|
157
169
|
# Remove a feature from $LOADED_FEATURES so it can be required again.
|
|
158
170
|
#
|
data/lib/padrino-core/server.rb
CHANGED
|
@@ -55,6 +55,8 @@ module Padrino
|
|
|
55
55
|
%i[INT TERM].each { |sig| trap(sig) { exit } }
|
|
56
56
|
super do |server|
|
|
57
57
|
server.threaded = true if server.respond_to?(:threaded=)
|
|
58
|
+
# WEBrick: stop on TERM the way Rackup stops it on INT, instead of raising SystemExit in its accept loop.
|
|
59
|
+
trap(:TERM) { server.shutdown } if server.respond_to?(:shutdown)
|
|
58
60
|
end
|
|
59
61
|
ensure
|
|
60
62
|
puts '<= Padrino leaves the gun, takes the cannoli' unless options[:daemonize]
|
data/lib/padrino-core/version.rb
CHANGED
data/test/helper.rb
CHANGED
|
@@ -15,6 +15,9 @@ require 'rack/test'
|
|
|
15
15
|
require 'yaml'
|
|
16
16
|
require 'padrino-core'
|
|
17
17
|
|
|
18
|
+
# Reloading outside development needs tracking enabled before the apps load, and any test file can load them first.
|
|
19
|
+
Padrino::Reloader.enabled = true
|
|
20
|
+
|
|
18
21
|
require 'ext/minitest-spec'
|
|
19
22
|
require 'ext/rack-test-methods'
|
|
20
23
|
|
data/test/test_binstub.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative 'helper'
|
|
2
|
+
require 'open3'
|
|
2
3
|
require 'tmpdir'
|
|
3
4
|
require 'padrino-core/cli/binstub'
|
|
4
5
|
|
|
@@ -7,15 +8,40 @@ describe 'Padrino.replace_with_binstub' do
|
|
|
7
8
|
Dir.mktmpdir do |dir|
|
|
8
9
|
File.write(File.join(dir, 'Gemfile'), '')
|
|
9
10
|
File.write(File.join(dir, '.components'), '')
|
|
10
|
-
|
|
11
|
+
_, err = capture_io do
|
|
11
12
|
Bundler.stub(:definition, -> { raise Bundler::GemfileNotFound }) do
|
|
12
13
|
Padrino.stub(:exit!, nil) do
|
|
13
14
|
Dir.chdir(dir) { Padrino.replace_with_binstub('padrino') }
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
end
|
|
17
|
-
assert_match(/bundle install && bundle binstubs --all/,
|
|
18
|
-
refute_match(/--binstubs/,
|
|
18
|
+
assert_match(/bundle install && bundle binstubs --all/, err)
|
|
19
|
+
refute_match(/--binstubs/, err)
|
|
19
20
|
end
|
|
20
21
|
end
|
|
22
|
+
|
|
23
|
+
# Runs the executable outside any bundle in a project without bin/, with its output piped.
|
|
24
|
+
def run_without_binstubs(bin, env = {})
|
|
25
|
+
Dir.mktmpdir do |dir|
|
|
26
|
+
File.write(File.join(dir, 'Gemfile'), '')
|
|
27
|
+
File.write(File.join(dir, '.components'), '')
|
|
28
|
+
run = -> { Open3.capture3(env, RbConfig.ruby, '-I', File.expand_path('../lib', __dir__), bin, 'version', chdir: dir) }
|
|
29
|
+
defined?(Bundler) ? Bundler.with_unbundled_env(&run) : run.call
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
%w[padrino-core/bin/padrino padrino-gen/bin/padrino-gen].each do |executable|
|
|
34
|
+
it "should show the binstubs hint and exit 1 when the output of #{executable} is piped" do
|
|
35
|
+
out, err, status = run_without_binstubs(File.expand_path("../../#{executable}", __dir__))
|
|
36
|
+
assert_equal 1, status.exitstatus
|
|
37
|
+
assert_includes err, 'bundle install && bundle binstubs --all'
|
|
38
|
+
refute_includes out, 'bundle binstubs'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'should show the binstubs hint with warnings turned off' do
|
|
43
|
+
_, err, status = run_without_binstubs(File.expand_path('../../padrino-core/bin/padrino', __dir__), 'RUBYOPT' => '-W0')
|
|
44
|
+
assert_equal 1, status.exitstatus
|
|
45
|
+
assert_includes err, 'bundle install && bundle binstubs --all'
|
|
46
|
+
end
|
|
21
47
|
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require_relative 'helper'
|
|
2
|
+
|
|
3
|
+
describe 'Host authorization' do
|
|
4
|
+
before do
|
|
5
|
+
skip 'needs Sinatra 4.1+' unless Sinatra::Base.respond_to?(:host_authorization)
|
|
6
|
+
Padrino.clear!
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def status_for(rack_app, host, path = '/')
|
|
10
|
+
Rack::MockRequest.new(rack_app).get(path, 'HTTP_HOST' => host).status
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe 'when the app sets host_authorization' do
|
|
14
|
+
it 'should answer permitted hosts and refuse others' do
|
|
15
|
+
mock_app do
|
|
16
|
+
set :host_authorization, permitted_hosts: ['good.example']
|
|
17
|
+
get('/') { 'ok' }
|
|
18
|
+
end
|
|
19
|
+
assert_equal 200, status_for(@app, 'good.example')
|
|
20
|
+
assert_equal 403, status_for(@app, 'evil.example')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'should apply it in development' do
|
|
24
|
+
mock_app do
|
|
25
|
+
set :environment, :development
|
|
26
|
+
disable :reload
|
|
27
|
+
set :host_authorization, permitted_hosts: ['good.example']
|
|
28
|
+
get('/') { 'ok' }
|
|
29
|
+
end
|
|
30
|
+
assert_equal 200, status_for(@app, 'good.example')
|
|
31
|
+
assert_equal 403, status_for(@app, 'evil.example')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'should apply a setting inherited from a parent app class' do
|
|
35
|
+
parent = Sinatra.new(Padrino::Application) do
|
|
36
|
+
set :host_authorization, permitted_hosts: ['good.example']
|
|
37
|
+
end
|
|
38
|
+
mock_app(parent) { get('/') { 'ok' } }
|
|
39
|
+
assert_equal 200, status_for(@app, 'good.example')
|
|
40
|
+
assert_equal 403, status_for(@app, 'evil.example')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'should apply a setting from Padrino.configure_apps' do
|
|
44
|
+
Padrino.configure_apps { set :host_authorization, permitted_hosts: ['good.example'] }
|
|
45
|
+
mock_app { get('/') { 'ok' } }
|
|
46
|
+
assert_equal 200, status_for(@app, 'good.example')
|
|
47
|
+
assert_equal 403, status_for(@app, 'evil.example')
|
|
48
|
+
ensure
|
|
49
|
+
Padrino.clear!
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe 'when the app does not set host_authorization' do
|
|
54
|
+
%i[development production test].each do |env|
|
|
55
|
+
it "should answer any host in #{env}" do
|
|
56
|
+
mock_app do
|
|
57
|
+
set :environment, env
|
|
58
|
+
disable :reload
|
|
59
|
+
get('/') { 'ok' }
|
|
60
|
+
end
|
|
61
|
+
assert_equal 200, status_for(@app, 'myapp.example')
|
|
62
|
+
assert_equal 200, status_for(@app, 'localhost')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'should treat a setting that is not a Hash as unset' do
|
|
67
|
+
mock_app do
|
|
68
|
+
disable :host_authorization
|
|
69
|
+
get('/') { 'ok' }
|
|
70
|
+
end
|
|
71
|
+
assert_equal 200, status_for(@app, 'myapp.example')
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe 'for mounted apps' do
|
|
76
|
+
before do
|
|
77
|
+
Object.send(:remove_const, :HostAuthApp) if defined?(::HostAuthApp)
|
|
78
|
+
::HostAuthApp = Class.new(Padrino::Application) do
|
|
79
|
+
set :host_authorization, permitted_hosts: ['good.example']
|
|
80
|
+
get('/') { 'ok' }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
after do
|
|
85
|
+
Object.send(:remove_const, :HostAuthApp)
|
|
86
|
+
Padrino.clear!
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'should apply it to an app mounted at the root' do
|
|
90
|
+
Padrino.mount('HostAuthApp', app_file: __FILE__).to('/')
|
|
91
|
+
assert_equal 200, status_for(Padrino.application, 'good.example')
|
|
92
|
+
assert_equal 403, status_for(Padrino.application, 'evil.example')
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'should apply it to an app mounted under a sub-path' do
|
|
96
|
+
Padrino.mount('HostAuthApp', app_file: __FILE__).to('/sub')
|
|
97
|
+
assert_equal 200, status_for(Padrino.application, 'good.example', '/sub')
|
|
98
|
+
assert_equal 403, status_for(Padrino.application, 'evil.example', '/sub')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'should apply it to an app mounted for a host pattern' do
|
|
102
|
+
Padrino.mount('HostAuthApp', app_file: __FILE__).host(/\.example\z/)
|
|
103
|
+
assert_equal 200, status_for(Padrino.application, 'good.example')
|
|
104
|
+
assert_equal 403, status_for(Padrino.application, 'evil.example')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'should leave a sibling app without the setting open to any host' do
|
|
108
|
+
::HostAuthOpenApp = Class.new(Padrino::Application) { get('/') { 'open' } }
|
|
109
|
+
Padrino.mount('HostAuthApp', app_file: __FILE__).to('/locked')
|
|
110
|
+
Padrino.mount('HostAuthOpenApp', app_file: __FILE__).to('/')
|
|
111
|
+
assert_equal 403, status_for(Padrino.application, 'evil.example', '/locked')
|
|
112
|
+
assert_equal 200, status_for(Padrino.application, 'evil.example', '/')
|
|
113
|
+
ensure
|
|
114
|
+
Object.send(:remove_const, :HostAuthOpenApp) if defined?(::HostAuthOpenApp)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
data/test/test_mounter.rb
CHANGED
|
@@ -254,6 +254,93 @@ describe 'Mounter' do
|
|
|
254
254
|
assert_equal File.read(__FILE__), res.body
|
|
255
255
|
end
|
|
256
256
|
|
|
257
|
+
describe 'serving static assets under a sub-path' do
|
|
258
|
+
def with_public_files(*files, &block)
|
|
259
|
+
Dir.mktmpdir do |root|
|
|
260
|
+
files.each do |file|
|
|
261
|
+
path = File.join(root, 'public', file)
|
|
262
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
263
|
+
File.write(path, file)
|
|
264
|
+
end
|
|
265
|
+
Padrino.stub(:root, proc { |*args| File.expand_path(File.join(root, *args)) }, &block)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
it 'should serve assets from the project public folder' do
|
|
270
|
+
class ::StaticSubApp < Padrino::Application; end
|
|
271
|
+
with_public_files('stylesheets/site.css') do
|
|
272
|
+
Padrino.mount('static_sub_app').to('/myapp')
|
|
273
|
+
res = Rack::MockRequest.new(Padrino.application).get('/myapp/stylesheets/site.css')
|
|
274
|
+
assert res.ok?
|
|
275
|
+
assert_equal 'stylesheets/site.css', res.body
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it 'should serve assets from a public folder named after the uri root' do
|
|
280
|
+
class ::StaticOwnApp < Padrino::Application; end
|
|
281
|
+
with_public_files('stylesheets/site.css', 'myapp/stylesheets/site.css') do
|
|
282
|
+
Padrino.mount('static_own_app').to('/myapp')
|
|
283
|
+
res = Rack::MockRequest.new(Padrino.application).get('/myapp/stylesheets/site.css')
|
|
284
|
+
assert res.ok?
|
|
285
|
+
assert_equal 'myapp/stylesheets/site.css', res.body
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it 'should fall back to the project public folder for files missing from the uri root folder' do
|
|
290
|
+
class ::StaticPartialApp < Padrino::Application; end
|
|
291
|
+
with_public_files('stylesheets/site.css', 'myapp/.keep') do
|
|
292
|
+
Padrino.mount('static_partial_app').to('/myapp')
|
|
293
|
+
res = Rack::MockRequest.new(Padrino.application).get('/myapp/stylesheets/site.css')
|
|
294
|
+
assert res.ok?
|
|
295
|
+
assert_equal 'stylesheets/site.css', res.body
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
it 'should keep serving shared assets when the application is built again' do
|
|
300
|
+
class ::StaticRebuiltApp < Padrino::Application; end
|
|
301
|
+
with_public_files('stylesheets/site.css') do
|
|
302
|
+
Padrino.mount('static_rebuilt_app').to('/myapp')
|
|
303
|
+
3.times do
|
|
304
|
+
res = Rack::MockRequest.new(Padrino.application).get('/myapp/stylesheets/site.css')
|
|
305
|
+
assert res.ok?
|
|
306
|
+
assert_equal 'stylesheets/site.css', res.body
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
it 'should serve assets and routes when the app has a helper named like an internal method' do
|
|
312
|
+
class ::StaticHelperApp < Padrino::Application
|
|
313
|
+
helpers(Module.new { define_method(:public_file) { |name| "/files/#{name}" } })
|
|
314
|
+
get('/page') { public_file('a') }
|
|
315
|
+
end
|
|
316
|
+
with_public_files('stylesheets/site.css') do
|
|
317
|
+
Padrino.mount('static_helper_app').to('/myapp')
|
|
318
|
+
assert_equal 'stylesheets/site.css', Rack::MockRequest.new(Padrino.application).get('/myapp/stylesheets/site.css').body
|
|
319
|
+
assert_equal '/files/a', Rack::MockRequest.new(Padrino.application).get('/myapp/page').body
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
it 'should not fall back from a custom public folder' do
|
|
324
|
+
class ::StaticCustomApp < Padrino::Application; end
|
|
325
|
+
with_public_files('stylesheets/site.css', 'custom/.keep') do
|
|
326
|
+
StaticCustomApp.set :public_folder, Padrino.root('public', 'custom')
|
|
327
|
+
Padrino.mount('static_custom_app').to('/myapp')
|
|
328
|
+
res = Rack::MockRequest.new(Padrino.application).get('/myapp/stylesheets/site.css')
|
|
329
|
+
assert_equal 404, res.status
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it 'should serve assets for an app mounted at the root' do
|
|
334
|
+
class ::StaticRootApp < Padrino::Application; end
|
|
335
|
+
with_public_files('stylesheets/site.css') do
|
|
336
|
+
Padrino.mount('static_root_app').to('/')
|
|
337
|
+
res = Rack::MockRequest.new(Padrino.application).get('/stylesheets/site.css')
|
|
338
|
+
assert res.ok?
|
|
339
|
+
assert_equal 'stylesheets/site.css', res.body
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
257
344
|
it 'should load apps from gems' do
|
|
258
345
|
spec_file = Padrino.root('fixtures', 'app_gem', 'app_gem.gemspec')
|
|
259
346
|
spec = Gem::Specification.load(spec_file)
|
|
@@ -30,15 +30,14 @@ describe 'Padrino::Reloader::Storage' do
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
it 'should return list of new classes' do
|
|
33
|
-
skip
|
|
34
33
|
class OSTest; end
|
|
35
34
|
module OSTestModule; class B; end; end
|
|
36
35
|
|
|
37
36
|
new_classes = Padrino::Reloader::Storage.send(:new_classes, @snapshot)
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
assert_includes new_classes, OSTest
|
|
39
|
+
assert_includes new_classes, OSTestModule::B
|
|
40
|
+
refute_includes new_classes, Padrino::Logger
|
|
42
41
|
end
|
|
43
42
|
|
|
44
43
|
it 'should return a Set object' do
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
require_relative 'helper'
|
|
2
2
|
require 'tmpdir'
|
|
3
3
|
|
|
4
|
-
# Reloading outside development needs tracking enabled before the apps load.
|
|
5
|
-
Padrino::Reloader.enabled = true
|
|
6
4
|
require_relative 'fixtures/apps/kiq'
|
|
7
5
|
require_relative 'fixtures/apps/system'
|
|
8
6
|
require_relative 'fixtures/apps/static'
|
|
@@ -70,9 +68,10 @@ describe 'SystemReloader' do
|
|
|
70
68
|
end
|
|
71
69
|
end
|
|
72
70
|
DOC
|
|
73
|
-
|
|
71
|
+
gem_dir = Dir.mktmpdir
|
|
72
|
+
File.write(File.join(gem_dir, 'padrino_class_demo.rb'), new_class)
|
|
73
|
+
$LOAD_PATH.unshift(gem_dir)
|
|
74
74
|
begin
|
|
75
|
-
File.open(tmp_file, 'w') { |f| f.write(new_class) }
|
|
76
75
|
Padrino.clear!
|
|
77
76
|
require File.expand_path("#{__dir__}/fixtures/apps/system_class_methods_demo.rb")
|
|
78
77
|
@app = SystemClassMethodsDemo
|
|
@@ -85,7 +84,8 @@ describe 'SystemReloader' do
|
|
|
85
84
|
assert defined?(SingletonClassTest), 'SingletonClassTest undefined'
|
|
86
85
|
assert_includes SingletonClassTest.singleton_methods, :external_test
|
|
87
86
|
ensure
|
|
88
|
-
|
|
87
|
+
$LOAD_PATH.delete(gem_dir)
|
|
88
|
+
FileUtils.rm_rf(gem_dir)
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
|
|
@@ -96,9 +96,10 @@ describe 'SystemReloader' do
|
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
DOC
|
|
99
|
-
|
|
99
|
+
gem_dir = Dir.mktmpdir
|
|
100
|
+
File.write(File.join(gem_dir, 'padrino_instance_demo.rb'), new_class)
|
|
101
|
+
$LOAD_PATH.unshift(gem_dir)
|
|
100
102
|
begin
|
|
101
|
-
File.open(tmp_file, 'w') { |f| f.write(new_class) }
|
|
102
103
|
Padrino.clear!
|
|
103
104
|
require File.expand_path("#{__dir__}/fixtures/apps/system_instance_methods_demo.rb")
|
|
104
105
|
@app = SystemInstanceMethodsDemo
|
|
@@ -111,7 +112,8 @@ describe 'SystemReloader' do
|
|
|
111
112
|
assert defined?(InstanceTest), 'InstanceTest undefined'
|
|
112
113
|
assert_includes InstanceTest.new.methods, :instance_test
|
|
113
114
|
ensure
|
|
114
|
-
|
|
115
|
+
$LOAD_PATH.delete(gem_dir)
|
|
116
|
+
FileUtils.rm_rf(gem_dir)
|
|
115
117
|
end
|
|
116
118
|
end
|
|
117
119
|
|
|
@@ -187,5 +189,99 @@ describe 'SystemReloader' do
|
|
|
187
189
|
Padrino.reload!
|
|
188
190
|
assert_equal original_value, BadModule.instance_variable_get(:@happy_global_variable)
|
|
189
191
|
end
|
|
192
|
+
|
|
193
|
+
it 'should redefine an ActiveSupport::Concern whose included block moved' do
|
|
194
|
+
begin
|
|
195
|
+
require 'active_support/concern'
|
|
196
|
+
rescue LoadError
|
|
197
|
+
skip 'ActiveSupport is not available'
|
|
198
|
+
end
|
|
199
|
+
concern_file = "#{__dir__}/fixtures/apps/concerned/models/mixins/filterable.rb"
|
|
200
|
+
includer_file = "#{__dir__}/fixtures/apps/concerned/models/filtered_item.rb"
|
|
201
|
+
original = File.read(concern_file)
|
|
202
|
+
includer_mtime = File.mtime(includer_file)
|
|
203
|
+
Padrino.clear!
|
|
204
|
+
require File.expand_path("#{__dir__}/fixtures/apps/concerned/app.rb")
|
|
205
|
+
@app = SystemConcernedClassDemo
|
|
206
|
+
Padrino.mount(SystemConcernedClassDemo).to('/')
|
|
207
|
+
get '/'
|
|
208
|
+
assert_equal 'v1', FilteredItem.new.filter
|
|
209
|
+
Filterable.instance_variable_set(:@config, :kept)
|
|
210
|
+
|
|
211
|
+
# A second edit checks that the concern stays tracked after its first reload.
|
|
212
|
+
[2, 3].each do |version|
|
|
213
|
+
File.write(concern_file, original.sub(' included do', "#{" # moved\n" * version} included do")
|
|
214
|
+
.sub("'v1'", "'v#{version}'").sub("'l1'", "'l#{version}'"))
|
|
215
|
+
FileUtils.touch(concern_file, mtime: Time.now + version)
|
|
216
|
+
Padrino.reload!
|
|
217
|
+
assert_equal "l#{version}", FilteredItem.new.label
|
|
218
|
+
FileUtils.touch(includer_file, mtime: Time.now + version)
|
|
219
|
+
Padrino.reload!
|
|
220
|
+
assert_equal "v#{version}", FilteredItem.new.filter
|
|
221
|
+
end
|
|
222
|
+
assert_equal :kept, Filterable.instance_variable_get(:@config)
|
|
223
|
+
ensure
|
|
224
|
+
File.write(concern_file, original) if original
|
|
225
|
+
# A future mtime would hide the next run's touch from Reloader.
|
|
226
|
+
File.utime(includer_mtime, includer_mtime, includer_file) if includer_mtime
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'should redefine an ActiveSupport::Concern that another file requires, also after Padrino.clear!' do
|
|
230
|
+
begin
|
|
231
|
+
require 'active_support/concern'
|
|
232
|
+
rescue LoadError
|
|
233
|
+
skip 'ActiveSupport is not available'
|
|
234
|
+
end
|
|
235
|
+
concern_file = "#{__dir__}/fixtures/apps/concerned/models/mixins/sortable.rb"
|
|
236
|
+
includer_file = "#{__dir__}/fixtures/apps/concerned/models/catalog_item.rb"
|
|
237
|
+
original = File.read(concern_file)
|
|
238
|
+
includer_mtime = File.mtime(includer_file)
|
|
239
|
+
# Sortable stays defined after Padrino.clear!, so the second load finds it already defined.
|
|
240
|
+
2.times do
|
|
241
|
+
Padrino.clear!
|
|
242
|
+
require File.expand_path("#{__dir__}/fixtures/apps/concerned/app.rb")
|
|
243
|
+
@app = SystemConcernedClassDemo
|
|
244
|
+
Padrino.mount(SystemConcernedClassDemo).to('/')
|
|
245
|
+
get '/'
|
|
246
|
+
end
|
|
247
|
+
assert_equal 's1', CatalogItem.new.sort_key
|
|
248
|
+
|
|
249
|
+
[2, 3].each do |version|
|
|
250
|
+
File.write(concern_file, original.sub(' included do', "#{" # moved\n" * version} included do").gsub("'s1'", "'s#{version}'"))
|
|
251
|
+
FileUtils.touch(concern_file, mtime: Time.now + version)
|
|
252
|
+
Padrino.reload!
|
|
253
|
+
assert_equal "s#{version}", CatalogItem.new.sort_label
|
|
254
|
+
FileUtils.touch(includer_file, mtime: Time.now + version)
|
|
255
|
+
Padrino.reload!
|
|
256
|
+
assert_equal "s#{version}", CatalogItem.new.sort_key
|
|
257
|
+
end
|
|
258
|
+
ensure
|
|
259
|
+
if original
|
|
260
|
+
File.write(concern_file, original)
|
|
261
|
+
File.utime(includer_mtime, includer_mtime, includer_file)
|
|
262
|
+
# The next Padrino.clear! keeps Sortable, and CatalogItem then requires the restored file without a reset.
|
|
263
|
+
Padrino::Reloader.reset_concern(Sortable) if defined?(Sortable)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
it 'should not call methods on deprecated constant proxies while looking for concerns' do
|
|
268
|
+
begin
|
|
269
|
+
require 'active_support'
|
|
270
|
+
require 'active_support/deprecation'
|
|
271
|
+
rescue LoadError
|
|
272
|
+
skip 'ActiveSupport is not available'
|
|
273
|
+
end
|
|
274
|
+
# A proxy is a Module that warns, or raises with this behavior, on any method call,
|
|
275
|
+
# so the test only keeps a reference to it and never calls it.
|
|
276
|
+
deprecator = ActiveSupport::Deprecation.new
|
|
277
|
+
deprecator.behavior = :raise
|
|
278
|
+
_proxy = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ReloaderDeprecatedMonitor', '::Monitor', deprecator)
|
|
279
|
+
Padrino.clear!
|
|
280
|
+
require File.expand_path("#{__dir__}/fixtures/apps/concerned/app.rb")
|
|
281
|
+
@app = SystemConcernedClassDemo
|
|
282
|
+
Padrino.mount(SystemConcernedClassDemo).to('/')
|
|
283
|
+
get '/'
|
|
284
|
+
assert_equal 'l1', FilteredItem.new.label
|
|
285
|
+
end
|
|
190
286
|
end
|
|
191
287
|
end
|
data/test/test_routing.rb
CHANGED
|
@@ -89,6 +89,52 @@ describe 'Routing' do
|
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
it 'should keep a plus sign in static file paths' do
|
|
93
|
+
Dir.mktmpdir do |tmp|
|
|
94
|
+
dir = "#{tmp}/\u00fcber"
|
|
95
|
+
FileUtils.mkdir_p(["#{dir}/public", "#{dir}/public_bak"])
|
|
96
|
+
File.write("#{dir}/public/a+b.txt", 'plus')
|
|
97
|
+
File.write("#{dir}/public/c d.txt", 'space')
|
|
98
|
+
File.write("#{dir}/public/caf\u00e9.txt", 'cafe')
|
|
99
|
+
File.write("#{dir}/public_bak/secret.txt", 'secret')
|
|
100
|
+
mock_app do
|
|
101
|
+
set :public_folder, "#{dir}/public"
|
|
102
|
+
get('/*') { 'not found' }
|
|
103
|
+
end
|
|
104
|
+
get '/a+b.txt'
|
|
105
|
+
assert_equal 'plus', body
|
|
106
|
+
get '/a%2Bb.txt'
|
|
107
|
+
assert_equal 'plus', body
|
|
108
|
+
get '/c%20d.txt'
|
|
109
|
+
assert_equal 'space', body
|
|
110
|
+
get '/c+d.txt'
|
|
111
|
+
assert_equal 'not found', body
|
|
112
|
+
get '/caf%C3%A9.txt'
|
|
113
|
+
assert_equal 'cafe', body
|
|
114
|
+
get '/%2e%2e/public_bak/secret.txt'
|
|
115
|
+
assert_equal 'not found', body
|
|
116
|
+
get '/a+b.txt%00.png'
|
|
117
|
+
assert_equal 'not found', body
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'should keep a plus sign in static file paths of an app mounted under a sub-path' do
|
|
122
|
+
Dir.mktmpdir do |dir|
|
|
123
|
+
FileUtils.mkdir_p("#{dir}/public")
|
|
124
|
+
File.write("#{dir}/public/a+b.txt", 'plus')
|
|
125
|
+
File.write("#{dir}/public/c d.txt", 'space')
|
|
126
|
+
mock_app do
|
|
127
|
+
set :public_folder, "#{dir}/public"
|
|
128
|
+
get('/*') { 'not found' }
|
|
129
|
+
end
|
|
130
|
+
mounted = Rack::Builder.app(@app) { |inner| map('/blog') { run inner } }
|
|
131
|
+
request = Rack::MockRequest.new(mounted)
|
|
132
|
+
assert_equal 'plus', request.get('/blog/a+b.txt').body
|
|
133
|
+
assert_equal 'not found', request.get('/blog/c+d.txt').body
|
|
134
|
+
assert_equal 'space', request.get('/blog/c%20d.txt').body
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
92
138
|
it 'should ignore trailing delimiters for basic route' do
|
|
93
139
|
mock_app do
|
|
94
140
|
get('/foo') { 'okey' }
|
|
@@ -253,6 +299,30 @@ describe 'Routing' do
|
|
|
253
299
|
assert_equal 'cpp', body
|
|
254
300
|
end
|
|
255
301
|
|
|
302
|
+
it 'should not find a route for a path that does not decode to valid UTF-8' do
|
|
303
|
+
mock_app do
|
|
304
|
+
get('/') { 'root' }
|
|
305
|
+
get("/caf\u00e9") { 'cafe' }
|
|
306
|
+
get(:show, map: '/items/:id') { params[:id] }
|
|
307
|
+
end
|
|
308
|
+
{ '' => Padrino::Router.new(path: '/', to: @app), '/blog' => Padrino::Router.new(path: '/blog', to: @app) }.each do |prefix, app|
|
|
309
|
+
# Set PATH_INFO directly: Rack::MockRequest refuses to parse a malformed escape such as '%zz'.
|
|
310
|
+
request = lambda do |path|
|
|
311
|
+
env = Rack::MockRequest.env_for('/')
|
|
312
|
+
env['PATH_INFO'] = "#{prefix}#{path}"
|
|
313
|
+
status, _headers, body = app.call(env)
|
|
314
|
+
[status, body.to_enum.to_a.join]
|
|
315
|
+
end
|
|
316
|
+
['/%ff.txt', '/a%80', '/items/%ff', '/items/%c0%ae%c0%ae', '/a%zz', '/items/%'].each do |path|
|
|
317
|
+
assert_equal 404, request.call(path)[0], "#{prefix}#{path}"
|
|
318
|
+
end
|
|
319
|
+
assert_equal [200, 'root'], request.call('/')
|
|
320
|
+
assert_equal [200, 'cafe'], request.call('/caf%C3%A9')
|
|
321
|
+
assert_equal [200, "caf\u00e9"], request.call('/items/caf%C3%A9')
|
|
322
|
+
assert_equal [200, 'a+b'], request.call('/items/a+b')
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
256
326
|
it 'should keep path params UTF-8 regardless of default_external' do
|
|
257
327
|
mock_app { get(:show, map: '/items/:id') { params[:id] == 'щ' ? 'ok' : "bad #{params[:id].encoding}" } }
|
|
258
328
|
old_verbose = $VERBOSE
|
|
@@ -1174,6 +1244,17 @@ describe 'Routing' do
|
|
|
1174
1244
|
assert_equal 'show 3 1 2', body
|
|
1175
1245
|
end
|
|
1176
1246
|
|
|
1247
|
+
it 'should name the parent param after a parent that ends in s' do
|
|
1248
|
+
mock_app do
|
|
1249
|
+
controller :comments, parent: :status do
|
|
1250
|
+
get(:index) { "index #{params[:status_id]}" }
|
|
1251
|
+
end
|
|
1252
|
+
end
|
|
1253
|
+
get '/status/1/comments'
|
|
1254
|
+
assert_equal 'index 1', body
|
|
1255
|
+
assert_equal '/status/1/comments', @app.url(:comments, :index, status_id: 1)
|
|
1256
|
+
end
|
|
1257
|
+
|
|
1177
1258
|
it 'should respect parent precedence: controllers parents go before route parents' do
|
|
1178
1259
|
mock_app do
|
|
1179
1260
|
controllers :project do
|
|
@@ -2501,4 +2582,21 @@ describe 'Routing' do
|
|
|
2501
2582
|
get '/'
|
|
2502
2583
|
assert_equal 200, status
|
|
2503
2584
|
end
|
|
2585
|
+
|
|
2586
|
+
it 'functions in a standalone app that has a public folder' do
|
|
2587
|
+
Dir.mktmpdir do |root|
|
|
2588
|
+
FileUtils.mkdir_p(File.join(root, 'public'))
|
|
2589
|
+
File.write(File.join(root, 'public', 'a.txt'), 'static')
|
|
2590
|
+
mock_app(Sinatra::Application) do
|
|
2591
|
+
set :root, root
|
|
2592
|
+
register Padrino::Routing
|
|
2593
|
+
get(:index) { 'Standalone' }
|
|
2594
|
+
end
|
|
2595
|
+
get '/'
|
|
2596
|
+
assert_equal 200, status
|
|
2597
|
+
assert_equal 'Standalone', body
|
|
2598
|
+
get '/a.txt'
|
|
2599
|
+
assert_equal 'static', body
|
|
2600
|
+
end
|
|
2601
|
+
end
|
|
2504
2602
|
end
|
data/test/test_server.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
require_relative 'helper'
|
|
2
|
+
require 'open3'
|
|
3
|
+
require 'timeout'
|
|
2
4
|
|
|
3
5
|
describe 'Padrino::Server' do
|
|
4
6
|
describe 'detect_rack_handler' do
|
|
@@ -29,4 +31,60 @@ describe 'Padrino::Server' do
|
|
|
29
31
|
end
|
|
30
32
|
end
|
|
31
33
|
end
|
|
34
|
+
|
|
35
|
+
describe 'signals' do
|
|
36
|
+
# Starts Padrino::Server in a child process on a fake handler that, like WEBrick, yields its
|
|
37
|
+
# server and then loops until the server is shut down, sends the child TERM and returns its output.
|
|
38
|
+
def start_and_term(server_methods)
|
|
39
|
+
script = <<~RUBY
|
|
40
|
+
require 'padrino-core'
|
|
41
|
+
class FakeServer
|
|
42
|
+
#{server_methods}
|
|
43
|
+
def start
|
|
44
|
+
puts 'READY'
|
|
45
|
+
$stdout.flush
|
|
46
|
+
sleep 0.05 until @stopped
|
|
47
|
+
puts 'STOPPED'
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
module FakeHandler
|
|
51
|
+
def self.run(_app, **_options)
|
|
52
|
+
server = FakeServer.new
|
|
53
|
+
yield server
|
|
54
|
+
server.start
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
Rackup::Handler.register('fake', FakeHandler)
|
|
58
|
+
Padrino::Server.start(->(_env) { [200, {}, []] }, server: 'fake', Port: 0)
|
|
59
|
+
RUBY
|
|
60
|
+
lib = File.expand_path('../lib', __dir__)
|
|
61
|
+
Open3.popen2e({ 'RACK_ENV' => 'test' }, RbConfig.ruby, '-I', lib, '-e', script) do |_stdin, out, wait|
|
|
62
|
+
output = +''
|
|
63
|
+
Timeout.timeout(60) do
|
|
64
|
+
output << out.gets.to_s until output.include?('READY') || out.eof?
|
|
65
|
+
Process.kill('TERM', wait.pid)
|
|
66
|
+
output << out.read
|
|
67
|
+
[output, wait.value]
|
|
68
|
+
end
|
|
69
|
+
rescue Timeout::Error
|
|
70
|
+
Process.kill('KILL', wait.pid)
|
|
71
|
+
raise
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'should shut the server down on TERM when it supports shutdown' do
|
|
76
|
+
output, status = start_and_term("def shutdown\n puts 'SHUTDOWN'\n @stopped = true\nend")
|
|
77
|
+
assert status.success?, output
|
|
78
|
+
assert_includes output, 'SHUTDOWN'
|
|
79
|
+
assert_includes output, 'STOPPED'
|
|
80
|
+
refute_includes output, 'SystemExit'
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'should exit on TERM when the server does not support shutdown' do
|
|
84
|
+
output, status = start_and_term('')
|
|
85
|
+
assert status.success?, output
|
|
86
|
+
refute_includes output, 'STOPPED'
|
|
87
|
+
assert_includes output, 'leaves the gun'
|
|
88
|
+
end
|
|
89
|
+
end
|
|
32
90
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: padrino-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.18.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Padrino Team
|
|
@@ -11,7 +11,7 @@ authors:
|
|
|
11
11
|
autorequire:
|
|
12
12
|
bindir: bin
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date: 2026-10-
|
|
14
|
+
date: 2026-10-31 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: ostruct
|
|
@@ -33,14 +33,14 @@ dependencies:
|
|
|
33
33
|
requirements:
|
|
34
34
|
- - '='
|
|
35
35
|
- !ruby/object:Gem::Version
|
|
36
|
-
version: 0.
|
|
36
|
+
version: 0.18.1
|
|
37
37
|
type: :runtime
|
|
38
38
|
prerelease: false
|
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
|
40
40
|
requirements:
|
|
41
41
|
- - '='
|
|
42
42
|
- !ruby/object:Gem::Version
|
|
43
|
-
version: 0.
|
|
43
|
+
version: 0.18.1
|
|
44
44
|
- !ruby/object:Gem::Dependency
|
|
45
45
|
name: rackup
|
|
46
46
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -145,7 +145,11 @@ files:
|
|
|
145
145
|
- test/fixtures/app_gem/lib/app_gem/version.rb
|
|
146
146
|
- test/fixtures/apps/complex.rb
|
|
147
147
|
- test/fixtures/apps/concerned/app.rb
|
|
148
|
+
- test/fixtures/apps/concerned/models/catalog_item.rb
|
|
149
|
+
- test/fixtures/apps/concerned/models/filtered_item.rb
|
|
148
150
|
- test/fixtures/apps/concerned/models/mixins/badmodule.rb
|
|
151
|
+
- test/fixtures/apps/concerned/models/mixins/filterable.rb
|
|
152
|
+
- test/fixtures/apps/concerned/models/mixins/sortable.rb
|
|
149
153
|
- test/fixtures/apps/custom_dependencies/custom_dependencies.rb
|
|
150
154
|
- test/fixtures/apps/custom_dependencies/my_dependencies/my_dependency.rb
|
|
151
155
|
- test/fixtures/apps/demo_app.rb
|
|
@@ -226,6 +230,7 @@ files:
|
|
|
226
230
|
- test/test_dependencies.rb
|
|
227
231
|
- test/test_filters.rb
|
|
228
232
|
- test/test_flash.rb
|
|
233
|
+
- test/test_host_authorization.rb
|
|
229
234
|
- test/test_launcher.rb
|
|
230
235
|
- test/test_locale.rb
|
|
231
236
|
- test/test_logger.rb
|
|
@@ -261,111 +266,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
261
266
|
- !ruby/object:Gem::Version
|
|
262
267
|
version: 1.3.6
|
|
263
268
|
requirements: []
|
|
264
|
-
rubygems_version: 3.5.
|
|
269
|
+
rubygems_version: 3.5.22
|
|
265
270
|
signing_key:
|
|
266
271
|
specification_version: 4
|
|
267
272
|
summary: The required Padrino core gem
|
|
268
|
-
test_files:
|
|
269
|
-
- test/fixtures/app_gem/Gemfile
|
|
270
|
-
- test/fixtures/app_gem/app/app.rb
|
|
271
|
-
- test/fixtures/app_gem/app_gem.gemspec
|
|
272
|
-
- test/fixtures/app_gem/lib/app_gem.rb
|
|
273
|
-
- test/fixtures/app_gem/lib/app_gem/version.rb
|
|
274
|
-
- test/fixtures/apps/complex.rb
|
|
275
|
-
- test/fixtures/apps/concerned/app.rb
|
|
276
|
-
- test/fixtures/apps/concerned/models/mixins/badmodule.rb
|
|
277
|
-
- test/fixtures/apps/custom_dependencies/custom_dependencies.rb
|
|
278
|
-
- test/fixtures/apps/custom_dependencies/my_dependencies/my_dependency.rb
|
|
279
|
-
- test/fixtures/apps/demo_app.rb
|
|
280
|
-
- test/fixtures/apps/demo_demo.rb
|
|
281
|
-
- test/fixtures/apps/demo_project/api/app.rb
|
|
282
|
-
- test/fixtures/apps/demo_project/api/lib/api_lib.rb
|
|
283
|
-
- test/fixtures/apps/demo_project/app.rb
|
|
284
|
-
- test/fixtures/apps/external_apps/fake_lib.rb
|
|
285
|
-
- test/fixtures/apps/external_apps/fake_root.rb
|
|
286
|
-
- test/fixtures/apps/external_constants/app.rb
|
|
287
|
-
- test/fixtures/apps/external_constants/helpers/external_constants_helpers.rb
|
|
288
|
-
- test/fixtures/apps/helpers/class_methods_helpers.rb
|
|
289
|
-
- test/fixtures/apps/helpers/instance_methods_helpers.rb
|
|
290
|
-
- test/fixtures/apps/helpers/system_helpers.rb
|
|
291
|
-
- test/fixtures/apps/kiq.rb
|
|
292
|
-
- test/fixtures/apps/lib/myklass.rb
|
|
293
|
-
- test/fixtures/apps/lib/myklass/mysubklass.rb
|
|
294
|
-
- test/fixtures/apps/models/child.rb
|
|
295
|
-
- test/fixtures/apps/models/parent.rb
|
|
296
|
-
- test/fixtures/apps/mountable_apps/rack_apps.rb
|
|
297
|
-
- test/fixtures/apps/mountable_apps/static.html
|
|
298
|
-
- test/fixtures/apps/precompiled_app.rb
|
|
299
|
-
- test/fixtures/apps/simple.rb
|
|
300
|
-
- test/fixtures/apps/static.rb
|
|
301
|
-
- test/fixtures/apps/stealthy/app.rb
|
|
302
|
-
- test/fixtures/apps/stealthy/helpers/stealthy_class_helpers.rb
|
|
303
|
-
- test/fixtures/apps/system.rb
|
|
304
|
-
- test/fixtures/apps/system_class_methods_demo.rb
|
|
305
|
-
- test/fixtures/apps/system_instance_methods_demo.rb
|
|
306
|
-
- test/fixtures/dependencies/a.rb
|
|
307
|
-
- test/fixtures/dependencies/autoload/a.rb
|
|
308
|
-
- test/fixtures/dependencies/autoload/b.rb
|
|
309
|
-
- test/fixtures/dependencies/autoload/lazy.rb
|
|
310
|
-
- test/fixtures/dependencies/b.rb
|
|
311
|
-
- test/fixtures/dependencies/c.rb
|
|
312
|
-
- test/fixtures/dependencies/circular/e.rb
|
|
313
|
-
- test/fixtures/dependencies/circular/f.rb
|
|
314
|
-
- test/fixtures/dependencies/circular/g.rb
|
|
315
|
-
- test/fixtures/dependencies/d.rb
|
|
316
|
-
- test/fixtures/dependencies/gc/t.rb
|
|
317
|
-
- test/fixtures/dependencies/gc/u.rb
|
|
318
|
-
- test/fixtures/dependencies/gc/v.rb
|
|
319
|
-
- test/fixtures/dependencies/gc/w.rb
|
|
320
|
-
- test/fixtures/dependencies/gc/x.rb
|
|
321
|
-
- test/fixtures/dependencies/gc/y.rb
|
|
322
|
-
- test/fixtures/dependencies/linear/h.rb
|
|
323
|
-
- test/fixtures/dependencies/linear/i.rb
|
|
324
|
-
- test/fixtures/dependencies/nested/j.rb
|
|
325
|
-
- test/fixtures/dependencies/nested/k.rb
|
|
326
|
-
- test/fixtures/dependencies/nested/l.rb
|
|
327
|
-
- test/fixtures/dependencies/nested/m.rb
|
|
328
|
-
- test/fixtures/dependencies/nested/n.rb
|
|
329
|
-
- test/fixtures/dependencies/nested/ooo.rb
|
|
330
|
-
- test/fixtures/dependencies/nested/ppp.rb
|
|
331
|
-
- test/fixtures/dependencies/nested/qqq.rb
|
|
332
|
-
- test/fixtures/dependencies/nested/rrr.rb
|
|
333
|
-
- test/fixtures/dependencies/nested/sss.rb
|
|
334
|
-
- test/fixtures/dependencies/parent/admin.rb
|
|
335
|
-
- test/fixtures/dependencies/parent/trackable.rb
|
|
336
|
-
- test/fixtures/dependencies/parent/user.rb
|
|
337
|
-
- test/fixtures/dependencies/partial/a.rb
|
|
338
|
-
- test/fixtures/dependencies/partial/b.rb
|
|
339
|
-
- test/fixtures/dependencies/reload/forced.rb
|
|
340
|
-
- test/fixtures/dependencies/reload/untracked.rb
|
|
341
|
-
- test/fixtures/dependencies/reload/untracked_forced.rb
|
|
342
|
-
- test/fixtures/dependencies/untracked/a.rb
|
|
343
|
-
- test/fixtures/dependencies/untracked/b.rb
|
|
344
|
-
- test/fixtures/reloadable_apps/external/app/app.rb
|
|
345
|
-
- test/fixtures/reloadable_apps/external/app/controllers/base.rb
|
|
346
|
-
- test/fixtures/reloadable_apps/main/app.rb
|
|
347
|
-
- test/helper.rb
|
|
348
|
-
- test/test_application.rb
|
|
349
|
-
- test/test_binstub.rb
|
|
350
|
-
- test/test_cli.rb
|
|
351
|
-
- test/test_configuration.rb
|
|
352
|
-
- test/test_core.rb
|
|
353
|
-
- test/test_csrf_protection.rb
|
|
354
|
-
- test/test_dependencies.rb
|
|
355
|
-
- test/test_filters.rb
|
|
356
|
-
- test/test_flash.rb
|
|
357
|
-
- test/test_launcher.rb
|
|
358
|
-
- test/test_locale.rb
|
|
359
|
-
- test/test_logger.rb
|
|
360
|
-
- test/test_mounter.rb
|
|
361
|
-
- test/test_params_protection.rb
|
|
362
|
-
- test/test_rake_tasks.rb
|
|
363
|
-
- test/test_reloader_complex.rb
|
|
364
|
-
- test/test_reloader_external.rb
|
|
365
|
-
- test/test_reloader_simple.rb
|
|
366
|
-
- test/test_reloader_storage.rb
|
|
367
|
-
- test/test_reloader_system.rb
|
|
368
|
-
- test/test_restful_routing.rb
|
|
369
|
-
- test/test_router.rb
|
|
370
|
-
- test/test_routing.rb
|
|
371
|
-
- test/test_server.rb
|
|
273
|
+
test_files: []
|