elasticsearch-transport 7.5.0 → 7.17.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +30 -13
  3. data/Gemfile-faraday1.gemfile +47 -0
  4. data/README.md +159 -64
  5. data/Rakefile +63 -13
  6. data/elasticsearch-transport.gemspec +55 -63
  7. data/lib/elasticsearch/transport/client.rb +183 -58
  8. data/lib/elasticsearch/transport/meta_header.rb +135 -0
  9. data/lib/elasticsearch/transport/redacted.rb +16 -3
  10. data/lib/elasticsearch/transport/transport/base.rb +69 -30
  11. data/lib/elasticsearch/transport/transport/connections/collection.rb +18 -8
  12. data/lib/elasticsearch/transport/transport/connections/connection.rb +25 -9
  13. data/lib/elasticsearch/transport/transport/connections/selector.rb +16 -3
  14. data/lib/elasticsearch/transport/transport/errors.rb +17 -3
  15. data/lib/elasticsearch/transport/transport/http/curb.rb +60 -35
  16. data/lib/elasticsearch/transport/transport/http/faraday.rb +32 -9
  17. data/lib/elasticsearch/transport/transport/http/manticore.rb +57 -32
  18. data/lib/elasticsearch/transport/transport/loggable.rb +16 -3
  19. data/lib/elasticsearch/transport/transport/response.rb +17 -5
  20. data/lib/elasticsearch/transport/transport/serializer/multi_json.rb +16 -3
  21. data/lib/elasticsearch/transport/transport/sniffer.rb +35 -15
  22. data/lib/elasticsearch/transport/version.rb +17 -4
  23. data/lib/elasticsearch/transport.rb +35 -33
  24. data/lib/elasticsearch-transport.rb +16 -3
  25. data/spec/elasticsearch/connections/collection_spec.rb +28 -3
  26. data/spec/elasticsearch/connections/selector_spec.rb +16 -3
  27. data/spec/elasticsearch/transport/base_spec.rb +106 -43
  28. data/spec/elasticsearch/transport/client_spec.rb +734 -164
  29. data/spec/elasticsearch/transport/http/curb_spec.rb +126 -0
  30. data/spec/elasticsearch/transport/http/faraday_spec.rb +141 -0
  31. data/spec/elasticsearch/transport/http/manticore_spec.rb +161 -0
  32. data/spec/elasticsearch/transport/meta_header_spec.rb +301 -0
  33. data/spec/elasticsearch/transport/sniffer_spec.rb +16 -16
  34. data/spec/spec_helper.rb +32 -6
  35. data/test/integration/jruby_test.rb +43 -0
  36. data/test/integration/transport_test.rb +109 -46
  37. data/test/profile/client_benchmark_test.rb +16 -3
  38. data/test/test_helper.rb +26 -25
  39. data/test/unit/adapters_test.rb +88 -0
  40. data/test/unit/connection_test.rb +23 -5
  41. data/test/unit/response_test.rb +18 -5
  42. data/test/unit/serializer_test.rb +16 -3
  43. data/test/unit/transport_base_test.rb +33 -11
  44. data/test/unit/transport_curb_test.rb +16 -4
  45. data/test/unit/transport_faraday_test.rb +18 -5
  46. data/test/unit/transport_manticore_test.rb +258 -158
  47. metadata +65 -89
@@ -1,6 +1,19 @@
1
- # Licensed to Elasticsearch B.V under one or more agreements.
2
- # Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3
- # See the LICENSE file in the project root for more information
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
4
17
 
5
18
  require 'manticore'
6
19
 
@@ -49,13 +62,20 @@ module Elasticsearch
49
62
  class Manticore
50
63
  include Base
51
64
 
52
- def initialize(arguments={}, &block)
65
+ def initialize(arguments = {}, &block)
66
+ @request_options = {
67
+ headers: (
68
+ arguments.dig(:transport_options, :headers) ||
69
+ arguments.dig(:options, :transport_options, :headers) ||
70
+ {}
71
+ )
72
+ }
53
73
  @manticore = build_client(arguments[:options] || {})
54
74
  super(arguments, &block)
55
75
  end
56
76
 
57
77
  # Should just be run once at startup
58
- def build_client(options={})
78
+ def build_client(options = {})
59
79
  client_options = options[:transport_options] || {}
60
80
  client_options[:ssl] = options[:ssl] || {}
61
81
 
@@ -67,26 +87,28 @@ module Elasticsearch
67
87
  # @return [Response]
68
88
  # @see Transport::Base#perform_request
69
89
  #
70
- def perform_request(method, path, params={}, body=nil, headers=nil, opts={})
90
+ def perform_request(method, path, params = {}, body = nil, headers = nil, opts = {})
71
91
  super do |connection, url|
72
- params[:body] = __convert_to_json(body) if body
92
+ body = body ? __convert_to_json(body) : nil
93
+ body, headers = compress_request(body, parse_headers(headers))
94
+ params[:body] = body if body
73
95
  params[:headers] = headers if headers
74
- params = params.merge @request_options
96
+
75
97
  case method
76
- when "GET"
98
+ when 'GET'
77
99
  resp = connection.connection.get(url, params)
78
- when "HEAD"
100
+ when 'HEAD'
79
101
  resp = connection.connection.head(url, params)
80
- when "PUT"
102
+ when 'PUT'
81
103
  resp = connection.connection.put(url, params)
82
- when "POST"
104
+ when 'POST'
83
105
  resp = connection.connection.post(url, params)
84
- when "DELETE"
106
+ when 'DELETE'
85
107
  resp = connection.connection.delete(url, params)
86
108
  else
87
109
  raise ArgumentError.new "Method #{method} not supported"
88
110
  end
89
- Response.new resp.code, resp.read_body, resp.headers
111
+ Response.new(resp.code, resp.read_body, resp.headers)
90
112
  end
91
113
  end
92
114
 
@@ -96,24 +118,21 @@ module Elasticsearch
96
118
  # @return [Connections::Collection]
97
119
  #
98
120
  def __build_connections
99
- @request_options = {}
100
- apply_headers(@request_options, options[:transport_options])
101
- apply_headers(@request_options, options)
121
+ apply_headers(options)
102
122
 
103
- Connections::Collection.new \
104
- :connections => hosts.map { |host|
105
- host[:protocol] = host[:scheme] || DEFAULT_PROTOCOL
106
- host[:port] ||= DEFAULT_PORT
123
+ Connections::Collection.new(
124
+ connections: hosts.map do |host|
125
+ host[:protocol] = host[:scheme] || DEFAULT_PROTOCOL
126
+ host[:port] ||= DEFAULT_PORT
107
127
 
108
128
  host.delete(:user) # auth is not supported here.
109
129
  host.delete(:password) # use the headers
110
130
 
111
- Connections::Connection.new \
112
- :host => host,
113
- :connection => @manticore
114
- },
115
- :selector_class => options[:selector_class],
116
- :selector => options[:selector]
131
+ Connections::Connection.new(host: host, connection: @manticore)
132
+ end,
133
+ selector_class: options[:selector_class],
134
+ selector: options[:selector]
135
+ )
117
136
  end
118
137
 
119
138
  # Closes all connections by marking them as dead
@@ -141,16 +160,22 @@ module Elasticsearch
141
160
 
142
161
  private
143
162
 
144
- def apply_headers(request_options, options)
145
- headers = (options && options[:headers]) || {}
163
+ def parse_headers(headers)
164
+ request_headers = @request_options.fetch(:headers, {})
165
+ headers = request_headers.merge(headers || {})
166
+ headers.empty? ? nil : headers
167
+ end
168
+
169
+ def apply_headers(options)
170
+ headers = options[:headers].clone || options.dig(:transport_options, :headers).clone || {}
146
171
  headers[CONTENT_TYPE_STR] = find_value(headers, CONTENT_TYPE_REGEX) || DEFAULT_CONTENT_TYPE
147
- headers[USER_AGENT_STR] = find_value(headers, USER_AGENT_REGEX) || user_agent_header
172
+ headers[USER_AGENT_STR] = find_value(headers, USER_AGENT_REGEX) || find_value(@request_options[:headers], USER_AGENT_REGEX) || user_agent_header
148
173
  headers[ACCEPT_ENCODING] = GZIP if use_compression?
149
- request_options.merge!(headers: headers)
174
+ @request_options[:headers].merge!(headers)
150
175
  end
151
176
 
152
177
  def user_agent_header
153
- @user_agent ||= begin
178
+ @user_agent_header ||= begin
154
179
  meta = ["RUBY_VERSION: #{JRUBY_VERSION}"]
155
180
  if RbConfig::CONFIG && RbConfig::CONFIG['host_os']
156
181
  meta << "#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
@@ -1,6 +1,19 @@
1
- # Licensed to Elasticsearch B.V under one or more agreements.
2
- # Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3
- # See the LICENSE file in the project root for more information
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
4
17
 
5
18
  module Elasticsearch
6
19
 
@@ -1,11 +1,23 @@
1
- # Licensed to Elasticsearch B.V under one or more agreements.
2
- # Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3
- # See the LICENSE file in the project root for more information
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
4
17
 
5
18
  module Elasticsearch
6
19
  module Transport
7
20
  module Transport
8
-
9
21
  # Wraps the response from Elasticsearch.
10
22
  #
11
23
  class Response
@@ -16,7 +28,7 @@ module Elasticsearch
16
28
  # @param headers [Hash] Response headers
17
29
  def initialize(status, body, headers={})
18
30
  @status, @body, @headers = status, body, headers
19
- @body = body.force_encoding('UTF-8') if body.respond_to?(:force_encoding)
31
+ @body = body.force_encoding('UTF-8') if body.respond_to?(:force_encoding) && !body.frozen?
20
32
  end
21
33
  end
22
34
 
@@ -1,6 +1,19 @@
1
- # Licensed to Elasticsearch B.V under one or more agreements.
2
- # Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3
- # See the LICENSE file in the project root for more information
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
4
17
 
5
18
  module Elasticsearch
6
19
  module Transport
@@ -1,6 +1,19 @@
1
- # Licensed to Elasticsearch B.V under one or more agreements.
2
- # Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3
- # See the LICENSE file in the project root for more information
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
4
17
 
5
18
  module Elasticsearch
6
19
  module Transport
@@ -32,21 +45,21 @@ module Elasticsearch
32
45
  #
33
46
  def hosts
34
47
  Timeout::timeout(timeout, SnifferTimeoutError) do
35
- nodes = transport.perform_request('GET', '_nodes/http', {}, nil, nil,
36
- reload_on_failure: false).body
48
+ nodes = perform_sniff_request.body
37
49
 
38
50
  hosts = nodes['nodes'].map do |id, info|
39
- if info[PROTOCOL]
40
- host, port = parse_publish_address(info[PROTOCOL]['publish_address'])
51
+ next unless info[PROTOCOL]
52
+ host, port = parse_publish_address(info[PROTOCOL]['publish_address'])
41
53
 
42
- { :id => id,
43
- :name => info['name'],
44
- :version => info['version'],
45
- :host => host,
46
- :port => port,
47
- :roles => info['roles'],
48
- :attributes => info['attributes'] }
49
- end
54
+ {
55
+ id: id,
56
+ name: info['name'],
57
+ version: info['version'],
58
+ host: host,
59
+ port: port,
60
+ roles: info['roles'],
61
+ attributes: info['attributes']
62
+ }
50
63
  end.compact
51
64
 
52
65
  hosts.shuffle! if transport.options[:randomize_hosts]
@@ -56,6 +69,13 @@ module Elasticsearch
56
69
 
57
70
  private
58
71
 
72
+ def perform_sniff_request
73
+ transport.perform_request(
74
+ 'GET', '_nodes/http', {}, nil, nil,
75
+ reload_on_failure: false
76
+ )
77
+ end
78
+
59
79
  def parse_publish_address(publish_address)
60
80
  # publish_address is in the format hostname/ip:port
61
81
  if publish_address =~ /\//
@@ -1,9 +1,22 @@
1
- # Licensed to Elasticsearch B.V under one or more agreements.
2
- # Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3
- # See the LICENSE file in the project root for more information
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
4
17
 
5
18
  module Elasticsearch
6
19
  module Transport
7
- VERSION = "7.5.0"
20
+ VERSION = '7.17.10'.freeze
8
21
  end
9
22
  end
@@ -1,36 +1,38 @@
1
- # Licensed to Elasticsearch B.V under one or more agreements.
2
- # Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3
- # See the LICENSE file in the project root for more information
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
4
17
 
5
- require "uri"
6
- require "time"
7
- require "timeout"
8
- require "multi_json"
9
- require "faraday"
18
+ require 'uri'
19
+ require 'time'
20
+ require 'timeout'
21
+ require 'zlib'
22
+ require 'multi_json'
23
+ require 'faraday'
10
24
 
11
- require "elasticsearch/transport/transport/loggable"
12
- require "elasticsearch/transport/transport/serializer/multi_json"
13
- require "elasticsearch/transport/transport/sniffer"
14
- require "elasticsearch/transport/transport/response"
15
- require "elasticsearch/transport/transport/errors"
16
- require "elasticsearch/transport/transport/base"
17
- require "elasticsearch/transport/transport/connections/selector"
18
- require "elasticsearch/transport/transport/connections/connection"
19
- require "elasticsearch/transport/transport/connections/collection"
20
- require "elasticsearch/transport/transport/http/faraday"
21
- require "elasticsearch/transport/client"
22
- require "elasticsearch/transport/redacted"
25
+ require 'elasticsearch/transport/transport/loggable'
26
+ require 'elasticsearch/transport/transport/serializer/multi_json'
27
+ require 'elasticsearch/transport/transport/sniffer'
28
+ require 'elasticsearch/transport/transport/response'
29
+ require 'elasticsearch/transport/transport/errors'
30
+ require 'elasticsearch/transport/transport/base'
31
+ require 'elasticsearch/transport/transport/connections/selector'
32
+ require 'elasticsearch/transport/transport/connections/connection'
33
+ require 'elasticsearch/transport/transport/connections/collection'
34
+ require 'elasticsearch/transport/transport/http/faraday'
35
+ require 'elasticsearch/transport/client'
36
+ require 'elasticsearch/transport/redacted'
23
37
 
24
- require "elasticsearch/transport/version"
25
-
26
- module Elasticsearch
27
- module Client
28
-
29
- # A convenience wrapper for {::Elasticsearch::Transport::Client#initialize}.
30
- #
31
- def new(arguments={}, &block)
32
- Elasticsearch::Transport::Client.new(arguments, &block)
33
- end
34
- extend self
35
- end
36
- end
38
+ require 'elasticsearch/transport/version'
@@ -1,5 +1,18 @@
1
- # Licensed to Elasticsearch B.V under one or more agreements.
2
- # Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3
- # See the LICENSE file in the project root for more information
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
4
17
 
5
18
  require 'elasticsearch/transport'
@@ -1,6 +1,19 @@
1
- # Licensed to Elasticsearch B.V under one or more agreements.
2
- # Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3
- # See the LICENSE file in the project root for more information
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
4
17
 
5
18
  require 'spec_helper'
6
19
 
@@ -236,6 +249,18 @@ describe Elasticsearch::Transport::Transport::Connections::Collection do
236
249
  collection.get_connection.host[:host]
237
250
  end).to eq((0..9).to_a)
238
251
  end
252
+
253
+ it 'always returns a connection' do
254
+ threads = 20.times.map do
255
+ Thread.new do
256
+ 20.times.map do
257
+ collection.get_connection.dead!
258
+ end
259
+ end
260
+ end
261
+
262
+ expect(threads.flat_map(&:value).size).to eq(400)
263
+ end
239
264
  end
240
265
  end
241
266
  end
@@ -1,6 +1,19 @@
1
- # Licensed to Elasticsearch B.V under one or more agreements.
2
- # Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3
- # See the LICENSE file in the project root for more information
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
4
17
 
5
18
  require 'spec_helper'
6
19