speedyrspec 0.0.2
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.
- checksums.yaml +7 -0
- data/README.md +83 -0
- data/Rakefile +0 -0
- data/lib/speedyrspec.rb +4 -0
- data/lib/speedyrspec/config.rb +22 -0
- data/lib/speedyrspec/dependency_manager.rb +68 -0
- data/lib/speedyrspec/json_writers.rb +23 -0
- data/lib/speedyrspec/rake_tasks.rb +67 -0
- data/lib/speedyrspec/resolver.rb +13 -0
- data/lib/speedyrspec/rspec_config.rb +21 -0
- data/lib/speedyrspec/spies.rb +50 -0
- data/lib/speedyrspec/version.rb +3 -0
- data/spec/models/dependency_manager_spec.rb +28 -0
- data/spec/models/resolver_spec.rb +16 -0
- data/spec/spec_helper.rb +6 -0
- metadata +104 -0
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
|
data/lib/speedyrspec.rb
ADDED
@@ -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,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
|
data/spec/spec_helper.rb
ADDED
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:
|