kronk 1.4.0 → 1.5.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.
@@ -0,0 +1,146 @@
1
+ class Kronk
2
+
3
+ ##
4
+ # Creates ordered data strings for rendering to the output.
5
+
6
+ module DataRenderer
7
+
8
+ ##
9
+ # Returns a ruby data string that is diff-able, meaning sorted by
10
+ # Hash keys when available.
11
+
12
+ def self.ruby data, struct_only=false
13
+ ordered_data_string data, struct_only do |type, obj|
14
+ case type
15
+ when :key_assign then " =>"
16
+ when :key then obj.inspect
17
+ when :value then obj.inspect
18
+ when :struct
19
+ (obj == true || obj == false) ? "Boolean" : obj.class
20
+ end
21
+ end
22
+ end
23
+
24
+ ##
25
+ # Returns a json data string that is diff-able, meaning sorted by
26
+ # Hash keys when available.
27
+
28
+ def self.json data, struct_only=false
29
+ ordered_data_string data, struct_only do |type, obj|
30
+ case type
31
+ when :key_assign then ":"
32
+ when :key
33
+ (Symbol === obj ? obj.inspect : obj.to_s).to_json
34
+ when :value
35
+ (Symbol === obj ? obj.inspect : obj).to_json
36
+ when :struct
37
+ ((obj == true || obj == false) ? "Boolean" : obj.class).to_json
38
+ end
39
+ end
40
+ end
41
+
42
+
43
+ def self.ordered_data_string data, struct_only=false, indent=nil, &block
44
+ i_width = Kronk.config[:indentation] || 1
45
+ indent ||= 0
46
+ indent += i_width
47
+
48
+ case data
49
+
50
+ when Hash
51
+ return "{}" if data.empty?
52
+
53
+ output = "{\n"
54
+
55
+ sorted_keys = sort_any data.keys
56
+
57
+ data_values =
58
+ sorted_keys.map do |key|
59
+ value = data[key]
60
+ pad = " " * indent
61
+ subdata = ordered_data_string value, struct_only, indent, &block
62
+ "#{pad}#{ yield(:key, key) }#{ yield(:key_assign) } #{subdata}"
63
+ end
64
+
65
+ output << data_values.join(",\n") << "\n"
66
+ output << "#{" " * (indent - i_width)}}"
67
+
68
+ when Array
69
+ return "[]" if data.empty?
70
+
71
+ output = "[\n"
72
+
73
+ data_values =
74
+ data.map do |value|
75
+ pad = " " * indent
76
+ "#{pad}#{ordered_data_string value, struct_only, indent, &block}"
77
+ end
78
+
79
+ output << data_values.join(",\n") << "\n"
80
+ output << "#{" " * (indent - i_width)}]"
81
+
82
+ else
83
+ struct_only ? yield(:struct, data) : yield(:value, data)
84
+ end
85
+ end
86
+
87
+
88
+
89
+ ##
90
+ # Sorts an array of any combination of string, integer, or symbols.
91
+
92
+ def self.sort_any arr
93
+ i = 1
94
+ until i >= arr.length
95
+ j = i-1
96
+ val = arr[i]
97
+ prev_val = arr[j]
98
+
99
+ loop do
100
+ if smaller?(val, arr[j])
101
+ arr[j+1] = arr[j]
102
+ j = j - 1
103
+ break if j < 0
104
+
105
+ else
106
+ break
107
+ end
108
+ end
109
+
110
+ arr[j+1] = val
111
+
112
+ i = i.next
113
+ end
114
+
115
+ arr
116
+ end
117
+
118
+
119
+ ##
120
+ # Compares Numerics, Strings, and Symbols and returns true if the left
121
+ # side is 'smaller' than the right side.
122
+
123
+ def self.smaller? left, right
124
+ case left
125
+ when Numeric
126
+ case right
127
+ when Numeric then right > left
128
+ else true
129
+ end
130
+
131
+ when Symbol
132
+ case right
133
+ when Numeric then false
134
+ when Symbol then right.to_s > left.to_s
135
+ else true
136
+ end
137
+
138
+ when String
139
+ case right
140
+ when String then right > left
141
+ else false
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
data/lib/kronk/diff.rb CHANGED
@@ -19,99 +19,11 @@ class Kronk
19
19
  # Returns a data string that is diff-able, meaning sorted by
20
20
  # Hash keys when available.
21
21
 
22
- def self.ordered_data_string data, struct_only=false, indent=0
23
- case data
24
-
25
- when Hash
26
- output = "{\n"
27
-
28
- sorted_keys = sort_any data.keys
29
-
30
- data_values =
31
- sorted_keys.map do |key|
32
- value = data[key]
33
- pad = " " * indent
34
- subdata = ordered_data_string value, struct_only, indent + 1
35
- "#{pad}#{key.inspect} => #{subdata}"
36
- end
37
-
38
- output << data_values.join(",\n") << "\n" unless data_values.empty?
39
- output << "#{" " * indent}}"
40
-
41
- when Array
42
- output = "[\n"
43
-
44
- data_values =
45
- data.map do |value|
46
- pad = " " * indent
47
- "#{pad}#{ordered_data_string value, struct_only, indent + 1}"
48
- end
49
-
50
- output << data_values.join(",\n") << "\n" unless data_values.empty?
51
- output << "#{" " * indent}]"
52
-
22
+ def self.ordered_data_string data, struct_only=false
23
+ case Kronk.config[:render_lang].to_s
24
+ when 'ruby' then DataRenderer.ruby(data, struct_only)
53
25
  else
54
- return data.inspect unless struct_only
55
- return "Boolean" if data == true || data == false
56
- data.class
57
- end
58
- end
59
-
60
-
61
- ##
62
- # Sorts an array of any combination of string, integer, or symbols.
63
-
64
- def self.sort_any arr
65
- i = 1
66
- until i >= arr.length
67
- j = i-1
68
- val = arr[i]
69
- prev_val = arr[j]
70
-
71
- loop do
72
- if smaller?(val, arr[j])
73
- arr[j+1] = arr[j]
74
- j = j - 1
75
- break if j < 0
76
-
77
- else
78
- break
79
- end
80
- end
81
-
82
- arr[j+1] = val
83
-
84
- i = i.next
85
- end
86
-
87
- arr
88
- end
89
-
90
-
91
- ##
92
- # Compares Numerics, Strings, and Symbols and returns true if the left
93
- # side is 'smaller' than the right side.
94
-
95
- def self.smaller? left, right
96
- case left
97
- when Numeric
98
- case right
99
- when Numeric then right > left
100
- else true
101
- end
102
-
103
- when Symbol
104
- case right
105
- when Numeric then false
106
- when Symbol then right.to_s > left.to_s
107
- else true
108
- end
109
-
110
- when String
111
- case right
112
- when String then right > left
113
- else false
114
- end
26
+ DataRenderer.json(data, struct_only)
115
27
  end
116
28
  end
117
29
 
@@ -94,6 +94,7 @@ class Kronk::Path::Transaction
94
94
 
95
95
 
96
96
  def transaction_select data, *data_paths # :nodoc:
97
+ data_paths = data_paths.compact
97
98
  return data if data_paths.empty?
98
99
 
99
100
  new_data = Hash.new
@@ -129,6 +130,7 @@ class Kronk::Path::Transaction
129
130
 
130
131
 
131
132
  def transaction_delete data, *data_paths # :nodoc:
133
+ data_paths = data_paths.compact
132
134
  return data if data_paths.empty?
133
135
 
134
136
  new_data = data.dup
@@ -0,0 +1,233 @@
1
+ class Kronk
2
+
3
+ ##
4
+ # The Player class is used for running multiple requests and comparisons and
5
+ # providing useful output through Player::Output classes.
6
+ # Kronk includes a Suite (test-like) output, a Stream (chunked) output,
7
+ # and a Benchmark output.
8
+
9
+ class Player
10
+
11
+ attr_accessor :number, :concurrency, :queue, :count, :input,
12
+ :output, :mutex, :threads, :reader_thread
13
+
14
+ ##
15
+ # Create a new Player for batch diff or response validation.
16
+ # Supported options are:
17
+ # :concurrency:: Fixnum - The maximum number of concurrent requests to make
18
+ # :number:: Fixnum - The number of requests to make
19
+ # :io:: IO - The IO instance to read from
20
+ # :output:: Class - The output class to use (see Player::Output)
21
+ # :parser:: Class - The IO parser to use.
22
+
23
+ def initialize opts={}
24
+ @number = opts[:number]
25
+ @concurrency = opts[:concurrency]
26
+ @concurrency = 1 if !@concurrency || @concurrency <= 0
27
+ self.output_from opts[:output] || Suite
28
+
29
+ @count = 0
30
+ @queue = []
31
+ @threads = []
32
+ @input = InputReader.new opts[:io], opts[:parser]
33
+ @last_req = nil
34
+
35
+ @mutex = Mutex.new
36
+ end
37
+
38
+
39
+ ##
40
+ # The kind of output to use. Typically Player::Suite or Player::Stream.
41
+ # Takes an output class or a string that represents a class constant.
42
+
43
+ def output_from new_output
44
+ return @output = new_output.new(self) if Class === new_output
45
+
46
+ klass =
47
+ case new_output.to_s
48
+ when /^(Player::)?benchmark$/i then Benchmark
49
+ when /^(Player::)?stream$/i then Stream
50
+ when /^(Player::)?suite$/i then Suite
51
+ else
52
+ Kronk.find_const new_output
53
+ end
54
+
55
+ @output = klass.new self if klass
56
+ end
57
+
58
+
59
+ ##
60
+ # Adds kronk request hash options to queue.
61
+ # See Kronk#compare for supported options.
62
+
63
+ def queue_req kronk_opts
64
+ @queue << kronk_opts
65
+ end
66
+
67
+
68
+ ##
69
+ # Populate the queue by reading from the given IO instance and
70
+ # parsing it into kronk options.
71
+ #
72
+ # Default parser is RequestParser. See InputReader for parser requirements.
73
+
74
+ def from_io io, parser=nil
75
+ @input.io = io
76
+ @input.parser = parser if parser
77
+ @input
78
+ end
79
+
80
+
81
+ ##
82
+ # Process the queue to compare two uris.
83
+ # If options are given, they are merged into every request.
84
+
85
+ def compare uri1, uri2, opts={}
86
+ return Cmd.compare uri1, uri2, @queue.shift.merge(opts) if single_request?
87
+
88
+ process_queue do |kronk_opts|
89
+ process_compare uri1, uri2, kronk_opts.merge(opts)
90
+ end
91
+ end
92
+
93
+
94
+ ##
95
+ # Process the queue to request uris.
96
+ # If options are given, they are merged into every request.
97
+
98
+ def request uri, opts={}
99
+ return Cmd.request(uri, @queue.shift.merge(opts)) if single_request?
100
+
101
+ process_queue do |kronk_opts|
102
+ process_request uri, kronk_opts.merge(opts)
103
+ end
104
+ end
105
+
106
+
107
+ ##
108
+ # Check if we're only processing a single case.
109
+ # If so, yield a single item and return immediately.
110
+
111
+ def single_request?
112
+ @queue << next_request if @queue.empty? && (!@number || @number <= 1)
113
+ @queue.length == 1 && @input.eof?
114
+ end
115
+
116
+
117
+ ##
118
+ # Start processing the queue and reading from IO if available.
119
+ # Calls Output#start method and returns the value of Output#completed
120
+ # once processing is finished.
121
+ #
122
+ # Yields queue item until queue and io (if available) are empty and the
123
+ # totaly number of requests to run is met (if number is set).
124
+
125
+ def process_queue
126
+ @reader_thread = try_fill_queue
127
+
128
+ trap 'INT' do
129
+ @threads.each{|t| t.kill}
130
+ @threads.clear
131
+ @reader_thread.kill
132
+ output_results
133
+ exit 2
134
+ end
135
+
136
+ @output.start
137
+ @count = 0
138
+
139
+ until finished?
140
+ @threads.delete_if{|t| !t.alive? }
141
+ next if @threads.length >= @concurrency || @queue.empty?
142
+
143
+ @threads << Thread.new(@queue.shift) do |kronk_opts|
144
+ yield kronk_opts if block_given?
145
+ end
146
+
147
+ @count += 1
148
+ end
149
+
150
+ @threads.each{|t| t.join}
151
+ @threads.clear
152
+
153
+ @reader_thread.kill
154
+
155
+ output_results
156
+ end
157
+
158
+
159
+ ##
160
+ # Attempt to fill the queue by reading from the IO instance.
161
+ # Starts a new thread and returns the thread instance.
162
+
163
+ def try_fill_queue
164
+ Thread.new do
165
+ loop do
166
+ break if !@number && @input.eof?
167
+ next if @queue.length >= @concurrency * 2
168
+
169
+ max_new = @concurrency * 2 - @queue.length
170
+
171
+ max_new.times do
172
+ @queue << next_request
173
+ break if !@number && @input.eof?
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+
180
+ ##
181
+ # Gets the next request to perform and always returns a Hash.
182
+ # Tries from input first, then from the last item in the queue.
183
+ # If both fail, returns an empty Hash.
184
+
185
+ def next_request
186
+ @last_req = @input.get_next || @queue.last || @last_req || Hash.new
187
+ end
188
+
189
+
190
+ ##
191
+ # Returns true if processing queue should be stopped, otherwise false.
192
+
193
+ def finished?
194
+ (@number && @count >= @number) || @queue.empty? &&
195
+ @input.eof? && @count > 0 && !@reader_thread.alive?
196
+ end
197
+
198
+
199
+ ##
200
+ # Process and output the results.
201
+ # Calls Output#completed method.
202
+
203
+ def output_results
204
+ @output.completed
205
+ end
206
+
207
+
208
+ ##
209
+ # Run a single compare and call the Output#result or Output#error method.
210
+
211
+ def process_compare uri1, uri2, opts={}
212
+ kronk = Kronk.new opts
213
+ kronk.compare uri1, uri2
214
+ @output.result kronk, @mutex
215
+
216
+ rescue Kronk::Exception, Response::MissingParser, Errno::ECONNRESET => e
217
+ @output.error e, kronk, @mutex
218
+ end
219
+
220
+
221
+ ##
222
+ # Run a single request and call the Output#result or Output#error method.
223
+
224
+ def process_request uri, opts={}
225
+ kronk = Kronk.new opts
226
+ kronk.retrieve uri
227
+ @output.result kronk, @mutex
228
+
229
+ rescue Kronk::Exception, Response::MissingParser, Errno::ECONNRESET => e
230
+ @output.error e, kronk, @mutex
231
+ end
232
+ end
233
+ end