-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* clean rake task * Extract explaining method and improve style * Pass count, use explaining kwargs * Update output_path_test.rb * Fix mtime of files * Use different file names per test * Enforce mtime * Ensure files are removed after test Co-authored-by: David Heinemeier Hansson <david@basecamp.com>
- Loading branch information
1 parent
920730e
commit 5cd9716
Showing
4 changed files
with
146 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
require "propshaft/asset" | ||
|
||
class Propshaft::OutputPath | ||
attr_reader :path, :manifest | ||
|
||
def initialize(path, manifest) | ||
@path, @manifest = path, manifest | ||
end | ||
|
||
def clean(count, age) | ||
asset_versions = files.group_by { |_, attrs| attrs[:logical_path] } | ||
asset_versions.each do |logical_path, versions| | ||
current = manifest[logical_path] | ||
|
||
versions | ||
.reject { |path, _| current && path == current } | ||
.sort_by { |_, attrs| attrs[:mtime] } | ||
.reverse | ||
.each_with_index | ||
.drop_while { |(_, attrs), index| fresh_version_within_limit(attrs[:mtime], count, expires_at: age, limit: index) } | ||
.each { |(path, _), _| remove(path) } | ||
end | ||
end | ||
|
||
def files | ||
Hash.new.tap do |files| | ||
all_files_from_tree(path).each do |file| | ||
digested_path = file.relative_path_from(path) | ||
logical_path, digest = extract_path_and_digest(digested_path) | ||
|
||
files[digested_path.to_s] = { | ||
logical_path: logical_path.to_s, | ||
digest: digest, | ||
mtime: File.mtime(file) | ||
} | ||
end | ||
end | ||
end | ||
|
||
private | ||
def fresh_version_within_limit(mtime, count, expires_at:, limit:) | ||
modified_at = [ 0, Time.now - mtime ].max | ||
modified_at < expires_at || limit < count | ||
end | ||
|
||
def remove(path) | ||
FileUtils.rm(@path.join(path)) | ||
Propshaft.logger.info "Removed #{path}" | ||
end | ||
|
||
def all_files_from_tree(path) | ||
path.children.flat_map { |child| child.directory? ? all_files_from_tree(child) : child } | ||
end | ||
|
||
def extract_path_and_digest(digested_path) | ||
digest = digested_path.to_s[/-([0-9a-f]{7,128})\.(?!digested)[^.]+\z/, 1] | ||
path = digest ? digested_path.sub("-#{digest}", "") : digested_path | ||
|
||
[path, digest] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
require "test_helper" | ||
require "minitest/mock" | ||
require "propshaft/asset" | ||
require "propshaft/load_path" | ||
require "propshaft/output_path" | ||
|
||
class Propshaft::OutputPathTest < ActiveSupport::TestCase | ||
setup do | ||
@manifest = { | ||
".manifest.json": ".manifest.json", | ||
"one.txt": "one-f2e1ec14d6856e1958083094170ca6119c529a73.txt" | ||
}.stringify_keys | ||
@output_path = Propshaft::OutputPath.new(Pathname.new("#{__dir__}/../fixtures/output"), @manifest) | ||
end | ||
|
||
test "files" do | ||
files = @output_path.files | ||
|
||
file = files["one-f2e1ec14d6856e1958083094170ca6119c529a73.txt"] | ||
assert_equal "one.txt", file[:logical_path] | ||
assert_equal "f2e1ec14d6856e1958083094170ca6119c529a73", file[:digest] | ||
assert file[:mtime].is_a?(Time) | ||
end | ||
|
||
test "clean always keeps most current versions" do | ||
@output_path.clean(0, 0) | ||
assert @output_path.path.join(@manifest["one.txt"]) | ||
assert @output_path.path.join(@manifest[".manifest.json"]) | ||
end | ||
|
||
test "clean keeps versions of assets that no longer exist" do | ||
removed = output_asset("no-longer-in-manifest.txt", "current") | ||
@output_path.clean(1, 0) | ||
assert File.exists?(removed) | ||
ensure | ||
FileUtils.rm(removed) if File.exists?(removed) | ||
end | ||
|
||
test "clean keeps the correct number of versions" do | ||
old = output_asset("by_count.txt", "old", created_at: Time.now - 300) | ||
current = output_asset("by_count.txt", "current", created_at: Time.now - 180) | ||
|
||
@output_path.clean(1, 0) | ||
|
||
assert File.exists?(current) | ||
assert_not File.exists?(old) | ||
ensure | ||
FileUtils.rm(old) if File.exists?(old) | ||
FileUtils.rm(current) if File.exists?(current) | ||
end | ||
|
||
test "clean keeps all versions under a certain age" do | ||
old = output_asset("by_age.txt", "old") | ||
current = output_asset("by_age.txt", "current") | ||
|
||
@output_path.clean(0, 3600) | ||
|
||
assert File.exists?(current) | ||
assert File.exists?(old) | ||
ensure | ||
FileUtils.rm(old) if File.exists?(old) | ||
FileUtils.rm(current) if File.exists?(current) | ||
end | ||
|
||
private | ||
def output_asset(filename, content, created_at: Time.now) | ||
asset = Propshaft::Asset.new(nil, logical_path: filename) | ||
asset.stub :content, content do | ||
output_path = @output_path.path.join(asset.digested_path) | ||
`touch -mt #{created_at.strftime('%y%m%d%H%M')} #{output_path}` | ||
output_path | ||
end | ||
end | ||
end |