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 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: