diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1178ca..e53308c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,48 @@ on: pull_request: jobs: - call-workflow-from-shared-config: - uses: rubyatscale/shared-config/.github/workflows/ci.yml@main - secrets: inherit + rspec: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ruby: + - 3.2 + - 3.3 + - 3.4 + - 4.0 + name: "RSpec tests: Ruby ${{ matrix.ruby }}" + steps: + - uses: actions/checkout@v6 + - name: Set up Ruby ${{ matrix.ruby }} + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + ruby-version: ${{ matrix.ruby }} + - name: Run tests + run: bundle exec rspec + static_type_check: + name: "Type Check" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + ruby-version: 3.3 + - name: Run static type checks + run: bundle exec srb tc + notify_on_failure: + runs-on: ubuntu-latest + needs: [rspec, static_type_check] + if: ${{ failure() && github.ref == 'refs/heads/main' }} + steps: + - uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ github.repository }}/${{ github.ref }}: FAILED\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } diff --git a/Gemfile.lock b/Gemfile.lock index a8506a9..7c5ae3a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,106 +1,137 @@ PATH remote: . specs: - packs-specification (0.0.10) + packs-specification (0.0.11) sorbet-runtime GEM remote: https://rubygems.org/ specs: - ast (2.4.2) - coderay (1.1.3) - diff-lcs (1.5.0) - json (2.7.2) - language_server-protocol (3.17.0.3) - method_source (1.0.0) + ast (2.4.3) + benchmark (0.5.0) + date (3.5.1) + debug (1.11.1) + irb (~> 1.10) + reline (>= 0.3.8) + diff-lcs (1.6.2) + erb (6.0.1) + erubi (1.13.1) + io-console (0.8.2) + irb (1.16.0) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.18.1) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.7.0) netrc (0.11.0) - parallel (1.24.0) - parser (3.3.1.0) + parallel (1.27.0) + parser (3.3.10.1) ast (~> 2.4.1) racc - pry (0.14.1) - coderay (~> 1.1) - method_source (~> 1.0) - racc (1.7.3) + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.9.0) + psych (5.3.1) + date + stringio + racc (1.8.1) rainbow (3.1.1) - rake (13.0.6) - rbi (0.0.16) - ast - parser (>= 2.6.4.0) - sorbet-runtime (>= 0.5.9204) - unparser - regexp_parser (2.9.1) - rexml (3.2.6) - rspec (3.11.0) - rspec-core (~> 3.11.0) - rspec-expectations (~> 3.11.0) - rspec-mocks (~> 3.11.0) - rspec-core (3.11.0) - rspec-support (~> 3.11.0) - rspec-expectations (3.11.1) + rake (13.3.1) + rbi (0.3.9) + prism (~> 1.0) + rbs (>= 3.4.4) + rbs (4.0.0.dev.5) + logger + prism (>= 1.3.0) + tsort + rdoc (7.1.0) + erb + psych (>= 4.0.0) + tsort + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) + require-hooks (0.2.3) + rexml (3.4.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.1) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-support (3.11.1) - rubocop (1.63.5) + rspec-support (~> 3.13.0) + rspec-support (3.13.7) + rubocop (1.84.1) json (~> 2.3) - language_server-protocol (>= 3.17.0) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.31.1, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.3) - parser (>= 3.3.1.0) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.0) + parser (>= 3.3.7.2) + prism (~> 1.7) ruby-progressbar (1.13.0) - sorbet (0.5.11370) - sorbet-static (= 0.5.11370) - sorbet-runtime (0.5.11370) - sorbet-static (0.5.11370-universal-darwin) - sorbet-static (0.5.11370-x86_64-linux) - sorbet-static-and-runtime (0.5.11370) - sorbet (= 0.5.11370) - sorbet-runtime (= 0.5.11370) - spoom (1.1.12) - sorbet (>= 0.5.9204) - sorbet-runtime (>= 0.5.9204) + sorbet (0.6.12913) + sorbet-static (= 0.6.12913) + sorbet-runtime (0.6.12913) + sorbet-static (0.6.12913-universal-darwin) + sorbet-static (0.6.12913-x86_64-linux) + sorbet-static-and-runtime (0.6.12913) + sorbet (= 0.6.12913) + sorbet-runtime (= 0.6.12913) + spoom (1.7.11) + erubi (>= 1.10.0) + prism (>= 0.28.0) + rbi (>= 0.3.3) + rbs (>= 4.0.0.dev.4) + rexml (>= 3.2.6) + sorbet-static-and-runtime (>= 0.5.10187) thor (>= 0.19.2) - tapioca (0.10.2) - bundler (>= 1.17.3) + stringio (3.2.0) + tapioca (0.17.10) + benchmark + bundler (>= 2.2.25) netrc (>= 0.11.0) parallel (>= 1.21.0) - pry (>= 0.12.2) - rbi (~> 0.0.0, >= 0.0.14) - sorbet-static-and-runtime (>= 0.5.9204) - spoom (~> 1.1.0, >= 1.1.11) + rbi (>= 0.3.7) + require-hooks (>= 0.2.2) + sorbet-static-and-runtime (>= 0.5.11087) + spoom (>= 1.7.9) thor (>= 1.2.0) yard-sorbet - thor (1.2.1) - unicode-display_width (2.5.0) - unparser (0.6.5) - diff-lcs (~> 1.3) - parser (>= 3.1.0) - webrick (1.7.0) - yard (0.9.28) - webrick (~> 1.7.0) - yard-sorbet (0.7.0) - sorbet-runtime (>= 0.5) - yard (>= 0.9) + thor (1.5.0) + tsort (0.2.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + yard (0.9.38) + yard-sorbet (0.9.0) + sorbet-runtime + yard PLATFORMS universal-darwin x86_64-linux DEPENDENCIES - bundler (~> 2.2.16) + bundler + debug packs-specification! rake - rspec (~> 3.0) + rspec rubocop sorbet tapioca diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..4c1fcd0 --- /dev/null +++ b/bin/console @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler/setup' +require 'packs-specification' +require 'irb' + +IRB.start(__FILE__) diff --git a/lib/packs/pack.rb b/lib/packs/pack.rb index 32f0f51..d0d45ee 100644 --- a/lib/packs/pack.rb +++ b/lib/packs/pack.rb @@ -1,17 +1,29 @@ # typed: strict module Packs - class Pack < T::Struct + class Pack extend T::Sig - const :name, String - const :path, Pathname - const :relative_path, Pathname - const :raw_hash, T::Hash[T.untyped, T.untyped] + sig { returns(String) } + attr_reader :name + + sig { returns(Pathname) } + attr_reader :path + + sig { returns(Pathname) } + attr_reader :relative_path + + sig { params(name: String, path: Pathname, relative_path: Pathname).void } + def initialize(name:, path:, relative_path:) + @name = name + @path = path + @relative_path = relative_path + @raw_hash = T.let(nil, T.nilable(T::Hash[T.untyped, T.untyped])) + @is_gem = T.let(nil, T.nilable(T::Boolean)) + end sig { params(package_yml_absolute_path: Pathname).returns(Pack) } def self.from(package_yml_absolute_path) - package_loaded_yml = YAML.load_file(package_yml_absolute_path) path = package_yml_absolute_path.dirname relative_path = path.relative_path_from(Specification.root) package_name = relative_path.cleanpath.to_s @@ -19,11 +31,15 @@ def self.from(package_yml_absolute_path) Pack.new( name: package_name, path: path, - relative_path: relative_path, - raw_hash: package_loaded_yml || {} + relative_path: relative_path ) end + sig { returns(T::Hash[T.untyped, T.untyped]) } + def raw_hash + @raw_hash ||= YAML.load_file(yml(relative: false)) || {} + end + sig { params(relative: T::Boolean).returns(Pathname) } def yml(relative: true) path_to_use = relative ? relative_path : path @@ -37,12 +53,25 @@ def last_name sig { returns(T::Boolean) } def is_gem? # rubocop:disable Naming/PredicateName - @is_gem ||= T.let(relative_path.glob('*.gemspec').any?, T.nilable(T::Boolean)) + @is_gem ||= relative_path.glob('*.gemspec').any? end sig { returns(T::Hash[T.untyped, T.untyped]) } def metadata raw_hash['metadata'] || {} end + + sig { returns(T::Array[Symbol]) } + private def instance_variables_to_inspect = [:@name] + + if RUBY_VERSION < '4' + sig { returns(String) } + def inspect + ivars = instance_variables_to_inspect.map do |ivar| + "#{ivar}=#{instance_variable_get(ivar).inspect}" + end.join(', ') + "#<#{self.class.name}:0x#{object_id.to_s(16)} #{ivars}>" + end + end end end diff --git a/lib/packs/rspec/support.rb b/lib/packs/rspec/support.rb index 83ca665..4675f06 100644 --- a/lib/packs/rspec/support.rb +++ b/lib/packs/rspec/support.rb @@ -1,3 +1,5 @@ +require 'fileutils' +require 'tmpdir' require_relative 'fixture_helper' RSpec.configure do |config| diff --git a/packs-specification.gemspec b/packs-specification.gemspec index e3b8c66..eb09a09 100644 --- a/packs-specification.gemspec +++ b/packs-specification.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = 'packs-specification' - spec.version = '0.0.10' + spec.version = '0.0.11' spec.authors = ['Gusto Engineers'] spec.email = ['dev@gusto.com'] spec.summary = 'The specification for packs in the `rubyatscale` ecosystem.' @@ -21,13 +21,14 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. spec.files = Dir['README.md', 'lib/**/*'] spec.require_paths = ['lib'] - spec.required_ruby_version = '>= 2.6' + spec.required_ruby_version = '>= 3.1' spec.add_dependency 'sorbet-runtime' - spec.add_development_dependency 'bundler', '~> 2.2.16' + spec.add_development_dependency 'bundler' + spec.add_development_dependency 'debug' spec.add_development_dependency 'rake' - spec.add_development_dependency 'rspec', '~> 3.0' + spec.add_development_dependency 'rspec' spec.add_development_dependency 'rubocop' spec.add_development_dependency 'sorbet' spec.add_development_dependency 'tapioca' diff --git a/spec/packs/pack_spec.rb b/spec/packs/pack_spec.rb index e1403e2..670b12d 100644 --- a/spec/packs/pack_spec.rb +++ b/spec/packs/pack_spec.rb @@ -44,6 +44,65 @@ end end + describe 'lazy loading' do + before do + write_file('packs/my_pack/package.yml', <<~CONTENTS) + enforce_privacy: true + metadata: + owner: MyTeam + CONTENTS + end + + let(:pack) { Packs::Pack.from(Pathname.pwd.join('packs/my_pack/package.yml')) } + + it 'does not load raw_hash until accessed' do + expect(pack.instance_variable_get(:@raw_hash)).to be_nil + end + + it 'loads raw_hash when accessed' do + pack.raw_hash + expect(pack.instance_variable_get(:@raw_hash)).to eq({ 'enforce_privacy' => true, 'metadata' => { 'owner' => 'MyTeam' } }) + end + + it 'loads raw_hash when metadata is accessed' do + pack.metadata + expect(pack.instance_variable_get(:@raw_hash)).not_to be_nil + end + end + + describe '#inspect' do + before do + write_file('packs/my_pack/package.yml', <<~CONTENTS) + enforce_privacy: true + CONTENTS + end + + let(:pack) { Packs::Pack.from(Pathname.pwd.join('packs/my_pack/package.yml')) } + + it 'only includes @name in inspect output' do + expect(pack.inspect).to match(/^#$/) + end + + it 'does not include other instance variables in inspect output' do + expect(pack.inspect).not_to include('@path') + expect(pack.inspect).not_to include('@relative_path') + expect(pack.inspect).not_to include('@raw_hash') + end + end + + describe '#instance_variables_to_inspect', if: RUBY_VERSION >= '4' do + before do + write_file('packs/my_pack/package.yml') + end + + let(:pack) { Packs::Pack.from(Pathname.pwd.join('packs/my_pack/package.yml')) } + + it 'is respected by Kernel#inspect' do + expect(pack.inspect).to include('@name=') + expect(pack.inspect).not_to include('@path=') + end + end + describe '.is_gem?' do let(:subject) { Packs.find('packs/my_pack').is_gem? } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c84a863..f67044a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,7 +2,7 @@ require 'bundler/setup' require 'packs-specification' -require 'pry' +require 'debug' require 'packs/rspec/support'