qspec 0.1.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 60f88a4ea3ba943add6780bee19d0aeda39e56a9
4
+ data.tar.gz: 303ff003aaf98ad67bc9aba6206c0bbb3d24ef56
5
+ SHA512:
6
+ metadata.gz: 11d3fb8ff5803f5a06dc607a3f5443a02a554eeea6c8c4463b4753a4904bee1955533136076cdfb3ec27e756db1f09bba6af1bc957c886539118e407eeff51b9
7
+ data.tar.gz: 7f5de6de95d530444e49665ad410d52b879523c40ea75035dcdbb89aed257dff024af1bbeb67709b1234c27a22de3ebe4b43bf47f813747168525b6ddf72b271
@@ -1,8 +1,15 @@
1
1
  require 'rspec/core'
2
+
3
+ # workaround https://github.com/rspec/rspec-core/pull/1637
4
+ require 'rspec/core/notifications'
5
+ class RSpec::Core::Notifications::NullColorizer
6
+ def wrap(line, ignored)
7
+ line
8
+ end
9
+ end
10
+
2
11
  require 'qspec/version'
3
12
  require 'qspec/ipc'
4
- require 'qspec/manager'
5
- require 'qspec/spork_helper'
6
13
  require 'qspec/command_line'
7
14
  require 'qspec/formatters/redis_formatter'
8
15
  require 'qspec/config'
@@ -1,54 +1,154 @@
1
- require 'rspec/core/command_line'
2
-
1
+ require 'parallel'
3
2
  module Qspec
4
- class CommandLine < ::RSpec::Core::CommandLine
5
- include Manager # defines start_worker
6
- include SporkHelper
7
- attr_reader :output, :ipc, :config, :id
3
+ class CommandLine
4
+ TIME_LOG_NAME = 'elapsed_time'
5
+ DEFAULT_ELAPSED_TIME = 3.0
6
+ attr_reader :ipc, :config, :id
8
7
 
9
- def initialize(options)
8
+ def initialize(args)
10
9
  @config = Config.new()
11
10
  @ipc = IPC.from_config(@config)
12
- @id = ENV['qspec_id']
13
-
14
- super(options)
11
+ @id = rand(10000)
12
+ @stats = []
13
+ @rspec_options = RSpec::Core::ConfigurationOptions.new(args)
14
+ @rspec_configuration = RSpec.configuration
15
+ @rspec_options.configure(@rspec_configuration)
15
16
  end
16
17
 
17
18
  def run(err, out)
18
- @configuration.error_stream = err
19
- @output = @configuration.output_stream ||= out
20
- @options.configure(@configuration)
19
+ @rspec_configuration.error_stream = err
20
+ @rspec_configuration.output_stream = out if @rspec_configuration.output_stream == $stdout
21
+
22
+ out.puts "ID: #{id}"
23
+ register_files(id)
24
+ puts "Forking #{@config['workers']} workers"
21
25
 
22
- if @id
26
+ thread = start_progress_thread(id)
27
+ success = Parallel.map(1..@config['workers'], in_processes: @config['workers']) do |no|
28
+ ENV['TEST_ENV_NUMBER'] = no == 1 ? '' : no.to_s
23
29
  process
24
- else
25
- start_worker
30
+ end
31
+ thread.exit
32
+
33
+ pop_stat(id)
34
+
35
+ @rspec_configuration.output_stream.puts "Failures: " if ipc.llen("failure_#{id}") > 0
36
+
37
+ each_object("failure_#{id}") do |failure|
38
+ dump_failure(failure)
39
+ end
40
+
41
+ log_elapsed_times
42
+ dump_summary
43
+ exit(success ? 0 : 1)
44
+ ensure
45
+ if ipc
46
+ ipc.del("to_run_#{id}")
47
+ ipc.del("stat_#{id}")
48
+ ipc.del("failure_#{id}")
26
49
  end
27
50
  end
28
51
 
29
52
  def process
53
+ ipc = IPC.from_config(@config)
30
54
  success = true
31
55
  while f = ipc.lpop("to_run_#{id}")
32
- @configuration.add_formatter(Qspec::Formatters::RedisFormatterFactory.build(id, f))
56
+ @rspec_configuration.add_formatter(Qspec::Formatters::RedisFormatterFactory.build(id, f))
57
+
58
+ require File.expand_path(f)
59
+ world = RSpec.world
60
+ example_groups = world.ordered_example_groups
61
+
33
62
  begin
34
- load File.expand_path(f)
35
- @configuration.reporter.report(@world.example_count, @configuration.randomize? ? @configuration.seed : nil) do |reporter|
63
+ @rspec_configuration.reporter.report(world.example_count(example_groups)) do |reporter|
36
64
  begin
37
- GC.disable if @config['no_gc']
38
- @configuration.run_hook(:before, :suite)
39
- success &&= @world.example_groups.ordered.all? {|g| g.run(reporter)}
65
+ hook_context = RSpec::Core::SuiteHookContext.new
66
+ @rspec_configuration.hooks.run(:before, :suite, hook_context)
67
+ success = example_groups.map { |g| g.run(reporter) }.all? && success
40
68
  ensure
41
- @configuration.run_hook(:after, :suite)
42
- GC.enable if @config['no_gc']
43
- GC.start if @config['no_gc']
69
+ @rspec_configuration.hooks.run(:after, :suite, hook_context)
44
70
  end
45
71
  end
46
72
  ensure
47
- @world.example_groups.clear
48
- @configuration.reset # formatter, reporter
73
+ world.example_groups.clear
74
+ @rspec_configuration.reset # formatter, reporter
75
+ end
76
+ end
77
+ success
78
+ end
79
+
80
+ private
81
+ def start_progress_thread(id)
82
+ Thread.new do
83
+ loop do
84
+ pop_stat(id)
85
+ sleep 1
86
+ end
87
+ end
88
+ end
89
+
90
+ def pop_stat(id)
91
+ o = @rspec_configuration.output_stream
92
+ each_object("stat_#{id}") do |obj|
93
+ @stats << obj
94
+ o.print "!!! " if obj[3] > 0
95
+ o.puts obj.inspect
96
+ end
97
+ end
98
+
99
+ def each_object(key)
100
+ while data = ipc.lpop(key)
101
+ yield(Marshal.load(data))
102
+ end
103
+ end
104
+
105
+ def dump_summary
106
+ sum = @stats.each_with_object({ example: 0, failure: 0, pending: 0 }) do |stat, sum|
107
+ sum[:example] += stat[2]
108
+ sum[:failure] += stat[3]
109
+ sum[:pending] += stat[4]
110
+ end
111
+ @rspec_configuration.output_stream.puts "\n#{sum[:example]} examples, #{sum[:failure]} failures, #{sum[:pending]} pendings"
112
+ end
113
+
114
+ def dump_failure(failure)
115
+ @rspec_configuration.output_stream.puts ""
116
+ @rspec_configuration.output_stream.puts failure[:exception]
117
+ failure[:backtrace].each do |line|
118
+ @rspec_configuration.output_stream.puts "\t#{line}"
119
+ end
120
+ end
121
+
122
+ def log_elapsed_times
123
+ File.open(Qspec.path(TIME_LOG_NAME), 'w') do |f|
124
+ @stats.each do |stat|
125
+ f.puts "#{File.expand_path(stat[0])}:#{stat[1].to_f}"
49
126
  end
50
127
  end
51
- success ? 0 : @configuration.failure_exit_code
128
+ end
129
+
130
+ def register_files(id)
131
+ sorted_files_to_run.uniq.each do |f|
132
+ ipc.rpush "to_run_#{id}", f
133
+ end
134
+ end
135
+
136
+ def sorted_files_to_run
137
+ @sorted_files_to_run ||= if @config['sort_by'] == 'time' && File.exists?(Qspec.path(TIME_LOG_NAME))
138
+ sort_by_time(@rspec_configuration.files_to_run)
139
+ else
140
+ sort_by_size(@rspec_configuration.files_to_run)
141
+ end
142
+ end
143
+
144
+ def sort_by_time(files)
145
+ log = Hash[File.readlines(Qspec.path(TIME_LOG_NAME)).map { |line| line.strip.split(":") }]
146
+ files.sort_by { |file| log[File.expand_path(file)].to_f || DEFAULT_ELAPSED_TIME }.reverse
147
+ end
148
+
149
+ # large to small
150
+ def sort_by_size(files)
151
+ files.sort_by { |file| -File.stat(file).size }
52
152
  end
53
153
  end
54
154
  end
@@ -5,6 +5,7 @@ module Qspec
5
5
  module RedisFormatterFactory
6
6
  def self.build(id, file)
7
7
  Class.new(RSpec::Core::Formatters::BaseFormatter) do
8
+ RSpec::Core::Formatters.register self, :dump_summary, :dump_failures
8
9
  @@id = id
9
10
  @@file = file
10
11
  include RedisFormatterFactory
@@ -16,32 +17,21 @@ module Qspec
16
17
  super
17
18
  end
18
19
 
19
- def dump_failures
20
- failed_examples.each do |example|
21
- ex = example.execution_result[:exception]
20
+ def dump_failures(notification)
21
+ notification.failure_notifications.each do |failure|
22
22
  data = {
23
- description: example.full_description,
24
- exception: format_exception(example, ex),
25
- backtrace: format_backtrace(ex.backtrace, example)
23
+ description: failure.description,
24
+ exception: failure.message_lines.join("\n"),
25
+ backtrace: failure.formatted_backtrace
26
26
  }
27
27
  @ipc.rpush("failure_#{@@id}", Marshal.dump(data))
28
28
  end
29
29
  end
30
30
 
31
- def dump_summary(duration, example_count, failure_count, pending_count)
32
- data = [@@file, duration, example_count, failure_count, pending_count]
31
+ def dump_summary(summary)
32
+ data = [@@file, summary.duration, summary.examples.size, summary.failed_examples.count, summary.pending_examples.count]
33
33
  @ipc.rpush("stat_#{@@id}", Marshal.dump(data))
34
34
  end
35
-
36
- def format_exception(example, ex)
37
- exception_class_name = ex.class.to_s
38
- output = StringIO.new
39
- output.puts "* #{example.full_description}"
40
- output.puts "\tFailure/Error: #{read_failed_line(ex, example)}"
41
- output.puts "\t#{exception_class_name}:" unless exception_class_name =~ /RSpec/
42
- ex.message.to_s.split("\n").each { |line| output.puts "\t #{line}" } if ex.message
43
- output.string
44
- end
45
35
  end
46
36
  end
47
37
  end
@@ -1,9 +1,7 @@
1
1
  module Qspec
2
2
  class Helper
3
- include SporkHelper
4
-
5
3
  def initialize(argv)
6
- @argv = argv
4
+ @argv = argv
7
5
  end
8
6
 
9
7
  def serve
@@ -11,11 +9,6 @@ module Qspec
11
9
  when 'init'
12
10
  puts "Creating template"
13
11
  Config.create_template
14
- when 'spork'
15
- Qspec.create_tmp_directory_if_not_exist
16
- @config = Config.new
17
- puts "Start #{@config['workers']} sporks"
18
- start_spork_workers(@config['workers'])
19
12
  end
20
13
  end
21
14
  end
@@ -1,3 +1,3 @@
1
1
  module Qspec
2
- VERSION = "0.1.3"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -19,5 +19,6 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.post_install_message = "Run qspec-helper init to create your config file"
21
21
 
22
- gem.add_dependency 'rspec-core', '~>2.14.0'
22
+ gem.add_dependency 'rspec-core', '~>3.0'
23
+ gem.add_dependency 'parallel'
23
24
  end
metadata CHANGED
@@ -1,32 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
5
- prerelease:
4
+ version: 1.0.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - tomykaira
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-07-18 00:00:00.000000000 Z
11
+ date: 2014-07-16 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rspec-core
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
- version: 2.14.0
19
+ version: '3.0'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ~>
24
+ - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: 2.14.0
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: parallel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
30
41
  description: QSpec inserts spec files to a queue. Workers process that queue one
31
42
  by one.
32
43
  email:
@@ -37,7 +48,7 @@ executables:
37
48
  extensions: []
38
49
  extra_rdoc_files: []
39
50
  files:
40
- - .gitignore
51
+ - ".gitignore"
41
52
  - Gemfile
42
53
  - LICENSE.txt
43
54
  - README.md
@@ -52,39 +63,30 @@ files:
52
63
  - lib/qspec/ipc.rb
53
64
  - lib/qspec/ipc/file.rb
54
65
  - lib/qspec/ipc/redis.rb
55
- - lib/qspec/manager.rb
56
- - lib/qspec/spork_helper.rb
57
66
  - lib/qspec/version.rb
58
- - lib/spork/test_framework/qspec.rb
59
67
  - qspec.gemspec
60
68
  homepage: ''
61
69
  licenses: []
70
+ metadata: {}
62
71
  post_install_message: Run qspec-helper init to create your config file
63
72
  rdoc_options: []
64
73
  require_paths:
65
74
  - lib
66
75
  required_ruby_version: !ruby/object:Gem::Requirement
67
- none: false
68
76
  requirements:
69
- - - ! '>='
77
+ - - ">="
70
78
  - !ruby/object:Gem::Version
71
79
  version: '0'
72
- segments:
73
- - 0
74
- hash: 901026415
75
80
  required_rubygems_version: !ruby/object:Gem::Requirement
76
- none: false
77
81
  requirements:
78
- - - ! '>='
82
+ - - ">="
79
83
  - !ruby/object:Gem::Version
80
84
  version: '0'
81
- segments:
82
- - 0
83
- hash: 901026415
84
85
  requirements: []
85
86
  rubyforge_project:
86
- rubygems_version: 1.8.23
87
+ rubygems_version: 2.2.0
87
88
  signing_key:
88
- specification_version: 3
89
+ specification_version: 4
89
90
  summary: QSpec is extension of RSpec. Q is for queue, and quick.
90
91
  test_files: []
92
+ has_rdoc:
@@ -1,131 +0,0 @@
1
- require 'rspec/core/formatters/helpers'
2
-
3
- module Qspec
4
- module Manager
5
- TIME_LOG_NAME = 'elapsed_time'
6
- DEFAULT_ELAPSED_TIME = 3.0
7
- attr_reader :output
8
- attr_reader :id
9
-
10
- def start_worker
11
- @id = rand(10000)
12
- output.puts "ID: #{id}"
13
- register_files(id)
14
- if runnning_ports
15
- puts "Connecting to spork: #{runnning_ports.inspect}"
16
- runnning_ports.each do |port|
17
- fork do
18
- connect_spork(port, id, @configuration.error_stream, output)
19
- end
20
- end
21
- else
22
- puts "Forking #{@config['workers']} workers"
23
- command = "qspec #{@options.drb_argv.join " "}"
24
- @config['workers'].times do |i|
25
- env = {
26
- "qspec_id" => id.to_s,
27
- "TEST_ENV_NUMBER" => i == 0 ? '' : (i + 1).to_s }
28
- spawn(env,
29
- command,
30
- out: '/dev/null')
31
- end
32
- end
33
-
34
- @stats = []
35
- thread = start_progress_thread(id)
36
- success = Process.waitall.all? { |pid, status| status.exitstatus == 0 }
37
- thread.exit
38
-
39
- pop_stat(id)
40
-
41
- output.puts "Failures: " if ipc.llen("failure_#{id}") > 0
42
-
43
- each_object("failure_#{id}") do |failure|
44
- dump_failure(failure)
45
- end
46
-
47
- log_elapsed_times
48
- dump_summary
49
- exit(success ? 0 : 1)
50
- ensure
51
- if ipc
52
- ipc.del("to_run_#{id}")
53
- ipc.del("stat_#{id}")
54
- ipc.del("failure_#{id}")
55
- end
56
- end
57
-
58
- private
59
- def start_progress_thread(id)
60
- Thread.new do
61
- loop do
62
- pop_stat(id)
63
- sleep 1
64
- end
65
- end
66
- end
67
-
68
- def pop_stat(id)
69
- each_object("stat_#{id}") do |obj|
70
- @stats << obj
71
- output.print "!!! " if obj[3] > 0
72
- output.puts obj.inspect
73
- end
74
- end
75
-
76
- def each_object(key)
77
- while data = ipc.lpop(key)
78
- yield(Marshal.load(data))
79
- end
80
- end
81
-
82
- def dump_summary
83
- sum = @stats.each_with_object({ example: 0, failure: 0, pending: 0 }) do |stat, sum|
84
- sum[:example] += stat[2]
85
- sum[:failure] += stat[3]
86
- sum[:pending] += stat[4]
87
- end
88
- output.puts "\n#{sum[:example]} examples, #{sum[:failure]} failures, #{sum[:pending]} pendings"
89
- end
90
-
91
- def dump_failure(failure)
92
- puts ""
93
- puts failure[:exception]
94
- failure[:backtrace].each do |line|
95
- output.puts "\t#{line}"
96
- end
97
- end
98
-
99
- def log_elapsed_times
100
- File.open(Qspec.path(TIME_LOG_NAME), 'w') do |f|
101
- @stats.each do |stat|
102
- f.puts "#{File.expand_path(stat[0])}:#{stat[1].to_f}"
103
- end
104
- end
105
- end
106
-
107
- def register_files(id)
108
- sorted_files_to_run.uniq.each do |f|
109
- ipc.rpush "to_run_#{id}", f
110
- end
111
- end
112
-
113
- def sorted_files_to_run
114
- @sorted_files_to_run ||= if @config['sort_by'] == 'time' && File.exists?(Qspec.path(TIME_LOG_NAME))
115
- sort_by_time(@configuration.files_to_run)
116
- else
117
- sort_by_size(@configuration.files_to_run)
118
- end
119
- end
120
-
121
- def sort_by_time(files)
122
- log = Hash[File.readlines(Qspec.path(TIME_LOG_NAME)).map { |line| line.strip.split(":") }]
123
- files.sort_by { |file| log[File.expand_path(file)].to_f || DEFAULT_ELAPSED_TIME }.reverse
124
- end
125
-
126
- # large to small
127
- def sort_by_size(files)
128
- files.sort_by { |file| -File.stat(file).size }
129
- end
130
- end
131
- end
@@ -1,65 +0,0 @@
1
- # spork is optional
2
- begin
3
- require 'drb/drb'
4
- require 'spork/test_framework/qspec'
5
- rescue LoadError
6
- end
7
-
8
- require 'rspec/core/drb_options'
9
-
10
- module Qspec
11
- module SporkHelper
12
- def start_spork_workers(count)
13
- Signal.trap(:INT){
14
- puts "Stop spork processes"
15
- remove_port_file
16
- exit(0)
17
- }
18
-
19
- default_port = (@config['spork_port'] || ::Spork::TestFramework::Qspec::DEFAULT_PORT).to_i
20
- ports = []
21
- count.times do |i|
22
- port = default_port+i
23
- spawn({ "TEST_ENV_NUMBER" => i == 0 ? '' : (i + 1).to_s },
24
- "spork qspec --port #{port}")
25
- ports << port
26
- end
27
- create_port_file(ports)
28
- Process.waitall.all? { |pid, status| status.exitstatus == 0 } ? 0 : 1
29
- end
30
-
31
- def connect_spork(port, id, err, out)
32
- begin
33
- DRb.start_service("druby://localhost:0")
34
- rescue SocketError, Errno::EADDRNOTAVAIL
35
- DRb.start_service("druby://:0")
36
- end
37
- spec_server = DRbObject.new_with_uri("druby://127.0.0.1:#{port||PORT}")
38
- exit spec_server.run(@options.drb_argv + [id], err, out).to_i
39
- end
40
-
41
- def create_port_file(ports)
42
- File.open(port_file, 'w') do |f|
43
- f.puts ports.join("\n")
44
- end
45
- end
46
-
47
- def runnning_ports
48
- @runnning_ports ||= begin
49
- ports = File.readlines(port_file).map { |line| line.strip.to_i }
50
- ports.empty? ? nil : ports
51
- rescue Errno::ENOENT
52
- nil
53
- end
54
- end
55
-
56
- def remove_port_file
57
- File.unlink(port_file)
58
- end
59
-
60
- private
61
- def port_file
62
- @port_file ||= Qspec.path('spork_ports')
63
- end
64
- end
65
- end
@@ -1,11 +0,0 @@
1
- require 'spork'
2
- class Spork::TestFramework::Qspec < Spork::TestFramework
3
- DEFAULT_PORT = 9240
4
- HELPER_FILE = File.join(Dir.pwd, "spec/spec_helper.rb")
5
-
6
- def run_tests(argv, stderr, stdout)
7
- require 'qspec'
8
- ENV['qspec_id'] = argv.pop.to_s
9
- ::Qspec::CommandLine.new(argv).run(stderr, stdout)
10
- end
11
- end