hadupils 0.1.1

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.
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+
2
+ ### 0.1.0
3
+
4
+ * Basic functionality for representing a command, extensions,
5
+ assets, runners.
6
+ * A hive runner enforcing user config and hadoop-ext extension
7
+ * A hadupils executable for entering the command model
8
+
9
+ ### 0.1.1
10
+
11
+ * Removed evil rubygems requirement from executable
12
+ * Added this glorious changelog
13
+
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+
3
+ gem 'mocha', :group => :test
4
+ gem 'rake', '10.1.0', :group => :development
5
+ gem 'shoulda-context', :group => :test
6
+
data/Gemfile.lock ADDED
@@ -0,0 +1,16 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ metaclass (0.0.1)
5
+ mocha (0.14.0)
6
+ metaclass (~> 0.0.1)
7
+ rake (10.1.0)
8
+ shoulda-context (1.1.5)
9
+
10
+ PLATFORMS
11
+ ruby
12
+
13
+ DEPENDENCIES
14
+ mocha
15
+ rake (= 10.1.0)
16
+ shoulda-context
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Ethan Rowe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ hadupils
2
+ ========
3
+
4
+ Operating environment oriented utilities for hadoop (Hadoop + Utils => hadupils)
data/Rakefile.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
4
+
5
+ CLOBBER.include('hadupils-*.gem')
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.pattern = 'test/**/*_test.rb'
9
+ t.libs = ['test', 'lib']
10
+ # This should initialize the environment properly.
11
+ t.ruby_opts << '-rhadupil_test_setup'
12
+ end
13
+
data/bin/hadupils ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # vim: set filetype=ruby:
4
+
5
+ require 'hadupils'
6
+
7
+ exit Hadupils::Commands.run ARGV[0], ARGV[1..-1]
8
+
@@ -0,0 +1,57 @@
1
+ module Hadupils::Assets
2
+ class File
3
+ attr_reader :name, :path
4
+
5
+ def self.determine_name(path)
6
+ ::File.basename(path)
7
+ end
8
+
9
+ def self.hiverc_type
10
+ :FILE
11
+ end
12
+
13
+ def initialize(path)
14
+ @path = path
15
+ @name = self.class.determine_name(path)
16
+ end
17
+
18
+ def hidden?
19
+ name[0] == '.'
20
+ end
21
+
22
+ def hiverc_command
23
+ "ADD #{self.class.hiverc_type} #{path};"
24
+ end
25
+ end
26
+
27
+ class Jar < File
28
+ def self.hiverc_type
29
+ :JAR
30
+ end
31
+ end
32
+
33
+ class Archive < File
34
+ def self.hiverc_type
35
+ :ARCHIVE
36
+ end
37
+ end
38
+
39
+ def self.asset_for(path)
40
+ return Archive.new(path) if path[-7..-1] == '.tar.gz'
41
+ return Jar.new(path) if path[-4..-1] == '.jar'
42
+ return File.new(path)
43
+ end
44
+
45
+ SKIP_NAMES = ['.', '..']
46
+
47
+ # Walks the top-level members of the stated directory and
48
+ # returns an array containing appropriate an HadoopAsset::*
49
+ # instance for each.
50
+ def self.assets_in(directory)
51
+ path = ::File.expand_path(directory)
52
+ ::Dir.entries(path).sort.inject([]) do |accum, entry|
53
+ accum << asset_for(::File.join(path, entry)) if not SKIP_NAMES.include? entry
54
+ accum
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,48 @@
1
+ module Hadupils::Commands
2
+ def self.run(command, params)
3
+ handler = handler_for command
4
+ handler.run params
5
+ end
6
+
7
+ def self.handler_for(command)
8
+ @handlers and @handlers[command.downcase.to_sym]
9
+ end
10
+
11
+ def self.register_handler(command, handler)
12
+ @handlers ||= {}
13
+ @handlers[command.downcase.to_sym] = handler
14
+ end
15
+
16
+ class SimpleCommand
17
+ def self.run(params)
18
+ self.new.run params
19
+ end
20
+ end
21
+
22
+ module HadoopExt
23
+ def hadoop_ext
24
+ @hadoop_ext ||= Hadupils::Extensions::Flat.new(Hadupils::Search.hadoop_assets)
25
+ end
26
+ end
27
+
28
+ module UserConf
29
+ def user_config
30
+ @user_config ||= Hadupils::Extensions::Static.new(Hadupils::Search.user_config)
31
+ end
32
+ end
33
+
34
+ class Hive < SimpleCommand
35
+ include HadoopExt
36
+ include UserConf
37
+
38
+ def assemble_parameters(parameters)
39
+ user_config.hivercs + hadoop_ext.hivercs + parameters
40
+ end
41
+
42
+ def run(parameters)
43
+ Hadupils::Runners::Hive.run assemble_parameters(parameters)
44
+ end
45
+ end
46
+
47
+ register_handler :hive, Hive
48
+ end
@@ -0,0 +1,162 @@
1
+ module Hadupils::Extensions
2
+ # Tools for managing hive initialization files ("hiverc").
3
+ module HiveRC
4
+ module HiveOpt
5
+ def hive_opts
6
+ ['-i', path]
7
+ end
8
+ end
9
+
10
+ # Wraps an extant hive initialization file, and providing
11
+ # an interface compatible with the critical parts of the
12
+ # Dynamic sibling class so they may be used interchangeably
13
+ # by runners when determining hive options.
14
+ class Static
15
+ attr_reader :path
16
+
17
+ include HiveOpt
18
+
19
+ # Given a path, expands it ti
20
+ def initialize(path)
21
+ @path = ::File.expand_path(path)
22
+ end
23
+
24
+ def close
25
+ end
26
+ end
27
+
28
+ # Manages dynamic hive initialization files, assembling a temporary file
29
+ # and understanding how to write assets/lines into the initialization file for
30
+ # use with hive's -i option.
31
+ class Dynamic
32
+ attr_reader :file
33
+
34
+ include HiveOpt
35
+ require 'tempfile'
36
+
37
+ # This will allow us to change what handles the dynamic files.
38
+ def self.file_handler=(handler)
39
+ @file_handler = handler
40
+ end
41
+
42
+ # The class to use for creating the files; defaults to ::Tempfile
43
+ def self.file_handler
44
+ @file_handler || ::Tempfile
45
+ end
46
+
47
+ # Sets up a wrapped file, using the class' file_handler,
48
+ def initialize
49
+ @file = self.class.file_handler.new('hadupils-hiverc')
50
+ end
51
+
52
+ def path
53
+ ::File.expand_path @file.path
54
+ end
55
+
56
+ def close
57
+ @file.close
58
+ end
59
+
60
+ # Writes the items to the file, using #hiverc_command on each item that
61
+ # responds to it (Hadupils::Assets::* instances) and #to_s on the rest.
62
+ # Separates lines by newline, and provides a trailing newline. However,
63
+ # the items are responsible for ensuring the proper terminating semicolon.
64
+ # The writes are flushed to the underlying file immediately afterward.
65
+ def write(items)
66
+ lines = items.collect do |item|
67
+ if item.respond_to? :hiverc_command
68
+ item.hiverc_command
69
+ else
70
+ item.to_s
71
+ end
72
+ end
73
+ @file.write(lines.join("\n") + "\n")
74
+ @file.flush
75
+ end
76
+ end
77
+ end
78
+
79
+ class EvalProxy
80
+ def initialize(scope)
81
+ @scope = scope
82
+ end
83
+
84
+ def assets(&block)
85
+ @scope.instance_eval do
86
+ @assets_block = block
87
+ end
88
+ end
89
+
90
+ def hiverc(&block)
91
+ @scope.instance_eval do
92
+ @hiverc_block = block
93
+ end
94
+ end
95
+ end
96
+
97
+ class Base
98
+ attr_reader :assets, :path
99
+
100
+ def initialize(directory, &block)
101
+ if block_given?
102
+ EvalProxy.new(self).instance_eval &block
103
+ end
104
+ @path = ::File.expand_path(directory) unless directory.nil?
105
+ @assets = merge_assets(self.class.gather_assets(@path))
106
+ end
107
+
108
+ def merge_assets(assets)
109
+ return @assets_block.call(assets) if @assets_block
110
+ assets
111
+ end
112
+
113
+ def hivercs
114
+ []
115
+ end
116
+
117
+ def self.gather_assets(directory)
118
+ if not directory.nil?
119
+ Hadupils::Assets.assets_in(directory)
120
+ else
121
+ []
122
+ end
123
+ end
124
+ end
125
+
126
+ class Flat < Base
127
+ def hivercs
128
+ @hiverc ||= assemble_hiverc
129
+ [@hiverc]
130
+ end
131
+
132
+ def assemble_hiverc
133
+ assets = @assets
134
+ if @hiverc_block
135
+ assets = @hiverc_block.call(assets.dup)
136
+ end
137
+ hiverc = Hadupils::Extensions::HiveRC::Dynamic.new
138
+ hiverc.write(assets)
139
+ hiverc
140
+ end
141
+ end
142
+
143
+ class Static < Base
144
+ def self.gather_assets(path)
145
+ []
146
+ end
147
+
148
+ def hiverc_path
149
+ ::File.join(path, 'hiverc')
150
+ end
151
+
152
+ def hiverc?
153
+ ::File.file? hiverc_path
154
+ end
155
+
156
+ def hivercs
157
+ r = []
158
+ r << Hadupils::Extensions::HiveRC::Static.new(hiverc_path) if hiverc?
159
+ r
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,45 @@
1
+ module Hadupils::Runners
2
+ class Base
3
+ attr_reader :params, :last_result, :last_status
4
+
5
+ def initialize(params)
6
+ @params = params
7
+ end
8
+
9
+ def command; end
10
+
11
+ def wait!
12
+ @last_result = Kernel.system(*command)
13
+ @last_status = $?
14
+ if @last_result.nil?
15
+ 255
16
+ else
17
+ @last_status.exitstatus
18
+ end
19
+ end
20
+
21
+ def self.run(params)
22
+ self.new(params).wait!
23
+ end
24
+ end
25
+
26
+ class Hive < Base
27
+ def self.base_runner
28
+ @base_runner || ::File.join(ENV['HIVE_HOME'], 'bin', 'hive')
29
+ end
30
+
31
+ def self.base_runner=(runner_path)
32
+ @base_runner = runner_path
33
+ end
34
+
35
+ def command
36
+ items = params.inject([self.class.base_runner]) do |result, param|
37
+ if param.respond_to? :hive_opts
38
+ result + param.hive_opts
39
+ else
40
+ result << param
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,49 @@
1
+ module Hadupils::Search
2
+ # Searches for directory containing a subdirectory `name`, starting at
3
+ # the specified `start` directory and walking upwards until it can go
4
+ # no farther. On first match, the absolute path to that subdirectory
5
+ # is returned. If not found, returns nil.
6
+ def self.find_from_dir(name, start)
7
+ curr = ::File.expand_path(start)
8
+ last = nil
9
+ while curr != last
10
+ p = ::File.join(curr, name)
11
+ return p if ::File.directory? p
12
+ last = curr
13
+ curr = ::File.dirname(curr)
14
+ end
15
+ nil
16
+ end
17
+
18
+ # Performs a `find_from_dir` starting at the present working directory.
19
+ def self.find_from_pwd(name)
20
+ find_from_dir(name, ::Dir.pwd)
21
+ end
22
+
23
+ # The directory for user-specific configuration files.
24
+ def self.user_config
25
+ @user_config || ::File.expand_path(::File.join('~', 'conf'))
26
+ end
27
+
28
+ def self.user_config=(path)
29
+ @user_config = path
30
+ end
31
+
32
+ # The basename to use when looking for hadoop assets from pwd.
33
+ def self.hadoop_assets_name
34
+ @hadoop_assets_name || 'hadoop-ext'
35
+ end
36
+
37
+ # Set the basename to use when looking for hadoop assets from pwd.
38
+ def self.hadoop_assets_name=(basename)
39
+ @hadoop_assets_name = basename
40
+ end
41
+
42
+ # A search for #hadoop_assets_name from the pwd.
43
+ # The default behavior is to look for a subdir named "hadoop-ext",
44
+ # starting from the current working directory and walking upwards until
45
+ # a match is found or the file system root is encountered.
46
+ def self.hadoop_assets
47
+ find_from_pwd(hadoop_assets_name)
48
+ end
49
+ end
data/lib/hadupils.rb ADDED
@@ -0,0 +1,9 @@
1
+
2
+ module Hadupils
3
+ end
4
+
5
+ require 'hadupils/assets'
6
+ require 'hadupils/commands'
7
+ require 'hadupils/extensions'
8
+ require 'hadupils/runners'
9
+ require 'hadupils/search'
@@ -0,0 +1,50 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+ require 'test/unit'
4
+ require 'shoulda-context'
5
+ require 'mocha/setup'
6
+ require 'tempfile'
7
+ require 'hadupils'
8
+
9
+ # Add tempdir niceties to Test::Unit::TestCase
10
+ # on top of the shoulda-context stuff.
11
+ class Test::Unit::TestCase
12
+ class DirWrapper
13
+ attr_reader :path
14
+
15
+ def initialize(path)
16
+ @path = path
17
+ end
18
+
19
+ def full_path(path)
20
+ ::File.expand_path(::File.join(@path, path))
21
+ end
22
+
23
+ def file(path)
24
+ path = full_path(path)
25
+ if block_given?
26
+ ::File.open(path, 'w') do |f|
27
+ yield f
28
+ end
29
+ else
30
+ ::File.new(path, 'w')
31
+ end
32
+ end
33
+ end
34
+
35
+ def self.tempdir_context(name, &block)
36
+ context name do
37
+ setup do
38
+ @tempdir = Test::Unit::TestCase::DirWrapper.new(::File.expand_path(::Dir.mktmpdir))
39
+ end
40
+
41
+ teardown do
42
+ FileUtils.remove_entry @tempdir.path
43
+ end
44
+
45
+ # Instance_eval instead of simple yield to ensure it happens in the Context object
46
+ # and not in the test case subclass.
47
+ instance_eval &block
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,166 @@
1
+ class AssetsTest < Test::Unit::TestCase
2
+ context 'a file' do
3
+ setup do
4
+ @path = '/foo/bar/some_file.blah'
5
+ @asset = Hadupils::Assets::File.new(@path)
6
+ end
7
+
8
+ should 'have a path' do
9
+ assert_equal @path, @asset.path
10
+ end
11
+
12
+ should 'have a name' do
13
+ assert_equal ::File.basename(@path), @asset.name
14
+ end
15
+
16
+ should 'have an ADD FILE hiverc command' do
17
+ assert_equal "ADD FILE #{@path};", @asset.hiverc_command
18
+ end
19
+
20
+ should 'not be hidden' do
21
+ assert_equal false, @asset.hidden?
22
+ end
23
+ end
24
+
25
+ context 'a jar' do
26
+ setup do
27
+ @path = '/blah/blargh/../foo/blee/something.jar'
28
+ @asset = Hadupils::Assets::Jar.new(@path)
29
+ end
30
+
31
+ should 'have a path' do
32
+ assert_equal @path, @asset.path
33
+ end
34
+
35
+ should 'have a name' do
36
+ assert_equal ::File.basename(@path), @asset.name
37
+ end
38
+
39
+ should 'have an ADD JAR hiverc command' do
40
+ assert_equal "ADD JAR #{@path};", @asset.hiverc_command
41
+ end
42
+
43
+ should 'not be hidden' do
44
+ assert_equal false, @asset.hidden?
45
+ end
46
+ end
47
+
48
+ context 'a tarball' do
49
+ setup do
50
+ @path = '/blah/blargh/../foo/blee/something.tar.gz'
51
+ @asset = Hadupils::Assets::Archive.new(@path)
52
+ end
53
+
54
+ should 'have a path' do
55
+ assert_equal @path, @asset.path
56
+ end
57
+
58
+ should 'have a name' do
59
+ assert_equal ::File.basename(@path), @asset.name
60
+ end
61
+
62
+ should 'have an ADD ARCHIVE hiverc command' do
63
+ assert_equal "ADD ARCHIVE #{@path};", @asset.hiverc_command
64
+ end
65
+
66
+ should 'not be hidden' do
67
+ assert_equal false, @asset.hidden?
68
+ end
69
+ end
70
+
71
+ context 'a hidden file' do
72
+ should 'have a hidden File asset' do
73
+ assert_equal true, Hadupils::Assets::File.new('/blah/blee/.foo.bar.txt').hidden?
74
+ end
75
+
76
+ should 'have a hidden Archive asset' do
77
+ assert_equal true, Hadupils::Assets::Archive.new('/floo/flam/.foo.tar.gz').hidden?
78
+ end
79
+
80
+ should 'have a hidden Jar asset' do
81
+ assert_equal true, Hadupils::Assets::Jar.new('/flibbidy/blop/.foo.bar.jar').hidden?
82
+ end
83
+ end
84
+
85
+ context 'asset_for' do
86
+ context 'given a file of no particular extension' do
87
+ setup do
88
+ @path = '/some/special/file.path'
89
+ @asset = Hadupils::Assets.asset_for(@path)
90
+ end
91
+
92
+ should 'produce a Hadupils::Assets::File' do
93
+ assert_same Hadupils::Assets::File, @asset.class
94
+ end
95
+
96
+ should 'pass the path through' do
97
+ assert_equal @path, @asset.path
98
+ end
99
+ end
100
+
101
+ context 'given a file with a .jar extension' do
102
+ setup do
103
+ @path = '/some/great/magical-1.7.9.jar'
104
+ @asset = Hadupils::Assets.asset_for(@path)
105
+ end
106
+
107
+ should 'product a Hadupils::Assets::Jar' do
108
+ assert_same Hadupils::Assets::Jar, @asset.class
109
+ end
110
+
111
+ should 'pass the path through' do
112
+ assert_equal @path, @asset.path
113
+ end
114
+ end
115
+
116
+ context 'given a file with a .tar.gz extension' do
117
+ setup do
118
+ @path = '/some/freaking/awesome.tar.gz'
119
+ @asset = Hadupils::Assets.asset_for(@path)
120
+ end
121
+
122
+ should 'produce a Hadupils::Assets::Archive' do
123
+ assert_same Hadupils::Assets::Archive, @asset.class
124
+ end
125
+
126
+ should 'pass the path through' do
127
+ assert_equal @path, @asset.path
128
+ end
129
+ end
130
+ end
131
+
132
+ tempdir_context 'a directory with files' do
133
+ setup do
134
+ @tempdir.file(@archive = 'Awesome-archive.tar.gz')
135
+ @tempdir.file(@jar = 'jarrIE.jar')
136
+ @tempdir.file(@file = 'sumyummy.yaml')
137
+ @matches = {@archive => Hadupils::Assets::Archive,
138
+ @jar => Hadupils::Assets::Jar,
139
+ @file => Hadupils::Assets::File}
140
+ end
141
+
142
+ context 'given to Hadupils::Assets.assets_in' do
143
+ setup do
144
+ @received = Hadupils::Assets.assets_in(@tempdir.path)
145
+ end
146
+
147
+ should 'get assets in lexicographic order' do
148
+ assert_equal @matches.keys.sort, (@received.collect {|a| a.name})
149
+ end
150
+
151
+ should 'get assets of appropriate type' do
152
+ type_map = @received.inject({}) {|hash, asset| hash[asset.name] = asset.class; hash}
153
+ assert_equal @matches, type_map
154
+ end
155
+
156
+ should 'get assets with expanded paths' do
157
+ path_map = @received.inject({}) {|hash, asset| hash[asset.name] = asset.path; hash}
158
+ expected = @matches.keys.inject({}) do |hash, name|
159
+ hash[name] = @tempdir.full_path(name)
160
+ hash
161
+ end
162
+ assert_equal expected, path_map
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,135 @@
1
+ class Hadupils::CommandsTest < Test::Unit::TestCase
2
+ context Hadupils::Commands do
3
+ context 'run singleton method' do
4
+ should 'pass trailing params to #run method of handler identified by first param' do
5
+ Hadupils::Commands.expects(:handler_for).with(cmd = mock()).returns(handler = mock())
6
+ handler.expects(:run).with(params = [mock(), mock(), mock()]).returns(result = mock())
7
+ assert_equal result, Hadupils::Commands.run(cmd, params)
8
+ end
9
+ end
10
+
11
+ context 'Hive' do
12
+ setup do
13
+ @klass = Hadupils::Commands::Hive
14
+ end
15
+
16
+ should 'register with :hive name' do
17
+ assert_same @klass, Hadupils::Commands.handler_for(:hive)
18
+ assert_same @klass, Hadupils::Commands.handler_for(:HivE)
19
+ assert_same @klass, Hadupils::Commands.handler_for('hive')
20
+ assert_same @klass, Hadupils::Commands.handler_for('hIVe')
21
+ end
22
+
23
+ should 'have a #run singleton method that dispatches to an instance #run' do
24
+ @klass.expects(:new).with.returns(instance = mock())
25
+ instance.expects(:run).with(params = mock()).returns(result = mock())
26
+ assert_equal result, @klass.run(params)
27
+ end
28
+
29
+ should 'have a Flat extension based on a search for hadoop-ext' do
30
+ Hadupils::Search.expects(:hadoop_assets).with.returns(assets = mock())
31
+ Hadupils::Extensions::Flat.expects(:new).with(assets).returns(extension = mock())
32
+ cmd = @klass.new
33
+ assert_equal extension, cmd.hadoop_ext
34
+ # This should cause failure if the previous result wasn't
35
+ # cached internally (by breaking expectations).
36
+ cmd.hadoop_ext
37
+ end
38
+
39
+ should 'have a Static extensions based on user config' do
40
+ Hadupils::Search.expects(:user_config).with.returns(conf = mock())
41
+ Hadupils::Extensions::Static.expects(:new).with(conf).returns(extension = mock())
42
+ cmd = @klass.new
43
+ assert_equal extension, cmd.user_config
44
+ # Fails on expectations if previous result wasn't cached.
45
+ cmd.user_config
46
+ end
47
+
48
+ context '#run' do
49
+ setup do
50
+ @command = @klass.new
51
+ @command.stubs(:user_config).with.returns(@user_config = mock())
52
+ @command.stubs(:hadoop_ext).with.returns(@hadoop_ext = mock())
53
+ @runner_class = Hadupils::Runners::Hive
54
+ end
55
+
56
+ context 'with user config and hadoop asssets hivercs' do
57
+ setup do
58
+ @user_config.stubs(:hivercs).returns(@user_config_hivercs = [mock(), mock()])
59
+ @hadoop_ext.stubs(:hivercs).returns(@hadoop_ext_hivercs = [mock(), mock(), mock()])
60
+ end
61
+
62
+ should 'apply hiverc options to hive runner call' do
63
+ @runner_class.expects(:run).with(@user_config_hivercs + @hadoop_ext_hivercs).returns(result = mock())
64
+ assert_equal result, @command.run([])
65
+ end
66
+
67
+ should 'prepend hiverc options before given params to hive runner call' do
68
+ params = [mock(), mock()]
69
+ @runner_class.expects(:run).with(@user_config_hivercs + @hadoop_ext_hivercs + params).returns(result = mock())
70
+ assert_equal result, @command.run(params)
71
+ end
72
+ end
73
+
74
+ context 'without hivercs' do
75
+ setup do
76
+ @user_config.stubs(:hivercs).returns([])
77
+ @hadoop_ext.stubs(:hivercs).returns([])
78
+ end
79
+
80
+ should 'pass params unchanged through to hive runner call' do
81
+ @runner_class.expects(:run).with(params = [mock(), mock()]).returns(result = mock())
82
+ assert_equal result, @command.run(params)
83
+ end
84
+
85
+ should 'handle empty params' do
86
+ @runner_class.expects(:run).with([]).returns(result = mock())
87
+ assert_equal result, @command.run([])
88
+ end
89
+ end
90
+ end
91
+
92
+ tempdir_context 'running for (mostly) realz' do
93
+ setup do
94
+ @conf = ::File.join(@tempdir.path, 'conf')
95
+ @ext = ::File.join(@tempdir.path, 'hadoop-ext')
96
+ ::Dir.mkdir(@conf)
97
+ ::Dir.mkdir(@ext)
98
+ @hiverc = @tempdir.file(File.join('conf', 'hiverc')) do |f|
99
+ f.write(@static_hiverc_content = 'my static content;')
100
+ f.path
101
+ end
102
+ file = Proc.new {|base, name| @tempdir.file(::File.join(base, name)).path }
103
+ @ext_file = file.call('hadoop-ext', 'a_file.yaml')
104
+ @ext_jar = file.call('hadoop-ext', 'a_jar.jar')
105
+ @ext_tar = file.call('hadoop-ext', 'a_tar.tar.gz')
106
+ @dynamic_hiverc_content = ["ADD FILE #{@ext_file}",
107
+ "ADD JAR #{@ext_jar}",
108
+ "ADD ARCHIVE #{@ext_tar}"].join(";\n") + ";\n"
109
+ @pwd = ::Dir.pwd
110
+ Hadupils::Search.stubs(:user_config).with.returns(@conf)
111
+ Hadupils::Runners::Hive.stubs(:base_runner).with.returns(@hive_prog = '/opt/hive/bin/hive')
112
+ ::Dir.chdir @tempdir.path
113
+ end
114
+
115
+ should 'produce a valid set of parameters and hivercs' do
116
+ Kernel.stubs(:system).with() do |*args|
117
+ args[0] == @hive_prog and
118
+ args[1] == '-i' and
119
+ File.open(args[2], 'r').read == @static_hiverc_content and
120
+ args[3] == '-i' and
121
+ File.open(args[4], 'r').read == @dynamic_hiverc_content and
122
+ args[5] == '--hiveconf' and
123
+ args[6] == 'my.foo=your.fu'
124
+ end
125
+ Hadupils::Commands.run 'hive', ['--hiveconf', 'my.foo=your.fu']
126
+ end
127
+
128
+ teardown do
129
+ ::Dir.chdir @pwd
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+
@@ -0,0 +1,266 @@
1
+ class Hadupils::ExtensionsTest < Test::Unit::TestCase
2
+ context Hadupils::Extensions::Base do
3
+ context 'initialization with nil path' do
4
+ should 'have nil as the path' do
5
+ ext = Hadupils::Extensions::Base.new(nil)
6
+ assert_equal nil, ext.path
7
+ end
8
+ end
9
+
10
+ context 'initialization' do
11
+ setup do
12
+ @path = mock()
13
+ @expanded_path = mock()
14
+ @assets = mock()
15
+ ::File.expects(:expand_path).with(@path).returns(@expanded_path)
16
+ end
17
+
18
+ should 'expand the given directory into :path' do
19
+ Hadupils::Extensions::Base.stubs(:gather_assets).returns(@assets)
20
+ assert_equal @expanded_path, Hadupils::Extensions::Base.new(@path).path
21
+ end
22
+
23
+ should "gather the expanded directory's assets" do
24
+ Hadupils::Extensions::Base.expects(:gather_assets).with(@expanded_path).returns(@assets)
25
+ assert_equal @assets, Hadupils::Extensions::Base.new(@path).assets
26
+ end
27
+
28
+ should "allow manipulation of assets post-expansion" do
29
+ Hadupils::Extensions::Base.stubs(:gather_assets).returns(@assets)
30
+ extra = mock()
31
+ ext = Hadupils::Extensions::Base.new(@path) do
32
+ assets do |items|
33
+ # We're just adding the new stuff to the original stuff
34
+ [items, extra]
35
+ end
36
+ end
37
+ # If the above assets block was applied, we'll see the additional
38
+ # item there.
39
+ assert_equal [@assets, extra], ext.assets
40
+ end
41
+
42
+ should 'have an empty hivercs list' do
43
+ Hadupils::Extensions::Base.stubs(:gather_assets).returns(@assets)
44
+ assert_equal [], Hadupils::Extensions::Base.new(@path).hivercs
45
+ end
46
+ end
47
+
48
+ context 'gather_assets' do
49
+ should 'assemble assets with Hadupils::Assets.assets_in' do
50
+ path = mock()
51
+ result = mock()
52
+ Hadupils::Assets.expects(:assets_in).with(path).returns(result)
53
+ assert_equal result, Hadupils::Extensions::Base.gather_assets(path)
54
+ end
55
+
56
+ should 'allow manipulation of assets' do
57
+ end
58
+
59
+ should 'produce empty list for a nil path' do
60
+ Hadupils::Assets.expects(:assets_in).never
61
+ assert_equal [], Hadupils::Extensions::Base.gather_assets(nil)
62
+ end
63
+ end
64
+ end
65
+
66
+ context 'a hiverc' do
67
+ context 'static wrapper' do
68
+ setup do
69
+ @klass = Hadupils::Extensions::HiveRC::Static
70
+ end
71
+
72
+ should 'expand the given path into its path attr' do
73
+ path = 'foo/bar/blah'
74
+ assert_equal ::File.expand_path(path), @klass.new(path).path
75
+ end
76
+
77
+ should 'provide a close no-op' do
78
+ assert_respond_to @klass.new('blah'), :close
79
+ end
80
+
81
+ should 'know how to convert to #hive_opts' do
82
+ path = 'some/awesome/path'
83
+ assert_equal ['-i', ::File.expand_path(path)],
84
+ @klass.new(path).hive_opts
85
+ end
86
+ end
87
+
88
+ context 'dynamic wrapper' do
89
+ setup do
90
+ @klass = Hadupils::Extensions::HiveRC::Dynamic
91
+ end
92
+
93
+ should 'use Tempfile for its default file_handler' do
94
+ assert_same ::Tempfile, @klass.file_handler
95
+ end
96
+
97
+ should 'know how to convert to #hive_opts' do
98
+ obj = @klass.new
99
+ obj.stubs(:path).returns(path = mock())
100
+ assert_equal ['-i', obj.path],
101
+ obj.hive_opts
102
+ end
103
+
104
+ context 'internal file' do
105
+ setup do
106
+ @klass.expects(:file_handler).with().returns(@handler = mock())
107
+ @handler.expects(:new).with('hadupils-hiverc').returns(@file = mock())
108
+ end
109
+
110
+ should "come from the class' file_handler" do
111
+ assert_equal @file, @klass.new.file
112
+ end
113
+
114
+ should 'provide the path' do
115
+ @file.stubs(:path).returns(path = mock())
116
+ ::File.stubs(:expand_path).with(path).returns(expanded = mock())
117
+ assert_equal expanded, @klass.new.path
118
+ end
119
+
120
+ should 'close the file on close()' do
121
+ @file.expects(:close).with()
122
+ @klass.new.close
123
+ end
124
+ end
125
+
126
+ context 'write operation' do
127
+ setup do
128
+ @hiverc = @klass.new
129
+ @file = File.open(@hiverc.path, 'r')
130
+ end
131
+
132
+ teardown do
133
+ @file.close
134
+ @hiverc.close
135
+ end
136
+
137
+ should 'handle simple text lines' do
138
+ lines = ['some stuff!', 'but what about...', 'this and this!?!']
139
+ @hiverc.write(lines)
140
+ expect = lines.join("\n") + "\n"
141
+ assert_equal expect, @file.read
142
+ end
143
+
144
+ context 'given assets' do
145
+ setup do
146
+ @asset_lines = ['ADD FILE foofoo;',
147
+ 'ADD ARCHIVE bloobloo.tar.gz;',
148
+ 'ADD JAR jarjar.jar;']
149
+ @assets = @asset_lines.collect do |line|
150
+ m = mock()
151
+ m.stubs(:hiverc_command).with.returns(line)
152
+ m
153
+ end
154
+ end
155
+
156
+ should 'use their hiverc_command for lines' do
157
+ expected = @asset_lines.join("\n") + "\n"
158
+ @hiverc.write(@assets)
159
+ assert_equal expected, @file.read
160
+ end
161
+
162
+ should 'handle intermingled text lines' do
163
+ text_lines = ['some line one', 'some line two']
164
+ [@assets, @asset_lines].each do |ary|
165
+ ary.insert(2, text_lines[1])
166
+ ary.insert(1, text_lines[0])
167
+ end
168
+ expected = @asset_lines.join("\n") + "\n"
169
+ @hiverc.write(@assets)
170
+ assert_equal expected, @file.read
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ context Hadupils::Extensions::Flat do
178
+ setup do
179
+ @klass = Hadupils::Extensions::Flat
180
+ end
181
+
182
+ should 'extend Hadupils::Extensions::Base' do
183
+ # I usually hate testing this sort of thing, but I want to quickly claim
184
+ # that @klass has the basic behaviors and focus on what's special about it.
185
+ assert @klass.ancestors.include? Hadupils::Extensions::Base
186
+ end
187
+
188
+ tempdir_context 'for realz' do
189
+ setup do
190
+ @tempdir.file(@file = 'a.file')
191
+ @tempdir.file(@jar = 'a.jar')
192
+ @tempdir.file(@archive = 'an.archive.tar.gz')
193
+ @file_line = "ADD FILE #{@tempdir.full_path(@file)};"
194
+ @jar_line = "ADD JAR #{@tempdir.full_path(@jar)};"
195
+ @archive_line = "ADD ARCHIVE #{@tempdir.full_path(@archive)};"
196
+ end
197
+
198
+ should 'produce only one hiverc' do
199
+ hivercs = @klass.new(@tempdir.path).hivercs
200
+ assert_equal 1, hivercs.size
201
+ end
202
+
203
+ should 'produce a hiverc for the expected assets' do
204
+ hivercs = @klass.new(@tempdir.path).hivercs
205
+ expected = "#{@file_line}\n#{@jar_line}\n#{@archive_line}\n"
206
+ File.open(hivercs[0].path, 'r') do |f|
207
+ assert_equal expected, f.read
208
+ end
209
+ end
210
+
211
+ should 'produce a hiverc of a dynamic type' do
212
+ # This is because I had a bug and was giving the hiverc File
213
+ # object instead of the dynamic hiverc wrapper object.
214
+ # Thus it blew up later on.
215
+ hivercs = @klass.new(@tempdir.path).hivercs
216
+ assert_kind_of Hadupils::Extensions::HiveRC::Dynamic, hivercs[0]
217
+ end
218
+
219
+ should 'allow manipulation of hiverc items' do
220
+ extension = @klass.new(@tempdir.path) do
221
+ hiverc do |assets|
222
+ assets.insert(1, 'INSERT SOME TEXT HERE')
223
+ assets << 'FINAL LINE!'
224
+ end
225
+ end
226
+ expected = "#{@file_line}\n" +
227
+ "INSERT SOME TEXT HERE\n" +
228
+ "#{@jar_line}\n#{@archive_line}\n" +
229
+ "FINAL LINE!\n"
230
+ File.open(extension.hivercs[0].path, 'r') do |f|
231
+ assert_equal expected, f.read
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ tempdir_context Hadupils::Extensions::Static do
238
+ setup do
239
+ @extension = Hadupils::Extensions::Static.new(@tempdir.path)
240
+ end
241
+
242
+ should 'have an empty list of assets from gather_assets' do
243
+ # These would ordinarily become assets in a dynamic extension.
244
+ @tempdir.file('some.jar')
245
+ @tempdir.file('some.tar.gz')
246
+ @tempdir.file('some.yaml')
247
+ # but not in this one.
248
+ assert_equal [], Hadupils::Extensions::Static.new(@tempdir.path).assets
249
+ end
250
+
251
+ should 'have an empty hivercs list when no hiverc file exists' do
252
+ assert_equal [], @extension.hivercs
253
+ end
254
+
255
+ context 'with a hiverc file' do
256
+ setup do
257
+ @hiverc = @tempdir.file('hiverc')
258
+ end
259
+
260
+ should 'have a static HiveRC instance in its hivercs list when a hiverc file exists' do
261
+ assert_equal [[Hadupils::Extensions::HiveRC::Static, @hiverc.path]],
262
+ @extension.hivercs.collect {|h| [h.class, h.path] }
263
+ end
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,80 @@
1
+ class Hadupils::RunnersTest < Test::Unit::TestCase
2
+ context Hadupils::Runners::Base do
3
+ setup do
4
+ @runner = Hadupils::Runners::Base.new(@params = mock())
5
+ end
6
+
7
+ should 'expose initialization params as attr' do
8
+ assert_equal @params, @runner.params
9
+ end
10
+
11
+ context 'wait!' do
12
+ setup do
13
+ @command = [mock(), mock(), mock()]
14
+ @runner.expects(:command).with.returns(@command)
15
+ # This will ensure that $? is non-nil
16
+ system(RbConfig.ruby, '-v')
17
+ end
18
+
19
+ should 'assemble system call via command method' do
20
+ Kernel.expects(:system).with(*@command).returns(true)
21
+ $?.stubs(:exitstatus).with.returns(mock())
22
+ @runner.wait!
23
+ end
24
+
25
+ should 'return 255 when system returns nil' do
26
+ Kernel.stubs(:system).returns(nil)
27
+ assert_equal 255, @runner.wait!
28
+ end
29
+
30
+ should 'return Process::Status#exitstatus when non-nil system result' do
31
+ Kernel.stubs(:system).returns(true)
32
+ $?.stubs(:exitstatus).with.returns(status = mock())
33
+ assert_equal status, @runner.wait!
34
+ end
35
+ end
36
+ end
37
+
38
+ context Hadupils::Runners::Hive do
39
+ setup do
40
+ @klass = Hadupils::Runners::Hive
41
+ end
42
+
43
+ should 'be a runner' do
44
+ assert_kind_of Hadupils::Runners::Base, @klass.new([])
45
+ end
46
+
47
+ should 'use $HIVE_HOME/bin/hive as the base runner' do
48
+ ENV.expects(:[]).with('HIVE_HOME').returns(home = mock().to_s)
49
+ assert_equal ::File.join(home, 'bin', 'hive'),
50
+ @klass.base_runner
51
+ end
52
+
53
+ context '#command' do
54
+ setup do
55
+ @klass.stubs(:base_runner).returns(@hive_path = mock().to_s + '-hive')
56
+ end
57
+
58
+ should 'provide invocation for bare hive if given empty parameters' do
59
+ assert_equal [@hive_path], @klass.new([]).command
60
+ end
61
+
62
+ should 'provide invocation for hive with all given parameters' do
63
+ params = [mock().to_s, mock().to_s, mock().to_s, mock().to_s]
64
+ assert_equal [@hive_path] + params,
65
+ @klass.new(params).command
66
+ end
67
+
68
+ should 'provide args for hive with :hive_opts on supporting params' do
69
+ p1 = mock()
70
+ p1.expects(:hive_opts).with.returns(p1_opts = ['-i', mock().to_s])
71
+ p2 = mock()
72
+ p2.expects(:hive_opts).with.returns(p2_opts = ['-i', mock().to_s])
73
+ s1 = mock().to_s
74
+ s2 = mock().to_s
75
+ assert_equal [@hive_path, s1] + p1_opts + [s2] + p2_opts,
76
+ @klass.new([s1, p1, s2, p2]).command
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,83 @@
1
+ class Hadupils::SearchTest < Test::Unit::TestCase
2
+ tempdir_context 'find_from_dir' do
3
+ setup do
4
+ @module = Hadupils::Search
5
+ @search_name = mock().to_s
6
+ end
7
+
8
+ should 'return nil if requested directory cannot be found' do
9
+ assert_equal nil, @module.find_from_dir(@search_name, @tempdir.path)
10
+ end
11
+
12
+ should 'should find the directory when it is in the start dir' do
13
+ p = @tempdir.full_path('blah')
14
+ Dir.mkdir p
15
+ assert_equal p, @module.find_from_dir('blah', @tempdir.path)
16
+ end
17
+
18
+ should 'find the directory when it is in a sibling of the start dir' do
19
+ target = @tempdir.full_path('target-dir')
20
+ start = @tempdir.full_path('start-dir')
21
+ [target, start].each {|d| Dir.mkdir(d) }
22
+ assert_equal target, @module.find_from_dir('target-dir', start)
23
+ end
24
+
25
+ should 'find the directory when it is above the start dir' do
26
+ d = @tempdir.full_path('flickityflu')
27
+ Dir.mkdir(d)
28
+ assert_equal @tempdir.path,
29
+ @module.find_from_dir(File.basename(@tempdir.path), d)
30
+ end
31
+ end
32
+
33
+ context 'find_from_pwd' do
34
+ setup do
35
+ @module = Hadupils::Search
36
+ @pwd = ::Dir.pwd
37
+ @target = mock()
38
+ end
39
+
40
+ should 'return the path found by find_from_dir for the pwd' do
41
+ @module.expects(:find_from_dir).with(@target, @pwd).returns(result = mock())
42
+ assert_equal result, @module.find_from_pwd(@target)
43
+ end
44
+
45
+ should 'return nil when given that by find_from_dir for the pwd' do
46
+ @module.expects(:find_from_dir).with(@target, @pwd).returns(nil)
47
+ assert_equal nil, @module.find_from_pwd(@target)
48
+ end
49
+ end
50
+
51
+ context 'user_config' do
52
+ setup do
53
+ @module = Hadupils::Search
54
+ end
55
+
56
+ should 'use ~/conf by default' do
57
+ assert_equal ::File.expand_path(::File.join('~', 'conf')),
58
+ @module.user_config
59
+ end
60
+
61
+ should 'be settable' do
62
+ assert_equal true, @module.respond_to?(:user_config=)
63
+ end
64
+ end
65
+
66
+ context 'hadoop_assets' do
67
+ should 'search for directory specified by #hadoop_assets_name' do
68
+ Hadupils::Search.expects(:hadoop_assets_name).with.returns(name = mock().to_s)
69
+ Hadupils::Search.expects(:find_from_pwd).with(name).returns(dir = mock())
70
+ assert_equal dir, Hadupils::Search.hadoop_assets
71
+ end
72
+ end
73
+
74
+ context 'hadoop_assets_name' do
75
+ should 'default to "hadoop-ext"' do
76
+ assert_equal 'hadoop-ext', Hadupils::Search.hadoop_assets_name
77
+ end
78
+
79
+ should 'be settable' do
80
+ assert_respond_to Hadupils::Search, :hadoop_assets_name=
81
+ end
82
+ end
83
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hadupils
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ethan Rowe
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: mocha
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: should-context
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Provides utilities for dynamic hadoop client environment configuration
79
+ email: ethan@the-rowes.com
80
+ executables:
81
+ - hadupils
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - lib/hadupils/search.rb
86
+ - lib/hadupils/commands.rb
87
+ - lib/hadupils/runners.rb
88
+ - lib/hadupils/extensions.rb
89
+ - lib/hadupils/assets.rb
90
+ - lib/hadupils.rb
91
+ - test/unit/assets_test.rb
92
+ - test/unit/commands_test.rb
93
+ - test/unit/extensions_test.rb
94
+ - test/unit/runners_test.rb
95
+ - test/unit/search_test.rb
96
+ - test/hadupil_test_setup.rb
97
+ - bin/hadupils
98
+ - Rakefile.rb
99
+ - Gemfile.lock
100
+ - README.md
101
+ - Gemfile
102
+ - LICENSE
103
+ - CHANGELOG.md
104
+ homepage: http://github.com/ethanrowe/hadupils
105
+ licenses:
106
+ - MIT
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.25
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: Provides utilities for dynamic hadoop client environment configuration
129
+ test_files: []