climate_control 1.0.1 → 1.1.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/.github/workflows/ci.yml +1 -1
- data/CHANGELOG.md +45 -0
- data/CODEOWNERS +1 -0
- data/README.md +34 -1
- data/climate_control.gemspec +6 -6
- data/lib/climate_control/version.rb +1 -1
- data/lib/climate_control.rb +35 -7
- data/spec/acceptance/climate_control_spec.rb +34 -1
- metadata +23 -22
- data/NEWS +0 -22
- data/lib/climate_control/environment.rb +0 -33
- data/lib/climate_control/modifier.rb +0 -92
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 919bb6d645214941bca1f5de4d30732891348bb76a29725e9db8ba00b6274dbc
|
4
|
+
data.tar.gz: 332e9222519c1c6a91d866783d4c3993c4372ffe29242f60759cf9948636d3ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c5c9c97dcef22aa4923d284f196addeb1ddeb2f87c80785fd5d8a38e3f23ea7e66e11acbe265e225bf3663d36dbe500edc981d5baaf53cc872a19670dfa1878
|
7
|
+
data.tar.gz: c56fb5c40969ed979a485503765b33f7f4adc2d34830309502a2207dad363f4c9a0b04c5dda5cb4c56c001e80c317954a8d19b0b21fce53aa0fd84962d33c091
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## Unreleased
|
9
|
+
|
10
|
+
## 1.1.0 / 2022-05-26
|
11
|
+
|
12
|
+
- Refactor to use `Monitor` instead of `Mutex`
|
13
|
+
- Add documentation about thread-safety
|
14
|
+
- Allow ClimateControl.modify to be called without environment variables
|
15
|
+
- Add test for concurrent access needed to be inside block
|
16
|
+
- Relax development dependencies
|
17
|
+
|
18
|
+
## 1.0.1 / 2021-05-26
|
19
|
+
|
20
|
+
- Require minimum Ruby version of 2.5.0
|
21
|
+
|
22
|
+
# 1.0.0 / 2021-03-06
|
23
|
+
|
24
|
+
- Commit to supporting latest patch versions of Ruby 2.5+
|
25
|
+
- Improve documentation
|
26
|
+
- Format code with StandardRB
|
27
|
+
- Bump gem dependencies
|
28
|
+
|
29
|
+
# 0.2.0 / 2017-05-12
|
30
|
+
|
31
|
+
- Allow nested environment changes in the same thread
|
32
|
+
|
33
|
+
# 0.1.0 / 2017-01-07
|
34
|
+
|
35
|
+
- Remove ActiveSupport dependency
|
36
|
+
|
37
|
+
# 0.0.4 / 2017-01-06
|
38
|
+
|
39
|
+
- Improved thread safety
|
40
|
+
- Handle TypeErrors during assignment
|
41
|
+
- Improve documentation
|
42
|
+
|
43
|
+
# 0.0.1 / 2012-11-28
|
44
|
+
|
45
|
+
- Initial release
|
data/CODEOWNERS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* @dorianmariefr
|
data/README.md
CHANGED
@@ -48,7 +48,7 @@ end
|
|
48
48
|
To use with RSpec, you could define this in your spec:
|
49
49
|
|
50
50
|
```ruby
|
51
|
-
def with_modified_env(options, &block)
|
51
|
+
def with_modified_env(options = {}, &block)
|
52
52
|
ClimateControl.modify(options, &block)
|
53
53
|
end
|
54
54
|
```
|
@@ -105,6 +105,39 @@ manner becomes more difficult:
|
|
105
105
|
Climate Control modifies environment variables only within the context of the
|
106
106
|
block, ensuring values are managed properly and consistently.
|
107
107
|
|
108
|
+
## Thread-safety
|
109
|
+
|
110
|
+
When using threads, for instance when running tests concurrently in the same
|
111
|
+
process, you may need to wrap your code inside `ClimateControl.modify` blocks,
|
112
|
+
e.g.:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
first_thread = Thread.new do
|
116
|
+
ClimateControl.modify(SECRET: "1") do
|
117
|
+
p ENV["SECRET"] # => "1"
|
118
|
+
sleep 2
|
119
|
+
p ENV["SECRET"] # => "1"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
second_thread = Thread.new do
|
124
|
+
ClimateControl.modify({}) do
|
125
|
+
sleep 1
|
126
|
+
p ENV["SECRET"] # => nil
|
127
|
+
sleep 1
|
128
|
+
p ENV["SECRET"] # => nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
first_thread.join
|
133
|
+
second_thread.join
|
134
|
+
```
|
135
|
+
|
136
|
+
> The modification wraps ENV in a mutex. If there's contention (the env being used - including potentially mutating values), it blocks until the value is freed (we shift out of the Ruby block).
|
137
|
+
>
|
138
|
+
> <cite><a href="https://github.com/thoughtbot/climate_control/issues/32#issuecomment-800713686">Josh Clayton</a></cite>
|
139
|
+
|
140
|
+
|
108
141
|
## Contributing
|
109
142
|
|
110
143
|
1. Fork it
|
data/climate_control.gemspec
CHANGED
@@ -5,8 +5,8 @@ require "climate_control/version"
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = "climate_control"
|
7
7
|
gem.version = ClimateControl::VERSION
|
8
|
-
gem.authors = ["Joshua Clayton"]
|
9
|
-
gem.email = ["joshua.clayton@gmail.com"]
|
8
|
+
gem.authors = ["Joshua Clayton", "Dorian Marié"]
|
9
|
+
gem.email = ["joshua.clayton@gmail.com", "dorian@dorianmarie.fr"]
|
10
10
|
gem.description = "Modify your ENV"
|
11
11
|
gem.summary = "Modify your ENV easily with ClimateControl"
|
12
12
|
gem.homepage = "https://github.com/thoughtbot/climate_control"
|
@@ -18,8 +18,8 @@ Gem::Specification.new do |gem|
|
|
18
18
|
|
19
19
|
gem.required_ruby_version = ">= 2.5.0"
|
20
20
|
|
21
|
-
gem.add_development_dependency "rspec"
|
22
|
-
gem.add_development_dependency "rake"
|
23
|
-
gem.add_development_dependency "simplecov"
|
24
|
-
gem.add_development_dependency "standard"
|
21
|
+
gem.add_development_dependency "rspec"
|
22
|
+
gem.add_development_dependency "rake"
|
23
|
+
gem.add_development_dependency "simplecov"
|
24
|
+
gem.add_development_dependency "standard"
|
25
25
|
end
|
data/lib/climate_control.rb
CHANGED
@@ -1,16 +1,44 @@
|
|
1
|
-
require "climate_control/environment"
|
2
1
|
require "climate_control/errors"
|
3
|
-
require "climate_control/modifier"
|
4
2
|
require "climate_control/version"
|
3
|
+
require "monitor"
|
5
4
|
|
6
5
|
module ClimateControl
|
7
|
-
|
6
|
+
extend self
|
8
7
|
|
9
|
-
|
10
|
-
|
8
|
+
SEMAPHORE = Monitor.new
|
9
|
+
private_constant :SEMAPHORE
|
10
|
+
|
11
|
+
def modify(environment_overrides = {}, &block)
|
12
|
+
environment_overrides = environment_overrides.transform_keys(&:to_s)
|
13
|
+
|
14
|
+
SEMAPHORE.synchronize do
|
15
|
+
previous = ENV.to_hash
|
16
|
+
middle = {}
|
17
|
+
copy environment_overrides
|
18
|
+
middle = ENV.to_hash
|
19
|
+
block.call
|
20
|
+
ensure
|
21
|
+
after = ENV
|
22
|
+
(previous.keys | middle.keys | after.keys).each do |key|
|
23
|
+
if previous[key] != after[key] && middle[key] == after[key]
|
24
|
+
ENV[key] = previous[key]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
11
28
|
end
|
12
29
|
|
13
|
-
def
|
14
|
-
|
30
|
+
def env
|
31
|
+
ENV
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def copy(overrides)
|
37
|
+
overrides.each do |key, value|
|
38
|
+
ENV[key] = value
|
39
|
+
rescue TypeError => e
|
40
|
+
raise UnassignableValueError,
|
41
|
+
"attempted to assign #{value} to #{key} but failed (#{e.message})"
|
42
|
+
end
|
15
43
|
end
|
16
44
|
end
|
@@ -113,6 +113,39 @@ describe "Climate control" do
|
|
113
113
|
expect(ENV["BAZ"]).to be_nil
|
114
114
|
end
|
115
115
|
|
116
|
+
it "handles threads accessing the same key wrapped in a block" do
|
117
|
+
first_thread = Thread.new {
|
118
|
+
with_modified_env do
|
119
|
+
with_modified_env CONFLICTING_KEY: "1" do
|
120
|
+
sleep 0.5
|
121
|
+
expect(ENV["CONFLICTING_KEY"]).to eq("1")
|
122
|
+
end
|
123
|
+
|
124
|
+
expect(ENV["CONFLICTING_KEY"]).to be_nil
|
125
|
+
end
|
126
|
+
}
|
127
|
+
|
128
|
+
second_thread = Thread.new {
|
129
|
+
with_modified_env do
|
130
|
+
sleep 0.25
|
131
|
+
expect(ENV["CONFLICTING_KEY"]).to be_nil
|
132
|
+
|
133
|
+
with_modified_env CONFLICTING_KEY: "2" do
|
134
|
+
expect(ENV["CONFLICTING_KEY"]).to eq("2")
|
135
|
+
sleep 0.5
|
136
|
+
expect(ENV["CONFLICTING_KEY"]).to eq("2")
|
137
|
+
end
|
138
|
+
|
139
|
+
expect(ENV["CONFLICTING_KEY"]).to be_nil
|
140
|
+
end
|
141
|
+
}
|
142
|
+
|
143
|
+
first_thread.join
|
144
|
+
second_thread.join
|
145
|
+
|
146
|
+
expect(ENV["CONFLICTING_KEY"]).to be_nil
|
147
|
+
end
|
148
|
+
|
116
149
|
it "is re-entrant" do
|
117
150
|
ret = with_modified_env(FOO: "foo") {
|
118
151
|
with_modified_env(BAR: "bar") do
|
@@ -134,7 +167,7 @@ describe "Climate control" do
|
|
134
167
|
}.to raise_error ClimateControl::UnassignableValueError, /attempted to assign .*Thing.* to FOO but failed \(#{message}\)$/
|
135
168
|
end
|
136
169
|
|
137
|
-
def with_modified_env(options, &block)
|
170
|
+
def with_modified_env(options = {}, &block)
|
138
171
|
ClimateControl.modify(options, &block)
|
139
172
|
end
|
140
173
|
|
metadata
CHANGED
@@ -1,91 +1,92 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: climate_control
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Clayton
|
8
|
+
- Dorian Marié
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2022-05-26 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: rspec
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
16
17
|
requirements:
|
17
|
-
- - "
|
18
|
+
- - ">="
|
18
19
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
+
version: '0'
|
20
21
|
type: :development
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
|
-
- - "
|
25
|
+
- - ">="
|
25
26
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
27
|
+
version: '0'
|
27
28
|
- !ruby/object:Gem::Dependency
|
28
29
|
name: rake
|
29
30
|
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
|
-
- - "
|
32
|
+
- - ">="
|
32
33
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
34
|
+
version: '0'
|
34
35
|
type: :development
|
35
36
|
prerelease: false
|
36
37
|
version_requirements: !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
|
-
- - "
|
39
|
+
- - ">="
|
39
40
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
41
|
+
version: '0'
|
41
42
|
- !ruby/object:Gem::Dependency
|
42
43
|
name: simplecov
|
43
44
|
requirement: !ruby/object:Gem::Requirement
|
44
45
|
requirements:
|
45
|
-
- - "
|
46
|
+
- - ">="
|
46
47
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0
|
48
|
+
version: '0'
|
48
49
|
type: :development
|
49
50
|
prerelease: false
|
50
51
|
version_requirements: !ruby/object:Gem::Requirement
|
51
52
|
requirements:
|
52
|
-
- - "
|
53
|
+
- - ">="
|
53
54
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0
|
55
|
+
version: '0'
|
55
56
|
- !ruby/object:Gem::Dependency
|
56
57
|
name: standard
|
57
58
|
requirement: !ruby/object:Gem::Requirement
|
58
59
|
requirements:
|
59
|
-
- - "
|
60
|
+
- - ">="
|
60
61
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
62
|
+
version: '0'
|
62
63
|
type: :development
|
63
64
|
prerelease: false
|
64
65
|
version_requirements: !ruby/object:Gem::Requirement
|
65
66
|
requirements:
|
66
|
-
- - "
|
67
|
+
- - ">="
|
67
68
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
69
|
+
version: '0'
|
69
70
|
description: Modify your ENV
|
70
71
|
email:
|
71
72
|
- joshua.clayton@gmail.com
|
73
|
+
- dorian@dorianmarie.fr
|
72
74
|
executables: []
|
73
75
|
extensions: []
|
74
76
|
extra_rdoc_files: []
|
75
77
|
files:
|
76
78
|
- ".github/workflows/ci.yml"
|
77
79
|
- ".gitignore"
|
80
|
+
- CHANGELOG.md
|
81
|
+
- CODEOWNERS
|
78
82
|
- CODE_OF_CONDUCT.md
|
79
83
|
- Gemfile
|
80
84
|
- LICENSE
|
81
|
-
- NEWS
|
82
85
|
- README.md
|
83
86
|
- Rakefile
|
84
87
|
- climate_control.gemspec
|
85
88
|
- lib/climate_control.rb
|
86
|
-
- lib/climate_control/environment.rb
|
87
89
|
- lib/climate_control/errors.rb
|
88
|
-
- lib/climate_control/modifier.rb
|
89
90
|
- lib/climate_control/version.rb
|
90
91
|
- spec/acceptance/climate_control_spec.rb
|
91
92
|
- spec/spec_helper.rb
|
@@ -108,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
109
|
- !ruby/object:Gem::Version
|
109
110
|
version: '0'
|
110
111
|
requirements: []
|
111
|
-
rubygems_version: 3.
|
112
|
+
rubygems_version: 3.3.13
|
112
113
|
signing_key:
|
113
114
|
specification_version: 4
|
114
115
|
summary: Modify your ENV easily with ClimateControl
|
data/NEWS
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
1.0.1 (May 26, 2021)
|
2
|
-
Require minimum Ruby version of 2.5.0
|
3
|
-
|
4
|
-
1.0.0 (March 6, 2021)
|
5
|
-
Commit to supporting latest patch versions of Ruby 2.5+
|
6
|
-
Improve documentation
|
7
|
-
Format code with StandardRB
|
8
|
-
Bump gem dependencies
|
9
|
-
|
10
|
-
0.2.0 (May 12, 2017)
|
11
|
-
Allow nested environment changes in the same thread
|
12
|
-
|
13
|
-
0.1.0 (January 7, 2017)
|
14
|
-
Remove ActiveSupport dependency
|
15
|
-
|
16
|
-
0.0.4 (January 6, 2017)
|
17
|
-
Improved thread safety
|
18
|
-
Handle TypeErrors during assignment
|
19
|
-
Improve documentation
|
20
|
-
|
21
|
-
0.0.1 (November 28, 2012)
|
22
|
-
Initial release
|
@@ -1,33 +0,0 @@
|
|
1
|
-
require "forwardable"
|
2
|
-
|
3
|
-
module ClimateControl
|
4
|
-
class Environment
|
5
|
-
extend Forwardable
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@semaphore = Mutex.new
|
9
|
-
@owner = nil
|
10
|
-
end
|
11
|
-
|
12
|
-
def_delegators :env, :[]=, :to_hash, :[], :delete
|
13
|
-
|
14
|
-
def synchronize
|
15
|
-
if @owner == Thread.current
|
16
|
-
return yield if block_given?
|
17
|
-
end
|
18
|
-
|
19
|
-
@semaphore.synchronize do
|
20
|
-
@owner = Thread.current
|
21
|
-
yield if block_given?
|
22
|
-
ensure
|
23
|
-
@owner = nil
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def env
|
30
|
-
ENV
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,92 +0,0 @@
|
|
1
|
-
module ClimateControl
|
2
|
-
class Modifier
|
3
|
-
def initialize(env, environment_overrides = {}, &block)
|
4
|
-
@environment_overrides = stringify_keys(environment_overrides)
|
5
|
-
@block = block
|
6
|
-
@env = env
|
7
|
-
end
|
8
|
-
|
9
|
-
def process
|
10
|
-
@env.synchronize do
|
11
|
-
prepare_environment_for_block
|
12
|
-
run_block
|
13
|
-
ensure
|
14
|
-
cache_environment_after_block
|
15
|
-
delete_keys_that_do_not_belong
|
16
|
-
revert_changed_keys
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def prepare_environment_for_block
|
23
|
-
@original_env = clone_environment
|
24
|
-
copy_overrides_to_environment
|
25
|
-
@env_with_overrides_before_block = clone_environment
|
26
|
-
end
|
27
|
-
|
28
|
-
def run_block
|
29
|
-
@block.call
|
30
|
-
end
|
31
|
-
|
32
|
-
def copy_overrides_to_environment
|
33
|
-
@environment_overrides.each do |key, value|
|
34
|
-
@env[key] = value
|
35
|
-
rescue TypeError => e
|
36
|
-
raise UnassignableValueError,
|
37
|
-
"attempted to assign #{value} to #{key} but failed (#{e.message})"
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def keys_to_remove
|
42
|
-
@environment_overrides.keys
|
43
|
-
end
|
44
|
-
|
45
|
-
def keys_changed_by_block
|
46
|
-
@keys_changed_by_block ||= OverlappingKeysWithChangedValues.new(@env_with_overrides_before_block, @env_after_block).keys
|
47
|
-
end
|
48
|
-
|
49
|
-
def cache_environment_after_block
|
50
|
-
@env_after_block = clone_environment
|
51
|
-
end
|
52
|
-
|
53
|
-
def delete_keys_that_do_not_belong
|
54
|
-
(keys_to_remove - keys_changed_by_block).each { |key| @env.delete(key) }
|
55
|
-
end
|
56
|
-
|
57
|
-
def revert_changed_keys
|
58
|
-
(@original_env.keys - keys_changed_by_block).each do |key|
|
59
|
-
@env[key] = @original_env[key]
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def clone_environment
|
64
|
-
@env.to_hash
|
65
|
-
end
|
66
|
-
|
67
|
-
def stringify_keys(env)
|
68
|
-
env.each_with_object({}) do |(key, value), hash|
|
69
|
-
hash[key.to_s] = value
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
class OverlappingKeysWithChangedValues
|
74
|
-
def initialize(hash_1, hash_2)
|
75
|
-
@hash_1 = hash_1 || {}
|
76
|
-
@hash_2 = hash_2
|
77
|
-
end
|
78
|
-
|
79
|
-
def keys
|
80
|
-
overlapping_keys.select do |overlapping_key|
|
81
|
-
@hash_1[overlapping_key] != @hash_2[overlapping_key]
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
private
|
86
|
-
|
87
|
-
def overlapping_keys
|
88
|
-
@hash_2.keys & @hash_1.keys
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|