rack-on-lambda 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0f7ad6d89db4356964720a3d68ffa9a1c653e98dacbc75fa92acd574207447c9
4
+ data.tar.gz: 4ee99667a87bdbd3be31f7e9e7d5834c7395c738d50e55b41a2d9913b48a46dc
5
+ SHA512:
6
+ metadata.gz: cf68a5117d15b831fb46d3b0cb6972f53eed0d8dd809b65021595182613517b31d55cbe048351ca135c02644977bcde09fbe785f3e70d8aa41474e8d5b468064
7
+ data.tar.gz: 8a6e50798ec86cd68361c41e020bf519ec745e47868d38f3b69525392a2a1ba10737f84e71f22de2d9904be76ecd8b3e0a868726d1e2e946c962d3b4c9a2aadb
@@ -0,0 +1,96 @@
1
+ name: Push Gem
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ rspec-ruby25:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v1
13
+ - name: Set up Ruby 2.5
14
+ uses: actions/setup-ruby@v1
15
+ with:
16
+ ruby-version: 2.5.x
17
+ - name: Run RSpec
18
+ run: |
19
+ gem install bundler
20
+ bundle install --jobs 4 --retry 3
21
+ bundle exec rspec
22
+
23
+ rubocop-ruby25:
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v1
27
+ - name: Set up Ruby 2.5
28
+ uses: actions/setup-ruby@v1
29
+ with:
30
+ ruby-version: 2.5.x
31
+ - name: Run rubocop
32
+ run: |
33
+ gem install bundler
34
+ bundle install --jobs 4 --retry 3
35
+ bundle exec rubocop
36
+
37
+ rspec-ruby27:
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@v1
41
+ - name: Set up Ruby 2.7
42
+ uses: actions/setup-ruby@v1
43
+ with:
44
+ ruby-version: 2.7.x
45
+ - name: Run RSpec
46
+ run: |
47
+ gem install bundler
48
+ bundle install --jobs 4 --retry 3
49
+ bundle exec rspec
50
+
51
+ rubocop-ruby27:
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - uses: actions/checkout@v1
55
+ - name: Set up Ruby 2.7
56
+ uses: actions/setup-ruby@v1
57
+ with:
58
+ ruby-version: 2.7.x
59
+ - name: Run rubocop
60
+ run: |
61
+ gem install bundler
62
+ bundle install --jobs 4 --retry 3
63
+ bundle exec rubocop
64
+
65
+ push-gem:
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - uses: actions/checkout@master
69
+ - name: Set up Ruby 2.5
70
+ uses: actions/setup-ruby@v1
71
+ with:
72
+ ruby-version: 2.5.x
73
+ - name: Build gem
74
+ run: |
75
+ gem install bundler
76
+ bundle install --jobs 4 --retry 3
77
+ bundle exec rake build
78
+ - name: Push to GitHub Package Repository
79
+ run: |
80
+ mkdir -p $HOME/.gem
81
+ echo ":github: Bearer $GEM_HOST_API_KEY" >> $HOME/.gem/credentials
82
+ chmod 0600 $HOME/.gem/credentials
83
+ bundle exec rake build
84
+ gem push --key github --host https://rubygems.pkg.github.com/Skudo pkg/`ls -1t pkg | head -1`
85
+ env:
86
+ GEM_HOST_API_KEY: ${{secrets.GH_AUTH_TOKEN}}
87
+ OWNER: username
88
+ - name: Push to RubyGems
89
+ run: |
90
+ mkdir -p $HOME/.gem
91
+ echo ":rubygems_api_key: $GEM_HOST_API_KEY" >> ~/.gem/credentials
92
+ chmod 0600 $HOME/.gem/credentials
93
+ bundle exec rake build
94
+ gem push pkg/`ls -1t pkg | head -1`
95
+ env:
96
+ GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
@@ -0,0 +1,60 @@
1
+ name: Tests
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ rspec-ruby25:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v1
10
+ - name: Set up Ruby 2.5
11
+ uses: actions/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.5.x
14
+ - name: Run RSpec
15
+ run: |
16
+ gem install bundler
17
+ bundle install --jobs 4 --retry 3
18
+ bundle exec rspec
19
+
20
+ rubocop-ruby25:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v1
24
+ - name: Set up Ruby 2.5
25
+ uses: actions/setup-ruby@v1
26
+ with:
27
+ ruby-version: 2.5.x
28
+ - name: Run rubocop
29
+ run: |
30
+ gem install bundler
31
+ bundle install --jobs 4 --retry 3
32
+ bundle exec rubocop
33
+
34
+ rspec-ruby27:
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v1
38
+ - name: Set up Ruby 2.7
39
+ uses: actions/setup-ruby@v1
40
+ with:
41
+ ruby-version: 2.7.x
42
+ - name: Run RSpec
43
+ run: |
44
+ gem install bundler
45
+ bundle install --jobs 4 --retry 3
46
+ bundle exec rspec
47
+
48
+ rubocop-ruby27:
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - uses: actions/checkout@v1
52
+ - name: Set up Ruby 2.7
53
+ uses: actions/setup-ruby@v1
54
+ with:
55
+ ruby-version: 2.7.x
56
+ - name: Run rubocop
57
+ run: |
58
+ gem install bundler
59
+ bundle install --jobs 4 --retry 3
60
+ bundle exec rubocop
@@ -0,0 +1,5 @@
1
+ .bundle
2
+ coverage
3
+
4
+ .rspec_status
5
+ gems.locked
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,21 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 2.5
4
+
5
+ Metrics/BlockLength:
6
+ Exclude:
7
+ - 'spec/**/*_spec.rb'
8
+ - 'spec/factories/*.rb'
9
+ - 'spec/shared_examples/**/*.rb'
10
+
11
+ Layout/LineLength:
12
+ Max: 80
13
+
14
+ Naming/FileName:
15
+ Exclude:
16
+ - lib/rack-on-lambda.rb
17
+ - spec/rack-on-lambda_spec.rb
18
+
19
+ # TODO: Add documentation and remove the Style/Documentation exception.
20
+ Style/Documentation:
21
+ Enabled: false
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,6 @@
1
+ ## Changelog
2
+
3
+ #### Unreleased
4
+
5
+ #### 1.0.0
6
+ - Initial release.
data/gems.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack_on_lambda'
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ require 'active_support/hash_with_indifferent_access'
6
+ require 'rack/body_proxy'
7
+ require 'rack/utils'
8
+ require 'rack/version'
9
+
10
+ require 'pry' if Gem.loaded_specs.key?('pry')
11
+
12
+ require_relative 'rack_on_lambda/version'
13
+
14
+ require_relative 'rack_on_lambda/response'
15
+ require_relative 'rack_on_lambda/query'
16
+ require_relative 'rack_on_lambda/adapters/rest_api'
17
+ require_relative 'rack_on_lambda/handlers/rest_api'
18
+
19
+ module RackOnLambda; end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RackOnLambda
4
+ module Adapters
5
+ class RestApi
6
+ attr_reader :context, :event
7
+
8
+ def initialize(context:, event:)
9
+ @context = context
10
+ @event = event.deep_stringify_keys
11
+ end
12
+
13
+ def env
14
+ @env ||= default_env.merge(env_request_metadata)
15
+ .merge(env_headers)
16
+ .merge(env_params)
17
+ .merge(env_body)
18
+ end
19
+
20
+ def response(status, headers, body)
21
+ Response.new(status, headers, body).as_json
22
+ end
23
+
24
+ private
25
+
26
+ def default_env
27
+ {
28
+ 'SCRIPT_NAME' => '',
29
+ 'rack.version' => Rack::VERSION,
30
+ 'rack.errors' => $stderr,
31
+ 'rack.multithread' => true,
32
+ 'rack.multiprocess' => true,
33
+ 'rack.run_once' => false
34
+ }
35
+ end
36
+
37
+ def env_body
38
+ result = event['body'] || ''
39
+ result = Base64.decode64(result) if event['isBase64Encoded']
40
+
41
+ {
42
+ 'rack.input' => StringIO.new(result).set_encoding(Encoding::BINARY),
43
+ 'CONTENT_LENGTH' => result.bytesize.to_s
44
+ }
45
+ end
46
+
47
+ def env_headers
48
+ result = {}
49
+ http_headers.each_pair do |header, value|
50
+ key = key_for_header(header)
51
+ result[key] = value.to_s
52
+ end
53
+ result
54
+ end
55
+
56
+ def env_params
57
+ {
58
+ 'QUERY_STRING' => query_string
59
+ }
60
+ end
61
+
62
+ def env_request_metadata
63
+ {
64
+ 'REQUEST_METHOD' => event['httpMethod'],
65
+ 'PATH_INFO' => path_info,
66
+ 'SERVER_NAME' => server_name,
67
+ 'SERVER_PORT' => server_port.to_s,
68
+ 'rack.url_scheme' => url_scheme
69
+ }
70
+ end
71
+
72
+ def http_headers
73
+ event['headers'] || {}
74
+ end
75
+
76
+ def key_for_header(header)
77
+ key = header.upcase.tr('-', '_')
78
+ case key
79
+ when 'CONTENT_LENGTH', 'CONTENT_TYPE' then key
80
+ else "HTTP_#{key}"
81
+ end
82
+ end
83
+
84
+ def path_info
85
+ event['path'] || ''
86
+ end
87
+
88
+ def query_string
89
+ return @query_string if defined?(@query_string)
90
+
91
+ query = Query.new
92
+ event['multiValueQueryStringParameters']&.each_pair do |key, value|
93
+ query.add(key, value)
94
+ end
95
+ @query_string = query.to_s
96
+ end
97
+
98
+ def server_name
99
+ http_headers['Host'] || 'localhost'
100
+ end
101
+
102
+ def server_port
103
+ http_headers['X-Forwarded-Port'] || 443
104
+ end
105
+
106
+ def url_scheme
107
+ http_headers['CloudFront-Forwarded-Proto'] ||
108
+ http_headers['X-Forwarded-Proto'] ||
109
+ 'https'
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RackOnLambda
4
+ module Handlers
5
+ class RestApi
6
+ def self.call(event:, context:, app:)
7
+ adapter = Adapters::RestApi.new(event: event, context: context)
8
+ status, headers, body = app.call(adapter.env)
9
+ adapter.response(status, headers, body)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RackOnLambda
4
+ # The `multiValueQueryStringParameters` object from the API Gateway event
5
+ # keeps _all_ values in an array, regardless of the actual size of the array
6
+ # and regardless of the "intent" of the query string parameter.
7
+ #
8
+ # In order to normalise this behaviour, we treat the query strings
9
+ #
10
+ # `key=value1&key=value2`
11
+ #
12
+ # and
13
+ #
14
+ # `key[]=value1&key[]=value2`
15
+ #
16
+ # the same. Both are to be serialised to the query string
17
+ #
18
+ # `key[]=value1&key[]=value2`
19
+ #
20
+ # However, the query strings
21
+ #
22
+ # `key=value`
23
+ #
24
+ # and
25
+ #
26
+ # `key[]=value`
27
+ #
28
+ # are _not_ to be treated the same.
29
+ class Query
30
+ def initialize(data = {})
31
+ @params = data.with_indifferent_access
32
+ end
33
+
34
+ def add(key, values)
35
+ if key.to_s.end_with?('[]')
36
+ actual_key = key[0..-3]
37
+ add_list(actual_key, values)
38
+ else
39
+ values.each { |value| add_item(key, value) }
40
+ end
41
+ end
42
+
43
+ def to_h
44
+ @params.dup
45
+ end
46
+
47
+ def to_s
48
+ Rack::Utils.build_nested_query(to_h)
49
+ end
50
+
51
+ private
52
+
53
+ def add_item(key, value)
54
+ if @params[key].nil?
55
+ @params[key] = value
56
+ else
57
+ @params[key] = Array(@params[key])
58
+ @params[key] << value
59
+ end
60
+ end
61
+
62
+ def add_list(key, value)
63
+ @params[key] ||= []
64
+ @params[key].concat(value)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RackOnLambda
4
+ class Response
5
+ def initialize(status, headers, body)
6
+ @status = status
7
+ @headers = headers
8
+ @body = stringify_body(body)
9
+ end
10
+
11
+ def as_json(_options = {})
12
+ {
13
+ 'statusCode' => @status,
14
+ 'headers' => @headers,
15
+ 'isBase64Encoded' => @base64_encoded,
16
+ 'body' => @body
17
+ }
18
+ end
19
+
20
+ private
21
+
22
+ def stringify_body(body)
23
+ result = ''
24
+ body.each { |chunk| result += chunk.to_s }
25
+
26
+ if result.ascii_only?
27
+ @base64_encoded = false
28
+ result
29
+ else
30
+ @base64_encoded = true
31
+ Base64.strict_encode64(result)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RackOnLambda
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'rack_on_lambda/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'rack-on-lambda'
9
+ spec.version = RackOnLambda::VERSION
10
+ spec.authors = ['Huy Dinh']
11
+ spec.email = ['mail@huydinh.eu']
12
+
13
+ spec.summary = 'Write beautiful Ruby applications for AWS Lambda'
14
+ spec.description = 'Use your Rack application on AWS Lambda.'
15
+ spec.homepage = 'https://github.com/Skudo/rack-on-lambda'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec)/}) }
20
+ end
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.required_ruby_version = '>= 2.5'
24
+
25
+ spec.add_dependency 'activesupport', '~> 6.0.0'
26
+ spec.add_dependency 'rack', '>= 2.0.8', '< 3'
27
+
28
+ spec.add_development_dependency 'bundler'
29
+ spec.add_development_dependency 'factory_bot'
30
+ spec.add_development_dependency 'pry-byebug'
31
+ spec.add_development_dependency 'rake'
32
+ spec.add_development_dependency 'rspec'
33
+ spec.add_development_dependency 'rubocop'
34
+ spec.add_development_dependency 'simplecov'
35
+ spec.add_development_dependency 'yard'
36
+ end
metadata ADDED
@@ -0,0 +1,206 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-on-lambda
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Huy Dinh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-09-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 6.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.8
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '3'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.0.8
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '3'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: factory_bot
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: pry-byebug
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rake
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rspec
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: rubocop
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: simplecov
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ - !ruby/object:Gem::Dependency
146
+ name: yard
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ description: Use your Rack application on AWS Lambda.
160
+ email:
161
+ - mail@huydinh.eu
162
+ executables: []
163
+ extensions: []
164
+ extra_rdoc_files: []
165
+ files:
166
+ - ".github/workflows/push-gem.yml"
167
+ - ".github/workflows/tests.yml"
168
+ - ".gitignore"
169
+ - ".rspec"
170
+ - ".rubocop.yml"
171
+ - Rakefile
172
+ - changelog.md
173
+ - gems.rb
174
+ - lib/rack-on-lambda.rb
175
+ - lib/rack_on_lambda.rb
176
+ - lib/rack_on_lambda/adapters/rest_api.rb
177
+ - lib/rack_on_lambda/handlers/rest_api.rb
178
+ - lib/rack_on_lambda/query.rb
179
+ - lib/rack_on_lambda/response.rb
180
+ - lib/rack_on_lambda/version.rb
181
+ - rack-on-lambda.gemspec
182
+ homepage: https://github.com/Skudo/rack-on-lambda
183
+ licenses:
184
+ - MIT
185
+ metadata: {}
186
+ post_install_message:
187
+ rdoc_options: []
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '2.5'
195
+ required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ requirements: []
201
+ rubyforge_project:
202
+ rubygems_version: 2.7.6.2
203
+ signing_key:
204
+ specification_version: 4
205
+ summary: Write beautiful Ruby applications for AWS Lambda
206
+ test_files: []