puma-simon 3.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/.github/issue_template.md +20 -0
  3. data/.gitignore +18 -0
  4. data/.hoeignore +12 -0
  5. data/.travis.yml +29 -0
  6. data/DEPLOYMENT.md +91 -0
  7. data/Gemfile +12 -0
  8. data/History.md +1254 -0
  9. data/LICENSE +26 -0
  10. data/Manifest.txt +78 -0
  11. data/README.md +353 -0
  12. data/Rakefile +158 -0
  13. data/Release.md +9 -0
  14. data/bin/puma +10 -0
  15. data/bin/puma-wild +31 -0
  16. data/bin/pumactl +12 -0
  17. data/docs/nginx.md +80 -0
  18. data/docs/signals.md +43 -0
  19. data/docs/systemd.md +197 -0
  20. data/examples/CA/cacert.pem +23 -0
  21. data/examples/CA/newcerts/cert_1.pem +19 -0
  22. data/examples/CA/newcerts/cert_2.pem +19 -0
  23. data/examples/CA/private/cakeypair.pem +30 -0
  24. data/examples/CA/serial +1 -0
  25. data/examples/config.rb +200 -0
  26. data/examples/plugins/redis_stop_puma.rb +46 -0
  27. data/examples/puma/cert_puma.pem +19 -0
  28. data/examples/puma/client-certs/ca.crt +19 -0
  29. data/examples/puma/client-certs/ca.key +27 -0
  30. data/examples/puma/client-certs/client.crt +19 -0
  31. data/examples/puma/client-certs/client.key +27 -0
  32. data/examples/puma/client-certs/client_expired.crt +19 -0
  33. data/examples/puma/client-certs/client_expired.key +27 -0
  34. data/examples/puma/client-certs/client_unknown.crt +19 -0
  35. data/examples/puma/client-certs/client_unknown.key +27 -0
  36. data/examples/puma/client-certs/generate.rb +78 -0
  37. data/examples/puma/client-certs/keystore.jks +0 -0
  38. data/examples/puma/client-certs/server.crt +19 -0
  39. data/examples/puma/client-certs/server.key +27 -0
  40. data/examples/puma/client-certs/server.p12 +0 -0
  41. data/examples/puma/client-certs/unknown_ca.crt +19 -0
  42. data/examples/puma/client-certs/unknown_ca.key +27 -0
  43. data/examples/puma/csr_puma.pem +11 -0
  44. data/examples/puma/keystore.jks +0 -0
  45. data/examples/puma/puma_keypair.pem +15 -0
  46. data/examples/qc_config.rb +13 -0
  47. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  48. data/ext/puma_http11/ext_help.h +15 -0
  49. data/ext/puma_http11/extconf.rb +15 -0
  50. data/ext/puma_http11/http11_parser.c +1069 -0
  51. data/ext/puma_http11/http11_parser.h +65 -0
  52. data/ext/puma_http11/http11_parser.java.rl +161 -0
  53. data/ext/puma_http11/http11_parser.rl +147 -0
  54. data/ext/puma_http11/http11_parser_common.rl +54 -0
  55. data/ext/puma_http11/io_buffer.c +155 -0
  56. data/ext/puma_http11/mini_ssl.c +457 -0
  57. data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
  58. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +473 -0
  59. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +339 -0
  60. data/ext/puma_http11/puma_http11.c +500 -0
  61. data/gemfiles/2.1-Gemfile +12 -0
  62. data/lib/puma.rb +15 -0
  63. data/lib/puma/accept_nonblock.rb +23 -0
  64. data/lib/puma/app/status.rb +66 -0
  65. data/lib/puma/binder.rb +402 -0
  66. data/lib/puma/cli.rb +220 -0
  67. data/lib/puma/client.rb +434 -0
  68. data/lib/puma/cluster.rb +510 -0
  69. data/lib/puma/commonlogger.rb +106 -0
  70. data/lib/puma/compat.rb +14 -0
  71. data/lib/puma/configuration.rb +364 -0
  72. data/lib/puma/const.rb +224 -0
  73. data/lib/puma/control_cli.rb +259 -0
  74. data/lib/puma/convenient.rb +23 -0
  75. data/lib/puma/daemon_ext.rb +31 -0
  76. data/lib/puma/delegation.rb +11 -0
  77. data/lib/puma/detect.rb +13 -0
  78. data/lib/puma/dsl.rb +486 -0
  79. data/lib/puma/events.rb +152 -0
  80. data/lib/puma/io_buffer.rb +7 -0
  81. data/lib/puma/java_io_buffer.rb +45 -0
  82. data/lib/puma/jruby_restart.rb +83 -0
  83. data/lib/puma/launcher.rb +410 -0
  84. data/lib/puma/minissl.rb +221 -0
  85. data/lib/puma/null_io.rb +42 -0
  86. data/lib/puma/plugin.rb +115 -0
  87. data/lib/puma/plugin/tmp_restart.rb +35 -0
  88. data/lib/puma/rack/backports/uri/common_193.rb +33 -0
  89. data/lib/puma/rack/builder.rb +298 -0
  90. data/lib/puma/rack/urlmap.rb +91 -0
  91. data/lib/puma/rack_default.rb +7 -0
  92. data/lib/puma/reactor.rb +210 -0
  93. data/lib/puma/runner.rb +171 -0
  94. data/lib/puma/server.rb +949 -0
  95. data/lib/puma/single.rb +112 -0
  96. data/lib/puma/state_file.rb +29 -0
  97. data/lib/puma/tcp_logger.rb +39 -0
  98. data/lib/puma/thread_pool.rb +297 -0
  99. data/lib/puma/util.rb +128 -0
  100. data/lib/rack/handler/puma.rb +78 -0
  101. data/puma.gemspec +52 -0
  102. data/test/ab_rs.rb +22 -0
  103. data/test/config.rb +2 -0
  104. data/test/config/app.rb +9 -0
  105. data/test/config/plugin.rb +1 -0
  106. data/test/config/settings.rb +2 -0
  107. data/test/config/state_file_testing_config.rb +14 -0
  108. data/test/hello-bind.ru +2 -0
  109. data/test/hello-delay.ru +3 -0
  110. data/test/hello-map.ru +3 -0
  111. data/test/hello-post.ru +4 -0
  112. data/test/hello-stuck.ru +1 -0
  113. data/test/hello-tcp.ru +5 -0
  114. data/test/hello.ru +1 -0
  115. data/test/hijack.ru +6 -0
  116. data/test/hijack2.ru +5 -0
  117. data/test/lobster.ru +4 -0
  118. data/test/shell/run.sh +24 -0
  119. data/test/shell/t1.rb +19 -0
  120. data/test/shell/t1_conf.rb +3 -0
  121. data/test/shell/t2.rb +17 -0
  122. data/test/shell/t2_conf.rb +6 -0
  123. data/test/shell/t3.rb +25 -0
  124. data/test/shell/t3_conf.rb +5 -0
  125. data/test/slow.ru +4 -0
  126. data/test/ssl_config.rb +4 -0
  127. data/test/test_app_status.rb +93 -0
  128. data/test/test_binder.rb +31 -0
  129. data/test/test_cli.rb +209 -0
  130. data/test/test_config.rb +95 -0
  131. data/test/test_events.rb +161 -0
  132. data/test/test_helper.rb +50 -0
  133. data/test/test_http10.rb +27 -0
  134. data/test/test_http11.rb +186 -0
  135. data/test/test_integration.rb +247 -0
  136. data/test/test_iobuffer.rb +39 -0
  137. data/test/test_minissl.rb +29 -0
  138. data/test/test_null_io.rb +49 -0
  139. data/test/test_persistent.rb +245 -0
  140. data/test/test_puma_server.rb +626 -0
  141. data/test/test_puma_server_ssl.rb +222 -0
  142. data/test/test_rack_handler.rb +57 -0
  143. data/test/test_rack_server.rb +138 -0
  144. data/test/test_tcp_logger.rb +39 -0
  145. data/test/test_tcp_rack.rb +36 -0
  146. data/test/test_thread_pool.rb +250 -0
  147. data/test/test_unix_socket.rb +35 -0
  148. data/test/test_web_server.rb +88 -0
  149. data/tools/jungle/README.md +9 -0
  150. data/tools/jungle/init.d/README.md +59 -0
  151. data/tools/jungle/init.d/puma +421 -0
  152. data/tools/jungle/init.d/run-puma +18 -0
  153. data/tools/jungle/upstart/README.md +61 -0
  154. data/tools/jungle/upstart/puma-manager.conf +31 -0
  155. data/tools/jungle/upstart/puma.conf +69 -0
  156. data/tools/trickletest.rb +45 -0
  157. metadata +297 -0
@@ -0,0 +1,25 @@
1
+ system "ruby -rubygems -I../../lib ../../bin/puma -p 10102 -C t3_conf.rb ../hello.ru &"
2
+ sleep 5
3
+
4
+ worker_pid_was_present = File.file? "t3-worker-2-pid"
5
+
6
+ system "kill `cat t3-worker-2-pid`" # kill off a worker
7
+
8
+ sleep 2
9
+
10
+ worker_index_within_number_of_workers = !File.file?("t3-worker-3-pid")
11
+
12
+ system "kill `cat t3-pid`"
13
+
14
+ File.unlink "t3-pid" if File.file? "t3-pid"
15
+ File.unlink "t3-worker-0-pid" if File.file? "t3-worker-0-pid"
16
+ File.unlink "t3-worker-1-pid" if File.file? "t3-worker-1-pid"
17
+ File.unlink "t3-worker-2-pid" if File.file? "t3-worker-2-pid"
18
+ File.unlink "t3-worker-3-pid" if File.file? "t3-worker-3-pid"
19
+
20
+ if worker_pid_was_present and worker_index_within_number_of_workers
21
+ exit 0
22
+ else
23
+ exit 1
24
+ end
25
+
@@ -0,0 +1,5 @@
1
+ pidfile "t3-pid"
2
+ workers 3
3
+ on_worker_boot do |index|
4
+ File.open("t3-worker-#{index}-pid", "w") { |f| f.puts Process.pid }
5
+ end
@@ -0,0 +1,4 @@
1
+ run lambda { |env|
2
+ 30000000.times { }
3
+ [200, {}, ["Hello World"]]
4
+ }
@@ -0,0 +1,4 @@
1
+ key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
2
+ cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
3
+
4
+ ssl_bind "0.0.0.0", 9292, :cert => cert, :key => key
@@ -0,0 +1,93 @@
1
+ require "test_helper"
2
+
3
+ require "puma/app/status"
4
+ require "rack"
5
+
6
+ class TestAppStatus < Minitest::Test
7
+ class FakeServer
8
+ def initialize
9
+ @status = :running
10
+ @backlog = 0
11
+ @running = 0
12
+ end
13
+
14
+ attr_reader :status
15
+ attr_accessor :backlog, :running
16
+
17
+ def stop
18
+ @status = :stop
19
+ end
20
+
21
+ def halt
22
+ @status = :halt
23
+ end
24
+
25
+ def stats
26
+ "{}"
27
+ end
28
+ end
29
+
30
+ def setup
31
+ @server = FakeServer.new
32
+ @app = Puma::App::Status.new(@server)
33
+ @app.auth_token = nil
34
+ end
35
+
36
+ def lint(uri)
37
+ app = Rack::Lint.new @app
38
+ mock_env = Rack::MockRequest.env_for uri
39
+ app.call mock_env
40
+ end
41
+
42
+ def test_bad_token
43
+ @app.auth_token = "abcdef"
44
+
45
+ status, _, _ = lint('/whatever')
46
+
47
+ assert_equal 403, status
48
+ end
49
+
50
+ def test_good_token
51
+ @app.auth_token = "abcdef"
52
+
53
+ status, _, _ = lint('/whatever?token=abcdef')
54
+
55
+ assert_equal 404, status
56
+ end
57
+
58
+ def test_unsupported
59
+ status, _, _ = lint('/not-real')
60
+
61
+ assert_equal 404, status
62
+ end
63
+
64
+ def test_stop
65
+ status, _ , app = lint('/stop')
66
+
67
+ assert_equal :stop, @server.status
68
+ assert_equal 200, status
69
+ assert_equal ['{ "status": "ok" }'], app.enum_for.to_a
70
+ end
71
+
72
+ def test_halt
73
+ status, _ , app = lint('/halt')
74
+
75
+ assert_equal :halt, @server.status
76
+ assert_equal 200, status
77
+ assert_equal ['{ "status": "ok" }'], app.enum_for.to_a
78
+ end
79
+
80
+ def test_stats
81
+ @server.backlog = 1
82
+ @server.running = 9
83
+ status, _ , app = lint('/stats')
84
+
85
+ assert_equal 200, status
86
+ assert_equal ['{}'], app.enum_for.to_a
87
+ end
88
+
89
+ def test_alternate_location
90
+ status, _ , _ = lint('__alternatE_location_/stats')
91
+ assert_equal 200, status
92
+ end
93
+ end
@@ -0,0 +1,31 @@
1
+ require "test_helper"
2
+
3
+ require "puma/binder"
4
+ require "puma/events"
5
+ require "puma/puma_http11"
6
+
7
+ class TestBinder < Minitest::Test
8
+ def setup
9
+ @events = Puma::Events.null
10
+ @binder = Puma::Binder.new(@events)
11
+ end
12
+
13
+ def test_localhost_addresses_dont_alter_listeners_for_tcp_addresses
14
+ skip_on_jruby
15
+
16
+ @binder.parse(["tcp://localhost:10001"], @events)
17
+
18
+ assert_equal [], @binder.listeners
19
+ end
20
+
21
+ def test_localhost_addresses_dont_alter_listeners_for_ssl_addresses
22
+ skip_on_jruby
23
+
24
+ key = File.expand_path "../../examples/puma/puma_keypair.pem", __FILE__
25
+ cert = File.expand_path "../../examples/puma/cert_puma.pem", __FILE__
26
+
27
+ @binder.parse(["ssl://localhost:10002?key=#{key}&cert=#{cert}"], @events)
28
+
29
+ assert_equal [], @binder.listeners
30
+ end
31
+ end
@@ -0,0 +1,209 @@
1
+ require "test_helper"
2
+
3
+ require "puma/cli"
4
+
5
+ class TestCLI < Minitest::Test
6
+ def setup
7
+ @environment = 'production'
8
+ @tmp_file = Tempfile.new("puma-test")
9
+ @tmp_path = @tmp_file.path
10
+ @tmp_file.close!
11
+
12
+ @tmp_path2 = "#{@tmp_path}2"
13
+
14
+ File.unlink @tmp_path if File.exist? @tmp_path
15
+ File.unlink @tmp_path2 if File.exist? @tmp_path2
16
+
17
+ @wait, @ready = IO.pipe
18
+
19
+ @events = Puma::Events.strings
20
+ @events.on_booted { @ready << "!" }
21
+ end
22
+
23
+ def wait_booted
24
+ @wait.sysread 1
25
+ end
26
+
27
+ def teardown
28
+ File.unlink @tmp_path if File.exist? @tmp_path
29
+ File.unlink @tmp_path2 if File.exist? @tmp_path2
30
+
31
+ @wait.close
32
+ @ready.close
33
+ end
34
+
35
+ def test_pid_file
36
+ cli = Puma::CLI.new ["--pidfile", @tmp_path]
37
+ cli.launcher.write_pid
38
+
39
+ assert_equal File.read(@tmp_path).strip.to_i, Process.pid
40
+ end
41
+
42
+ def test_control_for_tcp
43
+ url = "tcp://127.0.0.1:9877/"
44
+ cli = Puma::CLI.new ["-b", "tcp://127.0.0.1:9876",
45
+ "--control", url,
46
+ "--control-token", "",
47
+ "test/lobster.ru"], @events
48
+
49
+ t = Thread.new do
50
+ Thread.current.abort_on_exception = true
51
+ cli.run
52
+ end
53
+
54
+ wait_booted
55
+
56
+ s = TCPSocket.new "127.0.0.1", 9877
57
+ s << "GET /stats HTTP/1.0\r\n\r\n"
58
+ body = s.read
59
+ assert_equal '{ "backlog": 0, "running": 0 }', body.split("\r\n").last
60
+
61
+ cli.launcher.stop
62
+ t.join
63
+ end
64
+
65
+ unless Puma.jruby? || Puma.windows?
66
+ def test_control_clustered
67
+ url = "unix://#{@tmp_path}"
68
+
69
+ cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
70
+ "-t", "2:2",
71
+ "-w", "2",
72
+ "--control", url,
73
+ "--control-token", "",
74
+ "test/lobster.ru"], @events
75
+
76
+ t = Thread.new { cli.run }
77
+ t.abort_on_exception = true
78
+
79
+ wait_booted
80
+
81
+ sleep 2
82
+
83
+ s = UNIXSocket.new @tmp_path
84
+ s << "GET /stats HTTP/1.0\r\n\r\n"
85
+ body = s.read
86
+
87
+ require 'json'
88
+ status = JSON.parse(body.split("\n").last)
89
+
90
+ assert_equal 2, status["workers"]
91
+
92
+ # wait until the first status ping has come through
93
+ sleep 6
94
+ s = UNIXSocket.new @tmp_path
95
+ s << "GET /stats HTTP/1.0\r\n\r\n"
96
+ body = s.read
97
+ assert_match(/\{ "workers": 2, "phase": 0, "booted_workers": 2, "old_workers": 0, "worker_status": \[\{ "pid": \d+, "index": 0, "phase": 0, "booted": true, "last_checkin": "[^"]+", "last_status": \{ "backlog":0, "running":2 \} \},\{ "pid": \d+, "index": 1, "phase": 0, "booted": true, "last_checkin": "[^"]+", "last_status": \{ "backlog":0, "running":2 \} \}\] \}/, body.split("\r\n").last)
98
+
99
+ cli.launcher.stop
100
+ t.join
101
+ end
102
+
103
+ def test_control
104
+ url = "unix://#{@tmp_path}"
105
+
106
+ cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
107
+ "--control", url,
108
+ "--control-token", "",
109
+ "test/lobster.ru"], @events
110
+
111
+ t = Thread.new { cli.run }
112
+ t.abort_on_exception = true
113
+
114
+ wait_booted
115
+
116
+ s = UNIXSocket.new @tmp_path
117
+ s << "GET /stats HTTP/1.0\r\n\r\n"
118
+ body = s.read
119
+
120
+ assert_equal '{ "backlog": 0, "running": 0 }', body.split("\r\n").last
121
+
122
+ cli.launcher.stop
123
+ t.join
124
+ end
125
+
126
+ def test_control_stop
127
+ url = "unix://#{@tmp_path}"
128
+
129
+ cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
130
+ "--control", url,
131
+ "--control-token", "",
132
+ "test/lobster.ru"], @events
133
+
134
+ t = Thread.new { cli.run }
135
+ t.abort_on_exception = true
136
+
137
+ wait_booted
138
+
139
+ s = UNIXSocket.new @tmp_path
140
+ s << "GET /stop HTTP/1.0\r\n\r\n"
141
+ body = s.read
142
+
143
+ assert_equal '{ "status": "ok" }', body.split("\r\n").last
144
+
145
+ t.join
146
+ end
147
+
148
+ def test_tmp_control
149
+ url = "tcp://127.0.0.1:8232"
150
+ cli = Puma::CLI.new ["--state", @tmp_path, "--control", "auto"]
151
+ cli.launcher.write_state
152
+
153
+ data = YAML.load File.read(@tmp_path)
154
+
155
+ assert_equal Process.pid, data["pid"]
156
+
157
+ url = data["control_url"]
158
+
159
+ m = %r!unix://(.*)!.match(url)
160
+
161
+ assert m, "'#{url}' is not a URL"
162
+ end
163
+
164
+ def test_state_file_callback_filtering
165
+ cli = Puma::CLI.new [ "--config", "test/config/state_file_testing_config.rb",
166
+ "--state", @tmp_path ]
167
+ cli.launcher.write_state
168
+
169
+ data = YAML.load_file(@tmp_path)
170
+
171
+ keys_not_stripped = data.keys & Puma::CLI::KEYS_NOT_TO_PERSIST_IN_STATE
172
+ assert_empty keys_not_stripped
173
+ end
174
+
175
+ end # JRUBY or Windows
176
+
177
+ def test_state
178
+ url = "tcp://127.0.0.1:8232"
179
+ cli = Puma::CLI.new ["--state", @tmp_path, "--control", url]
180
+ cli.launcher.write_state
181
+
182
+ data = YAML.load File.read(@tmp_path)
183
+
184
+ assert_equal Process.pid, data["pid"]
185
+ assert_equal url, data["control_url"]
186
+ end
187
+
188
+ def test_load_path
189
+ cli = Puma::CLI.new ["--include", 'foo/bar']
190
+
191
+ assert_equal 'foo/bar', $LOAD_PATH[0]
192
+ $LOAD_PATH.shift
193
+
194
+ cli = Puma::CLI.new ["--include", 'foo/bar:baz/qux']
195
+
196
+ assert_equal 'foo/bar', $LOAD_PATH[0]
197
+ $LOAD_PATH.shift
198
+ assert_equal 'baz/qux', $LOAD_PATH[0]
199
+ $LOAD_PATH.shift
200
+ end
201
+
202
+ def test_environment
203
+ ENV.delete 'RACK_ENV'
204
+
205
+ Puma::CLI.new ["--environment", @environment]
206
+
207
+ assert_equal ENV['RACK_ENV'], @environment
208
+ end
209
+ end
@@ -0,0 +1,95 @@
1
+ require "test_helper"
2
+
3
+ require "puma/configuration"
4
+
5
+ class TestConfigFile < Minitest::Test
6
+ def test_app_from_rackup
7
+ conf = Puma::Configuration.new do |c|
8
+ c.rackup "test/hello-bind.ru"
9
+ end
10
+ conf.load
11
+
12
+ conf.app
13
+
14
+ assert_equal ["tcp://127.0.0.1:9292"], conf.options[:binds]
15
+ end
16
+
17
+ def test_app_from_app_DSL
18
+ conf = Puma::Configuration.new do |c|
19
+ c.load "test/config/app.rb"
20
+ end
21
+ conf.load
22
+
23
+ app = conf.app
24
+
25
+ assert_equal [200, {}, ["embedded app"]], app.call({})
26
+ end
27
+
28
+ def test_double_bind_port
29
+ port = (rand(10_000) + 30_000).to_s
30
+ with_env("PORT" => port) do
31
+ conf = Puma::Configuration.new do |c|
32
+ c.bind "tcp://#{Puma::Configuration::DefaultTCPHost}:#{port}"
33
+ c.load "test/config/app.rb"
34
+ end
35
+
36
+ conf.load
37
+
38
+ assert_equal ["tcp://0.0.0.0:#{port}"], conf.options[:binds]
39
+ end
40
+ end
41
+
42
+ def test_lowleve_error_handler_DSL
43
+ conf = Puma::Configuration.new do |c|
44
+ c.load "test/config/app.rb"
45
+ end
46
+ conf.load
47
+
48
+ app = conf.options[:lowlevel_error_handler]
49
+
50
+ assert_equal [200, {}, ["error page"]], app.call({})
51
+ end
52
+
53
+ def test_allow_users_to_override_default_options
54
+ conf = Puma::Configuration.new(restart_cmd: 'bin/rails server')
55
+
56
+ assert_equal 'bin/rails server', conf.options[:restart_cmd]
57
+ end
58
+
59
+ def test_overwrite_options
60
+ conf = Puma::Configuration.new do |c|
61
+ c.workers 3
62
+ end
63
+ conf.load
64
+
65
+ assert_equal conf.options[:workers], 3
66
+ conf.options[:workers] += 1
67
+ assert_equal conf.options[:workers], 4
68
+ end
69
+
70
+ def test_parameters_overwrite_files
71
+ conf = Puma::Configuration.new(config_files: ['test/config/settings.rb']) do |c|
72
+ c.port 3030
73
+ end
74
+ conf.load
75
+
76
+ assert_match(/:3030$/, conf.options[:binds].first)
77
+ assert_equal 3, conf.options[:min_threads]
78
+ assert_equal 5, conf.options[:max_threads]
79
+ end
80
+
81
+ private
82
+
83
+ def with_env(env = {})
84
+ original_env = {}
85
+ env.each do |k, v|
86
+ original_env[k] = ENV[k]
87
+ ENV[k] = v
88
+ end
89
+ yield
90
+ ensure
91
+ original_env.each do |k, v|
92
+ ENV[k] = v
93
+ end
94
+ end
95
+ end