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