crystalball 0.5.0 → 0.6.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.
- 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/docs/runner.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
## RSpec Runner
|
2
|
+
|
3
|
+
Crystalball has a custom RSpec runner you can use in your development with `bundle exec crystalball` command. It builds a prediction and runs it.
|
4
|
+
|
5
|
+
### Runner Configuration
|
6
|
+
|
7
|
+
#### Config file
|
8
|
+
|
9
|
+
Create a YAML file for the runner. Default locations are `./crystalball.yml` and `./config/crystalball.yml`.
|
10
|
+
Please check an [example of a config file](https://github.com/toptal/crystalball/blob/master/spec/fixtures/crystalball.yml) and [configuration defaults](https://github.com/toptal/crystalball/blob/master/lib/crystalball/rspec/runner/configuration.rb#L10) for available options.
|
11
|
+
Please keep in mind that additional generator\prediction strategies can introduce additional configuration options.
|
12
|
+
|
13
|
+
#### Overriding config file
|
14
|
+
|
15
|
+
If you want to override the path to config file please set `CRYSTALBALL_CONFIG=path/to/crystalball.yml` env variable.
|
16
|
+
|
17
|
+
Any specific configuration option in `crystalball.yml` can be overridden by providing ENV variable with "CRYSTALBALL_" prefix.
|
18
|
+
E.g. `CRYSTALBALL_EXAMPLES_LIMIT=10` will set `examples_limit` value to 10 regardless of what you have in config file.
|
19
|
+
|
20
|
+
More examples:
|
21
|
+
|
22
|
+
* `CRYSTALBALL_EXAMPLES_LIMIT=0` sets no limit on prediction size
|
23
|
+
* `CRYSTALBALL_MAP_EXPIRATION_PERIOD=0` sets no expiration period for maps
|
24
|
+
* `CRYSTALBALL_DIFF_FROM=origin/master` changes diff building to be `git diff origin/master`
|
data/lib/crystalball.rb
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'crystalball/logging'
|
3
4
|
require 'crystalball/git_repo'
|
5
|
+
require 'crystalball/extensions/git'
|
4
6
|
require 'crystalball/rspec/prediction_builder'
|
5
7
|
require 'crystalball/rspec/runner'
|
6
8
|
require 'crystalball/prediction'
|
7
9
|
require 'crystalball/predictor'
|
8
10
|
require 'crystalball/predictor/modified_execution_paths'
|
9
11
|
require 'crystalball/predictor/modified_specs'
|
12
|
+
require 'crystalball/predictor/modified_support_specs'
|
10
13
|
require 'crystalball/predictor/associated_specs'
|
11
|
-
require 'crystalball/
|
14
|
+
require 'crystalball/example_group_map'
|
12
15
|
require 'crystalball/execution_map'
|
13
16
|
require 'crystalball/map_generator'
|
14
17
|
require 'crystalball/map_generator/configuration'
|
@@ -23,7 +26,7 @@ module Crystalball
|
|
23
26
|
# Prints the list of specs which might fail
|
24
27
|
#
|
25
28
|
# @param [String] workdir - path to the root directory of repository (usually contains .git folder inside). Default: current directory
|
26
|
-
# @param [String] map_path - path to the execution map. Default:
|
29
|
+
# @param [String] map_path - path to the execution map. Default: crystalball_data.yml
|
27
30
|
# @param [Proc] block - used to configure predictors
|
28
31
|
#
|
29
32
|
# @example
|
@@ -31,8 +34,10 @@ module Crystalball
|
|
31
34
|
# predictor.use Crystalball::Predictor::ModifiedExecutionPaths.new
|
32
35
|
# predictor.use Crystalball::Predictor::ModifiedSpecs.new
|
33
36
|
# end
|
34
|
-
def self.foresee(workdir: '.', map_path: '
|
37
|
+
def self.foresee(workdir: '.', map_path: 'crystalball_data.yml', &block)
|
35
38
|
map = MapStorage::YAMLStorage.load(Pathname(map_path))
|
36
39
|
Predictor.new(map, GitRepo.open(Pathname(workdir)), from: map.commit, &block).prediction.compact
|
37
40
|
end
|
41
|
+
|
42
|
+
extend Logging
|
38
43
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Crystalball
|
4
|
+
# Data object to store execution map for specific example
|
5
|
+
class ExampleGroupMap
|
6
|
+
attr_reader :uid, :file_path, :used_files
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
delegate %i[push each] => :used_files
|
10
|
+
|
11
|
+
# @param [Example|ExampleGroup] example - RSpec example or example group
|
12
|
+
# @param [Array<String>] used_files - list of files affected by example
|
13
|
+
def initialize(example, used_files = [])
|
14
|
+
@uid = example.id
|
15
|
+
@file_path = example.file_path
|
16
|
+
@used_files = used_files
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -7,49 +7,50 @@ module Crystalball
|
|
7
7
|
|
8
8
|
# Simple data object for map metadata information
|
9
9
|
class Metadata
|
10
|
-
|
10
|
+
attr_reader :commit, :type, :version, :timestamp
|
11
11
|
|
12
12
|
# @param [String] commit - SHA of commit
|
13
13
|
# @param [String] type - type of execution map
|
14
14
|
# @param [Numeric] version - map generator version number
|
15
|
-
def initialize(commit: nil, type: nil, version: nil)
|
15
|
+
def initialize(commit: nil, type: nil, version: nil, timestamp: nil)
|
16
16
|
@commit = commit
|
17
17
|
@type = type
|
18
|
+
@timestamp = timestamp
|
18
19
|
@version = version
|
19
20
|
end
|
20
21
|
|
21
22
|
def to_h
|
22
|
-
{type: type, commit: commit, version: version}
|
23
|
+
{type: type, commit: commit, timestamp: timestamp, version: version}
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
|
-
attr_reader :
|
27
|
+
attr_reader :example_groups, :metadata
|
27
28
|
|
28
|
-
delegate %i[commit
|
29
|
-
delegate %i[size] => :
|
29
|
+
delegate %i[commit version timestamp] => :metadata
|
30
|
+
delegate %i[size] => :example_groups
|
30
31
|
|
31
32
|
# @param [Hash] metadata - add or override metadata of execution map
|
32
|
-
# @param [Hash]
|
33
|
-
def initialize(metadata: {},
|
34
|
-
@
|
33
|
+
# @param [Hash] example_groups - initial list of example groups data
|
34
|
+
def initialize(metadata: {}, example_groups: {})
|
35
|
+
@example_groups = example_groups
|
35
36
|
|
36
37
|
@metadata = Metadata.new(type: self.class.name, **metadata)
|
37
38
|
end
|
38
39
|
|
39
|
-
# Adds
|
40
|
+
# Adds example group map to the list
|
40
41
|
#
|
41
|
-
# @param [Crystalball::
|
42
|
-
def <<(
|
43
|
-
|
42
|
+
# @param [Crystalball::ExampleGroupMap] example_group_map
|
43
|
+
def <<(example_group_map)
|
44
|
+
example_groups[example_group_map.uid] = example_group_map.used_files.uniq
|
44
45
|
end
|
45
46
|
|
46
|
-
# Remove all
|
47
|
+
# Remove all example_groups
|
47
48
|
def clear!
|
48
|
-
self.
|
49
|
+
self.example_groups = {}
|
49
50
|
end
|
50
51
|
|
51
52
|
private
|
52
53
|
|
53
|
-
attr_writer :
|
54
|
+
attr_writer :example_groups, :metadata
|
54
55
|
end
|
55
56
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Git
|
4
|
+
# Represents git repo object itself.
|
5
|
+
class Base
|
6
|
+
# `git merge-base ...`. Returns common ancestor for all passed commits
|
7
|
+
#
|
8
|
+
# @param [Array<Object>] args - list of commits to process. Last argument can be options for merge-base command
|
9
|
+
# @return [Git::Object::Commit]
|
10
|
+
def merge_base(*args)
|
11
|
+
gcommit(lib.merge_base(*args))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Git
|
4
|
+
# Class wich holds whole collection of raw methods to work with git
|
5
|
+
class Lib
|
6
|
+
# `git merge-base ...`. Returns common ancestor for all passed commits
|
7
|
+
#
|
8
|
+
# @param [Array<Object>] args - list of commits to process. Last argument can be options for merge-base command
|
9
|
+
# @return [String]
|
10
|
+
def merge_base(*args)
|
11
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
12
|
+
|
13
|
+
arg_opts = opts.map { |k, v| "--#{k}" if v }.compact + args
|
14
|
+
|
15
|
+
command('merge-base', arg_opts)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/crystalball/git_repo.rb
CHANGED
@@ -26,18 +26,13 @@ module Crystalball
|
|
26
26
|
@repo_path = repo_path
|
27
27
|
end
|
28
28
|
|
29
|
-
# Check if repository has no uncommitted changes
|
30
|
-
def pristine?
|
31
|
-
diff.empty?
|
32
|
-
end
|
33
|
-
|
34
29
|
# Proxy all unknown calls to `Git` object
|
35
30
|
def method_missing(method, *args, &block)
|
36
|
-
repo.public_send(method, *args, &block)
|
31
|
+
repo.public_send(method, *args, &block)
|
37
32
|
end
|
38
33
|
|
39
34
|
def respond_to_missing?(method, *)
|
40
|
-
repo.respond_to?(method, false)
|
35
|
+
repo.respond_to?(method, false)
|
41
36
|
end
|
42
37
|
|
43
38
|
# Creates diff
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Crystalball
|
6
|
+
# This module logs information to the standard output based on the configured log level,
|
7
|
+
# and also logs unfiltered information to the configured log file.
|
8
|
+
module Logging
|
9
|
+
def log(severity_sym, *args, &block)
|
10
|
+
output_stream.log(severity(severity_sym), *args, &block)
|
11
|
+
log_file_output_stream.log(severity(severity_sym), *args, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.extended(base)
|
15
|
+
base.private_class_method :severity, :output_stream, :log_file_output_stream, :configured_level, :config
|
16
|
+
end
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
def reset_logger
|
20
|
+
@output_stream = nil
|
21
|
+
@log_file_output_stream = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def severity(severity_sym)
|
25
|
+
::Logger.const_get(severity_sym.to_s.upcase)
|
26
|
+
end
|
27
|
+
|
28
|
+
def output_stream
|
29
|
+
@output_stream ||= ::Logger.new(STDOUT).tap do |logger|
|
30
|
+
logger.level = severity(configured_level)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def log_file_output_stream
|
35
|
+
@log_file_output_stream ||= begin
|
36
|
+
config['log_file'].dirname.mkpath
|
37
|
+
::Logger.new(config['log_file']).tap do |logger|
|
38
|
+
logger.level = ::Logger::DEBUG
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def configured_level
|
44
|
+
config['log_level'].to_sym
|
45
|
+
end
|
46
|
+
|
47
|
+
def config
|
48
|
+
@config ||= Crystalball::RSpec::Runner.config
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -27,14 +27,12 @@ module Crystalball
|
|
27
27
|
|
28
28
|
def initialize
|
29
29
|
@configuration = Configuration.new
|
30
|
-
@configuration.commit = repo.
|
30
|
+
@configuration.commit = repo.gcommit('HEAD') if repo
|
31
31
|
yield @configuration if block_given?
|
32
32
|
end
|
33
33
|
|
34
34
|
# Registers strategies and prepares metadata for execution map
|
35
35
|
def start!
|
36
|
-
raise 'Repository is not pristine! Please stash all your changes' if repo && !repo.pristine?
|
37
|
-
|
38
36
|
self.map = nil
|
39
37
|
map_storage.clear!
|
40
38
|
map_storage.dump(map.metadata.to_h)
|
@@ -45,7 +43,7 @@ module Crystalball
|
|
45
43
|
|
46
44
|
# Runs example and collects execution map for it
|
47
45
|
def refresh_for_case(example)
|
48
|
-
map << strategies.run(
|
46
|
+
map << strategies.run(ExampleGroupMap.new(example), example) { example.run }
|
49
47
|
check_dump_threshold
|
50
48
|
end
|
51
49
|
|
@@ -54,11 +52,11 @@ module Crystalball
|
|
54
52
|
return unless started
|
55
53
|
|
56
54
|
strategies.each(&:before_finalize)
|
57
|
-
map_storage.dump(map.
|
55
|
+
map_storage.dump(map.example_groups) if map.size.positive?
|
58
56
|
end
|
59
57
|
|
60
58
|
def map
|
61
|
-
@map ||= map_class.new(metadata: {commit: configuration.commit, version: configuration.version})
|
59
|
+
@map ||= map_class.new(metadata: {commit: configuration.commit&.sha, timestamp: configuration.commit&.date&.to_i, version: configuration.version})
|
62
60
|
end
|
63
61
|
|
64
62
|
private
|
@@ -74,7 +72,7 @@ module Crystalball
|
|
74
72
|
def check_dump_threshold
|
75
73
|
return unless dump_threshold.positive? && map.size >= dump_threshold
|
76
74
|
|
77
|
-
map_storage.dump(map.
|
75
|
+
map_storage.dump(map.example_groups)
|
78
76
|
map.clear!
|
79
77
|
end
|
80
78
|
end
|
@@ -30,14 +30,15 @@ module Crystalball
|
|
30
30
|
@execution_detector = execution_detector
|
31
31
|
end
|
32
32
|
|
33
|
-
# Adds to the
|
33
|
+
# Adds to the used files every file which contain the definition of the
|
34
34
|
# classes of the objects allocated during the spec execution.
|
35
|
-
# @param [Crystalball::
|
36
|
-
|
35
|
+
# @param [Crystalball::ExampleGroupMap] example_map - object holding example metadata and used files
|
36
|
+
# @param [RSpec::Core::Example] example - a RSpec example
|
37
|
+
def call(example_map, example)
|
37
38
|
classes = object_tracker.used_classes_during do
|
38
|
-
yield
|
39
|
+
yield example_map, example
|
39
40
|
end
|
40
|
-
|
41
|
+
example_map.push(*execution_detector.detect(classes))
|
41
42
|
end
|
42
43
|
end
|
43
44
|
end
|
@@ -10,10 +10,11 @@ module Crystalball
|
|
10
10
|
|
11
11
|
def before_finalize; end
|
12
12
|
|
13
|
-
# Each strategy must implement #call augmenting the
|
14
|
-
# yielding back the
|
15
|
-
# @param [Crystalball::
|
16
|
-
|
13
|
+
# Each strategy must implement #call augmenting the used_files list and
|
14
|
+
# yielding back the ExampleGroupMap.
|
15
|
+
# @param [Crystalball::ExampleGroupMap] _example_map - object holding example metadata and used files
|
16
|
+
# @param [RSpec::Core::Example] _example - a RSpec example
|
17
|
+
def call(_example_map, _example)
|
17
18
|
raise NotImplementedError
|
18
19
|
end
|
19
20
|
end
|
@@ -20,14 +20,15 @@ module Crystalball
|
|
20
20
|
Coverage.start
|
21
21
|
end
|
22
22
|
|
23
|
-
# Adds to the
|
23
|
+
# Adds to the example_map's used files the ones the ones in which
|
24
24
|
# the coverage has changed after the tests runs.
|
25
|
-
# @param [Crystalball::
|
26
|
-
|
25
|
+
# @param [Crystalball::ExampleGroupMap] example_map - object holding example metadata and used files
|
26
|
+
# @param [RSpec::Core::Example] example - a RSpec example
|
27
|
+
def call(example_map, example)
|
27
28
|
before = Coverage.peek_result
|
28
|
-
yield
|
29
|
+
yield example_map, example
|
29
30
|
after = Coverage.peek_result
|
30
|
-
|
31
|
+
example_map.push(*execution_detector.detect(before, after))
|
31
32
|
end
|
32
33
|
end
|
33
34
|
end
|
@@ -21,14 +21,14 @@ module Crystalball
|
|
21
21
|
@execution_detector = execution_detector
|
22
22
|
end
|
23
23
|
|
24
|
-
# @param [Crystalball::
|
25
|
-
# @param [RSpec::Core::Example] example
|
26
|
-
def call(
|
27
|
-
yield
|
24
|
+
# @param [Crystalball::ExampleGroupMap] example_map - object holding example metadata and used files
|
25
|
+
# @param [RSpec::Core::Example] example - a RSpec example
|
26
|
+
def call(example_map, example)
|
27
|
+
yield example_map, example
|
28
28
|
|
29
29
|
described_class = example.metadata[:described_class]
|
30
30
|
|
31
|
-
|
31
|
+
example_map.push(*execution_detector.detect([described_class])) if described_class
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'crystalball/map_generator/factory_bot_strategy/factory_gem_loader'
|
4
|
+
|
5
|
+
Crystalball::MapGenerator::FactoryBotStrategy::FactoryGemLoader.require!
|
6
|
+
|
7
|
+
require 'crystalball/map_generator/base_strategy'
|
8
|
+
require 'crystalball/map_generator/helpers/path_filter'
|
9
|
+
require 'crystalball/map_generator/factory_bot_strategy/dsl_patch'
|
10
|
+
require 'crystalball/map_generator/factory_bot_strategy/factory_runner_patch'
|
11
|
+
|
12
|
+
module Crystalball
|
13
|
+
class MapGenerator
|
14
|
+
# Map generator strategy to include list of strategies which was used in an example.
|
15
|
+
class FactoryBotStrategy
|
16
|
+
include ::Crystalball::MapGenerator::BaseStrategy
|
17
|
+
include ::Crystalball::MapGenerator::Helpers::PathFilter
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def factory_bot_constant
|
21
|
+
defined?(::FactoryBot) ? ::FactoryBot : ::FactoryGirl
|
22
|
+
end
|
23
|
+
|
24
|
+
# List of factories used by current example
|
25
|
+
#
|
26
|
+
# @return [Array<String>]
|
27
|
+
def used_factories
|
28
|
+
@used_factories ||= []
|
29
|
+
end
|
30
|
+
|
31
|
+
# Map of factories to files
|
32
|
+
#
|
33
|
+
# @return [Hash<String, String>]
|
34
|
+
def factory_definitions
|
35
|
+
@factory_definitions ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Reset cached list of factories
|
39
|
+
def reset_used_factories
|
40
|
+
@used_factories = []
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def after_register
|
45
|
+
DSLPatch.apply!
|
46
|
+
FactoryRunnerPatch.apply!
|
47
|
+
end
|
48
|
+
|
49
|
+
# Adds factories related to the spec to the map
|
50
|
+
# @param [Crystalball::ExampleGroupMap] example_map - object holding example metadata and used files
|
51
|
+
# @param [RSpec::Core::Example] example - a RSpec example
|
52
|
+
def call(example_map, example)
|
53
|
+
self.class.reset_used_factories
|
54
|
+
yield example_map, example
|
55
|
+
example_map.push(*filter(self.class.used_factories.flat_map { |f| self.class.factory_definitions[f] }))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|