prspec 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/prspec.rb +393 -393
  2. metadata +2 -2
@@ -1,394 +1,394 @@
1
- require 'thread'
2
- require 'optparse'
3
- require 'log4r'
4
- require 'parallel'
5
- require 'yaml'
6
- require 'tempfile'
7
- require 'fileutils'
8
- include Log4r
9
-
10
- $log = Logger.new('prspec')
11
- $log.outputters = Outputter.stdout
12
- level = ENV['log_level'] || ''
13
- case level.downcase
14
- when 'all'
15
- $log.level = ALL
16
- when 'debug'
17
- $log.level = DEBUG
18
- when 'info'
19
- $log.level = INFO
20
- when 'warn'
21
- $log.level = WARN
22
- else
23
- $log.level = ERROR
24
- end
25
-
26
- class PRSpec
27
- attr_accessor :num_threads, :processes, :tests, :output
28
- SPEC_FILE_FILTER = '_spec.rb'
29
- INFO_FILE = ".prspec"
30
-
31
- def initialize(args)
32
- @output = ''
33
- # create tracking file
34
- yml = { :running_threads => 0 }
35
- File.open(INFO_FILE, 'w') { |f| f.write yml.to_yaml }
36
- if (!args.nil? && args.length > 0 && !args[0].nil?)
37
- opts = parse_args(args)
38
- if (!opts[:help])
39
- @num_threads = opts[:thread_count]
40
-
41
- @tests = get_spec_tests(opts)
42
- if (tests.length > 0)
43
- process_tests = divide_spec_tests(tests)
44
- $log.debug "#{tests.length} Spec tests divided among #{@num_threads} arrays."
45
- else
46
- $log.warn "No spec tests found. Exiting."
47
- exit 1
48
- end
49
-
50
- $log.info "Creating array of Child Processes..."
51
- @processes = build_process_array(process_tests, opts)
52
-
53
- begin_run(@processes, opts)
54
- end
55
- end
56
- ensure
57
- FileUtils.remove_file(INFO_FILE, :force => true) if File.exists?(INFO_FILE)
58
- end
59
-
60
- def parse_args(args)
61
- $log.debug("Parsing arguments of: #{args}")
62
- options = {
63
- :dir=>'.',
64
- :path=>'spec',
65
- :thread_count=>get_number_of_processors,
66
- :test_mode=>false,
67
- :help=>false,
68
- :excludes=>nil,
69
- :rspec_args=>[],
70
- :tag_name=>'',
71
- :tag_value=>'',
72
- :ignore_pending=>false,
73
- :serialize=>false
74
- }
75
- o = OptionParser.new do |opts|
76
- opts.banner = "Usage: prspec [options]"
77
- opts.on("-p", "--path PATH", "Relative path from the base directory to search for spec files") do |v|
78
- $log.debug "path specified... value: #{v}"
79
- options[:path] = v
80
- end
81
- opts.on("-e", "--exclude REGEX", "Regex string used to exclude files") do |v|
82
- $log.debug "excludes specified... value: #{v}"
83
- options[:excludes] = v
84
- end
85
- opts.on("-d", "--dir DIRECTORY", "The base directory to run from") do |v|
86
- $log.debug "directory specified... value: #{v}"
87
- options[:dir] = v
88
- end
89
- opts.on("-n", "--num-threads THREADS", "The number of threads to use") do |v|
90
- $log.debug "number of threads specified... value: #{v}"
91
- options[:thread_count] = v.to_i
92
- end
93
- opts.on("-t", "--tag TAG", "A rspec tag value to filter by") do |v|
94
- $log.debug "tag filter specified... value: #{v}"
95
- tag = v
96
- value = 'true'
97
- if (v.include?(':')) # split to tag and value
98
- tag_value = v.split(':')
99
- tag = ":#{tag_value[0]}"
100
- value = "#{tag_value[1]}"
101
- end
102
- options[:tag_name] = tag
103
- options[:tag_value] = value
104
- end
105
- opts.on("-r", "--rspec-args \"RSPEC_ARGS\"", "Additional arguments to be passed to rspec (must be surrounded with double quotes)") do |v|
106
- $log.debug "rspec arguments specified... value: #{v}"
107
- options[:rspec_args] = v.gsub(/"/,'').split(' ') # create an array of each argument
108
- end
109
- opts.on("--test-mode", "Do everything except actually starting the test threads") do
110
- $log.debug "test mode specified... threads will NOT be started."
111
- options[:test_mode] = true
112
- end
113
- opts.on("-q", "--quiet", "Quiet mode. Do not display parallel thread output") do
114
- $log.debug "quiet mode specified... thread output will not be displayed"
115
- options[:quiet_mode] = true
116
- end
117
- opts.on("-h", "--help", "Display a help message") do
118
- $log.debug "help message requested..."
119
- options[:help] = true
120
- puts opts
121
- end
122
- opts.on("--ignore-pending", "Ignore all pending tests") do
123
- $log.debug "ignore pending specified... all pending tests will be excluded"
124
- options[:ignore_pending] = true
125
- end
126
- opts.on("-s", "--serialize-output", "Wait for each thread to complete and then output to STDOUT serially") do
127
- $log.debug "serialize output specified... all threads will output to STDOUT as they complete"
128
- options[:serialize] = true
129
- end
130
- end
131
-
132
- # handle invalid options
133
- begin
134
- o.parse! args
135
- rescue OptionParser::InvalidOption => e
136
- $log.error e
137
- puts o
138
- exit 1
139
- end
140
-
141
- return options
142
- end
143
-
144
- def get_number_of_processors
145
- count = Parallel.processor_count
146
- return count
147
- end
148
-
149
- def self.get_number_of_running_threads
150
- prspec_info = YAML.load_file(INFO_FILE)
151
- return prspec_info[:running_threads].to_i
152
- end
153
-
154
- def get_spec_tests(options)
155
- files = get_spec_files(options)
156
- tests = get_tests_from_files(files, options)
157
- $log.debug "Found #{tests.length} tests in #{files.length} files"
158
- return tests
159
- end
160
-
161
- def get_spec_files(options)
162
- base_dir = options[:dir]
163
- path = options[:path]
164
- if (path.nil? || path == '')
165
- path = '.'
166
- end
167
- full_path = ""
168
- if (path.end_with?('.rb'))
169
- full_path = File.join(base_dir, path.to_s)
170
- else
171
- full_path = File.join(base_dir, path.to_s, '**', "*#{SPEC_FILE_FILTER}")
172
- end
173
- $log.debug "full_path: #{full_path}"
174
-
175
- files = []
176
- if (options[:excludes].nil?)
177
- files = Dir.glob(full_path)
178
- else
179
- files = Dir.glob(full_path).reject { |f| f[options[:excludes]] }
180
- end
181
-
182
- return files
183
- end
184
-
185
- def get_tests_from_files(files, options)
186
- tests = []
187
- match_test_name_format = /^[\s]*(it)[\s]*(')[\s\S]*(')[\s\S]*(do)/
188
- files.each do |file|
189
- lines = File.readlines(file)
190
- for i in 0..lines.length-1
191
- if lines[i] =~ match_test_name_format
192
- m = lines[i]
193
- match = true
194
- if (options[:tag_name] != '')
195
- if (m.rindex(options[:tag_name]).nil? || (m.rindex(options[:tag_value]) <= m.rindex(options[:tag_name])))
196
- match = false
197
- end
198
- end
199
- # if ignore_pending specified then skip tests containing 'pending' on next line
200
- if (options[:ignore_pending])
201
- if (i+1 < lines.length-1 && lines[i+1].include?('pending'))
202
- match = false
203
- end
204
- end
205
- if (match)
206
- description = get_test_description(m)
207
- tests.push("\"#{description}\"")
208
- end
209
- end
210
- end
211
- end
212
-
213
- return tests
214
- end
215
-
216
- def get_test_description(description_line)
217
- # get index of first "'"
218
- trim_front = description_line.sub(/[\s]*(it)[\s]*(')/,'')
219
- description = trim_front.sub(/(')[\s]*(,[\s\S]*)*(do)[\s]*/,'')
220
- return description.gsub(/["]/,'\"').gsub(/\\'/,"'")
221
- end
222
-
223
- def divide_spec_tests(tests)
224
- if (tests.length < @num_threads)
225
- @num_threads = tests.length
226
- $log.info "reducing number of threads due to low number of spec tests found: Threads = #{@num_threads}"
227
- end
228
- spec_arrays = Array.new(@num_threads)
229
- num_per_thread = tests.length.fdiv(@num_threads).ceil
230
- $log.debug "Approximate number of tests per thread: #{num_per_thread}"
231
- # ensure an even distribution
232
- i = 0
233
- tests.each do |tname|
234
- if (i >= @num_threads)
235
- i = 0
236
- end
237
- if (spec_arrays[i].nil?)
238
- spec_arrays[i] = []
239
- end
240
-
241
- spec_arrays[i].push(tname)
242
-
243
- i+=1
244
- end
245
-
246
- return spec_arrays
247
- end
248
-
249
- def build_process_array(process_tests, opts)
250
- processes = []
251
- for i in 0..@num_threads-1
252
- processes[i] = PRSpecThread.new(i, process_tests[i], {'TEST_ENV_NUMBER'=>i, 'HOME'=>nil}, opts)
253
- end
254
- return processes
255
- end
256
-
257
- def begin_run(processes, options)
258
- if (!processes.nil? && processes.length > 0)
259
- $log.info "Starting all Child Processes..."
260
- update_running_thread_count(processes.length)
261
- processes.each do |proc|
262
- if (proc.is_a?(PRSpecThread) && options.is_a?(Hash))
263
- proc.start unless options[:test_mode]
264
- else
265
- raise "Invalid datatype where PRSpecThread or Hash exepcted. Found: #{proc.class.to_s}, #{options.class.to_s}"
266
- end
267
- end
268
- $log.info "All processes started..."
269
- while processes.length > 0
270
- processes.each do |proc|
271
- if (!proc.done?) # confirm threads are running
272
- $log.debug "Thread#{proc.id}: alive..."
273
- else
274
- $log.debug "Thread#{proc.id}: done."
275
- if (options[:serialize])
276
- puts proc.output unless options[:quiet_mode]
277
- end
278
- # collect thread output if in quiet mode
279
- if (options[:quiet_mode])
280
- @output << proc.output
281
- end
282
- processes.delete(proc) # remove from the array of processes so we don't count it again
283
- update_running_thread_count(processes.length)
284
- end
285
- end
286
- sleep 0.5 # wait half a second for processes to run and then re-check their status
287
- end
288
- $log.info "All processes complete."
289
- else
290
- raise "Invalid input passed to method: 'processes' must be a valid Array of PRSpecThread objects"
291
- end
292
- end
293
-
294
- def update_running_thread_count(count)
295
- (file = File.new(INFO_FILE,'w')).flock(File::LOCK_EX)
296
- yml = { :running_threads => count }
297
- file.write yml.to_yaml
298
- ensure
299
- file.flock(File::LOCK_UN)
300
- file.close
301
- end
302
-
303
- def running?
304
- @processes.each do |proc|
305
- if (!proc.done?)
306
- $log.debug "Found running process..."
307
- return true
308
- end
309
- end
310
- return false
311
- end
312
-
313
- def close
314
- @processes.each do |proc|
315
- proc.close
316
- end
317
- end
318
-
319
- def self.is_windows?
320
- return (RUBY_PLATFORM.match(/mingw/i)) ? true : false
321
- end
322
- end
323
-
324
- class PRSpecThread
325
- attr_accessor :thread, :id, :tests, :env, :args, :output
326
- def initialize(id, tests, environment, args)
327
- @id = id
328
- @tests = tests
329
- @env = environment
330
- @args = args
331
- @output = ''
332
- @out = Tempfile.new("prspec-t-#{@id}.out").path
333
- $log.debug("Thread#{@id} @out file: #{@out}")
334
- @err = Tempfile.new("prspec-t-#{@id}.err").path
335
- $log.debug("Thread#{@id} @err file: #{@err}")
336
- end
337
-
338
- def start
339
- filter_str = @tests.join(" -e ")
340
- filter_str = "-e " + filter_str
341
- exports = get_exports
342
- rspec_args = @args[:rspec_args].join(' ')
343
- cmd = "#{exports}rspec #{@args[:path]} #{filter_str} #{rspec_args}"
344
- $log.debug "Starting child process for thread#{@id}: #{cmd}"
345
- @thread = Thread.new do
346
- Thread.current[:id] = @id
347
- Dir::chdir @args[:dir] do # change directory for process execution
348
- begin
349
- pid = nil
350
- if (@args[:quiet_mode] || @args[:serialize])
351
- pid = Process.spawn(cmd, :out=>@out, :err=>@err) # capture both sdtout and stderr
352
- else
353
- pid = Process.spawn(cmd)
354
- end
355
- Process.wait(pid)
356
- @output = File.readlines(@out).join("\n") if (@args[:quiet_mode] || @args[:serialize])
357
- rescue
358
- if (@args[:quiet_mode] || @args[:serialize])
359
- error = "ErrorCode: #{$?.errorcode}; ErrorOutput: "+File.readlines(@err).join("\n")
360
- $log.error "Something bad happened while executing thread#{@id}: #{error}"
361
- end
362
- end
363
- close
364
- end
365
- end
366
- end
367
-
368
- def done?
369
- return !@thread.alive?
370
- rescue
371
- return true # if there's a problem with @thread we're done
372
- end
373
-
374
- def close
375
- @thread.sleep
376
- @thread.kill
377
- @thread = nil
378
- FileUtils.remove_file(@out, :force => true) unless !File.exist?(@out)
379
- FileUtils.remove_file(@err, :force => true) unless !File.exist?(@err)
380
- end
381
-
382
- def get_exports
383
- separator = PRSpec.is_windows? ? ' & ' : ';'
384
- exports = @env.map do |k,v|
385
- if PRSpec.is_windows?
386
- "(SET \"#{k}=#{v}\")"
387
- else
388
- "export #{k}=#{v}"
389
- end
390
- end.join(separator)
391
-
392
- return exports+separator
393
- end
1
+ require 'thread'
2
+ require 'optparse'
3
+ require 'log4r'
4
+ require 'parallel'
5
+ require 'yaml'
6
+ require 'tempfile'
7
+ require 'fileutils'
8
+ include Log4r
9
+
10
+ $log = Logger.new('prspec')
11
+ $log.outputters = Outputter.stdout
12
+ level = ENV['log_level'] || ''
13
+ case level.downcase
14
+ when 'all'
15
+ $log.level = ALL
16
+ when 'debug'
17
+ $log.level = DEBUG
18
+ when 'info'
19
+ $log.level = INFO
20
+ when 'warn'
21
+ $log.level = WARN
22
+ else
23
+ $log.level = ERROR
24
+ end
25
+
26
+ class PRSpec
27
+ attr_accessor :num_threads, :processes, :tests, :output
28
+ SPEC_FILE_FILTER = '_spec.rb'
29
+ INFO_FILE = ".prspec"
30
+
31
+ def initialize(args)
32
+ @output = ''
33
+ # create tracking file
34
+ yml = { :running_threads => 0 }
35
+ File.open(INFO_FILE, 'w') { |f| f.write yml.to_yaml }
36
+ if (!args.nil? && args.length > 0 && !args[0].nil?)
37
+ opts = parse_args(args)
38
+ if (!opts[:help])
39
+ @num_threads = opts[:thread_count]
40
+
41
+ @tests = get_spec_tests(opts)
42
+ if (tests.length > 0)
43
+ process_tests = divide_spec_tests(tests)
44
+ $log.debug "#{tests.length} Spec tests divided among #{@num_threads} arrays."
45
+ else
46
+ $log.warn "No spec tests found. Exiting."
47
+ exit 1
48
+ end
49
+
50
+ $log.info "Creating array of Child Processes..."
51
+ @processes = build_process_array(process_tests, opts)
52
+
53
+ begin_run(@processes, opts)
54
+ end
55
+ end
56
+ ensure
57
+ FileUtils.remove_file(INFO_FILE, :force => true) if File.exists?(INFO_FILE)
58
+ end
59
+
60
+ def parse_args(args)
61
+ $log.debug("Parsing arguments of: #{args}")
62
+ options = {
63
+ :dir=>'.',
64
+ :path=>'spec',
65
+ :thread_count=>get_number_of_processors,
66
+ :test_mode=>false,
67
+ :help=>false,
68
+ :excludes=>nil,
69
+ :rspec_args=>[],
70
+ :tag_name=>'',
71
+ :tag_value=>'',
72
+ :ignore_pending=>false,
73
+ :serialize=>false
74
+ }
75
+ o = OptionParser.new do |opts|
76
+ opts.banner = "Usage: prspec [options]"
77
+ opts.on("-p", "--path PATH", "Relative path from the base directory to search for spec files") do |v|
78
+ $log.debug "path specified... value: #{v}"
79
+ options[:path] = v
80
+ end
81
+ opts.on("-e", "--exclude REGEX", "Regex string used to exclude files") do |v|
82
+ $log.debug "excludes specified... value: #{v}"
83
+ options[:excludes] = v
84
+ end
85
+ opts.on("-d", "--dir DIRECTORY", "The base directory to run from") do |v|
86
+ $log.debug "directory specified... value: #{v}"
87
+ options[:dir] = v
88
+ end
89
+ opts.on("-n", "--num-threads THREADS", "The number of threads to use") do |v|
90
+ $log.debug "number of threads specified... value: #{v}"
91
+ options[:thread_count] = v.to_i
92
+ end
93
+ opts.on("-t", "--tag TAG", "A rspec tag value to filter by") do |v|
94
+ $log.debug "tag filter specified... value: #{v}"
95
+ tag = v
96
+ value = 'true'
97
+ if (v.include?(':')) # split to tag and value
98
+ tag_value = v.split(':')
99
+ tag = ":#{tag_value[0]}"
100
+ value = "#{tag_value[1]}"
101
+ end
102
+ options[:tag_name] = tag
103
+ options[:tag_value] = value
104
+ end
105
+ opts.on("-r", "--rspec-args \"RSPEC_ARGS\"", "Additional arguments to be passed to rspec (must be surrounded with double quotes)") do |v|
106
+ $log.debug "rspec arguments specified... value: #{v}"
107
+ options[:rspec_args] = v.gsub(/"/,'').split(' ') # create an array of each argument
108
+ end
109
+ opts.on("--test-mode", "Do everything except actually starting the test threads") do
110
+ $log.debug "test mode specified... threads will NOT be started."
111
+ options[:test_mode] = true
112
+ end
113
+ opts.on("-q", "--quiet", "Quiet mode. Do not display parallel thread output") do
114
+ $log.debug "quiet mode specified... thread output will not be displayed"
115
+ options[:quiet_mode] = true
116
+ end
117
+ opts.on("-h", "--help", "Display a help message") do
118
+ $log.debug "help message requested..."
119
+ options[:help] = true
120
+ puts opts
121
+ end
122
+ opts.on("--ignore-pending", "Ignore all pending tests") do
123
+ $log.debug "ignore pending specified... all pending tests will be excluded"
124
+ options[:ignore_pending] = true
125
+ end
126
+ opts.on("-s", "--serialize-output", "Wait for each thread to complete and then output to STDOUT serially") do
127
+ $log.debug "serialize output specified... all threads will output to STDOUT as they complete"
128
+ options[:serialize] = true
129
+ end
130
+ end
131
+
132
+ # handle invalid options
133
+ begin
134
+ o.parse! args
135
+ rescue OptionParser::InvalidOption => e
136
+ $log.error e
137
+ puts o
138
+ exit 1
139
+ end
140
+
141
+ return options
142
+ end
143
+
144
+ def get_number_of_processors
145
+ count = Parallel.processor_count
146
+ return count
147
+ end
148
+
149
+ def self.get_number_of_running_threads
150
+ prspec_info = YAML.load_file(INFO_FILE)
151
+ return prspec_info[:running_threads].to_i
152
+ end
153
+
154
+ def get_spec_tests(options)
155
+ files = get_spec_files(options)
156
+ tests = get_tests_from_files(files, options)
157
+ $log.debug "Found #{tests.length} tests in #{files.length} files"
158
+ return tests
159
+ end
160
+
161
+ def get_spec_files(options)
162
+ base_dir = options[:dir]
163
+ path = options[:path]
164
+ if (path.nil? || path == '')
165
+ path = '.'
166
+ end
167
+ full_path = ""
168
+ if (path.end_with?('.rb'))
169
+ full_path = File.join(base_dir, path.to_s)
170
+ else
171
+ full_path = File.join(base_dir, path.to_s, '**', "*#{SPEC_FILE_FILTER}")
172
+ end
173
+ $log.debug "full_path: #{full_path}"
174
+
175
+ files = []
176
+ if (options[:excludes].nil?)
177
+ files = Dir.glob(full_path)
178
+ else
179
+ files = Dir.glob(full_path).reject { |f| f[options[:excludes]] }
180
+ end
181
+
182
+ return files
183
+ end
184
+
185
+ def get_tests_from_files(files, options)
186
+ tests = []
187
+ match_test_name_format = /^[\s]*(it)[\s]*(')[\s\S]*(')[\s\S]*(do|{)/
188
+ files.each do |file|
189
+ lines = File.readlines(file)
190
+ for i in 0..lines.length-1
191
+ if lines[i] =~ match_test_name_format
192
+ m = lines[i]
193
+ match = true
194
+ if (options[:tag_name] != '')
195
+ if (m.rindex(options[:tag_name]).nil? || (m.rindex(options[:tag_value]) <= m.rindex(options[:tag_name])))
196
+ match = false
197
+ end
198
+ end
199
+ # if ignore_pending specified then skip tests containing 'pending' on next line
200
+ if (options[:ignore_pending])
201
+ if (i+1 < lines.length-1 && lines[i+1].include?('pending'))
202
+ match = false
203
+ end
204
+ end
205
+ if (match)
206
+ description = get_test_description(m)
207
+ tests.push("\"#{description}\"")
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ return tests
214
+ end
215
+
216
+ def get_test_description(description_line)
217
+ # get index of first "'"
218
+ trim_front = description_line.sub(/[\s]*(it)[\s]*(')/,'')
219
+ description = trim_front.sub(/(')[\s]*(,[\s\S]*)*(do|{)[\s\S]*/,'')
220
+ return description.gsub(/["]/,'\"').gsub(/\\'/,"'")
221
+ end
222
+
223
+ def divide_spec_tests(tests)
224
+ if (tests.length < @num_threads)
225
+ @num_threads = tests.length
226
+ $log.info "reducing number of threads due to low number of spec tests found: Threads = #{@num_threads}"
227
+ end
228
+ spec_arrays = Array.new(@num_threads)
229
+ num_per_thread = tests.length.fdiv(@num_threads).ceil
230
+ $log.debug "Approximate number of tests per thread: #{num_per_thread}"
231
+ # ensure an even distribution
232
+ i = 0
233
+ tests.each do |tname|
234
+ if (i >= @num_threads)
235
+ i = 0
236
+ end
237
+ if (spec_arrays[i].nil?)
238
+ spec_arrays[i] = []
239
+ end
240
+
241
+ spec_arrays[i].push(tname)
242
+
243
+ i+=1
244
+ end
245
+
246
+ return spec_arrays
247
+ end
248
+
249
+ def build_process_array(process_tests, opts)
250
+ processes = []
251
+ for i in 0..@num_threads-1
252
+ processes[i] = PRSpecThread.new(i, process_tests[i], {'TEST_ENV_NUMBER'=>i, 'HOME'=>nil}, opts)
253
+ end
254
+ return processes
255
+ end
256
+
257
+ def begin_run(processes, options)
258
+ if (!processes.nil? && processes.length > 0)
259
+ $log.info "Starting all Child Processes..."
260
+ update_running_thread_count(processes.length)
261
+ processes.each do |proc|
262
+ if (proc.is_a?(PRSpecThread) && options.is_a?(Hash))
263
+ proc.start unless options[:test_mode]
264
+ else
265
+ raise "Invalid datatype where PRSpecThread or Hash exepcted. Found: #{proc.class.to_s}, #{options.class.to_s}"
266
+ end
267
+ end
268
+ $log.info "All processes started..."
269
+ while processes.length > 0
270
+ processes.each do |proc|
271
+ if (!proc.done?) # confirm threads are running
272
+ $log.debug "Thread#{proc.id}: alive..."
273
+ else
274
+ $log.debug "Thread#{proc.id}: done."
275
+ if (options[:serialize])
276
+ puts proc.output unless options[:quiet_mode]
277
+ end
278
+ # collect thread output if in quiet mode
279
+ if (options[:quiet_mode])
280
+ @output << proc.output
281
+ end
282
+ processes.delete(proc) # remove from the array of processes so we don't count it again
283
+ update_running_thread_count(processes.length)
284
+ end
285
+ end
286
+ sleep 0.5 # wait half a second for processes to run and then re-check their status
287
+ end
288
+ $log.info "All processes complete."
289
+ else
290
+ raise "Invalid input passed to method: 'processes' must be a valid Array of PRSpecThread objects"
291
+ end
292
+ end
293
+
294
+ def update_running_thread_count(count)
295
+ (file = File.new(INFO_FILE,'w')).flock(File::LOCK_EX)
296
+ yml = { :running_threads => count }
297
+ file.write yml.to_yaml
298
+ ensure
299
+ file.flock(File::LOCK_UN)
300
+ file.close
301
+ end
302
+
303
+ def running?
304
+ @processes.each do |proc|
305
+ if (!proc.done?)
306
+ $log.debug "Found running process..."
307
+ return true
308
+ end
309
+ end
310
+ return false
311
+ end
312
+
313
+ def close
314
+ @processes.each do |proc|
315
+ proc.close
316
+ end
317
+ end
318
+
319
+ def self.is_windows?
320
+ return (RUBY_PLATFORM.match(/mingw/i)) ? true : false
321
+ end
322
+ end
323
+
324
+ class PRSpecThread
325
+ attr_accessor :thread, :id, :tests, :env, :args, :output
326
+ def initialize(id, tests, environment, args)
327
+ @id = id
328
+ @tests = tests
329
+ @env = environment
330
+ @args = args
331
+ @output = ''
332
+ @out = Tempfile.new("prspec-t-#{@id}.out").path
333
+ $log.debug("Thread#{@id} @out file: #{@out}")
334
+ @err = Tempfile.new("prspec-t-#{@id}.err").path
335
+ $log.debug("Thread#{@id} @err file: #{@err}")
336
+ end
337
+
338
+ def start
339
+ filter_str = @tests.join(" -e ")
340
+ filter_str = "-e " + filter_str
341
+ exports = get_exports
342
+ rspec_args = @args[:rspec_args].join(' ')
343
+ cmd = "#{exports}rspec #{@args[:path]} #{filter_str} #{rspec_args}"
344
+ $log.debug "Starting child process for thread#{@id}: #{cmd}"
345
+ @thread = Thread.new do
346
+ Thread.current[:id] = @id
347
+ Dir::chdir @args[:dir] do # change directory for process execution
348
+ begin
349
+ pid = nil
350
+ if (@args[:quiet_mode] || @args[:serialize])
351
+ pid = Process.spawn(cmd, :out=>@out, :err=>@err) # capture both sdtout and stderr
352
+ else
353
+ pid = Process.spawn(cmd)
354
+ end
355
+ Process.wait(pid)
356
+ @output = File.readlines(@out).join("\n") if (@args[:quiet_mode] || @args[:serialize])
357
+ rescue
358
+ if (@args[:quiet_mode] || @args[:serialize])
359
+ error = "ErrorCode: #{$?.errorcode}; ErrorOutput: "+File.readlines(@err).join("\n")
360
+ $log.error "Something bad happened while executing thread#{@id}: #{error}"
361
+ end
362
+ end
363
+ close
364
+ end
365
+ end
366
+ end
367
+
368
+ def done?
369
+ return !@thread.alive?
370
+ rescue
371
+ return true # if there's a problem with @thread we're done
372
+ end
373
+
374
+ def close
375
+ @thread.sleep
376
+ @thread.kill
377
+ @thread = nil
378
+ FileUtils.remove_file(@out, :force => true) unless !File.exist?(@out)
379
+ FileUtils.remove_file(@err, :force => true) unless !File.exist?(@err)
380
+ end
381
+
382
+ def get_exports
383
+ separator = PRSpec.is_windows? ? ' & ' : ';'
384
+ exports = @env.map do |k,v|
385
+ if PRSpec.is_windows?
386
+ "(SET \"#{k}=#{v}\")"
387
+ else
388
+ "export #{k}=#{v}"
389
+ end
390
+ end.join(separator)
391
+
392
+ return exports+separator
393
+ end
394
394
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-15 00:00:00.000000000 Z
12
+ date: 2014-06-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: log4r