rspec-path_matchers 0.1.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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.commitlintrc.yml +37 -0
  3. data/.husky/commit-msg +1 -0
  4. data/.release-please-manifest.json +3 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +15 -0
  7. data/.vscode/settings.json +2 -0
  8. data/CHANGELOG.md +43 -0
  9. data/CODE_OF_CONDUCT.md +132 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +409 -0
  12. data/Rakefile +73 -0
  13. data/design.rb +76 -0
  14. data/lib/rspec/path_matchers/matchers/base.rb +197 -0
  15. data/lib/rspec/path_matchers/matchers/directory_contents_inspector.rb +57 -0
  16. data/lib/rspec/path_matchers/matchers/have_directory.rb +126 -0
  17. data/lib/rspec/path_matchers/matchers/have_file.rb +45 -0
  18. data/lib/rspec/path_matchers/matchers/have_no_entry.rb +49 -0
  19. data/lib/rspec/path_matchers/matchers/have_symlink.rb +43 -0
  20. data/lib/rspec/path_matchers/options/atime.rb +51 -0
  21. data/lib/rspec/path_matchers/options/birthtime.rb +60 -0
  22. data/lib/rspec/path_matchers/options/content.rb +59 -0
  23. data/lib/rspec/path_matchers/options/ctime.rb +51 -0
  24. data/lib/rspec/path_matchers/options/group.rb +63 -0
  25. data/lib/rspec/path_matchers/options/json_content.rb +43 -0
  26. data/lib/rspec/path_matchers/options/mode.rb +51 -0
  27. data/lib/rspec/path_matchers/options/mtime.rb +51 -0
  28. data/lib/rspec/path_matchers/options/owner.rb +63 -0
  29. data/lib/rspec/path_matchers/options/size.rb +51 -0
  30. data/lib/rspec/path_matchers/options/symlink_atime.rb +51 -0
  31. data/lib/rspec/path_matchers/options/symlink_birthtime.rb +60 -0
  32. data/lib/rspec/path_matchers/options/symlink_ctime.rb +51 -0
  33. data/lib/rspec/path_matchers/options/symlink_group.rb +63 -0
  34. data/lib/rspec/path_matchers/options/symlink_mtime.rb +51 -0
  35. data/lib/rspec/path_matchers/options/symlink_owner.rb +63 -0
  36. data/lib/rspec/path_matchers/options/symlink_target.rb +54 -0
  37. data/lib/rspec/path_matchers/options/symlink_target_exist.rb +54 -0
  38. data/lib/rspec/path_matchers/options/symlink_target_type.rb +59 -0
  39. data/lib/rspec/path_matchers/options/yaml_content.rb +44 -0
  40. data/lib/rspec/path_matchers/options.rb +33 -0
  41. data/lib/rspec/path_matchers/version.rb +7 -0
  42. data/lib/rspec/path_matchers.rb +33 -0
  43. data/package.json +11 -0
  44. data/release-please-config.json +36 -0
  45. metadata +280 -0
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module PathMatchers
5
+ module Options
6
+ # ctime: <expected>
7
+ class SymlinkCtime
8
+ 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
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module PathMatchers
5
+ module Options
6
+ # group: <expected>
7
+ class SymlinkGroup
8
+ 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
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module PathMatchers
5
+ module Options
6
+ # mtime: <expected>
7
+ class SymlinkMtime
8
+ 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.lstat(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
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module PathMatchers
5
+ module Options
6
+ # owner: <expected>
7
+ class SymlinkOwner
8
+ 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.lstat(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
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module PathMatchers
5
+ module Options
6
+ # target: <expected>
7
+ class SymlinkTarget
8
+ def self.key = :target
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
+ # Populates failure_messages if expected value does not match actual value
24
+ # @param path [String] the path of the entry to check
25
+ # @param expected [Object] the expected value
26
+ # @param failure_messages [Array<String>] the array to populate with failure messages
27
+ # @return [Void]
28
+ #
29
+ def self.match(path, expected, failure_messages)
30
+ actual = File.readlink(path)
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.match_string(actual, expected, failure_messages)
41
+ return if expected == actual
42
+
43
+ failure_messages << "expected #{key} to be #{expected.inspect}, but was #{actual.inspect}"
44
+ end
45
+
46
+ private_class_method def self.match_matcher(actual, expected, failure_messages)
47
+ return if expected.matches?(actual)
48
+
49
+ failure_messages << "expected #{key} to #{expected.description}, but was #{actual.inspect}"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module PathMatchers
5
+ module Options
6
+ # target_exist: <expected>
7
+ class SymlinkTargetExist
8
+ def self.key = :target_exist?
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?(TrueClass) || expected.is_a?(FalseClass) ||
17
+ RSpec::PathMatchers.matcher?(expected)
18
+
19
+ failure_messages <<
20
+ "expected `#{key}:` to be a Matcher, true, or false but was #{expected.inspect}"
21
+ end
22
+
23
+ # Populates failure_messages if expected value does not match actual value
24
+ # @param path [String] the path of the entry to check
25
+ # @param expected [Object] the expected value
26
+ # @param failure_messages [Array<String>] the array to populate with failure messages
27
+ # @return [Void]
28
+ #
29
+ def self.match(path, expected, failure_messages)
30
+ actual = File.exist?(File.expand_path(File.readlink(path), File.dirname(path)))
31
+
32
+ case expected
33
+ when true, false then match_boolean(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.match_boolean(actual, expected, failure_messages)
41
+ return if expected == actual
42
+
43
+ failure_messages << "expected #{key} to be #{expected.inspect}, but was #{actual.inspect}"
44
+ end
45
+
46
+ private_class_method def self.match_matcher(actual, expected, failure_messages)
47
+ return if expected.matches?(actual)
48
+
49
+ failure_messages << "expected #{key} to #{expected.description}, but was #{actual.inspect}"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module PathMatchers
5
+ module Options
6
+ # target_type: <expected>
7
+ class SymlinkTargetType
8
+ def self.key = :target_type
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) || expected.is_a?(Symbol) ||
17
+ RSpec::PathMatchers.matcher?(expected)
18
+
19
+ failure_messages <<
20
+ "expected `#{key}:` to be a Matcher, a String, or a Symbol but was #{expected.inspect}"
21
+ end
22
+
23
+ # Populates failure_messages if expected value does not match actual value
24
+ # @param path [String] the path of the entry to check
25
+ # @param expected [Object] the expected value
26
+ # @param failure_messages [Array<String>] the array to populate with failure messages
27
+ # @return [Void]
28
+ #
29
+ def self.match(path, expected, failure_messages)
30
+ begin
31
+ actual = File.ftype(File.expand_path(File.readlink(path), File.dirname(path)))
32
+ rescue Errno::ENOENT => e
33
+ failure_messages << "expected the symlink target to exist, but got error: #{e.message}"
34
+ return
35
+ end
36
+
37
+ case expected
38
+ when String, Symbol then match_string(actual, expected, failure_messages)
39
+ else match_matcher(actual, expected, failure_messages)
40
+ end
41
+ end
42
+
43
+ # private methods
44
+
45
+ private_class_method def self.match_string(actual, expected, failure_messages)
46
+ return if expected.to_s == actual
47
+
48
+ failure_messages << "expected #{key} to be #{expected.to_s.inspect}, but was #{actual.inspect}"
49
+ end
50
+
51
+ private_class_method def self.match_matcher(actual, expected, failure_messages)
52
+ return if expected.matches?(actual)
53
+
54
+ failure_messages << "expected #{key} to #{expected.description}, but was #{actual.inspect}"
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module RSpec
6
+ module PathMatchers
7
+ module Options
8
+ # yaml_content: <expected>
9
+ class YamlContent
10
+ def self.key = :yaml_content
11
+
12
+ def self.description(expected)
13
+ return 'be yaml 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
+ require 'yaml'
33
+ actual = YAML.safe_load_file(path)
34
+
35
+ return if expected == true
36
+
37
+ failure_messages << "expected YAML content to #{expected.description}" unless expected.matches?(actual)
38
+ rescue Psych::SyntaxError => e
39
+ failure_messages << "expected valid YAML content, but got error: #{e.message}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module PathMatchers
5
+ module Options
6
+ # The value used to indicate that an option was not given by the user
7
+ NOT_GIVEN = Object.new.freeze
8
+ end
9
+ end
10
+ end
11
+
12
+ # Load all option classes
13
+ require_relative 'options/atime'
14
+ require_relative 'options/birthtime'
15
+ require_relative 'options/content'
16
+ require_relative 'options/ctime'
17
+ require_relative 'options/group'
18
+ require_relative 'options/json_content'
19
+ require_relative 'options/mode'
20
+ require_relative 'options/mtime'
21
+ require_relative 'options/owner'
22
+ require_relative 'options/size'
23
+ require_relative 'options/yaml_content'
24
+
25
+ require_relative 'options/symlink_atime'
26
+ require_relative 'options/symlink_birthtime'
27
+ require_relative 'options/symlink_ctime'
28
+ require_relative 'options/symlink_group'
29
+ require_relative 'options/symlink_mtime'
30
+ require_relative 'options/symlink_owner'
31
+ require_relative 'options/symlink_target'
32
+ require_relative 'options/symlink_target_exist'
33
+ require_relative 'options/symlink_target_type'
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module PathMatchers
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec'
4
+
5
+ module RSpec
6
+ # RSpec::PathMatchers is a collection of matchers for testing directory entries
7
+ module PathMatchers
8
+ # Returns true if object is a matcher
9
+ def self.matcher?(object)
10
+ object.respond_to?(:matches?) && object.respond_to?(:description)
11
+ end
12
+ end
13
+ end
14
+
15
+ require_relative 'path_matchers/version'
16
+ require_relative 'path_matchers/options'
17
+
18
+ require_relative 'path_matchers/matchers/have_file'
19
+ require_relative 'path_matchers/matchers/have_directory'
20
+ require_relative 'path_matchers/matchers/have_symlink'
21
+ require_relative 'path_matchers/matchers/have_no_entry'
22
+
23
+ def have_file(name, **options_hash) # rubocop:disable Naming/PredicatePrefix
24
+ RSpec::PathMatchers::Matchers::HaveFile.new(name, **options_hash)
25
+ end
26
+
27
+ def have_dir(name, **options_hash, &) # rubocop:disable Naming/PredicatePrefix
28
+ RSpec::PathMatchers::Matchers::HaveDirectory.new(name, **options_hash, &)
29
+ end
30
+
31
+ def have_symlink(name, **options_hash) # rubocop:disable Naming/PredicatePrefix
32
+ RSpec::PathMatchers::Matchers::HaveSymlink.new(name, **options_hash)
33
+ end
data/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "devDependencies": {
3
+ "@commitlint/cli": "^19.5.0",
4
+ "@commitlint/config-conventional": "^19.5.0",
5
+ "husky": "^9.1.0"
6
+ },
7
+ "scripts": {
8
+ "postinstall": "husky",
9
+ "prepare": "husky"
10
+ }
11
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "packages": {
3
+ ".": {
4
+ "release-type": "ruby",
5
+ "package-name": "rspec-path_matchers",
6
+ "changelog-path": "CHANGELOG.md",
7
+ "version-file": "lib/rspec/path_matchers/version.rb",
8
+ "bump-minor-pre-major": true,
9
+ "bump-patch-for-minor-pre-major": true,
10
+ "draft": false,
11
+ "prerelease": false,
12
+ "include-component-in-tag": false,
13
+ "pull-request-title-pattern": "chore: release v${version}",
14
+ "changelog-sections": [
15
+ { "type": "feat", "section": "Features", "hidden": false },
16
+ { "type": "fix", "section": "Bug Fixes", "hidden": false },
17
+ { "type": "build", "section": "Other Changes", "hidden": false },
18
+ { "type": "chore", "section": "Other Changes", "hidden": false },
19
+ { "type": "ci", "section": "Other Changes", "hidden": false },
20
+ { "type": "docs", "section": "Other Changes", "hidden": false },
21
+ { "type": "perf", "section": "Other Changes", "hidden": false },
22
+ { "type": "refactor", "section": "Other Changes", "hidden": false },
23
+ { "type": "revert", "section": "Other Changes", "hidden": false },
24
+ { "type": "style", "section": "Other Changes", "hidden": false },
25
+ { "type": "test", "section": "Other Changes", "hidden": false }
26
+ ]
27
+ }
28
+ },
29
+ "initial-version": "0.1.0",
30
+ "plugins": [
31
+ {
32
+ "type": "sentence-case"
33
+ }
34
+ ],
35
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
36
+ }