rspec-remote_fixtures 0.2.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/.rspec +4 -0
- data/.rubocop.yml +27 -0
- data/.simplecov +6 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +99 -0
- data/README.md +119 -0
- data/Rakefile +12 -0
- data/lib/generators/rspec/fixture_manifest_generator.rb +64 -0
- data/lib/rspec/remote_fixtures/backend/s3_backend.rb +71 -0
- data/lib/rspec/remote_fixtures/backend.rb +11 -0
- data/lib/rspec/remote_fixtures/config.rb +75 -0
- data/lib/rspec/remote_fixtures/example_group.rb +20 -0
- data/lib/rspec/remote_fixtures/manifest.rb +47 -0
- data/lib/rspec/remote_fixtures/version.rb +7 -0
- data/lib/rspec/remote_fixtures.rb +100 -0
- data/rspec-remote_fixtures.gemspec +40 -0
- data/sig/rspec/remote_fixtures.rbs +6 -0
- metadata +107 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e56645e72d9aed2902fafbf7da230bb87e9a149b9ab501b340588143e506afee
|
|
4
|
+
data.tar.gz: 109d60b0288b9d4968f7507f5943fd22fe834bbd1bf2a4d718bcba5182992261
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4a58f66e38610dbe75cd1601b05c0622e2c30ca986a863642b8df92ca4d640491d9df9c665b9283dba0e33368064c15a066df00e3e45ed41f5f09585fcd0862c
|
|
7
|
+
data.tar.gz: '091bd17bcfd4ddf520df8270433ef5c38f6d4a33a7414fb9632c2875db7082ced63afafd133cc8cf284f1fe263f41f35d48f26f00795214c6dbac935facef621'
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 2.6
|
|
3
|
+
NewCops: enable
|
|
4
|
+
Exclude:
|
|
5
|
+
<% `git status --ignored --porcelain`.lines.grep(/^!! /).each do |path| %>
|
|
6
|
+
- <%= path.sub(/^!! /, '').sub(/\/$/, '/**/*') %>
|
|
7
|
+
<% end %>
|
|
8
|
+
|
|
9
|
+
Style/StringLiterals:
|
|
10
|
+
Enabled: true
|
|
11
|
+
EnforcedStyle: single_quotes
|
|
12
|
+
|
|
13
|
+
Style/StringLiteralsInInterpolation:
|
|
14
|
+
Enabled: true
|
|
15
|
+
EnforcedStyle: single_quotes
|
|
16
|
+
|
|
17
|
+
Layout/LineLength:
|
|
18
|
+
Max: 120
|
|
19
|
+
|
|
20
|
+
Metrics/MethodLength:
|
|
21
|
+
Max: 20
|
|
22
|
+
Exclude:
|
|
23
|
+
- 'spec/**/*'
|
|
24
|
+
Metrics/BlockLength:
|
|
25
|
+
Exclude:
|
|
26
|
+
- '**/*.rake'
|
|
27
|
+
- 'spec/**/*'
|
data/.simplecov
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
# Specify your gem's dependencies in rspec-remote-fixtures.gemspec
|
|
6
|
+
gemspec
|
|
7
|
+
|
|
8
|
+
gem 'rake', '~> 13.0'
|
|
9
|
+
gem 'rbs'
|
|
10
|
+
|
|
11
|
+
gem 'rubocop', '~> 1.21'
|
|
12
|
+
gem 'simplecov', require: false, group: :test
|
|
13
|
+
|
|
14
|
+
gem 'byebug', '~> 11.1'
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
rspec-remote_fixtures (0.2.0)
|
|
5
|
+
activesupport (>= 6.1)
|
|
6
|
+
aws-sdk-s3 (~> 1.0)
|
|
7
|
+
rspec (~> 3.12)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
activesupport (7.0.4)
|
|
13
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
14
|
+
i18n (>= 1.6, < 2)
|
|
15
|
+
minitest (>= 5.1)
|
|
16
|
+
tzinfo (~> 2.0)
|
|
17
|
+
ast (2.4.2)
|
|
18
|
+
aws-eventstream (1.2.0)
|
|
19
|
+
aws-partitions (1.688.0)
|
|
20
|
+
aws-sdk-core (3.168.4)
|
|
21
|
+
aws-eventstream (~> 1, >= 1.0.2)
|
|
22
|
+
aws-partitions (~> 1, >= 1.651.0)
|
|
23
|
+
aws-sigv4 (~> 1.5)
|
|
24
|
+
jmespath (~> 1, >= 1.6.1)
|
|
25
|
+
aws-sdk-kms (1.61.0)
|
|
26
|
+
aws-sdk-core (~> 3, >= 3.165.0)
|
|
27
|
+
aws-sigv4 (~> 1.1)
|
|
28
|
+
aws-sdk-s3 (1.117.2)
|
|
29
|
+
aws-sdk-core (~> 3, >= 3.165.0)
|
|
30
|
+
aws-sdk-kms (~> 1)
|
|
31
|
+
aws-sigv4 (~> 1.4)
|
|
32
|
+
aws-sigv4 (1.5.2)
|
|
33
|
+
aws-eventstream (~> 1, >= 1.0.2)
|
|
34
|
+
byebug (11.1.3)
|
|
35
|
+
concurrent-ruby (1.1.10)
|
|
36
|
+
diff-lcs (1.5.0)
|
|
37
|
+
docile (1.4.0)
|
|
38
|
+
i18n (1.12.0)
|
|
39
|
+
concurrent-ruby (~> 1.0)
|
|
40
|
+
jmespath (1.6.2)
|
|
41
|
+
json (2.6.3)
|
|
42
|
+
minitest (5.17.0)
|
|
43
|
+
parallel (1.22.1)
|
|
44
|
+
parser (3.2.0.0)
|
|
45
|
+
ast (~> 2.4.1)
|
|
46
|
+
rainbow (3.1.1)
|
|
47
|
+
rake (13.0.6)
|
|
48
|
+
rbs (2.8.3)
|
|
49
|
+
regexp_parser (2.6.1)
|
|
50
|
+
rexml (3.2.5)
|
|
51
|
+
rspec (3.12.0)
|
|
52
|
+
rspec-core (~> 3.12.0)
|
|
53
|
+
rspec-expectations (~> 3.12.0)
|
|
54
|
+
rspec-mocks (~> 3.12.0)
|
|
55
|
+
rspec-core (3.12.0)
|
|
56
|
+
rspec-support (~> 3.12.0)
|
|
57
|
+
rspec-expectations (3.12.1)
|
|
58
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
59
|
+
rspec-support (~> 3.12.0)
|
|
60
|
+
rspec-mocks (3.12.1)
|
|
61
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
62
|
+
rspec-support (~> 3.12.0)
|
|
63
|
+
rspec-support (3.12.0)
|
|
64
|
+
rubocop (1.42.0)
|
|
65
|
+
json (~> 2.3)
|
|
66
|
+
parallel (~> 1.10)
|
|
67
|
+
parser (>= 3.1.2.1)
|
|
68
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
69
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
70
|
+
rexml (>= 3.2.5, < 4.0)
|
|
71
|
+
rubocop-ast (>= 1.24.1, < 2.0)
|
|
72
|
+
ruby-progressbar (~> 1.7)
|
|
73
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
|
74
|
+
rubocop-ast (1.24.1)
|
|
75
|
+
parser (>= 3.1.1.0)
|
|
76
|
+
ruby-progressbar (1.11.0)
|
|
77
|
+
simplecov (0.22.0)
|
|
78
|
+
docile (~> 1.1)
|
|
79
|
+
simplecov-html (~> 0.11)
|
|
80
|
+
simplecov_json_formatter (~> 0.1)
|
|
81
|
+
simplecov-html (0.12.3)
|
|
82
|
+
simplecov_json_formatter (0.1.4)
|
|
83
|
+
tzinfo (2.0.5)
|
|
84
|
+
concurrent-ruby (~> 1.0)
|
|
85
|
+
unicode-display_width (2.4.2)
|
|
86
|
+
|
|
87
|
+
PLATFORMS
|
|
88
|
+
x86_64-linux
|
|
89
|
+
|
|
90
|
+
DEPENDENCIES
|
|
91
|
+
byebug (~> 11.1)
|
|
92
|
+
rake (~> 13.0)
|
|
93
|
+
rbs
|
|
94
|
+
rspec-remote_fixtures!
|
|
95
|
+
rubocop (~> 1.21)
|
|
96
|
+
simplecov
|
|
97
|
+
|
|
98
|
+
BUNDLED WITH
|
|
99
|
+
2.3.26
|
data/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# RSpec::RemoteFixtures
|
|
2
|
+
|
|
3
|
+
RemoteFixtures is a plugin for RSpec that lets you store test fixture files in s3 to avoid unnecessary overhead.
|
|
4
|
+
|
|
5
|
+
Why would I ever want to use this gem?
|
|
6
|
+
=========================================
|
|
7
|
+
This gem lets you stop committing large fixture files, without having to worry about git-lfs.
|
|
8
|
+
Furthermore, a great many applications use docker images to run in production and CI. If you have
|
|
9
|
+
hundreds of MB worth of fixture files, these files are first downloaded to wherever the image is being built,
|
|
10
|
+
then uploaded, over the network, likely to many CI workers and production instances.
|
|
11
|
+
Once the files are there, it's likely only a small proportion of these workers actually *need* any particular file.
|
|
12
|
+
|
|
13
|
+
Thus, rspec-remote-fixtures: A gem that sticks your rspec fixtures in S3, and downloads them transparently
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
19
|
+
|
|
20
|
+
$ bundle add rspec-remote_fixtures
|
|
21
|
+
|
|
22
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
23
|
+
|
|
24
|
+
$ gem install rspec-remote_fixtures
|
|
25
|
+
|
|
26
|
+
You will likely want to add the gem to the `develop` and `test` groups in your application's Gemfile
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
Create an initializer `rspec_remote_fixtures.rb`:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
if Rails.env.test? || Rails.env.development?
|
|
33
|
+
RSpec::RemoteFixtures::Config.backend_path = 's3://my-s3-bucket/some-prefix-path/'
|
|
34
|
+
# the following are defaults, only set if you want to override them
|
|
35
|
+
RSpec::RemoteFixtures::Config.manifest_path = 'spec/fixtures.json'
|
|
36
|
+
RSpec::RemoteFixtures::Config.backend = RSpec::RemoteFixtures::Backend::S3Backend
|
|
37
|
+
RSpec::RemoteFixtures::Config.fixture_path = Rails.root.join('spec/fixtures')
|
|
38
|
+
# use to set/override s3 auth if you need different credentials for the above bucket
|
|
39
|
+
RSpec::RemoteFixtures::Config.s3_client = Aws::S3::Client.new
|
|
40
|
+
|
|
41
|
+
# When should we validate the digest of a file fixture?
|
|
42
|
+
# always: any time the file is used in a spec
|
|
43
|
+
# download: whenever the file is downloaded to this runner for the first time
|
|
44
|
+
# never: <--
|
|
45
|
+
RSpec::RemoteFixtures::Config.check_remote_fixture_digest = :download
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
In your `rails_helper.rb`:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
require 'rspec/rails'
|
|
53
|
+
# Important: we hook into some of the helpers provided by rspec/rails so this must come after requiring it:
|
|
54
|
+
RSpec::RemoteFixtures.setup_rspec!
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
After the gem is configured, you will want to generate a manifest of your existing fixtures:
|
|
58
|
+
```shell
|
|
59
|
+
rails generate rspec:fixture_manifest
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If the s3 object in question has an `etag` which matches the local digest of the file, the file will not
|
|
63
|
+
be re-uploaded.
|
|
64
|
+
|
|
65
|
+
Future files can be added or updated by calling the generator again with the `--files` parameter:
|
|
66
|
+
|
|
67
|
+
```shell
|
|
68
|
+
rails generate rspec:fixture_manifest --files spec/fixtures/bob.txt
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Once the manifest has been set up, you can remove the fixture files from version control, and commit the manifest file.
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
There are two main ways to use this tool in your specs:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# fixture_file_path returns an absolute Pathname to a fixture located in RSpec::RemoteFixtures::Config.fixture_path
|
|
79
|
+
# The following invocation will ensure the file is present,
|
|
80
|
+
# and return Pathname.new('/my/rails/root/spec/fixtures/bob.txt')
|
|
81
|
+
# This method is available to FactoryBot factories as well as RSpec examples.
|
|
82
|
+
fixture_file_path('bob.txt')
|
|
83
|
+
|
|
84
|
+
# fixture_file_upload hooks into rspec/rails's method of the same name,
|
|
85
|
+
# downloading the file if not present and then calling super
|
|
86
|
+
fixture_file_upload('bob.txt')
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Warnings
|
|
90
|
+
|
|
91
|
+
S3 authentication relies on an accurate date. If you are using Timecop, RemoteFixtures will attempt to unfreeze for the
|
|
92
|
+
while downloading the file, and return, by calling `Timecop.unfreeze` in a block. Other libraries that freeze time may
|
|
93
|
+
cause downloads to fail.
|
|
94
|
+
|
|
95
|
+
## Design
|
|
96
|
+
|
|
97
|
+
RSpec::RemoteFixtures creates a manifest JSON file of the following form:
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"some/path.txt": {
|
|
101
|
+
"digest": "d8e8fca2dc0f896fd7cb4cb0031ba249",
|
|
102
|
+
"remote_path": "s3://some-bucket/prefix/d8e8fca2dc0f896fd7cb4cb0031ba249_path.txt"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
When `fixture_file_path` is called, the gem checks to see if the file is present on the local filesystem,
|
|
108
|
+
and conditionally verifies the digest of the local copy (see Configuration section). If the file is not present,
|
|
109
|
+
it retrieves the file, again potentially verifying the digest.
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
114
|
+
|
|
115
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
116
|
+
|
|
117
|
+
## Contributing
|
|
118
|
+
|
|
119
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/aleksclark/rspec-remote-fixtures.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../rspec/remote_fixtures'
|
|
4
|
+
|
|
5
|
+
module Rspec
|
|
6
|
+
module Generators
|
|
7
|
+
# @private
|
|
8
|
+
class FixtureManifestGenerator < ::Rails::Generators::Base
|
|
9
|
+
desc <<~DESC
|
|
10
|
+
Description:
|
|
11
|
+
Generate a fixture manifest for rspec/remote_fixtures.
|
|
12
|
+
DESC
|
|
13
|
+
class_option :files, type: :array, default: [], optional: true
|
|
14
|
+
class_option :force, type: :boolean, default: false, optional: true
|
|
15
|
+
|
|
16
|
+
def generate_manifest
|
|
17
|
+
create_file config.manifest_path, '{}' if !File.exist?(config.manifest_path) || options.force
|
|
18
|
+
|
|
19
|
+
add_files
|
|
20
|
+
|
|
21
|
+
say "Persisting manifest to #{config.manifest_path}"
|
|
22
|
+
RSpec::RemoteFixtures::Manifest.persist_manifest!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def add_files
|
|
28
|
+
if options.files.any?
|
|
29
|
+
options.files.each { |file| add_file(file) }
|
|
30
|
+
else
|
|
31
|
+
say "#{fixture_files.count} fixtures found..."
|
|
32
|
+
fixture_files.each { |path| add_file(path) }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def fixture_files
|
|
37
|
+
return @fixture_files if defined? @fixture_files
|
|
38
|
+
|
|
39
|
+
@fixture_files = []
|
|
40
|
+
Dir.chdir do
|
|
41
|
+
Dir.glob("#{config.fixture_path}/**/*", File::FNM_DOTMATCH) do |path|
|
|
42
|
+
next if File.directory?(path)
|
|
43
|
+
|
|
44
|
+
fixture_files << path
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@fixture_files
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def add_file(path)
|
|
52
|
+
path = Pathname.new(path)
|
|
53
|
+
path = Pathname.new(Dir.pwd).join(path) unless path.absolute?
|
|
54
|
+
path = path.relative_path_from(config.fixture_path)
|
|
55
|
+
say "Adding #{path} to the manifest.."
|
|
56
|
+
RSpec::RemoteFixtures::Manifest.add_fixture!(path)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def config
|
|
60
|
+
RSpec::RemoteFixtures::Config
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aws-sdk-s3'
|
|
4
|
+
require_relative '../config'
|
|
5
|
+
|
|
6
|
+
module RSpec
|
|
7
|
+
module RemoteFixtures
|
|
8
|
+
module Backend
|
|
9
|
+
# An S3 backend for RemoteFixtures
|
|
10
|
+
# Will only re-upload if the local digest doesn't match the remote object's etag
|
|
11
|
+
class S3Backend
|
|
12
|
+
attr_reader :s3_path, :s3_bucket, :s3_prefix, :bucket_name
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@s3_path = RemoteFixtures::Config.backend_path
|
|
16
|
+
raise Error, 'S3 path has not been configured!' unless @s3_path
|
|
17
|
+
|
|
18
|
+
components = s3_path.gsub('s3://', '').split('/')
|
|
19
|
+
@bucket_name = components[0]
|
|
20
|
+
@s3_bucket = s3.bucket(bucket_name)
|
|
21
|
+
@s3_prefix = components[1..].join('/')
|
|
22
|
+
super
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def s3
|
|
26
|
+
@s3 ||= Aws::S3::Resource.new(client: RemoteFixtures::Config.s3_client)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def upload(path, digest)
|
|
30
|
+
file_name = "#{digest}_#{File.basename(path)}"
|
|
31
|
+
bucket_path = "#{s3_prefix}/#{file_name}"
|
|
32
|
+
obj = s3_bucket.object(bucket_path)
|
|
33
|
+
obj.upload_file(path) unless digest_match?(obj, digest)
|
|
34
|
+
|
|
35
|
+
"s3://#{bucket_name}/#{bucket_path}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def download(remote_path, local_path)
|
|
39
|
+
report_download(remote_path, local_path)
|
|
40
|
+
if defined? Timecop && Timecop&.frozen? # rubocop:disable Style/SafeNavigation
|
|
41
|
+
Timecop.unfreeze do
|
|
42
|
+
perform_download(remote_path, local_path)
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
perform_download(remote_path, local_path)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def perform_download(remote_path, local_path)
|
|
52
|
+
bucket_path = remote_path.to_s.gsub('s3://', '').split('/')[1..].join('/')
|
|
53
|
+
obj = s3_bucket.object(bucket_path)
|
|
54
|
+
# byebug
|
|
55
|
+
obj.download_file(local_path.to_s)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def report_download(remote_path, local_path)
|
|
59
|
+
msg = "#{local_path} not present locally, retrieving from #{remote_path}"
|
|
60
|
+
RSpec.configuration.reporter.message(msg)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def digest_match?(obj, digest)
|
|
64
|
+
digest == JSON.parse(obj.etag)
|
|
65
|
+
rescue StandardError
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aws-sdk-s3'
|
|
4
|
+
require_relative 'backend'
|
|
5
|
+
|
|
6
|
+
module RSpec
|
|
7
|
+
module RemoteFixtures
|
|
8
|
+
# Configuration namespace for RemoteFixtures
|
|
9
|
+
#
|
|
10
|
+
# Defaults:
|
|
11
|
+
# `manifest_path`: `spec/fixtures.json`
|
|
12
|
+
# `fixture_path`: `spec/fixtures/`
|
|
13
|
+
# `backend`: `RSpec::RemoteFixtures::Backend::S3Backend`
|
|
14
|
+
# `backend_path`: None, must be configured
|
|
15
|
+
# `check_remote_fixture_path`: ``:download``
|
|
16
|
+
module Config
|
|
17
|
+
def self.manifest_path=(value)
|
|
18
|
+
@manifest_path = value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.manifest_path
|
|
22
|
+
@manifest_path || 'spec/fixtures.json'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.fixture_path=(value)
|
|
26
|
+
value = Pathname.new(value) unless value.is_a? Pathname
|
|
27
|
+
@fixture_path = value
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.fixture_path
|
|
31
|
+
@fixture_path ||= Pathname.new('spec/fixtures/')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.backend=(value)
|
|
35
|
+
@backend = value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.backend
|
|
39
|
+
@backend || Backend::S3Backend
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.backend_path=(value)
|
|
43
|
+
@backend_path = value
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.backend_path
|
|
47
|
+
@backend_path
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.s3_client
|
|
51
|
+
@s3_client ||= Aws::S3::Client.new
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.s3_client=(value)
|
|
55
|
+
@s3_client = value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.check_remote_fixture_digest=(value)
|
|
59
|
+
@check_remote_fixture_digest = value
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.check_remote_fixture_digest
|
|
63
|
+
@check_remote_fixture_digest || :download
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.reset!
|
|
67
|
+
@backend_path = nil
|
|
68
|
+
@check_remote_fixture_digest = nil
|
|
69
|
+
@fixture_path = nil
|
|
70
|
+
@backend = nil
|
|
71
|
+
@manifest_path = nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSpec
|
|
4
|
+
module RemoteFixtures
|
|
5
|
+
# Hooks into RSpec so that RemoteFixtures is available in examples
|
|
6
|
+
module ExampleGroup
|
|
7
|
+
def remote_fixture_path(path)
|
|
8
|
+
RemoteFixtures.ensure_file(path)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# need to maintain compatibility with rspec-rails
|
|
12
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
|
13
|
+
def fixture_file_upload(path, mime = nil, binary = false)
|
|
14
|
+
RemoteFixtures.ensure_file(path)
|
|
15
|
+
super(path, mime, binary)
|
|
16
|
+
end
|
|
17
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open-uri'
|
|
4
|
+
require 'active_support'
|
|
5
|
+
require 'active_support/core_ext'
|
|
6
|
+
|
|
7
|
+
module RSpec
|
|
8
|
+
module RemoteFixtures
|
|
9
|
+
# Keep track of fixture files, their digest, and their remote locations
|
|
10
|
+
module Manifest
|
|
11
|
+
# Support accepting a remote path and digest in case upload has already been performed
|
|
12
|
+
def self.add_fixture!(relative_path, remote_path = nil, digest = nil)
|
|
13
|
+
digest ||= compute_digest(relative_path)
|
|
14
|
+
remote_path ||= RemoteFixtures.upload(relative_path, digest)
|
|
15
|
+
manifest[relative_path] = { 'digest' => digest, 'remote_path' => remote_path }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.persist_manifest!
|
|
19
|
+
File.write(manifest_path, manifest.to_json)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.entry_for(relative_path)
|
|
23
|
+
manifest[relative_path]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.manifest
|
|
27
|
+
return @manifest if defined?(@manifest)
|
|
28
|
+
|
|
29
|
+
init_manifest! unless File.exist?(manifest_path)
|
|
30
|
+
@manifest = JSON.parse(File.read(manifest_path))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.init_manifest!
|
|
34
|
+
puts "Initializing rspec-remote-fixtures manifest #{manifest_path}"
|
|
35
|
+
File.write(manifest_path, {}.to_json)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.manifest_path
|
|
39
|
+
Config.manifest_path
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.compute_digest(path)
|
|
43
|
+
File.open(RemoteFixtures.full_path_from_relative(path), 'rb') { |f| Digest::MD5.hexdigest(f.read) }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'remote_fixtures/version'
|
|
4
|
+
require_relative 'remote_fixtures/manifest'
|
|
5
|
+
require_relative 'remote_fixtures/example_group'
|
|
6
|
+
require_relative 'remote_fixtures/backend'
|
|
7
|
+
require_relative 'remote_fixtures/config'
|
|
8
|
+
|
|
9
|
+
# Why would I ever want to use this gem?
|
|
10
|
+
# =========================================
|
|
11
|
+
# This gem lets you stop committing large fixture files, without having to worry about git-lfs.
|
|
12
|
+
# Furthermore, a great many applications use docker images to run in production and CI. If you have
|
|
13
|
+
# hundreds of MB worth of fixture files, these files are first downloaded to wherever the image is being built,
|
|
14
|
+
# then uploaded, over the network, likely to many different CI workers and production instances.
|
|
15
|
+
#
|
|
16
|
+
# Once the files are there, it's likely only a small proportion of these workers actually *need* any particular file.
|
|
17
|
+
#
|
|
18
|
+
# Thus, rspec-remote-fixtures: A gem that sticks your rspec fixtures in S3, and downloads them transparently
|
|
19
|
+
|
|
20
|
+
module RSpec
|
|
21
|
+
# An on-demand-fixture-downloader for RSpec
|
|
22
|
+
module RemoteFixtures
|
|
23
|
+
class Error < StandardError; end
|
|
24
|
+
|
|
25
|
+
def self.backend_inst
|
|
26
|
+
@backend_inst ||= Config.backend.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.ensure_file(relative_path)
|
|
30
|
+
full_path = full_path_from_relative(relative_path)
|
|
31
|
+
entry = Manifest.entry_for(relative_path)
|
|
32
|
+
log_not_found(relative_path) unless entry
|
|
33
|
+
|
|
34
|
+
verify_checksum(entry, full_path) if Config.check_remote_fixture_digest == :always
|
|
35
|
+
file_present = File.exist?(full_path)
|
|
36
|
+
|
|
37
|
+
retrieve_entry(entry, full_path) unless file_present
|
|
38
|
+
maybe_verify(entry, full_path, file_present)
|
|
39
|
+
|
|
40
|
+
full_path
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.full_path_from_relative(relative_path)
|
|
44
|
+
Pathname.new(Config.fixture_path).join(relative_path)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.retrieve_entry(entry, dest)
|
|
48
|
+
raise Error, "Attempted to retrieve #{dest} but it wasn't present in the manifest" unless entry
|
|
49
|
+
|
|
50
|
+
FileUtils.mkdir_p(dest.dirname)
|
|
51
|
+
backend_inst.download(entry['remote_path'], dest)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.maybe_verify(entry, path, file_present)
|
|
55
|
+
config_val = Config.check_remote_fixture_digest
|
|
56
|
+
return if config_val == :never
|
|
57
|
+
return if config_val == :download && file_present
|
|
58
|
+
|
|
59
|
+
raise Error, "Unable to verify digest for #{path} - entry not found" unless entry
|
|
60
|
+
|
|
61
|
+
digest = Manifest.compute_digest(path)
|
|
62
|
+
raise Error, "Digest for #{path} did not match manifest entry, aborting!" unless digest == entry['digest']
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.setup_examples!
|
|
66
|
+
return if @setup_done
|
|
67
|
+
|
|
68
|
+
@setup_done = true
|
|
69
|
+
RSpec.configuration.prepend(ExampleGroup)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.log_not_found(relative_path)
|
|
73
|
+
msg = "Warning: fixture #{relative_path} not found in manifest, this spec may fail elsewhere!"
|
|
74
|
+
RSpec.configuration.reporter.message(msg)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.upload(relative_path, digest)
|
|
78
|
+
full_path = full_path_from_relative(relative_path)
|
|
79
|
+
backend_inst.upload(full_path, digest)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.setup_rspec!
|
|
83
|
+
RSpec.configuration.fixture_path = Config.fixture_path if RSpec.configuration.respond_to?(:fixture_path=)
|
|
84
|
+
if RSpec.configuration.respond_to?(:file_fixture_path=)
|
|
85
|
+
RSpec.configuration.file_fixture_path = Config.fixture_path
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
RSpec::Core::World.prepend(World)
|
|
89
|
+
FactoryBot::SyntaxRunner.include(ExampleGroup) if defined? FactoryBot::SyntaxRunner
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# RSpec uses this for global data that's not configuration
|
|
93
|
+
module World
|
|
94
|
+
def ordered_example_groups
|
|
95
|
+
RSpec::RemoteFixtures.setup_examples!
|
|
96
|
+
super
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/rspec/remote_fixtures/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'rspec-remote_fixtures'
|
|
7
|
+
spec.version = RSpec::RemoteFixtures::VERSION
|
|
8
|
+
spec.authors = ['Aleks Clark']
|
|
9
|
+
spec.email = ['aleks.clark@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Allow rspec to fetch fixture files on demand'
|
|
12
|
+
spec.description = 'Allow rspec to fetch fixture files on demand.'
|
|
13
|
+
spec.homepage = 'https://github.com/aleksclark/rspec-remote_fixtures'
|
|
14
|
+
spec.required_ruby_version = '>= 2.6.0'
|
|
15
|
+
|
|
16
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
|
17
|
+
|
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/aleksclark/rspec-remote_fixtures'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/aleksclark/rspec-remote_fixtures/blob/main/CHANGELOG.md'
|
|
21
|
+
|
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
24
|
+
spec.files = Dir.chdir(__dir__) do
|
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
26
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
spec.bindir = 'exe'
|
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
31
|
+
spec.require_paths = ['lib']
|
|
32
|
+
|
|
33
|
+
spec.add_dependency 'activesupport', '>= 6.1'
|
|
34
|
+
spec.add_dependency 'aws-sdk-s3', '~> 1.0'
|
|
35
|
+
spec.add_dependency 'rspec', '~> 3.12'
|
|
36
|
+
|
|
37
|
+
# For more information and examples about making a new gem, check out our
|
|
38
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
39
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
40
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rspec-remote_fixtures
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Aleks Clark
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2023-01-09 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '6.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '6.1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: aws-sdk-s3
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.12'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.12'
|
|
55
|
+
description: Allow rspec to fetch fixture files on demand.
|
|
56
|
+
email:
|
|
57
|
+
- aleks.clark@gmail.com
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- ".rspec"
|
|
63
|
+
- ".rubocop.yml"
|
|
64
|
+
- ".simplecov"
|
|
65
|
+
- CHANGELOG.md
|
|
66
|
+
- Gemfile
|
|
67
|
+
- Gemfile.lock
|
|
68
|
+
- README.md
|
|
69
|
+
- Rakefile
|
|
70
|
+
- lib/generators/rspec/fixture_manifest_generator.rb
|
|
71
|
+
- lib/rspec/remote_fixtures.rb
|
|
72
|
+
- lib/rspec/remote_fixtures/backend.rb
|
|
73
|
+
- lib/rspec/remote_fixtures/backend/s3_backend.rb
|
|
74
|
+
- lib/rspec/remote_fixtures/config.rb
|
|
75
|
+
- lib/rspec/remote_fixtures/example_group.rb
|
|
76
|
+
- lib/rspec/remote_fixtures/manifest.rb
|
|
77
|
+
- lib/rspec/remote_fixtures/version.rb
|
|
78
|
+
- rspec-remote_fixtures.gemspec
|
|
79
|
+
- sig/rspec/remote_fixtures.rbs
|
|
80
|
+
homepage: https://github.com/aleksclark/rspec-remote_fixtures
|
|
81
|
+
licenses: []
|
|
82
|
+
metadata:
|
|
83
|
+
allowed_push_host: https://rubygems.org
|
|
84
|
+
homepage_uri: https://github.com/aleksclark/rspec-remote_fixtures
|
|
85
|
+
source_code_uri: https://github.com/aleksclark/rspec-remote_fixtures
|
|
86
|
+
changelog_uri: https://github.com/aleksclark/rspec-remote_fixtures/blob/main/CHANGELOG.md
|
|
87
|
+
rubygems_mfa_required: 'true'
|
|
88
|
+
post_install_message:
|
|
89
|
+
rdoc_options: []
|
|
90
|
+
require_paths:
|
|
91
|
+
- lib
|
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 2.6.0
|
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
requirements: []
|
|
103
|
+
rubygems_version: 3.3.26
|
|
104
|
+
signing_key:
|
|
105
|
+
specification_version: 4
|
|
106
|
+
summary: Allow rspec to fetch fixture files on demand
|
|
107
|
+
test_files: []
|