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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.rubocop.yml +4 -0
  4. data/.travis.yml +1 -1
  5. data/CHANGELOG.md +18 -0
  6. data/LICENSE +22 -674
  7. data/README.md +13 -158
  8. data/crystalball.gemspec +6 -2
  9. data/docs/img/favicon.ico +0 -0
  10. data/docs/img/logo.png +0 -0
  11. data/docs/index.md +44 -0
  12. data/docs/map_generators.md +149 -0
  13. data/docs/predictors.md +75 -0
  14. data/docs/runner.md +24 -0
  15. data/lib/crystalball.rb +8 -3
  16. data/lib/crystalball/active_record.rb +4 -0
  17. data/lib/crystalball/example_group_map.rb +19 -0
  18. data/lib/crystalball/execution_map.rb +17 -16
  19. data/lib/crystalball/extensions/git.rb +4 -0
  20. data/lib/crystalball/extensions/git/base.rb +14 -0
  21. data/lib/crystalball/extensions/git/lib.rb +18 -0
  22. data/lib/crystalball/factory_bot.rb +3 -0
  23. data/lib/crystalball/git_repo.rb +2 -7
  24. data/lib/crystalball/logging.rb +51 -0
  25. data/lib/crystalball/map_generator.rb +5 -7
  26. data/lib/crystalball/map_generator/allocated_objects_strategy.rb +6 -5
  27. data/lib/crystalball/map_generator/allocated_objects_strategy/object_tracker.rb +1 -0
  28. data/lib/crystalball/map_generator/base_strategy.rb +5 -4
  29. data/lib/crystalball/map_generator/configuration.rb +1 -1
  30. data/lib/crystalball/map_generator/coverage_strategy.rb +6 -5
  31. data/lib/crystalball/map_generator/described_class_strategy.rb +5 -5
  32. data/lib/crystalball/map_generator/factory_bot_strategy.rb +59 -0
  33. data/lib/crystalball/map_generator/factory_bot_strategy/dsl_patch.rb +40 -0
  34. data/lib/crystalball/map_generator/factory_bot_strategy/dsl_patch/factory_path_fetcher.rb +30 -0
  35. data/lib/crystalball/map_generator/factory_bot_strategy/factory_gem_loader.rb +27 -0
  36. data/lib/crystalball/map_generator/factory_bot_strategy/factory_runner_patch.rb +25 -0
  37. data/lib/crystalball/map_generator/parser_strategy.rb +60 -0
  38. data/lib/crystalball/map_generator/parser_strategy/processor.rb +129 -0
  39. data/lib/crystalball/map_generator/strategies_collection.rb +9 -9
  40. data/lib/crystalball/map_storage/yaml_storage.rb +7 -6
  41. data/lib/crystalball/prediction.rb +12 -11
  42. data/lib/crystalball/predictor.rb +7 -5
  43. data/lib/crystalball/predictor/associated_specs.rb +9 -4
  44. data/lib/crystalball/predictor/helpers/affected_example_groups_detector.rb +20 -0
  45. data/lib/crystalball/predictor/helpers/path_formatter.rb +18 -0
  46. data/lib/crystalball/predictor/modified_execution_paths.rb +11 -5
  47. data/lib/crystalball/predictor/modified_specs.rb +8 -2
  48. data/lib/crystalball/predictor/modified_support_specs.rb +39 -0
  49. data/lib/crystalball/predictor/strategy.rb +16 -0
  50. data/lib/crystalball/predictor_evaluator.rb +1 -1
  51. data/lib/crystalball/rails.rb +1 -0
  52. data/lib/crystalball/rails/helpers/base_schema_parser.rb +51 -0
  53. data/lib/crystalball/rails/helpers/schema_definition_parser.rb +36 -0
  54. data/lib/crystalball/rails/helpers/schema_definition_parser/active_record.rb +21 -0
  55. data/lib/crystalball/rails/helpers/schema_definition_parser/table_content_parser.rb +27 -0
  56. data/lib/crystalball/rails/map_generator/action_view_strategy.rb +5 -5
  57. data/lib/crystalball/rails/map_generator/action_view_strategy/patch.rb +1 -1
  58. data/lib/crystalball/rails/map_generator/i18n_strategy.rb +5 -5
  59. data/lib/crystalball/rails/map_generator/i18n_strategy/simple_patch.rb +1 -0
  60. data/lib/crystalball/rails/predictor/modified_schema.rb +81 -0
  61. data/lib/crystalball/rails/tables_map.rb +53 -0
  62. data/lib/crystalball/rails/tables_map_generator.rb +84 -0
  63. data/lib/crystalball/rails/tables_map_generator/configuration.rb +39 -0
  64. data/lib/crystalball/rspec/filtering.rb +52 -0
  65. data/lib/crystalball/rspec/prediction_builder.rb +21 -11
  66. data/lib/crystalball/rspec/prediction_pruning.rb +56 -0
  67. data/lib/crystalball/rspec/prediction_pruning/examples_pruner.rb +70 -0
  68. data/lib/crystalball/rspec/runner.rb +39 -27
  69. data/lib/crystalball/rspec/runner/configuration.rb +24 -14
  70. data/lib/crystalball/rspec/standard_prediction_builder.rb +17 -0
  71. data/lib/crystalball/source_diff.rb +12 -2
  72. data/lib/crystalball/source_diff/file_diff.rb +1 -1
  73. data/lib/crystalball/source_diff/formatting_checker.rb +50 -0
  74. data/lib/crystalball/version.rb +1 -1
  75. data/mkdocs.yml +23 -0
  76. metadata +102 -7
  77. data/lib/crystalball/case_map.rb +0 -19
  78. 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/case_map'
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: execution_map.yml
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: 'execution_map.yml', &block)
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,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'crystalball/rails/tables_map_generator'
4
+ require 'crystalball/rails/predictor/modified_schema'
@@ -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
- attr_accessor :commit, :type, :version
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 :cases, :metadata
27
+ attr_reader :example_groups, :metadata
27
28
 
28
- delegate %i[commit commit= version version=] => :metadata
29
- delegate %i[size] => :cases
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] cases - initial list of cases
33
- def initialize(metadata: {}, cases: {})
34
- @cases = cases
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 case map to the list
40
+ # Adds example group map to the list
40
41
  #
41
- # @param [Crystalball::CaseMap] case_map
42
- def <<(case_map)
43
- cases[case_map.uid] = case_map.affected_files.uniq
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 cases
47
+ # Remove all example_groups
47
48
  def clear!
48
- self.cases = {}
49
+ self.example_groups = {}
49
50
  end
50
51
 
51
52
  private
52
53
 
53
- attr_writer :cases, :metadata
54
+ attr_writer :example_groups, :metadata
54
55
  end
55
56
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'crystalball/extensions/git/base'
4
+ require 'crystalball/extensions/git/lib'
@@ -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
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'crystalball/map_generator/factory_bot_strategy'
@@ -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) || super
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) || super
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.object('HEAD').sha if 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(CaseMap.new(example), example) { example.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.cases) if map.size.positive?
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.cases)
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 affected files every file which contain the definition of 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::CaseMap] case_map - object holding example metadata and affected files
36
- def call(case_map, example)
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 case_map, example
39
+ yield example_map, example
39
40
  end
40
- case_map.push(*execution_detector.detect(classes))
41
+ example_map.push(*execution_detector.detect(classes))
41
42
  end
42
43
  end
43
44
  end
@@ -35,6 +35,7 @@ module Crystalball
35
35
  @trace_point ||= TracePoint.new(:c_call) do |tp|
36
36
  next unless tp.method_id == :new || tp.method_id == :allocate
37
37
  next unless whitelisted_constants.any? { |c| tp.self <= c }
38
+
38
39
  created_object_classes << tp.self
39
40
  end
40
41
  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 affected_files list and
14
- # yielding back the CaseMap.
15
- # @param [Crystalball::CaseMap] _case_map - object holding example metadata and affected files
16
- def call(_case_map, _example)
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
@@ -23,7 +23,7 @@ module Crystalball
23
23
  end
24
24
 
25
25
  def map_storage_path
26
- @map_storage_path ||= Pathname('execution_map.yml')
26
+ @map_storage_path ||= Pathname('tmp/crystalball_data.yml')
27
27
  end
28
28
 
29
29
  def map_storage_path=(value)
@@ -20,14 +20,15 @@ module Crystalball
20
20
  Coverage.start
21
21
  end
22
22
 
23
- # Adds to the case_map's affected files the ones the ones in which
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::CaseMap] case_map - object holding example metadata and affected files
26
- def call(case_map, _)
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 case_map
29
+ yield example_map, example
29
30
  after = Coverage.peek_result
30
- case_map.push(*execution_detector.detect(before, after))
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::CaseMap] case_map - object holding example metadata and affected files
25
- # @param [RSpec::Core::Example] example
26
- def call(case_map, example)
27
- yield case_map
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
- case_map.push(*execution_detector.detect([described_class])) if described_class
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