vcr 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +8 -8
  2. data/.travis.yml +0 -3
  3. data/CHANGELOG.md +32 -3
  4. data/Gemfile +33 -0
  5. data/Gemfile.lock +99 -119
  6. data/README.md +19 -7
  7. data/Rakefile +5 -9
  8. data/benchmarks/null_logging.rb +62 -0
  9. data/features/.nav +0 -1
  10. data/features/about_these_examples.md +1 -2
  11. data/features/cassettes/allow_unused_http_interactions.feature +15 -1
  12. data/features/cassettes/decompress.feature +6 -2
  13. data/features/cassettes/format.feature +20 -12
  14. data/features/cassettes/freezing_time.feature +68 -0
  15. data/features/configuration/cassette_library_dir.feature +5 -0
  16. data/features/configuration/preserve_exact_body_bytes.feature +5 -0
  17. data/features/configuration/uri_parser.feature +2 -4
  18. data/features/http_libraries/net_http.feature +1 -1
  19. data/features/request_matching/headers.feature +0 -1
  20. data/features/step_definitions/cli_steps.rb +1 -4
  21. data/features/test_frameworks/cucumber.feature +59 -0
  22. data/features/test_frameworks/rspec_metadata.feature +59 -1
  23. data/gemfiles/typhoeus_old.gemfile +19 -0
  24. data/gemfiles/typhoeus_old.gemfile.lock +84 -86
  25. data/lib/vcr.rb +12 -3
  26. data/lib/vcr/cassette.rb +32 -11
  27. data/lib/vcr/cassette/http_interaction_list.rb +3 -2
  28. data/lib/vcr/cassette/migrator.rb +1 -0
  29. data/lib/vcr/cassette/serializers/json.rb +1 -1
  30. data/lib/vcr/configuration.rb +17 -9
  31. data/lib/vcr/library_hooks/typhoeus.rb +3 -2
  32. data/lib/vcr/library_hooks/webmock.rb +1 -1
  33. data/lib/vcr/middleware/excon.rb +13 -1
  34. data/lib/vcr/middleware/faraday.rb +1 -0
  35. data/lib/vcr/request_handler.rb +1 -1
  36. data/lib/vcr/structs.rb +19 -4
  37. data/lib/vcr/test_frameworks/cucumber.rb +2 -2
  38. data/lib/vcr/test_frameworks/rspec.rb +10 -2
  39. data/lib/vcr/util/logger.rb +41 -7
  40. data/lib/vcr/version.rb +1 -1
  41. data/script/ci.sh +8 -1
  42. data/spec/acceptance/threading_spec.rb +6 -0
  43. data/spec/capture_warnings.rb +9 -1
  44. data/spec/spec_helper.rb +6 -2
  45. data/spec/support/configuration_stubbing.rb +8 -0
  46. data/spec/support/http_library_adapters.rb +1 -1
  47. data/spec/support/limited_uri.rb +1 -0
  48. data/spec/support/shared_example_groups/excon.rb +23 -1
  49. data/spec/support/shared_example_groups/hook_into_http_library.rb +12 -12
  50. data/spec/support/shared_example_groups/request_hooks.rb +1 -1
  51. data/spec/support/sinatra_app.rb +9 -0
  52. data/spec/support/vcr_localhost_server.rb +4 -25
  53. data/spec/support/vcr_stub_helpers.rb +1 -1
  54. data/spec/vcr/cassette/http_interaction_list_spec.rb +41 -14
  55. data/spec/vcr/cassette/migrator_spec.rb +1 -1
  56. data/spec/vcr/cassette/persisters_spec.rb +2 -2
  57. data/spec/vcr/cassette/serializers_spec.rb +13 -4
  58. data/spec/vcr/cassette_spec.rb +107 -58
  59. data/spec/vcr/configuration_spec.rb +23 -23
  60. data/spec/vcr/deprecations_spec.rb +9 -9
  61. data/spec/vcr/errors_spec.rb +6 -6
  62. data/spec/vcr/library_hooks/excon_spec.rb +15 -10
  63. data/spec/vcr/library_hooks/fakeweb_spec.rb +8 -8
  64. data/spec/vcr/library_hooks/faraday_spec.rb +1 -1
  65. data/spec/vcr/library_hooks/typhoeus_0.4_spec.rb +2 -2
  66. data/spec/vcr/library_hooks/typhoeus_spec.rb +68 -9
  67. data/spec/vcr/library_hooks/webmock_spec.rb +6 -10
  68. data/spec/vcr/middleware/faraday_spec.rb +33 -5
  69. data/spec/vcr/middleware/rack_spec.rb +2 -2
  70. data/spec/vcr/request_matcher_registry_spec.rb +11 -6
  71. data/spec/vcr/structs_spec.rb +114 -47
  72. data/spec/vcr/test_frameworks/cucumber_spec.rb +4 -4
  73. data/spec/vcr/util/hooks_spec.rb +2 -2
  74. data/spec/vcr/util/internet_connection_spec.rb +3 -3
  75. data/spec/vcr/util/version_checker_spec.rb +4 -4
  76. data/spec/vcr_spec.rb +22 -16
  77. data/vcr.gemspec +2 -31
  78. metadata +9 -328
  79. data/features/test_frameworks/shoulda.feature +0 -64
@@ -2,7 +2,7 @@ module VCR
2
2
  class Cassette
3
3
  # @private
4
4
  class HTTPInteractionList
5
- include Logger
5
+ include Logger::Mixin
6
6
 
7
7
  # @private
8
8
  module NullList
@@ -59,9 +59,10 @@ module VCR
59
59
  # @raise [VCR::Errors::UnusedHTTPInteractionError] if not all interactions were played back.
60
60
  def assert_no_unused_interactions!
61
61
  return unless has_unused_interactions?
62
+ logger = Logger.new(nil)
62
63
 
63
64
  descriptions = @interactions.map do |i|
64
- " - #{request_summary(i.request)} => #{response_summary(i.response)}"
65
+ " - #{logger.request_summary(i.request, @request_matchers)} => #{logger.response_summary(i.response)}"
65
66
  end.join("\n")
66
67
 
67
68
  raise Errors::UnusedHTTPInteractionError, "There are unused HTTP interactions left in the cassette:\n#{descriptions}"
@@ -30,6 +30,7 @@ module VCR
30
30
  end
31
31
 
32
32
  http_interactions.map! do |interaction|
33
+ interaction.response.adapter_metadata = {}
33
34
  interaction.recorded_at = File.mtime(cassette)
34
35
  remove_unnecessary_standard_port(interaction)
35
36
  denormalize_http_header_keys(interaction.request)
@@ -13,7 +13,7 @@ module VCR
13
13
  extend EncodingErrorHandling
14
14
 
15
15
  # @private
16
- ENCODING_ERRORS = [MultiJson::DecodeError]
16
+ ENCODING_ERRORS = [MultiJson::DecodeError, ArgumentError]
17
17
  ENCODING_ERRORS << EncodingError if defined?(EncodingError)
18
18
 
19
19
  # The file extension to use for this serializer.
@@ -7,7 +7,7 @@ module VCR
7
7
  class Configuration
8
8
  include Hooks
9
9
  include VariableArgsBlockCaller
10
- include Logger
10
+ include Logger::Mixin
11
11
 
12
12
  # Gets the directory to read cassettes from and write cassettes to.
13
13
  #
@@ -430,7 +430,21 @@ module VCR
430
430
  # VCR.configure do |c|
431
431
  # c.debug_logger = File.open('vcr.log', 'w')
432
432
  # end
433
- attr_accessor :debug_logger
433
+ attr_reader :debug_logger
434
+ # @private (documented above)
435
+ def debug_logger=(value)
436
+ @debug_logger = value
437
+
438
+ if value
439
+ @logger = Logger.new(value)
440
+ else
441
+ @logger = Logger::Null
442
+ end
443
+ end
444
+
445
+ # @private
446
+ # Logger object that provides logging APIs and helper methods.
447
+ attr_reader :logger
434
448
 
435
449
  # Sets a callback that determines whether or not to base64 encode
436
450
  # the bytes of a request or response body during serialization in
@@ -479,7 +493,7 @@ module VCR
479
493
 
480
494
  self.uri_parser = URI
481
495
  self.query_parser = CGI.method(:parse)
482
- self.debug_logger = NullDebugLogger
496
+ self.debug_logger = nil
483
497
 
484
498
  register_built_in_hooks
485
499
  end
@@ -537,11 +551,5 @@ module VCR
537
551
  # @private
538
552
  define_hook :after_library_hooks_loaded
539
553
  end
540
-
541
- # @private
542
- module NullDebugLogger
543
- extend self
544
- def puts(*); end
545
- end
546
554
  end
547
555
 
@@ -50,7 +50,7 @@ else
50
50
  :status_message => stubbed_response.status.message,
51
51
  :headers => stubbed_response_headers,
52
52
  :body => stubbed_response.body,
53
- :effective_url => request.url,
53
+ :effective_url => stubbed_response.adapter_metadata.fetch('effective_url', request.url),
54
54
  :mock => true
55
55
  end
56
56
 
@@ -69,7 +69,8 @@ else
69
69
  VCR::ResponseStatus.new(response.code, response.status_message),
70
70
  response.headers,
71
71
  response.body,
72
- response.http_version
72
+ response.http_version,
73
+ { "effective_url" => response.effective_url }
73
74
  end
74
75
 
75
76
  ::Typhoeus.on_complete do |response|
@@ -2,7 +2,7 @@ require 'vcr/util/version_checker'
2
2
  require 'vcr/request_handler'
3
3
  require 'webmock'
4
4
 
5
- VCR::VersionChecker.new('WebMock', WebMock.version, '1.8.0', '1.11').check_version!
5
+ VCR::VersionChecker.new('WebMock', WebMock.version, '1.8.0', '1.13').check_version!
6
6
 
7
7
  module VCR
8
8
  class LibraryHooks
@@ -2,7 +2,7 @@ require 'excon'
2
2
  require 'vcr/request_handler'
3
3
  require 'vcr/util/version_checker'
4
4
 
5
- VCR::VersionChecker.new('Excon', Excon::VERSION, '0.22.0', '0.22').check_version!
5
+ VCR::VersionChecker.new('Excon', Excon::VERSION, '0.22.0', '0.26').check_version!
6
6
 
7
7
  module VCR
8
8
  # Contains middlewares for use with different libraries.
@@ -39,6 +39,7 @@ module VCR
39
39
 
40
40
  # @private
41
41
  def error_call(params)
42
+ @request_handler.ensure_response_body_can_be_read_for_error_case
42
43
  @request_handler.after_request(params)
43
44
  super
44
45
  end
@@ -84,10 +85,21 @@ module VCR
84
85
  invoke_after_request_hook(vcr_response)
85
86
  end
86
87
 
88
+ def ensure_response_body_can_be_read_for_error_case
89
+ # Excon does not invoke the `:response_block` when an error
90
+ # has occurred, so we need to be sure to use the non-streaming
91
+ # body reader.
92
+ @response_body_reader = NonStreamingResponseBodyReader
93
+ end
94
+
87
95
  attr_reader :request_params, :response_params, :response_body_reader
88
96
 
89
97
  private
90
98
 
99
+ def externally_stubbed?
100
+ !!::Excon.stub_for(request_params)
101
+ end
102
+
91
103
  def should_record?
92
104
  @should_record
93
105
  end
@@ -27,6 +27,7 @@ module VCR
27
27
  #
28
28
  # @param [Hash] env the Faraday request env hash
29
29
  def call(env)
30
+ return if VCR.library_hooks.disabled?(:faraday)
30
31
  RequestHandler.new(@app, env).handle
31
32
  end
32
33
 
@@ -1,7 +1,7 @@
1
1
  module VCR
2
2
  # @private
3
3
  class RequestHandler
4
- include Logger
4
+ include Logger::Mixin
5
5
 
6
6
  def handle
7
7
  log "Handling request: #{request_summary} (disabled: #{disabled?})"
data/lib/vcr/structs.rb CHANGED
@@ -76,6 +76,11 @@ module VCR
76
76
  private
77
77
 
78
78
  def serializable_body
79
+ # Ensure it's just a string, and not a string with some
80
+ # extra state, as such strings serialize to YAML with
81
+ # all the extra state.
82
+ body = String.new(self.body.to_s)
83
+
79
84
  if VCR.configuration.preserve_exact_body_bytes_for?(self)
80
85
  base_body_hash(body).merge('base64_string' => Base64.encode64(body))
81
86
  else
@@ -166,7 +171,7 @@ module VCR
166
171
  module OrderedHashSerializer
167
172
  def each
168
173
  @ordered_keys.each do |key|
169
- yield key, self[key]
174
+ yield key, self[key] if has_key?(key)
170
175
  end
171
176
  end
172
177
 
@@ -341,10 +346,16 @@ module VCR
341
346
  # @attr [Hash{String => Array<String>}] headers the response headers
342
347
  # @attr [String] body the response body
343
348
  # @attr [nil, String] http_version the HTTP version
344
- class Response < Struct.new(:status, :headers, :body, :http_version)
349
+ # @attr [Hash] adapter_metadata Additional metadata used by a specific VCR adapter.
350
+ class Response < Struct.new(:status, :headers, :body, :http_version, :adapter_metadata)
345
351
  include Normalizers::Header
346
352
  include Normalizers::Body
347
353
 
354
+ def initialize(*args)
355
+ super(*args)
356
+ self.adapter_metadata ||= {}
357
+ end
358
+
348
359
  # Builds a serializable hash from the response data.
349
360
  #
350
361
  # @return [Hash] hash that represents this response
@@ -356,7 +367,10 @@ module VCR
356
367
  'headers' => headers,
357
368
  'body' => serializable_body,
358
369
  'http_version' => http_version
359
- }.tap { |h| OrderedHashSerializer.apply_to(h, members) }
370
+ }.tap do |hash|
371
+ hash['adapter_metadata'] = adapter_metadata unless adapter_metadata.empty?
372
+ OrderedHashSerializer.apply_to(hash, members)
373
+ end
360
374
  end
361
375
 
362
376
  # Constructs a new instance from a hash.
@@ -367,7 +381,8 @@ module VCR
367
381
  new ResponseStatus.from_hash(hash.fetch('status', {})),
368
382
  hash['headers'],
369
383
  body_from(hash['body']),
370
- hash['http_version']
384
+ hash['http_version'],
385
+ hash['adapter_metadata']
371
386
  end
372
387
 
373
388
  # Updates the Content-Length response header so that it is
@@ -52,8 +52,8 @@ module VCR
52
52
  VCR.insert_cassette(cassette_name, options)
53
53
  end
54
54
 
55
- @main_object.After(tag_name) do
56
- VCR.eject_cassette
55
+ @main_object.After(tag_name) do |scenario|
56
+ VCR.eject_cassette(:skip_no_unused_interactions_assertion => scenario.failed?)
57
57
  end
58
58
 
59
59
  self.class.add_tag(tag_name)
@@ -17,14 +17,22 @@ module VCR
17
17
  end
18
18
  end
19
19
 
20
- config.around(:each, :vcr => lambda { |v| !!v }) do |example|
20
+ when_tagged_with_vcr = { :vcr => lambda { |v| !!v } }
21
+
22
+ config.before(:each, when_tagged_with_vcr) do |ex|
23
+ example = respond_to?(:example) ? self.example : ex
24
+
21
25
  options = example.metadata[:vcr]
22
26
  options = options.is_a?(Hash) ? options.dup : {} # in case it's just :vcr => true
23
27
 
24
28
  cassette_name = options.delete(:cassette_name) ||
25
29
  vcr_cassette_name_for[example.metadata]
30
+ VCR.insert_cassette(cassette_name, options)
31
+ end
26
32
 
27
- VCR.use_cassette(cassette_name, options, &example)
33
+ config.after(:each, when_tagged_with_vcr) do |ex|
34
+ example = respond_to?(:example) ? self.example : ex
35
+ VCR.eject_cassette(:skip_no_unused_interactions_assertion => !!example.exception)
28
36
  end
29
37
  end
30
38
  end
@@ -1,14 +1,15 @@
1
1
  module VCR
2
2
  # @private
3
- module Logger
4
- def log(message, indentation_level = 0)
5
- indentation = ' ' * indentation_level
6
- log_message = indentation + log_prefix + message
7
- VCR.configuration.debug_logger.puts log_message
3
+ # Provides log message formatting helper methods.
4
+ class Logger
5
+ def initialize(stream)
6
+ @stream = stream
8
7
  end
9
8
 
10
- def log_prefix
11
- ''
9
+ def log(message, log_prefix, indentation_level = 0)
10
+ indentation = ' ' * indentation_level
11
+ log_message = indentation + log_prefix + message
12
+ @stream.puts log_message
12
13
  end
13
14
 
14
15
  def request_summary(request, request_matchers)
@@ -21,5 +22,38 @@ module VCR
21
22
  def response_summary(response)
22
23
  "[#{response.status.code} #{response.body[0, 80].inspect}]"
23
24
  end
25
+
26
+ # @private
27
+ # A null-object version of the Logger. Used when
28
+ # a `debug_logger` has not been set.
29
+ #
30
+ # @note We used to use a null object for the `debug_logger` itself,
31
+ # but some users noticed a negative perf impact from having the
32
+ # logger formatting logic still executing in that case, so we
33
+ # moved the null object interface up a layer to here.
34
+ module Null
35
+ module_function
36
+
37
+ def log(*); end
38
+ def request_summary(*); end
39
+ def response_summary(*); end
40
+ end
41
+
42
+ # @private
43
+ # Provides common logger helper methods that simply delegate to
44
+ # the underlying logger object.
45
+ module Mixin
46
+ def log(message, indentation_level = 0)
47
+ VCR.configuration.logger.log(message, log_prefix, indentation_level)
48
+ end
49
+
50
+ def request_summary(*args)
51
+ VCR.configuration.logger.request_summary(*args)
52
+ end
53
+
54
+ def response_summary(*args)
55
+ VCR.configuration.logger.response_summary(*args)
56
+ end
57
+ end
24
58
  end
25
59
  end
data/lib/vcr/version.rb CHANGED
@@ -10,7 +10,7 @@ module VCR
10
10
  # * `parts` [Array<Integer>] List of the version parts.
11
11
  def version
12
12
  @version ||= begin
13
- string = '2.5.0'
13
+ string = '2.6.0'
14
14
 
15
15
  def string.parts
16
16
  split('.').map { |p| p.to_i }
data/script/ci.sh CHANGED
@@ -1,6 +1,13 @@
1
1
  # Kill the whole script on error
2
2
  set -e -x
3
3
 
4
+ # idea taken from: http://blog.headius.com/2010/03/jruby-startup-time-tips.html
5
+ export JRUBY_OPTS='-X-C' # disable JIT since these processes are so short lived
6
+
7
+ # force jRuby to use client mode JVM or a compilation mode thats as close as possible,
8
+ # idea taken from https://github.com/jruby/jruby/wiki/Improving-startup-time
9
+ export JAVA_OPTS='-client -XX:+TieredCompilation -XX:TieredStopAtLevel=1'
10
+
4
11
  echo "-------- Running Typhoeus 0.4 Specs ---------"
5
12
  bundle install --gemfile=gemfiles/typhoeus_old.gemfile --without extras
6
13
  BUNDLE_GEMFILE=gemfiles/typhoeus_old.gemfile bundle exec rspec spec/vcr/library_hooks/typhoeus_0.4_spec.rb --format progress --backtrace
@@ -10,7 +17,7 @@ git submodule init
10
17
  git submodule update
11
18
 
12
19
  echo "-------- Running Specs ---------"
13
- bundle exec ruby -w -I./spec -r./spec/capture_warnings -rspec_helper -S rspec spec --format progress --backtrace
20
+ bundle exec ruby -I./spec -r./spec/capture_warnings -rspec_helper -S rspec spec --format progress --backtrace
14
21
 
15
22
  echo "-------- Running Cukes ---------"
16
23
  bundle exec cucumber
@@ -2,6 +2,12 @@ require 'spec_helper'
2
2
 
3
3
  describe VCR do
4
4
  context 'when used in a multithreaded environment', :with_monkey_patches => :excon do
5
+ def preload_yaml_serializer_to_avoid_circular_require_warning_race_condition
6
+ VCR.cassette_serializers[:yaml]
7
+ end
8
+
9
+ before { preload_yaml_serializer_to_avoid_circular_require_warning_race_condition }
10
+
5
11
  def recorded_content_for(name)
6
12
  VCR.cassette_persisters[:file_system]["#{name}.yml"].to_s
7
13
  end
@@ -1,17 +1,25 @@
1
1
  require 'rubygems' if RUBY_VERSION =~ /^1\.8/
2
+ require 'bundler/setup'
2
3
  require 'rspec/core'
3
4
  require 'rspec/expectations'
4
5
  require 'tempfile'
6
+
5
7
  stderr_file = Tempfile.new("vcr.stderr")
6
- $stderr.reopen(stderr_file.path)
7
8
  current_dir = Dir.pwd
8
9
 
9
10
  RSpec.configure do |config|
11
+ config.before(:suite) do
12
+ $stderr.reopen(stderr_file.path)
13
+ $VERBOSE = true
14
+ end
15
+
10
16
  config.after(:suite) do
11
17
  stderr_file.rewind
12
18
  lines = stderr_file.read.split("\n").uniq
13
19
  stderr_file.close!
14
20
 
21
+ $stderr.reopen(STDERR)
22
+
15
23
  vcr_warnings, other_warnings = lines.partition { |line| line.include?(current_dir) }
16
24
 
17
25
  # After upgrading to curb 0.8.1, I started to get a circular require
data/spec/spec_helper.rb CHANGED
@@ -3,7 +3,7 @@ require 'rubygems'
3
3
  using_git = File.exist?(File.expand_path('../../.git/', __FILE__))
4
4
  require 'bundler/setup' if using_git
5
5
 
6
- if RUBY_VERSION =~ /1.9/ && RUBY_ENGINE == 'ruby'
6
+ if RUBY_VERSION.to_f >= 1.9 && RUBY_ENGINE == 'ruby'
7
7
  require 'simplecov'
8
8
 
9
9
  SimpleCov.start do
@@ -41,7 +41,7 @@ require 'monkey_patches'
41
41
  require "support/http_library_adapters"
42
42
 
43
43
  module VCR
44
- SPEC_ROOT = File.dirname(__FILE__)
44
+ SPEC_ROOT = File.dirname(File.expand_path('.', __FILE__))
45
45
 
46
46
  def reset!(hook = :fakeweb)
47
47
  instance_variables.each do |ivar|
@@ -60,6 +60,10 @@ RSpec.configure do |config|
60
60
  expectations.syntax = :expect
61
61
  end
62
62
 
63
+ config.mock_with :rspec do |mocks|
64
+ mocks.syntax = :expect
65
+ end
66
+
63
67
  tmp_dir = File.expand_path('../../tmp/cassette_library_dir', __FILE__)
64
68
  config.before(:each) do
65
69
  unless example.metadata[:skip_vcr_reset]
@@ -0,0 +1,8 @@
1
+ shared_context "configuration stubbing" do
2
+ let(:config) { double("VCR::Configuration") }
3
+
4
+ before do
5
+ allow(VCR).to receive(:configuration) { config }
6
+ end
7
+ end
8
+