test_file_finder 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a5db49df6fa89ead1681625678682bc15b2f430c80c029fc12c3b1edd3e302b
4
- data.tar.gz: 7bf00383d07d235ec18bb745cd9847cbd23ef1af258f0ee38549369e8dba486c
3
+ metadata.gz: f430ea47bf579ef3674aaf0e0dc9cd41dcb667496e095fe5186a3fdf4763e3d0
4
+ data.tar.gz: f22662e4c8e9e20af318eeca9d6a6de2f24ef494a962406e202781c464cd38c9
5
5
  SHA512:
6
- metadata.gz: c18f06159878ea39337c9364a1eff56a94f22ff747d9da569a6fa5816fc7a51bc15e0990c00eb7844da9baf29d5f00154ea6b2ce9b5223ba1d96057496ca6f50
7
- data.tar.gz: 7431cb14b42c17e8d82a96c9a7fd47cf7ad88b26047c9ea5620bbe38b2c5db2c1d9458f2f655ef91f8ae41e64fee69e74ff4e84ba9903785142d9f0351972f7f
6
+ metadata.gz: 01da410e1a12bd34d58d0d3d0212d793ba99bdaef7d7b97d3fb03fb68a9b059af700a4da5a695be1976a61ef0dec7385f19e89660630b219acaf88be4b4e7601
7
+ data.tar.gz: 3e3cf7a6f6a74a7d7fcef43b462ad0b78e971cc1f1116ddf37607aa2837dce44e1b42635b7d0e275929f3d74146ba74151d3c32f717c6bdb151c49ec4afc73bb
@@ -4,6 +4,6 @@ rspec:
4
4
  - if: $CI
5
5
 
6
6
  check-executable:
7
- script: bash tff_test.sh
7
+ script: bundle; bundle exec rspec feature
8
8
  rules:
9
9
  - if: $CI
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- test_file_finder (0.1.0)
4
+ test_file_finder (0.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -44,7 +44,9 @@ $ rspec $(tff $(git diff --name-only master..head))
44
44
 
45
45
  #### Options
46
46
 
47
- `TestFileFinder` can be used with an optional mapping file to specify the mapping from a source file to a test file.
47
+ ##### `-f mapping`
48
+
49
+ `TestFileFinder` can be used with an optional YAML mapping file to specify the mapping from a source file to a test file.
48
50
 
49
51
  The mapping file is a yaml file containing to entries to match file patterns to its test files. The patterns may include capturing groups to be used to identify the test file. To refer to a captured value in the test file, use the `%s` placeholder. For example:
50
52
 
@@ -72,10 +74,50 @@ spec/models/widget_spec.rb
72
74
  Ruby example:
73
75
 
74
76
  ```ruby
75
- TestFileFinder::FileFinder.new(paths: file_paths, mapping_file: 'mapping.yml').test_files
77
+ tff = TestFileFinder::FileFinder.new(paths: file_paths)
78
+ tff.use TestFileFinder::MappingStrategies::PatternMatching.load('mapping.yml')
79
+ tff.test_files
80
+ ```
81
+
82
+ An example mapping file is available in `fixtures/mapping.yml`.
83
+
84
+ ##### `--json mapping`
85
+
86
+ `TestFileFinder` can be used with an optional JSON mapping file to specify the mapping from a source file to test files.
87
+
88
+ The mapping file is a JSON file containing a JSON object. The keys in the JSON are the source files, and the values are
89
+ arrays containing the test files. For example:
90
+
91
+ ```json
92
+ {
93
+ "app/models/project.rb": [
94
+ "spec/models/project_spec.rb",
95
+ "spec/controllers/projects_controller_spec.rb"
96
+ ],
97
+ "app/controllers/projects_controller.rb": [
98
+ "spec/controllers/projects_controller_spec.rb"
99
+ ]
100
+ }
101
+ ```
102
+
103
+ Command line example:
104
+
105
+ ```bash
106
+ $ file_paths="app/models/project.rb"
107
+ $ tff --json mapping.json $file_paths
108
+ spec/models/project_spec.rb
109
+ spec/controllers/projects_controller_spec.rb
110
+ ```
111
+
112
+ Ruby example:
113
+
114
+ ```ruby
115
+ tff = TestFileFinder::FileFinder.new(paths: file_paths)
116
+ tff.use TestFileFinder::MappingStrategies::DirectMatching.load('mapping.json')
117
+ tff.test_files
76
118
  ```
77
119
 
78
- An example mapping file is available in `spec/fixtures/mapping.yml`.
120
+ An example mapping file is available in `fixtures/mapping.json`.
79
121
 
80
122
  ## Development
81
123
 
data/exe/tff CHANGED
@@ -2,5 +2,18 @@
2
2
  require 'test_file_finder'
3
3
 
4
4
  options = TestFileFinder::OptionParser.parse!(ARGV)
5
- mapping = TestFileFinder::Mapping.load(options.mapping_file)
6
- puts TestFileFinder::FileFinder.new(paths: ARGV, mapping: mapping).test_files
5
+
6
+ TestFileFinder::FileFinder.new(paths: ARGV).tap do |file_finder|
7
+ if options.json
8
+ file_finder.use TestFileFinder::MappingStrategies::DirectMatching.load_json(options.json)
9
+ end
10
+ if options.mapping_file
11
+ file_finder.use TestFileFinder::MappingStrategies::PatternMatching.load(options.mapping_file)
12
+ end
13
+
14
+ if file_finder.strategies.empty?
15
+ file_finder.use TestFileFinder::MappingStrategies::PatternMatching.default_rails_mapping
16
+ end
17
+
18
+ puts file_finder.test_files
19
+ end
@@ -0,0 +1,49 @@
1
+ RSpec.describe 'tff exe' do
2
+ subject { `ruby -Ilib ../exe/tff #{options} #{files}` }
3
+
4
+ around do |example|
5
+ Dir.chdir(File.join(__dir__, '../fixtures')) do
6
+ example.run
7
+ end
8
+ end
9
+
10
+ context 'without any mapping' do
11
+ let(:options) {}
12
+ let(:files) { 'app/models/test_file_finder_gem_executable_widget.rb' }
13
+
14
+ it 'prints matching test files using default rails mapping' do
15
+ expect(subject).to eq("spec/models/test_file_finder_gem_executable_widget_spec.rb\n")
16
+ end
17
+ end
18
+
19
+ context 'with a yaml mapping' do
20
+ let(:options) { '-f mapping.yml' }
21
+ let(:files) { 'db/schema.rb' }
22
+
23
+ it 'prints matching test files using given yaml mapping' do
24
+ expect(subject).to eq("spec/db/schema_spec.rb\n")
25
+ end
26
+ end
27
+
28
+ context 'with a yaml mapping and json mapping' do
29
+ let(:options) { '-f mapping.yml --json mapping.json' }
30
+ let(:files) { 'db/schema.rb app/models/project.rb ' }
31
+
32
+ it 'prints matching test files using both yaml and json mappings' do
33
+ expect(subject.split("\n")).to match_array(%w[spec/db/schema_spec.rb spec/models/project_spec.rb spec/controllers/projects_controller_spec.rb])
34
+ end
35
+ end
36
+
37
+ context 'with only json mapping' do
38
+ let(:options) { '--json mapping.json' }
39
+ let(:files) { 'app/models/test_file_finder_gem_executable_widget.rb app/models/project.rb' }
40
+
41
+ it 'prints matching test files using json mapping' do
42
+ expect(subject.split("\n")).to match_array(%w[spec/models/project_spec.rb spec/controllers/projects_controller_spec.rb])
43
+ end
44
+
45
+ it 'does not match test file using rails mapping' do
46
+ expect(subject.split("\n")).not_to include('spec/models/test_file_finder_gem_executable_widget_spec.rb')
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,9 @@
1
+ {
2
+ "app/models/project.rb": [
3
+ "spec/models/project_spec.rb",
4
+ "spec/controllers/projects_controller_spec.rb"
5
+ ],
6
+ "app/controllers/projects_controller.rb": [
7
+ "spec/controllers/projects_controller_spec.rb"
8
+ ]
9
+ }
@@ -0,0 +1,9 @@
1
+ mapping:
2
+ - source: app/(.+)\.rb
3
+ test: spec/%s_spec.rb
4
+ - source: lib/(.+)\.rb
5
+ test: spec/lib/%s_spec.rb
6
+ - source: db/schema.rb
7
+ test: spec/db/schema_spec.rb
8
+ - source: spec/(.+)_spec.rb
9
+ test: spec/%s_spec.rb
@@ -0,0 +1 @@
1
+ test projects controller spec
@@ -0,0 +1 @@
1
+ test schema spec
@@ -0,0 +1 @@
1
+ test project spec
@@ -1,5 +1,6 @@
1
1
  require 'test_file_finder/file_finder'
2
2
  require 'test_file_finder/mapping'
3
+ require 'test_file_finder/mapping_strategies'
3
4
  require 'test_file_finder/option_parser'
4
5
  require 'test_file_finder/version'
5
6
 
@@ -1,8 +1,16 @@
1
+ require 'set'
2
+
1
3
  module TestFileFinder
2
4
  class FileFinder
3
- def initialize(paths: [], mapping: nil)
5
+ attr_reader :strategies
6
+
7
+ def initialize(paths: [])
4
8
  @paths = [paths].flatten
5
- @mapping = mapping
9
+ @strategies = []
10
+ end
11
+
12
+ def use(strategy)
13
+ @strategies << strategy
6
14
  end
7
15
 
8
16
  def test_files
@@ -18,13 +26,10 @@ module TestFileFinder
18
26
  end
19
27
 
20
28
  def file_path_guesses
21
- paths.flat_map do |path|
22
- guess_test_files_for(path)
23
- end.compact.uniq
24
- end
25
-
26
- def guess_test_files_for(path)
27
- @mapping.match(path)
29
+ strategies.each_with_object(Set.new) do |strategy, result|
30
+ matches = strategy.match(paths)
31
+ result.merge(matches)
32
+ end
28
33
  end
29
34
  end
30
35
  end
@@ -1,63 +1,7 @@
1
1
  require 'set'
2
2
  require 'yaml'
3
+ require 'test_file_finder/mapping_strategies'
3
4
 
4
5
  module TestFileFinder
5
- class Mapping
6
- attr_reader :pattern_matchers
7
-
8
- def self.load(mapping_file = nil)
9
- return default_rails_mapping unless mapping_file
10
-
11
- content = File.read(mapping_file)
12
- maps = YAML.load(content)['mapping']
13
-
14
- validate(maps)
15
-
16
- new do |mapping|
17
- maps.each do |map|
18
- source = map['source']
19
- test = map['test']
20
- mapping.relate(source, test)
21
- end
22
- end
23
- end
24
-
25
- def self.validate(maps)
26
- raise InvalidMappingFileError, 'missing `mapping` in test mapping file' if maps.nil?
27
- raise InvalidMappingFileError, 'missing `source` or `test` in test mapping file' if maps.any? { |map| incomplete?(map) }
28
- end
29
-
30
- def self.incomplete?(map)
31
- map['source'].nil? || map['test'].nil?
32
- end
33
-
34
- def self.default_rails_mapping
35
- new do |mapping|
36
- mapping.relate(%r{^app/(.+)\.rb$}, 'spec/%s_spec.rb')
37
- mapping.relate(%r{^lib/(.+)\.rb$}, 'spec/lib/%s_spec.rb')
38
- mapping.relate(%r{^spec/(.+)_spec.rb$}, 'spec/%s_spec.rb')
39
- end
40
- end
41
-
42
- def initialize
43
- @pattern_matchers = Hash.new { |h, k| h[k] = [] }
44
-
45
- yield self if block_given?
46
- end
47
-
48
- def relate(source, test)
49
- @pattern_matchers[source] << test
50
- end
51
-
52
- def match(file)
53
- @pattern_matchers.each_with_object(Set.new) do |(source, tests), result|
54
- regexp = %r{^#{source}$}
55
-
56
- if (match = regexp.match(file))
57
- test_files = tests.flat_map { |test| test % match.captures }
58
- result.merge(Array(test_files))
59
- end
60
- end.to_a
61
- end
62
- end
6
+ Mapping = MappingStrategies::PatternMatching
63
7
  end
@@ -0,0 +1,8 @@
1
+ require 'test_file_finder/mapping_strategies/direct_matching'
2
+ require 'test_file_finder/mapping_strategies/pattern_matching'
3
+
4
+ module TestFileFinder
5
+ module MappingStrategies
6
+
7
+ end
8
+ end
@@ -0,0 +1,34 @@
1
+ require 'json'
2
+
3
+ module TestFileFinder
4
+ module MappingStrategies
5
+ class DirectMatching
6
+ JSON_ERROR_MESSAGE = 'json file should contain a json object, with array of test files as the values'.freeze
7
+
8
+ def self.load_json(json_file)
9
+ map = JSON.parse(File.read(json_file))
10
+
11
+ validate(map)
12
+
13
+ new.tap do |strategy|
14
+ strategy.map = map
15
+ end
16
+ end
17
+
18
+ def self.validate(map)
19
+ return if map.is_a?(Hash) && map.values.all? { |value| value.is_a?(Array) }
20
+
21
+ raise InvalidMappingFileError, JSON_ERROR_MESSAGE
22
+ end
23
+
24
+ attr_accessor :map
25
+
26
+ def match(files)
27
+ Array(files).inject(Set.new) do |result, file|
28
+ test_files = @map.fetch(file, [])
29
+ result.merge(test_files)
30
+ end.to_a
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,70 @@
1
+ module TestFileFinder
2
+ module MappingStrategies
3
+ class PatternMatching
4
+ attr_reader :pattern_matchers
5
+
6
+ def self.load(mapping_file)
7
+ content = File.read(mapping_file)
8
+ maps = YAML.load(content)['mapping']
9
+
10
+ validate(maps)
11
+
12
+ new do |mapping|
13
+ maps.each do |map|
14
+ source = map['source']
15
+ test = map['test']
16
+ mapping.relate(source, test)
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.validate(maps)
22
+ raise InvalidMappingFileError, 'missing `mapping` in test mapping file' if maps.nil?
23
+ raise InvalidMappingFileError, 'missing `source` or `test` in test mapping file' if maps.any? { |map| incomplete?(map) }
24
+ end
25
+
26
+ def self.incomplete?(map)
27
+ map['source'].nil? || map['test'].nil?
28
+ end
29
+
30
+ def self.default_rails_mapping
31
+ new do |mapping|
32
+ mapping.relate(%r{^app/(.+)\.rb$}, 'spec/%s_spec.rb')
33
+ mapping.relate(%r{^lib/(.+)\.rb$}, 'spec/lib/%s_spec.rb')
34
+ mapping.relate(%r{^spec/(.+)_spec.rb$}, 'spec/%s_spec.rb')
35
+ end
36
+ end
37
+
38
+ def initialize
39
+ @pattern_matchers = []
40
+
41
+ yield self if block_given?
42
+ end
43
+
44
+ def relate(source, test)
45
+ @pattern_matchers << pattern_matcher_for(source, test)
46
+ end
47
+
48
+ def match(files)
49
+ @pattern_matchers.inject(Set.new) do |result, pattern_matcher|
50
+ test_files = pattern_matcher.call(files)
51
+ result.merge(test_files)
52
+ end.to_a
53
+ end
54
+
55
+ private
56
+
57
+ def pattern_matcher_for(source, test)
58
+ regexp = %r{^#{source}$}
59
+
60
+ proc do |files|
61
+ Array(files).flat_map do |file|
62
+ if (match = regexp.match(file))
63
+ test % match.captures
64
+ end
65
+ end.compact
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,7 +1,7 @@
1
1
  require 'optparse'
2
2
 
3
3
  module TestFileFinder
4
- Options = Struct.new(:mapping_file)
4
+ Options = Struct.new(:mapping_file, :json)
5
5
 
6
6
  class OptionParser
7
7
  def self.parse!(argv)
@@ -12,6 +12,14 @@ module TestFileFinder
12
12
  opts.on('-f', '--mapping-file FILE', String, 'Use a custom test mapping file') do |mapping_file|
13
13
  options.mapping_file = mapping_file
14
14
  end
15
+
16
+ opts.on('--yaml FILE', String, 'Use a YAML test mapping file') do |mapping_file|
17
+ options.mapping_file = mapping_file
18
+ end
19
+
20
+ opts.on('--json FILE', String, 'Use a JSON mapping file') do |json|
21
+ options.json = json
22
+ end
15
23
  end.parse!(argv)
16
24
  end
17
25
  end
@@ -1,3 +1,3 @@
1
1
  module TestFileFinder
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test_file_finder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitLab
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-08-25 00:00:00.000000000 Z
11
+ date: 2020-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -85,14 +85,23 @@ files:
85
85
  - bin/console
86
86
  - bin/setup
87
87
  - exe/tff
88
+ - feature/tff_exe_spec.rb
89
+ - fixtures/mapping.json
90
+ - fixtures/mapping.yml
91
+ - fixtures/spec/controllers/projects_controller_spec.rb
92
+ - fixtures/spec/db/schema_spec.rb
93
+ - fixtures/spec/models/project_spec.rb
94
+ - fixtures/spec/models/test_file_finder_gem_executable_widget_spec.rb
88
95
  - lib/test_file_finder.rb
89
96
  - lib/test_file_finder/file_finder.rb
90
97
  - lib/test_file_finder/mapping.rb
98
+ - lib/test_file_finder/mapping_strategies.rb
99
+ - lib/test_file_finder/mapping_strategies/direct_matching.rb
100
+ - lib/test_file_finder/mapping_strategies/pattern_matching.rb
91
101
  - lib/test_file_finder/option_parser.rb
92
102
  - lib/test_file_finder/version.rb
93
103
  - test_file_finder.gemspec
94
104
  - tests.yml
95
- - tff_test.sh
96
105
  homepage: https://gitlab.com/gitlab-org/ci-cd/test_file_finder
97
106
  licenses: []
98
107
  metadata:
@@ -1,30 +0,0 @@
1
-
2
- expected="spec/models/test_file_finder_gem_executable_widget_spec.rb"
3
- mkdir -p "spec/models"
4
- echo "existing specs" >> $expected
5
-
6
- actual=$( ruby -Ilib exe/tff app/models/test_file_finder_gem_executable_widget.rb)
7
- rm $expected
8
-
9
- if [[ -n $actual && $expected = $actual ]]
10
- then
11
- echo "Executable verified"
12
- else
13
- echo "Could not verify executable"
14
- exit 1
15
- fi
16
-
17
- expected="spec/db/schema_spec.rb"
18
- mkdir -p "spec/db"
19
- echo "existing schema specs" >> $expected
20
-
21
- actual=$( ruby -Ilib exe/tff -f spec/fixtures/mapping.yml db/schema.rb)
22
- rm $expected
23
-
24
- if [[ -n $actual && $expected = $actual ]]
25
- then
26
- echo "Executable using mapping verified"
27
- else
28
- echo "Could not verify executable using mapping"
29
- exit 1
30
- fi