spreadsheet_agent 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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