rspec-path_matchers 0.1.1 → 0.2.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/.rubocop.yml +5 -0
  4. data/.yardopts +7 -0
  5. data/CHANGELOG.md +34 -1
  6. data/README.md +164 -262
  7. data/design.rb +10 -10
  8. data/lib/rspec/path_matchers/matchers/base.rb +210 -56
  9. data/lib/rspec/path_matchers/matchers/directory_matcher.rb +172 -0
  10. data/lib/rspec/path_matchers/matchers/{have_file.rb → file_matcher.rb} +8 -5
  11. data/lib/rspec/path_matchers/matchers/no_entry_matcher.rb +64 -0
  12. data/lib/rspec/path_matchers/matchers/{have_symlink.rb → symlink_matcher.rb} +9 -6
  13. data/lib/rspec/path_matchers/options/atime.rb +6 -40
  14. data/lib/rspec/path_matchers/options/base.rb +315 -0
  15. data/lib/rspec/path_matchers/options/birthtime.rb +6 -49
  16. data/lib/rspec/path_matchers/options/content.rb +21 -40
  17. data/lib/rspec/path_matchers/options/ctime.rb +6 -40
  18. data/lib/rspec/path_matchers/options/etc_base.rb +42 -0
  19. data/lib/rspec/path_matchers/options/file_stat_base.rb +47 -0
  20. data/lib/rspec/path_matchers/options/group.rb +5 -52
  21. data/lib/rspec/path_matchers/options/json_content.rb +6 -30
  22. data/lib/rspec/path_matchers/options/mode.rb +6 -40
  23. data/lib/rspec/path_matchers/options/mtime.rb +7 -41
  24. data/lib/rspec/path_matchers/options/owner.rb +6 -53
  25. data/lib/rspec/path_matchers/options/parsed_content_base.rb +66 -0
  26. data/lib/rspec/path_matchers/options/size.rb +5 -40
  27. data/lib/rspec/path_matchers/options/symlink_atime.rb +7 -40
  28. data/lib/rspec/path_matchers/options/symlink_birthtime.rb +7 -49
  29. data/lib/rspec/path_matchers/options/symlink_ctime.rb +7 -40
  30. data/lib/rspec/path_matchers/options/symlink_group.rb +6 -52
  31. data/lib/rspec/path_matchers/options/symlink_mtime.rb +7 -40
  32. data/lib/rspec/path_matchers/options/symlink_owner.rb +7 -53
  33. data/lib/rspec/path_matchers/options/symlink_target.rb +6 -43
  34. data/lib/rspec/path_matchers/options/symlink_target_exist.rb +6 -41
  35. data/lib/rspec/path_matchers/options/symlink_target_type.rb +20 -43
  36. data/lib/rspec/path_matchers/options/yaml_content.rb +6 -31
  37. data/lib/rspec/path_matchers/options.rb +1 -0
  38. data/lib/rspec/path_matchers/refinements.rb +79 -0
  39. data/lib/rspec/path_matchers/version.rb +1 -1
  40. data/lib/rspec/path_matchers.rb +185 -16
  41. metadata +13 -8
  42. data/lib/rspec/path_matchers/matchers/directory_contents_inspector.rb +0 -57
  43. data/lib/rspec/path_matchers/matchers/have_directory.rb +0 -126
  44. data/lib/rspec/path_matchers/matchers/have_no_entry.rb +0 -49
@@ -1,62 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'etc_base'
4
+
3
5
  module RSpec
4
6
  module PathMatchers
5
7
  module Options
6
8
  # group: <expected>
7
- class Group
9
+ class Group < EtcBase
8
10
  def self.key = :group
9
-
10
- def self.description(expected)
11
- RSpec::PathMatchers.matcher?(expected) ? expected.description : expected.inspect
12
- end
13
-
14
- def self.validate_expected(expected, failure_messages)
15
- return if expected == NOT_GIVEN ||
16
- expected.is_a?(String) ||
17
- RSpec::PathMatchers.matcher?(expected)
18
-
19
- failure_messages <<
20
- "expected `#{key}:` to be a Matcher or a String, but was #{expected.inspect}"
21
- end
22
-
23
- # Returns nil if the path is owned by the expected owner
24
- # @param path [String] the path of the entry to check
25
- # @return [String, nil]
26
- #
27
- def self.match(path, expected, failure_messages)
28
- return if unsupported_platform?
29
-
30
- actual = Etc.getgrgid(File.stat(path).gid).name
31
-
32
- case expected
33
- when String then match_string(actual, expected, failure_messages)
34
- else match_matcher(actual, expected, failure_messages)
35
- end
36
- end
37
-
38
- # private methods
39
-
40
- private_class_method def self.unsupported_platform?
41
- return false if Etc.respond_to?(:getgrgid)
42
-
43
- # If the platform doesn't support Group checks, warn the user and skip the check
44
- message = 'WARNING: Group expectations are not supported on this platform and will be skipped.'
45
- RSpec.configuration.reporter.message(message)
46
- true
47
- end
48
-
49
- private_class_method def self.match_string(actual, expected, failure_messages)
50
- return if expected == actual
51
-
52
- failure_messages << "expected group to be #{expected.inspect}, but was #{actual.inspect}"
53
- end
54
-
55
- private_class_method def self.match_matcher(actual, expected, failure_messages)
56
- return if expected.matches?(actual)
57
-
58
- failure_messages << "expected group to #{expected.description}, but was #{actual.inspect}"
59
- end
11
+ def self.stat_attribute = :gid
12
+ def self.etc_method = :getgrgid
60
13
  end
61
14
  end
62
15
  end
@@ -2,41 +2,17 @@
2
2
 
3
3
  require 'json'
4
4
 
5
+ require_relative 'parsed_content_base'
6
+
5
7
  module RSpec
6
8
  module PathMatchers
7
9
  module Options
8
10
  # json_content: <expected>
9
- class JsonContent
11
+ class JsonContent < ParsedContentBase
10
12
  def self.key = :json_content
11
-
12
- def self.description(expected)
13
- return 'be json content' if expected == true
14
-
15
- expected.description
16
- end
17
-
18
- def self.validate_expected(expected, failure_messages)
19
- return if expected == NOT_GIVEN ||
20
- expected == true ||
21
- RSpec::PathMatchers.matcher?(expected)
22
-
23
- failure_messages <<
24
- "expected `#{key}:` to be a Matcher or true, but was #{expected.inspect}"
25
- end
26
-
27
- # Returns nil if the path matches the expected content
28
- # @param path [String] the path of the entry to check
29
- # @return [String, nil]
30
- #
31
- def self.match(path, expected, failure_messages)
32
- actual = JSON.parse(File.read(path))
33
-
34
- return if expected == true
35
-
36
- failure_messages << "expected JSON content to #{expected.description}" unless expected.matches?(actual)
37
- rescue JSON::ParserError => e
38
- failure_messages << "expected valid JSON content, but got error: #{e.message}"
39
- end
13
+ private_class_method def self.content_type = 'JSON'
14
+ private_class_method def self.parse(string) = JSON.parse(string)
15
+ private_class_method def self.parsing_error = JSON::ParserError
40
16
  end
41
17
  end
42
18
  end
@@ -1,50 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'file_stat_base'
4
+
3
5
  module RSpec
4
6
  module PathMatchers
5
7
  module Options
6
8
  # mode: <expected>
7
- class Mode
9
+ class Mode < FileStatBase
8
10
  def self.key = :mode
9
-
10
- def self.description(expected)
11
- RSpec::PathMatchers.matcher?(expected) ? expected.description : expected.inspect
12
- end
13
-
14
- def self.validate_expected(expected, failure_messages)
15
- return if expected == NOT_GIVEN ||
16
- expected.is_a?(String) ||
17
- RSpec::PathMatchers.matcher?(expected)
18
-
19
- failure_messages <<
20
- "expected `#{key}:` to be a Matcher or a String, but was #{expected.inspect}"
21
- end
22
-
23
- # Returns nil if the path matches the expected mode
24
- # @param path [String] the path of the entry to check
25
- # @return [String, nil]
26
- #
27
- def self.match(path, expected, failure_messages)
28
- actual = File.stat(path).mode.to_s(8)[-4..] # Get the last 4 characters of the octal mode
29
- case expected
30
- when String then match_string(actual, expected, failure_messages)
31
- else match_matcher(actual, expected, failure_messages)
32
- end
33
- end
34
-
35
- # private methods
36
-
37
- private_class_method def self.match_string(actual, expected, failure_messages)
38
- return if expected == actual
39
-
40
- failure_messages << "expected mode to be #{expected.inspect}, but was #{actual.inspect}"
41
- end
42
-
43
- private_class_method def self.match_matcher(actual, expected, failure_messages)
44
- return if expected.matches?(actual)
45
-
46
- failure_messages << "expected mode to #{expected.description}, but was #{actual.inspect}"
47
- end
11
+ def self.stat_attribute = :mode
12
+ def self.valid_expected_types = [String]
13
+ def self.fetch_actual(path, _failures) = File.stat(path).mode.to_s(8)[-4..]
48
14
  end
49
15
  end
50
16
  end
@@ -1,50 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'file_stat_base'
4
+
3
5
  module RSpec
4
6
  module PathMatchers
5
7
  module Options
6
- # mtime: <expected>
7
- class Mtime
8
+ # Inherits all logic from FileStatOption
9
+ class Mtime < FileStatBase
8
10
  def self.key = :mtime
9
-
10
- def self.description(expected)
11
- RSpec::PathMatchers.matcher?(expected) ? expected.description : expected.inspect
12
- end
13
-
14
- def self.validate_expected(expected, failure_messages)
15
- return if expected == NOT_GIVEN ||
16
- expected.is_a?(Time) || expected.is_a?(DateTime) ||
17
- RSpec::PathMatchers.matcher?(expected)
18
-
19
- failure_messages <<
20
- "expected `#{key}:` to be a Matcher, Time, or DateTime, but was #{expected.inspect}"
21
- end
22
-
23
- # Returns nil if the path matches the expected size
24
- # @param path [String] the path of the entry to check
25
- # @return [String, nil]
26
- #
27
- def self.match(path, expected, failure_messages)
28
- actual = File.stat(path).mtime
29
- case expected
30
- when Time, DateTime then match_time(actual, expected, failure_messages)
31
- else match_matcher(actual, expected, failure_messages)
32
- end
33
- end
34
-
35
- # private methods
36
-
37
- private_class_method def self.match_time(actual, expected, failure_messages)
38
- return if expected.to_time == actual
39
-
40
- failure_messages << "expected mtime to be #{expected.to_time.inspect}, but was #{actual.inspect}"
41
- end
42
-
43
- private_class_method def self.match_matcher(actual, expected, failure_messages)
44
- return if expected.matches?(actual)
45
-
46
- failure_messages << "expected mtime to #{expected.description}, but was #{actual.inspect}"
47
- end
11
+ def self.stat_attribute = :mtime
12
+ def self.valid_expected_types = [Time, DateTime]
13
+ def self.normalize_expected_literal(expected) = expected.to_time
48
14
  end
49
15
  end
50
16
  end
@@ -1,62 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'etc_base'
4
+
3
5
  module RSpec
4
6
  module PathMatchers
5
7
  module Options
6
- # owner: <expected>
7
- class Owner
8
+ # group: <expected>
9
+ class Owner < EtcBase
8
10
  def self.key = :owner
9
-
10
- def self.description(expected)
11
- RSpec::PathMatchers.matcher?(expected) ? expected.description : expected.inspect
12
- end
13
-
14
- def self.validate_expected(expected, failure_messages)
15
- return if expected == NOT_GIVEN ||
16
- expected.is_a?(String) ||
17
- RSpec::PathMatchers.matcher?(expected)
18
-
19
- failure_messages <<
20
- "expected `#{key}:` to be a Matcher or a String, but was #{expected.inspect}"
21
- end
22
-
23
- # Returns nil if the path is owned by the expected owner
24
- # @param path [String] the path of the entry to check
25
- # @return [String, nil]
26
- #
27
- def self.match(path, expected, failure_messages)
28
- return if unsupported_platform?
29
-
30
- actual = Etc.getpwuid(File.stat(path).uid).name
31
-
32
- case expected
33
- when String then match_string(actual, expected, failure_messages)
34
- else match_matcher(actual, expected, failure_messages)
35
- end
36
- end
37
-
38
- # private methods
39
-
40
- private_class_method def self.unsupported_platform?
41
- return false if Etc.respond_to?(:getpwuid)
42
-
43
- # If the platform doesn't support ownership, warn the user and skip the check
44
- message = 'WARNING: Owner expectations are not supported on this platform and will be skipped.'
45
- RSpec.configuration.reporter.message(message)
46
- true
47
- end
48
-
49
- private_class_method def self.match_string(actual, expected, failure_messages)
50
- return if expected == actual
51
-
52
- failure_messages << "expected owner to be #{expected.inspect}, but was #{actual.inspect}"
53
- end
54
-
55
- private_class_method def self.match_matcher(actual, expected, failure_messages)
56
- return if expected.matches?(actual)
57
-
58
- failure_messages << "expected owner to #{expected.description}, but was #{actual.inspect}"
59
- end
11
+ def self.stat_attribute = :uid
12
+ def self.etc_method = :getpwuid
60
13
  end
61
14
  end
62
15
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module RSpec
6
+ module PathMatchers
7
+ module Options
8
+ # Base class for options that parse file content like JSON, and YAML
9
+ class ParsedContentBase < Base
10
+ def self.valid_expected_types = [TrueClass]
11
+
12
+ # Reads and parses the file content
13
+ #
14
+ # This method will rescue any parsing errors (e.g., `JSON::ParserError`)
15
+ # and add a descriptive failure instead of crashing.
16
+ #
17
+ # @param (see RSpec::PathMatchers::Options::Base.fetch_actual)
18
+ #
19
+ # @return [Object, FETCH_ERROR] The parsed content (e.g., a Hash) or FETCH_ERROR.
20
+ #
21
+ def self.fetch_actual(path, failures)
22
+ parse(File.read(path))
23
+ rescue parsing_error => e
24
+ message = "expected valid #{content_type} content, but got error: #{e.message}"
25
+ add_failure(message, failures)
26
+ FETCH_ERROR
27
+ end
28
+
29
+ # This is the `xxxx_content: true` case. A successful fetch_actual is sufficient
30
+ def self.match_literal(_actual, _expected, _failures); end
31
+
32
+ # Failure message for when a matcher is used (e.g., `json_content: include(...)`)
33
+ #
34
+ # @param (see RSpec::PathMatchers::Options::Base.match_matcher)
35
+ #
36
+ # @return [void]
37
+ #
38
+ def self.matcher_failure_message(actual, expected)
39
+ actual_summary = actual.inspect.length > 100 ? 'it did not' : "was #{actual.inspect}"
40
+
41
+ "expected #{content_type} content to #{expected.description}, but #{actual_summary}"
42
+ end
43
+
44
+ # Provides a human-readable description for the option
45
+ #
46
+ # Returns a special message for the `json_content: true` case.
47
+ #
48
+ def self.description(expected)
49
+ expected == true ? "be #{content_type.downcase} content" : super
50
+ end
51
+
52
+ private_class_method def self.content_type
53
+ raise NotImplementedError, 'Subclasses must implement ParsedContentBase.content_type'
54
+ end
55
+
56
+ private_class_method def self.parse(string)
57
+ raise NotImplementedError, 'Subclasses must implement ParsedContentBase.parse'
58
+ end
59
+
60
+ private_class_method def self.parsing_error
61
+ raise NotImplementedError, 'Subclasses must implement ParsedContentBase.parsing_error'
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,50 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'file_stat_base'
4
+
3
5
  module RSpec
4
6
  module PathMatchers
5
7
  module Options
6
8
  # size: <expected>
7
- class Size
9
+ class Size < FileStatBase
8
10
  def self.key = :size
9
-
10
- def self.description(expected)
11
- RSpec::PathMatchers.matcher?(expected) ? expected.description : expected.to_s
12
- end
13
-
14
- def self.validate_expected(expected, failure_messages)
15
- return if expected == NOT_GIVEN ||
16
- expected.is_a?(Integer) ||
17
- RSpec::PathMatchers.matcher?(expected)
18
-
19
- failure_messages <<
20
- "expected `#{key}:` to be a Matcher or an Integer, but was #{expected.inspect}"
21
- end
22
-
23
- # Returns nil if the path matches the expected size
24
- # @param path [String] the path of the entry to check
25
- # @return [String, nil]
26
- #
27
- def self.match(path, expected, failure_messages)
28
- actual = File.size(path)
29
- case expected
30
- when Integer then match_integer(actual, expected, failure_messages)
31
- else match_matcher(actual, expected, failure_messages)
32
- end
33
- end
34
-
35
- # private methods
36
-
37
- private_class_method def self.match_integer(actual, expected, failure_messages)
38
- return if expected == actual
39
-
40
- failure_messages << "expected size to be #{expected.inspect}, but was #{actual.inspect}"
41
- end
42
-
43
- private_class_method def self.match_matcher(actual, expected, failure_messages)
44
- return if expected.matches?(actual)
45
-
46
- failure_messages << "expected size to #{expected.description}, but was #{actual.inspect}"
47
- end
11
+ def self.stat_attribute = :size
12
+ def self.valid_expected_types = [Integer]
48
13
  end
49
14
  end
50
15
  end
@@ -1,50 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'file_stat_base'
4
+
3
5
  module RSpec
4
6
  module PathMatchers
5
7
  module Options
6
8
  # atime: <expected>
7
- class SymlinkAtime
9
+ class SymlinkAtime < FileStatBase
8
10
  def self.key = :atime
9
-
10
- def self.description(expected)
11
- RSpec::PathMatchers.matcher?(expected) ? expected.description : expected.inspect
12
- end
13
-
14
- def self.validate_expected(expected, failure_messages)
15
- return if expected == NOT_GIVEN ||
16
- expected.is_a?(Time) || expected.is_a?(DateTime) ||
17
- RSpec::PathMatchers.matcher?(expected)
18
-
19
- failure_messages <<
20
- "expected `#{key}:` to be a Matcher, Time, or DateTime, but was #{expected.inspect}"
21
- end
22
-
23
- # Returns nil if the expected value matches the actual value
24
- # @param path [String] the path of the entry to check
25
- # @return [String, nil]
26
- #
27
- def self.match(path, expected, failure_messages)
28
- actual = File.lstat(path).atime
29
- case expected
30
- when Time, DateTime then match_time(actual, expected, failure_messages)
31
- else match_matcher(actual, expected, failure_messages)
32
- end
33
- end
34
-
35
- # private methods
36
-
37
- private_class_method def self.match_time(actual, expected, failure_messages)
38
- return if expected.to_time == actual
39
-
40
- failure_messages << "expected #{key} to be #{expected.to_time.inspect}, but was #{actual.inspect}"
41
- end
42
-
43
- private_class_method def self.match_matcher(actual, expected, failure_messages)
44
- return if expected.matches?(actual)
45
-
46
- failure_messages << "expected #{key} to #{expected.description}, but was #{actual.inspect}"
47
- end
11
+ def self.stat_attribute = :atime
12
+ def self.valid_expected_types = [Time, DateTime]
13
+ def self.normalize_expected_literal(expected) = expected.to_time
14
+ def self.stat_source_method = :lstat
48
15
  end
49
16
  end
50
17
  end
@@ -1,59 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'file_stat_base'
4
+
3
5
  module RSpec
4
6
  module PathMatchers
5
7
  module Options
6
8
  # birthtime: <expected>
7
- class SymlinkBirthtime
9
+ class SymlinkBirthtime < FileStatBase
8
10
  def self.key = :birthtime
9
-
10
- def self.description(expected)
11
- RSpec::PathMatchers.matcher?(expected) ? expected.description : expected.inspect
12
- end
13
-
14
- def self.validate_expected(expected, failure_messages)
15
- return if expected == NOT_GIVEN ||
16
- expected.is_a?(Time) || expected.is_a?(DateTime) ||
17
- RSpec::PathMatchers.matcher?(expected)
18
-
19
- failure_messages <<
20
- "expected `#{key}:` to be a Matcher, Time, or DateTime, but was #{expected.inspect}"
21
- end
22
-
23
- # Returns nil if the expected birthtime matches the actual birthtime
24
- # @param path [String] the path of the entry to check
25
- # @param expected [Object] the expected value to match against the actual value
26
- # @param failure_messages [Array<String>] the array to append failure messages to
27
- # @return [Void]
28
- #
29
- def self.match(path, expected, failure_messages) # rubocop:disable Metrics/MethodLength
30
- begin
31
- actual = File.lstat(path).birthtime
32
- rescue NotImplementedError
33
- message = "WARNING: #{key} expectations are not supported for #{path} and will be skipped"
34
- RSpec.configuration.reporter.message(message)
35
- return
36
- end
37
-
38
- case expected
39
- when Time, DateTime then match_time(actual, expected, failure_messages)
40
- else match_matcher(actual, expected, failure_messages)
41
- end
42
- end
43
-
44
- # private methods
45
-
46
- private_class_method def self.match_time(actual, expected, failure_messages)
47
- return if expected.to_time == actual
48
-
49
- failure_messages << "expected #{key} to be #{expected.to_time.inspect}, but was #{actual.inspect}"
50
- end
51
-
52
- private_class_method def self.match_matcher(actual, expected, failure_messages)
53
- return if expected.matches?(actual)
54
-
55
- failure_messages << "expected #{key} to #{expected.description}, but was #{actual.inspect}"
56
- end
11
+ def self.stat_attribute = :birthtime
12
+ def self.valid_expected_types = [Time, DateTime]
13
+ def self.normalize_expected_literal(expected) = expected.to_time
14
+ def self.stat_source_method = :lstat
57
15
  end
58
16
  end
59
17
  end
@@ -1,50 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'file_stat_base'
4
+
3
5
  module RSpec
4
6
  module PathMatchers
5
7
  module Options
6
8
  # ctime: <expected>
7
- class SymlinkCtime
9
+ class SymlinkCtime < FileStatBase
8
10
  def self.key = :ctime
9
-
10
- def self.description(expected)
11
- RSpec::PathMatchers.matcher?(expected) ? expected.description : expected.inspect
12
- end
13
-
14
- def self.validate_expected(expected, failure_messages)
15
- return if expected == NOT_GIVEN ||
16
- expected.is_a?(Time) || expected.is_a?(DateTime) ||
17
- RSpec::PathMatchers.matcher?(expected)
18
-
19
- failure_messages <<
20
- "expected `#{key}:` to be a Matcher, Time, or DateTime, but was #{expected.inspect}"
21
- end
22
-
23
- # Returns nil if the path matches the expected size
24
- # @param path [String] the path of the entry to check
25
- # @return [String, nil]
26
- #
27
- def self.match(path, expected, failure_messages)
28
- actual = File.lstat(path).ctime
29
- case expected
30
- when Time, DateTime then match_time(actual, expected, failure_messages)
31
- else match_matcher(actual, expected, failure_messages)
32
- end
33
- end
34
-
35
- # private methods
36
-
37
- private_class_method def self.match_time(actual, expected, failure_messages)
38
- return if expected.to_time == actual
39
-
40
- failure_messages << "expected ctime to be #{expected.to_time.inspect}, but was #{actual.inspect}"
41
- end
42
-
43
- private_class_method def self.match_matcher(actual, expected, failure_messages)
44
- return if expected.matches?(actual)
45
-
46
- failure_messages << "expected ctime to #{expected.description}, but was #{actual.inspect}"
47
- end
11
+ def self.stat_attribute = :ctime
12
+ def self.valid_expected_types = [Time, DateTime]
13
+ def self.normalize_expected_literal(expected) = expected.to_time
14
+ def self.stat_source_method = :lstat
48
15
  end
49
16
  end
50
17
  end
@@ -1,62 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'etc_base'
4
+
3
5
  module RSpec
4
6
  module PathMatchers
5
7
  module Options
6
8
  # group: <expected>
7
- class SymlinkGroup
9
+ class SymlinkGroup < EtcBase
8
10
  def self.key = :group
9
-
10
- def self.description(expected)
11
- RSpec::PathMatchers.matcher?(expected) ? expected.description : expected.inspect
12
- end
13
-
14
- def self.validate_expected(expected, failure_messages)
15
- return if expected == NOT_GIVEN ||
16
- expected.is_a?(String) ||
17
- RSpec::PathMatchers.matcher?(expected)
18
-
19
- failure_messages <<
20
- "expected `#{key}:` to be a Matcher or a String, but was #{expected.inspect}"
21
- end
22
-
23
- # Returns nil if the path is owned by the expected group
24
- # @param path [String] the path of the entry to check
25
- # @return [String, nil]
26
- #
27
- def self.match(path, expected, failure_messages)
28
- return if unsupported_platform?
29
-
30
- actual = Etc.getgrgid(File.lstat(path).gid).name
31
-
32
- case expected
33
- when String then match_string(actual, expected, failure_messages)
34
- else match_matcher(actual, expected, failure_messages)
35
- end
36
- end
37
-
38
- # private methods
39
-
40
- private_class_method def self.unsupported_platform?
41
- return false if Etc.respond_to?(:getgrgid)
42
-
43
- # If the platform doesn't support Group checks, warn the user and skip the check
44
- message = 'WARNING: Group expectations are not supported on this platform and will be skipped.'
45
- RSpec.configuration.reporter.message(message)
46
- true
47
- end
48
-
49
- private_class_method def self.match_string(actual, expected, failure_messages)
50
- return if expected == actual
51
-
52
- failure_messages << "expected group to be #{expected.inspect}, but was #{actual.inspect}"
53
- end
54
-
55
- private_class_method def self.match_matcher(actual, expected, failure_messages)
56
- return if expected.matches?(actual)
57
-
58
- failure_messages << "expected group to #{expected.description}, but was #{actual.inspect}"
59
- end
11
+ def self.stat_source_method = :lstat
12
+ def self.stat_attribute = :gid
13
+ def self.etc_method = :getgrgid
60
14
  end
61
15
  end
62
16
  end