jrf 0.1.12 → 0.1.13
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 +4 -4
- data/jrf.gemspec +2 -0
- data/lib/jrf/cli/runner.rb +57 -5
- data/lib/jrf/cli.rb +5 -4
- data/lib/jrf/version.rb +1 -1
- data/test/cli_runner_test.rb +951 -0
- data/test/library_api_test.rb +126 -0
- data/test/readme_examples_test.rb +16 -0
- data/test/test_helper.rb +118 -0
- metadata +33 -2
- data/test/jrf_test.rb +0 -1103
data/test/jrf_test.rb
DELETED
|
@@ -1,1103 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
begin
|
|
4
|
-
require "bundler/setup"
|
|
5
|
-
rescue LoadError
|
|
6
|
-
# Allow running tests in plain Ruby environments with globally installed gems.
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
require "json"
|
|
10
|
-
require "open3"
|
|
11
|
-
require "stringio"
|
|
12
|
-
require "tmpdir"
|
|
13
|
-
require "zlib"
|
|
14
|
-
require_relative "../lib/jrf/cli/runner"
|
|
15
|
-
|
|
16
|
-
def run_jrf(expr, input, *opts)
|
|
17
|
-
Open3.capture3("./exe/jrf", *opts, expr, stdin_data: input)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def assert_equal(expected, actual, msg = nil)
|
|
21
|
-
return if expected == actual
|
|
22
|
-
|
|
23
|
-
raise "assert_equal failed#{msg ? " (#{msg})" : ""}\nexpected: #{expected.inspect}\nactual: #{actual.inspect}"
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def assert_includes(text, fragment, msg = nil)
|
|
27
|
-
return if text.include?(fragment)
|
|
28
|
-
|
|
29
|
-
raise "assert_includes failed#{msg ? " (#{msg})" : ""}\ntext: #{text.inspect}\nfragment: #{fragment.inspect}"
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def assert_success(status, stderr, msg = nil)
|
|
33
|
-
return if status.success?
|
|
34
|
-
|
|
35
|
-
raise "expected success#{msg ? " (#{msg})" : ""}, got failure\nstderr: #{stderr}"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def assert_failure(status, msg = nil)
|
|
39
|
-
return unless status.success?
|
|
40
|
-
|
|
41
|
-
raise "expected failure#{msg ? " (#{msg})" : ""}, got success"
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def assert_float_close(expected, actual, epsilon = 1e-9, msg = nil)
|
|
45
|
-
return if (expected - actual).abs <= epsilon
|
|
46
|
-
|
|
47
|
-
raise "assert_float_close failed#{msg ? " (#{msg})" : ""}\nexpected: #{expected}\nactual: #{actual}\nepsilon: #{epsilon}"
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def lines(str)
|
|
51
|
-
str.lines.map(&:strip).reject(&:empty?)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
class RecordingRunner < Jrf::CLI::Runner
|
|
55
|
-
attr_reader :writes
|
|
56
|
-
|
|
57
|
-
def initialize(**kwargs)
|
|
58
|
-
super
|
|
59
|
-
@writes = []
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
private
|
|
63
|
-
|
|
64
|
-
def write_output(str)
|
|
65
|
-
return if str.empty?
|
|
66
|
-
|
|
67
|
-
@writes << str
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
class ChunkedSource
|
|
72
|
-
def initialize(str, chunk_size: 5)
|
|
73
|
-
@str = str
|
|
74
|
-
@chunk_size = chunk_size
|
|
75
|
-
@offset = 0
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def read(length = nil, outbuf = nil)
|
|
79
|
-
raise "expected chunked reads" if length.nil?
|
|
80
|
-
|
|
81
|
-
chunk = @str.byteslice(@offset, [length, @chunk_size].min)
|
|
82
|
-
return nil unless chunk
|
|
83
|
-
|
|
84
|
-
@offset += chunk.bytesize
|
|
85
|
-
if outbuf
|
|
86
|
-
outbuf.replace(chunk)
|
|
87
|
-
else
|
|
88
|
-
chunk
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
File.chmod(0o755, "./exe/jrf")
|
|
94
|
-
|
|
95
|
-
input = <<~NDJSON
|
|
96
|
-
{"foo":1,"x":5}
|
|
97
|
-
{"foo":2,"x":11}
|
|
98
|
-
{"foo":{"bar":"ok"},"x":50}
|
|
99
|
-
{"x":70}
|
|
100
|
-
NDJSON
|
|
101
|
-
|
|
102
|
-
stdout, stderr, status = run_jrf('_["foo"]', input)
|
|
103
|
-
assert_success(status, stderr, "simple extract")
|
|
104
|
-
assert_equal(%w[1 2 {"bar":"ok"} null], lines(stdout), "extract output")
|
|
105
|
-
|
|
106
|
-
input_nested = <<~NDJSON
|
|
107
|
-
{"foo":{"bar":"a"}}
|
|
108
|
-
{"foo":{"bar":"b"}}
|
|
109
|
-
NDJSON
|
|
110
|
-
|
|
111
|
-
stdout, stderr, status = run_jrf('_["foo"]["bar"]', input_nested)
|
|
112
|
-
assert_success(status, stderr, "nested extract")
|
|
113
|
-
assert_equal(%w["a" "b"], lines(stdout), "nested output")
|
|
114
|
-
|
|
115
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 10) >> _["foo"]', input)
|
|
116
|
-
assert_success(status, stderr, "select + extract")
|
|
117
|
-
assert_equal(%w[2 {"bar":"ok"} null], lines(stdout), "filtered output")
|
|
118
|
-
|
|
119
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 10)', input)
|
|
120
|
-
assert_success(status, stderr, "select only")
|
|
121
|
-
assert_equal(
|
|
122
|
-
['{"foo":2,"x":11}', '{"foo":{"bar":"ok"},"x":50}', '{"x":70}'],
|
|
123
|
-
lines(stdout),
|
|
124
|
-
"select-only output"
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
input_hello = <<~NDJSON
|
|
128
|
-
{"hello":123}
|
|
129
|
-
{"hello":456}
|
|
130
|
-
NDJSON
|
|
131
|
-
|
|
132
|
-
stdout, stderr, status = run_jrf('select(_["hello"] == 123)', input_hello)
|
|
133
|
-
assert_success(status, stderr, "select-only hello")
|
|
134
|
-
assert_equal(['{"hello":123}'], lines(stdout), "select-only hello output")
|
|
135
|
-
|
|
136
|
-
stdout, stderr, status = run_jrf('select(_["hello"] == 123) >> _["hello"]', input_hello, "-v")
|
|
137
|
-
assert_success(status, stderr, "dump stages")
|
|
138
|
-
assert_equal(%w[123], lines(stdout), "dump stages output")
|
|
139
|
-
assert_includes(stderr, 'stage[0]: select(_["hello"] == 123)')
|
|
140
|
-
assert_includes(stderr, 'stage[1]: _["hello"]')
|
|
141
|
-
|
|
142
|
-
stdout, stderr, status = Open3.capture3("./exe/jrf", "--help")
|
|
143
|
-
assert_success(status, stderr, "help option")
|
|
144
|
-
assert_includes(stdout, "usage: jrf [options] 'STAGE >> STAGE >> ...'")
|
|
145
|
-
assert_includes(stdout, "JSON filter with the power and speed of Ruby.")
|
|
146
|
-
assert_includes(stdout, "--lax")
|
|
147
|
-
assert_includes(stdout, "--pretty")
|
|
148
|
-
assert_includes(stdout, "--require LIBRARY")
|
|
149
|
-
assert_includes(stdout, "--no-jit")
|
|
150
|
-
assert_includes(stdout, "-V")
|
|
151
|
-
assert_includes(stdout, "--version")
|
|
152
|
-
assert_includes(stdout, "--atomic-write-bytes N")
|
|
153
|
-
assert_includes(stdout, "Pipeline:")
|
|
154
|
-
assert_includes(stdout, "Connect stages with top-level >>.")
|
|
155
|
-
assert_includes(stdout, "The current value in each stage is available as _.")
|
|
156
|
-
assert_includes(stdout, "See Also:")
|
|
157
|
-
assert_includes(stdout, "https://github.com/kazuho/jrf#readme")
|
|
158
|
-
assert_equal([], lines(stderr), "help stderr output")
|
|
159
|
-
|
|
160
|
-
stdout, stderr, status = Open3.capture3("./exe/jrf", "--version")
|
|
161
|
-
assert_success(status, stderr, "version long option")
|
|
162
|
-
assert_equal([Jrf::VERSION], lines(stdout), "version long option output")
|
|
163
|
-
assert_equal([], lines(stderr), "version long option stderr")
|
|
164
|
-
|
|
165
|
-
stdout, stderr, status = Open3.capture3("./exe/jrf", "-V")
|
|
166
|
-
assert_success(status, stderr, "version short option")
|
|
167
|
-
assert_equal([Jrf::VERSION], lines(stdout), "version short option output")
|
|
168
|
-
assert_equal([], lines(stderr), "version short option stderr")
|
|
169
|
-
|
|
170
|
-
threshold_input = StringIO.new((1..4).map { |i| "{\"foo\":\"#{'x' * 1020}\",\"i\":#{i}}\n" }.join)
|
|
171
|
-
buffered_runner = RecordingRunner.new(inputs: [threshold_input], out: StringIO.new, err: StringIO.new)
|
|
172
|
-
buffered_runner.run('_')
|
|
173
|
-
expected_line = JSON.generate({"foo" => "x" * 1020, "i" => 1}) + "\n"
|
|
174
|
-
assert_equal(2, buffered_runner.writes.length, "default atomic write limit buffers records until the configured threshold")
|
|
175
|
-
assert_equal(expected_line.bytesize * 3, buffered_runner.writes.first.bytesize, "default atomic write limit flushes before the next record would exceed the threshold")
|
|
176
|
-
assert_equal(expected_line.bytesize, buffered_runner.writes.last.bytesize, "final buffer flush emits the remaining record")
|
|
177
|
-
|
|
178
|
-
small_limit_runner = RecordingRunner.new(inputs: [StringIO.new("{\"foo\":1}\n{\"foo\":2}\n")], out: StringIO.new, err: StringIO.new, atomic_write_bytes: 1)
|
|
179
|
-
small_limit_runner.run('_["foo"]')
|
|
180
|
-
assert_equal(["1\n", "2\n"], small_limit_runner.writes, "small atomic write limit emits oversized records directly")
|
|
181
|
-
|
|
182
|
-
error_runner = RecordingRunner.new(inputs: [StringIO.new("{\"foo\":1}\n{\"foo\":")], out: StringIO.new, err: StringIO.new)
|
|
183
|
-
begin
|
|
184
|
-
error_runner.run('_["foo"]')
|
|
185
|
-
raise "expected parse error for buffered flush test"
|
|
186
|
-
rescue JSON::ParserError
|
|
187
|
-
assert_equal(["1\n"], error_runner.writes, "buffer flushes pending output before parse errors escape")
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
stdout, stderr, status = run_jrf('select(_["hello"] == 123) >> _["hello"]', input_hello, "--verbose")
|
|
191
|
-
assert_success(status, stderr, "dump stages verbose alias")
|
|
192
|
-
assert_equal(%w[123], lines(stdout), "dump stages verbose alias output")
|
|
193
|
-
assert_includes(stderr, 'stage[0]: select(_["hello"] == 123)')
|
|
194
|
-
|
|
195
|
-
stdout, stderr, status = run_jrf('_["hello"]', input_hello, "--atomic-write-bytes", "512")
|
|
196
|
-
assert_success(status, stderr, "atomic write bytes option")
|
|
197
|
-
assert_equal(%w[123 456], lines(stdout), "atomic write bytes option output")
|
|
198
|
-
|
|
199
|
-
stdout, stderr, status = run_jrf('_["hello"]', input_hello, "--atomic-write-bytes=512")
|
|
200
|
-
assert_success(status, stderr, "atomic write bytes equals form")
|
|
201
|
-
assert_equal(%w[123 456], lines(stdout), "atomic write bytes equals form output")
|
|
202
|
-
|
|
203
|
-
stdout, stderr, status = Open3.capture3("./exe/jrf", "--atomic-write-bytes", "0", '_["hello"]', stdin_data: input_hello)
|
|
204
|
-
assert_failure(status, "atomic write bytes rejects zero")
|
|
205
|
-
assert_includes(stderr, "--atomic-write-bytes requires a positive integer")
|
|
206
|
-
|
|
207
|
-
Dir.mktmpdir do |dir|
|
|
208
|
-
helper = File.join(dir, "helpers.rb")
|
|
209
|
-
File.write(helper, <<~RUBY)
|
|
210
|
-
def double(value)
|
|
211
|
-
value * 2
|
|
212
|
-
end
|
|
213
|
-
RUBY
|
|
214
|
-
|
|
215
|
-
stdout, stderr, status = Open3.capture3("./exe/jrf", "-r", helper, 'double(_["hello"])', stdin_data: input_hello)
|
|
216
|
-
assert_success(status, stderr, "require helper option")
|
|
217
|
-
assert_equal(%w[246 912], lines(stdout), "require helper option output")
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
if defined?(RubyVM::YJIT) && RubyVM::YJIT.respond_to?(:enabled?)
|
|
221
|
-
yjit_probe = "{\"probe\":1}\n"
|
|
222
|
-
|
|
223
|
-
stdout, stderr, status = run_jrf('RubyVM::YJIT.enabled?', yjit_probe)
|
|
224
|
-
assert_success(status, stderr, "default jit enablement")
|
|
225
|
-
assert_equal(%w[true], lines(stdout), "default jit enablement output")
|
|
226
|
-
|
|
227
|
-
stdout, stderr, status = run_jrf('RubyVM::YJIT.enabled?', yjit_probe, "--no-jit")
|
|
228
|
-
assert_success(status, stderr, "no-jit option")
|
|
229
|
-
assert_equal(%w[false], lines(stdout), "no-jit option output")
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
Dir.mktmpdir do |dir|
|
|
233
|
-
gz_path = File.join(dir, "input.ndjson.gz")
|
|
234
|
-
Zlib::GzipWriter.open(gz_path) do |io|
|
|
235
|
-
io.write("{\"foo\":10}\n{\"foo\":20}\n")
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
stdout, stderr, status = Open3.capture3("./exe/jrf", '_["foo"]', gz_path)
|
|
239
|
-
assert_success(status, stderr, "compressed input by suffix")
|
|
240
|
-
assert_equal(%w[10 20], lines(stdout), "compressed input output")
|
|
241
|
-
|
|
242
|
-
lax_gz_path = File.join(dir, "input-lax.json.gz")
|
|
243
|
-
Zlib::GzipWriter.open(lax_gz_path) do |io|
|
|
244
|
-
io.write("{\"foo\":30}\n\x1e{\"foo\":40}\n")
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
stdout, stderr, status = Open3.capture3("./exe/jrf", "--lax", '_["foo"]', lax_gz_path)
|
|
248
|
-
assert_success(status, stderr, "compressed lax input by suffix")
|
|
249
|
-
assert_equal(%w[30 40], lines(stdout), "compressed lax input output")
|
|
250
|
-
|
|
251
|
-
second_gz_path = File.join(dir, "input2.ndjson.gz")
|
|
252
|
-
Zlib::GzipWriter.open(second_gz_path) do |io|
|
|
253
|
-
io.write("{\"foo\":50}\n")
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
stdout, stderr, status = Open3.capture3("./exe/jrf", '_["foo"]', gz_path, second_gz_path)
|
|
257
|
-
assert_success(status, stderr, "multiple compressed inputs by suffix")
|
|
258
|
-
assert_equal(%w[10 20 50], lines(stdout), "multiple compressed input output")
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
stdout, stderr, status = run_jrf('_', input_hello, "--pretty")
|
|
262
|
-
assert_success(status, stderr, "pretty output")
|
|
263
|
-
assert_equal(
|
|
264
|
-
[
|
|
265
|
-
"{",
|
|
266
|
-
"\"hello\": 123",
|
|
267
|
-
"}",
|
|
268
|
-
"{",
|
|
269
|
-
"\"hello\": 456",
|
|
270
|
-
"}"
|
|
271
|
-
],
|
|
272
|
-
lines(stdout),
|
|
273
|
-
"pretty output lines"
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
input_regex = <<~NDJSON
|
|
277
|
-
{"foo":{"bar":"ok"},"x":50}
|
|
278
|
-
{"foo":{"bar":"ng"},"x":70}
|
|
279
|
-
NDJSON
|
|
280
|
-
|
|
281
|
-
stdout, stderr, status = run_jrf('select(/ok/.match(_["foo"]["bar"])) >> _["x"]', input_regex)
|
|
282
|
-
assert_success(status, stderr, "regex in select")
|
|
283
|
-
assert_equal(%w[50], lines(stdout), "regex filter output")
|
|
284
|
-
|
|
285
|
-
input_split = <<~NDJSON
|
|
286
|
-
{"x":1}
|
|
287
|
-
NDJSON
|
|
288
|
-
|
|
289
|
-
stdout, stderr, status = run_jrf('[1 >> 2] >> _', input_split)
|
|
290
|
-
assert_success(status, stderr, "no split inside []")
|
|
291
|
-
assert_equal(['[0]'], lines(stdout), "no split inside [] output")
|
|
292
|
-
|
|
293
|
-
stdout, stderr, status = run_jrf('{a: 1 >> 2} >> _[:a]', input_split)
|
|
294
|
-
assert_success(status, stderr, "no split inside {}")
|
|
295
|
-
assert_equal(%w[0], lines(stdout), "no split inside {} output")
|
|
296
|
-
|
|
297
|
-
stdout, stderr, status = run_jrf('(-> { 1 >> 2 }).call >> _ + 1', input_split)
|
|
298
|
-
assert_success(status, stderr, "no split inside block")
|
|
299
|
-
assert_equal(%w[1], lines(stdout), "no split inside block output")
|
|
300
|
-
|
|
301
|
-
input_flat = <<~NDJSON
|
|
302
|
-
{"items":[1,2]}
|
|
303
|
-
{"items":[3]}
|
|
304
|
-
{"items":[]}
|
|
305
|
-
NDJSON
|
|
306
|
-
|
|
307
|
-
stdout, stderr, status = run_jrf('_["items"] >> flat', input_flat)
|
|
308
|
-
assert_success(status, stderr, "flat basic")
|
|
309
|
-
assert_equal(%w[1 2 3], lines(stdout), "flat basic output")
|
|
310
|
-
|
|
311
|
-
input_flat_hash = <<~NDJSON
|
|
312
|
-
{"items":[{"x":1},{"x":2}]}
|
|
313
|
-
NDJSON
|
|
314
|
-
|
|
315
|
-
stdout, stderr, status = run_jrf('_["items"] >> flat >> _["x"]', input_flat_hash)
|
|
316
|
-
assert_success(status, stderr, "flat then extract")
|
|
317
|
-
assert_equal(%w[1 2], lines(stdout), "flat then extract output")
|
|
318
|
-
|
|
319
|
-
stdout, stderr, status = run_jrf('_["items"] >> flat >> sum(_)', input_flat)
|
|
320
|
-
assert_success(status, stderr, "flat then sum")
|
|
321
|
-
assert_equal(%w[6], lines(stdout), "flat then sum output")
|
|
322
|
-
|
|
323
|
-
stdout, stderr, status = run_jrf('_["items"] >> flat >> group', input_flat)
|
|
324
|
-
assert_success(status, stderr, "flat then group")
|
|
325
|
-
assert_equal(['[1,2,3]'], lines(stdout), "flat then group output")
|
|
326
|
-
|
|
327
|
-
stdout, stderr, status = run_jrf('map { |x| flat }', "[[1,2],[3],[4,5,6]]\n")
|
|
328
|
-
assert_success(status, stderr, "flat inside map")
|
|
329
|
-
assert_equal(['[1,2,3,4,5,6]'], lines(stdout), "flat inside map output")
|
|
330
|
-
|
|
331
|
-
stdout, stderr, status = run_jrf('map_values { |v| flat }', "{\"a\":[1,2],\"b\":[3]}\n")
|
|
332
|
-
assert_failure(status, "flat inside map_values")
|
|
333
|
-
assert_includes(stderr, "flat is not supported inside map_values")
|
|
334
|
-
|
|
335
|
-
stdout, stderr, status = run_jrf('_["foo"] >> flat', input)
|
|
336
|
-
assert_failure(status, "flat requires array")
|
|
337
|
-
assert_includes(stderr, "flat expects Array")
|
|
338
|
-
|
|
339
|
-
input_sum = <<~NDJSON
|
|
340
|
-
{"foo":1,"x":5}
|
|
341
|
-
{"foo":2,"x":11}
|
|
342
|
-
{"foo":3,"x":50}
|
|
343
|
-
{"foo":4,"x":70}
|
|
344
|
-
NDJSON
|
|
345
|
-
|
|
346
|
-
stdout, stderr, status = run_jrf('sum(_["foo"])', input_sum)
|
|
347
|
-
assert_success(status, stderr, "sum only")
|
|
348
|
-
assert_equal(%w[10], lines(stdout), "sum output")
|
|
349
|
-
|
|
350
|
-
stdout, stderr, status = run_jrf('count()', input_sum)
|
|
351
|
-
assert_success(status, stderr, "count only")
|
|
352
|
-
assert_equal(%w[4], lines(stdout), "count output")
|
|
353
|
-
|
|
354
|
-
stdout, stderr, status = run_jrf('count(_["foo"])', input_sum)
|
|
355
|
-
assert_success(status, stderr, "count(expr) only")
|
|
356
|
-
assert_equal(%w[4], lines(stdout), "count(expr) output")
|
|
357
|
-
|
|
358
|
-
stdout, stderr, status = run_jrf('min(_["foo"])', input_sum)
|
|
359
|
-
assert_success(status, stderr, "min only")
|
|
360
|
-
assert_equal(%w[1], lines(stdout), "min output")
|
|
361
|
-
|
|
362
|
-
stdout, stderr, status = run_jrf('max(_["foo"])', input_sum)
|
|
363
|
-
assert_success(status, stderr, "max only")
|
|
364
|
-
assert_equal(%w[4], lines(stdout), "max output")
|
|
365
|
-
|
|
366
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 10) >> sum(_["foo"])', input_sum)
|
|
367
|
-
assert_success(status, stderr, "select + sum")
|
|
368
|
-
assert_equal(%w[9], lines(stdout), "select + sum output")
|
|
369
|
-
|
|
370
|
-
stdout, stderr, status = run_jrf('{total: sum(_["foo"]), n: count()}', input_sum)
|
|
371
|
-
assert_success(status, stderr, "structured reducer result")
|
|
372
|
-
assert_equal(['{"total":10,"n":4}'], lines(stdout), "structured reducer result output")
|
|
373
|
-
|
|
374
|
-
stdout, stderr, status = run_jrf('average(_["foo"])', input_sum)
|
|
375
|
-
assert_success(status, stderr, "average")
|
|
376
|
-
assert_float_close(2.5, lines(stdout).first.to_f, 1e-12, "average output")
|
|
377
|
-
|
|
378
|
-
stdout, stderr, status = run_jrf('stdev(_["foo"])', input_sum)
|
|
379
|
-
assert_success(status, stderr, "stdev")
|
|
380
|
-
assert_float_close(1.118033988749895, lines(stdout).first.to_f, 1e-12, "stdev output")
|
|
381
|
-
|
|
382
|
-
stdout, stderr, status = run_jrf('_["foo"] >> sum(_ * 2)', input_sum)
|
|
383
|
-
assert_success(status, stderr, "extract + sum")
|
|
384
|
-
assert_equal(%w[20], lines(stdout), "extract + sum output")
|
|
385
|
-
|
|
386
|
-
stdout, stderr, status = run_jrf('sum(2 * _["foo"])', input_sum)
|
|
387
|
-
assert_success(status, stderr, "sum with literal on left")
|
|
388
|
-
assert_equal(%w[20], lines(stdout), "sum with literal on left output")
|
|
389
|
-
|
|
390
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 1000) >> sum(_["foo"])', input_sum)
|
|
391
|
-
assert_success(status, stderr, "sum no matches")
|
|
392
|
-
assert_equal([], lines(stdout), "sum no matches output")
|
|
393
|
-
|
|
394
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 1000) >> count()', input_sum)
|
|
395
|
-
assert_success(status, stderr, "count no matches")
|
|
396
|
-
assert_equal([], lines(stdout), "count no matches output")
|
|
397
|
-
|
|
398
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 1000) >> count(_["foo"])', input_sum)
|
|
399
|
-
assert_success(status, stderr, "count(expr) no matches")
|
|
400
|
-
assert_equal([], lines(stdout), "count(expr) no matches output")
|
|
401
|
-
|
|
402
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 1000) >> average(_["foo"])', input_sum)
|
|
403
|
-
assert_success(status, stderr, "average no matches")
|
|
404
|
-
assert_equal([], lines(stdout), "average no matches output")
|
|
405
|
-
|
|
406
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 1000) >> stdev(_["foo"])', input_sum)
|
|
407
|
-
assert_success(status, stderr, "stdev no matches")
|
|
408
|
-
assert_equal([], lines(stdout), "stdev no matches output")
|
|
409
|
-
|
|
410
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 1000) >> min(_["foo"])', input_sum)
|
|
411
|
-
assert_success(status, stderr, "min no matches")
|
|
412
|
-
assert_equal([], lines(stdout), "min no matches output")
|
|
413
|
-
|
|
414
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 1000) >> max(_["foo"])', input_sum)
|
|
415
|
-
assert_success(status, stderr, "max no matches")
|
|
416
|
-
assert_equal([], lines(stdout), "max no matches output")
|
|
417
|
-
|
|
418
|
-
stdout, stderr, status = run_jrf('sum(_["foo"]) >> _ + 1', input_sum)
|
|
419
|
-
assert_success(status, stderr, "reduce in middle")
|
|
420
|
-
assert_equal(%w[11], lines(stdout), "reduce in middle output")
|
|
421
|
-
|
|
422
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 10) >> _["foo"] >> sum(_ * 2) >> select(_ > 10) >> _ + 1', input_sum)
|
|
423
|
-
assert_success(status, stderr, "reduce mixed with select/extract")
|
|
424
|
-
assert_equal(%w[19], lines(stdout), "reduce mixed output")
|
|
425
|
-
|
|
426
|
-
stdout, stderr, status = run_jrf('_["foo"] >> sum(_) >> _ * 10 >> sum(_)', input_sum)
|
|
427
|
-
assert_success(status, stderr, "multiple reducers")
|
|
428
|
-
assert_equal(%w[100], lines(stdout), "multiple reducers output")
|
|
429
|
-
|
|
430
|
-
stdout, stderr, status = run_jrf('_["foo"] >> min(_) >> _ * 10 >> max(_)', input_sum)
|
|
431
|
-
assert_success(status, stderr, "min/max mixed reducers")
|
|
432
|
-
assert_equal(%w[10], lines(stdout), "min/max mixed reducers output")
|
|
433
|
-
|
|
434
|
-
input_sort_rows = <<~NDJSON
|
|
435
|
-
{"foo":"b","at":2}
|
|
436
|
-
{"foo":"c","at":3}
|
|
437
|
-
{"foo":"a","at":1}
|
|
438
|
-
NDJSON
|
|
439
|
-
|
|
440
|
-
stdout, stderr, status = run_jrf('sort(_["at"]) >> _["foo"]', input_sort_rows)
|
|
441
|
-
assert_success(status, stderr, "sort rows by field")
|
|
442
|
-
assert_equal(%w["a" "b" "c"], lines(stdout), "sort rows by field output")
|
|
443
|
-
|
|
444
|
-
stdout, stderr, status = run_jrf('sort { |a, b| b["at"] <=> a["at"] } >> _["foo"]', input_sort_rows)
|
|
445
|
-
assert_success(status, stderr, "sort rows by comparator")
|
|
446
|
-
assert_equal(%w["c" "b" "a"], lines(stdout), "sort rows by comparator output")
|
|
447
|
-
|
|
448
|
-
stdout, stderr, status = run_jrf('sort(_["at"]) >> _["foo"] >> group', input_sort_rows)
|
|
449
|
-
assert_success(status, stderr, "sort then group")
|
|
450
|
-
assert_equal(['["a","b","c"]'], lines(stdout), "sort then group output")
|
|
451
|
-
|
|
452
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 1000) >> sort(_["x"]) >> _["foo"]', input_sum)
|
|
453
|
-
assert_success(status, stderr, "sort no matches")
|
|
454
|
-
assert_equal([], lines(stdout), "sort no matches output")
|
|
455
|
-
|
|
456
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 1000) >> _["foo"] >> group', input_sum)
|
|
457
|
-
assert_success(status, stderr, "group no matches")
|
|
458
|
-
assert_equal([], lines(stdout), "group no matches output")
|
|
459
|
-
|
|
460
|
-
input_group_multi = <<~NDJSON
|
|
461
|
-
{"x":1,"y":"a"}
|
|
462
|
-
{"x":2,"y":"b"}
|
|
463
|
-
{"x":3,"y":"c"}
|
|
464
|
-
NDJSON
|
|
465
|
-
|
|
466
|
-
stdout, stderr, status = run_jrf('{a: group(_["x"]), b: group(_["y"])}', input_group_multi)
|
|
467
|
-
assert_success(status, stderr, "group in hash")
|
|
468
|
-
assert_equal(['{"a":[1,2,3],"b":["a","b","c"]}'], lines(stdout), "group in hash output")
|
|
469
|
-
|
|
470
|
-
stdout, stderr, status = run_jrf('select(_["x"] > 1000) >> {a: group(_["x"]), b: group(_["y"])}', input_group_multi)
|
|
471
|
-
assert_success(status, stderr, "group in hash no matches")
|
|
472
|
-
assert_equal([], lines(stdout), "group in hash no-match output")
|
|
473
|
-
|
|
474
|
-
stdout, stderr, status = run_jrf('percentile(_["foo"], 0.50)', input_sum)
|
|
475
|
-
assert_success(status, stderr, "single percentile")
|
|
476
|
-
assert_equal(%w[2], lines(stdout), "single percentile output")
|
|
477
|
-
|
|
478
|
-
stdout, stderr, status = run_jrf('percentile(_["foo"], [0.25, 0.50, 1.0])', input_sum)
|
|
479
|
-
assert_success(status, stderr, "array percentile")
|
|
480
|
-
assert_equal(
|
|
481
|
-
['[1,2,4]'],
|
|
482
|
-
lines(stdout),
|
|
483
|
-
"array percentile output"
|
|
484
|
-
)
|
|
485
|
-
|
|
486
|
-
stdout, stderr, status = run_jrf('percentile(_["foo"], 0.25.step(1.0, 0.25))', input_sum)
|
|
487
|
-
assert_success(status, stderr, "enumerable percentile")
|
|
488
|
-
assert_equal(
|
|
489
|
-
['[1,2,3,4]'],
|
|
490
|
-
lines(stdout),
|
|
491
|
-
"enumerable percentile output"
|
|
492
|
-
)
|
|
493
|
-
|
|
494
|
-
input_with_nil = <<~NDJSON
|
|
495
|
-
{"foo":1}
|
|
496
|
-
{"foo":null}
|
|
497
|
-
{"bar":999}
|
|
498
|
-
{"foo":3}
|
|
499
|
-
NDJSON
|
|
500
|
-
|
|
501
|
-
stdout, stderr, status = run_jrf('sum(_["foo"])', input_with_nil)
|
|
502
|
-
assert_success(status, stderr, "sum ignores nil")
|
|
503
|
-
assert_equal(%w[4], lines(stdout), "sum ignores nil output")
|
|
504
|
-
|
|
505
|
-
stdout, stderr, status = run_jrf('min(_["foo"])', input_with_nil)
|
|
506
|
-
assert_success(status, stderr, "min ignores nil")
|
|
507
|
-
assert_equal(%w[1], lines(stdout), "min ignores nil output")
|
|
508
|
-
|
|
509
|
-
stdout, stderr, status = run_jrf('max(_["foo"])', input_with_nil)
|
|
510
|
-
assert_success(status, stderr, "max ignores nil")
|
|
511
|
-
assert_equal(%w[3], lines(stdout), "max ignores nil output")
|
|
512
|
-
|
|
513
|
-
stdout, stderr, status = run_jrf('average(_["foo"])', input_with_nil)
|
|
514
|
-
assert_success(status, stderr, "average ignores nil")
|
|
515
|
-
assert_float_close(2.0, lines(stdout).first.to_f, 1e-12, "average ignores nil output")
|
|
516
|
-
|
|
517
|
-
stdout, stderr, status = run_jrf('stdev(_["foo"])', input_with_nil)
|
|
518
|
-
assert_success(status, stderr, "stdev ignores nil")
|
|
519
|
-
assert_float_close(1.0, lines(stdout).first.to_f, 1e-12, "stdev ignores nil output")
|
|
520
|
-
|
|
521
|
-
stdout, stderr, status = run_jrf('percentile(_["foo"], [0.5, 1.0])', input_with_nil)
|
|
522
|
-
assert_success(status, stderr, "percentile ignores nil")
|
|
523
|
-
assert_equal(
|
|
524
|
-
['[1,3]'],
|
|
525
|
-
lines(stdout),
|
|
526
|
-
"percentile ignores nil output"
|
|
527
|
-
)
|
|
528
|
-
|
|
529
|
-
stdout, stderr, status = run_jrf('count()', input_with_nil)
|
|
530
|
-
assert_success(status, stderr, "count with nil rows")
|
|
531
|
-
assert_equal(%w[4], lines(stdout), "count with nil rows output")
|
|
532
|
-
|
|
533
|
-
stdout, stderr, status = run_jrf('count(_["foo"])', input_with_nil)
|
|
534
|
-
assert_success(status, stderr, "count(expr) ignores nil")
|
|
535
|
-
assert_equal(%w[2], lines(stdout), "count(expr) ignores nil output")
|
|
536
|
-
|
|
537
|
-
# count_if
|
|
538
|
-
input_count_if = <<~NDJSON
|
|
539
|
-
{"x":1}
|
|
540
|
-
{"x":-2}
|
|
541
|
-
{"x":3}
|
|
542
|
-
{"x":-4}
|
|
543
|
-
{"x":5}
|
|
544
|
-
NDJSON
|
|
545
|
-
|
|
546
|
-
stdout, stderr, status = run_jrf('count_if(_["x"] > 0)', input_count_if)
|
|
547
|
-
assert_success(status, stderr, "count_if")
|
|
548
|
-
assert_equal(%w[3], lines(stdout), "count_if output")
|
|
549
|
-
|
|
550
|
-
stdout, stderr, status = run_jrf('[count_if(_["x"] > 0), count_if(_["x"] < 0)]', input_count_if)
|
|
551
|
-
assert_success(status, stderr, "count_if multiple")
|
|
552
|
-
assert_equal(["[3,2]"], lines(stdout), "count_if multiple output")
|
|
553
|
-
|
|
554
|
-
input_all_nil = <<~NDJSON
|
|
555
|
-
{"foo":null}
|
|
556
|
-
{"bar":1}
|
|
557
|
-
NDJSON
|
|
558
|
-
|
|
559
|
-
stdout, stderr, status = run_jrf('sum(_["foo"])', input_all_nil)
|
|
560
|
-
assert_success(status, stderr, "sum all nil")
|
|
561
|
-
assert_equal(%w[0], lines(stdout), "sum all nil output")
|
|
562
|
-
|
|
563
|
-
stdout, stderr, status = run_jrf('min(_["foo"])', input_all_nil)
|
|
564
|
-
assert_success(status, stderr, "min all nil")
|
|
565
|
-
assert_equal(%w[null], lines(stdout), "min all nil output")
|
|
566
|
-
|
|
567
|
-
stdout, stderr, status = run_jrf('max(_["foo"])', input_all_nil)
|
|
568
|
-
assert_success(status, stderr, "max all nil")
|
|
569
|
-
assert_equal(%w[null], lines(stdout), "max all nil output")
|
|
570
|
-
|
|
571
|
-
stdout, stderr, status = run_jrf('average(_["foo"])', input_all_nil)
|
|
572
|
-
assert_success(status, stderr, "average all nil")
|
|
573
|
-
assert_equal(%w[null], lines(stdout), "average all nil output")
|
|
574
|
-
|
|
575
|
-
stdout, stderr, status = run_jrf('stdev(_["foo"])', input_all_nil)
|
|
576
|
-
assert_success(status, stderr, "stdev all nil")
|
|
577
|
-
assert_equal(%w[null], lines(stdout), "stdev all nil output")
|
|
578
|
-
|
|
579
|
-
stdout, stderr, status = run_jrf('percentile(_["foo"], 0.5)', input_all_nil)
|
|
580
|
-
assert_success(status, stderr, "percentile all nil")
|
|
581
|
-
assert_equal(%w[null], lines(stdout), "percentile all nil output")
|
|
582
|
-
|
|
583
|
-
stdout, stderr, status = run_jrf('count(_["foo"])', input_all_nil)
|
|
584
|
-
assert_success(status, stderr, "count(expr) all nil")
|
|
585
|
-
assert_equal(%w[0], lines(stdout), "count(expr) all nil output")
|
|
586
|
-
|
|
587
|
-
input_multi_cols = <<~NDJSON
|
|
588
|
-
{"a":1,"b":10}
|
|
589
|
-
{"a":2,"b":20}
|
|
590
|
-
{"a":3,"b":30}
|
|
591
|
-
{"a":4,"b":40}
|
|
592
|
-
NDJSON
|
|
593
|
-
|
|
594
|
-
stdout, stderr, status = run_jrf('{a: percentile(_["a"], [0.25, 0.50, 1.0]), b: percentile(_["b"], [0.25, 0.50, 1.0])}', input_multi_cols)
|
|
595
|
-
assert_success(status, stderr, "nested array percentile for multiple columns")
|
|
596
|
-
assert_equal(
|
|
597
|
-
['{"a":[1,2,4],"b":[10,20,40]}'],
|
|
598
|
-
lines(stdout),
|
|
599
|
-
"nested array percentile output"
|
|
600
|
-
)
|
|
601
|
-
|
|
602
|
-
input_reduce = <<~NDJSON
|
|
603
|
-
{"s":"hello"}
|
|
604
|
-
{"s":"world"}
|
|
605
|
-
{"s":"jrf"}
|
|
606
|
-
NDJSON
|
|
607
|
-
|
|
608
|
-
stdout, stderr, status = run_jrf('_["s"] >> reduce("") { |acc, v| acc.empty? ? v : "#{acc} #{v}" }', input_reduce)
|
|
609
|
-
assert_success(status, stderr, "reduce with implicit value")
|
|
610
|
-
assert_equal(['"hello world jrf"'], lines(stdout), "reduce implicit value output")
|
|
611
|
-
|
|
612
|
-
stdout, stderr, status = run_jrf('_["s"] >> reduce("") { |acc, v| acc.empty? ? v : "#{acc} #{v}" }', input_reduce)
|
|
613
|
-
assert_success(status, stderr, "reduce in two-stage form")
|
|
614
|
-
assert_equal(['"hello world jrf"'], lines(stdout), "reduce in two-stage form output")
|
|
615
|
-
|
|
616
|
-
stdout, stderr, status = run_jrf('sum(_["foo"]) >> select(_ > 100)', input_sum)
|
|
617
|
-
assert_success(status, stderr, "post-reduce select drop")
|
|
618
|
-
assert_equal([], lines(stdout), "post-reduce select drop output")
|
|
619
|
-
|
|
620
|
-
input_whitespace_stream = "{\"foo\":1} {\"foo\":2}\n\t{\"foo\":3}\n"
|
|
621
|
-
stdout, stderr, status = run_jrf('_["foo"]', input_whitespace_stream)
|
|
622
|
-
assert_failure(status, "default NDJSON should reject same-line multi-values")
|
|
623
|
-
assert_includes(stderr, "JSON::ParserError")
|
|
624
|
-
|
|
625
|
-
stdout, stderr, status = run_jrf('_["foo"]', input_whitespace_stream, "--lax")
|
|
626
|
-
assert_success(status, stderr, "whitespace-separated JSON stream with --lax")
|
|
627
|
-
assert_equal(%w[1 2 3], lines(stdout), "whitespace-separated stream output")
|
|
628
|
-
|
|
629
|
-
input_json_seq = "\x1e{\"foo\":10}\n\x1e{\"foo\":20}\n"
|
|
630
|
-
stdout, stderr, status = run_jrf('_["foo"]', input_json_seq)
|
|
631
|
-
assert_failure(status, "RS framing requires --lax")
|
|
632
|
-
assert_includes(stderr, "JSON::ParserError")
|
|
633
|
-
|
|
634
|
-
stdout, stderr, status = run_jrf('_["foo"]', input_json_seq, "--lax")
|
|
635
|
-
assert_success(status, stderr, "json-seq style RS framing with --lax")
|
|
636
|
-
assert_equal(%w[10 20], lines(stdout), "json-seq style output")
|
|
637
|
-
|
|
638
|
-
input_lax_multiline = <<~JSONS
|
|
639
|
-
{
|
|
640
|
-
"foo": 101,
|
|
641
|
-
"bar": {"x": 1}
|
|
642
|
-
}
|
|
643
|
-
{
|
|
644
|
-
"foo": 202,
|
|
645
|
-
"bar": {"x": 2}
|
|
646
|
-
}
|
|
647
|
-
JSONS
|
|
648
|
-
stdout, stderr, status = run_jrf('_["foo"]', input_lax_multiline)
|
|
649
|
-
assert_failure(status, "default NDJSON rejects multiline objects")
|
|
650
|
-
assert_includes(stderr, "JSON::ParserError")
|
|
651
|
-
|
|
652
|
-
stdout, stderr, status = run_jrf('_["bar"]["x"]', input_lax_multiline, "--lax")
|
|
653
|
-
assert_success(status, stderr, "lax accepts multiline objects")
|
|
654
|
-
assert_equal(%w[1 2], lines(stdout), "lax multiline object output")
|
|
655
|
-
|
|
656
|
-
input_lax_mixed_separators = "{\"foo\":1}\n\x1e{\"foo\":2}\t{\"foo\":3}\n"
|
|
657
|
-
stdout, stderr, status = run_jrf('_["foo"]', input_lax_mixed_separators, "--lax")
|
|
658
|
-
assert_success(status, stderr, "lax accepts mixed whitespace and RS separators")
|
|
659
|
-
assert_equal(%w[1 2 3], lines(stdout), "lax mixed separators output")
|
|
660
|
-
|
|
661
|
-
input_lax_with_escaped_newline = "{\"s\":\"line1\\nline2\"}\n{\"s\":\"ok\"}\n"
|
|
662
|
-
stdout, stderr, status = run_jrf('_["s"]', input_lax_with_escaped_newline, "--lax")
|
|
663
|
-
assert_success(status, stderr, "lax handles escaped newlines in strings")
|
|
664
|
-
assert_equal(['"line1\nline2"', '"ok"'], lines(stdout), "lax escaped newline string output")
|
|
665
|
-
|
|
666
|
-
input_lax_trailing_rs = "\x1e{\"foo\":9}\n\x1e"
|
|
667
|
-
stdout, stderr, status = run_jrf('_["foo"]', input_lax_trailing_rs, "--lax")
|
|
668
|
-
assert_success(status, stderr, "lax ignores trailing separator")
|
|
669
|
-
assert_equal(%w[9], lines(stdout), "lax trailing separator output")
|
|
670
|
-
|
|
671
|
-
chunked_lax_out = RecordingRunner.new(
|
|
672
|
-
inputs: [ChunkedSource.new("{\"foo\":1}\n\x1e{\"foo\":2}\n\t{\"foo\":3}\n")],
|
|
673
|
-
out: StringIO.new,
|
|
674
|
-
err: StringIO.new,
|
|
675
|
-
lax: true
|
|
676
|
-
)
|
|
677
|
-
chunked_lax_out.run('_["foo"]')
|
|
678
|
-
assert_equal(%w[1 2 3], lines(chunked_lax_out.writes.join), "lax mode streams chunked input without whole-input reads")
|
|
679
|
-
|
|
680
|
-
Dir.mktmpdir do |dir|
|
|
681
|
-
one = File.join(dir, "one.json")
|
|
682
|
-
two = File.join(dir, "two.json")
|
|
683
|
-
File.write(one, "1")
|
|
684
|
-
File.write(two, "2")
|
|
685
|
-
|
|
686
|
-
stdout, stderr, status = Open3.capture3("./exe/jrf", "--lax", "_", one, two)
|
|
687
|
-
assert_success(status, stderr, "lax keeps file boundaries")
|
|
688
|
-
assert_equal(%w[1 2], lines(stdout), "lax does not merge JSON across file boundaries")
|
|
689
|
-
end
|
|
690
|
-
|
|
691
|
-
stdout, stderr, status = run_jrf('select(_["x"] > ) >> _["foo"]', "")
|
|
692
|
-
assert_failure(status, "syntax error should fail before row loop")
|
|
693
|
-
assert_includes(stderr, "syntax error")
|
|
694
|
-
|
|
695
|
-
stdout, stderr, status = run_jrf('([)] >> _', "")
|
|
696
|
-
assert_failure(status, "mismatched delimiter should fail")
|
|
697
|
-
assert_includes(stderr, "mismatched delimiter")
|
|
698
|
-
|
|
699
|
-
stdout, stderr, status = run_jrf('(_["x"] >> _["y"]', "")
|
|
700
|
-
assert_failure(status, "unclosed delimiter should fail")
|
|
701
|
-
assert_includes(stderr, "unclosed delimiter")
|
|
702
|
-
|
|
703
|
-
input_broken_tail = <<~NDJSON
|
|
704
|
-
{"foo":1}
|
|
705
|
-
{"foo":2}
|
|
706
|
-
{"foo":
|
|
707
|
-
NDJSON
|
|
708
|
-
|
|
709
|
-
stdout, stderr, status = run_jrf('sum(_["foo"])', input_broken_tail)
|
|
710
|
-
assert_failure(status, "broken input should fail")
|
|
711
|
-
assert_equal(%w[3], lines(stdout), "reducers flush before parse error")
|
|
712
|
-
assert_includes(stderr, "JSON::ParserError")
|
|
713
|
-
|
|
714
|
-
input_chain = <<~NDJSON
|
|
715
|
-
{"foo":{"bar":{"z":1},"keep":true}}
|
|
716
|
-
{"foo":{"bar":{"z":2},"keep":false}}
|
|
717
|
-
{"foo":{"bar":{"z":3},"keep":true}}
|
|
718
|
-
NDJSON
|
|
719
|
-
|
|
720
|
-
stdout, stderr, status = run_jrf('_["foo"] >> select(_["keep"]) >> _["bar"] >> select(_["z"] > 1) >> _["z"]', input_chain)
|
|
721
|
-
assert_success(status, stderr, "select/extract chain")
|
|
722
|
-
assert_equal(%w[3], lines(stdout), "chain output")
|
|
723
|
-
|
|
724
|
-
input_map = <<~NDJSON
|
|
725
|
-
{"values":[1,10,100]}
|
|
726
|
-
{"values":[2,20,200]}
|
|
727
|
-
{"values":[3,30,300]}
|
|
728
|
-
NDJSON
|
|
729
|
-
|
|
730
|
-
stdout, stderr, status = run_jrf('_["values"] >> map { |x| sum(x) }', input_map)
|
|
731
|
-
assert_success(status, stderr, "map with sum")
|
|
732
|
-
assert_equal(['[6,60,600]'], lines(stdout), "map with sum output")
|
|
733
|
-
|
|
734
|
-
stdout, stderr, status = run_jrf('_["values"] >> map { |x| min(x) }', input_map)
|
|
735
|
-
assert_success(status, stderr, "map with min")
|
|
736
|
-
assert_equal(['[1,10,100]'], lines(stdout), "map with min output")
|
|
737
|
-
|
|
738
|
-
stdout, stderr, status = run_jrf('_["values"] >> map { |x| max(x) }', input_map)
|
|
739
|
-
assert_success(status, stderr, "map with max")
|
|
740
|
-
assert_equal(['[3,30,300]'], lines(stdout), "map with max output")
|
|
741
|
-
|
|
742
|
-
stdout, stderr, status = run_jrf('_["values"] >> map { |x| sum(_[0] + x) }', input_map)
|
|
743
|
-
assert_success(status, stderr, "map keeps ambient _")
|
|
744
|
-
assert_equal(['[12,66,606]'], lines(stdout), "map ambient _ output")
|
|
745
|
-
|
|
746
|
-
stdout, stderr, status = run_jrf('_["values"] >> map { |x| reduce(0) { |acc, v| acc + v } }', input_map)
|
|
747
|
-
assert_success(status, stderr, "map with reduce")
|
|
748
|
-
assert_equal(['[6,60,600]'], lines(stdout), "map with reduce output")
|
|
749
|
-
|
|
750
|
-
input_map_varying = <<~NDJSON
|
|
751
|
-
[1,10]
|
|
752
|
-
[2,20,200]
|
|
753
|
-
[3]
|
|
754
|
-
NDJSON
|
|
755
|
-
|
|
756
|
-
stdout, stderr, status = run_jrf('map { |x| sum(x) }', input_map_varying)
|
|
757
|
-
assert_success(status, stderr, "map varying lengths")
|
|
758
|
-
assert_equal(['[6,30,200]'], lines(stdout), "map varying lengths output")
|
|
759
|
-
|
|
760
|
-
input_map_unsorted = <<~NDJSON
|
|
761
|
-
{"values":[3,30]}
|
|
762
|
-
{"values":[1,10]}
|
|
763
|
-
{"values":[2,20]}
|
|
764
|
-
NDJSON
|
|
765
|
-
|
|
766
|
-
stdout, stderr, status = run_jrf('_["values"] >> map { |x| group }', input_map)
|
|
767
|
-
assert_success(status, stderr, "map with group")
|
|
768
|
-
assert_equal(['[[1,2,3],[10,20,30],[100,200,300]]'], lines(stdout), "map with group output")
|
|
769
|
-
|
|
770
|
-
stdout, stderr, status = run_jrf('_["values"] >> map { |x| sort }', input_map_unsorted)
|
|
771
|
-
assert_success(status, stderr, "map with sort default key")
|
|
772
|
-
assert_equal(['[[1,2,3],[10,20,30]]'], lines(stdout), "map with sort default key output")
|
|
773
|
-
|
|
774
|
-
input_map_values = <<~NDJSON
|
|
775
|
-
{"a":1,"b":10}
|
|
776
|
-
{"a":2,"b":20}
|
|
777
|
-
{"a":3,"b":30}
|
|
778
|
-
NDJSON
|
|
779
|
-
|
|
780
|
-
stdout, stderr, status = run_jrf('map_values { |v| sum(v) }', input_map_values)
|
|
781
|
-
assert_success(status, stderr, "map_values with sum")
|
|
782
|
-
assert_equal(['{"a":6,"b":60}'], lines(stdout), "map_values with sum output")
|
|
783
|
-
|
|
784
|
-
stdout, stderr, status = run_jrf('map_values { |v| min(v) }', input_map_values)
|
|
785
|
-
assert_success(status, stderr, "map_values with min")
|
|
786
|
-
assert_equal(['{"a":1,"b":10}'], lines(stdout), "map_values with min output")
|
|
787
|
-
|
|
788
|
-
input_map_values_varying = <<~NDJSON
|
|
789
|
-
{"a":1}
|
|
790
|
-
{"a":2,"b":20}
|
|
791
|
-
{"a":3,"b":30}
|
|
792
|
-
NDJSON
|
|
793
|
-
|
|
794
|
-
stdout, stderr, status = run_jrf('map_values { |v| sum(v) }', input_map_values_varying)
|
|
795
|
-
assert_success(status, stderr, "map_values varying keys")
|
|
796
|
-
assert_equal(['{"a":6,"b":50}'], lines(stdout), "map_values varying keys output")
|
|
797
|
-
|
|
798
|
-
stdout, stderr, status = run_jrf('map_values { |v| count(v) }', input_map_values)
|
|
799
|
-
assert_success(status, stderr, "map_values with count")
|
|
800
|
-
assert_equal(['{"a":3,"b":3}'], lines(stdout), "map_values with count output")
|
|
801
|
-
|
|
802
|
-
stdout, stderr, status = run_jrf('map_values { |v| group }', input_map_values)
|
|
803
|
-
assert_success(status, stderr, "map_values with group")
|
|
804
|
-
assert_equal(['{"a":[1,2,3],"b":[10,20,30]}'], lines(stdout), "map_values with group output")
|
|
805
|
-
|
|
806
|
-
stdout, stderr, status = run_jrf('map_values { |v| sum(_["a"] + v) }', input_map_values)
|
|
807
|
-
assert_success(status, stderr, "map_values keeps ambient _")
|
|
808
|
-
assert_equal(['{"a":12,"b":66}'], lines(stdout), "map_values ambient _ output")
|
|
809
|
-
|
|
810
|
-
stdout, stderr, status = run_jrf('map_values { |v| reduce(0) { |acc, x| acc + x } }', input_map_values)
|
|
811
|
-
assert_success(status, stderr, "map_values with reduce")
|
|
812
|
-
assert_equal(['{"a":6,"b":60}'], lines(stdout), "map_values with reduce output")
|
|
813
|
-
|
|
814
|
-
stdout, stderr, status = run_jrf('map { |k, v| "#{k}:#{v}" }', input_map_values)
|
|
815
|
-
assert_success(status, stderr, "map over hash transform")
|
|
816
|
-
assert_equal(['["a:1","b:10"]', '["a:2","b:20"]', '["a:3","b:30"]'], lines(stdout), "map over hash transform output")
|
|
817
|
-
|
|
818
|
-
stdout, stderr, status = run_jrf('map { |pair| pair }', input_map_values)
|
|
819
|
-
assert_success(status, stderr, "map over hash single block arg")
|
|
820
|
-
assert_equal(['[["a",1],["b",10]]', '[["a",2],["b",20]]', '[["a",3],["b",30]]'], lines(stdout), "map over hash single block arg output")
|
|
821
|
-
|
|
822
|
-
stdout, stderr, status = run_jrf('map { |k, v| select(v >= 10 && k != "a") }', input_map_values)
|
|
823
|
-
assert_success(status, stderr, "map over hash transform with select")
|
|
824
|
-
assert_equal(['[10]', '[20]', '[30]'], lines(stdout), "map over hash transform with select output")
|
|
825
|
-
|
|
826
|
-
stdout, stderr, status = run_jrf('map { |k, v| sum(v + k.length) }', input_map_values)
|
|
827
|
-
assert_success(status, stderr, "map over hash with sum")
|
|
828
|
-
assert_equal(['[9,63]'], lines(stdout), "map over hash with sum output")
|
|
829
|
-
|
|
830
|
-
stdout, stderr, status = run_jrf('map { |k, v| sum(_["a"] + v + k.length) }', input_map_values)
|
|
831
|
-
assert_success(status, stderr, "map over hash keeps ambient _")
|
|
832
|
-
assert_equal(['[15,69]'], lines(stdout), "map over hash ambient _ output")
|
|
833
|
-
|
|
834
|
-
stdout, stderr, status = run_jrf('select(false) >> map { |x| sum(x) }', input_map)
|
|
835
|
-
assert_success(status, stderr, "map no matches")
|
|
836
|
-
assert_equal([], lines(stdout), "map no matches output")
|
|
837
|
-
|
|
838
|
-
stdout, stderr, status = run_jrf('select(false) >> map_values { |v| sum(v) }', input_map_values)
|
|
839
|
-
assert_success(status, stderr, "map_values no matches")
|
|
840
|
-
assert_equal([], lines(stdout), "map_values no matches output")
|
|
841
|
-
|
|
842
|
-
stdout, stderr, status = run_jrf('map_values { |v| sum(v) } >> map_values { |v| v * 10 }', input_map_values)
|
|
843
|
-
assert_success(status, stderr, "map_values piped to map_values passthrough")
|
|
844
|
-
assert_equal(['{"a":60,"b":600}'], lines(stdout), "map_values piped output")
|
|
845
|
-
|
|
846
|
-
# map/map_values transformation (no reducers)
|
|
847
|
-
stdout, stderr, status = run_jrf('_["values"] >> map { |x| x + 1 }', input_map)
|
|
848
|
-
assert_success(status, stderr, "map transform")
|
|
849
|
-
assert_equal(['[2,11,101]', '[3,21,201]', '[4,31,301]'], lines(stdout), "map transform output")
|
|
850
|
-
|
|
851
|
-
stdout, stderr, status = run_jrf('_["values"] >> map { |x| select(x >= 20) }', input_map)
|
|
852
|
-
assert_success(status, stderr, "map transform with select")
|
|
853
|
-
assert_equal(['[100]', '[20,200]', '[30,300]'], lines(stdout), "map transform with select output")
|
|
854
|
-
|
|
855
|
-
stdout, stderr, status = run_jrf('map_values { |v| v * 2 }', input_map_values)
|
|
856
|
-
assert_success(status, stderr, "map_values transform")
|
|
857
|
-
assert_equal(['{"a":2,"b":20}', '{"a":4,"b":40}', '{"a":6,"b":60}'], lines(stdout), "map_values transform output")
|
|
858
|
-
|
|
859
|
-
stdout, stderr, status = run_jrf('map_values { |v| select(v >= 10) }', input_map_values)
|
|
860
|
-
assert_success(status, stderr, "map_values transform with select")
|
|
861
|
-
assert_equal(['{"b":10}', '{"b":20}', '{"b":30}'], lines(stdout), "map_values transform with select output")
|
|
862
|
-
|
|
863
|
-
stdout, stderr, status = run_jrf('_["values"] >> map { |x| x + 1 } >> map { |x| x * 10 }', input_map)
|
|
864
|
-
assert_success(status, stderr, "chained map transforms")
|
|
865
|
-
assert_equal(['[20,110,1010]', '[30,210,2010]', '[40,310,3010]'], lines(stdout), "chained map transforms output")
|
|
866
|
-
|
|
867
|
-
stdout, stderr, status = run_jrf('map { map { |y| [ sum(y[0]), sum(y[1]) ] } }', "[[[1,2]]]\n[[[3,4]]]\n")
|
|
868
|
-
assert_success(status, stderr, "nested map reducer binds to current target")
|
|
869
|
-
assert_equal(['[[[4,6]]]'], lines(stdout), "nested map reducer output")
|
|
870
|
-
|
|
871
|
-
stdout, stderr, status = run_jrf('map_values { |obj| map_values { |v| sum(v) } }', "{\"a\":{\"x\":1,\"y\":2},\"b\":{\"x\":10,\"y\":20}}\n{\"a\":{\"x\":3,\"y\":4},\"b\":{\"x\":30,\"y\":40}}\n")
|
|
872
|
-
assert_success(status, stderr, "nested map_values reducer binds to current target")
|
|
873
|
-
assert_equal(['{"a":{"x":4,"y":6},"b":{"x":40,"y":60}}'], lines(stdout), "nested map_values reducer output")
|
|
874
|
-
|
|
875
|
-
# apply - aggregation
|
|
876
|
-
stdout, stderr, status = run_jrf('[apply { |x| sum(x["foo"]) }, _.length]', '[{"foo":1},{"foo":2}]' + "\n" + '[{"foo":10}]' + "\n")
|
|
877
|
-
assert_success(status, stderr, "apply with sum")
|
|
878
|
-
assert_equal(["[3,2]", "[10,1]"], lines(stdout), "apply with sum output")
|
|
879
|
-
|
|
880
|
-
# apply - passthrough
|
|
881
|
-
stdout, stderr, status = run_jrf('apply { |x| x["foo"] }', '[{"foo":1},{"foo":2}]' + "\n")
|
|
882
|
-
assert_success(status, stderr, "apply passthrough")
|
|
883
|
-
assert_equal(["[1,2]"], lines(stdout), "apply passthrough output")
|
|
884
|
-
|
|
885
|
-
# apply - percentile
|
|
886
|
-
stdout, stderr, status = run_jrf('apply { |x| percentile(x, 0.5) }', '[10,20,30]' + "\n")
|
|
887
|
-
assert_success(status, stderr, "apply with percentile")
|
|
888
|
-
assert_equal(["20"], lines(stdout), "apply with percentile output")
|
|
889
|
-
|
|
890
|
-
# apply with explicit collection inside map
|
|
891
|
-
stdout, stderr, status = run_jrf('map { |o| [apply(o["vals"]) { |x| sum(x) }, o["name"]] }', '[{"name":"a","vals":[1,2]},{"name":"b","vals":[10,20]}]' + "\n")
|
|
892
|
-
assert_success(status, stderr, "apply with explicit collection")
|
|
893
|
-
assert_equal(['[[3,"a"],[30,"b"]]'], lines(stdout), "apply with explicit collection output")
|
|
894
|
-
|
|
895
|
-
# map with explicit collection
|
|
896
|
-
stdout, stderr, status = run_jrf('map(_["items"]) { |x| x * 2 }', '{"items":[1,2,3]}' + "\n")
|
|
897
|
-
assert_success(status, stderr, "map with explicit collection")
|
|
898
|
-
assert_equal(["[2,4,6]"], lines(stdout), "map with explicit collection output")
|
|
899
|
-
|
|
900
|
-
# map_values with explicit collection
|
|
901
|
-
stdout, stderr, status = run_jrf('map_values(_["data"]) { |v| v * 10 }', '{"data":{"a":1,"b":2}}' + "\n")
|
|
902
|
-
assert_success(status, stderr, "map_values with explicit collection")
|
|
903
|
-
assert_equal(['{"a":10,"b":20}'], lines(stdout), "map_values with explicit collection output")
|
|
904
|
-
|
|
905
|
-
input_gb = <<~NDJSON
|
|
906
|
-
{"status":200,"path":"/a","latency":10}
|
|
907
|
-
{"status":404,"path":"/b","latency":50}
|
|
908
|
-
{"status":200,"path":"/c","latency":30}
|
|
909
|
-
{"status":200,"path":"/d","latency":20}
|
|
910
|
-
NDJSON
|
|
911
|
-
|
|
912
|
-
stdout, stderr, status = run_jrf('group_by(_["status"]) { count() }', input_gb)
|
|
913
|
-
assert_success(status, stderr, "group_by with count")
|
|
914
|
-
assert_equal(['{"200":3,"404":1}'], lines(stdout), "group_by with count output")
|
|
915
|
-
|
|
916
|
-
stdout, stderr, status = run_jrf('group_by(_["status"]) { |row| sum(row["latency"]) }', input_gb)
|
|
917
|
-
assert_success(status, stderr, "group_by with sum")
|
|
918
|
-
assert_equal(['{"200":60,"404":50}'], lines(stdout), "group_by with sum output")
|
|
919
|
-
|
|
920
|
-
stdout, stderr, status = run_jrf('group_by(_["status"]) { |row| average(row["latency"]) }', input_gb)
|
|
921
|
-
assert_success(status, stderr, "group_by with average")
|
|
922
|
-
result = JSON.parse(lines(stdout).first)
|
|
923
|
-
assert_float_close(20.0, result["200"], 1e-12, "group_by average 200")
|
|
924
|
-
assert_float_close(50.0, result["404"], 1e-12, "group_by average 404")
|
|
925
|
-
|
|
926
|
-
stdout, stderr, status = run_jrf('group_by(_["status"])', input_gb)
|
|
927
|
-
assert_success(status, stderr, "group_by default (collect rows)")
|
|
928
|
-
result = JSON.parse(lines(stdout).first)
|
|
929
|
-
assert_equal(3, result["200"].length, "group_by default 200 count")
|
|
930
|
-
assert_equal(1, result["404"].length, "group_by default 404 count")
|
|
931
|
-
assert_equal("/a", result["200"][0]["path"], "group_by default first row")
|
|
932
|
-
|
|
933
|
-
stdout, stderr, status = run_jrf('group_by(_["status"]) { |row| group(row["path"]) }', input_gb)
|
|
934
|
-
assert_success(status, stderr, "group_by with group(expr)")
|
|
935
|
-
assert_equal(['{"200":["/a","/c","/d"],"404":["/b"]}'], lines(stdout), "group_by with group(expr) output")
|
|
936
|
-
|
|
937
|
-
stdout, stderr, status = run_jrf('group_by(_["status"]) { group }', input_gb)
|
|
938
|
-
assert_success(status, stderr, "group_by with implicit group")
|
|
939
|
-
result = JSON.parse(lines(stdout).first)
|
|
940
|
-
assert_equal(3, result["200"].length, "group_by implicit group 200 count")
|
|
941
|
-
assert_equal("/a", result["200"][0]["path"], "group_by implicit group first row")
|
|
942
|
-
|
|
943
|
-
stdout, stderr, status = run_jrf('group_by(_["status"]) { |row| min(row["latency"]) }', input_gb)
|
|
944
|
-
assert_success(status, stderr, "group_by with min")
|
|
945
|
-
assert_equal(['{"200":10,"404":50}'], lines(stdout), "group_by with min output")
|
|
946
|
-
|
|
947
|
-
stdout, stderr, status = run_jrf('group_by(_["status"]) { |row| {total: sum(row["latency"]), n: count()} }', input_gb)
|
|
948
|
-
assert_success(status, stderr, "group_by with multi-reducer")
|
|
949
|
-
assert_equal(['{"200":{"total":60,"n":3},"404":{"total":50,"n":1}}'], lines(stdout), "group_by multi-reducer output")
|
|
950
|
-
|
|
951
|
-
stdout, stderr, status = run_jrf('group_by(_["status"]) { reduce(0) { |acc, row| acc + row["latency"] } }', input_gb)
|
|
952
|
-
assert_success(status, stderr, "group_by with reduce")
|
|
953
|
-
assert_equal(['{"200":60,"404":50}'], lines(stdout), "group_by with reduce output")
|
|
954
|
-
|
|
955
|
-
stdout, stderr, status = run_jrf('select(false) >> group_by(_["status"]) { count() }', input_gb)
|
|
956
|
-
assert_success(status, stderr, "group_by no matches")
|
|
957
|
-
assert_equal([], lines(stdout), "group_by no matches output")
|
|
958
|
-
|
|
959
|
-
stdout, stderr, status = run_jrf('group_by(_["status"]) { count() } >> _[200]', input_gb)
|
|
960
|
-
assert_success(status, stderr, "group_by then extract")
|
|
961
|
-
assert_equal(%w[3], lines(stdout), "group_by then extract output")
|
|
962
|
-
|
|
963
|
-
# === Library API (Jrf.new) ===
|
|
964
|
-
|
|
965
|
-
require_relative "../lib/jrf"
|
|
966
|
-
|
|
967
|
-
# passthrough
|
|
968
|
-
j = Jrf.new(proc { _ })
|
|
969
|
-
assert_equal([{"a" => 1}, {"a" => 2}], j.call([{"a" => 1}, {"a" => 2}]), "library passthrough")
|
|
970
|
-
|
|
971
|
-
# extract
|
|
972
|
-
j = Jrf.new(proc { _["a"] })
|
|
973
|
-
assert_equal([1, 2], j.call([{"a" => 1}, {"a" => 2}]), "library extract")
|
|
974
|
-
|
|
975
|
-
# select + extract (two stages)
|
|
976
|
-
j = Jrf.new(
|
|
977
|
-
proc { select(_["a"] > 1) },
|
|
978
|
-
proc { _["a"] }
|
|
979
|
-
)
|
|
980
|
-
assert_equal([2, 3], j.call([{"a" => 1}, {"a" => 2}, {"a" => 3}]), "library select + extract")
|
|
981
|
-
|
|
982
|
-
# sum
|
|
983
|
-
j = Jrf.new(proc { sum(_["a"]) })
|
|
984
|
-
assert_equal([6], j.call([{"a" => 1}, {"a" => 2}, {"a" => 3}]), "library sum")
|
|
985
|
-
|
|
986
|
-
# sum with literal on left
|
|
987
|
-
j = Jrf.new(proc { sum(2 * _["a"]) })
|
|
988
|
-
assert_equal([12], j.call([{"a" => 1}, {"a" => 2}, {"a" => 3}]), "library sum literal on left")
|
|
989
|
-
|
|
990
|
-
# structured reducers
|
|
991
|
-
j = Jrf.new(proc { {total: sum(_["a"]), n: count()} })
|
|
992
|
-
assert_equal([{total: 6, n: 3}], j.call([{"a" => 1}, {"a" => 2}, {"a" => 3}]), "library structured reducers")
|
|
993
|
-
|
|
994
|
-
# map transform
|
|
995
|
-
j = Jrf.new(proc { map { |x| x + 1 } })
|
|
996
|
-
assert_equal([[2, 3], [4, 5]], j.call([[1, 2], [3, 4]]), "library map transform")
|
|
997
|
-
|
|
998
|
-
# map reduce
|
|
999
|
-
j = Jrf.new(proc { map { |x| sum(x) } })
|
|
1000
|
-
assert_equal([[4, 6]], j.call([[1, 2], [3, 4]]), "library map reduce")
|
|
1001
|
-
|
|
1002
|
-
# nested map reduce binds to current target
|
|
1003
|
-
j = Jrf.new(proc { map { map { |y| [sum(y[0]), sum(y[1])] } } })
|
|
1004
|
-
assert_equal([[[[4, 6]]]], j.call([[[[1, 2]]], [[[3, 4]]]]), "library nested map reduce")
|
|
1005
|
-
|
|
1006
|
-
# map_values transform
|
|
1007
|
-
j = Jrf.new(proc { map_values { |v| v * 10 } })
|
|
1008
|
-
assert_equal([{"a" => 10, "b" => 20}], j.call([{"a" => 1, "b" => 2}]), "library map_values transform")
|
|
1009
|
-
|
|
1010
|
-
# nested map_values reduce binds to current target
|
|
1011
|
-
j = Jrf.new(proc { map_values { |obj| map_values { |v| sum(v) } } })
|
|
1012
|
-
assert_equal([{"a" => {"x" => 4, "y" => 6}, "b" => {"x" => 40, "y" => 60}}], j.call([{"a" => {"x" => 1, "y" => 2}, "b" => {"x" => 10, "y" => 20}}, {"a" => {"x" => 3, "y" => 4}, "b" => {"x" => 30, "y" => 40}}]), "library nested map_values reduce")
|
|
1013
|
-
|
|
1014
|
-
# map hash transform
|
|
1015
|
-
j = Jrf.new(proc { map { |k, v| "#{k}=#{v}" } })
|
|
1016
|
-
assert_equal([["a=1", "b=2"]], j.call([{"a" => 1, "b" => 2}]), "library map hash transform")
|
|
1017
|
-
|
|
1018
|
-
# map hash single block arg
|
|
1019
|
-
j = Jrf.new(proc { map { |pair| pair } })
|
|
1020
|
-
assert_equal([[["a", 1], ["b", 2]]], j.call([{"a" => 1, "b" => 2}]), "library map hash single block arg")
|
|
1021
|
-
|
|
1022
|
-
# map hash reduce
|
|
1023
|
-
j = Jrf.new(proc { map { |k, v| sum(v + k.length) } })
|
|
1024
|
-
assert_equal([[5, 7]], j.call([{"a" => 1, "b" => 2}, {"a" => 2, "b" => 3}]), "library map hash reduce")
|
|
1025
|
-
|
|
1026
|
-
# apply - aggregation
|
|
1027
|
-
j = Jrf.new(proc { [apply { |x| sum(x["foo"]) }, _.length] })
|
|
1028
|
-
assert_equal([[3, 2], [10, 1]], j.call([[{"foo" => 1}, {"foo" => 2}], [{"foo" => 10}]]), "library apply reducer")
|
|
1029
|
-
|
|
1030
|
-
# apply - passthrough
|
|
1031
|
-
j = Jrf.new(proc { apply { |x| x["foo"] } })
|
|
1032
|
-
assert_equal([[1, 2]], j.call([[{"foo" => 1}, {"foo" => 2}]]), "library apply passthrough")
|
|
1033
|
-
|
|
1034
|
-
# apply - percentile
|
|
1035
|
-
j = Jrf.new(proc { apply { |x| percentile(x, 0.5) } })
|
|
1036
|
-
assert_equal([20], j.call([[10, 20, 30]]), "library apply percentile")
|
|
1037
|
-
|
|
1038
|
-
# apply with explicit collection inside map
|
|
1039
|
-
j = Jrf.new(proc { map { |o| [apply(o["vals"]) { |x| sum(x) }, o["name"]] } })
|
|
1040
|
-
assert_equal([[[3, "a"], [30, "b"]]], j.call([[{"name" => "a", "vals" => [1, 2]}, {"name" => "b", "vals" => [10, 20]}]]), "library apply explicit collection")
|
|
1041
|
-
|
|
1042
|
-
# map with explicit collection
|
|
1043
|
-
j = Jrf.new(proc { map(_["items"]) { |x| x * 2 } })
|
|
1044
|
-
assert_equal([[2, 4, 6]], j.call([{"items" => [1, 2, 3]}]), "library map explicit collection")
|
|
1045
|
-
|
|
1046
|
-
# map_values with explicit collection
|
|
1047
|
-
j = Jrf.new(proc { map_values(_["data"]) { |v| v * 10 } })
|
|
1048
|
-
assert_equal([{"a" => 10, "b" => 20}], j.call([{"data" => {"a" => 1, "b" => 2}}]), "library map_values explicit collection")
|
|
1049
|
-
|
|
1050
|
-
# group_by
|
|
1051
|
-
j = Jrf.new(proc { group_by(_["k"]) { count() } })
|
|
1052
|
-
assert_equal([{"x" => 2, "y" => 1}], j.call([{"k" => "x"}, {"k" => "x"}, {"k" => "y"}]), "library group_by")
|
|
1053
|
-
|
|
1054
|
-
# reducer configuration is fixed by the first row
|
|
1055
|
-
j = Jrf.new(proc { percentile(_["a"], _["p"]) })
|
|
1056
|
-
assert_equal([2], j.call([{"a" => 1, "p" => 0.5}, {"a" => 2, "p" => [0.5, 1.0]}, {"a" => 3, "p" => [0.5, 1.0]}]), "library percentile configuration fixed by first row")
|
|
1057
|
-
|
|
1058
|
-
counting_percentiles = Class.new do
|
|
1059
|
-
include Enumerable
|
|
1060
|
-
|
|
1061
|
-
attr_reader :each_calls
|
|
1062
|
-
|
|
1063
|
-
def initialize(values)
|
|
1064
|
-
@values = values
|
|
1065
|
-
@each_calls = 0
|
|
1066
|
-
end
|
|
1067
|
-
|
|
1068
|
-
def each(&block)
|
|
1069
|
-
@each_calls += 1
|
|
1070
|
-
@values.each(&block)
|
|
1071
|
-
end
|
|
1072
|
-
end.new([0.25, 0.5, 1.0])
|
|
1073
|
-
|
|
1074
|
-
j = Jrf.new(proc { percentile(_["a"], counting_percentiles) })
|
|
1075
|
-
assert_equal([[1, 2, 3]], j.call([{"a" => 1}, {"a" => 2}, {"a" => 3}]), "library percentile enumerable values")
|
|
1076
|
-
assert_equal(1, counting_percentiles.each_calls, "library percentile materializes enumerable once")
|
|
1077
|
-
|
|
1078
|
-
# reducer then passthrough
|
|
1079
|
-
j = Jrf.new(
|
|
1080
|
-
proc { sum(_["a"]) },
|
|
1081
|
-
proc { _ + 1 }
|
|
1082
|
-
)
|
|
1083
|
-
assert_equal([7], j.call([{"a" => 1}, {"a" => 2}, {"a" => 3}]), "library reducer then passthrough")
|
|
1084
|
-
|
|
1085
|
-
# closure over local variables
|
|
1086
|
-
threshold = 2
|
|
1087
|
-
j = Jrf.new(proc { select(_["a"] > threshold) })
|
|
1088
|
-
assert_equal([{"a" => 3}], j.call([{"a" => 1}, {"a" => 2}, {"a" => 3}]), "library closure")
|
|
1089
|
-
|
|
1090
|
-
# empty input
|
|
1091
|
-
j = Jrf.new(proc { sum(_) })
|
|
1092
|
-
assert_equal([], j.call([]), "library empty input")
|
|
1093
|
-
|
|
1094
|
-
ctx = Jrf::RowContext.new
|
|
1095
|
-
stage = Jrf::Stage.new(ctx, proc { })
|
|
1096
|
-
first_token = stage.step_reduce(1, initial: 0) { |acc, v| acc + v }
|
|
1097
|
-
assert_equal(0, first_token.index, "step_reduce returns token while classifying reducer stage")
|
|
1098
|
-
stage.instance_variable_set(:@mode, :reducer)
|
|
1099
|
-
stage.instance_variable_set(:@cursor, 0)
|
|
1100
|
-
second_token = stage.step_reduce(2, initial: 0) { |acc, v| acc + v }
|
|
1101
|
-
raise "expected DROPPED for established reducer slot" unless second_token.equal?(Jrf::Control::DROPPED)
|
|
1102
|
-
|
|
1103
|
-
puts "ok"
|