speedyrspec 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ea0fb5d298348727492a61c9c521baf64aa37955
4
+ data.tar.gz: f1cc5b4d5f3e9b294f134177ec9d83b895fecdea
5
+ SHA512:
6
+ metadata.gz: e7e6829b791caf95e95f63c96966f0678c5e7cc8e267c591bb4b54b5c0816fe35da7db3011b48d6feee1aa9f4fb51e2aad9ecdee79c9b3c80c422a1535724fa1
7
+ data.tar.gz: c0d7f785ee02bfd3d62865b645a7b1797e3ed5fc0f63b8e5a4a10371e22841f9bdcdbad3418bb5fe7187d97fa00d2a69651cd85f6b306409e512189abb33d1c3
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # Speedy RSpec
2
+
3
+ Faster testing by running only the needed tests.
4
+
5
+ ## Description
6
+
7
+ Speedy RSpec computes which tests are relevant for certain code changes. It
8
+ does this by tracing all calls during a test run and saving for each test all
9
+ the files which are seen. At runtime it uses this file to compute which tests
10
+ offer coverage for a particular file.
11
+
12
+ ## Install
13
+
14
+ ```ruby
15
+ gem 'speedyrspec'
16
+ ```
17
+
18
+ ## Configuration
19
+
20
+ Set path for the trace_file. This is where SpeedyRspec stores its dependency
21
+ graph.
22
+
23
+ ```ruby
24
+ SpeedyRspec.configure do |config|
25
+ config.trace_file = 'speedy_traces.json'
26
+ end
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ In order to compute the smallest amount of tests needed to be run, the
32
+ following steps should be followed.
33
+
34
+ 1. Compute dependency graph. This stores in the trace_file the dependency list
35
+ of each file. This process is best repeated weekly.
36
+
37
+ ```bash
38
+ rake speedyrspec:collect
39
+ ```
40
+
41
+ 2. Run tests for the changed files.
42
+
43
+ 2.1 Tests for app files.
44
+
45
+ ```bash
46
+ rake speedyrspec:run <app/files>
47
+ ```
48
+
49
+ 2.2. Tests from git changes. Load list of changed files and compute which
50
+ tests to run.
51
+
52
+ ```bash
53
+ rake speedyrspec:run:git
54
+ ```
55
+
56
+ ## Rake tasks
57
+
58
+ SppedyRspec exposes the following rake tasks:
59
+
60
+ ```bash
61
+ rake speedyrspec:collect # run tests and collect trace information.
62
+ rake speedyrspec:run # run tests that exercise code in files given as parameters.
63
+ rake speedyrspec:run:git # run tests that exercise code in modiffied git files.
64
+ rake speedyrspec:show # show which files should be run for given tests.
65
+ rake speedyrspec:show:git # show which files should be run for modffied git files.
66
+ ```
67
+
68
+
69
+ ## Recommended setup
70
+
71
+ It is recommended to periodically collect traces. This can be done weekly in a
72
+ test run followed by a commit of the traces file into your repository. This is
73
+ possible with a configuration similar to the following:
74
+
75
+ ```ruby
76
+ SpeedyRspec.configure do |config|
77
+ config.trace_file = 'speedy_traces.json'
78
+ end
79
+ ```
80
+
81
+ Another option is to write the traces file in S3 bucket set-up for static
82
+ hosting. At trace-collection the AWS access credentials will have to be stored
83
+ in the environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
data/Rakefile ADDED
File without changes
@@ -0,0 +1,4 @@
1
+ require_relative 'speedyrspec/config'
2
+ require_relative 'speedyrspec/rake_tasks'
3
+ require_relative 'speedyrspec/resolver'
4
+ require_relative 'speedyrspec/dependency_manager'
@@ -0,0 +1,22 @@
1
+ module SpeedyRspec
2
+ class << self
3
+ attr_accessor :trace_file
4
+ def output=(val)
5
+ @output = val
6
+ end
7
+
8
+ def output
9
+ return @output if @output
10
+ return {type: :file, name: self.trace_file}
11
+ end
12
+
13
+ def configure
14
+ yield self
15
+ end
16
+ end
17
+ end
18
+
19
+ SpeedyRspec.configure do |config|
20
+ config.trace_file ||= 'speedy_traces.json'
21
+ # config.output = {type: :s3, name: 'shore_core_traces.json', bucket: 'speedyrspec'}.merge(config.output || {})
22
+ end
@@ -0,0 +1,68 @@
1
+ require 'json'
2
+ require 'open-uri'
3
+ require_relative 'json_writers'
4
+
5
+ module SpeedyRspec
6
+ class DependencyManagerFactory
7
+ class << self
8
+ def new_dependencies
9
+ build_manager(build_traces_writer).tap { |manager| manager.new_dependencies}
10
+ end
11
+
12
+ def load_dependencies
13
+ build_manager(nil).tap { |manager| manager.load_dependencies}
14
+ end
15
+
16
+ private
17
+
18
+ def build_traces_writer
19
+ case SpeedyRspec.output[:type]
20
+ when :file
21
+ JsonFileWriter.new
22
+ when :s3
23
+ JsonS3Writer.new
24
+ else
25
+ JsonFileWriter.new
26
+ end
27
+ end
28
+
29
+ def build_manager(file_writer)
30
+ JsonDependencyManager.new(file_writer)
31
+ end
32
+ end
33
+ end
34
+
35
+ class JsonDependencyManager
36
+ def initialize(data_writer)
37
+ @writer = data_writer
38
+ end
39
+
40
+ def new_dependencies
41
+ @data = Hash.new{|h, k| h[k] = Set.new}
42
+ end
43
+
44
+ def load_dependencies
45
+ open(SpeedyRspec.trace_file) do |f|
46
+ @data = JSON.parse(f.read)
47
+ end
48
+ end
49
+
50
+ def add_dependency(from, to)
51
+ @data[from].add(to)
52
+ end
53
+
54
+ def get_dependencies(from)
55
+ Array(@data[from]) || []
56
+ end
57
+
58
+ def finish
59
+ @writer.write(to_json)
60
+ end
61
+
62
+ private
63
+
64
+ def to_json
65
+ JSON.pretty_generate(@data.map{|k,v| [k, Array(v)]}.to_h)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,23 @@
1
+ require 'aws-sdk-resources'
2
+
3
+ class JsonFileWriter
4
+ def write(json)
5
+ File.write(SpeedyRspec.output[:name], json)
6
+ end
7
+ end
8
+
9
+ class JsonS3Writer
10
+ def initialize
11
+ fail 'Please setup AWS login environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY' \
12
+ unless ENV.key?('AWS_SECRET_ACCESS_KEY') && ENV.key?('AWS_SECRET_ACCESS_KEY')
13
+
14
+ @s3 = Aws::S3::Resource.new(region: 'eu-west-1')
15
+ end
16
+
17
+ def write(json)
18
+ bucket = @s3.bucket(SpeedyRspec.output[:bucket] || 'speedyrspec')
19
+ object = bucket.object(SpeedyRspec.output[:name] || SpeedyRspec.trace_file)
20
+ object.put(body: json)
21
+ end
22
+ end
23
+
@@ -0,0 +1,67 @@
1
+ require 'rake/tasklib'
2
+ require 'rspec/core/rake_task'
3
+
4
+ def git_modified_files
5
+ status = open('|git status -s').readlines
6
+ status.map(&:strip).flat_map{|s| s.split.drop(1)}.tap do |files|
7
+ raise 'No file was modiffied in your git repository.' if files.empty?
8
+ end
9
+ end
10
+
11
+ def set_files_to_run(t, files_to_test)
12
+ t.pattern = 'deliberately-left-blank'
13
+ t.rspec_opts = SpeedyRspec::Resolver.new.get_tests(files_to_test)
14
+ end
15
+
16
+ def files_from_args(args)
17
+ (args[:files] || ARGV.drop(1) || []).tap do |files_to_test|
18
+ raise 'Please specify which file needs to be tested' if files_to_test.empty?
19
+ end
20
+ end
21
+
22
+ def show_files_to_run(files_to_test)
23
+ files = SpeedyRspec::Resolver.new.get_tests(files_to_test)
24
+ puts "Files to run: \n\t#{files.join("\n\t")}"
25
+ end
26
+
27
+ Rake::Task.define_task(:environment) do
28
+ ENV['RACK_ENV'] = 'test'
29
+ ENV['RAILS_ENV'] = 'test'
30
+ end
31
+
32
+ desc 'run tests and collect trace information.'
33
+ RSpec::Core::RakeTask.new('speedyrspec:collect' => :environment) do |t, args|
34
+ t.rspec_opts ||= []
35
+ specfiles = ARGV.drop(1)
36
+
37
+ if specfiles && !specfiles.empty?
38
+ t.rspec_opts << specfiles.join(' ')
39
+ t.pattern = 'deliberately-left-blank'
40
+ end
41
+
42
+ t.rspec_opts << '--require speedyrspec/rspec_config'
43
+ end
44
+
45
+ desc 'run tests that exercise code in files given as parameters.'
46
+ RSpec::Core::RakeTask.new('speedyrspec:run' => :environment) do |t, args|
47
+ files_to_test = files_from_args(args)
48
+ set_files_to_run(t, files_to_test)
49
+ end
50
+
51
+ desc 'run tests that exercise code in modiffied git files.'
52
+ RSpec::Core::RakeTask.new('speedyrspec:run:git' => :environment) do |t, args|
53
+ files_to_test = git_modified_files
54
+ set_files_to_run(t, files_to_test)
55
+ end
56
+
57
+ desc 'show which files should be run for given tests.'
58
+ task 'speedyrspec:show' => :environment do |t, args|
59
+ files_to_test = files_from_args(args)
60
+ show_files_to_run(files_to_test)
61
+ end
62
+
63
+ desc 'show which files should be run for modffied git files.'
64
+ task 'speedyrspec:show:git' => :environment do |t, args|
65
+ files_to_test = git_modified_files
66
+ show_files_to_run(files_to_test)
67
+ end
@@ -0,0 +1,13 @@
1
+ module SpeedyRspec
2
+ class Resolver
3
+ def initialize
4
+ @dependencies = DependencyManagerFactory.load_dependencies
5
+ end
6
+
7
+ def get_tests(files_to_test)
8
+ files_to_test.flat_map do |f|
9
+ @dependencies.get_dependencies(f)
10
+ end.compact.uniq
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ require 'speedyrspec'
2
+ require_relative 'spies'
3
+ require 'rspec'
4
+
5
+ RSpec.configure do |config|
6
+ config.before(:suite) do
7
+ SpeedyRspec.spy.start
8
+ end
9
+
10
+ config.after(:suite) do
11
+ SpeedyRspec.spy.finish
12
+ end
13
+
14
+ config.before(:each) do |example|
15
+ SpeedyRspec.spy.test_starts(example)
16
+ end
17
+
18
+ config.after(:each) do |example|
19
+ SpeedyRspec.spy.test_ends(example)
20
+ end
21
+ end
@@ -0,0 +1,50 @@
1
+ module SpeedyRspec
2
+ def self.spy
3
+ @spy ||= TracingSpy.new
4
+ end
5
+
6
+ class TracingSpy
7
+ def initialize
8
+ @datamanager = DependencyManagerFactory.new_dependencies
9
+ end
10
+
11
+ def start
12
+ puts 'Starting test suite with tracing enabled.'
13
+ @tracer = set_tracer
14
+ end
15
+
16
+ def finish
17
+ @datamanager.finish
18
+ end
19
+
20
+ def test_starts(example)
21
+ deduce_workingdir(example) unless @working_dir
22
+ @current_test = example.file_path
23
+ @tracer.enable
24
+ end
25
+
26
+ def test_ends(example)
27
+ @tracer.disable
28
+ end
29
+
30
+ private
31
+
32
+ def deduce_workingdir(example)
33
+ fp = example.metadata[:file_path]
34
+ path = example.metadata[:absolute_file_path]
35
+ @working_dir = path[0, path.index(fp[1, fp.length])]
36
+ @wd_path = Pathname.new(@working_dir)
37
+ end
38
+
39
+ def set_tracer
40
+ TracePoint.new(*%i[call b_call]) do |tp|
41
+ unless tp.path.index(@working_dir).nil?
42
+ tp_path = Pathname.new(tp.path)
43
+ rel_path = tp_path.relative_path_from(@wd_path).to_s
44
+ @datamanager.add_dependency(rel_path, @current_test) unless @current_test.index(rel_path)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,3 @@
1
+ module SpeedyRspec
2
+ VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SpeedyRspec::JsonDependencyManager do
4
+ describe 'new traces' do
5
+ let(:manager) { SpeedyRspec::DependencyManagerFactory.new_dependencies }
6
+
7
+ it 'has no dependencies' do
8
+ expect(manager.get_dependencies('foo')).to be_empty
9
+ end
10
+
11
+ it 'can add dependencies' do
12
+ manager.add_dependency('foo', 'bar')
13
+ expect(manager.get_dependencies('foo')).to eq(['bar'])
14
+ end
15
+ end
16
+
17
+ describe 'existing traces' do
18
+ before do
19
+ SpeedyRspec.trace_file = 'spec/models/test.json'
20
+ end
21
+ let(:manager) { SpeedyRspec::DependencyManagerFactory.load_dependencies }
22
+
23
+ it 'has correct dependencies' do
24
+ expect(manager.get_dependencies('foo')).to eq ['bar']
25
+ expect(manager.get_dependencies('bar')).to be_empty
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'pry-byebug'
3
+
4
+ RSpec.describe SpeedyRspec::Resolver do
5
+ before do
6
+ SpeedyRspec.trace_file = 'spec/models/test.json'
7
+ allow(File).to receive(:absolute_path) { |arg| arg }
8
+ end
9
+
10
+ let(:resolver) { SpeedyRspec::Resolver.new }
11
+
12
+ it 'returns tests for file' do
13
+ expect(resolver.get_tests(['foo'])).to eq(['bar'])
14
+ expect(resolver.get_tests(['foo', 'baz'])).to eq(['bar'])
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ require 'speedyrspec/config'
2
+ require 'speedyrspec/resolver'
3
+ require 'speedyrspec/dependency_manager'
4
+
5
+ RSpec.configure do |config|
6
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: speedyrspec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Cristian Eigel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-resources
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.3'
55
+ description: Compute the dependency list in a certain project and use this information
56
+ for computing the minimum list of tests to run for a change.
57
+ email: eigelc@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - README.md
63
+ - Rakefile
64
+ - lib/speedyrspec.rb
65
+ - lib/speedyrspec/config.rb
66
+ - lib/speedyrspec/dependency_manager.rb
67
+ - lib/speedyrspec/json_writers.rb
68
+ - lib/speedyrspec/rake_tasks.rb
69
+ - lib/speedyrspec/resolver.rb
70
+ - lib/speedyrspec/rspec_config.rb
71
+ - lib/speedyrspec/spies.rb
72
+ - lib/speedyrspec/version.rb
73
+ - spec/models/dependency_manager_spec.rb
74
+ - spec/models/resolver_spec.rb
75
+ - spec/spec_helper.rb
76
+ homepage: https://github.com/ceigel/speedyrspec
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.5.1
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Compute the minimum set of tests exercising certain files
100
+ test_files:
101
+ - spec/models/dependency_manager_spec.rb
102
+ - spec/models/resolver_spec.rb
103
+ - spec/spec_helper.rb
104
+ has_rdoc: