wreq 1.2.10 → 1.2.12

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.
data/src/lib.rs CHANGED
@@ -15,6 +15,7 @@ mod http;
15
15
  mod options;
16
16
  mod rt;
17
17
  mod serde;
18
+ mod tls;
18
19
 
19
20
  use magnus::{Error, Module, Ruby, Value};
20
21
 
@@ -85,6 +86,7 @@ pub fn patch(ruby: &Ruby, args: &[Value]) -> Result<Response, magnus::Error> {
85
86
  fn init(ruby: &Ruby) -> Result<(), Error> {
86
87
  let gem_module = ruby.define_module(RUBY_MODULE_NAME)?;
87
88
  gem_module.const_set("VERSION", VERSION)?;
89
+ error::include(ruby, &gem_module)?;
88
90
  gem_module.define_module_function("request", magnus::function!(request, -1))?;
89
91
  gem_module.define_module_function("get", magnus::function!(get, -1))?;
90
92
  gem_module.define_module_function("post", magnus::function!(post, -1))?;
@@ -97,8 +99,10 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
97
99
  http::include(ruby, &gem_module)?;
98
100
  header::include(ruby, &gem_module)?;
99
101
  cookie::include(ruby, &gem_module)?;
102
+ tls::include(ruby, &gem_module)?;
100
103
  client::include(ruby, &gem_module)?;
101
104
  emulate::include(ruby, &gem_module)?;
102
- error::include(ruby, &gem_module)?;
105
+ #[cfg(unix)]
106
+ rt::initialize(ruby)?;
103
107
  Ok(())
104
108
  }
data/src/macros.rs CHANGED
@@ -148,11 +148,11 @@ macro_rules! define_ruby_enum {
148
148
  }
149
149
 
150
150
  macro_rules! extract_request {
151
- ($args:expr, $required:ty) => {{
151
+ ($ruby:expr, $args:expr, $required:ty) => {{
152
+ crate::rt::ensure_current($ruby)?;
152
153
  let args = magnus::scan_args::scan_args::<$required, (), (), (), magnus::RHash, ()>($args)?;
153
154
  let required = args.required;
154
- let ruby = magnus::Ruby::get_with(args.keywords);
155
- let request = crate::client::req::Request::new(&ruby, args.keywords)?;
155
+ let request = crate::client::req::Request::new($ruby, args.keywords)?;
156
156
  (required, request)
157
157
  }};
158
158
  }
data/src/rt.rs CHANGED
@@ -1,26 +1,57 @@
1
- use std::sync::LazyLock;
1
+ use std::{io, sync::OnceLock};
2
2
 
3
3
  use magnus::Ruby;
4
- use tokio::runtime::{Builder, Runtime};
4
+ use tokio::runtime::{Builder, Runtime as TokioRuntime};
5
5
 
6
6
  use crate::{
7
7
  error::{interrupt_error, runtime_initialization_error},
8
8
  gvl,
9
9
  };
10
10
 
11
- /// Initialize the global runtime lazily and preserve failures for Ruby.
12
- static RUNTIME: LazyLock<Result<Runtime, std::io::Error>> = LazyLock::new(|| {
13
- let mut builder = Builder::new_multi_thread();
11
+ #[cfg(unix)]
12
+ use crate::{
13
+ arch,
14
+ error::{fork_error, fork_handler_error},
15
+ };
14
16
 
15
- builder.enable_all().build()
16
- });
17
+ /// Initialize the global runtime lazily and preserve failures for Ruby.
18
+ static RUNTIME: OnceLock<Result<TokioRuntime, io::Error>> = OnceLock::new();
17
19
 
18
20
  enum BlockOnError<E> {
19
21
  Interrupted,
20
22
  Future(E),
21
23
  }
22
24
 
23
- /// Block on a future to completion on the global Tokio runtime.
25
+ /// Register fork tracking while the native extension is being loaded.
26
+ ///
27
+ /// # Errors
28
+ ///
29
+ /// Returns `Wreq::ForkError` if the platform cannot install its child-process
30
+ /// callback.
31
+ #[cfg(unix)]
32
+ pub fn initialize(ruby: &Ruby) -> Result<(), magnus::Error> {
33
+ arch::initialize_fork_tracking().map_err(|err| fork_handler_error(ruby, &err))
34
+ }
35
+
36
+ /// Reject a child process that inherited the loaded native extension.
37
+ ///
38
+ /// # Errors
39
+ ///
40
+ /// Returns `Wreq::ForkError` when the extension was loaded before the current
41
+ /// process was forked.
42
+ pub fn ensure_current(ruby: &Ruby) -> Result<(), magnus::Error> {
43
+ #[cfg(unix)]
44
+ if let Some((owner_pid, current_pid)) = arch::forked_process_ids() {
45
+ return Err(fork_error(ruby, owner_pid, current_pid));
46
+ }
47
+
48
+ #[cfg(not(unix))]
49
+ let _ = ruby;
50
+
51
+ Ok(())
52
+ }
53
+
54
+ /// Block on a future to completion on the current process's global Tokio runtime.
24
55
  ///
25
56
  /// The future runs without Ruby's GVL, so it must not construct Ruby objects or
26
57
  /// Ruby exceptions. Convert Rust errors back into Ruby errors after the GVL has
@@ -28,7 +59,8 @@ enum BlockOnError<E> {
28
59
  ///
29
60
  /// # Errors
30
61
  ///
31
- /// Returns `Wreq::BuilderError` if the Tokio runtime cannot be initialized,
62
+ /// Returns `Wreq::ForkError` if the extension belongs to a parent process,
63
+ /// `Wreq::BuilderError` if the Tokio runtime cannot be initialized,
32
64
  /// `Wreq::InterruptError` if Ruby interrupts the request, or the error produced
33
65
  /// by `map_err` if the future fails.
34
66
  pub fn try_block_on<F, T, E, M>(ruby: &Ruby, future: F, map_err: M) -> Result<T, magnus::Error>
@@ -36,7 +68,12 @@ where
36
68
  F: Future<Output = Result<T, E>>,
37
69
  M: FnOnce(&Ruby, E) -> magnus::Error,
38
70
  {
71
+ ensure_current(ruby)?;
39
72
  let runtime = RUNTIME
73
+ .get_or_init(|| {
74
+ let mut builder = Builder::new_multi_thread();
75
+ builder.enable_all().build()
76
+ })
40
77
  .as_ref()
41
78
  .map_err(|err| runtime_initialization_error(ruby, err))?;
42
79
  let result = gvl::nogvl_cancellable(|flag| {
data/src/tls.rs ADDED
@@ -0,0 +1,51 @@
1
+ //! Ruby wrappers for TLS metadata attached to a response.
2
+ //!
3
+ //! Certificates use the DER encoding described by the X.509 profile in
4
+ //! [RFC 5280 section 4.1](https://www.rfc-editor.org/rfc/rfc5280#section-4.1).
5
+
6
+ use magnus::{Error, Module, RArray, RModule, RString, Ruby, value::ReprValue};
7
+
8
+ /// Read-only Ruby wrapper around [`wreq::tls::TlsInfo`].
9
+ ///
10
+ /// The native value keeps certificate bytes alive independently of the response
11
+ /// body. Its `Bytes` buffers are cheap to clone, while accessors copy the data
12
+ /// into Ruby-owned Strings so callers cannot mutate the stored metadata.
13
+ #[derive(Clone)]
14
+ #[magnus::wrap(class = "Wreq::TlsInfo", free_immediately, size)]
15
+ pub(crate) struct TlsInfo(pub(crate) wreq::tls::TlsInfo);
16
+
17
+ impl TlsInfo {
18
+ /// Copy the DER-encoded leaf certificate into a binary Ruby String.
19
+ fn peer_certificate(ruby: &Ruby, rb_self: &Self) -> Option<RString> {
20
+ rb_self
21
+ .0
22
+ .peer_certificate()
23
+ .map(|der| ruby.str_from_slice(der))
24
+ }
25
+
26
+ /// Copy the certificate chain into a frozen Array of binary Ruby Strings.
27
+ ///
28
+ /// Only the Array is frozen. Its Strings are independent copies and remain
29
+ /// mutable in Ruby.
30
+ fn peer_certificate_chain(ruby: &Ruby, rb_self: &Self) -> Option<RArray> {
31
+ rb_self.0.peer_certificate_chain().map(|chain| {
32
+ let certificates = ruby.ary_from_iter(chain.map(|cert| ruby.str_from_slice(cert)));
33
+ certificates.freeze();
34
+ certificates
35
+ })
36
+ }
37
+ }
38
+
39
+ /// Define the `Wreq::TlsInfo` Ruby class and its readers.
40
+ pub(crate) fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
41
+ let tls_info_class = gem_module.define_class("TlsInfo", ruby.class_object())?;
42
+ tls_info_class.define_method(
43
+ "peer_certificate",
44
+ magnus::method!(TlsInfo::peer_certificate, 0),
45
+ )?;
46
+ tls_info_class.define_method(
47
+ "peer_certificate_chain",
48
+ magnus::method!(TlsInfo::peer_certificate_chain, 0),
49
+ )?;
50
+ Ok(())
51
+ }
data/test/fork_test.rb ADDED
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require "rbconfig"
5
+ require "tempfile"
6
+ require "timeout"
7
+
8
+ class ForkTest < Minitest::Test
9
+ FORK_ERROR_LABELS = %w[
10
+ before_runtime
11
+ invalid_client
12
+ invalid_request
13
+ fresh_body_sender
14
+ inherited_body_sender_push
15
+ inherited_body_sender_close
16
+ inherited_body_sender_closed
17
+ fresh_client
18
+ inherited_client
19
+ inherited_response
20
+ inherited_response_text
21
+ inherited_response_chunks
22
+ inherited_response_close
23
+ ].freeze
24
+
25
+ def test_fork_error_is_a_runtime_error
26
+ assert_operator Wreq::ForkError, :<, RuntimeError
27
+ end
28
+
29
+ def test_loaded_extension_is_rejected_after_fork
30
+ skip "fork is not supported on this platform" unless Process.respond_to?(:fork)
31
+
32
+ stdout, stderr, status = run_fork_script("fork_safety.rb")
33
+
34
+ assert status.success?, "subprocess failed with #{status.inspect}: #{stderr}"
35
+ assert_equal "ok\n", stdout
36
+ FORK_ERROR_LABELS.each do |label|
37
+ assert_match(/#{label}=Wreq::ForkError:.*cannot be used after fork/, stderr)
38
+ assert_match(/#{label}_retry=Wreq::ForkError:.*cannot be used after fork/, stderr)
39
+ end
40
+ assert_match(/inherited_gc=ok/, stderr)
41
+ refute_match(/\[BUG\]|segmentation fault|panicked/i, stderr)
42
+ end
43
+
44
+ private
45
+
46
+ def run_fork_script(name)
47
+ lib_dir = File.expand_path("../lib", __dir__)
48
+ script = File.expand_path("scripts/#{name}", __dir__)
49
+
50
+ Tempfile.create("wreq-fork-stdout") do |stdout|
51
+ Tempfile.create("wreq-fork-stderr") do |stderr|
52
+ pid = Process.spawn(
53
+ RbConfig.ruby,
54
+ "-I",
55
+ lib_dir,
56
+ script,
57
+ out: stdout,
58
+ err: stderr,
59
+ pgroup: true
60
+ )
61
+ status = Timeout.timeout(30) { Process.wait2(pid).last }
62
+ stdout.rewind
63
+ stderr.rewind
64
+ return [stdout.read, stderr.read, status]
65
+ rescue Timeout::Error
66
+ begin
67
+ Process.kill("KILL", -pid)
68
+ rescue Errno::ESRCH
69
+ nil
70
+ end
71
+
72
+ begin
73
+ Process.wait(pid)
74
+ rescue Errno::ECHILD
75
+ nil
76
+ end
77
+
78
+ flunk "#{name} timed out"
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "timeout"
5
+ require "weakref"
6
+ require "wreq"
7
+
8
+ $stdout.sync = true
9
+ $stderr.sync = true
10
+
11
+ def expect_fork_error(label)
12
+ child_pid = fork do
13
+ 2.times do |attempt|
14
+ attempt_label = attempt.zero? ? label : "#{label}_retry"
15
+
16
+ begin
17
+ Timeout.timeout(5) { yield }
18
+ rescue Wreq::ForkError => error
19
+ warn "#{attempt_label}=#{error.class}: #{error.message}"
20
+ next
21
+ rescue => error
22
+ warn "#{attempt_label}=unexpected #{error.class}: #{error.message}"
23
+ exit! 2
24
+ end
25
+
26
+ warn "#{attempt_label}=missing Wreq::ForkError"
27
+ exit! 3
28
+ end
29
+
30
+ exit! 0
31
+ end
32
+
33
+ _, status = Process.wait2(child_pid)
34
+ abort "#{label} child failed with #{status.inspect}" unless status.success?
35
+ end
36
+
37
+ expect_fork_error("before_runtime") { Wreq::Client.new }
38
+ expect_fork_error("invalid_client") { Wreq::Client.new(unknown: true) }
39
+ expect_fork_error("invalid_request") { Wreq.get(1) }
40
+ expect_fork_error("fresh_body_sender") { Wreq::BodySender.new(0) }
41
+
42
+ server = TCPServer.new("127.0.0.1", 0)
43
+ port = server.addr[1]
44
+ server_pid = fork do
45
+ 3.times do
46
+ ready = IO.select([server], nil, nil, 10)
47
+ exit! 4 unless ready
48
+
49
+ socket = server.accept
50
+ begin
51
+ while (line = socket.gets)
52
+ break if line == "\r\n"
53
+ end
54
+ socket.write("HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok")
55
+ ensure
56
+ socket.close
57
+ end
58
+ end
59
+ exit! 0
60
+ ensure
61
+ server.close
62
+ end
63
+ server.close
64
+
65
+ url = "http://127.0.0.1:#{port}/"
66
+ client = Wreq::Client.new
67
+ abort "parent warm-up failed" unless client.get(url).bytes == "ok"
68
+
69
+ def build_inherited_objects(client, url)
70
+ [Wreq::Client.new, Wreq::BodySender.new, client.get(url)]
71
+ end
72
+
73
+ inherited_objects = build_inherited_objects(client, url)
74
+ inherited_weak_refs = inherited_objects.map { |object| WeakRef.new(object) }
75
+
76
+ expect_fork_error("inherited_body_sender_push") do
77
+ inherited_objects[1].push("chunk")
78
+ end
79
+ expect_fork_error("inherited_body_sender_close") { inherited_objects[1].close }
80
+ expect_fork_error("inherited_body_sender_closed") { inherited_objects[1].closed? }
81
+ expect_fork_error("fresh_client") { Wreq::Client.new }
82
+ expect_fork_error("inherited_client") { client.get(url) }
83
+ expect_fork_error("inherited_response") { inherited_objects[2].bytes }
84
+ expect_fork_error("inherited_response_text") { inherited_objects[2].text(1) }
85
+ expect_fork_error("inherited_response_chunks") { inherited_objects[2].chunks }
86
+ expect_fork_error("inherited_response_close") { inherited_objects[2].close }
87
+
88
+ # Release the earlier test blocks so this array is the only strong reference.
89
+ GC.start(full_mark: true, immediate_sweep: true)
90
+ gc_pid = fork do
91
+ inherited_objects = nil
92
+ 3.times { GC.start(full_mark: true, immediate_sweep: true) }
93
+ alive = inherited_weak_refs.each_index.select do |index|
94
+ inherited_weak_refs[index].weakref_alive?
95
+ end
96
+ abort "inherited objects were not collected: #{alive.join(", ")}" unless alive.empty?
97
+ warn "inherited_gc=ok"
98
+ exit! 0
99
+ rescue => error
100
+ warn "inherited_gc=unexpected #{error.class}: #{error.message}"
101
+ exit! 5
102
+ end
103
+ _, gc_status = Process.wait2(gc_pid)
104
+ abort "inherited GC child failed with #{gc_status.inspect}" unless gc_status.success?
105
+
106
+ abort "parent request after fork failed" unless client.get(url).bytes == "ok"
107
+ _, server_status = Process.wait2(server_pid)
108
+ abort "server failed with #{server_status.inspect}" unless server_status.success?
109
+
110
+ puts "ok"
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "socket"
5
+ require "timeout"
6
+
7
+ # A small HTTPS server that serves every expected request on one TLS connection.
8
+ module TlsTestServer
9
+ RESPONSE_BODY = "ok"
10
+
11
+ module_function
12
+
13
+ def with_connection(request_count:)
14
+ tcp_server = TCPServer.new("127.0.0.1", 0)
15
+ context, certificate_der = server_context
16
+ ssl_server = OpenSSL::SSL::SSLServer.new(tcp_server, context)
17
+ outcome = Queue.new
18
+ server_thread = Thread.new do
19
+ socket = ssl_server.accept
20
+ request_lines = []
21
+
22
+ request_count.times do |index|
23
+ request_lines << read_request(socket)
24
+ connection = (index == request_count - 1) ? "close" : "keep-alive"
25
+ socket.write(response(connection))
26
+ socket.flush
27
+ end
28
+
29
+ outcome << {connections: 1, requests: request_lines}
30
+ rescue => error
31
+ outcome << error
32
+ ensure
33
+ socket&.close
34
+ end
35
+ server_thread.report_on_exception = false
36
+
37
+ yield "https://127.0.0.1:#{tcp_server.addr[1]}/", certificate_der
38
+
39
+ result = Timeout.timeout(5) { outcome.pop }
40
+ raise result if result.is_a?(StandardError)
41
+
42
+ result
43
+ ensure
44
+ tcp_server&.close
45
+ server_thread&.join(5)
46
+ if server_thread&.alive?
47
+ server_thread.kill
48
+ server_thread.join
49
+ end
50
+ end
51
+
52
+ def read_request(socket)
53
+ request_line = socket.gets
54
+ raise EOFError, "client closed before sending a request" unless request_line
55
+
56
+ loop do
57
+ line = socket.gets
58
+ raise EOFError, "client closed while sending headers" unless line
59
+ break if line == "\r\n"
60
+ end
61
+
62
+ request_line
63
+ end
64
+ private_class_method :read_request
65
+
66
+ def response(connection)
67
+ [
68
+ "HTTP/1.1 200 OK",
69
+ "Content-Length: #{RESPONSE_BODY.bytesize}",
70
+ "Connection: #{connection}",
71
+ "",
72
+ RESPONSE_BODY
73
+ ].join("\r\n")
74
+ end
75
+ private_class_method :response
76
+
77
+ def server_context
78
+ key = OpenSSL::PKey::RSA.new(2048)
79
+ certificate = OpenSSL::X509::Certificate.new
80
+ certificate.version = 2
81
+ certificate.serial = 1
82
+ certificate.subject = certificate.issuer = OpenSSL::X509::Name.parse("/CN=127.0.0.1")
83
+ certificate.public_key = key.public_key
84
+ certificate.not_before = Time.now - 60
85
+ certificate.not_after = Time.now + 3600
86
+ certificate.sign(key, OpenSSL::Digest.new("SHA256"))
87
+
88
+ context = OpenSSL::SSL::SSLContext.new.tap do |ssl_context|
89
+ ssl_context.cert = certificate
90
+ ssl_context.key = key
91
+ end
92
+ [context, certificate.to_der]
93
+ end
94
+ private_class_method :server_context
95
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require_relative "support/tls_server"
5
+
6
+ class TlsInfoTest < Minitest::Test
7
+ HTTPBIN_HTTP_URL = ENV.fetch("HTTPBIN_HTTP_URL", HTTPBIN_URL.sub(/\Ahttps:/, "http:"))
8
+
9
+ def test_tls_info_is_nil_when_disabled_or_request_is_plain_http
10
+ default_response = Wreq::Client.new.get("#{HTTPBIN_URL}/get")
11
+ plain_response = Wreq::Client.new(tls_info: true).get("#{HTTPBIN_HTTP_URL}/get")
12
+
13
+ assert_nil default_response.tls_info
14
+ assert_nil plain_response.tls_info
15
+ end
16
+
17
+ def test_certificate_data_survives_body_lifecycle_on_a_reused_connection
18
+ fixture = TlsTestServer.with_connection(request_count: 2) do |base_url, certificate_der|
19
+ client = Wreq::Client.new(
20
+ tls_info: true,
21
+ verify: false,
22
+ http1_only: true,
23
+ no_proxy: true,
24
+ timeout: 5
25
+ )
26
+
27
+ read_response = client.get("#{base_url}read")
28
+ assert_equal "ok", read_response.text
29
+ read_tls = read_response.tls_info
30
+
31
+ closed_response = client.get("#{base_url}close")
32
+ closed_response.close
33
+ closed_tls = closed_response.tls_info
34
+
35
+ assert_instance_of Wreq::TlsInfo, read_tls
36
+ certificate = read_tls.peer_certificate
37
+ chain = read_tls.peer_certificate_chain
38
+ assert_equal certificate_der, certificate
39
+ assert_equal Encoding::BINARY, certificate.encoding
40
+ assert_equal [certificate_der], chain
41
+ assert_equal Encoding::BINARY, chain.first.encoding
42
+ assert_predicate chain, :frozen?
43
+ assert_equal(
44
+ "#<Wreq::TlsInfo peer_certificate=#{certificate_der.bytesize}B peer_certificate_chain=1>",
45
+ read_tls.inspect
46
+ )
47
+ assert_empty Wreq::TlsInfo.instance_methods(false) & %i[to_h to_s]
48
+
49
+ certificate.clear
50
+ assert_equal certificate_der, read_tls.peer_certificate
51
+ assert_equal certificate_der, closed_tls.peer_certificate
52
+ end
53
+
54
+ assert_equal(
55
+ {connections: 1, requests: ["GET /read HTTP/1.1\r\n", "GET /close HTTP/1.1\r\n"]},
56
+ fixture
57
+ )
58
+ end
59
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wreq
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.10
4
+ version: 1.2.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - SearchApi
@@ -80,6 +80,8 @@ files:
80
80
  - crates/wreq-util/tests/emulate_safari.rs
81
81
  - crates/wreq-util/tests/support/mod.rs
82
82
  - crates/wreq-util/tests/support/server.rs
83
+ - docs/fork-safety.md
84
+ - docs/interrupt-handling.md
83
85
  - docs/windows-gnu-tokio-crash.md
84
86
  - examples/body.rb
85
87
  - examples/client.rb
@@ -90,6 +92,7 @@ files:
90
92
  - examples/send_stream.rb
91
93
  - examples/stream.rb
92
94
  - examples/thread_interrupt.rb
95
+ - examples/tls_info.rb
93
96
  - extconf.rb
94
97
  - lib/wreq.rb
95
98
  - lib/wreq_ruby/body.rb
@@ -100,6 +103,7 @@ files:
100
103
  - lib/wreq_ruby/header.rb
101
104
  - lib/wreq_ruby/http.rb
102
105
  - lib/wreq_ruby/response.rb
106
+ - lib/wreq_ruby/tls.rb
103
107
  - script/build_platform_gem.rb
104
108
  - script/rust_env.rb
105
109
  - src/arch.rs
@@ -142,12 +146,14 @@ files:
142
146
  - src/serde/ser/struct_variant_serializer.rs
143
147
  - src/serde/ser/tuple_variant_serializer.rs
144
148
  - src/serde/tests.rs
149
+ - src/tls.rs
145
150
  - test/body_sender_test.rb
146
151
  - test/client_cookie_test.rb
147
152
  - test/client_test.rb
148
153
  - test/cookie_test.rb
149
154
  - test/emulation_test.rb
150
155
  - test/error_handling_test.rb
156
+ - test/fork_test.rb
151
157
  - test/header_test.rb
152
158
  - test/inspect_test.rb
153
159
  - test/json_precision_test.rb
@@ -157,8 +163,11 @@ files:
157
163
  - test/request_parameters_test.rb
158
164
  - test/request_test.rb
159
165
  - test/response_test.rb
166
+ - test/scripts/fork_safety.rb
160
167
  - test/stream_test.rb
168
+ - test/support/tls_server.rb
161
169
  - test/test_helper.rb
170
+ - test/tls_info_test.rb
162
171
  - test/value_semantics_test.rb
163
172
  - wreq.gemspec
164
173
  homepage: https://github.com/SearchApi/wreq-ruby