qspec 0.1.3 → 1.0.0

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.
@@ -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