prspec 0.2.5 → 0.2.6

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