parallel_tests-fine_grain_test 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.hound.yml +8 -0
  4. data/.rubocop.default.yml +1114 -0
  5. data/.rubocop.disabled.yml +125 -0
  6. data/.rubocop.enabled.yml +1352 -0
  7. data/.rubocop.yml +66 -0
  8. data/.ruby-version +1 -0
  9. data/.travis.yml +5 -0
  10. data/Appraisals +14 -0
  11. data/Gemfile +8 -0
  12. data/README.md +51 -0
  13. data/Rakefile +10 -0
  14. data/bin/parallel_fine_grain_test +9 -0
  15. data/exe/parallel_tests-fine_grain_test +3 -0
  16. data/gemfiles/activesupport_3.gemfile +11 -0
  17. data/gemfiles/activesupport_3.gemfile.lock +44 -0
  18. data/gemfiles/activesupport_4.gemfile +10 -0
  19. data/gemfiles/activesupport_4.gemfile.lock +48 -0
  20. data/gemfiles/activesupport_5.gemfile +10 -0
  21. data/gemfiles/activesupport_5.gemfile.lock +47 -0
  22. data/lib/parallel_tests/fine_grain_test.rb +8 -0
  23. data/lib/parallel_tests/fine_grain_test/file_queue.rb +80 -0
  24. data/lib/parallel_tests/fine_grain_test/minitest.rb +4 -0
  25. data/lib/parallel_tests/fine_grain_test/minitest/extension.rb +27 -0
  26. data/lib/parallel_tests/fine_grain_test/minitest/suite.rb +35 -0
  27. data/lib/parallel_tests/fine_grain_test/railtie.rb +8 -0
  28. data/lib/parallel_tests/fine_grain_test/runner.rb +27 -0
  29. data/lib/parallel_tests/fine_grain_test/runtime_logger.rb +81 -0
  30. data/lib/parallel_tests/fine_grain_test/tasks.rb +21 -0
  31. data/lib/parallel_tests/fine_grain_test/test_case.rb +20 -0
  32. data/lib/parallel_tests/fine_grain_test/test_helper.rb +16 -0
  33. data/lib/parallel_tests/fine_grain_test/test_unit.rb +11 -0
  34. data/lib/parallel_tests/fine_grain_test/test_unit/collector.rb +54 -0
  35. data/lib/parallel_tests/fine_grain_test/test_unit/test_suite.rb +59 -0
  36. data/lib/parallel_tests/fine_grain_test/version.rb +5 -0
  37. data/parallel_tests-fine_grain_test.gemspec +23 -0
  38. metadata +122 -0
@@ -0,0 +1,4 @@
1
+ require 'minitest/autorun'
2
+ require_relative 'minitest/extension'
3
+
4
+ Minitest.singleton_class.prepend ::ParallelTests::FineGrainTest::Minitest::Extension
@@ -0,0 +1,27 @@
1
+ require_relative 'suite'
2
+
3
+ module ParallelTests
4
+ module FineGrainTest
5
+ module Minitest
6
+ module Runnable
7
+ def run_one_method(klass, method_name, _)
8
+ ::Minitest.fine_grain_suite.add(klass, method_name)
9
+ end
10
+ end
11
+
12
+ module Extension
13
+ attr_reader :fine_grain_suite
14
+
15
+ def __run(reporter, options)
16
+ ::Minitest::Runnable.singleton_class.prepend Runnable
17
+
18
+ @fine_grain_suite = Suite.new
19
+ super(reporter, options)
20
+ @fine_grain_suite.run(reporter, options)
21
+ ensure
22
+ @fine_grain_suite = nil
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ require 'parallel_tests/fine_grain_test/test_case'
2
+ require 'parallel_tests/fine_grain_test/file_queue'
3
+ require 'parallel_tests/fine_grain_test/runtime_logger'
4
+
5
+ module ParallelTests
6
+ module FineGrainTest
7
+ module Minitest
8
+ class Suite
9
+ def initialize(file_queue = FileQueue.new, runtime_logger = RuntimeLogger.new)
10
+ @test_cases = []
11
+ @file_queue = file_queue
12
+ @runtime_logger = runtime_logger
13
+ end
14
+
15
+ def add(klass, method_name)
16
+ @test_cases << TestCase.new(klass, method_name)
17
+ end
18
+
19
+ def run(reporter, _)
20
+ @file_queue.enq_all(@test_cases) do |tcs|
21
+ runtimes = @runtime_logger.runtimes
22
+ @runtime_logger.reset
23
+ tcs.sort_by { |test_case| runtimes[test_case] || 0 }.reverse
24
+ end
25
+
26
+ until (test_case = @file_queue.deq).nil?
27
+ @runtime_logger.log_runtime(test_case) do
28
+ reporter.record ::Minitest.run_one_method(test_case.suite, test_case.name)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ # rake tasks for Rails 3+
2
+ module ParallelTests
3
+ class Railtie < ::Rails::Railtie
4
+ rake_tasks do
5
+ require 'parallel_tests/fine_grain_test/tasks'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,27 @@
1
+ require 'parallel_tests/test/runner'
2
+ require 'parallel_tests/fine_grain_test/file_queue'
3
+
4
+ module ParallelTests
5
+ module FineGrainTest
6
+ class Runner < ParallelTests::Test::Runner
7
+ class << self
8
+ def run_tests(_, process_number, num_processes, options)
9
+ require_list = @@tests.map { |file| file.sub(" ", "\\ ") }.join(" ")
10
+ test_helper = File.expand_path("../test_helper", __FILE__)
11
+ cmd = "#{executable} -Itest -e '%w[#{require_list}].each { |f| " \
12
+ "require %{./\#{f}}; require %{#{test_helper}}; " \
13
+ "}' -- #{options[:test_options]}"
14
+ execute_command(cmd, process_number, num_processes, options)
15
+ end
16
+
17
+ def tests_in_groups(tests, num_groups, options={})
18
+ ParallelTests::FineGrainTest::FileQueue.new.reset
19
+
20
+ results = super
21
+ @@tests = results.flatten
22
+ results
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,81 @@
1
+ require 'fileutils'
2
+ require 'parallel_tests/fine_grain_test/test_case'
3
+
4
+ module ParallelTests
5
+ module FineGrainTest
6
+ class RuntimeLogger
7
+ class << self
8
+ def now
9
+ if Time.respond_to?(:now_without_mock_time) # Timecop
10
+ Time.now_without_mock_time
11
+ else
12
+ Time.now
13
+ end
14
+ end
15
+
16
+ def delta
17
+ before = now.to_f
18
+ yield
19
+ now.to_f - before
20
+ end
21
+ end
22
+
23
+ def initialize(file_name = ENV['FINE_GRAIN_TEST_RUNTIME_LOGGER'])
24
+ @file_name = file_name
25
+ end
26
+
27
+ def reset
28
+ lock do |f|
29
+ f.pos = 0
30
+ f.truncate(0)
31
+ end
32
+ end
33
+
34
+ def runtimes
35
+ times = {}
36
+ lock do |f|
37
+ lines = f.read.split(/\n/)
38
+ lines.each do |line|
39
+ time, test_case = line.split(/ /, 2)
40
+ next unless time and test_case
41
+
42
+ time = time.to_f
43
+ test_case = TestCase.decode(test_case)
44
+ times[test_case] = time
45
+ end
46
+ end
47
+ times
48
+ end
49
+
50
+ def log_runtime(test_case)
51
+ result = nil
52
+ time = self.class.delta { result = yield }
53
+ log(test_case, time)
54
+ result
55
+ end
56
+
57
+ private
58
+
59
+ def log(test_case, time)
60
+ lock do |f|
61
+ f.seek(0, :END)
62
+ f.puts [ time, TestCase.encode(test_case) ].join(' ')
63
+ end
64
+ end
65
+
66
+ def lock
67
+ return unless @file_name
68
+
69
+ FileUtils.mkdir_p(File.dirname(@file_name))
70
+ File.open(@file_name, File::RDWR|File::CREAT) do |f|
71
+ begin
72
+ f.flock File::LOCK_EX
73
+ yield(f)
74
+ ensure
75
+ f.flock File::LOCK_UN
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,21 @@
1
+ require 'rake'
2
+
3
+ namespace :parallel do
4
+ desc "run test in parallel (fine grain parallelism) with parallel:fine_grain_test[num_cpus]"
5
+ task :fine_grain_test, [:count, :pattern, :options] do |t, args|
6
+ require 'parallel_tests/tasks'
7
+ ParallelTests::Tasks.check_for_pending_migrations
8
+
9
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
10
+ require 'parallel_tests/fine_grain_test'
11
+
12
+ count, pattern, options = ParallelTests::Tasks.parse_args(args)
13
+ executable = File.join(File.dirname(__FILE__), '../../../bin/parallel_fine_grain_test')
14
+
15
+ command = "#{Shellwords.escape executable} test --type fine_grain_test " \
16
+ "-n #{count} --pattern '#{pattern}' --verbose " \
17
+ "--test-options '#{options}'"
18
+ abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
19
+ end
20
+ end
21
+
@@ -0,0 +1,20 @@
1
+
2
+ module ParallelTests
3
+ module FineGrainTest
4
+ class TestCase < Struct.new(:suite, :name)
5
+ def self.encode(test_case)
6
+ [ test_case.suite.name, test_case.name.gsub("\\", "\\\\\\").gsub("\n", "\\n") ].join(' ')
7
+ end
8
+
9
+ def self.decode(string)
10
+ suite, name = string.split(/ /, 2)
11
+ name = name.gsub("\\n", "\n").gsub("\\\\", "\\")
12
+ new(Object.module_eval("::#{suite}", __FILE__, __LINE__), name)
13
+ end
14
+
15
+ def ==(other)
16
+ other.suite == suite && other.name == name
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ msg = "FineGrainTest pid:#{$$} " \
2
+ "tid:#{Thread.current.object_id} " \
3
+ "test_env_number:#{ENV['TEST_ENV_NUMBER']}"
4
+
5
+ puts msg
6
+ Rails.logger.info msg if defined?(Rails)
7
+
8
+ if defined?(Minitest::Runnable) # Minitest 5
9
+ require 'parallel_tests/fine_grain_test/minitest'
10
+ elsif defined?(MiniTest::Unit) # Minitest 4
11
+ raise NotImplementedError.new("FineGrainTest does not currently support Minitest 4")
12
+ elsif defined?(Test::Unit)
13
+ require 'parallel_tests/fine_grain_test/test_unit'
14
+ else # WAT?
15
+ raise "Count not detect Minitest or TestUnit"
16
+ end
@@ -0,0 +1,11 @@
1
+ require 'test/unit/autorunner'
2
+
3
+ Test::Unit::AutoRunner.register_collector(:fine_grain) do |auto_runner|
4
+ require 'parallel_tests/fine_grain_test/test_unit/collector'
5
+
6
+ collector = ParallelTests::FineGrainTest::TestUnit::Collector.new
7
+ collector.collect(auto_runner)
8
+ end
9
+
10
+ # Tell auto runner to use our collector.
11
+ ARGV << "--collector=fine_grain"
@@ -0,0 +1,54 @@
1
+ require 'parallel_tests/fine_grain_test/test_case'
2
+ require 'parallel_tests/fine_grain_test/file_queue'
3
+ require 'parallel_tests/fine_grain_test/runtime_logger'
4
+ require 'parallel_tests/fine_grain_test/test_unit/test_suite'
5
+
6
+ module ParallelTests
7
+ module FineGrainTest
8
+ module TestUnit
9
+ class Collector
10
+ def collect(auto_runner)
11
+ runtime_logger = RuntimeLogger.new
12
+ test_suite = collect_test_suite(auto_runner)
13
+ file_queue = create_file_queue_from_test_suite(test_suite, runtime_logger)
14
+ create_test_suite_from_file_queue(file_queue, runtime_logger)
15
+ end
16
+
17
+ private
18
+
19
+ def collect_test_suite(auto_runner)
20
+ auto_runner.send(:default_collector)[auto_runner]
21
+ end
22
+
23
+ def create_file_queue_from_test_suite(test_suite, runtime_logger)
24
+ file_queue = FileQueue.new
25
+
26
+ test_cases = Set.new
27
+ test_suite_to_test_cases(test_suite, test_cases)
28
+
29
+ file_queue.enq_all(test_cases) do |tcs|
30
+ runtimes = runtime_logger.runtimes
31
+ runtime_logger.reset
32
+ tcs.sort_by { |test_case| runtimes[test_case] || 0 }.reverse
33
+ end
34
+
35
+ file_queue
36
+ end
37
+
38
+ def test_suite_to_test_cases(test_suite, test_cases)
39
+ if test_suite.is_a?(Test::Unit::TestSuite)
40
+ test_suite.tests.each do |test|
41
+ test_suite_to_test_cases(test, test_cases)
42
+ end
43
+ else
44
+ test_cases << TestCase.new(test_suite.class, test_suite.method_name)
45
+ end
46
+ end
47
+
48
+ def create_test_suite_from_file_queue(file_queue, runtime_logger)
49
+ TestSuite.new(file_queue, runtime_logger)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,59 @@
1
+ require 'test/unit/testsuite'
2
+
3
+ module ParallelTests
4
+ module FineGrainTest
5
+ module TestUnit
6
+ class TestSuite < Test::Unit::TestSuite
7
+
8
+ def initialize(file_queue = nil, runtime_logger = nil)
9
+ super("Fine Grain Test")
10
+ @file_queue = file_queue
11
+ @runtime_logger = runtime_logger
12
+
13
+ @tests = self
14
+ end
15
+
16
+ def <<(test)
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def delete(test)
21
+ raise NotImplementedError
22
+ end
23
+
24
+ def delete_tests(tests)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def size
29
+ @file_queue ? @file_queue.size : 0
30
+ end
31
+
32
+ def shift
33
+ test_case = @file_queue ? @file_queue.deq : nil
34
+ if test_case
35
+ test_suite = Test::Unit::TestSuite.new(test_case.suite.name)
36
+ test_suite << test_case.suite.new(test_case.name)
37
+ test_suite
38
+ end
39
+ end
40
+
41
+ protected
42
+
43
+ def run_test(test_suite, result)
44
+ if @runtime_logger
45
+ test_case = test_suite.tests.first
46
+ test_case = TestCase.new(test_case.class, test_case.method_name)
47
+
48
+ @runtime_logger.log_runtime(test_case) do
49
+ super(test_suite, result)
50
+ end
51
+ else
52
+ super(test_suite, result)
53
+ end
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ module ParallelTests
2
+ module FineGrainTest
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'parallel_tests/fine_grain_test/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "parallel_tests-fine_grain_test"
8
+ spec.version = ParallelTests::FineGrainTest::VERSION
9
+ spec.authors = ["Paul Kmiec"]
10
+ spec.email = ["paul.kmiec@appfolio.com"]
11
+
12
+ spec.summary = %q{Parallelizes tests dynamically at runtime based on test cases.}
13
+ spec.description = %q{Parallelizes tests dynamically at runtime based on test cases.}
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+ spec.add_dependency 'parallel_tests', '~> 2.4'
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.11"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parallel_tests-fine_grain_test
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Kmiec
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-06-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parallel_tests
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: Parallelizes tests dynamically at runtime based on test cases.
56
+ email:
57
+ - paul.kmiec@appfolio.com
58
+ executables:
59
+ - parallel_tests-fine_grain_test
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".hound.yml"
65
+ - ".rubocop.default.yml"
66
+ - ".rubocop.disabled.yml"
67
+ - ".rubocop.enabled.yml"
68
+ - ".rubocop.yml"
69
+ - ".ruby-version"
70
+ - ".travis.yml"
71
+ - Appraisals
72
+ - Gemfile
73
+ - README.md
74
+ - Rakefile
75
+ - bin/parallel_fine_grain_test
76
+ - exe/parallel_tests-fine_grain_test
77
+ - gemfiles/activesupport_3.gemfile
78
+ - gemfiles/activesupport_3.gemfile.lock
79
+ - gemfiles/activesupport_4.gemfile
80
+ - gemfiles/activesupport_4.gemfile.lock
81
+ - gemfiles/activesupport_5.gemfile
82
+ - gemfiles/activesupport_5.gemfile.lock
83
+ - lib/parallel_tests/fine_grain_test.rb
84
+ - lib/parallel_tests/fine_grain_test/file_queue.rb
85
+ - lib/parallel_tests/fine_grain_test/minitest.rb
86
+ - lib/parallel_tests/fine_grain_test/minitest/extension.rb
87
+ - lib/parallel_tests/fine_grain_test/minitest/suite.rb
88
+ - lib/parallel_tests/fine_grain_test/railtie.rb
89
+ - lib/parallel_tests/fine_grain_test/runner.rb
90
+ - lib/parallel_tests/fine_grain_test/runtime_logger.rb
91
+ - lib/parallel_tests/fine_grain_test/tasks.rb
92
+ - lib/parallel_tests/fine_grain_test/test_case.rb
93
+ - lib/parallel_tests/fine_grain_test/test_helper.rb
94
+ - lib/parallel_tests/fine_grain_test/test_unit.rb
95
+ - lib/parallel_tests/fine_grain_test/test_unit/collector.rb
96
+ - lib/parallel_tests/fine_grain_test/test_unit/test_suite.rb
97
+ - lib/parallel_tests/fine_grain_test/version.rb
98
+ - parallel_tests-fine_grain_test.gemspec
99
+ homepage:
100
+ licenses: []
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 2.4.8
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Parallelizes tests dynamically at runtime based on test cases.
122
+ test_files: []