From 4a17be48a6c72a2dbeb7ac91e02c616ae52f69b1 Mon Sep 17 00:00:00 2001 From: Renato Arruda Date: Sat, 8 Oct 2022 01:20:51 +0200 Subject: [PATCH 1/4] feat: add experimental RBS support, TODO: - add rbs to gemspec - add rbs tests to gha - add rbs tests - consider checking using steep / Steepfile --- Steepfile | 28 +++++++++++++++++++++ lib/unleash/client.rbs | 39 ++++++++++++++++++++++++++++++ lib/unleash/context.rbs | 21 ++++++++++++++++ lib/unleash/scheduled_executor.rbs | 29 ++++++++++++++++++++++ lib/unleash/variant.rbs | 15 ++++++++++++ 5 files changed, 132 insertions(+) create mode 100644 Steepfile create mode 100644 lib/unleash/client.rbs create mode 100644 lib/unleash/context.rbs create mode 100644 lib/unleash/scheduled_executor.rbs create mode 100644 lib/unleash/variant.rbs diff --git a/Steepfile b/Steepfile new file mode 100644 index 00000000..2bd8b029 --- /dev/null +++ b/Steepfile @@ -0,0 +1,28 @@ +# D = Steep::Diagnostic +# +target :lib do + signature "sig" + + # check "lib/unleash/client.rbs" # Directory name + check "lib" # Directory name + # check "Gemfile" # File name + # check "app/models/**/*.rb" # Glob + # ignore "lib/templates/*.rb" + + # library "pathname", "set" # Standard libraries + # library "strong_json" # Gems + + # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting + # configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting + # configure_code_diagnostics do |hash| # You can setup everything yourself + # hash[D::Ruby::NoMethod] = :information + # end +end + +# target :test do +# # signature "sig", "sig-private" +# # +# check "spec" +# # +# # # library "pathname", "set" # Standard libraries +# end diff --git a/lib/unleash/client.rbs b/lib/unleash/client.rbs new file mode 100644 index 00000000..b3e17d9f --- /dev/null +++ b/lib/unleash/client.rbs @@ -0,0 +1,39 @@ +module Unleash + class Client + attr_accessor fetcher_scheduled_executor: ScheduledExecutor + + attr_accessor metrics_scheduled_executor: ScheduledExecutor + + def initialize: (*untyped opts) -> void + + def is_enabled?: (untyped feature, ?Context? context, ?bool default_value_param) ?{ () -> bool } -> bool + + # enabled? is a more ruby idiomatic method name than is_enabled? + alias enabled? is_enabled? + + # execute a code block (passed as a parameter), if is_enabled? is true. + def if_enabled: (untyped feature, ?Context context, ?bool default_value) { (untyped) -> bool } -> (untyped | nil) + + def get_variant: (untyped feature, ?Context context, ?Variant fallback_variant) -> Variant + + # safe shutdown: also flush metrics to server and toggles to disk + def shutdown: () -> nil + + # quick shutdown: just kill running threads + def shutdown!: () -> nil + + private + + def info: () -> { appName: String, instanceId: String, sdkVersion: String, strategies: Array[String], started: String, interval: Integer } + + def start_toggle_fetcher: () -> nil + + def start_metrics: () -> nil + + def register: () -> nil + + def disabled_variant: () -> Variant + + def first_fetch_is_eager: () -> bool + end +end diff --git a/lib/unleash/context.rbs b/lib/unleash/context.rbs new file mode 100644 index 00000000..3c544d73 --- /dev/null +++ b/lib/unleash/context.rbs @@ -0,0 +1,21 @@ +module Unleash + class Context + ATTRS: ::Array[:app_name | :environment | :user_id | :session_id | :remote_address | :current_time] + + def initialize: (?::Hash[String, any] params) -> Context + + def to_s: () -> ::String + + def get_by_name: ((String | Symbol) name) -> any + + def include?: ((String | Symbol) name) -> bool + + private + + # Method to fetch values from hash for two types of keys: string in camelCase and symbol in snake_case + def value_for: ((String | Symbol) key, Hash[String, any] params, ?any? default_value) -> any + + # converts CamelCase to snake_case + def underscore: ((String | Symbol) camel_cased_word) -> String + end +end diff --git a/lib/unleash/scheduled_executor.rbs b/lib/unleash/scheduled_executor.rbs new file mode 100644 index 00000000..6751642f --- /dev/null +++ b/lib/unleash/scheduled_executor.rbs @@ -0,0 +1,29 @@ +module Unleash + class ScheduledExecutor + attr_accessor name: String + + attr_accessor interval: Numeric + + attr_accessor max_exceptions: ::Integer + + attr_accessor retry_count: ::Integer + + attr_accessor thread: (Thread | nil) + + attr_accessor immediate_execution: bool + + def initialize: (String name, untyped interval, ?::Integer max_exceptions, ?bool immediate_execution) -> void + + def run: () ?{ () -> nil } -> nil + + def running?: () -> bool + + def exit: () -> nil + + private + + def run_blk: () { () -> nil } -> nil + + def exceeded_max_exceptions?: () -> bool + end +end diff --git a/lib/unleash/variant.rbs b/lib/unleash/variant.rbs new file mode 100644 index 00000000..43486f0b --- /dev/null +++ b/lib/unleash/variant.rbs @@ -0,0 +1,15 @@ +module Unleash + class Variant + attr_accessor name: String + + attr_accessor enabled: bool + + attr_accessor payload: String + + def initialize: (?::Hash[String, String] params) -> void + + def to_s: () -> ::String + + def ==: (Variant other) -> bool + end +end From f98fe6b2809de99844f2b1b2a991f4a53e85f488 Mon Sep 17 00:00:00 2001 From: Renato Arruda Date: Tue, 15 Nov 2022 22:54:32 +0100 Subject: [PATCH 2/4] Add steep infrastructure for testing type signatures - added steep and rbs to gemspec - added type signature tests to github actions - updated Steepfile --- .github/workflows/pull_request.yml | 4 +++ Steepfile | 41 ++++++++++++++++-------------- lib/unleash/client.rb | 2 +- unleash-client.gemspec | 3 +++ 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index e962a28d..d868035e 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -18,6 +18,10 @@ jobs: bundler-cache: true - name: Run RuboCop run: bundle exec rubocop + - name: Run Steep / RBS Type signature verification + run: | + steep check --steepfile=./Steepfile --severity-level=error | ruby -pe 'sub(/^(.+):(\d+):(\d+): (.+)$/, %q{::error file=\1,line=\2,col=\3::\4})' + shell: bash test: runs-on: ${{ matrix.os }}-latest diff --git a/Steepfile b/Steepfile index 2bd8b029..71a612e3 100644 --- a/Steepfile +++ b/Steepfile @@ -1,28 +1,31 @@ -# D = Steep::Diagnostic -# +D = Steep::Diagnostic + target :lib do - signature "sig" + signature "*.rbs" + + check "lib/unleash/client.rb" + check "lib/unleash/context.rb" + check "lib/unleash/scheduled_executor.rb" - # check "lib/unleash/client.rbs" # Directory name - check "lib" # Directory name - # check "Gemfile" # File name - # check "app/models/**/*.rb" # Glob - # ignore "lib/templates/*.rb" + ignore "lib/unleash/bootstrap" + ignore "lib/unleash/strategy/*.rb" + ignore "lib/unleash/util" + + ignore "lib/unleash/constraint.rb" + ignore "lib/unleash/feature_toggle.rb" + ignore "lib/unleash/metrics.rb" + ignore "lib/unleash/metrics_reporter.rb" + ignore "lib/unleash/toggle_fetcher.rb" + ignore "lib/unleash/variant_definition.rb" + ignore "lib/unleash/variant_override.rb" # library "pathname", "set" # Standard libraries # library "strong_json" # Gems # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting # configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting - # configure_code_diagnostics do |hash| # You can setup everything yourself - # hash[D::Ruby::NoMethod] = :information - # end + configure_code_diagnostics do |hash| # You can setup everything yourself + hash[D::Ruby::NoMethod] = :information + hash[D::Ruby::UnsupportedSyntax] = :information + end end - -# target :test do -# # signature "sig", "sig-private" -# # -# check "spec" -# # -# # # library "pathname", "set" # Standard libraries -# end diff --git a/lib/unleash/client.rb b/lib/unleash/client.rb index c88024fe..2abbbacd 100644 --- a/lib/unleash/client.rb +++ b/lib/unleash/client.rb @@ -162,7 +162,7 @@ def register end def disabled_variant - @disabled_variant ||= Unleash::FeatureToggle.disabled_variant + Unleash::FeatureToggle.disabled_variant end def first_fetch_is_eager diff --git a/unleash-client.gemspec b/unleash-client.gemspec index ec156341..c05b6811 100644 --- a/unleash-client.gemspec +++ b/unleash-client.gemspec @@ -39,4 +39,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "simplecov", "~> 0.21.2" spec.add_development_dependency "simplecov-lcov", "~> 0.8.0" + + spec.add_development_dependency "rbs", "~> 2.7" + spec.add_development_dependency "steep", "~> 1.2" end From c4b87284dbad503b80594d72e213d5ec2d2aa804 Mon Sep 17 00:00:00 2001 From: Renato Arruda Date: Tue, 15 Nov 2022 23:01:20 +0100 Subject: [PATCH 3/4] fix gha --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d868035e..7d27c7ce 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -20,7 +20,7 @@ jobs: run: bundle exec rubocop - name: Run Steep / RBS Type signature verification run: | - steep check --steepfile=./Steepfile --severity-level=error | ruby -pe 'sub(/^(.+):(\d+):(\d+): (.+)$/, %q{::error file=\1,line=\2,col=\3::\4})' + bundle exec steep check --steepfile=./Steepfile --severity-level=error | ruby -pe 'sub(/^(.+):(\d+):(\d+): (.+)$/, %q{::error file=\1,line=\2,col=\3::\4})' shell: bash test: From 891a3967e54b84a0f57f8343e714513ab975a4d8 Mon Sep 17 00:00:00 2001 From: Renato Arruda Date: Tue, 15 Nov 2022 23:10:40 +0100 Subject: [PATCH 4/4] fix gemspec - appease rubocop - more fixes apparently i don't run all tests in all platforms locally --- Steepfile | 4 ++++ unleash-client.gemspec | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Steepfile b/Steepfile index 71a612e3..88ceffb9 100644 --- a/Steepfile +++ b/Steepfile @@ -3,10 +3,14 @@ D = Steep::Diagnostic target :lib do signature "*.rbs" + # NOTE: All client-exposed methods/types should now have type signatures / rbs support check "lib/unleash/client.rb" check "lib/unleash/context.rb" + check "lib/unleash/variant.rb" check "lib/unleash/scheduled_executor.rb" + # TODO: add signatures to the rest + # Mostly internal SDK files. ignore "lib/unleash/bootstrap" ignore "lib/unleash/strategy/*.rb" ignore "lib/unleash/util" diff --git a/unleash-client.gemspec b/unleash-client.gemspec index c05b6811..3b53a9b5 100644 --- a/unleash-client.gemspec +++ b/unleash-client.gemspec @@ -40,6 +40,11 @@ Gem::Specification.new do |spec| spec.add_development_dependency "simplecov", "~> 0.21.2" spec.add_development_dependency "simplecov-lcov", "~> 0.8.0" - spec.add_development_dependency "rbs", "~> 2.7" - spec.add_development_dependency "steep", "~> 1.2" + # NOTE: only require rbs/steep in supported ruby versions. In EOL ruby/jruby, just ignore. + # rubocop:disable Gemspec/RubyVersionGlobalsUsage + if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') && RUBY_ENGINE != 'jruby' + spec.add_development_dependency "rbs", "~> 2.8" + spec.add_development_dependency "steep", "~> 1.3" + end + # rubocop:enable Gemspec/RubyVersionGlobalsUsage end