spreadsheet_agent 0.0.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.
@@ -0,0 +1,12 @@
1
+ # Author: Darin London
2
+ # The license of this source is "MIT Licence"
3
+
4
+ module SpreadsheetAgent
5
+
6
+ # SpreadsheetAgent::Error is an extension of Error that SpreadsheetAgent classes throw
7
+ # when critical errors are encountered
8
+ class Error < RuntimeError
9
+ end
10
+
11
+ end
12
+
@@ -0,0 +1,306 @@
1
+ # Author: Darin London
2
+ # The license of this source is "MIT Licence"
3
+
4
+ require 'spreadsheet_agent/db'
5
+
6
+ module SpreadsheetAgent
7
+
8
+
9
+ # SpreadsheetAgent::Runner is a class designed to facilitate the automated traversal of all, or some
10
+ # defined set of pages, entries, and goals defined in a SpreadsheetAgent compatible Google Spreadsheet,
11
+ # and run agents or processes on them. By placing a SpreadsheetAgent::Runner script into the scheduling
12
+ # system (cron, etc.) on one or more compute nodes, desired pages, entries, and goals can be processed
13
+ # efficiently over a period of time, and new pages, entries, or goals can be automatically picked up
14
+ # as they are introduced. Runners can be designed to automate the submission of agent scripts, check
15
+ # the status of jobs, aggregate information about job status, or automate cleanup tasks.
16
+ class Runner < SpreadsheetAgent::Db
17
+
18
+ # Boolean. Optional (default false). If true, run will generate the commands that
19
+ # it would run for all runnable entry-goals, print them to STDERR, but not actually
20
+ # run the commands. Automatically sets debug to 1.
21
+ # Note, if the process_entries_with coderef is overridden, dry_run is ignored.
22
+ attr_accessor :dry_run
23
+
24
+ # Boolean, Optional (default false). If true, the default process_entries_with PROC
25
+ # runs each agent_script executable in the foreground, rather than in the background,
26
+ # thus in serial. If false, all agent_script executables are run in parallel, in the
27
+ # background. This is not used when process_entries_with is set to a different PROC.
28
+ attr_accessor :run_in_serial
29
+
30
+ # Boolean, Optional (default false). If true, information about pages, entries, and
31
+ # goals that are checked and filtered is printed to STDERR.
32
+ attr_accessor :debug
33
+
34
+ # Integer, Optional (default 5). The number of seconds that the runner sleeps between
35
+ # each call to process an entry-goal.
36
+ attr_accessor :sleep_between
37
+
38
+ # String Path, Optional. The path to the directory containing agent executable programs that
39
+ # the default process PROC executes. The default is the ../agent_bin directory relative
40
+ # to the directory containing the calling script, $0.
41
+ attr_accessor :agent_bin
42
+
43
+ # Readonly access to the Hash of key_fields, as defined in :config. The runner uses this to construct
44
+ # the commandline for each agent on each entry in a page that gets run, with the value of the
45
+ # GoogleDrive::List entry for the given 'key' passed as argument in the order specified by the
46
+ # 'rank' field for each key in the config.
47
+ attr_reader :query_fields
48
+
49
+ # Readonly access to the array of pages to be processed. Only pages will only be defined
50
+ # when :only_pages or :skip_pages are defined in the constructor params, or when the skip_pages_if,
51
+ # or only_pages_if methods are called.
52
+ attr_reader :only_pages
53
+
54
+ @skip_entry_code = nil
55
+ @skip_goal_code = nil
56
+
57
+ # Create a new SpreadsheetAgent::Runner. Can be created with any of the following optional attributes:
58
+ # * :skip_pages - raises SpreadsheetAgentError if passed along with :only_pages
59
+ # * :only_pages - raises SpreadsheetAgentError if passed along with :skip_pages
60
+ # * :dry_run
61
+ # * :run_in_serial
62
+ # * :debug
63
+ # * :config_file (see SpreadsheetAgent::Db)
64
+ # * :sleep_between
65
+ # * :agent_bin
66
+ #
67
+ def initialize(attributes = { })
68
+ if (!attributes[:skip_pages].nil? && !attributes[:only_pages].nil?)
69
+ raise SpreadsheetAgentError, "You cannot construct a runner with both only_pages and skip_pages"
70
+ end
71
+
72
+ @dry_run = attributes[:dry_run]
73
+ @run_in_serial = attributes[:run_in_serial]
74
+ @debug = attributes[:debug]
75
+ @config_file = attributes[:config_file]
76
+
77
+ @sleep_between = 5
78
+ unless attributes[:sleep_between].nil?
79
+ @sleep_between = attributes[:sleep_between]
80
+ end
81
+
82
+ @agent_bin = find_bin() + '../agent_bin'
83
+ unless attributes[:agent_bin].nil?
84
+ @agent_bin = attributes[:agent_bin]
85
+ end
86
+
87
+ if attributes[:skip_pages]
88
+ @skip_pages = attributes[:skip_pages].clone
89
+ end
90
+
91
+ if attributes[:only_pages]
92
+ @only_pages = attributes[:only_pages].clone
93
+ end
94
+
95
+ build_db()
96
+ @query_fields = build_query_fields()
97
+
98
+ if @skip_pages
99
+ skip_pages_if do |page|
100
+ @skip_pages.include? page
101
+ end
102
+ end
103
+
104
+ if @dry_run
105
+ @debug = true
106
+ end
107
+ end
108
+
109
+ # Provide a PROC designed to intelligently filter out pages that are not to be processed.
110
+ # If not called, all pages not defined in :only_pages, or :skip_pages parameters in the constructor,
111
+ # or a previous call to only_pages_if will be processed.
112
+ # This will override only_pages, or skip_pages passed as arguments to the constructor, and
113
+ # any previous call to skip_pages_if, or only_pages_if. The PROC should take the title of
114
+ # a page as a string, and return true if a process decides to skip the page, false otherwise.
115
+ # Must be called before the process! method to affect the pages it processes. Returns the runner
116
+ # self to facilitate chained processing with skip_goal, skip_entry, and/or process! if desired.
117
+ #
118
+ # skip pages whose title contains 'skip'
119
+ # runner.skip_pages_if {|title| title.match(/skip/) }.process!
120
+ #
121
+ # Same, but without calling process so that skip_entry or skip_goal can be called on the runner
122
+ # runner.skip_pages_if do |title|
123
+ # title.match(/skip/)
124
+ # end
125
+ # ... can call skip_entry, skip_goal, etc
126
+ # runner.process!
127
+ #
128
+ def skip_pages_if(&skip_code)
129
+ @only_pages = @db.worksheets.collect{ |p| p.title }.reject{ |ptitle| skip_code.call(ptitle) }
130
+ self
131
+ end
132
+
133
+ # Provide a PROC desinged to intelligently determine pages to process. If not called, all pages
134
+ # not affected by the :skip_pages, or :only_pages constructor params, or a previous call to
135
+ # skip_pages_if will be processed.
136
+ # This will override only_pages, or skip_pages passed as arguments to the constructor, and
137
+ # any previous call to skip_pages_if, or only_pages_if. The PROC should take the title of
138
+ # a page as a string, and return true if a process decides to include the page, false otherwise.
139
+ # Must be called before the process! method to affect the pages it processes. Returns the runner
140
+ # self to facilitate chained processing with skip_goal, skip_entry, and/or process! if desired.
141
+ #
142
+ # include only pages whose title begins with 'foo'
143
+ # runner.only_pages_if {|title| title.match(/^foo/)}.process!
144
+ #
145
+ # Same, but without calling process so that skip_entry or skip_goal can be called on the runner
146
+ # runner.only_pages_if do |title|
147
+ # title.match(/^foo/)
148
+ # end
149
+ # ... can call skip_entry, skip_goal
150
+ # runner.process!
151
+ #
152
+ def only_pages_if(&include_code)
153
+ @only_pages = @db.worksheets.collect{ |p| p.title }.select { |ptitle| include_code.call(ptitle) }
154
+ self
155
+ end
156
+
157
+ # Provide a PROC desinged to intelligently determine entries on any page to skip. If not called,
158
+ # all entries on processed pages will be processed.
159
+ # The PROC should take a GoogleDrive::List representing the record in the spreadsheet, which can be accessed
160
+ # as a Hash with fields as key and that fields value as value. It should return true if the code decides to
161
+ # skip processing the entry, false otherwise. Must be called before the process! method to affect the entries
162
+ # on each page that it processes. Returns the runner self to facilitate chained processing with skip_pages_if,
163
+ # only_pages_if, skip_goal, and/or process! if desired.
164
+ #
165
+ # skip entries which have run foo or bar
166
+ # runner.only_pages_if {|entry| entry['foo'] == 1 || entry['bar'] == 1 }.process!
167
+ #
168
+ # skip entries that a human reading the spreadsheet has annotated with less than 3.5 in the 'threshold' field
169
+ # runner.only_pages_if do |entry|
170
+ # entry['threshold'] < 3.5
171
+ # end
172
+ # ... can call skip_pages_if, only_pages_if, skip_goal
173
+ # runner.process!
174
+ #
175
+ def skip_entry(&skip_code)
176
+ @skip_entry_code = skip_code
177
+ self
178
+ end
179
+
180
+ # Provide a PROC desinged to skip a specific goal in any entry on all pages processed. If not called,
181
+ # all goals of each entry and page to be processed by the runner will be processed.
182
+ # [note!] Ignored when a PROC is passed to the process! method, e.g. it is only used when process! executes
183
+ # agent scripts for the goal.
184
+ # The PROC should take a string, which will be one of the header fields in the spreadsheet. It should return
185
+ # true if that goal is to be skipped, falsed otherwise.
186
+ # Returns the runner self to facilitate chained processing with skip_pages_if, only_pages_if, skip_entry,
187
+ # and/or process! if desired.
188
+ #
189
+ # skip the 'post_process' goal on each entry of each page processed
190
+ # runner.skip_goal{|goal| goal == 'post_process' }.process!
191
+ #
192
+ # This is best when used in conjunction with skip_entry to skip_goals for particular entries
193
+ # runner.skip_entry{|entry| entry['threshold'] < 2.5 }.skip_goal{|goal| goal == 'post_process' }.process!
194
+ #
195
+ def skip_goal(&skip_code)
196
+ @skip_goal_code = skip_code
197
+ self
198
+ end
199
+
200
+ # Processes configured pages, entries, and goals with a PROC. The default PROC takes the entry, iterates
201
+ # over each goal not skipped by skip_goal, and:
202
+ # * determines if an executable #{ @agent_bin }/#{ goal }_agent.rb script exists
203
+ # * if so, executes the goal_agent script with commandline arguments constructed from the values in the entry for each field in the query_fields array defined in config.
204
+ # If run_in_serial is false, the default PROC runs each agent in the background, in parallel.
205
+ # Otherwise, it runs each serially in the foreground. If dry_run is true, the command is printed to STDERR,
206
+ # but is not run.
207
+ # A PROC supplied to override the default PROC should take an GoogleDrive::List, and GoogleDrive::Worksheet as arguments.
208
+ # This allows the process to query the entry for information using its hash access, and/or update the entry on the
209
+ # spreadsheet. In order for changes to the GoogleDrive::List to take effect, the GoogleDrive::Worksheet must be saved in the PROC.
210
+ # The process sleeps @sleep_between between each call to the PROC (default or otherwise). If dry_run is true
211
+ # when a PROC is supplied, the page.title and runnable_entry hash inspection are printed to STDERR but the PROC
212
+ # is not called.
213
+ #
214
+ # # call each goal agent script in agent_bin on each entry in each page
215
+ # runner = SpreadsheetAgent::Runer.new
216
+ # runner.process!
217
+ #
218
+ # # find entries with a threshold > 5 and update the 'threshold_exceeded' field
219
+ # runner.skip_entry{|entry| entry['threshold'] <= 5 }.process! do |entry,page|
220
+ # entry.update 'threshold_exceeded', "1"
221
+ # page.save
222
+ #
223
+ # # only process entries on the 'main' page where the threshold has not been exceeded
224
+ # runner.only_pages = ['main']
225
+ # runner.skip_entry{|entry| entry['threshold'] != 1 }.process!
226
+ #
227
+ def process!(&runner_code)
228
+ get_runnable_entries().each do |entry_info|
229
+ entry_page, runnable_entry = entry_info
230
+ if runner_code.nil?
231
+ default_process(runnable_entry)
232
+ elsif @dry_run
233
+ $stderr.print "Would run #{ entry_page.title } #{ runnable_entry.inspect }"
234
+ else
235
+ runner_code.call(runnable_entry, entry_page)
236
+ end
237
+ sleep @sleep_between
238
+ end
239
+ end
240
+
241
+ private
242
+
243
+ def get_runnable_entries
244
+ runnable_entries = []
245
+ get_pages_to_process().each do |page|
246
+ $stderr.puts "Processing page " + page.title if @debug
247
+ runnable_entries += page.list.reject { |check_entry | @skip_entry_code.nil? ? false : @skip_entry_code.call(check_entry) }.collect{ |entry| [page, entry] }
248
+ end
249
+
250
+ return runnable_entries
251
+ end
252
+
253
+ def run_entry_goal(entry, goal)
254
+ $stderr.puts "Running goal #{goal}" if @debug
255
+
256
+ goal_agent = [@agent_bin, "#{ goal }_agent.rb"].join('/')
257
+ cmd = [goal_agent]
258
+
259
+ @query_fields.each do |query_field|
260
+ if entry[query_field]
261
+ cmd.push entry[query_field]
262
+ end
263
+ end
264
+
265
+ command = cmd.join(' ')
266
+ command += '&' unless @run_in_serial
267
+ $stderr.puts command if @debug
268
+ if File.executable?(goal_agent)
269
+ unless @dry_run
270
+ system(command)
271
+ end
272
+ else
273
+ $stderr.puts "AGENT RUNNER DOES NOT EXIST!" if @debug
274
+ end
275
+ end
276
+
277
+ def default_process(runnable_entry)
278
+ title = query_fields.collect { |field| runnable_entry[field] }.join(' ')
279
+ $stderr.puts "Checking goals for #{ title }" if @debug
280
+
281
+ runnable_entry.keys.reject { |key|
282
+ if @skip_goal_code.nil?
283
+ false
284
+ else
285
+ ( @skip_goal_code.call(key) )
286
+ end
287
+ }.each do |goal|
288
+ run_entry_goal(runnable_entry, goal)
289
+ sleep @sleep_between
290
+ end
291
+ end
292
+
293
+ def build_query_fields
294
+ @config['key_fields'].keys.sort { |a,b| @config['key_fields'][a]['rank'] <=> @config['key_fields'][b]['rank'] }
295
+ end
296
+
297
+ def get_pages_to_process
298
+ if @only_pages.nil?
299
+ @db.worksheets
300
+ else
301
+ @db.worksheets.select { |page| @only_pages.include? page.title }
302
+ end
303
+ end
304
+ end #Runner
305
+ end #SpreadsheetAgent
306
+
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift( File.expand_path( File.dirname(__FILE__) + '/../../lib' ) )
3
+ require 'spreadsheet_agent'
4
+
5
+ page, entry = ARGV
6
+
7
+ google_agent = SpreadsheetAgent::Agent.new({
8
+ :agent_name => File.basename($0).sub('_agent.rb',''),
9
+ :config_file => File.expand_path( File.dirname(__FILE__) + '/../../config/agent.conf.yml' ),
10
+ :page_name => page,
11
+ :keys => { 'testentry' => entry, 'testpage' => page }
12
+ })
13
+
14
+ google_agent.process! do |entry|
15
+ true
16
+ end
17
+ exit
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift( File.expand_path( File.dirname(__FILE__) + '/../../lib' ) )
4
+ require 'spreadsheet_agent'
5
+
6
+ page, entry = ARGV
7
+
8
+ google_agent = SpreadsheetAgent::Agent.new({
9
+ :agent_name => File.basename($0).sub('_agent.rb',''),
10
+ :config_file => File.expand_path( File.dirname(__FILE__) + '/../../config/agent.conf.yml' ),
11
+ :page_name => page,
12
+ :keys => { 'testentry' => entry, 'testpage' => page }
13
+ })
14
+
15
+ google_agent.process! do |entry|
16
+ true
17
+ end
18
+ exit
@@ -0,0 +1,13 @@
1
+ require 'test/unit'
2
+ require 'spreadsheet_agent/db'
3
+
4
+ class TC_SpreadsheetAgentDbTest < Test::Unit::TestCase
5
+ def test_instantiate_db
6
+ conf_file = File.expand_path(File.dirname( __FILE__ )) + '/../config/agent.conf.yml'
7
+ assert File.exists?( conf_file ), "You must create a valid test Google Spreadsheet and a valid #{ conf_file } configuration file pointing to it to run the tests. See README.txt file for more information on how to run the tests."
8
+
9
+ google_db = SpreadsheetAgent::Db.new()
10
+ assert_not_nil google_db
11
+ assert_instance_of(SpreadsheetAgent::Db, google_db)
12
+ end
13
+ end #TC_SpreadsheetAgentDbTest
@@ -0,0 +1,468 @@
1
+ require 'test/unit'
2
+ require 'shoulda/context'
3
+ require 'spreadsheet_agent'
4
+ require 'spreadsheet_agent/runner'
5
+ require 'capture_io'
6
+
7
+ class TC_SpreadsheetAgentRunnerTest < Test::Unit::TestCase
8
+
9
+ context 'Runner' do
10
+
11
+ setup do
12
+ @config_file = File.expand_path(File.dirname( __FILE__ )) + '/../config/agent.conf.yml'
13
+
14
+ unless File.exists? @config_file
15
+ $stderr.puts "You must create a valid test Google Spreadsheet and a valid #{ @config_file } configuration file pointing to it to run the tests. See README.txt file for more information on how to run the tests."
16
+ exit(1)
17
+ end
18
+ @test_agent_bin = find_bin() + 'agent_bin'
19
+ @testing_pages = nil
20
+ @agent_scripts = nil
21
+ end
22
+
23
+ teardown do
24
+ runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :config_file => @config_file)
25
+ if runner.db.worksheet_by_title('testing').nil?
26
+ tpage = runner.db.add_worksheet('testing')
27
+ tpage.max_rows = 2
28
+ tpage.save
29
+ end
30
+ unless @testing_pages.nil?
31
+ @testing_pages.each do |testing_page|
32
+ testing_page.delete
33
+ end
34
+ end
35
+ agent_scripts_executable(false)
36
+ end
37
+
38
+ context 'instantiated' do
39
+
40
+ should 'be a SpreadsheetAgent::Runner' do
41
+ runner = SpreadsheetAgent::Runner.new(:config_file => @config_file)
42
+ assert_not_nil runner, 'runner is nil!'
43
+ assert_instance_of(SpreadsheetAgent::Runner, runner)
44
+ end
45
+
46
+ end #instantiated
47
+
48
+ context 'agent_bin' do
49
+
50
+ should 'have a sensible default' do
51
+ expected_agent_bin = File.expand_path(File.dirname( $0 )) + '/../agent_bin'
52
+ runner = SpreadsheetAgent::Runner.new(:config_file => @config_file)
53
+ assert_equal expected_agent_bin, runner.agent_bin
54
+ end
55
+
56
+ should 'be overridable on construction' do
57
+ runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :config_file => @config_file)
58
+ assert_equal @test_agent_bin, runner.agent_bin
59
+ end
60
+
61
+ end #agent_bin
62
+
63
+ context 'agent scripts' do
64
+
65
+ setup do
66
+ @entries = {'dotest' => false, 'donottest' => false }
67
+ @headers = ['testentry', 'testpage', 'ready', 'testgoal', 'othergoal', 'complete']
68
+ @runner = prepare_runner(
69
+ ['foo_page', 'bar_page'],
70
+ @headers,
71
+ @entries
72
+ )
73
+ end
74
+
75
+ should 'pass tests' do
76
+ #should 'not run if scripts in agent_bin are not executable' do
77
+ reset_testing_pages(@testing_pages,true)
78
+ @testing_pages.each do |page|
79
+ page.reload
80
+ page.rows(1).each do |entry|
81
+ assert_equal 1, entry[2].to_i
82
+ end
83
+ end
84
+ agent_scripts_executable(false)
85
+
86
+ Dir.glob("#{ @test_agent_bin }/*.rb") do |rb_file|
87
+ assert !File.executable?(rb_file), "#{ rb_file } should not be executable"
88
+ end
89
+
90
+ @runner.process!
91
+
92
+ @testing_pages.each do |page|
93
+ page.reload
94
+ page.rows(1).each do |entry|
95
+ assert entry[3].nil? || entry[3].empty?, "entry #{ entry[0] } should not have run!: #{ entry.inspect }"
96
+ assert entry[4].nil? || entry[3].empty?, "entry #{ entry[0] } should not have run!: #{ entry.inspect }"
97
+ end
98
+ end
99
+
100
+ #should not run on entries that are not ready
101
+ reset_testing_pages(@testing_pages)
102
+ agent_scripts_executable(true)
103
+ @testing_pages.each do |page|
104
+ page.rows(1).each do |entry|
105
+ assert entry[2].to_i != 1, "entry #{ entry[0] } should not be ready!: #{ entry.inspect }"
106
+ end
107
+ end
108
+
109
+ @runner.process!
110
+ sleep 2
111
+
112
+ @testing_pages.each do |page|
113
+ page.reload
114
+ page.rows(1).each do |entry|
115
+ assert entry[3].to_i != 1, "entry #{ entry[0] } should not have run!: #{ entry.inspect }"
116
+ end
117
+ end
118
+
119
+ # should 'if executable, run for each goal of each ready entry by default' do
120
+ reset_testing_pages(@testing_pages,true)
121
+
122
+ @runner.process!
123
+ sleep 5
124
+ @testing_pages.each do |page|
125
+ page.reload
126
+ page.rows(1).each do |entry|
127
+ assert_equal "1", entry[4]
128
+ end
129
+ end
130
+
131
+ # should 'allow code to be passed to override default process! function' do
132
+ reset_testing_pages(@testing_pages, true)
133
+ expected_to_run = {}
134
+ @testing_pages.each do |page|
135
+ expected_to_run[page.title] = {}
136
+ (2..page.num_rows).to_a.each do |rownum|
137
+ expected_to_run[page.title][page[rownum,1]] = false
138
+ end
139
+ page.save
140
+ end
141
+
142
+ @runner.process! do |ran_entry, ran_page|
143
+ ran_entry.update 'testgoal' => "1"
144
+ ran_page.save
145
+ expected_to_run[ran_page.title][ran_entry['testentry']] = true
146
+ end
147
+ sleep 2
148
+
149
+ @testing_pages.each do |page|
150
+ page.reload
151
+ page.rows(1).each do |entry|
152
+ assert_equal "1", entry[2]
153
+ assert_equal "1", entry[3]
154
+ assert expected_to_run[page.title][entry[0]], "#{ page.title } should have been processed on #{ entry[0] }"
155
+ end
156
+ end
157
+
158
+ # should 'allow code passed to override default process! function to update fields on each entry' do
159
+ reset_testing_pages(@testing_pages, true)
160
+
161
+ @runner.process! do |ran_entry, ran_page|
162
+ ran_entry.update 'complete' => "1"
163
+ ran_page.save
164
+ end
165
+ sleep 2
166
+
167
+ @testing_pages.each do |page|
168
+ page.reload
169
+ page.rows(1).each do |entry|
170
+ assert_equal "1", entry[5]
171
+ end
172
+ end
173
+
174
+ # should 'not print anything to STDERR if debug is not set to true' do
175
+ reset_testing_pages(@testing_pages, true)
176
+
177
+ nothing_output = CaptureIO.new
178
+ nothing_output.start
179
+ @runner.process! do |re, rp|
180
+ re.update 'othergoal' => "1"
181
+ rp.save
182
+ end
183
+ nothing_captured = nothing_output.stop
184
+ assert nothing_captured[:stderr].length < 1, 'should not have captured any stderr'
185
+ @testing_pages.each do |page|
186
+ page.reload
187
+ page.rows(1).each do |entry|
188
+ assert_equal "1", entry[4]
189
+ end
190
+ end
191
+
192
+ # should 'allow debug to be set and print output to STDERR' do
193
+ reset_testing_pages(@testing_pages, true)
194
+ @runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :debug => true, :config_file => @config_file)
195
+ assert @runner.debug, 'debug should be true'
196
+ debug_output = CaptureIO.new
197
+ debug_output.start
198
+ @runner.process! do |re, rp|
199
+ re.update 'testgoal' => "1"
200
+ rp.save
201
+ end
202
+ debugged_output = debug_output.stop
203
+ assert debugged_output[:stderr].length > 0, "should have captured stderr in debug mode"
204
+ sleep 2
205
+ @testing_pages.each do |page|
206
+ page.reload
207
+ page.rows(1).each do |entry|
208
+ assert_equal "1", entry[3]
209
+ end
210
+ end
211
+
212
+ # should 'allow dry_run to be set, and not run any entries, but set debug to true' do
213
+ reset_testing_pages(@testing_pages, true)
214
+ @runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :dry_run => true, :config_file => @config_file)
215
+ assert @runner.debug, 'debug should be true with dry run'
216
+
217
+ dry_output = CaptureIO.new
218
+ dry_output.start
219
+ @runner.process! do |re, rp|
220
+ re.update 'testgoal' => "1"
221
+ rp.save
222
+ end
223
+ captured_dry_output = dry_output.stop
224
+ assert captured_dry_output[:stderr].length > 0, 'should have captured stderr in debug mode'
225
+ sleep 2
226
+ @testing_pages.each do |page|
227
+ page.reload
228
+ page.rows(1).each do |entry|
229
+ assert entry[3].nil? || entry[3].empty?, "#{ page.title } should not have run #{ entry[0] } #{ entry.inspect }"
230
+ end
231
+ end
232
+
233
+ # should 'allow sleep_between to be set, and sleep that amount between process for each entry' do
234
+ reset_testing_pages(@testing_pages, true)
235
+ expected_time_between = 10
236
+ @runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :sleep_between => expected_time_between,:config_file => @config_file)
237
+
238
+ beginning_time = Time.now
239
+ first_time = true
240
+ @runner.process! do |re, rp|
241
+ if first_time
242
+ first_time = false
243
+ else
244
+ elapsed_time = Time.now - beginning_time
245
+ assert_equal expected_time_between, elapsed_time.to_i
246
+ end
247
+ re.update 'testgoal' => "1"
248
+ rp.save
249
+ beginning_time = Time.now
250
+ end
251
+ sleep 2
252
+
253
+ @testing_pages.each do |page|
254
+ page.reload
255
+ page.rows(1).each do |entry|
256
+ assert_equal "1", entry[3]
257
+ end
258
+ end
259
+
260
+ #should 'allow skip_entry to be set with code, and only process entries that return false from the code when passed the entry' do
261
+ reset_testing_pages(@testing_pages, true)
262
+
263
+ @runner.skip_entry do |entry|
264
+ (entry['testentry'] == 'donottest')
265
+ end
266
+
267
+ @runner.process! do |ran_entry, ran_page|
268
+ ran_entry.update 'testgoal' => "1"
269
+ ran_page.save
270
+ end
271
+ sleep 2
272
+ @testing_pages.each do |page|
273
+ page.reload
274
+ page.rows(1).each do |entry|
275
+ if entry[0] == 'donottest'
276
+ assert entry[3].nil? || entry[3].empty?, "#{ page.title } #{ entry[0]} should not have run"
277
+ else
278
+ assert_equal "1", entry[3]
279
+ end
280
+ end
281
+ end
282
+
283
+ # should 'allow skip_goal to be set with code, and only process goals for each entry that return false from the code when passed the goal' do
284
+ reset_testing_pages(@testing_pages, true)
285
+
286
+ @runner.skip_goal { |goal|
287
+ if goal == 'othergoal'
288
+ return true
289
+ else
290
+ return false
291
+ end
292
+ }.process!
293
+ sleep 2
294
+ @testing_pages.each do |page|
295
+ page.reload
296
+ page.rows(1).each do |entry|
297
+ assert entry[3] == "1", "entry #{ entry[0] } should have run testgoal #{ entry.inspect }"
298
+ assert entry[4].nil? || entry[4].empty?, "othergoal should not have run for any entry #{ entry.inspect }"
299
+ end
300
+ end
301
+
302
+ #should 'allow only_pages to be set and only process @only_pages' do
303
+ @test_only_pages = ['foo_page', 'baz_page']
304
+ @runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :only_pages => @test_only_pages, :config_file => @config_file)
305
+ @testing_pages.push(add_page_to_runner(@runner, 'baz', @headers, @entries))
306
+
307
+
308
+ expected_to_run = {}
309
+ @testing_pages.each do |page|
310
+ expected_to_run[page.title] = @test_only_pages.include? page.title
311
+ end
312
+
313
+ @runner.process! do |ran_entry, ran_page|
314
+ ran_entry.update 'complete' => "1"
315
+ ran_page.save
316
+ assert expected_to_run[ran_page.title], "#{ ran_page.title } is not in #{ @test_only_pages.inspect }!"
317
+ end
318
+
319
+ @testing_pages.select { |page| @test_only_pages.include? page.title }.each do |page|
320
+ page.reload
321
+ page.rows(1).each do |entry|
322
+ assert_equal "1", entry[5]
323
+ end
324
+ end
325
+
326
+ #should 'allow only_pages_if to be set and only process those pages' do
327
+ @runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin,:config_file => @config_file)
328
+ reset_testing_pages(@testing_pages, true)
329
+
330
+ @runner.only_pages_if{ |title|
331
+ if title.match('ba*')
332
+ return true
333
+ else
334
+ return false
335
+ end
336
+ }.process! do |ran_entry, ran_page|
337
+ ran_entry.update 'complete' => "1"
338
+ ran_page.save
339
+ assert ran_page.title.match('ba*'), "#{ ran_page.title } was not expected to run!"
340
+ end
341
+
342
+ @testing_pages.select { |page| page.title.match('ba*') }.each do |page|
343
+ page.reload
344
+ page.rows(1).each do |entry|
345
+ assert_equal "1", entry[5]
346
+ end
347
+ end
348
+
349
+ #should 'allow skip_pages to be set and only process pages not in @skip_pages' do
350
+ @test_skip_pages = ['foo_page', 'baz_page']
351
+ @runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin, :skip_pages => @test_skip_pages,:config_file => @config_file)
352
+ reset_testing_pages(@testing_pages, true)
353
+ expected_to_run = {}
354
+ @testing_pages.each do |page|
355
+ expected_to_run[page.title] = !( @test_skip_pages.include? page.title )
356
+ end
357
+
358
+ @runner.process! do |ran_entry, ran_page|
359
+ ran_entry.update 'complete' => "1"
360
+ ran_page.save
361
+ assert expected_to_run[ran_page.title], "#{ ran_page.title } is in #{ @test_skip_pages.inspect }!"
362
+ end
363
+
364
+ @testing_pages.each do |page|
365
+ page.reload
366
+ if @test_skip_pages.include?(page.title)
367
+ should_equal = nil
368
+ else
369
+ should_equal = "1"
370
+ end
371
+ page.rows(1).each do |entry|
372
+ assert_equal should_equal, entry[4]
373
+ end
374
+ end
375
+
376
+ #should 'allow skip_pages_if to be set and only process pages not skipped by the code' do
377
+ @runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin,:config_file => @config_file)
378
+ reset_testing_pages(@testing_pages, true)
379
+
380
+ @runner.skip_pages_if { |title|
381
+ title.match('ba*')
382
+ }.process! do |ran_entry, ran_page|
383
+ ran_entry.update 'complete' => "1"
384
+ ran_page.save
385
+ assert !( ran_page.title.match('ba*') ), "#{ ran_page.title } was not expected to run!"
386
+ end
387
+
388
+ @testing_pages.reject { |page| page.title.match('ba*') }.each do |page|
389
+ page.reload
390
+ page.rows(1).each do |entry|
391
+ assert_equal "1", entry[5]
392
+ end
393
+ end
394
+
395
+ end #pass tests
396
+
397
+ end #agent script executable
398
+
399
+ end #Runner
400
+
401
+ def find_bin()
402
+ File.expand_path(File.dirname( __FILE__ )) + '/'
403
+ end
404
+
405
+ def prepare_runner(pages, headers, entries)
406
+ runner = SpreadsheetAgent::Runner.new(:agent_bin => @test_agent_bin,:config_file => @config_file)
407
+ @testing_pages = []
408
+
409
+ pages.each do |page|
410
+ @testing_pages.push(add_page_to_runner(runner, page, headers, entries))
411
+ end
412
+ testing_page = runner.db.worksheet_by_title('testing')
413
+ unless testing_page.nil?
414
+ testing_page.delete
415
+ end
416
+
417
+ runner
418
+ end
419
+
420
+ def add_page_to_runner(runner, page, headers, entries)
421
+ testing_page = runner.db.worksheet_by_title(page)
422
+ if testing_page.nil?
423
+ testing_page = runner.db.add_worksheet(page)
424
+ else
425
+ testing_page.max_rows = 1
426
+ end
427
+
428
+ colnum = 1
429
+ headers.each do |field|
430
+ testing_page[1,colnum] = field
431
+ colnum += 1
432
+ end
433
+ entry_row = 2
434
+ entries.each do |entry, ready|
435
+ testing_page[entry_row,1] = entry
436
+ testing_page[entry_row,2] = page
437
+ testing_page[entry_row,3] = "1" if ready
438
+ entry_row += 1
439
+
440
+ end
441
+ testing_page.max_rows = entry_row
442
+ testing_page.save
443
+ testing_page
444
+ end
445
+
446
+ def agent_scripts_executable(set_executable)
447
+ if set_executable
448
+ `chmod 700 #{ @test_agent_bin }/*.rb`
449
+ else
450
+ `chmod 600 #{ @test_agent_bin }/*.rb`
451
+ end
452
+ end
453
+
454
+ def reset_testing_pages(testing_pages, ready = false)
455
+ ready_value = ready ? "1" : nil
456
+
457
+ testing_pages.each do |page|
458
+ (2..page.num_rows).to_a.each do |rownum|
459
+ page[rownum,3] = ready_value
460
+ page[rownum,4] = nil
461
+ page[rownum,5] = nil
462
+ page[rownum,6] = nil
463
+ end
464
+ page.save
465
+ end
466
+ end
467
+
468
+ end #TC_SpreadsheetAgentRunnerTest