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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +5 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +34 -1
- data/README.md +164 -262
- data/design.rb +10 -10
- data/lib/rspec/path_matchers/matchers/base.rb +210 -56
- data/lib/rspec/path_matchers/matchers/directory_matcher.rb +172 -0
- data/lib/rspec/path_matchers/matchers/{have_file.rb → file_matcher.rb} +8 -5
- data/lib/rspec/path_matchers/matchers/no_entry_matcher.rb +64 -0
- data/lib/rspec/path_matchers/matchers/{have_symlink.rb → symlink_matcher.rb} +9 -6
- data/lib/rspec/path_matchers/options/atime.rb +6 -40
- data/lib/rspec/path_matchers/options/base.rb +315 -0
- data/lib/rspec/path_matchers/options/birthtime.rb +6 -49
- data/lib/rspec/path_matchers/options/content.rb +21 -40
- data/lib/rspec/path_matchers/options/ctime.rb +6 -40
- data/lib/rspec/path_matchers/options/etc_base.rb +42 -0
- data/lib/rspec/path_matchers/options/file_stat_base.rb +47 -0
- data/lib/rspec/path_matchers/options/group.rb +5 -52
- data/lib/rspec/path_matchers/options/json_content.rb +6 -30
- data/lib/rspec/path_matchers/options/mode.rb +6 -40
- data/lib/rspec/path_matchers/options/mtime.rb +7 -41
- data/lib/rspec/path_matchers/options/owner.rb +6 -53
- data/lib/rspec/path_matchers/options/parsed_content_base.rb +66 -0
- data/lib/rspec/path_matchers/options/size.rb +5 -40
- data/lib/rspec/path_matchers/options/symlink_atime.rb +7 -40
- data/lib/rspec/path_matchers/options/symlink_birthtime.rb +7 -49
- data/lib/rspec/path_matchers/options/symlink_ctime.rb +7 -40
- data/lib/rspec/path_matchers/options/symlink_group.rb +6 -52
- data/lib/rspec/path_matchers/options/symlink_mtime.rb +7 -40
- data/lib/rspec/path_matchers/options/symlink_owner.rb +7 -53
- data/lib/rspec/path_matchers/options/symlink_target.rb +6 -43
- data/lib/rspec/path_matchers/options/symlink_target_exist.rb +6 -41
- data/lib/rspec/path_matchers/options/symlink_target_type.rb +20 -43
- data/lib/rspec/path_matchers/options/yaml_content.rb +6 -31
- data/lib/rspec/path_matchers/options.rb +1 -0
- data/lib/rspec/path_matchers/refinements.rb +79 -0
- data/lib/rspec/path_matchers/version.rb +1 -1
- data/lib/rspec/path_matchers.rb +185 -16
- metadata +13 -8
- data/lib/rspec/path_matchers/matchers/directory_contents_inspector.rb +0 -57
- data/lib/rspec/path_matchers/matchers/have_directory.rb +0 -126
- data/lib/rspec/path_matchers/matchers/have_no_entry.rb +0 -49
@@ -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
|
# mtime: <expected>
|
7
|
-
class SymlinkMtime
|
9
|
+
class SymlinkMtime < FileStatBase
|
8
10
|
def self.key = :mtime
|
9
|
-
|
10
|
-
def self.
|
11
|
-
|
12
|
-
|
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
|
11
|
+
def self.stat_attribute = :mtime
|
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
|
-
#
|
7
|
-
class SymlinkOwner
|
8
|
+
# group: <expected>
|
9
|
+
class SymlinkOwner < EtcBase
|
8
10
|
def self.key = :owner
|
9
|
-
|
10
|
-
def self.
|
11
|
-
|
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
|
11
|
+
def self.stat_source_method = :lstat
|
12
|
+
def self.stat_attribute = :uid
|
13
|
+
def self.etc_method = :getpwuid
|
60
14
|
end
|
61
15
|
end
|
62
16
|
end
|
@@ -1,53 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'base'
|
4
|
+
|
3
5
|
module RSpec
|
4
6
|
module PathMatchers
|
5
7
|
module Options
|
6
8
|
# target: <expected>
|
7
|
-
class SymlinkTarget
|
9
|
+
class SymlinkTarget < Base
|
8
10
|
def self.key = :target
|
9
|
-
|
10
|
-
def self.
|
11
|
-
|
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
|
11
|
+
def self.valid_expected_types = [String]
|
12
|
+
def self.normalize_expected_literal(expected) = expected.to_s
|
13
|
+
def self.fetch_actual(path, _failures) = File.readlink(path)
|
51
14
|
end
|
52
15
|
end
|
53
16
|
end
|
@@ -1,52 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'base'
|
4
|
+
|
3
5
|
module RSpec
|
4
6
|
module PathMatchers
|
5
7
|
module Options
|
6
8
|
# target_exist: <expected>
|
7
|
-
class SymlinkTargetExist
|
9
|
+
class SymlinkTargetExist < Base
|
8
10
|
def self.key = :target_exist
|
11
|
+
def self.valid_expected_types = [TrueClass, FalseClass]
|
9
12
|
|
10
|
-
def self.
|
11
|
-
|
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}"
|
13
|
+
def self.fetch_actual(path, _failures) # rubocop:disable Naming/PredicateMethod
|
14
|
+
File.exist?(File.expand_path(File.readlink(path), File.dirname(path)))
|
50
15
|
end
|
51
16
|
end
|
52
17
|
end
|
@@ -1,57 +1,34 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'base'
|
4
|
+
|
3
5
|
module RSpec
|
4
6
|
module PathMatchers
|
5
7
|
module Options
|
6
8
|
# target_type: <expected>
|
7
|
-
|
9
|
+
#
|
10
|
+
# Checks the type of the entry a symlink points to (e.g., 'file', 'directory').
|
11
|
+
#
|
12
|
+
class SymlinkTargetType < Base
|
8
13
|
def self.key = :target_type
|
14
|
+
def self.valid_expected_types = [String, Symbol]
|
15
|
+
def self.normalize_expected_literal(expected) = expected.to_s
|
9
16
|
|
10
|
-
|
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]
|
17
|
+
# Overrides the base match method to gracefully handle dangling symlinks
|
28
18
|
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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}"
|
19
|
+
# If `File.ftype` fails because the target doesn't exist, it adds a
|
20
|
+
# descriptive failure instead of crashing.
|
21
|
+
#
|
22
|
+
def self.match(path, expected, failures)
|
23
|
+
super
|
24
|
+
rescue Errno::ENOENT => e
|
25
|
+
message = "expected the symlink target to exist, but got error: #{e.message}"
|
26
|
+
add_failure(message, failures)
|
27
|
+
nil
|
49
28
|
end
|
50
29
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
failure_messages << "expected #{key} to #{expected.description}, but was #{actual.inspect}"
|
30
|
+
def self.fetch_actual(path, _failures)
|
31
|
+
File.ftype(File.expand_path(File.readlink(path), File.dirname(path)))
|
55
32
|
end
|
56
33
|
end
|
57
34
|
end
|
@@ -2,42 +2,17 @@
|
|
2
2
|
|
3
3
|
require 'yaml'
|
4
4
|
|
5
|
+
require_relative 'parsed_content_base'
|
6
|
+
|
5
7
|
module RSpec
|
6
8
|
module PathMatchers
|
7
9
|
module Options
|
8
10
|
# yaml_content: <expected>
|
9
|
-
class YamlContent
|
11
|
+
class YamlContent < ParsedContentBase
|
10
12
|
def self.key = :yaml_content
|
11
|
-
|
12
|
-
def self.
|
13
|
-
|
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
|
13
|
+
private_class_method def self.content_type = 'YAML'
|
14
|
+
private_class_method def self.parse(string) = YAML.safe_load(string)
|
15
|
+
private_class_method def self.parsing_error = Psych::SyntaxError
|
41
16
|
end
|
42
17
|
end
|
43
18
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module PathMatchers
|
5
|
+
# Refinements for various classes used in RSpec::PathMatchers
|
6
|
+
module Refinements
|
7
|
+
# Refinements for Array to provide a `to_sentence` method
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# using RSpec::PathMatchers::Refinements::ArrayRefinements
|
11
|
+
#
|
12
|
+
module ArrayRefinements
|
13
|
+
DEFAULT_SENTENCE_OPTIONS = Data.define(:conjunction, :delimiter, :oxford) do
|
14
|
+
def initialize(conjunction: 'and', delimiter: ',', oxford: true)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def two_word_connector
|
19
|
+
"#{delimiter} "
|
20
|
+
end
|
21
|
+
|
22
|
+
def last_word_connector
|
23
|
+
oxford ? "#{delimiter} #{conjunction} " : " #{conjunction} "
|
24
|
+
end
|
25
|
+
end.new
|
26
|
+
|
27
|
+
refine Array do
|
28
|
+
# Converts an array to a sentence with proper conjunctions and delimiters
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# using RSpec::PathMatchers::Refinements::ArrayRefinements
|
32
|
+
# [].to_sentence # => ''
|
33
|
+
# ['apple'].to_sentence # => 'apple'
|
34
|
+
# ['apple', 'banana'].to_sentence # => 'apple and banana'
|
35
|
+
# ['apple', 'banana', 'cherry'].to_sentence # => 'apple, banana, and cherry'
|
36
|
+
#
|
37
|
+
# @example using a different conjunction
|
38
|
+
# using RSpec::PathMatchers::Refinements::ArrayRefinements
|
39
|
+
# ['apple', 'banana', 'cherry'].to_sentence(conjunction: 'or')
|
40
|
+
# #=> 'apple, banana, or cherry'
|
41
|
+
#
|
42
|
+
# @example using a different delimiter
|
43
|
+
# using RSpec::PathMatchers::Refinements::ArrayRefinements
|
44
|
+
# ['apple', 'banana', 'cherry'].to_sentence(delimiter: ';')
|
45
|
+
# #=> 'apple; banana; and cherry'
|
46
|
+
#
|
47
|
+
# @example without the Oxford comma
|
48
|
+
# using RSpec::PathMatchers::Refinements::ArrayRefinements
|
49
|
+
# ['apple', 'banana', 'cherry'].to_sentence(oxford: false)
|
50
|
+
# #=> 'apple, banana and cherry'
|
51
|
+
#
|
52
|
+
# @param options_hash [Hash] Options to customize the sentence format
|
53
|
+
#
|
54
|
+
# @option options_hash [String] :conjunction ('and') The word to use
|
55
|
+
# before the last item in the sentence
|
56
|
+
#
|
57
|
+
# @option options_hash [String] :delimiter (',') The delimiter to use
|
58
|
+
# between items in the sentence when there are three or more items
|
59
|
+
#
|
60
|
+
# @option options_hash [Boolean] :oxford (true) Whether to use the
|
61
|
+
# Oxford comma before the conjunction
|
62
|
+
#
|
63
|
+
# @return [String] The array converted to a sentence
|
64
|
+
#
|
65
|
+
def to_sentence(options_hash = {})
|
66
|
+
options = DEFAULT_SENTENCE_OPTIONS.with(**options_hash)
|
67
|
+
|
68
|
+
case length
|
69
|
+
when 0 then ''
|
70
|
+
when 1 then first.to_s
|
71
|
+
when 2 then join(" #{options.conjunction} ")
|
72
|
+
else "#{self[0..-2].join(options.two_word_connector)}#{options.last_word_connector}#{last}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|