wukong 3.0.0.pre3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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