rspec_parallel 0.1.1 → 0.1.2

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/rspec_parallel.rb +405 -92
  2. metadata +3 -3
@@ -3,30 +3,57 @@ $LOAD_PATH << File.dirname(__FILE__)
3
3
  require 'progressbar'
4
4
  require 'color_helper'
5
5
  require 'thread'
6
+ require 'rexml/document'
7
+ include REXML
6
8
  include ColorHelpers
7
9
 
8
10
  class Rspec_parallel
9
- def initialize(thread_number, case_folder, filter_options = {}, env_list = [], show_pending = false)
10
- @thread_number = thread_number
11
+
12
+ MAX_RERUN_TIMES = 10
13
+
14
+ def initialize(options = {})
15
+ @options = {:thread_number => 4, :case_folder => "./spec/", :report_folder => "./reports/",
16
+ :filter => {}, :env_list => [], :show_pending => false, :rerun => false,
17
+ :separate_rerun_report => true}.merge(options)
18
+ end
19
+
20
+ def run_tests()
21
+ start_time = Time.now # timer of rspec task
22
+ @queue = Queue.new # store all tests to run
23
+ @case_info_list = [] # store results of all tests
24
+ @lock = Mutex.new # use lock to avoid output mess up
25
+
26
+ thread_number = @options[:thread_number]
11
27
  if thread_number < 1
12
28
  puts red("threads_number can't be less than 1")
13
- return
29
+ exit(1)
14
30
  end
15
- @case_folder = case_folder
16
- @filter_options = filter_options
17
- @env_list = env_list
18
- @show_pending = show_pending
19
- @queue = Queue.new
20
- end
31
+ puts yellow("threads number: #{thread_number}\n")
21
32
 
22
- def run_tests()
23
- # use lock to avoid output mess up
24
- @lock = Mutex.new
25
- # timer of rspec task
26
- start_time = Time.now
33
+ rerun = @options[:rerun]
34
+ separate_rerun_report = @options[:separate_rerun_report]
35
+ if rerun && separate_rerun_report
36
+ @report_folder = get_rerun_folder(true)
37
+ if @report_folder.include? "rerun#{MAX_RERUN_TIMES + 1}"
38
+ puts yellow("rerun task has been executed for #{MAX_RERUN_TIMES}" +
39
+ " times, maybe you should start a new run")
40
+ exit(1)
41
+ end
42
+ else
43
+ @report_folder = @options[:report_folder]
44
+ end
45
+
46
+ filter = @options[:filter]
47
+ if rerun
48
+ get_failed_cases
49
+ else
50
+ parse_case_list(filter)
51
+ end
27
52
 
28
- puts yellow("threads number: #{@thread_number}\n")
29
- parse_case_list
53
+ if @queue.empty?
54
+ puts yellow("no cases to run, exit.")
55
+ return
56
+ end
30
57
 
31
58
  pbar = ProgressBar.new("0/#{@queue.size}", @queue.size, $stdout)
32
59
  pbar.format_arguments = [:title, :percentage, :bar, :stat]
@@ -39,34 +66,45 @@ class Rspec_parallel
39
66
  Thread.abort_on_exception = false
40
67
  threads = []
41
68
 
42
- @thread_number.times do |i|
69
+ thread_number.times do |i|
43
70
  threads << Thread.new do
44
71
  until @queue.empty?
45
72
  task = @queue.pop
46
73
  env_extras = {}
47
- if @env_list && @env_list[i]
48
- env_extras = @env_list[i]
74
+ env_list = @options[:env_list]
75
+ if env_list && env_list[i]
76
+ env_extras = env_list[i]
49
77
  end
78
+ t1 = Time.now
50
79
  task_output = run_task(task, env_extras)
80
+ t2 = Time.now
81
+ case_info = parse_case_log(task_output)
82
+ unless case_info
83
+ puts task_output
84
+ next
85
+ end
86
+ case_info['duration'] = t2 - t1
87
+ @case_info_list << case_info
51
88
 
52
- if task_output =~ /Failures/ || task_output =~ /0 examples/
89
+ if case_info['status'] == 'fail'
53
90
  @lock.synchronize do
54
91
  failure_number += 1
55
- failure_log = parse_failure_log(task_output)
56
- failure_list << [task, failure_log]
92
+ failure_list << case_info
57
93
 
58
94
  # print failure immediately during the execution
59
95
  $stdout.print "\e[K"
60
96
  if failure_number == 1
61
- puts "Failures:"
97
+ $stdout.print "Failures:\n\n"
62
98
  end
63
- puts " #{failure_number}) #{failure_log}\n"
64
- puts red(" (Failure time: #{Time.now})\n\n")
99
+ puts " #{failure_number}) #{case_info['test_name']}"
100
+ $stdout.print "#{red(case_info['error_message'])}"
101
+ $stdout.print "#{cyan(case_info['error_stack_trace'])}"
102
+ $stdout.print red(" (Failure time: #{Time.now})\n\n")
65
103
  end
66
- elsif task_output =~ /Pending/
104
+ elsif case_info['status'] == 'pending'
67
105
  @lock.synchronize do
68
106
  pending_number += 1
69
- pending_list << [task, parse_pending_log(task_output)]
107
+ pending_list << case_info
70
108
  end
71
109
  end
72
110
  case_number += 1
@@ -82,49 +120,44 @@ class Rspec_parallel
82
120
  pbar.finish
83
121
 
84
122
  # print pending cases if configured
85
- $stdout.print "\n"
86
- if @show_pending && pending_number > 0
123
+ show_pending = @options[:show_pending]
124
+ if show_pending && pending_number > 0
125
+ $stdout.print "\n"
87
126
  puts "Pending:"
88
- pending_list.each {|p|
89
- puts " #{p[1]}\n"
127
+ pending_list.each {|case_info|
128
+ puts " #{yellow(case_info['test_name'])}\n"
129
+ $stdout.print cyan("#{case_info['pending_info']}")
90
130
  }
91
131
  end
92
- $stdout.print "\n"
93
- t2 = Time.now
94
- puts green("Finished in #{format_time(t2-start_time)}\n")
132
+
133
+ # print total time and summary result
134
+ end_time = Time.now
135
+ puts "\nFinished in #{format_time(end_time-start_time)}\n"
95
136
  if failure_number > 0
96
137
  $stdout.print red("#{case_number} examples, #{failure_number} failures")
97
138
  $stdout.print red(", #{pending_number} pending") if pending_number > 0
139
+ elsif pending_number > 0
140
+ $stdout.print yellow("#{case_number} examples, #{failure_number} failures, #{pending_number} pending")
98
141
  else
99
- $stdout.print yellow("#{case_number} examples, #{failure_number} failures")
100
- $stdout.print yellow(", #{pending_number} pending") if pending_number > 0
142
+ $stdout.print green("#{case_number} examples, 0 failures")
101
143
  end
102
144
  $stdout.print "\n"
103
145
 
104
- # record failed rspec examples to rerun.sh
146
+ # print rerun command of failed examples
105
147
  unless failure_list.empty?
106
- rerun_file = File.new('./rerun.sh', 'w', 0777)
107
148
  $stdout.print "\nFailed examples:\n\n"
108
- failure_list.each_with_index do |log, i|
109
- case_desc = ''
110
- log[1].each_line {|line|
111
- case_desc = line
112
- break
113
- }
114
-
115
- rerun_cmd = 'rspec .' + log[0].match(/\/spec\/.*_spec\.rb:\d{1,4}/).to_s
116
- rerun_file.puts "echo ----#{case_desc}"
117
- rerun_file.puts rerun_cmd + " # #{case_desc}"
118
- $stdout.print red(rerun_cmd)
119
- $stdout.print cyan(" # #{case_desc}")
149
+ failure_list.each do |case_info|
150
+ $stdout.print red(case_info['rerun_cmd'].split(' # ')[0])
151
+ $stdout.print cyan(" # #{case_info['test_name']}\n")
120
152
  end
121
- rerun_file.close
122
- $stdout.print "\n"
123
153
  end
154
+
155
+ generate_reports(end_time - start_time, rerun && !separate_rerun_report)
124
156
  end
125
157
 
126
158
  def get_case_list
127
- file_list = `grep -rl '' #{@case_folder}`
159
+ case_folder = @options[:case_folder]
160
+ file_list = `grep -rl '' #{case_folder}`
128
161
  case_list = []
129
162
  file_list.each_line { |filename|
130
163
  unless filename.include? "_spec.rb"
@@ -209,14 +242,14 @@ class Rspec_parallel
209
242
  case_list
210
243
  end
211
244
 
212
- def parse_case_list()
245
+ def parse_case_list(filter)
213
246
  all_case_list = get_case_list
214
247
  pattern_filter_list = []
215
248
  tags_filter_list = []
216
249
 
217
- if @filter_options["pattern"]
250
+ if filter["pattern"]
218
251
  all_case_list.each { |c|
219
- if c["line"].match(@filter_options["pattern"])
252
+ if c["line"].match(filter["pattern"])
220
253
  pattern_filter_list << c
221
254
  end
222
255
  }
@@ -224,10 +257,10 @@ class Rspec_parallel
224
257
  pattern_filter_list = all_case_list
225
258
  end
226
259
 
227
- if @filter_options["tags"]
260
+ if filter["tags"]
228
261
  include_tags = []
229
262
  exclude_tags = []
230
- all_tags = @filter_options["tags"].split(",")
263
+ all_tags = filter["tags"].split(",")
231
264
  all_tags.each { |tag|
232
265
  if tag.start_with? "~"
233
266
  exclude_tags << tag.gsub("~", "")
@@ -245,15 +278,65 @@ class Rspec_parallel
245
278
  tags_filter_list = pattern_filter_list
246
279
  end
247
280
 
281
+ tags_filter_list = reorder_tests(tags_filter_list)
282
+
248
283
  tags_filter_list.each { |t|
249
284
  @queue << t["line"]
250
285
  }
251
286
  end
252
287
 
288
+ def get_rerun_folder(get_next=false)
289
+ rerun_folder = @options[:report_folder]
290
+ i = MAX_RERUN_TIMES
291
+ while(i > 0)
292
+ if File.exists? File.join(rerun_folder, "rerun#{i}")
293
+ if get_next
294
+ rerun_folder = File.join(rerun_folder, "rerun#{i + 1}")
295
+ else
296
+ rerun_folder = File.join(rerun_folder, "rerun#{i}")
297
+ end
298
+ break
299
+ end
300
+ i -= 1
301
+ end
302
+ if get_next && (rerun_folder.include? "rerun") == false
303
+ rerun_folder = File.join(rerun_folder, 'rerun1')
304
+ end
305
+ rerun_folder
306
+ end
307
+
308
+ def get_failed_cases
309
+ if @options[:separate_rerun_report]
310
+ last_report_folder = get_rerun_folder
311
+ last_report_file_path = File.join(last_report_folder, "junitResult.xml")
312
+ else
313
+ last_report_file_path = File.join(@report_folder, "junitResult.xml")
314
+ end
315
+ unless File.exists? last_report_file_path
316
+ puts yellow("can't find result of last run")
317
+ exit(1)
318
+ end
319
+ report_file = File.new(last_report_file_path)
320
+ begin
321
+ @doc = REXML::Document.new report_file
322
+ rescue
323
+ puts red("invalid format of report xml")
324
+ exit(1)
325
+ end
326
+
327
+ @doc.elements.each("result/suites/suite/cases/case") do |c|
328
+ if c.get_elements("errorDetails")[0]
329
+ rerun_cmd = c.get_elements("rerunCommand")[0].text
330
+ line = rerun_cmd.split('#')[0].gsub('rspec ', '').strip
331
+ @queue << line
332
+ end
333
+ end
334
+ end
335
+
253
336
  def run_task(task, env_extras)
254
337
  cmd = [] # Preparing command for popen
255
338
  cmd << ENV.to_hash.merge(env_extras)
256
- cmd += ["bundle", "exec", "rspec", "--color", task]
339
+ cmd += ["bundle", "exec", "rspec", "-f", "d", "--color", task]
257
340
  cmd
258
341
 
259
342
  output = ""
@@ -264,6 +347,10 @@ class Rspec_parallel
264
347
  output
265
348
  end
266
349
 
350
+ def reorder_tests(case_list)
351
+ return case_list
352
+ end
353
+
267
354
  def format_time(t)
268
355
  time_str = ''
269
356
  time_str += (t / 3600).to_i.to_s + " hours " if t > 3600
@@ -272,42 +359,268 @@ class Rspec_parallel
272
359
  time_str
273
360
  end
274
361
 
275
- def parse_failure_log(str)
276
- return str if str =~ /0 examples/
277
- index1 = str.index('1) ')
278
- index2 = str.index('Finished in')
279
- output = ""
280
- temp = str.slice(index1+3..index2-1).strip
281
- first_line = true
282
- temp.each_line { |line|
283
- if first_line
284
- output += line
285
- elsif line.strip.start_with? "# "
286
- output += cyan(line)
287
- else
288
- output += red(line)
362
+ def parse_case_log(str)
363
+ return nil if str =~ /0 examples/
364
+ result = {}
365
+ logs = []
366
+ str.each_line {|l| logs << l}
367
+ return nil if logs == []
368
+
369
+ stderr = ''
370
+ unless logs[0].start_with? 'Run options:'
371
+ clear_logs = []
372
+ logs_start = false
373
+ for i in 0..logs.length-1
374
+ if logs[i].strip.start_with? 'Run options:'
375
+ logs_start = true
376
+ end
377
+ if logs_start
378
+ clear_logs << logs[i]
379
+ else
380
+ stderr += logs[i]
381
+ end
289
382
  end
290
- first_line = false
291
- }
292
- output
383
+ logs = clear_logs
384
+ end
385
+ result['stderr'] = stderr
386
+
387
+ stdout = ''
388
+ if logs[4].strip != ''
389
+ clear_logs = []
390
+ stdout_start = true
391
+ for i in 0..logs.length-1
392
+ if i < 3
393
+ clear_logs << logs[i]
394
+ elsif stdout_start && logs[i+1].strip == ''
395
+ clear_logs << logs[i]
396
+ stdout_start = false
397
+ elsif !stdout_start
398
+ clear_logs << logs[i]
399
+ else
400
+ stdout += logs[i]
401
+ end
402
+ end
403
+ logs = clear_logs
404
+ end
405
+ result['stdout'] = stdout
406
+
407
+ result['class_name'] = logs[2].strip
408
+ result['test_desc'] = logs[3].gsub(/\((FAILED|PENDING).+\)/, '').strip
409
+ result['test_name'] = result['class_name'] + ' ' + result['test_desc']
410
+
411
+ if logs[-1].include? '1 pending'
412
+ result['status'] = 'pending'
413
+ pending_info = ''
414
+ for i in 7..logs.length-4
415
+ next if logs[i].strip == ''
416
+ pending_info += logs[i]
417
+ end
418
+ result['pending_info'] = pending_info
419
+ elsif logs[-1].include? '0 failures'
420
+ result['status'] = 'pass'
421
+ elsif logs[-1].start_with? 'rspec '
422
+ result['status'] = 'fail'
423
+ result['rerun_cmd'] = logs[-1]
424
+ error_message = logs[8]
425
+ error_stack_trace = ''
426
+ for i in 9..logs.length-8
427
+ next if logs[i].strip == ''
428
+ if logs[i].strip.start_with? '# '
429
+ error_stack_trace += logs[i]
430
+ else
431
+ error_message += logs[i]
432
+ end
433
+ end
434
+ error_message.each_line do |l|
435
+ next if l.include? 'Error:'
436
+ result['error_details'] = l.strip
437
+ break
438
+ end
439
+ if error_message.index(result['error_details']) < error_message.length - result['error_details'].length - 10
440
+ result['error_details'] += "..."
441
+ end
442
+ result['error_message'] = error_message
443
+ result['error_stack_trace'] = error_stack_trace
444
+ else
445
+ result['status'] = 'unknown'
446
+ end
447
+
448
+ result
293
449
  end
294
450
 
295
- def parse_pending_log(str)
296
- index1 = str.index('Pending:')
297
- index2 = str.index('Finished in')
298
- output = ""
299
- temp = str.slice(index1+8..index2-1).strip
300
- first_line = true
301
- temp.each_line { |line|
302
- if first_line
303
- output += yellow(line)
304
- elsif line.strip.start_with? "# "
305
- output += cyan(line)
306
- else
307
- output += line
451
+ def generate_reports(time, update_report)
452
+ %x[mkdir #{@report_folder}] unless File.exists? @report_folder
453
+ @summary_report = ""
454
+ @summary_report += "<?xml version='1.0' encoding='UTF-8'?>\n"
455
+ @summary_report += "<result>\n"
456
+ @summary_report += "<suites>\n"
457
+
458
+ class_name_list = []
459
+ @case_info_list.each do |case_info|
460
+ class_name_list << case_info['class_name']
461
+ end
462
+ class_name_list.uniq!
463
+ class_name_list.sort!
464
+ class_name_list.each do |class_name|
465
+ temp_case_info_list = []
466
+ @case_info_list.each do |case_info|
467
+ if case_info['class_name'] == class_name
468
+ temp_case_info_list << case_info
469
+ end
308
470
  end
309
- first_line = false
310
- }
311
- output
471
+ generate_single_file_report(temp_case_info_list)
472
+ end
473
+
474
+ if update_report
475
+ update_ci_report
476
+ end
477
+
478
+ @summary_report += "</suites>\n"
479
+ @summary_report += "<duration>#{time}</duration>\n"
480
+ @summary_report += "<keepLongStdio>false</keepLongStdio>\n"
481
+ @summary_report += "</result>\n"
482
+
483
+ report_file_path = File.join(@report_folder, 'junitResult.xml')
484
+ fr = File.new(report_file_path, 'w')
485
+ if update_report
486
+ fr.puts @doc
487
+ else
488
+ fr.puts @summary_report
489
+ end
490
+ fr.close
491
+ end
492
+
493
+ def update_ci_report
494
+ @doc.elements.each("result/suites/suite/cases/case") do |c1|
495
+ if c1.get_elements("errorDetails")[0]
496
+ test_name = c1.get_elements("testName")[0].text
497
+ @case_info_list.each do |c2|
498
+ if test_name == c2['test_name'].encode({:xml => :attr})
499
+ c1.get_elements("duration")[0].text = c2['duration']
500
+ if c2['status'] == 'fail'
501
+ text = c2['error_message'].gsub('Failure/Error: ', '') + "\n"
502
+ text += c2['error_stack_trace'].gsub('# ', '')
503
+ c1.get_elements("errorStackTrace")[0].text = text
504
+ c1.get_elements("errorDetails")[0].text = c2['error_details']
505
+ c1.get_elements("rerunCommand")[0].text = c2['rerun_cmd']
506
+ else
507
+ c1.delete c1.get_elements("errorDetails")[0]
508
+ c1.delete c1.get_elements("errorStackTrace")[0]
509
+ c1.delete c1.get_elements("rerunCommand")[0]
510
+ end
511
+ break
512
+ end
513
+ end
514
+ end
515
+ end
516
+ end
517
+
518
+ def generate_single_file_report(case_info_list)
519
+ return if case_info_list == []
520
+ class_name = case_info_list[0]['class_name']
521
+ file_name = File.join(@report_folder, class_name.gsub(/:+/, '-') + '.xml')
522
+ name = class_name.gsub(':', '_')
523
+
524
+ suite_duration = 0.0
525
+ fail_num = 0
526
+ error_num = 0
527
+ pending_num = 0
528
+ stdout = ''
529
+ stderr = ''
530
+ stdout_list = []
531
+ stderr_list = []
532
+ case_desc_list = []
533
+ case_info_list.each do |case_info|
534
+ suite_duration += case_info['duration']
535
+ stdout_list << case_info['stdout']
536
+ stderr_list << case_info['stderr']
537
+ case_desc_list << case_info['test_desc']
538
+ if case_info['status'] == 'fail'
539
+ if case_info['error_message'].include? "expect"
540
+ fail_num += 1
541
+ else
542
+ error_num += 1
543
+ end
544
+ elsif case_info['status'] == 'pending'
545
+ pending_num += 1
546
+ end
547
+ end
548
+ stdout_list.uniq!
549
+ stderr_list.uniq!
550
+ case_desc_list.sort!
551
+ stdout_list.each {|s| stdout += s}
552
+ stderr_list.each {|s| stderr += s}
553
+
554
+ @summary_report += "<suite>\n"
555
+ @summary_report += "<file>#{file_name}</file>\n"
556
+ @summary_report += "<name>#{name}</name>\n"
557
+ @summary_report += "<stdout>\n"
558
+ @summary_report += stdout.encode({:xml => :text}) if stdout.length > 0
559
+ @summary_report += "</stdout>\n"
560
+ @summary_report += "<stderr>\n"
561
+ @summary_report += stderr.encode({:xml => :text}) if stderr.length > 0
562
+ @summary_report += "</stderr>\n"
563
+ @summary_report += "<duration>#{suite_duration}</duration>\n"
564
+ @summary_report += "<cases>\n"
565
+
566
+ ff = File.new(file_name, 'w')
567
+ ff.puts '<?xml version="1.0" encoding="UTF-8"?>'
568
+ ff.puts "<testsuite name=\"#{class_name}\" tests=\"#{case_info_list.size}\" time=\"#{suite_duration}\" failures=\"#{fail_num}\" errors=\"#{error_num}\" skipped=\"#{pending_num}\">"
569
+
570
+ case_desc_list.each do |case_desc|
571
+ i = case_info_list.index {|c| c['test_desc'] == case_desc}
572
+ case_info = case_info_list[i]
573
+ test_name = case_info['test_name']
574
+ test_name += " (PENDING)" if case_info['status'] == 'pending'
575
+ test_name = test_name.encode({:xml => :attr})
576
+ @summary_report += "<case>\n"
577
+ @summary_report += "<duration>#{case_info['duration']}</duration>\n"
578
+ @summary_report += "<className>#{case_info['class_name']}</className>\n"
579
+ @summary_report += "<testName>#{test_name}</testName>\n"
580
+ @summary_report += "<skipped>#{case_info['status'] == 'pending'}</skipped>\n"
581
+
582
+ ff.puts "<testcase name=#{test_name} time=\"#{case_info['duration']}\">"
583
+ ff.puts "<skipped/>" if case_info['status'] == 'pending'
584
+
585
+ if case_info['status'] == 'fail'
586
+ @summary_report += "<errorStackTrace>\n"
587
+ @summary_report += case_info['error_message'].encode({:xml => :text}).gsub('Failure/Error: ', '')
588
+ @summary_report += case_info['error_stack_trace'].encode({:xml => :text}).gsub('# ', '')
589
+ @summary_report += "</errorStackTrace>\n"
590
+ @summary_report += "<errorDetails>\n"
591
+ @summary_report += case_info['error_details'].encode({:xml => :text})
592
+ @summary_report += "</errorDetails>\n"
593
+ @summary_report += "<rerunCommand>#{case_info['rerun_cmd'].encode({:xml => :text})}</rerunCommand>\n"
594
+
595
+ if case_info['error_message'].include? "expected"
596
+ type = "RSpec::Expectations::ExpectationNotMetError"
597
+ elsif case_info['error_message'].include? "RuntimeError"
598
+ type = "RuntimeError"
599
+ else
600
+ type = "UnknownError"
601
+ end
602
+ ff.puts "<failure type=\"#{type}\" message=#{case_info['error_details'].encode({:xml => :attr})}>"
603
+ ff.puts case_info['error_message'].encode({:xml => :text}).gsub('Failure/Error: ', '')
604
+ ff.puts case_info['error_stack_trace'].encode({:xml => :text}).gsub('# ', '')
605
+ ff.puts "</failure>"
606
+ ff.puts "<rerunCommand>#{case_info['rerun_cmd'].encode({:xml => :text})}</rerunCommand>"
607
+ end
608
+ @summary_report += "<failedSince>0</failedSince>\n"
609
+ @summary_report += "</case>\n"
610
+
611
+ ff.puts "</testcase>"
612
+ end
613
+
614
+ @summary_report += "</cases>\n"
615
+ @summary_report += "</suite>"
616
+
617
+ ff.puts "<system-out>"
618
+ ff.puts stdout.encode({:xml => :text}) if stdout.length > 0
619
+ ff.puts "</system-out>"
620
+ ff.puts "<system-err>"
621
+ ff.puts stderr.encode({:xml => :text}) if stderr.length > 0
622
+ ff.puts "</system-err>"
623
+ ff.puts "</testsuite>"
624
+ ff.close
312
625
  end
313
626
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec_parallel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
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: 2012-10-15 00:00:00.000000000 Z
12
+ date: 2012-11-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: progressbar
@@ -28,7 +28,7 @@ dependencies:
28
28
  - !ruby/object:Gem::Version
29
29
  version: 0.11.0
30
30
  description: parallel all rspec examples
31
- email: zhchshy@hotmail.com
31
+ email: zhangcheng@rbcon.com
32
32
  executables: []
33
33
  extensions: []
34
34
  extra_rdoc_files: []