diff --git a/lib/datadog/core/utils/safe_dup.rb b/lib/datadog/core/utils/safe_dup.rb index 33d20c11998..a31e9651d65 100644 --- a/lib/datadog/core/utils/safe_dup.rb +++ b/lib/datadog/core/utils/safe_dup.rb @@ -13,20 +13,70 @@ def dup end end + # Ensures #initialize can call true.dup safely + module RefineTrue + refine TrueClass do + def dup + self + end + end + end + + # Ensures #initialize can call false.dup safely + module RefineFalse + refine FalseClass do + def dup + self + end + end + end + + # Ensures #initialize can call 1.dup safely + module RefineInteger + refine Integer do + def dup + self + end + end + end + + # Ensures #initialize can call 1.0.dup safely + module RefineFloat + refine Float do + def dup + self + end + end + end + using RefineNil + using RefineTrue + using RefineFalse + using RefineInteger + using RefineFloat end # String#+@ was introduced in Ruby 2.3 if String.method_defined?(:+@) && String.method_defined?(:-@) def self.frozen_or_dup(v) - # If the string is not frozen, the +(-v) will: - # - first create a frozen deduplicated copy with -v - # - then it will dup it more efficiently with +v - v.frozen? ? v : +(-v) + case v + when String + # If the string is not frozen, the +(-v) will: + # - first create a frozen deduplicated copy with -v + # - then it will dup it more efficiently with +v + v.frozen? ? v : +(-v) + else + v.frozen? ? v : v.dup + end end def self.frozen_dup(v) - -v if v + case v + when String + -v if v + else + v.frozen? ? v : v.dup.freeze + end end else def self.frozen_or_dup(v) diff --git a/spec/datadog/core/utils/safe_dup_spec.rb b/spec/datadog/core/utils/safe_dup_spec.rb index d043cd00f00..34b16ef7663 100644 --- a/spec/datadog/core/utils/safe_dup_spec.rb +++ b/spec/datadog/core/utils/safe_dup_spec.rb @@ -1,62 +1,260 @@ require 'datadog/core/utils/safe_dup' RSpec.describe Datadog::Core::Utils::SafeDup do - describe '.frozen_or_dup' do - context 'when given a frozen string' do - it 'returns the original input' do - input = 'a_frozen_string'.freeze + context 'String' do + describe '.frozen_or_dup' do + context 'when given a frozen string' do + it 'returns the original input' do + input = 'a_frozen_string'.freeze - result = described_class.frozen_or_dup(input) + result = described_class.frozen_or_dup(input) - expect(input).to be_frozen + expect(input).to be_frozen - expect(result).to eq(input) - expect(result).to be(input) - expect(result).to be_frozen + expect(result).to eq(input) + expect(result).to be(input) + expect(result).to be_frozen + end + end + + context 'when given a string' do + it 'returns a non frozen dupliacte' do + input = 'a_string' + + result = described_class.frozen_or_dup(input) + + expect(input).not_to be_frozen + + expect(result).to eq(input) + expect(result).not_to be(input) + expect(result).not_to be_frozen + end + end + end + + describe '.frozen_dup' do + context 'when given a frozen string' do + it 'returns the original input' do + input = 'a_frozen_string'.freeze + + result = described_class.frozen_dup(input) + + expect(input).to be_frozen + + expect(result).to eq(input) + expect(result).to be(input) + expect(result).to be_frozen + end + end + + context 'when given a string' do + it 'returns a frozen duplicate' do + input = 'a_string' + + result = described_class.frozen_dup(input) + + expect(input).not_to be_frozen + + expect(result).to eq(input) + expect(result).not_to be(input) + expect(result).to be_frozen + end + end + end + end + + context 'Hash' do + describe '.frozen_or_dup' do + context 'when given a frozen hash' do + it 'returns the original input' do + input = { a: :b }.freeze + + result = described_class.frozen_or_dup(input) + + expect(input).to be_frozen + + expect(result).to eq(input) + expect(result).to be(input) + expect(result).to be_frozen + end + end + + context 'when given a hash' do + it 'returns a non frozen dupliacte' do + input = { a: :b } + + result = described_class.frozen_or_dup(input) + + expect(input).not_to be_frozen + + expect(result).to eq(input) + expect(result).not_to be(input) + expect(result).not_to be_frozen + end end end - context 'when given a string' do - it 'returns a non frozen dupliacte' do - input = 'a_string' + describe '.frozen_dup' do + context 'when given a frozen hash' do + it 'returns the original input' do + input = { a: :b }.freeze + + result = described_class.frozen_dup(input) + + expect(input).to be_frozen + + expect(result).to eq(input) + expect(result).to be(input) + expect(result).to be_frozen + end + end + + context 'when given a hash' do + it 'returns a frozen duplicate' do + input = { a: :b } + + result = described_class.frozen_dup(input) + + expect(input).not_to be_frozen + + expect(result).to eq(input) + expect(result).not_to be(input) + expect(result).to be_frozen + end + end + end + end + + context 'Boolean' do + before do + skip 'TrueClass and FalseClass are not frozen by default on ruby 2.1' if RUBY_VERSION < '2.2' + end + + describe '.frozen_or_dup' do + context 'when given a boolean' do + it 'returns the original input' do + input = true + + result = described_class.frozen_or_dup(input) + + expect(input).to be_frozen + + expect(result).to eq(input) + expect(result).to be(input) + expect(result).to be_frozen + end + end + end + + describe '.frozen_dup' do + context 'when given a boolean' do + it 'returns the original input' do + input = true + + result = described_class.frozen_dup(input) + + expect(input).to be_frozen + + expect(result).to eq(input) + expect(result).to be(input) + expect(result).to be_frozen + end + end + end + end + + context 'Array' do + describe '.frozen_or_dup' do + context 'when given a frozen array' do + it 'returns the original input' do + input = [1].freeze + + result = described_class.frozen_or_dup(input) + + expect(input).to be_frozen + + expect(result).to eq(input) + expect(result).to be(input) + expect(result).to be_frozen + end + end + + context 'when given a string' do + it 'returns a non frozen array' do + input = [1] + + result = described_class.frozen_or_dup(input) + + expect(input).not_to be_frozen + + expect(result).to eq(input) + expect(result).not_to be(input) + expect(result).not_to be_frozen + end + end + end + + describe '.frozen_dup' do + context 'when given a frozen array' do + it 'returns the original input' do + input = [1].freeze + + result = described_class.frozen_dup(input) + + expect(input).to be_frozen + + expect(result).to eq(input) + expect(result).to be(input) + expect(result).to be_frozen + end + end + + context 'when given a array' do + it 'returns a frozen duplicate' do + input = [1] - result = described_class.frozen_or_dup(input) + result = described_class.frozen_dup(input) - expect(input).not_to be_frozen + expect(input).not_to be_frozen - expect(result).to eq(input) - expect(result).not_to be(input) - expect(result).not_to be_frozen + expect(result).to eq(input) + expect(result).not_to be(input) + expect(result).to be_frozen + end end end end - describe '.frozen_dup' do - context 'when given a frozen string' do - it 'returns the original input' do - input = 'a_frozen_string'.freeze + context 'Numeric' do + describe '.frozen_or_dup' do + context 'when given a numeric' do + it 'returns the original input' do + input = 1 - result = described_class.frozen_dup(input) + result = described_class.frozen_or_dup(input) - expect(input).to be_frozen + expect(input).to be_frozen - expect(result).to eq(input) - expect(result).to be(input) - expect(result).to be_frozen + expect(result).to eq(input) + expect(result).to be(input) + expect(result).to be_frozen + end end end - context 'when given a string' do - it 'returns a frozen duplicate' do - input = 'a_string' + describe '.frozen_dup' do + context 'when given a numeric' do + it 'returns the original input' do + input = 10.0 - result = described_class.frozen_dup(input) + result = described_class.frozen_dup(input) - expect(input).not_to be_frozen + expect(input).to be_frozen - expect(result).to eq(input) - expect(result).not_to be(input) - expect(result).to be_frozen + expect(result).to eq(input) + expect(result).to be(input) + expect(result).to be_frozen + end end end end