memoria 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.
- checksums.yaml +7 -0
- data/.editorconfig +10 -0
- data/.gitignore +13 -0
- data/.overcommit.yml +21 -0
- data/.rspec +3 -0
- data/.rubocop.yml +34 -0
- data/.travis.yml +22 -0
- data/.yardopts +1 -0
- data/.yardstick.yml +77 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +6 -0
- data/Guardfile +20 -0
- data/README.md +90 -0
- data/ROADMAP.md +22 -0
- data/Rakefile +32 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/documentation/class-diagram.svg +360 -0
- data/lib/memoria.rb +119 -0
- data/lib/memoria/configuration.rb +112 -0
- data/lib/memoria/errors.rb +4 -0
- data/lib/memoria/errors/duplicate_setting.rb +16 -0
- data/lib/memoria/errors/invalid_configuration.rb +16 -0
- data/lib/memoria/errors/invalid_record_mode.rb +16 -0
- data/lib/memoria/errors/invalid_snapshot_extension.rb +16 -0
- data/lib/memoria/rspec.rb +14 -0
- data/lib/memoria/rspec/configurator.rb +46 -0
- data/lib/memoria/rspec/matcher.rb +30 -0
- data/lib/memoria/rspec/metadata.rb +16 -0
- data/lib/memoria/rspec/metadata_parser.rb +49 -0
- data/lib/memoria/snapshot.rb +29 -0
- data/lib/memoria/snapshot_saver.rb +133 -0
- data/lib/memoria/version.rb +3 -0
- data/memoria.gemspec +42 -0
- metadata +329 -0
data/lib/memoria.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'memoria/configuration'
|
2
|
+
require 'memoria/snapshot'
|
3
|
+
require 'memoria/snapshot_saver'
|
4
|
+
require 'memoria/version'
|
5
|
+
|
6
|
+
# Encapsulates and provides access to the public interface of the gem.
|
7
|
+
module Memoria
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# The last recorded snapshot.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# Memoria.record('name-of-snapshot')
|
14
|
+
# Memoria.current_snapshot # => #<Memoria::Snapshot:0x00007fc5610a17b0 @name="name-of-snapshot">
|
15
|
+
#
|
16
|
+
# @return [Snapshot] The current snapshot or nil if there is no current snapshot.
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
#
|
20
|
+
def current_snapshot
|
21
|
+
snapshots.last
|
22
|
+
end
|
23
|
+
|
24
|
+
# Configures Memoria.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# Memoria.configure do |config|
|
28
|
+
# config.snapshot_extension = 'snappy'
|
29
|
+
# config.snapshot_record_mode = :new_episodes
|
30
|
+
# config.add_setting :custom_setting_name do
|
31
|
+
# Time.now
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# @yield The configuration block.
|
36
|
+
# @yieldparam config [Configuration] The configuration object.
|
37
|
+
# @return [void]
|
38
|
+
#
|
39
|
+
# @see Memoria::Configuration The configuration object and its available configuration options.
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
#
|
43
|
+
def configure
|
44
|
+
yield configuration
|
45
|
+
end
|
46
|
+
|
47
|
+
# Exposes the gem's configuration.
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
# Memoria.configuration.snapshot_extension # => 'snap'
|
51
|
+
#
|
52
|
+
# @return [Configuration] The Memoria configuration.
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
#
|
56
|
+
def configuration
|
57
|
+
@configuration ||= Configuration.new
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the list of recorded snapshots.
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# Memoria.record('name-of-snapshot-one')
|
64
|
+
# Memoria.snapshots # => [#<Memoria::Snapshot:0x00007fc7a3870808 @name="name-of-snapshot-one">]
|
65
|
+
#
|
66
|
+
# @return [Array<Snapshot>] An array of recorded snapshots.
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
#
|
70
|
+
def snapshots
|
71
|
+
@snapshots ||= []
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns an entity to persist snapshots (on the file system).
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# snapshot_saver = Memoria.snapshot_saver
|
78
|
+
# snapshot_saver.save(Snapshot.new('index-page'))
|
79
|
+
#
|
80
|
+
# @return [SnapshotSaver] Reads and writes snapshots to the disk.
|
81
|
+
#
|
82
|
+
# @api public
|
83
|
+
#
|
84
|
+
def snapshot_saver
|
85
|
+
@snapshot_saver ||= SnapshotSaver.new(configuration)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Records a new snapshot.
|
89
|
+
#
|
90
|
+
# @example
|
91
|
+
# Memoria.record('name-of-snapshot')
|
92
|
+
# Memoria.current_snapshot # => #<Memoria::Snapshot:0x00007fc5610a17b0 @name="name-of-snapshot">
|
93
|
+
#
|
94
|
+
# @param [String] snapshot_name The name of the snapshot.
|
95
|
+
# @return [Snapshot] A new snapshot.
|
96
|
+
#
|
97
|
+
# @api public
|
98
|
+
#
|
99
|
+
def record(snapshot_name)
|
100
|
+
snapshot = Snapshot.new(snapshot_name)
|
101
|
+
snapshots.push(snapshot)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Stops recording snapshots.
|
105
|
+
#
|
106
|
+
# @example
|
107
|
+
# Memoria.record('name-of-snapshot')
|
108
|
+
# Memoria.current_snapshot # => #<Memoria::Snapshot:0x00007fc5610a17b0 @name="name-of-snapshot">
|
109
|
+
# Memoria.stop_recording
|
110
|
+
# Memoria.current_snapshot # => nil
|
111
|
+
#
|
112
|
+
# @return [Array] An empty array.
|
113
|
+
#
|
114
|
+
# @api public
|
115
|
+
#
|
116
|
+
def stop_recording
|
117
|
+
snapshots.clear
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'memoria/errors'
|
2
|
+
|
3
|
+
module Memoria
|
4
|
+
# Stores the configuration of the gem.
|
5
|
+
class Configuration
|
6
|
+
# The only allowed snapshot record modes.
|
7
|
+
VALID_SNAPSHOT_RECORD_MODES = %i[all new_episodes none].freeze
|
8
|
+
|
9
|
+
# @return [String] Directory where the snapshots will be saved
|
10
|
+
# @api public
|
11
|
+
#
|
12
|
+
attr_accessor :snapshot_directory
|
13
|
+
|
14
|
+
# @return [String] The way the snapshots are recorded.
|
15
|
+
# @api public
|
16
|
+
#
|
17
|
+
attr_reader :snapshot_record_mode
|
18
|
+
|
19
|
+
# @return [String] File extension of new snapshots.
|
20
|
+
# @api public
|
21
|
+
#
|
22
|
+
attr_reader :snapshot_extension
|
23
|
+
|
24
|
+
# Creates an instance of the configuration.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# configuration = Memoria::Configuration.new
|
28
|
+
#
|
29
|
+
# @return [Configuration] An instance of +Configuration+.
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
#
|
33
|
+
def initialize
|
34
|
+
self.snapshot_extension = 'snap'
|
35
|
+
self.snapshot_record_mode = :new_episodes
|
36
|
+
end
|
37
|
+
|
38
|
+
# Sets the extension for new snapshots.
|
39
|
+
#
|
40
|
+
# @param [Symbol] snapshot_extension The new snapshot extension.
|
41
|
+
# @raise [Errors::InvalidSnapshotExtension] If the snapshot extension is not valid.
|
42
|
+
#
|
43
|
+
# @return [String] The given snapshot extension.
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
#
|
47
|
+
def snapshot_extension=(snapshot_extension)
|
48
|
+
validate_snapshot_extension(snapshot_extension)
|
49
|
+
@snapshot_extension = snapshot_extension
|
50
|
+
end
|
51
|
+
|
52
|
+
# Sets the snapshot record mode. TODO
|
53
|
+
#
|
54
|
+
# @param [Symbol] record_mode The new snapshot record mode.
|
55
|
+
# @raise [Errors::InvalidRecordMode] If record mode is not one of +VALID_SNAPSHOT_RECORD_MODES+
|
56
|
+
#
|
57
|
+
# @return [String] The given snapshot extension.
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
#
|
61
|
+
def snapshot_record_mode=(record_mode)
|
62
|
+
validate_record_mode(record_mode)
|
63
|
+
@snapshot_record_mode = record_mode
|
64
|
+
end
|
65
|
+
|
66
|
+
# Adds a new setting with the given +name+.
|
67
|
+
#
|
68
|
+
# @example Configuring a setting directly.
|
69
|
+
# configuration = Memoria::Configuration
|
70
|
+
# configuration.add_setting(:now) do
|
71
|
+
# Time.now
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# @example Configuring a setting through Memoria's DSL.
|
75
|
+
# Memoria.configure do |config|
|
76
|
+
# config.add_setting(:now) do
|
77
|
+
# Time.now
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# @param [Symbol] name The name of the setting.
|
82
|
+
# @param [Symbol] block The block to be executed when the setting is called.
|
83
|
+
# @raise [Errors::DuplicateSetting] If there is already a setting defined with that name.
|
84
|
+
#
|
85
|
+
# @return [Symbol] The name of the setting
|
86
|
+
#
|
87
|
+
# @api public
|
88
|
+
#
|
89
|
+
def add_setting(name, &block)
|
90
|
+
validate_setting_existence(name)
|
91
|
+
define_singleton_method(name, block)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Checks if there is a setting already defined with the given +setting_name.
|
97
|
+
def validate_setting_existence(setting_name)
|
98
|
+
error_message = "There is already a setting defined with the name #{setting_name}"
|
99
|
+
raise Errors::DuplicateSetting, error_message if respond_to?(setting_name)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Checks if the snapshot record mode is one of the modes declared in +VALID_SNAPSHOT_RECORD_MODES+.
|
103
|
+
def validate_record_mode(record_mode)
|
104
|
+
raise Errors::InvalidRecordMode unless VALID_SNAPSHOT_RECORD_MODES.include?(record_mode.to_sym)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Checks if the snapshot extension is valid.
|
108
|
+
def validate_snapshot_extension(snapshot_extension)
|
109
|
+
raise Errors::InvalidSnapshotExtension unless snapshot_extension =~ /^[a-zA-Z0-9]+$/
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Memoria
|
2
|
+
module Errors
|
3
|
+
# Raised when attempting to add a setting that already exists.
|
4
|
+
class DuplicateSetting < InvalidConfiguration
|
5
|
+
# The generic message of the exception.
|
6
|
+
#
|
7
|
+
# @return [String] Exception's message.
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
#
|
11
|
+
def message
|
12
|
+
"There's already a setting with that name."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Memoria
|
2
|
+
module Errors
|
3
|
+
# Raised when the configuration is invalid. This is a parent class to all the other classes.
|
4
|
+
class InvalidConfiguration < RuntimeError
|
5
|
+
# The generic message of the exception.
|
6
|
+
#
|
7
|
+
# @return [String] Exception's message.
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
#
|
11
|
+
def message
|
12
|
+
"Memoria's configuration is invalid."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Memoria
|
2
|
+
module Errors
|
3
|
+
# Raised when the record mode isn't one of +all+, +none+ or +new_snapshots+.
|
4
|
+
class InvalidRecordMode < InvalidConfiguration
|
5
|
+
# The generic message of the exception.
|
6
|
+
#
|
7
|
+
# @return [String] Exception's message.
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
#
|
11
|
+
def message
|
12
|
+
'The snapshot record mode is invalid. The only valid modes are :all, :none and :new_snapshots.'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Memoria
|
2
|
+
module Errors
|
3
|
+
# Raised when the snapshot extension doesn't follow a file naming pattern.
|
4
|
+
class InvalidSnapshotExtension < InvalidConfiguration
|
5
|
+
# The generic message of the exception.
|
6
|
+
#
|
7
|
+
# @return [String] Exception's message.
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
#
|
11
|
+
def message
|
12
|
+
'The snapshot extension is invalid.'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'memoria'
|
2
|
+
require 'memoria/rspec/configurator'
|
3
|
+
|
4
|
+
Memoria.configure do |config|
|
5
|
+
config.add_setting(:configure_rspec_hooks) do
|
6
|
+
Memoria::RSpec::Configurator.configure_rspec_hooks
|
7
|
+
end
|
8
|
+
|
9
|
+
config.add_setting(:include_rspec_matchers) do
|
10
|
+
Memoria::RSpec::Configurator.include_rspec_matchers
|
11
|
+
end
|
12
|
+
|
13
|
+
config.snapshot_directory = 'spec/fixtures/snapshots' unless config.snapshot_directory.nil?
|
14
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'memoria/rspec/metadata'
|
2
|
+
require 'memoria/rspec/metadata_parser'
|
3
|
+
|
4
|
+
module Memoria
|
5
|
+
module RSpec
|
6
|
+
# Configures the integration with RSpec.
|
7
|
+
module Configurator
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# Configures RSpec's +before+ and +after+ hooks to record snapshots when +match_snapshot+ is called.
|
11
|
+
#
|
12
|
+
# @return [void]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
#
|
16
|
+
def configure_rspec_hooks
|
17
|
+
::RSpec.configure do |config|
|
18
|
+
config.before(:each, snapshot: true) do |example|
|
19
|
+
current_example = example.respond_to?(:metadata) ? example : example.example
|
20
|
+
snapshot_name = Memoria::RSpec::MetadataParser.find_description_for(current_example.metadata)
|
21
|
+
|
22
|
+
Memoria.record(snapshot_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
config.after(:each, snapshot: true) do
|
26
|
+
Memoria.stop_recording
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Includes RSpec's matchers such as +match_snapshot+.
|
32
|
+
#
|
33
|
+
# @return [void]
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
#
|
37
|
+
def include_rspec_matchers
|
38
|
+
require 'memoria/rspec/matcher'
|
39
|
+
|
40
|
+
::RSpec.configure do |config|
|
41
|
+
config.include Memoria::RSpec::Metadata
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rspec/expectations'
|
2
|
+
|
3
|
+
RSpec::Matchers.define :match_snapshot do
|
4
|
+
diffable
|
5
|
+
|
6
|
+
match do |actual|
|
7
|
+
snapshot_name = Memoria.current_snapshot.name
|
8
|
+
|
9
|
+
if snapshot_saver.snapshot_exists?(snapshot_name)
|
10
|
+
@expected = snapshot_saver.read(snapshot_name)
|
11
|
+
expect(expected).to eq(actual)
|
12
|
+
else
|
13
|
+
snapshot_saver.write(snapshot_name, actual)
|
14
|
+
RSpec.configuration.reporter.message "Generated snapshot: #{snapshot_name}"
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# :nocov:
|
20
|
+
failure_message do
|
21
|
+
'Received value does not match the stored snapshot'
|
22
|
+
end
|
23
|
+
# :nocov:
|
24
|
+
|
25
|
+
def snapshot_saver
|
26
|
+
Memoria.snapshot_saver
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :expected
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Memoria
|
2
|
+
module RSpec
|
3
|
+
# Provides methods to retrieve RSpec's example metadata
|
4
|
+
module Metadata
|
5
|
+
# Retrieve the metadata from the current example
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
# @return [Hash] RSpec's metadata of the current example.
|
10
|
+
#
|
11
|
+
def current_example_metadata
|
12
|
+
self.class.metadata
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Memoria
|
2
|
+
module RSpec
|
3
|
+
# Extracts information from RSpec's examples metadata.
|
4
|
+
module MetadataParser
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Finds RSpec's description of a given example's metadata.
|
8
|
+
#
|
9
|
+
# @param [Hash] metadata RSpec's metadata of a given example.
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
#
|
15
|
+
def find_description_for(metadata)
|
16
|
+
description = find_description_in(metadata)
|
17
|
+
example_group = find_example_group_in(metadata)
|
18
|
+
|
19
|
+
if example_group
|
20
|
+
[find_description_for(example_group), description].join('/')
|
21
|
+
else
|
22
|
+
description
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Finds an RSpec description in a given metadata.
|
29
|
+
#
|
30
|
+
# @param [Hash] metadata RSpec's metadata
|
31
|
+
#
|
32
|
+
# @return [String] The description of an RSpec example
|
33
|
+
#
|
34
|
+
def find_description_in(metadata)
|
35
|
+
metadata[:description].empty? ? metadata[:scoped_id] : metadata[:description]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Finds an RSpec example group in a given metadata.
|
39
|
+
#
|
40
|
+
# @param [Hash] metadata RSpec's metadata
|
41
|
+
#
|
42
|
+
# @return [Hash] The metadata of an RSpec example group
|
43
|
+
#
|
44
|
+
def find_example_group_in(metadata)
|
45
|
+
metadata.key?(:example_group) ? metadata[:example_group] : metadata[:parent_example_group]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|