puppet_forge 4.1.0 → 5.0.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 +4 -4
- data/.github/workflows/ruby-rspec.yml +2 -1
- data/CHANGELOG.md +6 -0
- data/CODEOWNERS +1 -1
- data/lib/puppet_forge/lru_cache.rb +78 -0
- data/lib/puppet_forge/v3/base.rb +18 -1
- data/lib/puppet_forge/v3/module.rb +5 -0
- data/lib/puppet_forge/version.rb +1 -1
- data/spec/spec_helper.rb +5 -2
- data/spec/unit/forge/lru_cache_spec.rb +127 -0
- data/spec/unit/forge/v3/base_spec.rb +30 -0
- data/spec/unit/forge/v3/module_spec.rb +2 -2
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2eed3dead78d19c535ee9a2b372a89e17efcf7b24f01c752bb38096a2ed52e37
|
4
|
+
data.tar.gz: 2036388fc19159a57d425383aa6c89326d222a900f9fd61fbbb529e62d004f28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca288c45d5329d1656221858c03a1125e0e29eb1e6701b95da90baa2cd6ba254907e15fafbf4f1474b69a43014a3c9f10fbcaed620d3913d4bf221194b3a2cc5
|
7
|
+
data.tar.gz: 8f5a441289712e2cb9ef9c7d34a17cef347b2b09ff0f60fc1430cceae3780229201aecee3b0baa601a3fd343060ed1976fad247e8e54abbbfb7d5a9a37f30fc0
|
@@ -15,13 +15,14 @@ jobs:
|
|
15
15
|
strategy:
|
16
16
|
matrix:
|
17
17
|
ruby:
|
18
|
+
- '3.2'
|
18
19
|
- '3.1'
|
19
20
|
- '3.0'
|
20
21
|
- '2.7'
|
21
22
|
- '2.6'
|
22
23
|
|
23
24
|
steps:
|
24
|
-
- uses: actions/checkout@
|
25
|
+
- uses: actions/checkout@v3
|
25
26
|
- name: Set up Ruby ${{ matrix.ruby }}
|
26
27
|
uses: ruby/setup-ruby@v1
|
27
28
|
with:
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,12 @@
|
|
3
3
|
Starting with v2.0.0, all notable changes to this project will be documented in this file.
|
4
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
5
5
|
|
6
|
+
## v5.0.0 - 2023-05-07
|
7
|
+
|
8
|
+
* Ruby 3.2 support.
|
9
|
+
* LRU caching for HTTP response caching.
|
10
|
+
* Raise a ModuleNotFound error instead of just nil when a module is not found.
|
11
|
+
|
6
12
|
## v4.1.0 - 2023-02-21
|
7
13
|
|
8
14
|
* Add upload method functionality.
|
data/CODEOWNERS
CHANGED
@@ -1 +1 @@
|
|
1
|
-
* @puppetlabs/forge-team
|
1
|
+
* @puppetlabs/forge-team @bastelfreak
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module PuppetForge
|
4
|
+
# Implements a simple LRU cache. This is used internally by the
|
5
|
+
# {PuppetForge::V3::Base} class to cache API responses.
|
6
|
+
class LruCache
|
7
|
+
# Takes a list of strings (or objects that respond to #to_s) and
|
8
|
+
# returns a SHA256 hash of the strings joined with colons. This is
|
9
|
+
# a convenience method for generating cache keys. Cache keys do not
|
10
|
+
# have to be SHA256 hashes, but they must be unique.
|
11
|
+
def self.new_key(*string_args)
|
12
|
+
Digest::SHA256.hexdigest(string_args.map(&:to_s).join(':'))
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Integer] the maximum number of items to cache.
|
16
|
+
attr_reader :max_size
|
17
|
+
|
18
|
+
# @param max_size [Integer] the maximum number of items to cache. This can
|
19
|
+
# be overridden by setting the PUPPET_FORGE_MAX_CACHE_SIZE environment
|
20
|
+
# variable.
|
21
|
+
def initialize(max_size = 30)
|
22
|
+
raise ArgumentError, "max_size must be a positive integer" unless max_size.is_a?(Integer) && max_size > 0
|
23
|
+
|
24
|
+
@max_size = ENV['PUPPET_FORGE_MAX_CACHE_SIZE'] ? ENV['PUPPET_FORGE_MAX_CACHE_SIZE'].to_i : max_size
|
25
|
+
@cache = {}
|
26
|
+
@lru = []
|
27
|
+
@semaphore = Mutex.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# Retrieves a value from the cache.
|
31
|
+
# @param key [Object] the key to look up in the cache
|
32
|
+
# @return [Object] the cached value for the given key, or nil if
|
33
|
+
# the key is not present in the cache.
|
34
|
+
def get(key)
|
35
|
+
if cache.key?(key)
|
36
|
+
semaphore.synchronize do
|
37
|
+
# If the key is present, move it to the front of the LRU
|
38
|
+
# list.
|
39
|
+
lru.delete(key)
|
40
|
+
lru.unshift(key)
|
41
|
+
end
|
42
|
+
cache[key]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Adds a value to the cache.
|
47
|
+
# @param key [Object] the key to add to the cache
|
48
|
+
# @param value [Object] the value to add to the cache
|
49
|
+
def put(key, value)
|
50
|
+
semaphore.synchronize do
|
51
|
+
if cache.key?(key)
|
52
|
+
# If the key is already present, delete it from the LRU list.
|
53
|
+
lru.delete(key)
|
54
|
+
elsif cache.size >= max_size
|
55
|
+
# If the cache is full, remove the least recently used item.
|
56
|
+
cache.delete(lru.pop)
|
57
|
+
end
|
58
|
+
# Add the key to the front of the LRU list and add the value
|
59
|
+
# to the cache.
|
60
|
+
lru.unshift(key)
|
61
|
+
cache[key] = value
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Clears the cache.
|
66
|
+
def clear
|
67
|
+
semaphore.synchronize do
|
68
|
+
cache.clear
|
69
|
+
lru.clear
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# Makes testing easier as these can be accessed directly with #send.
|
76
|
+
attr_reader :cache, :lru, :semaphore
|
77
|
+
end
|
78
|
+
end
|
data/lib/puppet_forge/v3/base.rb
CHANGED
@@ -4,6 +4,7 @@ require 'puppet_forge/error'
|
|
4
4
|
|
5
5
|
require 'puppet_forge/lazy_accessors'
|
6
6
|
require 'puppet_forge/lazy_relations'
|
7
|
+
require 'puppet_forge/lru_cache'
|
7
8
|
|
8
9
|
module PuppetForge
|
9
10
|
module V3
|
@@ -53,8 +54,22 @@ module PuppetForge
|
|
53
54
|
API_VERSION
|
54
55
|
end
|
55
56
|
|
57
|
+
# @private
|
58
|
+
def lru_cache
|
59
|
+
@lru_cache ||= PuppetForge::LruCache.new
|
60
|
+
end
|
61
|
+
|
62
|
+
# @private
|
63
|
+
def lru_cache_key(*args)
|
64
|
+
PuppetForge::LruCache.new_key(*args)
|
65
|
+
end
|
66
|
+
|
56
67
|
# @private
|
57
68
|
def request(resource, item = nil, params = {}, reset_connection = false, conn_opts = {})
|
69
|
+
cache_key = lru_cache_key(resource, item, params)
|
70
|
+
cached = lru_cache.get(cache_key)
|
71
|
+
return cached unless cached.nil?
|
72
|
+
|
58
73
|
conn(reset_connection, conn_opts) if reset_connection
|
59
74
|
unless conn.url_prefix.to_s =~ /^#{PuppetForge.host}/
|
60
75
|
conn.url_prefix = "#{PuppetForge.host}"
|
@@ -69,7 +84,9 @@ module PuppetForge
|
|
69
84
|
# The API expects a space separated string. This allows the user to invoke it with a more natural feeling array.
|
70
85
|
params['endorsements'] = params['endorsements'].join(' ') if params['endorsements'].is_a? Array
|
71
86
|
|
72
|
-
PuppetForge::V3::Base.conn.get uri_path, params
|
87
|
+
result = PuppetForge::V3::Base.conn.get uri_path, params
|
88
|
+
lru_cache.put(cache_key, result)
|
89
|
+
result
|
73
90
|
end
|
74
91
|
|
75
92
|
# @private
|
@@ -11,6 +11,11 @@ module PuppetForge
|
|
11
11
|
lazy :current_release, 'Release'
|
12
12
|
lazy_collection :releases, 'Release'
|
13
13
|
|
14
|
+
def self.find(slug)
|
15
|
+
super
|
16
|
+
rescue Faraday::ResourceNotFound
|
17
|
+
raise PuppetForge::ModuleNotFound, "Module #{slug} not found"
|
18
|
+
end
|
14
19
|
end
|
15
20
|
end
|
16
21
|
end
|
data/lib/puppet_forge/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -12,7 +12,10 @@ require 'puppet_forge'
|
|
12
12
|
|
13
13
|
module StubbingFaraday
|
14
14
|
|
15
|
-
def stub_api_for(klass, base_url = "http://api.example.com")
|
15
|
+
def stub_api_for(klass, base_url = "http://api.example.com", lru_cache: false)
|
16
|
+
unless lru_cache # Disable LRU cache by default
|
17
|
+
allow(klass).to receive(:lru_cache).and_return(instance_double('PuppetForge::LruCache', get: nil, put: nil, clear: nil))
|
18
|
+
end
|
16
19
|
allow(klass).to receive(:conn) do
|
17
20
|
Faraday.new :url => base_url do |builder|
|
18
21
|
builder.response(:json, :content_type => /\bjson$/, :parser_options => { :symbolize_names => true })
|
@@ -38,7 +41,7 @@ module StubbingFaraday
|
|
38
41
|
[ 404 ].tap do |response|
|
39
42
|
local = File.join(PROJECT_ROOT, 'spec', 'fixtures', xplatform_path)
|
40
43
|
|
41
|
-
if File.
|
44
|
+
if File.exist?("#{local}.headers") && File.exist?("#{local}.json")
|
42
45
|
File.open("#{local}.headers") do |file|
|
43
46
|
response[0] = file.readline[/\d{3}/].to_i
|
44
47
|
response[1] = headers = {}
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PuppetForge::LruCache do
|
4
|
+
it 'creates a cache key from a list of strings' do
|
5
|
+
expect { subject.class.new_key('foo', 'bar', 'baz') }.not_to raise_error
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'creates a new instance' do
|
9
|
+
expect { PuppetForge::LruCache.new(1) }.not_to raise_error
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'raises an error if max_size is not a positive integer' do
|
13
|
+
expect { PuppetForge::LruCache.new(-1) }.to raise_error(ArgumentError)
|
14
|
+
expect { PuppetForge::LruCache.new(0) }.to raise_error(ArgumentError)
|
15
|
+
expect { PuppetForge::LruCache.new(1.5) }.to raise_error(ArgumentError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'defaults to a max_size of 30' do
|
19
|
+
expect(PuppetForge::LruCache.new.max_size).to eq(30)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'allows max_size to be set via the max_size parameter' do
|
23
|
+
expect(PuppetForge::LruCache.new(42).max_size).to eq(42)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'provides a #get method' do
|
27
|
+
expect(PuppetForge::LruCache.new).to respond_to(:get)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'provides a #put method' do
|
31
|
+
expect(PuppetForge::LruCache.new).to respond_to(:put)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'provides a #clear method' do
|
35
|
+
expect(PuppetForge::LruCache.new).to respond_to(:clear)
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with environment variables' do
|
39
|
+
around(:each) do |example|
|
40
|
+
@old_max_size = ENV['PUPPET_FORGE_MAX_CACHE_SIZE']
|
41
|
+
ENV['PUPPET_FORGE_MAX_CACHE_SIZE'] = '42'
|
42
|
+
example.run
|
43
|
+
ENV['PUPPET_FORGE_MAX_CACHE_SIZE'] = @old_max_size
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'uses the value of the PUPPET_FORGE_MAX_CACHE_SIZE environment variable if present' do
|
47
|
+
expect(PuppetForge::LruCache.new.max_size).to eq(42)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context '#get' do
|
52
|
+
it 'returns nil if the key is not present in the cache' do
|
53
|
+
expect(PuppetForge::LruCache.new.get('foo')).to be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns the cached value for the given key' do
|
57
|
+
cache = PuppetForge::LruCache.new
|
58
|
+
cache.put('foo', 'bar')
|
59
|
+
expect(cache.get('foo')).to eq('bar')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'moves the key to the front of the LRU list' do
|
63
|
+
cache = PuppetForge::LruCache.new
|
64
|
+
cache.put('foo', 'bar')
|
65
|
+
cache.put('baz', 'qux')
|
66
|
+
cache.get('foo')
|
67
|
+
expect(cache.send(:lru)).to eq(['foo', 'baz'])
|
68
|
+
end
|
69
|
+
|
70
|
+
# The below test is non-deterministic but I'm not sure how to unit
|
71
|
+
# test thread-safety.
|
72
|
+
# it 'is thread-safe' do
|
73
|
+
# cache = PuppetForge::LruCache.new
|
74
|
+
# cache.put('foo', 'bar')
|
75
|
+
# cache.put('baz', 'qux')
|
76
|
+
# threads = []
|
77
|
+
# threads << Thread.new { 100.times { cache.get('foo') } }
|
78
|
+
# threads << Thread.new { 100.times { cache.get('baz') } }
|
79
|
+
# threads.each(&:join)
|
80
|
+
# expect(cache.send(:lru)).to eq(['baz', 'foo'])
|
81
|
+
# end
|
82
|
+
end
|
83
|
+
|
84
|
+
context '#put' do
|
85
|
+
it 'adds the key to the front of the LRU list' do
|
86
|
+
cache = PuppetForge::LruCache.new
|
87
|
+
cache.put('foo', 'bar')
|
88
|
+
expect(cache.send(:lru)).to eq(['foo'])
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'adds the value to the cache' do
|
92
|
+
cache = PuppetForge::LruCache.new
|
93
|
+
cache.put('foo', 'bar')
|
94
|
+
expect(cache.send(:cache)).to eq({ 'foo' => 'bar' })
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'removes the least recently used item if the cache is full' do
|
98
|
+
cache = PuppetForge::LruCache.new(2)
|
99
|
+
cache.put('foo', 'bar')
|
100
|
+
cache.put('baz', 'qux')
|
101
|
+
cache.put('quux', 'corge')
|
102
|
+
expect(cache.send(:lru)).to eq(['quux', 'baz'])
|
103
|
+
end
|
104
|
+
|
105
|
+
# The below test is non-deterministic but I'm not sure how to unit
|
106
|
+
# test thread-safety.
|
107
|
+
# it 'is thread-safe' do
|
108
|
+
# cache = PuppetForge::LruCache.new
|
109
|
+
# threads = []
|
110
|
+
# threads << Thread.new { 100.times { cache.put('foo', 'bar') } }
|
111
|
+
# threads << Thread.new { 100.times { cache.put('baz', 'qux') } }
|
112
|
+
# threads.each(&:join)
|
113
|
+
# expect(cache.send(:lru)).to eq(['baz', 'foo'])
|
114
|
+
# end
|
115
|
+
end
|
116
|
+
|
117
|
+
context '#clear' do
|
118
|
+
it 'clears the cache' do
|
119
|
+
cache = PuppetForge::LruCache.new
|
120
|
+
cache.put('foo', 'bar')
|
121
|
+
cache.put('baz', 'qux')
|
122
|
+
cache.clear
|
123
|
+
expect(cache.send(:lru).empty?).to be_truthy
|
124
|
+
expect(cache.send(:cache).empty?).to be_truthy
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -61,6 +61,7 @@ describe PuppetForge::V3::Base do
|
|
61
61
|
describe 'the host url setting' do
|
62
62
|
context 'without a path prefix' do
|
63
63
|
before(:each) do
|
64
|
+
PuppetForge::V3::Base.lru_cache.clear # We test the cache later, so clear it now
|
64
65
|
@orig_host = PuppetForge.host
|
65
66
|
PuppetForge.host = 'https://api.example.com'
|
66
67
|
|
@@ -83,10 +84,25 @@ describe PuppetForge::V3::Base do
|
|
83
84
|
base = PuppetForge::V3::Base.find 'puppet'
|
84
85
|
expect(base.username).to eq('foo')
|
85
86
|
end
|
87
|
+
|
88
|
+
it 'caches responses' do
|
89
|
+
stub_api_for(PuppetForge::V3::Base, lru_cache: true) do |stubs|
|
90
|
+
stub_fixture(stubs, :get, '/v3/bases/puppet')
|
91
|
+
end
|
92
|
+
allow(PuppetForge::V3::Base.lru_cache).to receive(:put).and_call_original
|
93
|
+
allow(PuppetForge::V3::Base.lru_cache).to receive(:get).and_call_original
|
94
|
+
|
95
|
+
PuppetForge::V3::Base.find 'puppet'
|
96
|
+
PuppetForge::V3::Base.find 'puppet'
|
97
|
+
PuppetForge::V3::Base.find 'puppet'
|
98
|
+
expect(PuppetForge::V3::Base.lru_cache).to have_received(:put).once
|
99
|
+
expect(PuppetForge::V3::Base.lru_cache).to have_received(:get).exactly(3).times
|
100
|
+
end
|
86
101
|
end
|
87
102
|
|
88
103
|
context 'with a path prefix' do
|
89
104
|
before(:each) do
|
105
|
+
PuppetForge::V3::Base.lru_cache.clear # We test the cache later, so clear it now
|
90
106
|
@orig_host = PuppetForge.host
|
91
107
|
PuppetForge.host = 'https://api.example.com/uri/prefix'
|
92
108
|
|
@@ -109,6 +125,20 @@ describe PuppetForge::V3::Base do
|
|
109
125
|
base = PuppetForge::V3::Base.find 'puppet'
|
110
126
|
expect(base.username).to eq('bar')
|
111
127
|
end
|
128
|
+
|
129
|
+
it 'caches responses' do
|
130
|
+
stub_api_for(PuppetForge::V3::Base, PuppetForge.host, lru_cache: true) do |stubs|
|
131
|
+
stub_fixture(stubs, :get, '/uri/prefix/v3/bases/puppet')
|
132
|
+
end
|
133
|
+
allow(PuppetForge::V3::Base.lru_cache).to receive(:put).and_call_original
|
134
|
+
allow(PuppetForge::V3::Base.lru_cache).to receive(:get).and_call_original
|
135
|
+
|
136
|
+
PuppetForge::V3::Base.find 'puppet'
|
137
|
+
PuppetForge::V3::Base.find 'puppet'
|
138
|
+
PuppetForge::V3::Base.find 'puppet'
|
139
|
+
expect(PuppetForge::V3::Base.lru_cache).to have_received(:put).once
|
140
|
+
expect(PuppetForge::V3::Base.lru_cache).to have_received(:get).exactly(3).times
|
141
|
+
end
|
112
142
|
end
|
113
143
|
end
|
114
144
|
end
|
@@ -28,8 +28,8 @@ describe PuppetForge::V3::Module do
|
|
28
28
|
expect(mod_stateless.name).to eq('apache')
|
29
29
|
end
|
30
30
|
|
31
|
-
it '
|
32
|
-
expect { missing_mod }.to raise_error(
|
31
|
+
it 'raises exception for non-existent modules' do
|
32
|
+
expect { missing_mod }.to raise_error(PuppetForge::ModuleNotFound, 'Module absent-apache not found')
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puppet_forge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet Labs
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -187,6 +187,7 @@ files:
|
|
187
187
|
- lib/puppet_forge/error.rb
|
188
188
|
- lib/puppet_forge/lazy_accessors.rb
|
189
189
|
- lib/puppet_forge/lazy_relations.rb
|
190
|
+
- lib/puppet_forge/lru_cache.rb
|
190
191
|
- lib/puppet_forge/tar.rb
|
191
192
|
- lib/puppet_forge/tar/mini.rb
|
192
193
|
- lib/puppet_forge/unpacker.rb
|
@@ -236,6 +237,7 @@ files:
|
|
236
237
|
- spec/unit/forge/connection_spec.rb
|
237
238
|
- spec/unit/forge/lazy_accessors_spec.rb
|
238
239
|
- spec/unit/forge/lazy_relations_spec.rb
|
240
|
+
- spec/unit/forge/lru_cache_spec.rb
|
239
241
|
- spec/unit/forge/tar/mini_spec.rb
|
240
242
|
- spec/unit/forge/tar_spec.rb
|
241
243
|
- spec/unit/forge/unpacker_spec.rb
|
@@ -251,7 +253,7 @@ homepage: https://github.com/puppetlabs/forge-ruby
|
|
251
253
|
licenses:
|
252
254
|
- Apache-2.0
|
253
255
|
metadata: {}
|
254
|
-
post_install_message:
|
256
|
+
post_install_message:
|
255
257
|
rdoc_options: []
|
256
258
|
require_paths:
|
257
259
|
- lib
|
@@ -266,8 +268,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
266
268
|
- !ruby/object:Gem::Version
|
267
269
|
version: '0'
|
268
270
|
requirements: []
|
269
|
-
rubygems_version: 3.
|
270
|
-
signing_key:
|
271
|
+
rubygems_version: 3.1.6
|
272
|
+
signing_key:
|
271
273
|
specification_version: 4
|
272
274
|
summary: Access the Puppet Forge API from Ruby for resource information and to download
|
273
275
|
releases.
|
@@ -308,6 +310,7 @@ test_files:
|
|
308
310
|
- spec/unit/forge/connection_spec.rb
|
309
311
|
- spec/unit/forge/lazy_accessors_spec.rb
|
310
312
|
- spec/unit/forge/lazy_relations_spec.rb
|
313
|
+
- spec/unit/forge/lru_cache_spec.rb
|
311
314
|
- spec/unit/forge/tar/mini_spec.rb
|
312
315
|
- spec/unit/forge/tar_spec.rb
|
313
316
|
- spec/unit/forge/unpacker_spec.rb
|