crystalball 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.rubocop.yml +4 -0
- data/.travis.yml +1 -1
- data/CHANGELOG.md +18 -0
- data/LICENSE +22 -674
- data/README.md +13 -158
- data/crystalball.gemspec +6 -2
- data/docs/img/favicon.ico +0 -0
- data/docs/img/logo.png +0 -0
- data/docs/index.md +44 -0
- data/docs/map_generators.md +149 -0
- data/docs/predictors.md +75 -0
- data/docs/runner.md +24 -0
- data/lib/crystalball.rb +8 -3
- data/lib/crystalball/active_record.rb +4 -0
- data/lib/crystalball/example_group_map.rb +19 -0
- data/lib/crystalball/execution_map.rb +17 -16
- data/lib/crystalball/extensions/git.rb +4 -0
- data/lib/crystalball/extensions/git/base.rb +14 -0
- data/lib/crystalball/extensions/git/lib.rb +18 -0
- data/lib/crystalball/factory_bot.rb +3 -0
- data/lib/crystalball/git_repo.rb +2 -7
- data/lib/crystalball/logging.rb +51 -0
- data/lib/crystalball/map_generator.rb +5 -7
- data/lib/crystalball/map_generator/allocated_objects_strategy.rb +6 -5
- data/lib/crystalball/map_generator/allocated_objects_strategy/object_tracker.rb +1 -0
- data/lib/crystalball/map_generator/base_strategy.rb +5 -4
- data/lib/crystalball/map_generator/configuration.rb +1 -1
- data/lib/crystalball/map_generator/coverage_strategy.rb +6 -5
- data/lib/crystalball/map_generator/described_class_strategy.rb +5 -5
- data/lib/crystalball/map_generator/factory_bot_strategy.rb +59 -0
- data/lib/crystalball/map_generator/factory_bot_strategy/dsl_patch.rb +40 -0
- data/lib/crystalball/map_generator/factory_bot_strategy/dsl_patch/factory_path_fetcher.rb +30 -0
- data/lib/crystalball/map_generator/factory_bot_strategy/factory_gem_loader.rb +27 -0
- data/lib/crystalball/map_generator/factory_bot_strategy/factory_runner_patch.rb +25 -0
- data/lib/crystalball/map_generator/parser_strategy.rb +60 -0
- data/lib/crystalball/map_generator/parser_strategy/processor.rb +129 -0
- data/lib/crystalball/map_generator/strategies_collection.rb +9 -9
- data/lib/crystalball/map_storage/yaml_storage.rb +7 -6
- data/lib/crystalball/prediction.rb +12 -11
- data/lib/crystalball/predictor.rb +7 -5
- data/lib/crystalball/predictor/associated_specs.rb +9 -4
- data/lib/crystalball/predictor/helpers/affected_example_groups_detector.rb +20 -0
- data/lib/crystalball/predictor/helpers/path_formatter.rb +18 -0
- data/lib/crystalball/predictor/modified_execution_paths.rb +11 -5
- data/lib/crystalball/predictor/modified_specs.rb +8 -2
- data/lib/crystalball/predictor/modified_support_specs.rb +39 -0
- data/lib/crystalball/predictor/strategy.rb +16 -0
- data/lib/crystalball/predictor_evaluator.rb +1 -1
- data/lib/crystalball/rails.rb +1 -0
- data/lib/crystalball/rails/helpers/base_schema_parser.rb +51 -0
- data/lib/crystalball/rails/helpers/schema_definition_parser.rb +36 -0
- data/lib/crystalball/rails/helpers/schema_definition_parser/active_record.rb +21 -0
- data/lib/crystalball/rails/helpers/schema_definition_parser/table_content_parser.rb +27 -0
- data/lib/crystalball/rails/map_generator/action_view_strategy.rb +5 -5
- data/lib/crystalball/rails/map_generator/action_view_strategy/patch.rb +1 -1
- data/lib/crystalball/rails/map_generator/i18n_strategy.rb +5 -5
- data/lib/crystalball/rails/map_generator/i18n_strategy/simple_patch.rb +1 -0
- data/lib/crystalball/rails/predictor/modified_schema.rb +81 -0
- data/lib/crystalball/rails/tables_map.rb +53 -0
- data/lib/crystalball/rails/tables_map_generator.rb +84 -0
- data/lib/crystalball/rails/tables_map_generator/configuration.rb +39 -0
- data/lib/crystalball/rspec/filtering.rb +52 -0
- data/lib/crystalball/rspec/prediction_builder.rb +21 -11
- data/lib/crystalball/rspec/prediction_pruning.rb +56 -0
- data/lib/crystalball/rspec/prediction_pruning/examples_pruner.rb +70 -0
- data/lib/crystalball/rspec/runner.rb +39 -27
- data/lib/crystalball/rspec/runner/configuration.rb +24 -14
- data/lib/crystalball/rspec/standard_prediction_builder.rb +17 -0
- data/lib/crystalball/source_diff.rb +12 -2
- data/lib/crystalball/source_diff/file_diff.rb +1 -1
- data/lib/crystalball/source_diff/formatting_checker.rb +50 -0
- data/lib/crystalball/version.rb +1 -1
- data/mkdocs.yml +23 -0
- metadata +102 -7
- data/lib/crystalball/case_map.rb +0 -19
- data/lib/crystalball/simple_predictor.rb +0 -18
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Crystalball
|
2
2
|
|
3
|
-
Crystalball is a Ruby library which implements [Regression Test Selection mechanism](https://tenderlovemaking.com/2015/02/13/predicting-test-failues.html) originally published by Aaron Patterson.
|
3
|
+
Crystalball is a Ruby library which implements [Regression Test Selection mechanism](https://tenderlovemaking.com/2015/02/13/predicting-test-failues.html) originally published by Aaron Patterson.
|
4
|
+
Its main purpose is to select a minimal subset of your test suite which should be run to ensure your changes didn't break anything.
|
4
5
|
|
5
6
|
[![Build Status](https://travis-ci.org/toptal/crystalball.svg?branch=master)](https://travis-ci.org/toptal/crystalball)
|
6
7
|
[![Maintainability](https://api.codeclimate.com/v1/badges/c8bfc25a43a1a2ecf964/maintainability)](https://codeclimate.com/github/toptal/crystalball/maintainability)
|
@@ -26,171 +27,25 @@ Or install it yourself as:
|
|
26
27
|
|
27
28
|
## Usage
|
28
29
|
|
29
|
-
|
30
|
-
```ruby
|
31
|
-
Crystalball::MapGenerator.start! do |config|
|
32
|
-
config.register Crystalball::MapGenerator::CoverageStrategy.new
|
33
|
-
end
|
34
|
-
```
|
35
|
-
1. Run your test suite on clean branch with green build. This step will generate file `execution_map.yml` in your project root
|
36
|
-
1. Make some changes to your app code
|
37
|
-
1. Run `bundle exec crystalball` to build a prediction and run RSpec with it. Check out [RSpec runner section](#rspec-runner) for customization details.
|
30
|
+
Please see our [official documentation](https://toptal.github.io/crystalball/).
|
38
31
|
|
39
|
-
|
40
|
-
so you need to regenerate execution maps regularly.
|
32
|
+
### Versioning
|
41
33
|
|
42
|
-
|
43
|
-
|
44
|
-
There are different map generator strategies that can (and should) be used together for better predictions. Each one has its own benefits and drawbacks, so they should be configured to best fit your needs.
|
45
|
-
|
46
|
-
### CoverageStrategy
|
47
|
-
|
48
|
-
Uses coverage information to detect which files are covered by the given spec (i.e. the files that, if changed, may potentially break the spec);
|
49
|
-
To customize the way the execution detection works, pass an object that responds to #detect and returns the paths to the strategy initialization:
|
50
|
-
|
51
|
-
```ruby
|
52
|
-
# ...
|
53
|
-
config.register Crystalball::MapGenerator::CoverageStrategy.new(MyDetector)
|
54
|
-
```
|
55
|
-
|
56
|
-
By default, the execution detector is a `Crystalball::MapGenerator::CoverageStrategy::ExecutionDetector`, which filters out the paths outside the root and converts absolute paths to relative.
|
57
|
-
|
58
|
-
### AllocatedObjectsStrategy
|
59
|
-
|
60
|
-
Looks for the files in which the objects allocated during the spec execution are defined. It is considerably slower than `CoverageStrategy`.
|
61
|
-
To use this strategy, use the convenient method `.build` which takes two optional keyword arguments: `only`, used to define the classes or modules to have their descendants tracked (defaults to `[]`); and `root`, which is the path where the detection will take place (defaults to `Dir.pwd`).
|
62
|
-
Here's an example that tracks allocation of `ActiveRecord::Base` objects:
|
63
|
-
|
64
|
-
```ruby
|
65
|
-
# ...
|
66
|
-
config.register Crystalball::MapGenerator::AllocatedObjectsStrategy.build(only: ['ActiveRecord::Base'])
|
67
|
-
```
|
68
|
-
|
69
|
-
That method is fine for most uses, but if you need to further customize the behavior of the strategy, you can directly instantiate the class.
|
70
|
-
|
71
|
-
```ruby
|
72
|
-
# ...
|
73
|
-
config.register Crystalball::MapGenerator::AllocatedObjectsStrategy
|
74
|
-
.new(execution_detector: MyCustomDetector, object_tracker: MyCustomTracker)
|
75
|
-
```
|
76
|
-
|
77
|
-
The initialization takes two keyword arguments: `execution_detector` and `object_tracker`.
|
78
|
-
`execution_detector` must be an object that responds to `#detect` receiving a list of objects and returning the paths affected by said objects. `object_tracker` is something that responds to `#used_classes_during` which yields to the caller and returns the array of classes of objects allocated during the execution of the block.
|
79
|
-
|
80
|
-
### DescribedClassStrategy
|
81
|
-
|
82
|
-
This strategy will take each example that has a `described_class` (i.e. examples inside `describe` blocks of classes and not strings) and add the paths where the described class and its ancestors are defined to the case map of the example;
|
83
|
-
|
84
|
-
To use it, add to your `Crystalball::MapGenerator.start!` block:
|
85
|
-
|
86
|
-
```ruby
|
87
|
-
# ...
|
88
|
-
config.register Crystalball::MapGenerator::DescribedClassStrategy.new
|
89
|
-
```
|
90
|
-
|
91
|
-
As with `AllocatedObjectsStrategy`, you can pass a custom execution detector (an object that responds to `#detect` and returns the paths) to the initialization:
|
92
|
-
|
93
|
-
```ruby
|
94
|
-
# ...
|
95
|
-
config.register Crystalball::MapGenerator::DescribedClassStrategy.new(MyDetector)
|
96
|
-
```
|
97
|
-
|
98
|
-
### Rails specific strategies
|
99
|
-
|
100
|
-
To use Rails specific strategies you must first `require 'crystalball/rails'`.
|
101
|
-
|
102
|
-
#### ActionViewStrategy
|
103
|
-
|
104
|
-
This strategy patches `ActionView::Template#compile!` to map the examples to affected views. Use it as follows:
|
105
|
-
|
106
|
-
```ruby
|
107
|
-
# ...
|
108
|
-
config.register Crystalball::MapGenerator::ActionViewStrategy.new
|
109
|
-
```
|
110
|
-
|
111
|
-
#### I18nStrategy
|
112
|
-
|
113
|
-
Patches I18n to have access to the path where the locales are defined, so that those paths can be added to the case map.
|
114
|
-
To use it, add to your config:
|
115
|
-
|
116
|
-
```ruby
|
117
|
-
# ...
|
118
|
-
config.register Crystalball::MapGenerator::I18nStrategy.new
|
119
|
-
```
|
120
|
-
|
121
|
-
### Custom strategies
|
122
|
-
|
123
|
-
You can create your own strategy and use it with the map generator. Any object that responds to `#call(case_map, example)` (where `case_map` is a `Crystalball::CaseMap` and `example` a `RSpec::Core::Example`) and augmenting its list of affected files using `case_map.push(*paths_to_files)`.
|
124
|
-
Check out the [implementation](https://github.com/toptal/crystalball/tree/master/lib/crystalball/map_generator) of the default strategies for details.
|
125
|
-
|
126
|
-
Keep in mind that all the strategies configured for the map generator will run for each example of your test suite, so it may slow down the generation process considerably.
|
127
|
-
|
128
|
-
## Predictor
|
129
|
-
|
130
|
-
The predictor can also be customized with different strategies:
|
131
|
-
|
132
|
-
### AssociatedSpecs
|
133
|
-
|
134
|
-
Needs to be configured with rules for detecting which specs should be on the prediction.
|
135
|
-
`predictor.use Crystalball::Predictor::AssociatedSpecs.new(from: %r{models/(.*).rb}, to: "./spec/models/%s_spec.rb")`
|
136
|
-
will add `./spec/models/foo_spec.rb` to prediction when `models/foo.rb` changes.
|
137
|
-
This strategy does not depend on a previoulsy generated case map.
|
138
|
-
|
139
|
-
### ModifiedExecutionPaths
|
140
|
-
|
141
|
-
Checks the case map and the diff to see which specs are affected by the new or modified files.
|
142
|
-
|
143
|
-
### ModifiedSpecs
|
144
|
-
|
145
|
-
As the name implies, checks for modified specs. The scope can be modified by passing a regex as argument, which defaults to `%r{spec/.*_spec\.rb\z}`.
|
146
|
-
This strategy does not depend on a previously generated case map.
|
147
|
-
|
148
|
-
### Custom strategies
|
149
|
-
|
150
|
-
As with the map generator you may define custom strategies for prediction. It must be an object that responds to `#call(diff, case_map)` (where `diff` is a `Crystalball::SourceDiff` and `case_map` is a `Crystalball::CaseMap`) and returns an array of paths.
|
151
|
-
|
152
|
-
Check out [default strategies implementation](https://github.com/toptal/crystalball/tree/master/lib/crystalball/predictor) for details.
|
153
|
-
|
154
|
-
## Under the hood
|
155
|
-
|
156
|
-
TODO: Write good description for anyone who wants to customize behavior
|
157
|
-
|
158
|
-
## Spring integration
|
159
|
-
|
160
|
-
It's very easy to integrate Crystalball with [Spring](https://github.com/rails/spring). Check out [spring-commands-crystalball](https://github.com/pluff/spring-commands-crystalball) for details.
|
161
|
-
|
162
|
-
## Plans
|
163
|
-
|
164
|
-
1. RSpec parallel integration
|
165
|
-
1. Map size optimization
|
166
|
-
1. Different strategies for execution map
|
167
|
-
1. Different strategies for failure predictor
|
168
|
-
1. Integration for git hook
|
169
|
-
|
170
|
-
## RSpec Runner
|
171
|
-
|
172
|
-
There is a custom RSpec runner you can use in your development with `bundle exec crystalball` command. It builds a prediction and runs it.
|
173
|
-
|
174
|
-
### Runner Configuration
|
175
|
-
|
176
|
-
#### Config file
|
177
|
-
|
178
|
-
Create a YAML file for the runner. Default locations are `./crystalball.yml` and `./config/crystalball.yml`. You can override config path with `CRYSTALBALL_CONFIG` env variable.
|
179
|
-
Please check an [example of a config file](https://github.com/toptal/crystalball/blob/master/spec/fixtures/crystalball.yml) for available options
|
180
|
-
|
181
|
-
#### Environment variables
|
182
|
-
|
183
|
-
`CRYSTALBALL_CONFIG=path/to/crystalball.yml` if you want to override default path to config file.
|
184
|
-
`CRYSTALBALL_SKIP_MAP_CHECK=true` if you want to skip maps expiration period check.
|
185
|
-
`CRYSTALBALL_SKIP_EXAMPLES_LIMIT=true` if you want to skip examples limit check.
|
34
|
+
We use [semantic versioning](https://semver.org/) for our [releases](https://github.com/toptal/crystalball/releases).
|
186
35
|
|
187
36
|
## Development
|
188
37
|
|
189
38
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
190
39
|
|
191
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
40
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
192
41
|
|
193
42
|
## Contributing
|
194
43
|
|
195
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/toptal/crystalball.
|
44
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/toptal/crystalball.
|
45
|
+
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
46
|
+
|
47
|
+
## License
|
48
|
+
|
49
|
+
Crystalball is released under the [MIT License](https://opensource.org/licenses/MIT).
|
50
|
+
|
196
51
|
|
data/crystalball.gemspec
CHANGED
@@ -7,7 +7,7 @@ require 'crystalball/version'
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
8
|
spec.name = "crystalball"
|
9
9
|
spec.version = Crystalball::VERSION
|
10
|
-
spec.authors = ["Pavel Shutsin"]
|
10
|
+
spec.authors = ["Pavel Shutsin", "Evgenii Pecherkin", "Jaimerson Araujo"]
|
11
11
|
spec.email = ["publicshady@gmail.com"]
|
12
12
|
|
13
13
|
spec.summary = 'A library for RSpec regression test selection'
|
@@ -35,14 +35,18 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.required_ruby_version = '> 2.3.0'
|
36
36
|
|
37
37
|
spec.add_development_dependency 'actionview'
|
38
|
+
spec.add_development_dependency 'activerecord'
|
38
39
|
spec.add_development_dependency "bundler", "~> 1.14"
|
40
|
+
spec.add_development_dependency 'factory_bot'
|
39
41
|
spec.add_development_dependency 'i18n'
|
42
|
+
spec.add_development_dependency 'parser'
|
40
43
|
spec.add_development_dependency 'pry'
|
41
44
|
spec.add_development_dependency 'pry-byebug'
|
42
45
|
spec.add_development_dependency "rake", "~> 10.0"
|
43
46
|
spec.add_development_dependency "rspec", "~> 3.0"
|
44
|
-
spec.add_development_dependency 'rubocop'
|
47
|
+
spec.add_development_dependency 'rubocop', ">= 0.56"
|
45
48
|
spec.add_development_dependency 'rubocop-rspec'
|
46
49
|
spec.add_development_dependency 'simplecov'
|
50
|
+
spec.add_development_dependency 'sqlite3'
|
47
51
|
spec.add_development_dependency 'yard'
|
48
52
|
end
|
Binary file
|
data/docs/img/logo.png
ADDED
Binary file
|
data/docs/index.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Crystalball
|
2
|
+
|
3
|
+
Crystalball is a Ruby library which implements [Regression Test Selection mechanism](https://tenderlovemaking.com/2015/02/13/predicting-test-failues.html) originally published by Aaron Patterson.
|
4
|
+
Its main purpose is to select a minimal subset of your test suite which should be run to ensure your changes didn't break anything.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Please check our [installation instructions](https://github.com/toptal/crystalball#installation).
|
9
|
+
|
10
|
+
## Basic Usage
|
11
|
+
|
12
|
+
1. Start MapGenerator in your `spec_helper` before you loaded any file of your app. E.g.
|
13
|
+
|
14
|
+
if ENV['CRYSTALBALL'] == 'true' do
|
15
|
+
Crystalball::MapGenerator.start! do |config|
|
16
|
+
config.register Crystalball::MapGenerator::CoverageStrategy.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
1. Run your test suite with Crystaball enabled on clean master branch with green build. `CRYSTALBALL=true bundle exec rspec .` This step will generate file `tmp/crystalball_data.yml` in your project root. This file contains useful profiling data for Crystalball.
|
21
|
+
1. Make some changes to your app code
|
22
|
+
1. Run `bundle exec crystalball` to build a prediction and run RSpec with it. Check out [RSpec runner section](runner.md) for customization details.
|
23
|
+
|
24
|
+
Keep in mind that as your target branch (usually master) code changes your execution maps will become outdated,
|
25
|
+
so you need to regenerate execution maps regularly.
|
26
|
+
|
27
|
+
## Advanced Usage
|
28
|
+
|
29
|
+
Crystalball workflow can be divided into 2 parts.
|
30
|
+
1. Full build profiling where Crystalball gathers some data about your RSpec suite for later use in predictions. This is where map generators do their job.
|
31
|
+
2. Actual predicting where Crystalball uses profiling info from step above and tries to get best prediction possible. This is where predictors do their job.
|
32
|
+
|
33
|
+
Both of these steps can be heavily customized and enchanted based on your project specifics and your needs.
|
34
|
+
|
35
|
+
You might want to check:
|
36
|
+
|
37
|
+
* [map generators docs](map_generators.md) for details related to suite profiling.
|
38
|
+
* [predictors docs](predictors.md) for details related to actual prediction.
|
39
|
+
* [runner docs](runner.md) for runner configuration details.
|
40
|
+
|
41
|
+
|
42
|
+
## Spring integration
|
43
|
+
|
44
|
+
It's very easy to integrate Crystalball with [Spring](https://github.com/rails/spring). Check out [spring-commands-crystalball](https://github.com/pluff/spring-commands-crystalball) for details.
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# Map generators
|
2
|
+
|
3
|
+
## Execution Map Generator
|
4
|
+
|
5
|
+
There are different map generator strategies that can (and should) be used together for better predictions. Each one has its own benefits and drawbacks, so they should be configured to best fit your needs.
|
6
|
+
|
7
|
+
### Custom map file name
|
8
|
+
|
9
|
+
You can customize resulting map filename with `map_storage_path` value. E.g.
|
10
|
+
```ruby
|
11
|
+
Crystalball::MapGenerator.start! do |config|
|
12
|
+
#...
|
13
|
+
config.map_storage_path = "execution_map_#{ENV['TEST_ENV_NUMBER'].to_i}.yml"
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
### CoverageStrategy
|
18
|
+
|
19
|
+
Uses coverage information to detect which files are covered by the given spec (i.e. the files that, if changed, may potentially break the spec);
|
20
|
+
To customize the way the execution detection works, pass an object that responds to #detect and returns the paths to the strategy initialization:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
Crystalball::MapGenerator.start! do |config|
|
24
|
+
#...
|
25
|
+
config.register Crystalball::MapGenerator::CoverageStrategy.new(my_detector)
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
By default, the execution detector is a `Crystalball::MapGenerator::CoverageStrategy::ExecutionDetector`, which filters out the paths outside of the project root and converts absolute paths to relative.
|
30
|
+
|
31
|
+
### AllocatedObjectsStrategy
|
32
|
+
|
33
|
+
Looks for the files in which the objects allocated during the spec execution are defined. It is considerably slower than `CoverageStrategy`.
|
34
|
+
To use this strategy, use the convenient method `.build` which takes two optional keyword arguments: `only`, used to define the classes or modules to have their descendants tracked (defaults to `[]`); and `root`, which is the path where the detection will take place (defaults to `Dir.pwd`).
|
35
|
+
Here's an example that tracks allocation of `ActiveRecord::Base` objects:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
Crystalball::MapGenerator.start! do |config|
|
39
|
+
#...
|
40
|
+
config.register Crystalball::MapGenerator::AllocatedObjectsStrategy.build(only: ['ActiveRecord::Base'])
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
That method is fine for most uses, but if you need to further customize the behavior of the strategy, you can directly instantiate the class.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
Crystalball::MapGenerator.start! do |config|
|
48
|
+
#...
|
49
|
+
config.register Crystalball::MapGenerator::AllocatedObjectsStrategy
|
50
|
+
.new(execution_detector: my_detector, object_tracker: my_tracker)
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
The initialization takes two keyword arguments: `execution_detector` and `object_tracker`.
|
55
|
+
`execution_detector` must be an object that responds to `#detect` receiving a list of objects and returning the paths affected by said objects. `object_tracker` is something that responds to `#used_classes_during` which yields to the caller and returns the array of classes of objects allocated during the execution of the block.
|
56
|
+
|
57
|
+
### DescribedClassStrategy
|
58
|
+
|
59
|
+
This strategy will take each example that has a `described_class` (i.e. examples inside `describe` blocks of classes and not strings) and add the paths where the described class and its ancestors are defined to the example group map of the example;
|
60
|
+
|
61
|
+
To use it, add to your `Crystalball::MapGenerator.start!` block:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
Crystalball::MapGenerator.start! do |config|
|
65
|
+
#...
|
66
|
+
config.register Crystalball::MapGenerator::DescribedClassStrategy.new
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
As with `AllocatedObjectsStrategy`, you can pass a custom execution detector (an object that responds to `#detect` and returns the paths) to the initialization:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
Crystalball::MapGenerator.start! do |config|
|
74
|
+
#...
|
75
|
+
config.register Crystalball::MapGenerator::DescribedClassStrategy.new(my_detector)
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
### ParserStrategy
|
80
|
+
|
81
|
+
The `ParserStrategy`, as the name suggests parses the files in order to detect which files are affected by an example.
|
82
|
+
It works by first parsing all (`.rb`) files that match the given pattern under the configured root directory (defaults to current directory) to collect the constants definition paths.
|
83
|
+
Then, when each example is executed, the used files of the current example group map are parsed to check for method calls to those constants. For that reason, `ParserStrategy` **only works when used with other strategies and is placed at the end of the strategies list**.
|
84
|
+
|
85
|
+
To use it, add the `parser` gem to your `Gemfile` and:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
require 'crystalball/map_generator/parser_strategy'
|
89
|
+
Crystalball::MapGenerator.start! do |config|
|
90
|
+
#...
|
91
|
+
config.register Crystalball::MapGenerator::ParserStrategy.new(pattern: /\A(app)|(lib)/)
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
### ActionViewStrategy
|
96
|
+
|
97
|
+
To use Rails specific strategies you must first `require 'crystalball/rails'`.
|
98
|
+
This strategy patches `ActionView::Template#compile!` to map the examples to affected views. Use it as follows:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
Crystalball::MapGenerator.start! do |config|
|
102
|
+
#...
|
103
|
+
config.register Crystalball::MapGenerator::ActionViewStrategy.new
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
### I18nStrategy
|
108
|
+
|
109
|
+
To use Rails specific strategies you must first `require 'crystalball/rails'`.
|
110
|
+
Patches I18n to have access to the path where the locales are defined, so that those paths can be added to the example group map.
|
111
|
+
To use it, add to your config:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
Crystalball::MapGenerator.start! do |config|
|
115
|
+
#...
|
116
|
+
config.register Crystalball::MapGenerator::I18nStrategy.new
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
### FactoryBotStrategy
|
121
|
+
|
122
|
+
Tracks which factories were used during the example and add files with corresponding definitions to the example group map.
|
123
|
+
To use it, add to your config:
|
124
|
+
```ruby
|
125
|
+
Crystalball::MapGenerator.start! do |config|
|
126
|
+
#...
|
127
|
+
config.register Crystalball::MapGenerator::FactoryBotStrategy.new
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
### Custom strategies
|
132
|
+
|
133
|
+
You can create your own strategy and use it with the map generator. Any object that responds to `#call(example_group_map, example)` (where `example_group_map` is a `Crystalball::ExampleGroupMap` and `example` a `RSpec::Core::Example`) and augmenting its list of used files using `example_group_map.push(*paths_to_files)`.
|
134
|
+
Check out the [implementation](https://github.com/toptal/crystalball/tree/master/lib/crystalball/map_generator) of the default strategies for examples.
|
135
|
+
|
136
|
+
Keep in mind that all the strategies configured for the map generator will run for each example of your test suite, so it may slow down the generation process considerably.
|
137
|
+
|
138
|
+
## TablesMapGenerator
|
139
|
+
|
140
|
+
TablesMapGenerator is a separate map generator for Rails applications. It collects information about tables-to-models mapping and stores it in a file. The file is used by `Crystalball::Rails::Predictor::ModifiedSchema`.
|
141
|
+
Use `Crystalball::Rails::TablesMapGenerator.start!` to start it.
|
142
|
+
|
143
|
+
By default TablesMapGenerator will generate `tables_map.yml` file. You can customize this behavior by setting `map_storage_path` variable:
|
144
|
+
```ruby
|
145
|
+
Crystalball::TablesMapGenerator.start! do |config|
|
146
|
+
#...
|
147
|
+
config.map_storage_path = 'my_custom_tables_map_name.yml'
|
148
|
+
end
|
149
|
+
```
|
data/docs/predictors.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Predictors
|
2
|
+
|
3
|
+
## Basic usage
|
4
|
+
|
5
|
+
[By default](https://github.com/toptal/crystalball/blob/master/lib/crystalball/rspec/runner/configuration.rb) Crystalball uses `Crystalball::RSpec::StandardPredictionBuilder` which uses
|
6
|
+
just two strategies for prediction [ModifiedExecutionPaths](#ModifiedExecutionPaths) and [ModifiedSpecs](#ModifiedSpecs).
|
7
|
+
This is more than enough for first time use.
|
8
|
+
|
9
|
+
## Advanced usage
|
10
|
+
|
11
|
+
If you want to squeeze out the most out of Crystalball you might want to add additional available prediction strategies or even add your own.
|
12
|
+
To do that you should create a class inherited from `Crystalball::RSpec::PredictionBuilder` and overload `predictor` method with your custom predictor setup similar
|
13
|
+
to what we have in [StandardPredictionBuilder](https://github.com/toptal/crystalball/blob/master/lib/crystalball/rspec/standard_prediction_builder.rb)
|
14
|
+
|
15
|
+
E.g.
|
16
|
+
```ruby
|
17
|
+
class MyPredictionBuilder < Crystalball::RSpec::PredictionBuilder
|
18
|
+
def predictor
|
19
|
+
super do |p|
|
20
|
+
p.use Crystalball::Predictor::ModifiedSpecs.new
|
21
|
+
p.use Crystalball::Predictor::ModifiedExecutionPaths.new
|
22
|
+
p.use Crystalball::Predictor::ModifiedSupportSpecs.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
```
|
28
|
+
creates a predictor with additional `ModifiedSupportSpecs` strategy enabled.
|
29
|
+
|
30
|
+
You also must let our runner know about your new prediction builder by adding it to configuration. Please see [runner configuration](runner.md) page for details.
|
31
|
+
|
32
|
+
### Strategies
|
33
|
+
|
34
|
+
#### AssociatedSpecs
|
35
|
+
|
36
|
+
Needs to be configured with rules for detecting which specs should be on the prediction.
|
37
|
+
```ruby
|
38
|
+
predictor.use Crystalball::Predictor::AssociatedSpecs.new(
|
39
|
+
from: %r{models/(.*).rb},
|
40
|
+
to: "./spec/models/%s_spec.rb"
|
41
|
+
)
|
42
|
+
```
|
43
|
+
will add `./spec/models/foo_spec.rb` to prediction when `models/foo.rb` changes.
|
44
|
+
This strategy does not depend on a previously generated example group map.
|
45
|
+
|
46
|
+
#### ModifiedExecutionPaths
|
47
|
+
|
48
|
+
Checks the example group map and the diff to see which specs are affected by the new or modified files.
|
49
|
+
|
50
|
+
#### ModifiedSpecs
|
51
|
+
|
52
|
+
As the name implies, checks for modified specs. The scope can be modified by passing a regex as argument, which defaults to `%r{spec/.*_spec\.rb\z}`.
|
53
|
+
This strategy does not depend on a previously generated example group map.
|
54
|
+
|
55
|
+
#### ModifiedSupportSpecs
|
56
|
+
|
57
|
+
Checks for modified support files used in specs and predicts full spec file. The scope can be modified by passing a regex as argument, which defaults to `%r{spec/support/.*\.rb\z}`.
|
58
|
+
Mostly usable for shared_contexts and shared_examples.
|
59
|
+
|
60
|
+
#### ModifiedSchema
|
61
|
+
|
62
|
+
Checks for modified db schema in rails application. You need to specify a path to a file with tables map generated by `TablesMapGenerator`. It checks schema diff to see which models are affected by modified tables and which specs are affected by this models.
|
63
|
+
```ruby
|
64
|
+
predictor.use Crystalball::Rails::Predictor::ModifiedSchema.new(
|
65
|
+
tables_map_path: './tables_map.yml'
|
66
|
+
)
|
67
|
+
```
|
68
|
+
|
69
|
+
_**Note**_: You may meet a warning like "WARNING: there are no model files for changed table ...". Usually, such tables are leftovers or a relation table for `has-and-belongs-to-many` associations. For the first case - nothing to worry about. For the second case - it means you want to change the default relation table.
|
70
|
+
|
71
|
+
### Custom strategies
|
72
|
+
|
73
|
+
As with the map generator you may define custom strategies for prediction. It must be an object that responds to `#call(diff, example_group_map)` (where `diff` is a `Crystalball::SourceDiff` and `example_group_map` is a `Crystalball::ExampleGroupMap`) and returns an array of paths.
|
74
|
+
|
75
|
+
Check out [default strategies implementation](https://github.com/toptal/crystalball/tree/master/lib/crystalball/predictor) for examples.
|