blood_contracts-instrumentation 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BloodContracts
4
+ module Instrumentation
5
+ # Prependable module for matching session recording
6
+ module SessionRecording
7
+ # Adds @session initialization to constructor
8
+ def initialize(*)
9
+ super
10
+ self.class.instruments
11
+ @session = self.class.session_klass.new(self.class.name)
12
+ end
13
+
14
+ # Wrapper for BC::Refined#match call, to add instrumentaion
15
+ # Usage:
16
+ # class JsonType < BC::Refined
17
+ # # now during #match call you will have access to @session
18
+ # prepend BloodContracts::Instrumentation::Match
19
+ #
20
+ # def match
21
+ # context[:parsed] = JSON.parse(value.to_s)
22
+ # self
23
+ # end
24
+ # end
25
+ module Match
26
+ # Wraps original call in session start and finish call
27
+ # Note that @session.finish(result) is called even when
28
+ # exception was raised during the call
29
+ #
30
+ # @return [BC::Refined]
31
+ #
32
+ def match
33
+ @session.start
34
+ self.class.instruments.each { |i| i.before(@session) }
35
+
36
+ result = super
37
+ rescue StandardError => e
38
+ result = FailedMatch.new(e, context: @context)
39
+ raise e
40
+ ensure
41
+ finalize!(result)
42
+ end
43
+
44
+ # Finish the matching session and delegate finalize to SessionFinalizer
45
+ #
46
+ # @param result [BC::Refined] result of type matching pipeline
47
+ #
48
+ # @return [Nothing]
49
+ #
50
+ def finalize!(result)
51
+ @session.finish(result)
52
+ self.class.instruments.each { |i| i.after(@session) }
53
+ SessionFinalizer.instance.finalize!(self.class.instruments, @session)
54
+ end
55
+ end
56
+
57
+ # Modification to inheritance for the session recording
58
+ module Inheritance
59
+ # Register the inherited type and set the session klass same as parent
60
+ #
61
+ # @param child [BC::Refined] class to enhance
62
+ #
63
+ # @return [Nothing]
64
+ #
65
+ def inherited(child)
66
+ child.session_klass = session_klass
67
+ Instrumentation.register_type(child)
68
+ super
69
+ end
70
+ end
71
+
72
+ # Modifications in singleton class of BC::Refined
73
+ #
74
+ # @param other [BC::Refined] class to enhance
75
+ #
76
+ # rubocop:disable Metrics/MethodLength
77
+ def self.prepended(other)
78
+ class << other
79
+ prepend Inheritance
80
+
81
+ # Class to use as a session (writer)
82
+ #
83
+ # @return [Session]
84
+ #
85
+ attr_writer :session_klass
86
+
87
+ # Class to use as a session
88
+ # By default is set to Session
89
+ #
90
+ # @return [Session]
91
+ #
92
+ def session_klass
93
+ @session_klass ||= Session
94
+ end
95
+
96
+ # Whether type anonymous or not
97
+ #
98
+ # @return [Boolean]
99
+ #
100
+ def anonymous?
101
+ name.nil?
102
+ end
103
+
104
+ # List of instruments for the type
105
+ #
106
+ # @return [Array<Instrument>]
107
+ #
108
+ def instruments
109
+ return @instruments if defined? @instruments
110
+
111
+ reset_instruments!
112
+ end
113
+
114
+ # Alias for instruments reader
115
+ # See #instruments
116
+ alias setup_instruments instruments
117
+
118
+ # Reset the List of instruments for the type
119
+ # Note, that if list of instruments is empty there is no need to
120
+ # init the session, so type is not prepended by Match wrapper
121
+ #
122
+ # @return [Array<Instrument>]
123
+ #
124
+ def reset_instruments!
125
+ @instruments = Instrumentation.select_instruments(name)
126
+ ensure
127
+ prepend(Match) unless @instruments.empty?
128
+ end
129
+ end
130
+ end
131
+ # rubocop:enable Metrics/MethodLength
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe BloodContracts::Instrumentation do
4
+ before do
5
+ module Test
6
+ require "json"
7
+
8
+ class EmailType < BC::Refined
9
+ REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
10
+ def match
11
+ context[:email_input] = value.to_s
12
+ return failure(:invalid_email) if context[:email_input] !~ REGEX
13
+ context[:email] = context[:email_input]
14
+ self
15
+ end
16
+ end
17
+
18
+ class JsonType < BC::Refined
19
+ def match
20
+ context[:parsed] = JSON.parse(value.to_s)
21
+ self
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ describe ".configure" do
28
+ subject do
29
+ described_class.configure { |cfg| cfg.finalizer_pool_size = 5 }
30
+ end
31
+
32
+ it do
33
+ is_expected.to eq(described_class.config)
34
+ expect(subject.finalizer_pool_size).to eq(5)
35
+ end
36
+ end
37
+
38
+ describe ".register_type" do
39
+ before { described_class.register_type(Test::JsonType) }
40
+
41
+ subject { described_class.config }
42
+
43
+ it { expect(subject.types).to include(Test::JsonType) }
44
+ end
45
+
46
+ describe ".select_instruments" do
47
+ before do
48
+ described_class.configure do |cfg|
49
+ cfg.instrument "Json", lambda { |session|
50
+ puts "[SID:#{session.id}] #{session.result_type_name}"
51
+ }
52
+ end
53
+ end
54
+
55
+ subject { described_class.select_instruments(type) }
56
+
57
+ context "when type matches instruments condition" do
58
+ let(:type) { Test::JsonType.name }
59
+
60
+ it { is_expected.to match_array([kind_of(described_class::Instrument)]) }
61
+ end
62
+
63
+ context "when type doesn't match instruments condition" do
64
+ let(:type) { Test::EmailType.name }
65
+
66
+ it { is_expected.to be_empty }
67
+ end
68
+ end
69
+
70
+ describe ".reset_session_finalizer!" do
71
+ before do
72
+ described_class.configure { |cfg| cfg.session_finalizer = :fibers }
73
+ old_finalizer
74
+ end
75
+
76
+ let(:old_finalizer) { described_class::SessionFinalizer.instance }
77
+
78
+ subject { described_class.reset_session_finalizer! }
79
+
80
+ it { is_expected.not_to eq(old_finalizer) }
81
+ end
82
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "blood_contracts/instrumentation"
5
+
6
+ RSpec.configure do |config|
7
+ # Enable flags like --only-failures and --next-failure
8
+ config.example_status_persistence_file_path = ".rspec_status"
9
+
10
+ # Disable RSpec exposing methods globally on `Module` and `main`
11
+ config.disable_monkey_patching!
12
+
13
+ config.expect_with :rspec do |c|
14
+ c.syntax = :expect
15
+ end
16
+
17
+ config.around do |example|
18
+ module Test; end
19
+ example.run
20
+ Object.send(:remove_const, :Test)
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blood_contracts-instrumentation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sergey Dolganov (sclinede)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: blood_contracts-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.49'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.49'
97
+ description: Adds instrumentation to BloodContracts refinement types
98
+ email:
99
+ - sclinede@evilmartians.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files:
103
+ - CODE_OF_CONDUCT.md
104
+ - README.md
105
+ - CHANGELOG.md
106
+ files:
107
+ - ".gitignore"
108
+ - ".rspec"
109
+ - ".rubocop.yml"
110
+ - ".travis.yml"
111
+ - CHANGELOG.md
112
+ - CODE_OF_CONDUCT.md
113
+ - Gemfile
114
+ - LICENSE.txt
115
+ - README.md
116
+ - Rakefile
117
+ - bin/console
118
+ - bin/setup
119
+ - blood_contracts-instrumentation.gemspec
120
+ - lib/blood_contracts-instrumentation.rb
121
+ - lib/blood_contracts/instrumentation.rb
122
+ - lib/blood_contracts/instrumentation/config.rb
123
+ - lib/blood_contracts/instrumentation/failed_match.rb
124
+ - lib/blood_contracts/instrumentation/instrument.rb
125
+ - lib/blood_contracts/instrumentation/session.rb
126
+ - lib/blood_contracts/instrumentation/session_finalizer.rb
127
+ - lib/blood_contracts/instrumentation/session_finalizer/basic.rb
128
+ - lib/blood_contracts/instrumentation/session_finalizer/fibers.rb
129
+ - lib/blood_contracts/instrumentation/session_finalizer/threads.rb
130
+ - lib/blood_contracts/instrumentation/session_recording.rb
131
+ - spec/blood_contracts/instrumentation_spec.rb
132
+ - spec/spec_helper.rb
133
+ homepage: https://github.com/sclinede/blood_contracts-core
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '2.4'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubygems_version: 3.0.3
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Adds instrumentation to BloodContracts refinement types
156
+ test_files:
157
+ - spec/blood_contracts/instrumentation_spec.rb
158
+ - spec/spec_helper.rb