gitlab-experiment 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3305c24f350ec02a3ef81da742a6d108c7aef00623f8b2534f989f06ec4a2ba6
4
- data.tar.gz: 5f9113235e1623a2012ea5e4c451ed41baeb847e5883951b95b44fd6d7fd40fa
3
+ metadata.gz: ea78e416b0b830fe748b29e64d0871d4ece35d99bb6c4142fae1a6b1e2bbfb96
4
+ data.tar.gz: ec16e265a80b00380528ba3d2aee36f1da8ffb13b7941a838dcc73e20671aca4
5
5
  SHA512:
6
- metadata.gz: d7120d36eb59039dbe36eb728990b951b96b7aa118a0a73af32e18809eaed265d03dec4689b55c3118e14a252418c9c84c4e25f7c25573205c6ef5269e5f456f
7
- data.tar.gz: 9233969833c74c432b187721a2b35a38e521fce3679bccf64f879b87c899e6202b0f60b047c5aa57aef10665816f3fd7b3d21f7e403b30424b1227017c9991b2
6
+ metadata.gz: a767ae547fae832e9053ba91779fc0c1ee85bd16bda1d2a1eb41239fe34a01921b2ef734bea5fbe4724c45f70bed2f3cfbec9c68624d4003c9f30493bf50e99b
7
+ data.tar.gz: cabbdd641f2ee46db0be4209e28866eb7f5437f46f60ba23c0fe82dfb16944620f360506a7ddad3f17d2fcbf149c374a876545473afc474b943518fbfb631a7f
data/README.md CHANGED
@@ -669,20 +669,52 @@ end
669
669
 
670
670
  ### Stub helpers
671
671
 
672
- You can stub experiment variant resolution using the `stub_experiments` helper. Pass a hash of experiment names and the variant each should resolve to.
672
+ You can stub experiment variant resolution using the `stub_experiments` helper. The helper supports multiple formats for
673
+ flexibility:
674
+
675
+ **Simple hash format:**
673
676
 
674
677
  ```ruby
675
- it "stubs experiments to resolve to a specific variant" do
678
+ it "stubs experiments using hash format" do
676
679
  stub_experiments(pill_color: :red)
677
680
 
678
681
  experiment(:pill_color) do |e|
679
682
  expect(e).to be_enabled
680
683
  expect(e.assigned.name).to eq('red')
681
684
  end
682
- end
685
+ end
686
+ ```
687
+
688
+ **Hash format with options:**
689
+
690
+ ```ruby
691
+ it "stubs experiments with assigned option" do
692
+ stub_experiments(pill_color: { variant: :red, assigned: true })
693
+
694
+ experiment(:pill_color) do |e|
695
+ expect(e).to be_enabled
696
+ expect(e.assigned.name).to eq('red')
697
+ end
698
+ end
699
+ ```
700
+
701
+ **Mixed formats (symbols and hashes together):**
702
+
703
+ ```ruby
704
+ it "stubs multiple experiments with mixed formats" do
705
+ stub_experiments(
706
+ pill_color: :red,
707
+ hippy: { variant: :free_love, assigned: true },
708
+ yuppie: :financial_success
709
+ )
710
+
711
+ expect(experiment(:pill_color).assigned.name).to eq(:red)
712
+ expect(experiment(:hippy).assigned.name).to eq(:free_love)
713
+ expect(experiment(:yuppie).assigned.name).to eq(:financial_success)
714
+ end
683
715
  ```
684
716
 
685
- In special cases you can use a boolean `true` instead of a variant name. This allows the rollout strategy to resolve the variant however it wants to, but is otherwise just making sure the experiment is considered enabled.
717
+ **Boolean true (allows rollout strategy to assign):**
686
718
 
687
719
  ```ruby
688
720
  it "stubs experiments while allowing the rollout strategy to assign the variant" do
@@ -695,30 +727,36 @@ it "stubs experiments while allowing the rollout strategy to assign the variant"
695
727
  end
696
728
  ```
697
729
 
698
- You can also test the `only_assigned` behavior by stubbing cache states:
730
+ #### Testing `only_assigned` behavior
731
+
732
+ When you use the `assigned: true` option in `stub_experiments`, the `find_variant` method is automatically stubbed
733
+ to return the specified variant. This allows you to test the `only_assigned` behavior:
699
734
 
700
735
  ```ruby
701
- it "tests only_assigned behavior with cached variants" do
702
- # Stub a cached variant to exist
703
- allow_any_instance_of(Gitlab::Experiment).to receive(:find_variant).and_return('red')
704
-
736
+ it "tests only_assigned behavior with a cached variant" do
737
+ stub_experiments(pill_color: { variant: :red, assigned: true })
738
+
705
739
  experiment_instance = experiment(:pill_color, actor: user, only_assigned: true)
706
-
740
+
707
741
  expect(experiment_instance).not_to be_excluded
708
742
  expect(experiment_instance.run).to eq('red')
709
743
  end
710
744
 
711
- it "tests only_assigned behavior without cached variants" do
712
- # Stub no cached variant
713
- allow_any_instance_of(Gitlab::Experiment).to receive(:find_variant).and_return(nil)
714
-
745
+ it "tests only_assigned behavior without a cached variant" do
746
+ stub_experiments(pill_color: :red)
747
+
715
748
  experiment_instance = experiment(:pill_color, actor: user, only_assigned: true)
716
-
749
+
717
750
  expect(experiment_instance).to be_excluded
718
- expect(experiment_instance.run).to eq('blue') # control behavior
751
+ expect(experiment_instance.run).to eq('red')
719
752
  end
720
753
  ```
721
754
 
755
+ **Note:** The `assigned: true` option only works correctly when caching is disabled. When caching is enabled,
756
+ `find_variant` will attempt to read from the actual cache store rather than using the stub. In this case, you can
757
+ populate the cache naturally by running the experiment first to assign and cache a variant before testing with
758
+ `only_assigned: true`.
759
+
722
760
  ### Registered behaviors matcher
723
761
 
724
762
  It's useful to test our registered behaviors, as well as their return values when we implement anything complex in them. The `register_behavior` matcher is useful for this.
@@ -6,7 +6,7 @@ module Gitlab
6
6
  autoload :Trackable, 'gitlab/experiment/test_behaviors/trackable.rb'
7
7
  end
8
8
 
9
- WrappedExperiment = Struct.new(:klass, :experiment_name, :variant_name, :expectation_chain, :blocks)
9
+ WrappedExperiment = Struct.new(:klass, :experiment_name, :variant_name, :expectation_chain, :blocks, :assigned)
10
10
 
11
11
  module RSpecMocks
12
12
  @__gitlab_experiment_receivers = {}
@@ -44,6 +44,12 @@ module Gitlab
44
44
  # Call the original method if we specified simply `true`.
45
45
  wrapped.variant_name == true ? method.call : wrapped.variant_name
46
46
  }
47
+
48
+ # Stub find_variant only if caching is not enabled
49
+ unless Configuration.cache
50
+ variant_return_value = wrapped.assigned ? wrapped.variant_name.to_s : nil
51
+ allow(instance).to receive(:find_variant).and_return(variant_return_value)
52
+ end
47
53
  end
48
54
  end
49
55
 
@@ -51,11 +57,11 @@ module Gitlab
51
57
  end
52
58
 
53
59
  def wrapped_experiment(experiment, remock: false, &block)
54
- klass, experiment_name, variant_name = *extract_experiment_details(experiment)
60
+ klass, experiment_name, variant_name, assigned = *extract_experiment_details(experiment)
55
61
 
56
62
  wrapped_experiment = wrapped_experiments[experiment_name] =
57
63
  (!remock && wrapped_experiments[experiment_name]) ||
58
- WrappedExperiment.new(klass, experiment_name, variant_name, wrapped_experiment_chain_for(klass), [])
64
+ WrappedExperiment.new(klass, experiment_name, variant_name, wrapped_experiment_chain_for(klass), [], assigned)
59
65
 
60
66
  wrapped_experiment.blocks << block if block
61
67
  wrapped_experiment
@@ -84,9 +90,16 @@ module Gitlab
84
90
  def extract_experiment_details(experiment)
85
91
  experiment_name = nil
86
92
  variant_name = nil
87
-
88
- experiment_name = experiment if experiment.is_a?(Symbol)
89
- experiment_name, variant_name = *experiment if experiment.is_a?(Array)
93
+ assigned = nil
94
+
95
+ if experiment.is_a?(Array)
96
+ # From normalize_experiments: [experiment_name, variant_name_or_config]
97
+ experiment_name, variant_name = *experiment
98
+ assigned = variant_name.is_a?(Hash) ? variant_name.delete(:assigned) : nil
99
+ variant_name = variant_name[:variant] if variant_name.is_a?(Hash)
100
+ elsif experiment.is_a?(Symbol)
101
+ experiment_name = experiment
102
+ end
90
103
 
91
104
  base_klass = Configuration.base_class.constantize
92
105
  variant_name = experiment.assigned.name if experiment.is_a?(base_klass)
@@ -94,7 +107,7 @@ module Gitlab
94
107
  resolved_klass = experiment_klass(experiment) { base_klass.constantize(experiment_name) }
95
108
  experiment_name ||= experiment.instance_variable_get(:@_name)
96
109
 
97
- [resolved_klass, experiment_name.to_s, variant_name]
110
+ [resolved_klass, experiment_name.to_s, variant_name, assigned]
98
111
  end
99
112
 
100
113
  def experiment_klass(experiment, &block)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  class Experiment
5
- VERSION = '1.1.0'
5
+ VERSION = '1.2.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-experiment
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitLab
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-27 00:00:00.000000000 Z
11
+ date: 2025-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport