diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 6f358dbde..9acaad953 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -19,13 +19,21 @@ env: jobs: benchmark: runs-on: ubuntu-latest + env: + GH_EVENT: ${{github.event_name}} + GH_REF: ${{github.head_ref}} + GH_REPO: ${{github.repository}} + EXECUTION_ENV: github-action strategy: fail-fast: false matrix: - ruby: ['3.0', 3.1, 3.2, 3.3] + # Supported ruby versions + ruby: [jruby-9.4, '3.0', 3.1, 3.2, 3.3] steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: Setup Java uses: actions/setup-java@v1 @@ -36,25 +44,27 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - bundler-cache: true + + - name: Install gems + run: | + bundle config set --local with 'benchmark' + bundle install - name: Build SDK run: bundle exec rake codegen:build - - name: Benchmark - run: EXECUTION_ENV=github-action bundle exec rake benchmark:run + - name: Benchmark gems + run: bundle exec rake benchmark:run - - name: configure aws credentials - uses: aws-actions/configure-aws-credentials@v2 + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: arn:aws:iam::469596866844:role/aws-sdk-ruby-performance-reporter - role-session-name: benchmark-report - aws-region: us-west-2 + role-to-assume: arn:aws:iam::373952703873:role/BenchmarkReporter + role-session-name: benchmark-reporter + aws-region: us-east-1 - - name: Archive benchmark report - run: | - GH_REPO=${{github.repository}} GH_REF=${{github.head_ref}} GH_EVENT=${{github.event_name}} bundle exec rake benchmark:archive + - name: Upload benchmark report + run: bundle exec rake benchmark:upload-report - - name: Upload benchmark metrics - run: | - GH_REPO=${{github.repository}} GH_REF=${{github.head_ref}} GH_EVENT=${{github.event_name}} bundle exec rake benchmark:put-metrics + - name: Put benchmark metrics + run: bundle exec rake benchmark:put-metrics diff --git a/.github/workflows/codegen_ci.yml b/.github/workflows/codegen_ci.yml index b0b1b23f9..6a51c0985 100644 --- a/.github/workflows/codegen_ci.yml +++ b/.github/workflows/codegen_ci.yml @@ -14,8 +14,7 @@ env: ruby_version: 3.3 jobs: - - ruby-rbs-type-check: + whitelabel-rbs: runs-on: ubuntu-latest steps: @@ -29,10 +28,12 @@ jobs: with: ruby-version: ${{ env.ruby_version }} - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install gems - run: bundle install + run: | + bundle config set --local with 'rbs test' + bundle install - name: Build the SDK run: bundle exec rake codegen:build @@ -43,12 +44,13 @@ jobs: - name: rbs:test run: bundle exec rake rbs:white_label - ruby-specs: + codegen-test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - ruby: ['3.0', 3.1, 3.2, 3.3] + # Supported ruby versions + ruby: [jruby-9.4, '3.0', 3.1, 3.2, 3.3] steps: - uses: actions/checkout@v4 @@ -57,13 +59,17 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - bundler-cache: true - name: Setup Java uses: actions/setup-java@v1 with: java-version: ${{ env.java_version }} + - name: Install gems + run: | + bundle config set --local with 'test' + bundle install + - name: Build SDK run: bundle exec rake codegen:build @@ -83,7 +89,11 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ env.ruby_version }} - bundler-cache: true + + - name: Install gems + run: | + bundle config set --local with 'development' + bundle install - name: Rubocop run: bundle exec rake rubocop:codegen diff --git a/.github/workflows/downstream_ci.yml b/.github/workflows/downstream_ci.yml index 6ef395428..bed6c877b 100644 --- a/.github/workflows/downstream_ci.yml +++ b/.github/workflows/downstream_ci.yml @@ -14,8 +14,7 @@ env: ruby_version: 3.3 jobs: - - aws-sdk-v4-tests: + aws-sdk-ruby-v4-tests: runs-on: ubuntu-latest steps: @@ -30,9 +29,13 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ env.ruby_version }} - bundler-cache: true - - name: publish smithy-ruby to maven local + - name: Install gems + run: | + bundle config set --local with 'test' + bundle install + + - name: Publish smithy-ruby to mavenLocal run: bundle exec rake codegen:publish-local - name: v4 setup and build diff --git a/.github/workflows/hearth_ci.yml b/.github/workflows/hearth_ci.yml index 7acfb460f..4ad6f9e5a 100644 --- a/.github/workflows/hearth_ci.yml +++ b/.github/workflows/hearth_ci.yml @@ -13,12 +13,8 @@ env: ruby_version: 3.3 jobs: - rspec: + hearth-rbs: runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - ruby: ['3.0', 3.1, 3.2, 3.3] steps: - uses: actions/checkout@v4 @@ -26,14 +22,26 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true + ruby-version: ${{ env.ruby_version }} - - name: Unit tests - run: rake test:hearth + - name: Install gems + run: | + bundle config set --local with 'rbs test' + bundle install + + - name: Steep + run: rake steep:hearth + + - name: RBS + run: rake rbs:hearth - type-check: + hearth-test: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Supported ruby versions + ruby: [jruby-9.4, '3.0', 3.1, 3.2, 3.3] steps: - uses: actions/checkout@v4 @@ -41,16 +49,17 @@ jobs: - name: Setup Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: ${{ env.ruby_version }} - bundler-cache: true + ruby-version: ${{ matrix.ruby }} - - name: Steep Check - run: rake steep:hearth + - name: Install gems + run: | + bundle config set --local with 'test' + bundle install - - name: RBS Validate - run: rake rbs:hearth + - name: Test + run: rake test:hearth - rubocop: + hearth-rubocop: runs-on: ubuntu-latest steps: @@ -60,7 +69,11 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ env.ruby_version }} - bundler-cache: true + + - name: Install gems + run: | + bundle config set --local with 'development' + bundle install - name: Rubocop run: rake rubocop:hearth diff --git a/.github/workflows/rails_codegen_ci.yml b/.github/workflows/rails_codegen_ci.yml index 26ab47b86..9f761f6cd 100644 --- a/.github/workflows/rails_codegen_ci.yml +++ b/.github/workflows/rails_codegen_ci.yml @@ -14,10 +14,12 @@ env: ruby_version: 3.3 jobs: - ruby-rbs-type-check: + railsjson-rbs: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 + - name: Setup Java uses: actions/setup-java@v1 with: @@ -28,10 +30,10 @@ jobs: with: ruby-version: ${{ env.ruby_version }} - - uses: actions/checkout@v2 - - name: Install gems - run: bundle install + run: | + bundle config set --local with 'rbs test' + bundle install - name: Build SDK run: bundle exec rake codegen:build @@ -42,28 +44,30 @@ jobs: - name: RBS Checks run: bundle exec rake rbs:rails_json - ruby-specs: + railsjson-test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - ruby: ['3.0', 3.1, 3.2, 3.3] + ruby: [jruby-9.4, '3.0', 3.1, 3.2, 3.3] steps: - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} + - uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v1 with: java-version: ${{ env.java_version }} - - uses: actions/checkout@v2 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} - name: Install gems - run: bundle install + run: | + bundle config set --local with 'test' + bundle install - name: Build SDK run: bundle exec rake codegen:build diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..5b6a0a728 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tasks/benchmark"] + path = tasks/benchmark + url = git@github.com:aws/aws-sdk-ruby-benchmark-tools.git diff --git a/Gemfile b/Gemfile index 10ddc5f22..239253878 100644 --- a/Gemfile +++ b/Gemfile @@ -7,24 +7,27 @@ gem 'jmespath' gem 'rake', require: false gem 'rexml' +group :benchmark do + gem 'memory_profiler' + + # required for uploading archive/metrics + gem 'aws-sdk-cloudwatch' + gem 'aws-sdk-s3' +end + group :development do - gem 'byebug' - gem 'rbs' + gem 'byebug', platforms: :ruby gem 'rubocop' gem 'rubocop-rake' - gem 'steep' end group :docs do gem 'yard' end -group :benchmark do - gem 'memory_profiler' - - # required for uploading archive/metrics - gem 'aws-sdk-cloudwatch' - gem 'aws-sdk-s3' +group :rbs do + gem 'rbs', platforms: :ruby + gem 'steep', platforms: :ruby end group :test do diff --git a/codegen/projections/rails_json/rails_json.gemspec b/codegen/projections/rails_json/rails_json.gemspec index 114247b0a..a95b3e7fe 100644 --- a/codegen/projections/rails_json/rails_json.gemspec +++ b/codegen/projections/rails_json/rails_json.gemspec @@ -12,7 +12,10 @@ Gem::Specification.new do |spec| spec.version = File.read(File.expand_path('VERSION', __dir__)).strip spec.author = 'Amazon Web Services' spec.summary = 'RailsJson Protocol Test Service' + spec.homepage = 'https://github.com/smithy-lang/smithy-ruby' spec.files = Dir['lib/**/*.rb', 'VERSION'] + spec.license = 'Apache-2.0' + spec.required_ruby_version = '>= 3.0' spec.add_runtime_dependency 'hearth', '~> 1.0.0.pre3' end diff --git a/codegen/projections/rails_json/spec/protocol_spec.rb b/codegen/projections/rails_json/spec/protocol_spec.rb index 929b62dcd..b8dfe59f7 100644 --- a/codegen/projections/rails_json/spec/protocol_spec.rb +++ b/codegen/projections/rails_json/spec/protocol_spec.rb @@ -35,7 +35,8 @@ module RailsJson expected_query = ::CGI.parse(['String=Hello%20there', 'StringList=a', 'StringList=b', 'StringList=c', 'StringSet=a', 'StringSet=b', 'StringSet=c', 'Byte=1', 'Short=2', 'Integer=3', 'IntegerList=1', 'IntegerList=2', 'IntegerList=3', 'IntegerSet=1', 'IntegerSet=2', 'IntegerSet=3', 'Long=4', 'Float=1.1', 'Double=1.1', 'DoubleList=1.1', 'DoubleList=2.1', 'DoubleList=3.1', 'Boolean=true', 'BooleanList=true', 'BooleanList=false', 'BooleanList=true', 'Timestamp=1970-01-01T00%3A00%3A01Z', 'TimestampList=1970-01-01T00%3A00%3A01Z', 'TimestampList=1970-01-01T00%3A00%3A02Z', 'TimestampList=1970-01-01T00%3A00%3A03Z', 'Enum=Foo', 'EnumList=Foo', 'EnumList=Baz', 'EnumList=Bar', 'IntegerEnum=1', 'IntegerEnumList=1', 'IntegerEnumList=2', 'IntegerEnumList=3'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -190,7 +191,8 @@ module RailsJson expected_query = ::CGI.parse(['QueryParamsStringKeyA=Foo', 'QueryParamsStringKeyB=Bar'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -217,7 +219,8 @@ module RailsJson expected_query = ::CGI.parse(['String=%20%25%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D%F0%9F%98%B9'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -242,7 +245,8 @@ module RailsJson expected_query = ::CGI.parse(['Float=NaN', 'Double=NaN'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -271,7 +275,8 @@ module RailsJson expected_query = ::CGI.parse(['Float=Infinity', 'Double=Infinity'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -300,7 +305,8 @@ module RailsJson expected_query = ::CGI.parse(['Float=-Infinity', 'Double=-Infinity'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -329,7 +335,8 @@ module RailsJson expected_query = ::CGI.parse(['Integer=0', 'Boolean=false'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -366,7 +373,8 @@ module RailsJson expected_query = ::CGI.parse(['foo=bar', 'baz=bam'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end forbid_query = ['maybeSet'] actual_query = ::CGI.parse(request.uri.query) @@ -391,7 +399,8 @@ module RailsJson expected_query = ::CGI.parse(['foo=bar', 'baz=bam', 'maybeSet=yes'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -420,7 +429,8 @@ module RailsJson expected_query = ::CGI.parse(['foo=bar', 'hello'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -5562,7 +5572,8 @@ module RailsJson expected_query = ::CGI.parse(['Empty='].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -6714,7 +6725,8 @@ module RailsJson expected_query = ::CGI.parse(['token=00000000-0000-4000-8000-000000000000'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -6733,7 +6745,8 @@ module RailsJson expected_query = ::CGI.parse(['token=00000000-0000-4000-8000-000000000000'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -6761,7 +6774,8 @@ module RailsJson expected_query = ::CGI.parse(['corge=named', 'baz=bar', 'baz=qux'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end @@ -6795,7 +6809,8 @@ module RailsJson expected_query = ::CGI.parse(['bar=named', 'qux=alsoFromMap'].join('&')) actual_query = ::CGI.parse(request.uri.query) expected_query.each do |k, v| - expect(actual_query[k]).to eq(v) + actual = actual_query[k].map { |s| s.force_encoding('utf-8') } + expect(actual).to eq(v) end expect(request.body.read).to eq('') end diff --git a/codegen/projections/rpcv2_cbor/rpcv2_cbor.gemspec b/codegen/projections/rpcv2_cbor/rpcv2_cbor.gemspec index fafa1cf23..e7f5aaf1a 100644 --- a/codegen/projections/rpcv2_cbor/rpcv2_cbor.gemspec +++ b/codegen/projections/rpcv2_cbor/rpcv2_cbor.gemspec @@ -12,7 +12,10 @@ Gem::Specification.new do |spec| spec.version = File.read(File.expand_path('VERSION', __dir__)).strip spec.author = 'Amazon Web Services' spec.summary = 'Rpcv2Cbor Protocol Test Service' + spec.homepage = 'https://github.com/smithy-lang/smithy-ruby' spec.files = Dir['lib/**/*.rb', 'VERSION'] + spec.license = 'Apache-2.0' + spec.required_ruby_version = '>= 3.0' spec.add_runtime_dependency 'hearth', '~> 1.0.0.pre3' end diff --git a/codegen/projections/white_label/white_label.gemspec b/codegen/projections/white_label/white_label.gemspec index f054c3877..185cb5d80 100644 --- a/codegen/projections/white_label/white_label.gemspec +++ b/codegen/projections/white_label/white_label.gemspec @@ -12,7 +12,10 @@ Gem::Specification.new do |spec| spec.version = File.read(File.expand_path('VERSION', __dir__)).strip spec.author = 'Amazon Web Services' spec.summary = 'White Label Test Service' + spec.homepage = 'https://github.com/smithy-lang/smithy-ruby' spec.files = Dir['lib/**/*.rb', 'VERSION'] + spec.license = 'Apache-2.0' + spec.required_ruby_version = '>= 3.0' spec.add_runtime_dependency 'hearth', '~> 1.0.0.pre3' end diff --git a/codegen/smithy-ruby-codegen-test/base-smithy-build.json b/codegen/smithy-ruby-codegen-test/base-smithy-build.json index fd3bb7b39..100d7cc80 100644 --- a/codegen/smithy-ruby-codegen-test/base-smithy-build.json +++ b/codegen/smithy-ruby-codegen-test/base-smithy-build.json @@ -15,7 +15,8 @@ "gemspec": { "gemName": "white_label", "gemVersion": "0.0.1", - "gemSummary": "White Label Test Service" + "gemSummary": "White Label Test Service", + "gemHomepage": "https://github.com/smithy-lang/smithy-ruby" } } } @@ -36,7 +37,8 @@ "gemspec": { "gemName": "rpcv2_cbor", "gemVersion": "0.0.1", - "gemSummary": "Rpcv2Cbor Protocol Test Service" + "gemSummary": "Rpcv2Cbor Protocol Test Service", + "gemHomepage": "https://github.com/smithy-lang/smithy-ruby" } } } diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubySettings.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubySettings.java index aa385c6d3..939edd449 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubySettings.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/RubySettings.java @@ -41,12 +41,14 @@ public final class RubySettings { private static final String GEM_NAME = "gemName"; private static final String GEM_VERSION = "gemVersion"; private static final String GEM_SUMMARY = "gemSummary"; + private static final String GEM_HOMEPAGE = "gemHomepage"; private ShapeId service; private String module; private String gemName; private String gemVersion; private String gemSummary; + private String gemHomepage; /** * Create a settings object from a configuration object node. @@ -67,6 +69,10 @@ public static RubySettings from(ObjectNode config) { settings.setGemName(gemspec.expectStringMember(GEM_NAME).getValue()); settings.setGemVersion(gemspec.expectStringMember(GEM_VERSION).getValue()); settings.setGemSummary(gemspec.expectStringMember(GEM_SUMMARY).getValue()); + // optional gemspec values + if (gemspec.getMember(GEM_HOMEPAGE).isPresent()) { + settings.setGemHomepage(gemspec.getStringMember(GEM_HOMEPAGE).get().getValue()); + } LOGGER.info("Created Ruby Settings: " + settings); @@ -143,11 +149,24 @@ public void setGemSummary(String gemSummary) { this.gemSummary = gemSummary; } + /** + * @return homepage of the gem. + */ + public String getGemHomepage() { + return gemHomepage; + } + + /** + * @param gemHomepage homepage of the gem. + */ + public void setGemHomepage(String gemHomepage) { + this.gemHomepage = gemHomepage; + } + /** * @return default/base dependencies to include. */ public Set getBaseDependencies() { - //TODO: Read from config (eg allow override of hearth version?) return (Set.of(RubyDependency.HEARTH)); } diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/GemspecGenerator.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/GemspecGenerator.java index fd1e745f0..1dbd0179a 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/GemspecGenerator.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/GemspecGenerator.java @@ -70,8 +70,15 @@ public void render() { .write("spec.version = File.read(File.expand_path('VERSION', __dir__)).strip") .write("spec.author = 'Amazon Web Services'") .write("spec.summary = '$L'", settings.getGemSummary()) + .call(() -> { + if (settings.getGemHomepage() != null) { + writer.write("spec.homepage = '$L'", settings.getGemHomepage()); + } + }) .write("spec.files = Dir['lib/**/*.rb', 'VERSION']") + .write("spec.license = 'Apache-2.0'") .write("") + .write("spec.required_ruby_version = '>= 3.0'") .call(() -> { // determine set of indirect dependencies - covered by requiring another Set indirectDependencies = new HashSet<>(); diff --git a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/HttpProtocolTestGenerator.java b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/HttpProtocolTestGenerator.java index cbbccf4c0..c212b3fc3 100644 --- a/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/HttpProtocolTestGenerator.java +++ b/codegen/smithy-ruby-codegen/src/main/java/software/amazon/smithy/ruby/codegen/generators/HttpProtocolTestGenerator.java @@ -508,7 +508,8 @@ RubyImportContainer.CGI, getRubyArrayFromList(queryParams)) .write("actual_query = $T.parse(request.uri.query)", RubyImportContainer.CGI) .openBlock("expected_query.each do |k, v|") - .write("expect(actual_query[k]).to eq(v)") + .write("actual = actual_query[k].map { |s| s.force_encoding('utf-8') }") + .write("expect(actual).to eq(v)") .closeBlock("end"); } } diff --git a/codegen/smithy-ruby-rails-codegen-test/smithy-build.json b/codegen/smithy-ruby-rails-codegen-test/smithy-build.json index 577505b2f..c01260028 100644 --- a/codegen/smithy-ruby-rails-codegen-test/smithy-build.json +++ b/codegen/smithy-ruby-rails-codegen-test/smithy-build.json @@ -15,7 +15,8 @@ "gemspec": { "gemName": "rails_json", "gemVersion": "0.0.1", - "gemSummary": "RailsJson Protocol Test Service" + "gemSummary": "RailsJson Protocol Test Service", + "gemHomepage": "https://github.com/smithy-lang/smithy-ruby" } } } diff --git a/hearth/hearth.gemspec b/hearth/hearth.gemspec index c45cfb56a..45d4c972b 100755 --- a/hearth/hearth.gemspec +++ b/hearth/hearth.gemspec @@ -12,8 +12,8 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' - spec.add_dependency 'jmespath' - spec.add_dependency 'rexml' + spec.add_dependency 'jmespath', '~> 1.6' + spec.add_dependency 'rexml', '~> 3' spec.license = 'Apache-2.0' end diff --git a/hearth/lib/hearth/http/fields.rb b/hearth/lib/hearth/http/fields.rb index e63a35e44..64def5f0f 100644 --- a/hearth/lib/hearth/http/fields.rb +++ b/hearth/lib/hearth/http/fields.rb @@ -61,9 +61,10 @@ def clear @entries = {} end - # @api private def inspect - super.gsub(/ @entries={.*},/, '') + super + .gsub(/ @entries={.*},/, '') + .gsub(/, @entries={.*}/, '') end # Proxy class that wraps Fields to create Headers and Trailers @@ -107,9 +108,8 @@ def each(&block) end alias each_pair each - # @api private def inspect - to_h + to_h.inspect end end end diff --git a/hearth/lib/hearth/xml/node.rb b/hearth/lib/hearth/xml/node.rb index 2e2920b7e..287d15fce 100644 --- a/hearth/lib/hearth/xml/node.rb +++ b/hearth/lib/hearth/xml/node.rb @@ -37,8 +37,8 @@ def <<(*children) @text << child else - raise ArgumentError, 'expected Hearth::XML::Node or String, ' \ - "got #{child.class}" + raise ArgumentError, + "expected Hearth::XML::Node or String, got #{child.class}" end end end diff --git a/hearth/sig/lib/hearth/http/request.rbs b/hearth/sig/lib/hearth/http/request.rbs index ed54d0ff3..39db4c15c 100644 --- a/hearth/sig/lib/hearth/http/request.rbs +++ b/hearth/sig/lib/hearth/http/request.rbs @@ -1,9 +1,9 @@ module Hearth module HTTP class Request < Hearth::Request - def initialize: (?http_method: Symbol?, ?fields: Fields, ?uri: URI, ?body: IO) -> void + def initialize: (?http_method: String?, ?fields: Fields, ?uri: URI, ?body: IO) -> void - attr_accessor http_method: Symbol + attr_accessor http_method: String? attr_reader fields: Fields diff --git a/hearth/spec/hearth/http/fields_spec.rb b/hearth/spec/hearth/http/fields_spec.rb index 5910a039c..cdd17f690 100644 --- a/hearth/spec/hearth/http/fields_spec.rb +++ b/hearth/spec/hearth/http/fields_spec.rb @@ -177,8 +177,8 @@ module HTTP end describe '#inspect' do - it 'is to_h' do - expect(proxy.inspect).to eq(proxy.to_h) + it 'is to_h as a string' do + expect(proxy.inspect).to eq(proxy.to_h.to_s) end end end diff --git a/hearth/spec/spec_helper.rb b/hearth/spec/spec_helper.rb index a61c5ceb8..dc8143a3a 100644 --- a/hearth/spec/spec_helper.rb +++ b/hearth/spec/spec_helper.rb @@ -2,7 +2,7 @@ require 'simplecov' -unless ENV['NO_COVERAGE'] +if !ENV['NO_COVERAGE'] && !defined?(JRUBY_VERSION) SimpleCov.minimum_coverage 100 SimpleCov.start do add_filter %r{/spec/} diff --git a/tasks/benchmark b/tasks/benchmark new file mode 160000 index 000000000..f3d048c1b --- /dev/null +++ b/tasks/benchmark @@ -0,0 +1 @@ +Subproject commit f3d048c1b5c94b4a017756a38783557728c3aca7 diff --git a/tasks/benchmark.rake b/tasks/benchmark.rake deleted file mode 100644 index cf0be754f..000000000 --- a/tasks/benchmark.rake +++ /dev/null @@ -1,114 +0,0 @@ -# frozen_string_literal: true - -namespace :benchmark do - desc 'Runs a performance benchmark on the SDK' - task 'run' do - require 'tmpdir' - require 'memory_profiler' # MemoryProfiler does not work for JRuby - require 'json' - - require_relative 'benchmark/benchmark' - - # Modify load path to include codegen gems from build directories - Dir.glob('codegen/*/build/smithyprojections/**/ruby-codegen/*/lib') do |gem_path| - $LOAD_PATH.unshift(File.expand_path(gem_path)) - end - - report_data = Benchmark.initialize_report_data - benchmark_data = report_data['benchmark'] - - puts 'Benchmarking gem size/requires/client initialization' - Dir.mktmpdir('ruby-sdk-benchmark') do |_tmpdir| - Benchmark::Gem.descendants.each do |benchmark_gem_klass| - benchmark_gem = benchmark_gem_klass.new - puts "\tBenchmarking #{benchmark_gem.gem_name}" - gem_data = benchmark_data[benchmark_gem.gem_name] ||= {} - benchmark_gem.benchmark_gem_size(gem_data) - benchmark_gem.benchmark_require(gem_data) - benchmark_gem.benchmark_client(gem_data) - end - end - - # Benchmarking operations needs to be done after all require/client init tests - # have been done. This ensures that no gem requires/cache state is in - # process memory for those tests - puts "\nBenchmarking operations" - Benchmark::Gem.descendants.each do |benchmark_gem_klass| - benchmark_gem = benchmark_gem_klass.new - puts "\tBenchmarking #{benchmark_gem.gem_name}" - benchmark_gem.benchmark_operations(benchmark_data[benchmark_gem.gem_name]) - end - - puts 'Benchmarking complete, writing out report to: benchmark_report.json' - File.write('benchmark_report.json', JSON.pretty_generate(report_data)) - end - - desc 'Upload/archive the benchmark report' - task 'archive' do - require 'aws-sdk-s3' - require 'securerandom' - - repo = ENV.fetch('GH_REPO', nil) - ref = ENV.fetch('GH_REF', nil) - event = ENV.fetch('GH_EVENT', nil) - puts 'Archiving benchmark report from GH with ' \ - "repo: #{repo}, ref: #{ref}, event: #{event}" - folder = - if ENV['GH_EVENT'] == 'pull_request' - "pr/#{ENV.fetch('GH_REF', nil)}" - else - 'release' - end - key = "#{folder}/#{Time.now.strftime('%Y-%m-%d')}/benchmark_#{SecureRandom.uuid}.json" - - puts "Uploading report to: #{key}" - client = Aws::S3::Client.new - client.put_object( - bucket: 'hearth-performance-benchmark-archive', - key: key, - body: File.read('benchmark_report.json') - ) - puts 'Upload complete' - end - - desc 'Upload benchmarking data to cloudwatch' - task 'put-metrics' do - require 'aws-sdk-cloudwatch' - require_relative 'benchmark/metrics' - - event = - if ENV['GH_EVENT'] == 'pull_request' - 'pr' - else - 'release' - end - report = JSON.parse(File.read('benchmark_report.json')) - target = "#{report['ruby_engine']}-#{report['ruby_version'].split('.').first(2).join('.')}" - - # common dimensions - report_dims = { - event: event, - target: target, - os: report['os'], - cpu: report['cpu'], - env: report['execution_env'] - } - - puts 'Uploading benchmarking metrics' - client = Aws::CloudWatch::Client.new - benchmark_data = report['benchmark'] - benchmark_data.each do |gem_name, gem_data| - dims = report_dims.merge(gem: gem_name) - gem_data.each do |k, v| - Benchmark::Metrics.put_metric( - client: client, - dims: dims, - timestamp: report['timestamp'] || Time.now, - metric_name: k, - value: v - ) - end - end - puts 'Benchmarking metrics uploaded' - end -end diff --git a/tasks/benchmark/benchmark.rb b/tasks/benchmark/benchmark.rb deleted file mode 100644 index 616febe41..000000000 --- a/tasks/benchmark/benchmark.rb +++ /dev/null @@ -1,219 +0,0 @@ -# frozen_string_literal: true - -require_relative 'test_data' - -module Benchmark - # monotonic system clock should be used for any time difference measurements - def self.monotonic_milliseconds - if defined?(Process::CLOCK_MONOTONIC) - Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) / 1000.0 - else - Time.now.to_f * 1000.0 - end - end - - # benchmark a block, returning an array of times (to allow statistic computation) - def self.measure_time(n = 300, &block) - values = Array.new(n) - n.times do |i| - t1 = monotonic_milliseconds - block.call - values[i] = monotonic_milliseconds - t1 - end - values - end - - # Run a block in a fork and returns the data from it - # the block must take a single argument and will be called with an empty hash - # any data that should be communicated back to the parent process can be written to that hash - def self.fork_run(&block) - # fork is not supported in JRuby, for now, just run this in the same process - # data collected will not be as useful, but still valid for relative comparisons over time - if defined?(JRUBY_VERSION) - h = {} - block.call(h) - return h - end - - rd, wr = IO.pipe - p1 = fork do - h = {} - block.call(h) - wr.write(JSON.dump(h)) - wr.close - end - Process.wait(p1) - wr.close - h = JSON.parse(rd.read, symbolize_names: true) - rd.close - h - end - - def self.host_os - case RbConfig::CONFIG['host_os'] - when /mac|darwin/ - 'macos' - when /linux|cygwin/ - 'linux' - when /mingw|mswin/ - 'windows' - else - 'other' - end - end - - def self.initialize_report_data - report_data = { 'version' => '1.0' } - begin - report_data['commit_id'] = `git rev-parse HEAD`.strip - rescue StandardError - # unable to get a commit, maybe run outside of a git repo. Skip - end - report_data['ruby_engine'] = RUBY_ENGINE - report_data['ruby_engine_version'] = RUBY_ENGINE_VERSION - report_data['ruby_version'] = RUBY_VERSION - - report_data['cpu'] = RbConfig::CONFIG['host_cpu'] - report_data['os'] = host_os - report_data['execution_env'] = ENV['EXECUTION_ENV'] || 'unknown' - - report_data['timestamp'] = Time.now.to_i - - report_data['benchmark'] = {} - report_data - end - - # abstract base class for benchmarking an SDK Gem - # implementors must define the gem_name, client_klass, and operation_benchmarks methods - class Gem - # the name of the gem - def gem_name; end - - # the location of the gem - def gem_dir; end - - # the module that contains the client (eg :S3) - def client_module_name; end - - # return a hash with definitions for operation benchmarks to run - # the key should be the name of the test (reported as the metric name) - # Values should be a hash with keys: setup (proc), test (proc) and n (optional, integer) - # - # setup: must be a proc that takes a client. Client will be pre initialized. - # Setup may initialize stubs (eg `client.stub_responses(:operation, [...])`) - # Setup MUST also return a hash with the request used in the test. - # This is done to avoid the cost of creating the argument in each run of the test. - # - # test: a proc that takes a client and request (generated from calling the setup proc) - def operation_benchmarks; end - - # build the gem from its gemspec, then get the file size on disc - # done within a temp directory to prevent accumulation of .gem artifacts - def benchmark_gem_size(report_data) - Dir.mktmpdir('ruby-sdk-benchmark') do |tmpdir| - Dir.chdir(gem_dir) do - `gem build #{gem_name}.gemspec -o #{tmpdir}/#{gem_name}.gem` - report_data['gem_size_kb'] = - File.size("#{tmpdir}/#{gem_name}.gem") / 1024.0 - report_data['gem_version'] = File.read('VERSION').strip - end - end - end - - # benchmark requiring a gem - runs in a forked process (when supported) - # to ensure state of parent process is not modified by the require - # For accurate results, should be run before any SDK gems are required - # in the parent process - def benchmark_require(report_data) - return unless gem_name - - report_data.merge!(Benchmark.fork_run do |out| - t1 = Benchmark.monotonic_milliseconds - require gem_name - out[:require_time_ms] = (Benchmark.monotonic_milliseconds - t1) - end) - - report_data.merge!(Benchmark.fork_run do |out| - unless defined?(JRUBY_VERSION) - r = ::MemoryProfiler.report { require gem_name } - out[:require_mem_retained_kb] = r.total_retained_memsize / 1024.0 - out[:require_mem_allocated_kb] = r.total_allocated_memsize / 1024.0 - end - end) - end - - # benchmark creating a client - runs in a forked process (when supported) - # For accurate results, should be run before the client is initialized - # in the parent process to ensure cache is clean - def benchmark_client(report_data) - return unless client_module_name - - report_data.merge!(Benchmark.fork_run do |out| - require gem_name - client_klass = Kernel.const_get(client_module_name).const_get(:Client) - unless defined?(JRUBY_VERSION) - r = ::MemoryProfiler.report do - client_klass.new(stub_responses: true) - end - out[:client_mem_retained_kb] = r.total_retained_memsize / 1024.0 - out[:client_mem_allocated_kb] = r.total_allocated_memsize / 1024.0 - end - end) - end - - # This runs in the main process and requires service gems. - # It MUST be done after ALL testing of gem loads/client creates - def benchmark_operations(report_data) - return unless gem_name && client_module_name && operation_benchmarks - - require gem_name - - client_klass = Kernel.const_get(client_module_name).const_get(:Client) - - report_data[:client_init_ms] = Benchmark.measure_time(300) do - client_klass.new(stub_responses: true) - end - - values = report_data[:client_init_ms] - ms = format('%.2f', (values.sum(0.0) / values.size)) - puts "\t\t#{gem_name} client init avg: #{ms} ms" - - operation_benchmarks.each do |test_name, test_def| - client = client_klass.new(stub_responses: true) - req = test_def[:setup].call(client) - - # warmup (run a few iterations without measurement) - 2.times { test_def[:test].call(client, req) } - - mem_allocated = 0 - unless defined?(JRUBY_VERSION) - r = ::MemoryProfiler.report { test_def[:test].call(client, req) } - mem_allocated = report_data["#{test_name}_allocated_kb"] = - r.total_allocated_memsize / 1024.0 - end - - n = test_def[:n] || 300 - values = Benchmark.measure_time(n) do - test_def[:test].call(client, req) - end - report_data["#{test_name}_ms"] = values - ms = format('%.2f', (values.sum(0.0) / values.size)) - puts "\t\t#{test_name} avg: #{ms} ms\t" \ - "mem_allocated: #{'%.2f' % mem_allocated} kb" - end - end - - def self.descendants - descendants = [] - ObjectSpace.each_object(singleton_class) do |k| - next if k.singleton_class? - - descendants.unshift k unless k == self - end - descendants - end - end -end - -# require all gem benchmarks -Dir[File.join(__dir__, 'gems', '*.rb')].each { |file| require file } diff --git a/tasks/benchmark/metrics.rb b/tasks/benchmark/metrics.rb deleted file mode 100644 index 98556b048..000000000 --- a/tasks/benchmark/metrics.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Benchmark - module Metrics - # put metrics generated from `run_benchmarks` to cloudwatch - def self.put_metric(client:, dims:, timestamp:, metric_name:, value:) - return unless value.is_a?(Numeric) || value.is_a?(Array) - - # attempt to determine unit - unit_suffix = metric_name.split('_').last - unit = { - 'kb' => 'Kilobytes', - 'b' => 'Bytes', - 's' => 'Seconds', - 'ms' => 'Milliseconds' - }.fetch(unit_suffix, 'None') - - metric_data = { - metric_name: metric_name, - timestamp: timestamp, - unit: unit, - dimensions: dims.map { |k, v| { name: k.to_s, value: v } } - } - - case value - when Numeric - metric_data[:value] = value - client.put_metric_data(namespace: 'hearth-performance', metric_data: [metric_data]) - when Array - # cloudwatch has a limit of 150 values - value.each_slice(150) do |values| - metric_data[:values] = values - client.put_metric_data(namespace: 'hearth-performance', metric_data: [metric_data]) - end - else - raise 'Unknown type for metric value' - end - end - end -end diff --git a/tasks/benchmark/test_data.rb b/tasks/benchmark/test_data.rb deleted file mode 100644 index bd3c8868a..000000000 --- a/tasks/benchmark/test_data.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module Benchmark - module TestData - # generate predictable, but variable test values of different types - def self.random_value(i = 0, seed = 0) - r = Random.new(i + seed) # use the index as the seed for predictable results - case random_number(r, 5) - when 0 then "Some String value #{i}" - when 1 then r.rand # a float value - when 2 then random_number(r, 100_000) # a large integer - when 3 then (0..random_number(r, 100)).to_a # an array - when 4 then { a: 1, b: 2, c: 3 } # a hash - else - 'generic string' - end - end - - # generate a predictable, but variable hash with a range of data types - def self.test_hash(n_keys = 5, seed = 0) - n_keys.times.to_h { |i| ["key#{i}", random_value(i, seed)] } - end - - def self.random_number(r, n) - (r.rand * n).floor - end - end -end diff --git a/tasks/benchmark/gems/hearth.rb b/tasks/gems/hearth.rb similarity index 55% rename from tasks/benchmark/gems/hearth.rb rename to tasks/gems/hearth.rb index fe5bc0403..4ee34bc2b 100644 --- a/tasks/benchmark/gems/hearth.rb +++ b/tasks/gems/hearth.rb @@ -1,5 +1,11 @@ # frozen_string_literal: true +# Modify load path to include codegen gems from build directories +projections = 'codegen/*/build/smithyprojections/**/ruby-codegen/*/lib' +Dir.glob(projections) do |gem_path| + $LOAD_PATH.unshift(File.expand_path(gem_path)) +end + module Benchmark module Gems class Hearth < Benchmark::Gem diff --git a/tasks/benchmark/gems/rails_json.rb b/tasks/gems/rails_json.rb similarity index 99% rename from tasks/benchmark/gems/rails_json.rb rename to tasks/gems/rails_json.rb index 37aec9df4..51f605afe 100644 --- a/tasks/benchmark/gems/rails_json.rb +++ b/tasks/gems/rails_json.rb @@ -12,7 +12,7 @@ def gem_dir end def client_module_name - :RailsJson + 'RailsJson' end def operation_benchmarks diff --git a/tasks/benchmark/gems/white_label.rb b/tasks/gems/white_label.rb similarity index 97% rename from tasks/benchmark/gems/white_label.rb rename to tasks/gems/white_label.rb index a58352b9c..68cd154be 100644 --- a/tasks/benchmark/gems/white_label.rb +++ b/tasks/gems/white_label.rb @@ -12,7 +12,7 @@ def gem_dir end def client_module_name - :WhiteLabel + 'WhiteLabel' end def operation_benchmarks