sus 0.17.2 → 0.18.1

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: b86dafae68ebeadfd3e2b7aa2f94e955a05f9b65bf3fcaf1dfdd65a8822a0722
4
- data.tar.gz: 7714b5ca6dbac3f8a68f386a369c152c2086e1b9d907040c3163f4f7df8b2670
3
+ metadata.gz: e39bdf28a3135bf28cf66714d3ac2998c1c863e979c172573a3e5e509de2246e
4
+ data.tar.gz: 88273ca646abe719cb562693f233d6210d8e505456ae8dd663d74971086d33b7
5
5
  SHA512:
6
- metadata.gz: 1d35bd5af4e176105da748d46df24d101e828bd579f6daa32f1907909daa8401abdd73d0bb8f63195ac2c5c787db8af56ec1d7c7131947e0553a7005db0127c6
7
- data.tar.gz: 510e795dfe14659e2dc2ab166a546c1547b26d2c1476595260ee67124c5d01e162223196057e22635317cac382d399e36a46f8487b6bdc98baa29867b4606966
6
+ metadata.gz: eca3ca7daddbc30591698658f6d87cc11a56c9dee2456b1060339fe1442aa5d8479a11ccdf7d9f4e7d617478ff033b0c24482db8d8145b7046e69a106a75409c
7
+ data.tar.gz: 2df536e6206a37f475bde130d8a6f8f4f8c8eab49a3ea2f7d29f48b4a64b53d6145cc77c27f2105043385756cc8d44d34983334a6de633c5c8d78275777b1ee7
checksums.yaml.gz.sig CHANGED
Binary file
data/bin/sus-host ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+
5
+ require_relative '../lib/sus/config'
6
+ config = Sus::Config.load
7
+
8
+ require_relative '../lib/sus'
9
+
10
+ verbose = false
11
+ guard = Thread::Mutex.new
12
+
13
+ require 'etc'
14
+ count = Etc.nprocessors
15
+
16
+ $stdout.sync = true
17
+
18
+ input = $stdin.dup
19
+ $stdin.reopen(File::NULL)
20
+ output = $stdout.dup
21
+ $stdout.reopen($stderr)
22
+
23
+ def failure_messages_for(assertions)
24
+ messages = []
25
+
26
+ assertions.each_failure do |failure|
27
+ messages << failure.message
28
+ end
29
+
30
+ return messages
31
+ end
32
+
33
+ while line = input.gets
34
+ message = JSON.parse(line)
35
+
36
+ if tests = message['run']
37
+ jobs = Thread::Queue.new
38
+ results = Thread::Queue.new
39
+
40
+ top = Sus::Assertions.new(measure: true)
41
+ config.before_tests(top)
42
+
43
+ aggregate = Thread.new do
44
+ while result = results.pop
45
+ top.add(result)
46
+ end
47
+ end
48
+
49
+ loader = Thread.new do
50
+ registry = config.load_registry(tests)
51
+
52
+ registry.each do |child|
53
+ jobs << child
54
+ end
55
+
56
+ jobs.close
57
+ end
58
+
59
+ workers = count.times.map do |index|
60
+ Thread.new do
61
+ while job = jobs.pop
62
+ guard.synchronize do
63
+ output.puts JSON.generate({started: job.identity})
64
+ end
65
+
66
+ assertions = Sus::Assertions.new(measure: true)
67
+ job.call(assertions)
68
+ results.push(assertions)
69
+
70
+ guard.synchronize do
71
+ if assertions.passed?
72
+ output.puts JSON.generate({passed: job.identity, duration: assertions.clock.ms})
73
+ elsif assertions.errored?
74
+ output.puts JSON.generate({errored: job.identity, messages: failure_messages_for(assertions), duration: assertions.clock.ms})
75
+ else
76
+ output.puts JSON.generate({failed: job.identity, messages: failure_messages_for(assertions), duration: assertions.clock.ms})
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ loader.join
84
+ workers.each(&:join)
85
+ results.close
86
+
87
+ aggregate.join
88
+ config.after_tests(top)
89
+
90
+ workers.each(&:join)
91
+
92
+ if config.respond_to?(:covered)
93
+ if covered = config.covered and covered.record?
94
+ covered.policy.each do |coverage|
95
+ output.puts JSON.generate({coverage: coverage.path, counts: coverage.counts})
96
+ end
97
+ end
98
+ end
99
+
100
+ output.puts JSON.generate({finished: true, message: top.output.string, duration: top.clock.ms})
101
+ else
102
+ $stderr.puts "Unknown message: #{message}"
103
+ end
104
+ end
data/bin/sus-tree ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+
5
+ require_relative '../lib/sus/config'
6
+ config = Sus::Config.load
7
+
8
+ require_relative '../lib/sus'
9
+
10
+ verbose = false
11
+ registry = config.registry
12
+ puts Sus::Tree.new(registry.base).to_json
@@ -41,6 +41,7 @@ module Sus
41
41
  @count = 0
42
42
  end
43
43
 
44
+ attr :identity
44
45
  attr :target
45
46
  attr :output
46
47
  attr :level
@@ -75,6 +76,13 @@ module Sus
75
76
  "\#<#{self.class} #{@passed.size} passed #{@failed.size} failed #{@deferred.size} deferred #{@skipped.size} skipped #{@errored.size} errored>"
76
77
  end
77
78
 
79
+ def message
80
+ {
81
+ text: @output.string,
82
+ location: @identity&.to_location
83
+ }
84
+ end
85
+
78
86
  def total
79
87
  @passed.size + @failed.size + @deferred.size + @skipped.size + @errored.size
80
88
  end
@@ -138,24 +146,65 @@ module Sus
138
146
  @errored.any?
139
147
  end
140
148
 
149
+ class Assert
150
+ def initialize(identity, message)
151
+ @identity = identity
152
+ @message = message
153
+ end
154
+
155
+ attr :identity
156
+ attr :message
157
+
158
+ def each_failure(&block)
159
+ yield self
160
+ end
161
+
162
+ def message
163
+ {
164
+ text: @message,
165
+ location: @identity&.to_location
166
+ }
167
+ end
168
+ end
169
+
141
170
  def assert(condition, message = nil)
142
171
  @count += 1
143
172
 
173
+ message ||= self.output.string
174
+ backtrace = Output::Backtrace.first(@identity)
175
+ identity = @identity&.scoped
176
+
144
177
  if condition
145
- @passed << self
178
+ @passed << Assert.new(identity, message)
146
179
 
147
180
  if !@orientation || @verbose
148
- @output.puts(:indent, *pass_prefix, message || "assertion", Output::Backtrace.first(@identity))
181
+ @output.puts(:indent, *pass_prefix, message, backtrace)
149
182
  end
150
183
  else
151
- @failed << self
184
+ @failed << Assert.new(identity, message)
152
185
 
153
186
  if @orientation || @verbose
154
- @output.puts(:indent, *fail_prefix, message || "assertion", Output::Backtrace.first(@identity))
187
+ @output.puts(:indent, *fail_prefix, message, backtrace)
155
188
  end
156
189
  end
157
190
  end
158
191
 
192
+ def each_failure(&block)
193
+ return to_enum(__method__) unless block_given?
194
+
195
+ # if self.failed? and @identity
196
+ # yield self
197
+ # end
198
+
199
+ @failed.each do |assertions|
200
+ assertions.each_failure(&block)
201
+ end
202
+
203
+ @errored.each do |assertions|
204
+ assertions.each_failure(&block)
205
+ end
206
+ end
207
+
159
208
  def skip(reason)
160
209
  @output.puts(:indent, :skipped, skip_prefix, reason)
161
210
  @skipped << self
@@ -184,8 +233,31 @@ module Sus
184
233
  end
185
234
  end
186
235
 
236
+ class Error
237
+ def initialize(identity, error)
238
+ @identity = identity
239
+ @error = error
240
+ end
241
+
242
+ attr :identity
243
+ attr :error
244
+
245
+ def each_failure(&block)
246
+ yield self
247
+ end
248
+
249
+ def message
250
+ {
251
+ text: @error.message,
252
+ location: @identity&.to_location
253
+ }
254
+ end
255
+ end
256
+
187
257
  def error!(error)
188
- @errored << self
258
+ identity = @identity.scoped(error.backtrace_locations)
259
+
260
+ @errored << Error.new(identity, error)
189
261
 
190
262
  lines = error.message.split(/\r?\n/)
191
263
 
@@ -198,11 +270,11 @@ module Sus
198
270
  @output.write(Output::Backtrace.for(error, @identity))
199
271
  end
200
272
 
201
- def nested(target, identity: @identity, isolated: false, inverted: false, **options)
273
+ def nested(target, identity: @identity, isolated: false, buffered: false, inverted: false, **options)
202
274
  result = nil
203
275
 
204
276
  # Isolated assertions need to have buffered output so they can be replayed if they fail:
205
- if isolated
277
+ if isolated or buffered
206
278
  output = @output.buffered
207
279
  else
208
280
  output = @output
data/lib/sus/base.rb CHANGED
@@ -39,12 +39,13 @@ module Sus
39
39
  end
40
40
  end
41
41
 
42
- def self.base(description = nil)
42
+ def self.base(description = nil, root: nil)
43
43
  base = Class.new(Base)
44
44
 
45
45
  base.extend(Context)
46
+ base.identity = Identity.new(root) if root
46
47
  base.description = description
47
-
48
+
48
49
  return base
49
50
  end
50
51
  end
data/lib/sus/clock.rb CHANGED
@@ -33,6 +33,10 @@ module Sus
33
33
  duration
34
34
  end
35
35
 
36
+ def ms
37
+ duration * 1000.0
38
+ end
39
+
36
40
  def to_s
37
41
  duration = self.duration
38
42
 
@@ -45,6 +49,10 @@ module Sus
45
49
  end
46
50
  end
47
51
 
52
+ def reset!(duration = 0.0)
53
+ @duration = duration
54
+ end
55
+
48
56
  def start!
49
57
  @start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
50
58
  end
data/lib/sus/config.rb CHANGED
@@ -82,12 +82,12 @@ module Sus
82
82
  @registry ||= self.load_registry
83
83
  end
84
84
 
85
- def load_registry
86
- registry = Sus::Registry.new
85
+ def load_registry(paths = @paths)
86
+ registry = Sus::Registry.new(root: @root)
87
87
 
88
- if @paths&.any?
88
+ if paths&.any?
89
89
  registry = Sus::Filter.new(registry)
90
- @paths.each do |path|
90
+ paths.each do |path|
91
91
  registry.load(path)
92
92
  end
93
93
  else
@@ -99,42 +99,41 @@ module Sus
99
99
  return registry
100
100
  end
101
101
 
102
- def before_tests(assertions)
102
+ def before_tests(assertions, output: self.output)
103
+ @clock.reset!
103
104
  @clock.start!
104
105
  end
105
106
 
106
- def after_tests(assertions)
107
+ def after_tests(assertions, output: self.output)
107
108
  @clock.stop!
108
109
 
109
- self.print_summary(assertions)
110
+ self.print_summary(output, assertions)
110
111
  end
111
112
 
112
113
  protected
113
114
 
114
- def print_summary(assertions)
115
- output = self.output
116
-
115
+ def print_summary(output, assertions)
117
116
  assertions.print(output)
118
117
  output.puts
119
118
 
120
- print_finished_statistics(assertions)
119
+ print_finished_statistics(output, assertions)
121
120
 
122
121
  if !partial? and assertions.passed?
123
- print_test_feedback(assertions)
122
+ print_test_feedback(output, assertions)
124
123
  end
125
124
 
126
- print_slow_tests(assertions)
127
- print_failed_assertions(assertions)
125
+ print_slow_tests(output, assertions)
126
+ print_failed_assertions(output, assertions)
128
127
  end
129
128
 
130
- def print_finished_statistics(assertions)
129
+ def print_finished_statistics(output, assertions)
131
130
  duration = @clock.duration
132
131
  rate = assertions.count / duration
133
132
 
134
133
  output.puts "🏁 Finished in ", @clock, "; #{rate.round(3)} assertions per second."
135
134
  end
136
135
 
137
- def print_test_feedback(assertions)
136
+ def print_test_feedback(output, assertions)
138
137
  duration = @clock.duration
139
138
  rate = assertions.count / duration
140
139
 
@@ -179,7 +178,7 @@ module Sus
179
178
  end
180
179
  end
181
180
 
182
- def print_slow_tests(assertions, threshold = 0.1)
181
+ def print_slow_tests(output, assertions, threshold = 0.1)
183
182
  slowest_tests = assertions.passed.select{|test| test.clock > threshold}.sort_by(&:clock).reverse!
184
183
 
185
184
  if slowest_tests.empty?
@@ -193,7 +192,7 @@ module Sus
193
192
  end
194
193
  end
195
194
 
196
- def print_assertions(title, assertions)
195
+ def print_assertions(output, title, assertions)
197
196
  if assertions.any?
198
197
  output.puts
199
198
  output.puts title
@@ -206,9 +205,9 @@ module Sus
206
205
  end
207
206
  end
208
207
 
209
- def print_failed_assertions(assertions)
210
- print_assertions("🤔 Failed assertions:", assertions.failed)
211
- print_assertions("🔥 Errored assertions:", assertions.errored)
208
+ def print_failed_assertions(output, assertions)
209
+ print_assertions(output, "🤔 Failed assertions:", assertions.failed)
210
+ print_assertions(output, "🔥 Errored assertions:", assertions.errored)
212
211
  end
213
212
  end
214
213
  end
data/lib/sus/expect.rb CHANGED
@@ -5,14 +5,16 @@
5
5
 
6
6
  module Sus
7
7
  class Expect
8
- def initialize(assertions, subject, inverted: false)
8
+ def initialize(assertions, subject, inverted: false, buffered: false)
9
9
  @assertions = assertions
10
10
  @subject = subject
11
11
  @inverted = inverted
12
+ @buffered = buffered
12
13
  end
13
-
14
+
14
15
  attr :subject
15
16
  attr :inverted
17
+ attr :assertions
16
18
 
17
19
  def not
18
20
  self.dup.tap do |expect|
@@ -31,13 +33,16 @@ module Sus
31
33
  end
32
34
 
33
35
  def to(predicate)
34
- @assertions.nested(self, inverted: @inverted) do |assertions|
36
+ # This gets the identity scoped to the current call stack, which ensures that any failures are logged at this point in the code.
37
+ identity = @assertions.identity&.scoped
38
+
39
+ @assertions.nested(self, inverted: @inverted, buffered: @buffered, identity: identity) do |assertions|
35
40
  predicate.call(assertions, @subject)
36
41
  end
37
42
 
38
43
  return self
39
44
  end
40
-
45
+
41
46
  def and(predicate)
42
47
  return to(predicate)
43
48
  end
@@ -46,9 +51,9 @@ module Sus
46
51
  class Base
47
52
  def expect(subject = nil, &block)
48
53
  if block_given?
49
- Expect.new(@__assertions__, block)
54
+ Expect.new(@__assertions__, block, buffered: true)
50
55
  else
51
- Expect.new(@__assertions__, subject)
56
+ Expect.new(@__assertions__, subject, buffered: true)
52
57
  end
53
58
  end
54
59
  end
data/lib/sus/file.rb CHANGED
@@ -19,11 +19,17 @@ module Sus
19
19
 
20
20
  def self.build(parent, path)
21
21
  base = Class.new(parent)
22
+
22
23
  base.extend(File)
23
24
  base.description = path
24
- base.identity = Identity.new(path)
25
+ base.identity = Identity.file(parent.identity, path)
25
26
 
26
- TOPLEVEL_CLASS_EVAL.call(base, path)
27
+ begin
28
+ TOPLEVEL_CLASS_EVAL.call(base, path)
29
+ rescue StandardError, LoadError, SyntaxError => error
30
+ # We add this as a child of the base class so that it is included in the tree under the file rather than completely replacing it, which can be confusing:
31
+ base.add FileLoadError.build(self, path, error)
32
+ end
27
33
 
28
34
  return base
29
35
  end
@@ -35,7 +41,8 @@ module Sus
35
41
 
36
42
  class FileLoadError
37
43
  def self.build(parent, path, error)
38
- self.new(Identity.new(path), path, error)
44
+ identity = Identity.file(parent.identity, path).scoped(error.backtrace_locations)
45
+ self.new(identity, path, error)
39
46
  end
40
47
 
41
48
  def initialize(identity, path, error)
@@ -50,6 +57,16 @@ module Sus
50
57
  true
51
58
  end
52
59
 
60
+ EMPTY = Hash.new.freeze
61
+
62
+ def children
63
+ EMPTY
64
+ end
65
+
66
+ def description
67
+ @path
68
+ end
69
+
53
70
  def print(output)
54
71
  output.write("file ", :path, @identity)
55
72
  end
@@ -66,8 +83,6 @@ module Sus
66
83
  module Context
67
84
  def file(path)
68
85
  add File.build(self, path)
69
- rescue StandardError, LoadError, SyntaxError => error
70
- add FileLoadError.build(self, path, error)
71
86
  end
72
87
  end
73
88
  end
data/lib/sus/identity.rb CHANGED
@@ -5,6 +5,10 @@
5
5
 
6
6
  module Sus
7
7
  class Identity
8
+ def self.file(parent, path, name = path, **options)
9
+ self.new(path, name, nil, nil, **options)
10
+ end
11
+
8
12
  def self.nested(parent, name, location = nil, **options)
9
13
  location ||= caller_locations(3...4).first
10
14
 
@@ -32,6 +36,13 @@ module Sus
32
36
  self.key
33
37
  end
34
38
 
39
+ def to_location
40
+ {
41
+ path: ::File.expand_path(@path),
42
+ line: @line,
43
+ }
44
+ end
45
+
35
46
  def inspect
36
47
  "\#<#{self.class} #{self.to_s}>"
37
48
  end
@@ -69,8 +80,33 @@ module Sus
69
80
  return @key
70
81
  end
71
82
 
83
+ # Given a set of locations, find the first one which matches this identity and return a new identity with the updated line number. This can be used to extract a location from a backtrace.
84
+ def scoped(locations = nil)
85
+ if locations
86
+ # This code path is normally taken if we've got an exception with a backtrace:
87
+ locations.each do |location|
88
+ if location.path == @path
89
+ return self.with_line(location.lineno)
90
+ end
91
+ end
92
+ else
93
+ # In theory this should be a bit faster:
94
+ Thread.each_caller_location do |location|
95
+ if location.path == @path
96
+ return self.with_line(location.lineno)
97
+ end
98
+ end
99
+ end
100
+
101
+ return self
102
+ end
103
+
72
104
  protected
73
105
 
106
+ def with_line(line)
107
+ Identity.new(@path, @name, line, @parent, unique: @unique)
108
+ end
109
+
74
110
  def append_unique_key(key, unique = @unique)
75
111
  if @parent
76
112
  @parent.append_unique_key(key)
@@ -30,14 +30,12 @@ module Sus
30
30
  self.class.new(self)
31
31
  end
32
32
 
33
- attr :output
34
-
35
33
  def each(&block)
36
34
  @chunks.each(&block)
37
35
  end
38
36
 
39
37
  def append(buffer)
40
- @chunks.concat(buffer.output)
38
+ @chunks.concat(buffer.chunks)
41
39
  @tee&.append(buffer)
42
40
  end
43
41
 
data/lib/sus/registry.rb CHANGED
@@ -23,16 +23,21 @@ module Sus
23
23
  DIRECTORY_GLOB = "**/*.rb"
24
24
 
25
25
  # Create a top level scope with self as the instance:
26
- def initialize(base = Sus.base(self))
27
- @base = base
26
+ def initialize(**options)
27
+ @base = Sus.base(self, **options)
28
+ @loaded = {}
28
29
  end
29
-
30
+
30
31
  attr :base
31
32
 
32
33
  def print(output)
33
34
  output.write("Test Registry")
34
35
  end
35
36
 
37
+ def to_s
38
+ @base.identity.to_s
39
+ end
40
+
36
41
  def load(path)
37
42
  if ::File.directory?(path)
38
43
  load_directory(path)
@@ -42,7 +47,7 @@ module Sus
42
47
  end
43
48
 
44
49
  private def load_file(path)
45
- @base.file(path)
50
+ @loaded[path] ||= @base.file(path)
46
51
  end
47
52
 
48
53
  private def load_directory(path)
data/lib/sus/tree.rb ADDED
@@ -0,0 +1,27 @@
1
+ module Sus
2
+ class Tree
3
+ def initialize(context)
4
+ @context = context
5
+ end
6
+
7
+ def traverse(current = @context, &block)
8
+ node = {}
9
+
10
+ node[:self] = yield(current)
11
+
12
+ if children = current.children # and children.any?
13
+ node[:children] = children.values.map do |context|
14
+ self.traverse(context, &block)
15
+ end
16
+ end
17
+
18
+ return node
19
+ end
20
+
21
+ def to_json(options = nil)
22
+ traverse do |context|
23
+ [context.identity, context.description, context.leaf?]
24
+ end.to_json(options)
25
+ end
26
+ end
27
+ end
data/lib/sus/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2021-2022, by Samuel Williams.
5
5
 
6
6
  module Sus
7
- VERSION = "0.17.2"
7
+ VERSION = "0.18.1"
8
8
  end
data/lib/sus.rb CHANGED
@@ -7,6 +7,7 @@ require_relative 'sus/version'
7
7
  require_relative 'sus/config'
8
8
  require_relative 'sus/registry'
9
9
  require_relative 'sus/assertions'
10
+ require_relative 'sus/tree'
10
11
 
11
12
  require_relative 'sus/expect'
12
13
  require_relative 'sus/be'
data.tar.gz.sig CHANGED
@@ -1,4 +1,2 @@
1
- .��4����RZM��3��dZ Q�;Q���Du+���JQ6`�1G��`z��뤫��aJ����ޏ�
2
- �;E���<� FL��܀q=x��i!�X�_�p ����H��[}N�q$P���H���6�k3ɹ)��>�A`�P;�:6�#�=#)� �����:�k(a�� л���b�u�!&%�wiQ2�\O� L��c��1T� ^�LJ�ؘ��y��
3
- ǜ���Դ���2w�D1� }�yOy `��b��.yG��qJc����M
4
- X񯶅*�e�
1
+ am�����tJ�� )�p����Bn�ɠϝռ:����*����-TIV�3#AFHpga�WY��*k5�:�4^�>zI�=UD,ϑk*�8�t�۳nb"��k��~�k(x�&}�� M-�cH��̚���kΑ�
2
+ ��P߾��D;q�����!���L7V�����d���^�@s��?kF ��:-�a�3ag��=\{�<����#!ȔDI����p����kvM�$��K`�zP��jOם¡�)bѽ/��y�#���թ��
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.2
4
+ version: 0.18.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -38,7 +38,7 @@ cert_chain:
38
38
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
39
39
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
40
40
  -----END CERTIFICATE-----
41
- date: 2023-02-18 00:00:00.000000000 Z
41
+ date: 2023-02-20 00:00:00.000000000 Z
42
42
  dependencies:
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: bake-test
@@ -87,11 +87,15 @@ email:
87
87
  executables:
88
88
  - sus
89
89
  - sus-parallel
90
+ - sus-tree
91
+ - sus-host
90
92
  extensions: []
91
93
  extra_rdoc_files: []
92
94
  files:
93
95
  - bin/sus
96
+ - bin/sus-host
94
97
  - bin/sus-parallel
98
+ - bin/sus-tree
95
99
  - lib/sus.rb
96
100
  - lib/sus/assertions.rb
97
101
  - lib/sus/base.rb
@@ -131,6 +135,7 @@ files:
131
135
  - lib/sus/registry.rb
132
136
  - lib/sus/respond_to.rb
133
137
  - lib/sus/shared.rb
138
+ - lib/sus/tree.rb
134
139
  - lib/sus/version.rb
135
140
  - lib/sus/with.rb
136
141
  - license.md
metadata.gz.sig CHANGED
Binary file