async-websocket 0.14.0 → 0.19.0

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/lib/async/websocket/adapters/http.rb +64 -0
  3. data/lib/async/websocket/adapters/rack.rb +11 -28
  4. data/{spec/async/websocket/upgrade.rb → lib/async/websocket/adapters/rails.rb} +20 -19
  5. data/lib/async/websocket/client.rb +19 -13
  6. data/lib/async/websocket/connect_request.rb +4 -0
  7. data/lib/async/websocket/connection.rb +1 -0
  8. data/lib/async/websocket/version.rb +1 -1
  9. metadata +37 -101
  10. data/.editorconfig +0 -6
  11. data/.gitignore +0 -13
  12. data/.rspec +0 -3
  13. data/.travis.yml +0 -21
  14. data/Gemfile +0 -7
  15. data/README.md +0 -143
  16. data/async-websocket.gemspec +0 -29
  17. data/examples/chat/.env +0 -3
  18. data/examples/chat/README.md +0 -147
  19. data/examples/chat/client.rb +0 -32
  20. data/examples/chat/config.ru +0 -150
  21. data/examples/chat/multi-client.rb +0 -79
  22. data/examples/mud/client.rb +0 -34
  23. data/examples/mud/config.ru +0 -142
  24. data/examples/rack/client.rb +0 -19
  25. data/examples/rack/config.ru +0 -12
  26. data/examples/utopia/.bowerrc +0 -4
  27. data/examples/utopia/.gitignore +0 -9
  28. data/examples/utopia/.rspec +0 -4
  29. data/examples/utopia/Gemfile +0 -33
  30. data/examples/utopia/Guardfile +0 -29
  31. data/examples/utopia/README.md +0 -16
  32. data/examples/utopia/Rakefile +0 -8
  33. data/examples/utopia/config.ru +0 -47
  34. data/examples/utopia/config/README.md +0 -7
  35. data/examples/utopia/config/environment.rb +0 -8
  36. data/examples/utopia/lib/readme.txt +0 -1
  37. data/examples/utopia/pages/_heading.xnode +0 -2
  38. data/examples/utopia/pages/_page.xnode +0 -30
  39. data/examples/utopia/pages/client/client.js +0 -28
  40. data/examples/utopia/pages/client/index.xnode +0 -8
  41. data/examples/utopia/pages/errors/exception.xnode +0 -5
  42. data/examples/utopia/pages/errors/file-not-found.xnode +0 -5
  43. data/examples/utopia/pages/links.yaml +0 -2
  44. data/examples/utopia/pages/server/controller.rb +0 -26
  45. data/examples/utopia/public/_static/icon.png +0 -0
  46. data/examples/utopia/public/_static/site.css +0 -205
  47. data/examples/utopia/public/_static/utopia-background.svg +0 -1
  48. data/examples/utopia/public/_static/utopia.svg +0 -1
  49. data/examples/utopia/public/readme.txt +0 -1
  50. data/examples/utopia/spec/spec_helper.rb +0 -31
  51. data/examples/utopia/spec/website_context.rb +0 -11
  52. data/examples/utopia/spec/website_spec.rb +0 -56
  53. data/examples/utopia/tasks/bower.rake +0 -45
  54. data/examples/utopia/tasks/deploy.rake +0 -13
  55. data/examples/utopia/tasks/development.rake +0 -34
  56. data/examples/utopia/tasks/environment.rake +0 -17
  57. data/examples/utopia/tasks/log.rake +0 -17
  58. data/examples/utopia/tasks/static.rake +0 -43
  59. data/spec/async/websocket/adapters/rack/client.rb +0 -38
  60. data/spec/async/websocket/adapters/rack/config.ru +0 -23
  61. data/spec/async/websocket/adapters/rack_spec.rb +0 -84
  62. data/spec/async/websocket/connection_spec.rb +0 -34
  63. data/spec/async/websocket/server_examples.rb +0 -117
  64. data/spec/async/websocket/server_spec.rb +0 -31
  65. data/spec/spec_helper.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc7cbd8769e7a5f2c98114c19e3e5b17b8e65b1d172e961b2efaca084b108dda
4
- data.tar.gz: 25f69580b9e2f8839c2e3408bb926430eb720e9ec98ff483b4e69d9c3202f207
3
+ metadata.gz: 0fd37d5eb2b762f6f5d7bb8027d3d51c1a9300f63d6d38b5bd7aec5a373d1a0d
4
+ data.tar.gz: 0c8cf165cf58f920ac80534b08fef3146e08caa3676542bea72b6ae2b2fdbe9c
5
5
  SHA512:
6
- metadata.gz: dee41300f15a14eab436499d4ffaf3ec6906bd23000b4f2d1d5db996d70d43aec64f63d4c444731888eed71ecb6258a3361db643b782c815b85e74a062e45e7e
7
- data.tar.gz: 68228e3e5abb6ac3eab178ecaf4d7a2e4556d6baa99c088b4b0bab6788cef669d1fdd1dae54a512b042b586fe35f94b2031d0540aa7261d3c2af26cffc5eaad5
6
+ metadata.gz: e05aaeb2c9f4ce782bf99c57789d0e21d89a6fc262eae1141451c40c2d17776920117deae3f7c2864218a90ec5a5f7feb5ff4604460cb1cb5ee2ab783fac230d
7
+ data.tar.gz: 919c2aa1bafd05fe0e2283d201553fd546f9e734f7f4134c9adfd62ea9fb6ac714eace090d487b22fd3613dcd52d64eed6a225143a56687f9cecddea973e84db
@@ -0,0 +1,64 @@
1
+ # frozen_string_literals: true
2
+ #
3
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require_relative '../connection'
24
+ require_relative '../response'
25
+
26
+ module Async
27
+ module WebSocket
28
+ module Adapters
29
+ module HTTP
30
+ include ::Protocol::WebSocket::Headers
31
+
32
+ def self.websocket?(request)
33
+ Array(request.protocol).include?(PROTOCOL)
34
+ end
35
+
36
+ def self.open(request, headers: [], protocols: [], handler: Connection, **options, &block)
37
+ if Array(request.protocol).include?(PROTOCOL)
38
+ # Select websocket sub-protocol:
39
+ if requested_protocol = request.headers[SEC_WEBSOCKET_PROTOCOL]
40
+ protocol = (requested_protocol & protocols).first
41
+ end
42
+
43
+ response = Response.for(request, headers, protocol: protocol, **options) do |stream|
44
+ # Once we get to this point, we no longer need to hold on to the response:
45
+ response = nil
46
+
47
+ framer = Protocol::WebSocket::Framer.new(stream)
48
+ connection = handler.call(framer, protocol)
49
+
50
+ yield connection
51
+
52
+ connection.close unless connection.closed?
53
+ end
54
+
55
+ # Once we get to this point, we no longer need to hold on to the request:
56
+ request = nil
57
+
58
+ return response
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -20,8 +20,7 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- require_relative '../connection'
24
- require_relative '../response'
23
+ require_relative 'http'
25
24
 
26
25
  module Async
27
26
  module WebSocket
@@ -33,37 +32,21 @@ module Async
33
32
  request = env['async.http.request'] and Array(request.protocol).include?(PROTOCOL)
34
33
  end
35
34
 
36
- def self.open(env, headers: [], protocols: [], handler: Connection, **options, &block)
37
- if request = env['async.http.request'] and Array(request.protocol).include?(PROTOCOL)
35
+ def self.open(env, **options, &block)
36
+ if request = env['async.http.request']
38
37
  env = nil
39
38
 
40
- # Select websocket sub-protocol:
41
- if requested_protocol = request.headers[SEC_WEBSOCKET_PROTOCOL]
42
- protocol = (requested_protocol & protocols).first
43
- end
44
-
45
- response = Response.for(request, headers, protocol: protocol, **options) do |stream|
46
- response = nil
47
-
48
- framer = Protocol::WebSocket::Framer.new(stream)
49
-
50
- connection = handler.call(framer, protocol)
39
+ if response = HTTP.open(request, **options, &block)
40
+ headers = response.headers
51
41
 
52
- yield connection
42
+ if protocol = response.protocol
43
+ headers = Protocol::HTTP::Headers::Merged.new(headers, [
44
+ ['rack.protocol', protocol]
45
+ ])
46
+ end
53
47
 
54
- connection.close unless connection.closed?
48
+ return [response.status, headers.to_h, response.body]
55
49
  end
56
-
57
- request = nil
58
- headers = response.headers
59
-
60
- if protocol = response.protocol
61
- headers = Protocol::HTTP::Headers::Merged.new(headers, [
62
- ['rack.protocol', protocol]
63
- ])
64
- end
65
-
66
- return [response.status, headers, response.body]
67
50
  end
68
51
  end
69
52
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literals: true
2
+ #
1
3
  # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -18,26 +20,25 @@
18
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
21
  # THE SOFTWARE.
20
22
 
21
- require 'async/websocket/adapters/rack'
23
+ require_relative 'rack'
22
24
 
23
- class Upgrade
24
- def initialize(app)
25
- @app = app
26
- end
27
-
28
- def call(env)
29
- Async::WebSocket::Adapters::Rack.open(env, supported_protocols: ['ws']) do |connection|
30
- read, write = IO.pipe
31
-
32
- Process.spawn("ls -lah", :out => write)
33
- write.close
34
-
35
- read.each_line do |line|
36
- connection.write({line: line})
25
+ module Async
26
+ module WebSocket
27
+ module Adapters
28
+ module Rails
29
+ def self.open(request, **options, &block)
30
+ if response = Rack.open(request.env, **options, &block)
31
+ response[1]['rack.hijack'] = lambda do |stream|
32
+ response[2].call(stream)
33
+ end
34
+
35
+ # Close the response to prevent Rails from... trying to render a view?
36
+ return ::ActionDispatch::Response.new(response[0], response[1], nil).tap(&:close)
37
+ end
38
+
39
+ return ::ActionDispatch::Response.new(404, [], [])
40
+ end
37
41
  end
38
-
39
- # Gracefully close the connection:
40
- connection.close
41
- end or @app.call(env)
42
+ end
42
43
  end
43
44
  end
@@ -35,8 +35,8 @@ module Async
35
35
  include ::Protocol::WebSocket::Headers
36
36
 
37
37
  # @return [Client] a client which can be used to establish websocket connections to the given endpoint.
38
- def self.open(endpoint, *args, &block)
39
- client = self.new(HTTP::Client.new(endpoint, *args), mask: endpoint.secure?)
38
+ def self.open(endpoint, **options, &block)
39
+ client = self.new(HTTP::Client.new(endpoint, **options), mask: true)
40
40
 
41
41
  return client unless block_given?
42
42
 
@@ -49,16 +49,16 @@ module Async
49
49
 
50
50
  # @return [Connection] an open websocket connection to the given endpoint.
51
51
  def self.connect(endpoint, *args, **options, &block)
52
- self.open(endpoint, *args) do |client|
53
- connection = client.connect(endpoint.path, **options)
52
+ client = self.open(endpoint, *args)
53
+ connection = client.connect(endpoint.authority, endpoint.path, **options)
54
54
 
55
- return connection unless block_given?
55
+ return connection unless block_given?
56
56
 
57
- begin
58
- yield connection
59
- ensure
60
- connection.close
61
- end
57
+ begin
58
+ yield connection
59
+ ensure
60
+ connection.close
61
+ client.close
62
62
  end
63
63
  end
64
64
 
@@ -86,9 +86,9 @@ module Async
86
86
  end
87
87
  end
88
88
 
89
- def connect(path, headers: nil, handler: Connection, **options, &block)
89
+ def connect(authority, path, headers: nil, handler: Connection, **options, &block)
90
90
  headers = ::Protocol::HTTP::Headers[headers]
91
- request = Request.new(nil, nil, path, headers, **options)
91
+ request = Request.new(nil, authority, path, headers, **options)
92
92
 
93
93
  pool = @delegate.pool
94
94
  connection = pool.acquire
@@ -100,9 +100,15 @@ module Async
100
100
  end
101
101
 
102
102
  protocol = response.headers[SEC_WEBSOCKET_PROTOCOL]&.first
103
- framer = Framer.new(pool, connection, response.stream)
103
+ stream = response.stream
104
+ response = nil
105
+
106
+ framer = Framer.new(pool, connection, stream)
107
+ connection = nil
104
108
 
105
109
  handler.call(framer, protocol, **@options, &block)
110
+ ensure
111
+ pool.release(connection) if connection
106
112
  end
107
113
  end
108
114
  end
@@ -72,6 +72,10 @@ module Async
72
72
  @stream = nil
73
73
  end
74
74
 
75
+ def stream?
76
+ true
77
+ end
78
+
75
79
  attr :stream
76
80
 
77
81
  def call(stream)
@@ -45,6 +45,7 @@ module Async
45
45
 
46
46
  def initialize(framer, protocol = nil, response: nil, **options)
47
47
  super(framer, **options)
48
+
48
49
  @protocol = protocol
49
50
  @response = response
50
51
  end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module WebSocket
23
- VERSION = "0.14.0"
23
+ VERSION = "0.19.0"
24
24
  end
25
25
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-websocket
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-08 00:00:00.000000000 Z
11
+ date: 2021-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: async-io
14
+ name: async-http
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.23'
19
+ version: '0.54'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.23'
26
+ version: '0.54'
27
27
  - !ruby/object:Gem::Dependency
28
- name: async-http
28
+ name: async-io
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.51'
33
+ version: '1.23'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.51'
40
+ version: '1.23'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: protocol-websocket
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -67,21 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: falcon
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '0.34'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '0.34'
83
- - !ruby/object:Gem::Dependency
84
- name: covered
70
+ name: bundler
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
73
  - - ">="
@@ -95,7 +81,7 @@ dependencies:
95
81
  - !ruby/object:Gem::Version
96
82
  version: '0'
97
83
  - !ruby/object:Gem::Dependency
98
- name: bundler
84
+ name: covered
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
87
  - - ">="
@@ -109,21 +95,21 @@ dependencies:
109
95
  - !ruby/object:Gem::Version
110
96
  version: '0'
111
97
  - !ruby/object:Gem::Dependency
112
- name: rspec
98
+ name: falcon
113
99
  requirement: !ruby/object:Gem::Requirement
114
100
  requirements:
115
101
  - - "~>"
116
102
  - !ruby/object:Gem::Version
117
- version: '3.6'
103
+ version: '0.34'
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
107
  requirements:
122
108
  - - "~>"
123
109
  - !ruby/object:Gem::Version
124
- version: '3.6'
110
+ version: '0.34'
125
111
  - !ruby/object:Gem::Dependency
126
- name: bake-bundler
112
+ name: rack-test
127
113
  requirement: !ruby/object:Gem::Requirement
128
114
  requirements:
129
115
  - - ">="
@@ -136,64 +122,30 @@ dependencies:
136
122
  - - ">="
137
123
  - !ruby/object:Gem::Version
138
124
  version: '0'
139
- description:
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.6'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.6'
139
+ description:
140
140
  email:
141
- - samuel.williams@oriontransfer.co.nz
142
141
  executables: []
143
142
  extensions: []
144
143
  extra_rdoc_files: []
145
144
  files:
146
- - ".editorconfig"
147
- - ".gitignore"
148
- - ".rspec"
149
- - ".travis.yml"
150
- - Gemfile
151
- - README.md
152
- - async-websocket.gemspec
153
- - examples/chat/.env
154
- - examples/chat/README.md
155
- - examples/chat/client.rb
156
- - examples/chat/config.ru
157
- - examples/chat/multi-client.rb
158
- - examples/mud/client.rb
159
- - examples/mud/config.ru
160
- - examples/rack/client.rb
161
- - examples/rack/config.ru
162
- - examples/utopia/.bowerrc
163
- - examples/utopia/.gitignore
164
- - examples/utopia/.rspec
165
- - examples/utopia/Gemfile
166
- - examples/utopia/Guardfile
167
- - examples/utopia/README.md
168
- - examples/utopia/Rakefile
169
- - examples/utopia/config.ru
170
- - examples/utopia/config/README.md
171
- - examples/utopia/config/environment.rb
172
- - examples/utopia/lib/readme.txt
173
- - examples/utopia/pages/_heading.xnode
174
- - examples/utopia/pages/_page.xnode
175
- - examples/utopia/pages/client/client.js
176
- - examples/utopia/pages/client/index.xnode
177
- - examples/utopia/pages/errors/exception.xnode
178
- - examples/utopia/pages/errors/file-not-found.xnode
179
- - examples/utopia/pages/links.yaml
180
- - examples/utopia/pages/server/controller.rb
181
- - examples/utopia/public/_static/icon.png
182
- - examples/utopia/public/_static/site.css
183
- - examples/utopia/public/_static/utopia-background.svg
184
- - examples/utopia/public/_static/utopia.svg
185
- - examples/utopia/public/readme.txt
186
- - examples/utopia/spec/spec_helper.rb
187
- - examples/utopia/spec/website_context.rb
188
- - examples/utopia/spec/website_spec.rb
189
- - examples/utopia/tasks/bower.rake
190
- - examples/utopia/tasks/deploy.rake
191
- - examples/utopia/tasks/development.rake
192
- - examples/utopia/tasks/environment.rake
193
- - examples/utopia/tasks/log.rake
194
- - examples/utopia/tasks/static.rake
195
145
  - lib/async/websocket.rb
146
+ - lib/async/websocket/adapters/http.rb
196
147
  - lib/async/websocket/adapters/rack.rb
148
+ - lib/async/websocket/adapters/rails.rb
197
149
  - lib/async/websocket/client.rb
198
150
  - lib/async/websocket/connect_request.rb
199
151
  - lib/async/websocket/connect_response.rb
@@ -206,19 +158,11 @@ files:
206
158
  - lib/async/websocket/upgrade_request.rb
207
159
  - lib/async/websocket/upgrade_response.rb
208
160
  - lib/async/websocket/version.rb
209
- - spec/async/websocket/adapters/rack/client.rb
210
- - spec/async/websocket/adapters/rack/config.ru
211
- - spec/async/websocket/adapters/rack_spec.rb
212
- - spec/async/websocket/connection_spec.rb
213
- - spec/async/websocket/server_examples.rb
214
- - spec/async/websocket/server_spec.rb
215
- - spec/async/websocket/upgrade.rb
216
- - spec/spec_helper.rb
217
- homepage: ''
161
+ homepage: https://github.com/socketry/async-websocket
218
162
  licenses:
219
163
  - MIT
220
164
  metadata: {}
221
- post_install_message:
165
+ post_install_message:
222
166
  rdoc_options: []
223
167
  require_paths:
224
168
  - lib
@@ -233,16 +177,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
233
177
  - !ruby/object:Gem::Version
234
178
  version: '0'
235
179
  requirements: []
236
- rubygems_version: 3.1.2
237
- signing_key:
180
+ rubygems_version: 3.3.0.dev
181
+ signing_key:
238
182
  specification_version: 4
239
183
  summary: An async websocket library on top of websocket-driver.
240
- test_files:
241
- - spec/async/websocket/adapters/rack/client.rb
242
- - spec/async/websocket/adapters/rack/config.ru
243
- - spec/async/websocket/adapters/rack_spec.rb
244
- - spec/async/websocket/connection_spec.rb
245
- - spec/async/websocket/server_examples.rb
246
- - spec/async/websocket/server_spec.rb
247
- - spec/async/websocket/upgrade.rb
248
- - spec/spec_helper.rb
184
+ test_files: []