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 +4 -4
- data/CHANGELOG.md +9 -1
- data/lib/climate_control/version.rb +1 -1
- data/lib/climate_control.rb +24 -0
- data/spec/acceptance/climate_control_spec.rb +11 -1
- data/spec/acceptance/unsafe_modify_spec.rb +186 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '00584062039c6b9161ff0e4a1b61ca51e4d8742225fc611180c4effcc43959c5'
|
4
|
+
data.tar.gz: 99135f45c57f58d87e17480896045c00eecebd632ed64eaf18bd1964ef7d454d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
##
|
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
|
|
data/lib/climate_control.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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
|