is-module-reset 0.8.0 → 0.8.0.2
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/README.md +194 -1
- data/lib/is-module-reset/info.rb +1 -1
- data/lib/is-module-reset/main.rb +8 -6
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4c3492581888c7c5515f550a39afb1b9760551aa0ab212e60a263f36cc1d88d7
|
|
4
|
+
data.tar.gz: 3003eee4ccc3ca344e1d61d14eab567935ddfc6b2a8d91c7ec86a202f1708a0b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb1def03555aaab2ac065cd94c9746c3d475afefa94e233d9fddb829a7455bc83aa37ec715963dac24a23477edd5d5cd1ce02fb8b8f207d25841e6a5db8e8b1a
|
|
7
|
+
data.tar.gz: 96d73839eb00418981af55744d585b1bb5c7693ccc47fb323848d6182262d32fc0e99d765a3636fd0793ec728af1138b71c5911602fad0ad754b1e0f9fe5ca7b
|
data/README.md
CHANGED
|
@@ -1 +1,194 @@
|
|
|
1
|
-
# is-module-reset
|
|
1
|
+
# is-module-reset
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://badge.fury.io/rb/is-module-reset)
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Save and restore the state of Ruby modules and classes **in tests**: class
|
|
8
|
+
variables (`@@var`) and instance variables of the modules and classes
|
|
9
|
+
themselves (`@var`). It keeps tests that touch global state (singletons,
|
|
10
|
+
registries, caches, settings stored in modules) from affecting each other.
|
|
11
|
+
|
|
12
|
+
> **Not intended for production use.** The gem does not create a truly
|
|
13
|
+
> isolated environment: it only puts values back into the same objects.
|
|
14
|
+
|
|
15
|
+
Русская версия: [README-ru.md](README-ru.md)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add to your Gemfile:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem 'is-module-reset', group: :test
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or install directly:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
gem install is-module-reset
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
require 'is-module-reset'
|
|
35
|
+
|
|
36
|
+
module App
|
|
37
|
+
@@requests = 0
|
|
38
|
+
@settings = { debug: false }
|
|
39
|
+
|
|
40
|
+
def self.requests
|
|
41
|
+
@@requests
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.hit!
|
|
45
|
+
@@requests += 1
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.settings
|
|
49
|
+
@settings
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Run a block and roll everything back afterwards:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
IS::ModuleReset.isolate(App) do
|
|
58
|
+
App.hit!
|
|
59
|
+
App.settings[:debug] = true
|
|
60
|
+
App.requests # => 1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
App.requests # => 0
|
|
64
|
+
App.settings # => { debug: false }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or take a snapshot and restore it manually:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
state = IS::ModuleReset.dump(App)
|
|
71
|
+
|
|
72
|
+
App.hit!
|
|
73
|
+
App.settings[:debug] = true
|
|
74
|
+
|
|
75
|
+
IS::ModuleReset.undump(state)
|
|
76
|
+
|
|
77
|
+
App.requests # => 0
|
|
78
|
+
App.settings # => { debug: false }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## What is saved
|
|
82
|
+
|
|
83
|
+
For every module or class you pass in, and for every module or class
|
|
84
|
+
reachable from it through constants (recursively, using only the module's own
|
|
85
|
+
constants, i.e. `Module#constants(false)`), the gem saves:
|
|
86
|
+
|
|
87
|
+
- the class variables defined by that module itself (inherited ones are not
|
|
88
|
+
included);
|
|
89
|
+
- the instance variables of the module object itself (`@var` set at module or
|
|
90
|
+
class level, including those created by `attr_accessor` in `class << self`).
|
|
91
|
+
|
|
92
|
+
When restoring, all such variables are removed and the saved ones are set
|
|
93
|
+
again. This means that variables created after the snapshot disappear, changed
|
|
94
|
+
ones return to their old values, and deleted ones come back.
|
|
95
|
+
|
|
96
|
+
Values are copied with `deep_dup` from [is-deep](https://github.com/inat-get/is-deep):
|
|
97
|
+
`Hash` and `Array` are copied recursively (shared and circular references are
|
|
98
|
+
preserved), modules and classes are kept as they are, and other objects are
|
|
99
|
+
copied with `dup` (if an object cannot be duplicated, the same object is kept).
|
|
100
|
+
Restoring also works on a copy, so the same snapshot can be restored any
|
|
101
|
+
number of times.
|
|
102
|
+
|
|
103
|
+
## API
|
|
104
|
+
|
|
105
|
+
### `IS::ModuleReset.dump(*mods) → Hash`
|
|
106
|
+
|
|
107
|
+
Takes a snapshot of the given modules and everything reachable from them via
|
|
108
|
+
constants. The returned hash is an internal representation; treat it as an
|
|
109
|
+
opaque value and pass it to `undump`.
|
|
110
|
+
|
|
111
|
+
### `IS::ModuleReset.undump(state)`
|
|
112
|
+
|
|
113
|
+
Restores the state from a snapshot returned by `dump`. May be called multiple
|
|
114
|
+
times with the same snapshot.
|
|
115
|
+
|
|
116
|
+
### `IS::ModuleReset.isolate(*mods) { ... }`
|
|
117
|
+
|
|
118
|
+
Takes a snapshot, runs the block, and restores the snapshot afterwards, even if
|
|
119
|
+
the block raised an exception. Returns the value of the block. Raises
|
|
120
|
+
`ArgumentError` if no block is given.
|
|
121
|
+
|
|
122
|
+
## Test framework integration
|
|
123
|
+
|
|
124
|
+
### RSpec
|
|
125
|
+
|
|
126
|
+
Isolate every example:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
RSpec.configure do |config|
|
|
130
|
+
config.around do |example|
|
|
131
|
+
IS::ModuleReset.isolate(MyApp) { example.run }
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`around` hooks wrap `before` and `after` hooks, so changes made there are
|
|
137
|
+
rolled back too.
|
|
138
|
+
|
|
139
|
+
Or restore the state captured when the suite started:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
RSpec.configure do |config|
|
|
143
|
+
baseline = nil
|
|
144
|
+
config.before(:suite) { baseline = IS::ModuleReset.dump(MyApp) }
|
|
145
|
+
config.after(:each) { IS::ModuleReset.undump(baseline) }
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Minitest
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
class Minitest::Test
|
|
153
|
+
def setup
|
|
154
|
+
super
|
|
155
|
+
@module_reset_state = IS::ModuleReset.dump(MyApp)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def teardown
|
|
159
|
+
IS::ModuleReset.undump(@module_reset_state)
|
|
160
|
+
super
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
If your test classes define their own `setup` and `teardown`, call `super`.
|
|
166
|
+
|
|
167
|
+
## Limitations
|
|
168
|
+
|
|
169
|
+
- **Constants are not restored.** Neither reassigned, added, or removed
|
|
170
|
+
constants, nor the contents of mutable constants (`CONFIG = {}`).
|
|
171
|
+
- **Only module-level state is saved.** Global variables (`$var`), `ENV`,
|
|
172
|
+
thread-local variables, instance variables of ordinary objects, and anything
|
|
173
|
+
outside the namespaces you pass in are not touched.
|
|
174
|
+
- **Values are copies.** Objects other than `Hash` and `Array` are copied with
|
|
175
|
+
a shallow `dup`: their inner mutable state is shared with the original, and
|
|
176
|
+
after restoring the module holds the copy rather than the original instance.
|
|
177
|
+
If you store, say, a singleton object in `@instance`, it may become a
|
|
178
|
+
different object after restoring.
|
|
179
|
+
- **Copies are not frozen.** A frozen value becomes mutable after restoring.
|
|
180
|
+
- **Constants are followed blindly.** An alias to a module from another
|
|
181
|
+
namespace (`Log = ::Logger`) is included in the snapshot and rolled back too.
|
|
182
|
+
Traversal may also trigger loading of `autoload` constants.
|
|
183
|
+
- **Not thread-safe.** The gem changes global state, so do not use it with
|
|
184
|
+
tests running in parallel threads of the same process. Process-based
|
|
185
|
+
parallelism is fine.
|
|
186
|
+
|
|
187
|
+
## Requirements
|
|
188
|
+
|
|
189
|
+
- Ruby >= 3.4
|
|
190
|
+
- [is-deep](https://github.com/inat-get/is-deep)
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
LGPL-3.0-or-later. See the [LICENSE](LICENSE) file.
|
data/lib/is-module-reset/info.rb
CHANGED
data/lib/is-module-reset/main.rb
CHANGED
|
@@ -17,8 +17,8 @@ module IS::ModuleReset
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def undump state
|
|
20
|
-
state.each do |mod, mod_state|
|
|
21
|
-
mod.class_variables.each { mod.remove_class_variable it }
|
|
20
|
+
state.deep_dup.each do |mod, mod_state|
|
|
21
|
+
mod.class_variables(false).each { mod.remove_class_variable it }
|
|
22
22
|
mod_state[:class_vars].each do |var, value|
|
|
23
23
|
mod.class_variable_set var, value
|
|
24
24
|
end
|
|
@@ -32,16 +32,18 @@ module IS::ModuleReset
|
|
|
32
32
|
def isolate *mods
|
|
33
33
|
raise ArgumentError, 'Block is required!' unless block_given?
|
|
34
34
|
state = dump(*mods)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
begin
|
|
36
|
+
yield
|
|
37
|
+
ensure
|
|
38
|
+
undump state
|
|
39
|
+
end
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
private
|
|
41
43
|
|
|
42
44
|
def dump_module state, mod
|
|
43
45
|
mod_state = { instance_vars: {}, class_vars: {} }
|
|
44
|
-
mod.class_variables.each do |var|
|
|
46
|
+
mod.class_variables(false).each do |var|
|
|
45
47
|
mod_state[:class_vars][var] = mod.class_variable_get(var)
|
|
46
48
|
end
|
|
47
49
|
mod.instance_variables.each do |var|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: is-module-reset
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.0
|
|
4
|
+
version: 0.8.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ivan Shikhalev
|
|
@@ -16,6 +16,9 @@ dependencies:
|
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
18
|
version: '0.8'
|
|
19
|
+
- - ">="
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 0.8.0.4
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -23,6 +26,9 @@ dependencies:
|
|
|
23
26
|
- - "~>"
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
28
|
version: '0.8'
|
|
29
|
+
- - ">="
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: 0.8.0.4
|
|
26
32
|
- !ruby/object:Gem::Dependency
|
|
27
33
|
name: rspec
|
|
28
34
|
requirement: !ruby/object:Gem::Requirement
|