wukong 3.0.0.pre3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/Gemfile +1 -0
  2. data/README.md +689 -50
  3. data/bin/wu-local +1 -74
  4. data/diagrams/wu_local.dot +39 -0
  5. data/diagrams/wu_local.dot.png +0 -0
  6. data/examples/loadable.rb +2 -0
  7. data/examples/string_reverser.rb +7 -0
  8. data/lib/hanuman/stage.rb +2 -2
  9. data/lib/wukong.rb +21 -10
  10. data/lib/wukong/dataflow.rb +2 -5
  11. data/lib/wukong/doc_helpers.rb +14 -0
  12. data/lib/wukong/doc_helpers/dataflow_handler.rb +29 -0
  13. data/lib/wukong/doc_helpers/field_handler.rb +91 -0
  14. data/lib/wukong/doc_helpers/processor_handler.rb +29 -0
  15. data/lib/wukong/driver.rb +11 -1
  16. data/lib/wukong/local.rb +40 -0
  17. data/lib/wukong/local/event_machine_driver.rb +27 -0
  18. data/lib/wukong/local/runner.rb +98 -0
  19. data/lib/wukong/local/stdio_driver.rb +44 -0
  20. data/lib/wukong/local/tcp_driver.rb +47 -0
  21. data/lib/wukong/logger.rb +16 -7
  22. data/lib/wukong/plugin.rb +48 -0
  23. data/lib/wukong/processor.rb +57 -15
  24. data/lib/wukong/rake_helper.rb +6 -0
  25. data/lib/wukong/runner.rb +151 -128
  26. data/lib/wukong/runner/boot_sequence.rb +123 -0
  27. data/lib/wukong/runner/code_loader.rb +52 -0
  28. data/lib/wukong/runner/deploy_pack_loader.rb +75 -0
  29. data/lib/wukong/runner/help_message.rb +42 -0
  30. data/lib/wukong/spec_helpers.rb +4 -12
  31. data/lib/wukong/spec_helpers/integration_tests.rb +150 -0
  32. data/lib/wukong/spec_helpers/{integration_driver_matchers.rb → integration_tests/integration_test_matchers.rb} +28 -62
  33. data/lib/wukong/spec_helpers/integration_tests/integration_test_runner.rb +97 -0
  34. data/lib/wukong/spec_helpers/shared_examples.rb +19 -10
  35. data/lib/wukong/spec_helpers/unit_tests.rb +134 -0
  36. data/lib/wukong/spec_helpers/{processor_methods.rb → unit_tests/unit_test_driver.rb} +42 -8
  37. data/lib/wukong/spec_helpers/{spec_driver_matchers.rb → unit_tests/unit_test_matchers.rb} +6 -32
  38. data/lib/wukong/spec_helpers/unit_tests/unit_test_runner.rb +54 -0
  39. data/lib/wukong/version.rb +1 -1
  40. data/lib/wukong/widget/filters.rb +134 -8
  41. data/lib/wukong/widget/processors.rb +64 -5
  42. data/lib/wukong/widget/reducers/bin.rb +68 -18
  43. data/lib/wukong/widget/reducers/count.rb +12 -0
  44. data/lib/wukong/widget/reducers/group.rb +48 -5
  45. data/lib/wukong/widget/reducers/group_concat.rb +30 -2
  46. data/lib/wukong/widget/reducers/moments.rb +4 -4
  47. data/lib/wukong/widget/reducers/sort.rb +53 -3
  48. data/lib/wukong/widget/serializers.rb +37 -12
  49. data/lib/wukong/widget/utils.rb +1 -1
  50. data/spec/spec_helper.rb +20 -2
  51. data/spec/wukong/driver_spec.rb +2 -0
  52. data/spec/wukong/local/runner_spec.rb +40 -0
  53. data/spec/wukong/local_spec.rb +6 -0
  54. data/spec/wukong/logger_spec.rb +49 -0
  55. data/spec/wukong/processor_spec.rb +22 -0
  56. data/spec/wukong/runner_spec.rb +128 -8
  57. data/spec/wukong/widget/filters_spec.rb +28 -10
  58. data/spec/wukong/widget/processors_spec.rb +5 -5
  59. data/spec/wukong/widget/reducers/bin_spec.rb +14 -14
  60. data/spec/wukong/widget/reducers/count_spec.rb +1 -1
  61. data/spec/wukong/widget/reducers/group_spec.rb +7 -6
  62. data/spec/wukong/widget/reducers/moments_spec.rb +2 -2
  63. data/spec/wukong/widget/reducers/sort_spec.rb +1 -1
  64. data/spec/wukong/widget/serializers_spec.rb +84 -88
  65. data/spec/wukong/wu-local_spec.rb +109 -0
  66. metadata +43 -20
  67. data/bin/wu-server +0 -70
  68. data/lib/wukong/boot.rb +0 -96
  69. data/lib/wukong/configuration.rb +0 -8
  70. data/lib/wukong/emitter.rb +0 -22
  71. data/lib/wukong/server.rb +0 -119
  72. data/lib/wukong/spec_helpers/integration_driver.rb +0 -157
  73. data/lib/wukong/spec_helpers/processor_helpers.rb +0 -89
  74. data/lib/wukong/spec_helpers/spec_driver.rb +0 -28
  75. data/spec/wukong/local_runner_spec.rb +0 -31
  76. data/spec/wukong/wu_local_spec.rb +0 -125
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Wukong::Processor do
4
+
5
+ subject { Wukong::Processor.new }
6
+
7
+ describe "has an interface" do
8
+ it{ should respond_to(:setup) }
9
+ it{ should respond_to(:process) }
10
+ it{ should respond_to(:finalize) }
11
+ it{ should respond_to(:stop) }
12
+ it{ should respond_to(:notify) }
13
+ end
14
+
15
+ describe "default process method" do
16
+ it "yields the original input record by default on process" do
17
+ expect { |b| subject.process(1, &b) }.to yield_with_args(1)
18
+ end
19
+ end
20
+
21
+ end
22
+
@@ -1,12 +1,132 @@
1
1
  require 'spec_helper'
2
- # require 'wukong'
3
2
 
4
- # describe :runner, :helpers => true do
3
+ describe Wukong::Runner do
5
4
 
6
- # context 'tiny_count example script' do
7
- # it 'is shorter than a tweet' do
8
- # example_script_contents('tiny_count.rb').length.should < 140
9
- # end
5
+ context "can boot itself by" do
10
6
 
11
- # end
12
- # end
7
+ describe "loading" do
8
+ it "files passed on the command-line" do
9
+ loadable = examples_dir('loadable.rb')
10
+ generic_runner(loadable) do
11
+ should_receive(:load_ruby_file).with(loadable)
12
+ end
13
+ end
14
+ it "files when its in a deploy pack" do
15
+ generic_runner { should_receive(:load_deploy_pack) }
16
+ end
17
+ end
18
+
19
+ describe "configuring" do
20
+ subject do
21
+ Class.new(Wukong::Runner).tap do |configured|
22
+ configured.class_eval do
23
+ usage 'a nice usage message'
24
+ description 'a lovely description'
25
+ end
26
+ end
27
+ end
28
+
29
+ it "a usage message" do
30
+ runner(subject, 'wu-generic').settings.usage.should =~ /^usage: .* a nice usage message$/
31
+ end
32
+
33
+ it "a description" do
34
+ runner(subject, 'wu-generic').settings.description.should == 'a lovely description'
35
+ end
36
+
37
+ it "plugins" do
38
+ generic_runner { Wukong.should_receive(:configure_plugins) }
39
+ end
40
+ end
41
+
42
+ describe "resolving" do
43
+ it "doesn't move on if resolve causes an error" do
44
+ generic_runner do
45
+ settings.should_receive(:resolve!).and_raise(RuntimeError)
46
+ should_not_receive(:setup)
47
+ should_not_receive(:validate)
48
+ should_not_receive(:run)
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "setting up" do
54
+ it "asks plugins to boot" do
55
+ generic_runner do
56
+ Wukong.should_receive(:boot_plugins)
57
+ end
58
+ end
59
+ end
60
+
61
+ describe "validating" do
62
+ it "should run if validatation passes" do
63
+ generic_runner do
64
+ should_receive(:validate).and_return(true)
65
+ should_receive(:run)
66
+ end
67
+ end
68
+ it "dies if validate fails" do
69
+ generic_runner do
70
+ should_receive(:validate).and_return(false)
71
+ should_not_receive(:run)
72
+ should_receive(:die)
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ context "situating itself within a deploy pack" do
79
+ context "in an arbitrary directory" do
80
+ let(:dir) { examples_dir('empty') }
81
+ before { FileUtils.cd(dir) }
82
+ subject { generic_runner }
83
+ its(:deploy_pack_dir) { should == '/' }
84
+ its(:environment_file) { should == '/config/environment.rb' }
85
+ its(:in_deploy_pack?) { should be_false }
86
+ end
87
+
88
+ context "when BUNDLE_GEMFILE points at a regular Ruby project" do
89
+ let(:dir) { examples_dir('empty') }
90
+ let(:deploy_pack_dir) { examples_dir('ruby_project') }
91
+ before do
92
+ FileUtils.cd(dir)
93
+ ENV.stub!(:[]).with("BUNDLE_GEMFILE").and_return(File.join(deploy_pack_dir, 'Gemfile'))
94
+ end
95
+ subject { generic_runner }
96
+ its(:deploy_pack_dir) { should == '/' }
97
+ its(:environment_file) { should == '/config/environment.rb' }
98
+ its(:in_deploy_pack?) { should be_false }
99
+ end
100
+
101
+ context "when BUNDLE_GEMFILE points at a deploy pack" do
102
+ let(:dir) { examples_dir('empty') }
103
+ let(:deploy_pack_dir) { examples_dir('deploy_pack') }
104
+ before do
105
+ FileUtils.cd(dir)
106
+ ENV.stub!(:[]).with("BUNDLE_GEMFILE").and_return(File.join(deploy_pack_dir, 'Gemfile'))
107
+ end
108
+ subject { generic_runner }
109
+ its(:deploy_pack_dir) { should == deploy_pack_dir.to_s }
110
+ its(:environment_file) { should == File.join(deploy_pack_dir, 'config/environment.rb')}
111
+ its(:in_deploy_pack?) { should be_true }
112
+ end
113
+
114
+ context "in an arbitrary Ruby project with a Gemfile" do
115
+ let(:dir) { examples_dir('ruby_project') }
116
+ before { FileUtils.cd(dir) }
117
+ subject { generic_runner }
118
+ its(:deploy_pack_dir) { should == '/' }
119
+ its(:environment_file) { should == '/config/environment.rb' }
120
+ its(:in_deploy_pack?) { should be_false }
121
+ end
122
+
123
+ context "in a deploy pack with a Gemfile and a config/environment.rb" do
124
+ let(:dir) { examples_dir('deploy_pack') }
125
+ before { FileUtils.cd(dir) }
126
+ subject { generic_runner }
127
+ its(:deploy_pack_dir) { should == dir }
128
+ its(:environment_file) { should == File.join(dir, 'config/environment.rb')}
129
+ its(:in_deploy_pack?) { should be_true }
130
+ end
131
+ end
132
+ end
@@ -2,21 +2,21 @@ require 'spec_helper'
2
2
 
3
3
  describe "Filters" do
4
4
 
5
- context :null do
5
+ describe :null do
6
6
  it_behaves_like 'a processor', :named => :null
7
7
  it "should not pass anything, ever" do
8
8
  processor.given('', 3, 'hi', nil).should emit(0).records
9
9
  end
10
10
  end
11
11
 
12
- context :identity do
12
+ describe :identity do
13
13
  it_behaves_like 'a processor', :named => :identity
14
14
  it "should pass everything, always" do
15
15
  processor.given('', 3, 'hi', nil).should emit('', 3, 'hi', nil)
16
16
  end
17
17
  end
18
18
 
19
- context :regexp do
19
+ describe :regexp do
20
20
  it_behaves_like 'a processor', :named => :regexp
21
21
  it "should pass everything given no 'match' argument" do
22
22
  processor.given('snap', 'crackle', 'pop').should emit('snap', 'crackle', 'pop')
@@ -26,7 +26,7 @@ describe "Filters" do
26
26
  end
27
27
  end
28
28
 
29
- context :not_regexp do
29
+ describe :not_regexp do
30
30
  it_behaves_like 'a processor', :named => :not_regexp
31
31
  it "should pass everything given no 'match' argument" do
32
32
  processor.given('snap', 'crackle', 'pop').should emit('snap', 'crackle', 'pop')
@@ -36,7 +36,7 @@ describe "Filters" do
36
36
  end
37
37
  end
38
38
 
39
- context :limit do
39
+ describe :limit do
40
40
  it_behaves_like 'a processor', :named => :limit
41
41
  it "should pass everything given no 'max' argument" do
42
42
  processor.given('snap', 'crackle', 'pop').should emit('snap', 'crackle', 'pop')
@@ -46,16 +46,34 @@ describe "Filters" do
46
46
  end
47
47
  end
48
48
 
49
- context :sample do
49
+ describe :sample do
50
50
  it_behaves_like 'a processor', :named => :sample
51
51
  it "should pass everything given no 'fraction' argument" do
52
52
  processor.given('snap', 'crackle', 'pop').should emit('snap', 'crackle', 'pop')
53
53
  end
54
- it "should pass everything given no 'fraction' argument" do
55
- processor(:fraction => 0.5).tap do |proc|
56
- proc.should_receive(:rand).and_return(0.7, 0.1, 0.6)
57
- end.given('snap', 'crackle', 'pop').should emit('crackle')
54
+ it "should pass a fraction of records matching its 'fraction' argument" do
55
+ processor(:fraction => 0.5) { |proc| proc.should_receive(:rand).and_return(0.7, 0.1, 0.6) }.given('snap', 'crackle', 'pop').should emit('crackle')
56
+ end
57
+ end
58
+
59
+ describe :head do
60
+ it_behaves_like 'a processor', :named => :head
61
+ it "should pass the first 10 records given no argument" do
62
+ processor.given(*(1..100).to_a).should emit(10).records
63
+ end
64
+ it "should pass the first n records" do
65
+ processor(:n => 5).given(*(1..100).to_a).should emit(5).records
58
66
  end
59
67
  end
60
68
 
69
+ describe :tail do
70
+ it_behaves_like 'a processor', :named => :tail
71
+ it "should pass all records given no argument" do
72
+ processor.given(*(1..100).to_a).should emit(100).records
73
+ end
74
+ it "should skip the first n records" do
75
+ processor(:n => 5).given(*(1..100).to_a).should emit(95).records
76
+ end
77
+ end
78
+
61
79
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Wukong::Processor do
3
+ describe "Processors" do
4
4
 
5
5
  let(:hsh) { { "hi" => "there", "top" => { "lower" => { "lowest" => "value" } } } }
6
6
  let(:ary) { ['1', 2, 'three'] }
@@ -12,8 +12,8 @@ describe Wukong::Processor do
12
12
  log = mock("logger")
13
13
  log.should_receive(:info).with('hi there')
14
14
  log.should_receive(:info).with('buddy')
15
- processor(:logger) do
16
- stub!(:log).and_return(log)
15
+ processor(:logger) do |proc|
16
+ proc.stub!(:log).and_return(log)
17
17
  end.given('hi there', 'buddy').should emit(0).records
18
18
  end
19
19
 
@@ -21,8 +21,8 @@ describe Wukong::Processor do
21
21
  log = mock("logger")
22
22
  log.should_receive(:debug).with('hi there')
23
23
  log.should_receive(:debug).with('buddy')
24
- processor(:logger, level: :debug) do
25
- stub!(:log).and_return(log)
24
+ processor(:logger, level: :debug) do |proc|
25
+ proc.stub!(:log).and_return(log)
26
26
  end.given('hi there', 'buddy').should emit(0).records
27
27
  end
28
28
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Wukong::Processor do
3
+ describe "Reducers" do
4
4
  describe :bin do
5
5
  include_context "reducers"
6
6
  it_behaves_like 'a processor', :named => :bin
@@ -32,34 +32,34 @@ describe Wukong::Processor do
32
32
  end
33
33
 
34
34
  it "counts correctly in each bin" do
35
- processor(num_bins: 5).given(*nums).should emit_tsv(*bins)
35
+ processor(num_bins: 5).given(*nums).should emit(*bins)
36
36
  end
37
37
 
38
38
  it "can express counts logarithmically" do
39
- row = processor(num_bins: 5, log_counts: true).given(*nums).tsv_output.first
39
+ row = processor(num_bins: 5, log_counts: true).given(*nums).output.first
40
40
  row.size.should == 3
41
41
  row[2].to_f.should be_within(0.1).of(2.197)
42
42
  end
43
43
 
44
44
  it "can add a normalized frequency" do
45
- row = processor(num_bins: 5, normalize: true).given(*nums).tsv_output.first
45
+ row = processor(num_bins: 5, normalize: true).given(*nums).output.first
46
46
  row.size.should == 4
47
47
  row[3].to_f.should be_within(0.1).of(0.18)
48
48
  end
49
49
 
50
50
  it "can add a normalized frequency and express counts logarithmically" do
51
- row = processor(num_bins: 5, normalize: true, log_counts: true).given(*nums).tsv_output.first
51
+ row = processor(num_bins: 5, normalize: true, log_counts: true).given(*nums).output.first
52
52
  row.size.should == 4
53
53
  row[2].to_f.should be_within(0.1).of(2.197)
54
54
  row[3].to_f.should be_within(0.1).of(-1.715)
55
55
  end
56
56
 
57
57
  it "can bin on the fly given min, max, and num_bins options" do
58
- output = processor(min: -30, max: 30, num_bins: 3) do
58
+ output = processor(min: -30, max: 30, num_bins: 3) do |proc|
59
59
  # we can bin on the fly
60
- values.should_not_receive(:<<)
61
- should_not_receive(:bin!)
62
- end.given(*nums).tsv_output
60
+ proc.values.should_not_receive(:<<)
61
+ proc.should_not_receive(:bin!)
62
+ end.given(*nums).output
63
63
 
64
64
  output.size.should == 3
65
65
  output.first[0].to_f.should be_within(0.1).of(-30)
@@ -67,11 +67,11 @@ describe Wukong::Processor do
67
67
  end
68
68
 
69
69
  it "can bin on the fly given fixed bin edges" do
70
- output = processor(edges: [0,1,5,10]) do
70
+ output = processor(edges: [0,1,5,10]) do |proc|
71
71
  # we can bin on the fly
72
- values.should_not_receive(:<<)
73
- should_not_receive(:bin!)
74
- end.given(*nums).tsv_output
72
+ proc.values.should_not_receive(:<<)
73
+ proc.should_not_receive(:bin!)
74
+ end.given(*nums).output
75
75
  output.size.should == 3
76
76
  output[0][0].to_f.should be_within(0.1).of(0.0)
77
77
  output[0][1].to_f.should be_within(0.1).of(1.0)
@@ -82,7 +82,7 @@ describe Wukong::Processor do
82
82
  end
83
83
 
84
84
  it "can extract the value to bin by from an object" do
85
- output = processor(by: 'data.n', min: 0).given(*json).tsv_output
85
+ output = processor(by: 'data.n', min: 0).given(*json).output
86
86
  output.size.should == 2
87
87
  output.first[0].to_f.should be_within(0.1).of(0.0)
88
88
  output.last[1].to_f.should be_within(0.1).of(100.0)
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Wukong::Processor do
3
+ describe "Reducers" do
4
4
  describe :count do
5
5
  include_context "reducers"
6
6
  it_behaves_like 'a processor', :named => :count
@@ -1,20 +1,21 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Wukong::Processor do
3
+ describe "Reducers" do
4
4
  describe :group do
5
5
  include_context "reducers"
6
6
  it_behaves_like 'a processor', :named => :group
7
7
 
8
- let(:grouped_strings) { [['apple', '2'], ['banana', '1'], ['cookie', '1']] }
9
- let(:grouped_nums) { [['', '2'], ['1', '1'], ['5', '1'], ['10', '1'], ['100', '1']] }
8
+ let(:grouped_strings) { [['apple', 2], ['banana', 1], ['cookie', 1]] }
9
+ let(:grouped_nums_from_json) { [[nil, 2], [1, 1], [5, 1], [10, 1], [100, 1]] }
10
+ let(:grouped_nums_from_tsv) { [[nil, 2], ['1', 1], ['5', 1], ['10', 1], ['100', 1]] }
10
11
  it "will group single values" do
11
- processor(:group).given(*strings.sort).should emit_tsv(*grouped_strings)
12
+ processor(:group).given(*strings.sort).should emit(*grouped_strings)
12
13
  end
13
14
  it "can group from within a JSON hash" do
14
- proc = processor(:group, by: 'data.n').given(*json_sorted_n).should emit_tsv(*grouped_nums)
15
+ proc = processor(:group, by: 'data.n').given(*json_sorted_n).should emit(*grouped_nums_from_json)
15
16
  end
16
17
  it "can group from within a TSV row" do
17
- proc = processor(:group, by: '3').given(*tsv_sorted).should emit_tsv(*grouped_nums)
18
+ proc = processor(:group, by: '3').given(*tsv_sorted).should emit(*grouped_nums_from_tsv)
18
19
  end
19
20
  end
20
21
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Wukong::Processor do
3
+ describe "Reducers" do
4
4
  describe :moments do
5
5
  include_context "reducers"
6
6
  it_behaves_like 'a processor', :named => :moments
@@ -23,7 +23,7 @@ describe Wukong::Processor do
23
23
  end
24
24
 
25
25
  it "will leave off the standard deviation if desired" do
26
- processor(:moments, group_by: 'outer', of: 'data.n', std_dev: false).given(*json_sorted_outer).should emit(
26
+ processor(:moments, group_by: 'outer', of: 'data.n', no_std_dev: true).given(*json_sorted_outer).should emit(
27
27
  {group: nil, count: 2, results: {"data.n" => {}}},
28
28
  {group: 'apple', count: 2, results: {"data.n"=>{:count=>2, :mean=>3.0 }}},
29
29
  {group: 'banana', count: 1, results: {"data.n"=>{:count=>1, :mean=>100.0 }}},
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Wukong::Processor do
3
+ describe "Reducers" do
4
4
  describe :sort do
5
5
  include_context "reducers"
6
6
  it_behaves_like 'a processor', :named => :sort
@@ -1,118 +1,114 @@
1
1
  require 'spec_helper'
2
2
 
3
- shared_context 'serializers', serializer: true do
4
- let(:bad_record){ nil }
5
- let(:serializer){ create_processor(self.class.top_level_description, on_error: :skip) }
6
- end
3
+ describe "Serializers" do
7
4
 
8
- shared_examples_for 'a serializer' do
9
- it_behaves_like 'a processor'
10
- end
5
+ describe :to_json do
6
+ it_behaves_like 'a processor', :named => :to_json
7
+
8
+ let(:valid_record) { { hi: 'there' } }
9
+ let(:record_as_json) { '{"hi":"there"}' }
10
+ let(:model_as_json) { '{"model":"json"}' }
11
+ let(:valid_model) { double('model', to_json: model_as_json) }
12
+
13
+ it 'serializes records to JSON' do
14
+ processor.given(valid_record).should emit(record_as_json)
15
+ end
16
+
17
+ it 'serializes records as pretty JSON when asked' do
18
+ processor(:pretty => true).given(valid_record).output.first.should include("\n")
19
+ end
11
20
 
12
- shared_examples_for 'a serializer that complains on bad recors', :handles_errors => true do
13
- it 'handles errors on bad records' do
14
- serializer.should_receive(:handle_error).with(bad_record, a_kind_of(Exception)).and_return(nil)
15
- serializer.given(bad_record).should emit(0).records
21
+ it 'defers to models to let them serialize themselves as JSON' do
22
+ processor.given(valid_model).should emit(model_as_json)
23
+ end
16
24
  end
17
- end
18
25
 
19
- describe :to_json, serializer: true do
20
- let(:valid_record) { { hi: 'there' } }
21
- it 'serializes valid records' do
22
- serializer.given(valid_record).should emit('{"hi":"there"}')
23
- end
24
26
 
25
- it 'handles errors on bad records' do
26
- MultiJson.should_receive(:dump).with(:unserializable_record, kind_of(Hash)).and_raise(StandardError)
27
- serializer.should_receive(:handle_error).with(:unserializable_record, a_kind_of(Exception)).and_return(nil)
28
- serializer.given(:unserializable_record).should emit(0).records
27
+ describe :from_json do
28
+ it_behaves_like 'a processor', :named => :from_json
29
+
30
+ let(:valid_json) { '{"hi": "there"}' }
31
+ let(:json_parsed) { {"hi" => "there"} }
32
+ let(:invalid_json) { '{"832323:' }
33
+
34
+ it 'deserializes valid JSON' do
35
+ processor.given(valid_json).should emit(json_parsed)
36
+ end
37
+
38
+ it 'handles errors on invalid JSON' do
39
+ processor { |proc| proc.should_receive(:handle_error).with(invalid_json, kind_of(Exception)) }.given(invalid_json).should emit(0).records
40
+ end
29
41
  end
30
42
 
31
- context 'pretty' do
32
- let(:serializer){ create_processor(:to_json, pretty: true) }
43
+ describe :to_tsv do
44
+ it_behaves_like 'a processor', :named => :to_tsv
33
45
 
34
- it 'prettifies valid records' do
35
- serializer.given(valid_record).output.first.should include("\n")
36
- end
37
- end
38
-
39
- context 'given a model' do
40
- let(:json_record) { '{"foo":"bar"}' }
41
- let(:valid_model) { double('model', to_json: json_record) }
46
+ let(:valid_record) { ["foo", 2, :a] }
47
+ let(:invalid_record) { nil }
48
+ let(:record_as_tsv) { "foo\t2\ta" }
49
+ let(:model_as_tsv) { "foo\tbar\tbaz" }
50
+ let(:valid_model) { double('model', to_tsv: model_as_tsv) }
42
51
 
43
- it 'defers to the model to serialize' do
44
- valid_model.should_receive(:to_json).and_return(json_record)
45
- serializer.given(valid_model).should emit(json_record)
52
+ it 'serializes records to JSON' do
53
+ processor.given(valid_record).should emit(record_as_tsv)
46
54
  end
47
- end
48
- end
49
55
 
50
- describe :to_tsv, serializer: true, handles_errors: true do
51
- let(:valid_record) { ["foo", 2, :a] }
52
-
53
- it 'serializes valid records' do
54
- serializer.given(valid_record).should emit("foo\t2\ta")
55
- end
56
+ it 'defers to models to let them serialize themselves as JSON' do
57
+ processor.given(valid_model).should emit(model_as_tsv)
58
+ end
56
59
 
57
- context 'given a model' do
58
- let(:tsv_record) { "foo\tbar\tbaz" }
59
- let(:valid_model) { double('model', to_tsv: tsv_record) }
60
-
61
- it 'defers to the model to serialize' do
62
- valid_model.should_receive(:to_tsv).and_return(tsv_record)
63
- serializer.given(valid_model).should emit(tsv_record)
60
+ it 'handles errors on bad records' do
61
+ processor { |proc| proc.should_receive(:handle_error) }.given(invalid_record).should emit(0).records
64
62
  end
65
63
  end
66
- end
67
-
68
- describe :from_json, serializer: true, handles_errors: true do
69
- let(:valid_record) { '{"hi": "there"}' }
70
- let(:bad_record) { '{"832323:' }
71
-
72
- it 'deserializes valid records' do
73
- serializer.given(valid_record).should emit({'hi' => 'there'})
74
- end
75
64
 
76
- context 'given a model' do
77
- let(:wire_format) { { foo: 'bar' } }
78
- let(:valid_model) { double('model', from_json: wire_format) }
65
+ describe :from_tsv, serializer: true, handles_errors: true do
66
+ it_behaves_like 'a processor', :named => :from_tsv
67
+
68
+ let(:valid_tsv) { "foo\t2\ta" }
69
+ let(:tsv_parsed) { ["foo", "2", "a"] }
70
+ let(:invalid_tsv) { nil }
79
71
 
80
- it 'defers to the model to serialize' do
81
- valid_model.should_receive(:from_json).and_return(wire_format)
82
- serializer.given(valid_model).should emit(wire_format)
72
+ it 'deserializes valid TSV' do
73
+ processor.given(valid_tsv).should emit(tsv_parsed)
83
74
  end
84
- end
85
- end
86
75
 
87
- describe :from_tsv, serializer: true, handles_errors: true do
88
- let(:valid_record) { "foo\t2\ta" }
89
-
90
- it 'deserializes valid records' do
91
- serializer.given(valid_record).should emit(['foo', '2', 'a' ])
76
+ it "handles errors on invalid TSV" do
77
+ processor { |proc| proc.should_receive(:handle_error).with(invalid_tsv, kind_of(Exception)) }.given(invalid_tsv).should emit(0).records
78
+ end
92
79
  end
93
80
 
94
- context 'given a model' do
95
- let(:wire_format) { { foo: 'bar' } }
96
- let(:valid_model) { double('model', from_tsv: wire_format) }
81
+ describe :to_inspect do
82
+ it_behaves_like 'a processor', :named => :to_inspect
83
+
84
+ let(:valid_record) { {"a" => 1 } }
85
+ let(:record_as_inspect) { valid_record.inspect }
86
+ let(:model_as_inspect) { '<Model #13e233>' }
87
+ let(:valid_model) { double('model', inspect: model_as_inspect) }
97
88
 
98
- it 'defers to the model to serialize' do
99
- valid_model.should_receive(:from_tsv).and_return(wire_format)
100
- serializer.given(valid_model).should emit(wire_format)
89
+ it 'serializes records via inspect' do
90
+ processor.given(valid_record).should emit(record_as_inspect)
91
+ end
92
+
93
+ it 'defers to models to let them inspect themselves' do
94
+ processor.given(valid_model).should emit(model_as_inspect)
101
95
  end
102
96
  end
103
- end
104
97
 
105
- describe :to_inspect do
106
- it_behaves_like 'a processor'
107
- end
98
+ describe :recordize do
99
+ let(:model_instance) { double('model') }
100
+ let(:model_klass) { double('model_def', receive: model_instance) }
101
+ let(:serializer) { processor(:recordize, model: model_klass, on_error: :skip) }
102
+ let(:valid_record) { { foo: 'bar' } }
103
+ let(:invalid_record) { [1,2,3] }
108
104
 
109
- describe :recordize, serializer: true, handles_errors: true do
110
- let(:model_instance) { double('model') }
111
- let(:model_klass) { double('model_def', receive: model_instance) }
112
- let(:serializer) { create_processor(:recordize, model: model_klass, on_error: :skip) }
113
- let(:valid_record) { { foo: 'bar' } }
105
+ it 'recordizes valid records' do
106
+ processor(model: model_klass).given(valid_record).should emit(model_instance)
107
+ end
114
108
 
115
- it 'recordizes valid records' do
116
- serializer.given(valid_record).should emit(model_instance)
109
+ it 'handles errors on invalid models' do
110
+ processor(model: model_klass) { |proc| proc.should_receive(:handle_error).with(invalid_record, kind_of(Exception)) }.given(invalid_record).should emit(0).records
111
+ end
112
+
117
113
  end
118
114
  end