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