stable 1.0.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 +7 -0
- data/lib/stable/spec.rb +40 -0
- data/lib/stable.rb +124 -0
- metadata +43 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b5d2929b75b7c0dbf556f482bd3b4eeb852cfebff63fd26e4debd66c1c6980e3
|
4
|
+
data.tar.gz: fad79c07ad6849822bf0d73ff71ab104d4d4876efa6d6f146239ec7faa31a69b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c2de06eb210c9f700779a3d07b131695749e7f6ad1e8d3d70d5d701bf7204b2a6834d3c0411d86be0c9bb7fbf81aab3a8d56603e259c8cca5cb1fd3ed9c72883
|
7
|
+
data.tar.gz: 38511e64502515d0b24578c4d056a289ce3f65837a3e54b249b04b77714148a15e668f033b1746e1958206c51b4ee13995dde283d88642995916dc10802f0718
|
data/lib/stable/spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# lib/stable/spec.rb
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Stable
|
5
|
+
class Spec
|
6
|
+
attr_reader :class_name, :method_name, :args, :result, :error, :timestamp
|
7
|
+
|
8
|
+
def initialize(class_name:, method_name:, args:, result: nil, error: nil, timestamp: Time.now.iso8601)
|
9
|
+
@class_name = class_name
|
10
|
+
@method_name = method_name
|
11
|
+
@args = args
|
12
|
+
@result = result
|
13
|
+
@error = error
|
14
|
+
@timestamp = timestamp
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_jsonl
|
18
|
+
{
|
19
|
+
class: class_name,
|
20
|
+
method: method_name,
|
21
|
+
args: args,
|
22
|
+
result: result,
|
23
|
+
error: error,
|
24
|
+
timestamp: timestamp
|
25
|
+
}.compact.to_json
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.from_jsonl(jsonl_string)
|
29
|
+
data = JSON.parse(jsonl_string)
|
30
|
+
new(
|
31
|
+
class_name: data['class'],
|
32
|
+
method_name: data['method'],
|
33
|
+
args: data['args'],
|
34
|
+
result: data['result'],
|
35
|
+
error: data['error'],
|
36
|
+
timestamp: data['timestamp']
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/stable.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
# stable is a library for recording and replaying method calls. the idea of
|
2
|
+
# this is to reduce the amount of manual unit tests you need to write, while
|
3
|
+
# keeping the stability/notification system that unit tests provide.
|
4
|
+
#
|
5
|
+
# usage:
|
6
|
+
#
|
7
|
+
# require 'stable'
|
8
|
+
#
|
9
|
+
# # stable uses an IO-like object to write the captured inputs/outputs.
|
10
|
+
# # if you don't set this, the default it to print the interaction records to
|
11
|
+
# # $stdout, so you could also pipe the result to another place.
|
12
|
+
# Stable.storage = File.open('captured_calls.jsonl', 'a')
|
13
|
+
#
|
14
|
+
# # wrap a method on a given class
|
15
|
+
# Stable.capture(MyClass, :my_method)
|
16
|
+
#
|
17
|
+
# # enable runtime input/output capture
|
18
|
+
# Stable.enable!
|
19
|
+
#
|
20
|
+
# MyClass.my_metehod # this will be captures by stable
|
21
|
+
#
|
22
|
+
# # disable input/output capture
|
23
|
+
# Stable.disable!
|
24
|
+
#
|
25
|
+
# # replay captured calls, which gives you a unit test-list pass/fail
|
26
|
+
# record = JSON.parse(File.read('captured_calls.jsonl').lines.first)
|
27
|
+
# Stable.replay(record)
|
28
|
+
require_relative 'stable/spec'
|
29
|
+
|
30
|
+
module Stable
|
31
|
+
class << self
|
32
|
+
def enable!
|
33
|
+
Thread.current[:stable_enabled] = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def disable!
|
37
|
+
Thread.current[:stable_enabled] = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def enabled?
|
41
|
+
Thread.current[:stable_enabled] || false
|
42
|
+
end
|
43
|
+
|
44
|
+
def storage=(io)
|
45
|
+
@storage = io
|
46
|
+
end
|
47
|
+
|
48
|
+
def storage
|
49
|
+
@storage || raise("Stable.storage must be set to an IO-like object")
|
50
|
+
end
|
51
|
+
|
52
|
+
def capture(klass, method_name)
|
53
|
+
original_method = klass.instance_method(method_name)
|
54
|
+
wrapper_module = Module.new do
|
55
|
+
define_method(method_name) do |*args, &block|
|
56
|
+
if Stable.enabled?
|
57
|
+
begin
|
58
|
+
result = original_method.bind(self).call(*args, &block)
|
59
|
+
spec = Spec.new(
|
60
|
+
class_name: klass.name,
|
61
|
+
method_name: method_name,
|
62
|
+
args: args,
|
63
|
+
result: result
|
64
|
+
)
|
65
|
+
Stable.storage.puts(spec.to_jsonl)
|
66
|
+
result
|
67
|
+
rescue => e
|
68
|
+
spec = Spec.new(
|
69
|
+
class_name: klass.name,
|
70
|
+
method_name: method_name,
|
71
|
+
args: args,
|
72
|
+
error: {
|
73
|
+
class: e.class.name,
|
74
|
+
message: e.message,
|
75
|
+
backtrace: e.backtrace
|
76
|
+
}
|
77
|
+
)
|
78
|
+
Stable.storage.puts(spec.to_jsonl)
|
79
|
+
raise e
|
80
|
+
end
|
81
|
+
else
|
82
|
+
original_method.bind(self).call(*args, &block)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
klass.prepend(wrapper_module)
|
87
|
+
end
|
88
|
+
|
89
|
+
def replay(record_hash)
|
90
|
+
spec = Spec.from_jsonl(record_hash.to_json)
|
91
|
+
klass = Object.const_get(spec.class_name)
|
92
|
+
instance = klass.new
|
93
|
+
description = "#{spec.class_name}##{spec.method_name}(#{spec.args.join(', ')})"
|
94
|
+
|
95
|
+
begin
|
96
|
+
actual_result = instance.public_send(spec.method_name, *spec.args)
|
97
|
+
if spec.error
|
98
|
+
puts "FAILED: #{description}"
|
99
|
+
puts " Expected error: #{spec.error['class']}"
|
100
|
+
puts " Actual result: #{actual_result.inspect}"
|
101
|
+
elsif actual_result == spec.result
|
102
|
+
puts "PASSED: #{description}"
|
103
|
+
else
|
104
|
+
puts "FAILED: #{description}"
|
105
|
+
puts " Expected: #{spec.result.inspect}"
|
106
|
+
puts " Actual: #{actual_result.inspect}"
|
107
|
+
end
|
108
|
+
rescue => e
|
109
|
+
if spec.error && e.class.name == spec.error["class"]
|
110
|
+
puts "PASSED: #{description} (error)"
|
111
|
+
elsif spec.error
|
112
|
+
puts "FAILED: #{description}"
|
113
|
+
puts " Expected error: #{spec.error['class']}"
|
114
|
+
puts " Actual error: #{e.class.name}: #{e.message}"
|
115
|
+
else
|
116
|
+
puts "FAILED: #{description}"
|
117
|
+
puts " Expected result: #{spec.result.inspect}"
|
118
|
+
puts " Actual error: #{e.class.name}: #{e.message}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
metadata
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeff Lunt
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
description: an automatic unit test system that captures your usage and records it
|
13
|
+
for future playback
|
14
|
+
email: jefflunt@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/stable.rb
|
20
|
+
- lib/stable/spec.rb
|
21
|
+
homepage: https://github.com/jefflunt/stable
|
22
|
+
licenses:
|
23
|
+
- MIT
|
24
|
+
metadata: {}
|
25
|
+
rdoc_options: []
|
26
|
+
require_paths:
|
27
|
+
- lib
|
28
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
requirements: []
|
39
|
+
rubygems_version: 3.6.9
|
40
|
+
specification_version: 4
|
41
|
+
summary: an automatic unit test system that captures your usage and records it for
|
42
|
+
future playback
|
43
|
+
test_files: []
|