Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix frozen string literal issue for ruby 3.4 #27

Merged
merged 1 commit into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,21 @@ permissions:
contents: read

jobs:
Shared:
uses: fog/.github/.github/workflows/[email protected]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll want to drop this and use the shared one again I think once we are satisfied (though perhaps we'll want to update the shared one with RUBYOPTS in some way also).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geemus I just had the idea, but I can create a shared config on fog/.github for fog-aws fog-xml and fog-core.

and once all gems have migrated to this new shared config. we can completely remove the old config.

If you agree with this, please let me know and I'll go ahead and create a PR on fog/.github 👌

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll have to figure out some specifics, but yeah that sounds fine to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also or instead, we could leave this for now, but also add the shared workflow back (so that it just runs both). I didn't want to lose the shared workflow. Both is probably redundant, but it would be ok as an interim step.

test:

runs-on: ubuntu-latest

strategy:
matrix:
ruby-version: ['3.0', '3.1', '3.2', '3.3', 'head']
continue-on-error: ${{ matrix.ruby-version == 'head' }}

steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Run tests
run: bundle exec rake RUBYOPT="--enable-frozen-string-literal"
2 changes: 1 addition & 1 deletion lib/fog/parsers/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def reset
end

def characters(string)
@value ||= ''
@value ||= +''
@value << string
end

Expand Down
12 changes: 7 additions & 5 deletions lib/fog/to_hash_document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ def initialize
end

def characters(string)
@value ||= ""
@value ||= +''
@value << string.strip
end

def end_element(name)
last = @stack.pop
if last.empty? && @value.empty?
@stack.last[name.to_sym] = ""
@stack.push({}) if @stack.empty?

if last&.empty? && @value.empty?
@stack.last[name.to_sym] = +''
elsif last == { :i_nil => "true" }
@stack.last[name.to_sym] = nil
elsif [email protected]?
@stack.last[name.to_sym] = @value
end
@value = ""
@value = +''
end

def body
Expand All @@ -30,7 +32,7 @@ def response
end

def start_element(name, attributes = [])
@value = ""
@value = +''
parsed_attributes = {}
until attributes.empty?
if attributes.first.is_a?(Array)
Expand Down
4 changes: 2 additions & 2 deletions lib/fog/xml/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Response
def initialize(parser)
@parser = parser
@data_stream = Nokogiri::XML::SAX::PushParser.new(parser)
@response_string = ""
@response_string = +''
end

def call(chunk, _remaining, _total)
Expand All @@ -14,7 +14,7 @@ def call(chunk, _remaining, _total)

def rewind
@parser.reset
@response_string = ""
@response_string = +''
end

def finish
Expand Down
142 changes: 142 additions & 0 deletions spec/fog/to_hash_document_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# frozen_string_literal: true

require 'minitest_helper'
require 'fog/xml'

# We expose accessors just for testing purposes
Fog::ToHashDocument.attr_accessor(:value, :stack)

describe Fog::ToHashDocument do
before do
@document = Fog::ToHashDocument.new
end

describe '#characters' do
it 'appends characters to @value' do
@document.characters('some text')
_(@document.value).must_equal 'some text'
end

it 'strips whitespace from characters' do
@document.characters(' some text ')
_(@document.value).must_equal 'some text'
end
end

describe '#end_element' do
before do
@document.stack << {}
@document.characters('some text')
end

it 'adds element with text content to the stack' do
@document.end_element('element')

expected = { element: 'some text' }
_(@document.stack.last).must_equal(expected)
end

it 'can mutate the new empty value' do
@document.end_element('element')

_(@document.value).must_equal('')

# Mutate the new empty value even when frozen string literals are enabled
_(@document.characters('one'))
end

it 'adds empty string if element is empty and value is empty' do
@document.value = ''

@document.end_element('element')

expected = { element: '' }
_(@document.stack.last).must_equal(expected)
end

it 'adds nil if element has :i_nil attribute' do
@document.stack.last[:i_nil] = 'true'
@document.value = ''

@document.end_element('element')

expected = { element: nil }
_(@document.stack.last).must_equal(expected)
end
end

describe '#body' do
it 'returns the first element of the stack' do
@document.stack << { key: 'value' }

expected = { key: 'value' }
_(@document.body).must_equal(expected)
end
end

describe '#response' do
it 'returns the body' do
@document.stack << { key: 'value' }

expected = { key: 'value' }
_(@document.response).must_equal(expected)
end
end

describe '#start_element' do
it 'parses attributes correctly' do
@document.start_element('element', [%w[key value]])

expected = { key: 'value' }
_(@document.stack.last).must_equal(expected)
end

it 'handles elements without attributes' do
@document.start_element('element')

_(@document.stack.last).must_equal({})
end

it 'adds nested elements to the stack' do
@document.start_element('parent')
@document.start_element('child')

_(@document.stack).must_equal([{ child: {} }, { child: {} }, {}])
end

it 'adds nested elements with attributes to the stack' do
@document.start_element('parent')
@document.start_element('child', [%w[key value]])
expected = [
{ child: { key: 'value' } },
{ child: { key: 'value' } },
{ key: 'value' }
]

_(@document.stack).must_equal(expected)
end

it 'handles multiple children elements correctly' do
@document.start_element('parent')
@document.start_element('child1')
@document.end_element('child1')
@document.start_element('child2', [%w[key value]])
@document.end_element('child2')
expected = {
child1: '',
child2: { key: 'value' }
}

_(@document.stack.first).must_equal(expected)
end

it 'handles text content within elements' do
@document.start_element('parent')
@document.characters('some text')
@document.end_element('parent')

expected = { parent: 'some text' }
_(@document.stack.first).must_equal(expected)
end
end
end