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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/stable/spec.rb +40 -0
  3. data/lib/stable.rb +124 -0
  4. 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
@@ -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: []