test_file_finder 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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