climate_control 1.1.1 → 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: 1052215413e209a2ce0531a9b293d56f51df36db70688af69ae15e70571a043d
4
- data.tar.gz: ed855bfdddc57fc46532edc296d9a8de6a2a3653f5b148ab10849433aeb61026
3
+ metadata.gz: '00584062039c6b9161ff0e4a1b61ca51e4d8742225fc611180c4effcc43959c5'
4
+ data.tar.gz: 99135f45c57f58d87e17480896045c00eecebd632ed64eaf18bd1964ef7d454d
5
5
  SHA512:
6
- metadata.gz: e292d7055db48aaa3cf664f447c6826d3ccf59cc1142dc71a89e8b8e97dbac26d553ebd2912a1f8b2a15b3f75b71de5f3345244cc0cb2f4dd8970f47b73b12c1
7
- data.tar.gz: 96f9e0f2e8193c29d708f94c1cdced3d7a1b305ae0cb781729895b838be70233a0082f767f840a5d943e5b206cc55539606de156e4b301bab787e29ab45507b4
6
+ metadata.gz: 01ba4ad3241cea796d408f0adb02f8c23b85771d1ce8338eedf973b95228a7cac9978218f08921915918c90a0f8f292dbb38f9401ca29de8d7a6ef3dc76d99b5
7
+ data.tar.gz: 9b20ce4affbcf22b6204016d31b7b54465ad5ba2a143fd2e6578d3dc5d4c80ac167c02c9ccc47d91c2a62441c5ce3037ebb13c445407876c163edaba73eb484d
data/CHANGELOG.md CHANGED
@@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## Unreleased
8
+ ## 1.2.0 / 2022-07-15
9
+
10
+ - Added: `ClimateControl.unsafe_modify` for a thread-unsafe version of
11
+ `ClimateControl.modify` (useful for minitest-around for instance)
12
+ - Deprecates `ClimateControl.env`, `ENV` should be used instead
13
+
14
+ ## 1.1.1 / 2022-05-28
15
+
16
+ - Fixed: ENV was not restored if an error was thrown when assigning ENV
9
17
 
10
18
  ## 1.1.0 / 2022-05-26
11
19
 
@@ -1,3 +1,3 @@
1
1
  module ClimateControl
2
- VERSION = "1.1.1".freeze
2
+ VERSION = "1.2.0".freeze
3
3
  end
@@ -4,6 +4,7 @@ require "monitor"
4
4
 
5
5
  module ClimateControl
6
6
  extend self
7
+ extend Gem::Deprecate
7
8
 
8
9
  SEMAPHORE = Monitor.new
9
10
  private_constant :SEMAPHORE
@@ -31,10 +32,33 @@ module ClimateControl
31
32
  end
32
33
  end
33
34
 
35
+ def unsafe_modify(environment_overrides = {}, &block)
36
+ environment_overrides = environment_overrides.transform_keys(&:to_s)
37
+
38
+ previous = ENV.to_hash
39
+
40
+ begin
41
+ copy environment_overrides
42
+ ensure
43
+ middle = ENV.to_hash
44
+ end
45
+
46
+ block.call
47
+ ensure
48
+ after = ENV
49
+ (previous.keys | middle.keys | after.keys).each do |key|
50
+ if previous[key] != after[key] && middle[key] == after[key]
51
+ ENV[key] = previous[key]
52
+ end
53
+ end
54
+ end
55
+
34
56
  def env
35
57
  ENV
36
58
  end
37
59
 
60
+ deprecate :env, "ENV", 2022, 10
61
+
38
62
  private
39
63
 
40
64
  def copy(overrides)
@@ -5,7 +5,7 @@ Thing = Class.new
5
5
  describe "Climate control" do
6
6
  it "allows modification of the environment" do
7
7
  block_run = false
8
- ClimateControl.modify FOO: "bar" do
8
+ with_modified_env FOO: "bar" do
9
9
  expect(ENV["FOO"]).to eq "bar"
10
10
  block_run = true
11
11
  end
@@ -182,6 +182,16 @@ describe "Climate control" do
182
182
  expect(ENV["KEY_THAT_WILL_ERROR_OUT"]).to eq("initial_value_2")
183
183
  end
184
184
 
185
+ it "doesn't block on nested modify calls" do
186
+ with_modified_env(SMS_DEFAULT_COUNTRY_CODE: nil) do
187
+ with_modified_env(SMS_DEFAULT_COUNTRY_CODE: "++56") do
188
+ expect(ENV.fetch("SMS_DEFAULT_COUNTRY_CODE", "++41")).to eq("++56")
189
+ end
190
+
191
+ expect(ENV.fetch("SMS_DEFAULT_COUNTRY_CODE", "++41")).to eq("++41")
192
+ end
193
+ end
194
+
185
195
  def with_modified_env(options = {}, &block)
186
196
  ClimateControl.modify(options, &block)
187
197
  end
@@ -0,0 +1,186 @@
1
+ require "spec_helper"
2
+
3
+ describe "ClimateControl#unsafe_modify" do
4
+ it "allows modification of the environment" do
5
+ block_run = false
6
+ with_modified_env FOO: "bar" do
7
+ expect(ENV["FOO"]).to eq "bar"
8
+ block_run = true
9
+ end
10
+
11
+ expect(ENV["FOO"]).to be_nil
12
+ expect(block_run).to be true
13
+ end
14
+
15
+ it "modifies the environment" do
16
+ with_modified_env VARIABLE_1: "bar", VARIABLE_2: "qux" do
17
+ expect(ENV["VARIABLE_1"]).to eq "bar"
18
+ expect(ENV["VARIABLE_2"]).to eq "qux"
19
+ end
20
+
21
+ expect(ENV["VARIABLE_1"]).to be_nil
22
+ expect(ENV["VARIABLE_2"]).to be_nil
23
+ end
24
+
25
+ it "allows for environment variables to be assigned within the block" do
26
+ with_modified_env VARIABLE_1: "modified" do
27
+ ENV["ASSIGNED_IN_BLOCK"] = "assigned"
28
+ end
29
+
30
+ expect(ENV["ASSIGNED_IN_BLOCK"]).to eq "assigned"
31
+ end
32
+
33
+ it "reassigns previously set environment variables" do
34
+ ENV["VARIABLE_ASSIGNED_BEFORE_MODIFYING_ENV"] = "original"
35
+ expect(ENV["VARIABLE_ASSIGNED_BEFORE_MODIFYING_ENV"]).to eq "original"
36
+
37
+ with_modified_env VARIABLE_ASSIGNED_BEFORE_MODIFYING_ENV: "overridden" do
38
+ expect(ENV["VARIABLE_ASSIGNED_BEFORE_MODIFYING_ENV"]).to eq "overridden"
39
+ end
40
+
41
+ expect(ENV["VARIABLE_ASSIGNED_BEFORE_MODIFYING_ENV"]).to eq "original"
42
+ end
43
+
44
+ it "persists the change when overriding the variable in the block" do
45
+ with_modified_env VARIABLE_MODIFIED_AND_THEN_ASSIGNED: "modified" do
46
+ ENV["VARIABLE_MODIFIED_AND_THEN_ASSIGNED"] = "assigned value"
47
+ end
48
+
49
+ expect(ENV["VARIABLE_MODIFIED_AND_THEN_ASSIGNED"]).to eq "assigned value"
50
+ end
51
+
52
+ it "resets environment variables even if the block raises" do
53
+ expect {
54
+ with_modified_env FOO: "bar" do
55
+ raise "broken"
56
+ end
57
+ }.to raise_error("broken")
58
+
59
+ expect(ENV["FOO"]).to be_nil
60
+ end
61
+
62
+ it "preserves environment variables set within the block" do
63
+ ENV["CHANGED"] = "old value"
64
+
65
+ with_modified_env IRRELEVANT: "ignored value" do
66
+ ENV["CHANGED"] = "new value"
67
+ end
68
+
69
+ expect(ENV["CHANGED"]).to eq "new value"
70
+ end
71
+
72
+ it "returns the value of the block" do
73
+ value = with_modified_env VARIABLE_1: "bar" do
74
+ "value inside block"
75
+ end
76
+
77
+ expect(value).to eq "value inside block"
78
+ end
79
+
80
+ it "handles threads correctly" do
81
+ # failure path without mutex
82
+ # [thread_removing_env] BAZ is assigned
83
+ # 0.25s passes
84
+ # [other_thread] FOO is assigned and ENV is copied (which includes BAZ)
85
+ # 0.25s passes
86
+ # [thread_removing_env] thread resolves and BAZ is removed from env; other_thread still retains knowledge of BAZ
87
+ # 0.25s passes
88
+ # [other_thread] thread resolves, FOO is removed, BAZ is copied back to ENV
89
+
90
+ thread_removing_env = Thread.new {
91
+ with_modified_env BAZ: "buzz" do
92
+ sleep 0.5
93
+ end
94
+
95
+ expect(ENV["BAZ"]).to be_nil
96
+ }
97
+
98
+ other_thread = Thread.new {
99
+ sleep 0.25
100
+ with_modified_env FOO: "bar" do
101
+ sleep 0.5
102
+ end
103
+
104
+ expect(ENV["FOO"]).to be_nil
105
+ }
106
+
107
+ thread_removing_env.join
108
+ other_thread.join
109
+
110
+ expect(ENV["FOO"]).to be_nil
111
+ expect(ENV["BAZ"]).to be_nil
112
+ end
113
+
114
+ it "is re-entrant" do
115
+ ret = with_modified_env(FOO: "foo") {
116
+ with_modified_env(BAR: "bar") do
117
+ "bar"
118
+ end
119
+ }
120
+
121
+ expect(ret).to eq("bar")
122
+
123
+ expect(ENV["FOO"]).to be_nil
124
+ expect(ENV["BAR"]).to be_nil
125
+ end
126
+
127
+ it "raises when the value cannot be assigned properly" do
128
+ message = generate_type_error_for_object(Thing.new)
129
+
130
+ expect {
131
+ with_modified_env(FOO: Thing.new)
132
+ }.to raise_error ClimateControl::UnassignableValueError, /attempted to assign .*Thing.* to FOO but failed \(#{message}\)$/
133
+ end
134
+
135
+ it "restores the ENV even when an error was raised when assigning values" do
136
+ ENV["KEY_TO_OVERRIDE"] = "initial_value_1"
137
+ ENV["KEY_THAT_WILL_ERROR_OUT"] = "initial_value_2"
138
+
139
+ expect {
140
+ with_modified_env(
141
+ KEY_TO_OVERRIDE: "overwriten_value_1",
142
+ KEY_THAT_WILL_ERROR_OUT: :value_that_will_error_out
143
+ ) {}
144
+ }.to raise_error ClimateControl::UnassignableValueError
145
+
146
+ expect(ENV["KEY_TO_OVERRIDE"]).to eq("initial_value_1")
147
+ expect(ENV["KEY_THAT_WILL_ERROR_OUT"]).to eq("initial_value_2")
148
+ end
149
+
150
+ it "doesn't block on nested modify calls" do
151
+ with_modified_env(SMS_DEFAULT_COUNTRY_CODE: nil) do
152
+ with_modified_env(SMS_DEFAULT_COUNTRY_CODE: "++56") do
153
+ expect(ENV.fetch("SMS_DEFAULT_COUNTRY_CODE", "++41")).to eq("++56")
154
+ end
155
+
156
+ expect(ENV.fetch("SMS_DEFAULT_COUNTRY_CODE", "++41")).to eq("++41")
157
+ end
158
+ end
159
+
160
+ def with_modified_env(options = {}, &block)
161
+ ClimateControl.unsafe_modify(options, &block)
162
+ end
163
+
164
+ def generate_type_error_for_object(object)
165
+ message = nil
166
+
167
+ begin
168
+ "1" + object
169
+ rescue TypeError => e
170
+ message = e.message
171
+ end
172
+
173
+ message
174
+ end
175
+
176
+ around do |example|
177
+ old_env = ENV.to_hash
178
+
179
+ example.run
180
+
181
+ ENV.clear
182
+ old_env.each do |key, value|
183
+ ENV[key] = value
184
+ end
185
+ end
186
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: climate_control
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Clayton
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-05-28 00:00:00.000000000 Z
12
+ date: 2022-07-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -89,6 +89,7 @@ files:
89
89
  - lib/climate_control/errors.rb
90
90
  - lib/climate_control/version.rb
91
91
  - spec/acceptance/climate_control_spec.rb
92
+ - spec/acceptance/unsafe_modify_spec.rb
92
93
  - spec/spec_helper.rb
93
94
  homepage: https://github.com/thoughtbot/climate_control
94
95
  licenses:
@@ -115,4 +116,5 @@ specification_version: 4
115
116
  summary: Modify your ENV easily with ClimateControl
116
117
  test_files:
117
118
  - spec/acceptance/climate_control_spec.rb
119
+ - spec/acceptance/unsafe_modify_spec.rb
118
120
  - spec/spec_helper.rb