parallel_tests-fine_grain_test 0.2.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.
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: []