indexmap 0.8.2 → 0.9.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/CHANGELOG.md +65 -0
- data/README.md +287 -35
- data/UPGRADE.md +361 -0
- data/docs/architecture.md +55 -0
- data/docs/deployment.md +223 -0
- data/docs/domain-terms.md +27 -0
- data/docs/dynamic-sitemaps.md +302 -0
- data/docs/search-engine-ping.md +368 -0
- data/docs/storage.md +94 -0
- data/lib/generators/indexmap/install_generator.rb +1 -1
- data/lib/generators/indexmap/templates/initializer.rb.tt +18 -18
- data/lib/indexmap/artifact.rb +9 -0
- data/lib/indexmap/configuration.rb +12 -1
- data/lib/indexmap/creator.rb +45 -42
- data/lib/indexmap/index_now_configuration.rb +3 -1
- data/lib/indexmap/output.rb +19 -5
- data/lib/indexmap/parser.rb +112 -52
- data/lib/indexmap/pinger/base.rb +6 -5
- data/lib/indexmap/pinger/google.rb +12 -8
- data/lib/indexmap/pinger/index_now.rb +97 -52
- data/lib/indexmap/publication.rb +89 -0
- data/lib/indexmap/robots.rb +67 -0
- data/lib/indexmap/robots_controller.rb +13 -0
- data/lib/indexmap/sitemaps_controller.rb +57 -0
- data/lib/indexmap/storage/active_storage.rb +132 -1
- data/lib/indexmap/storage/filesystem.rb +182 -11
- data/lib/indexmap/storage/memory.rb +1 -1
- data/lib/indexmap/task_runner.rb +21 -28
- data/lib/indexmap/validator/duplicates.rb +83 -0
- data/lib/indexmap/validator.rb +171 -59
- data/lib/indexmap/version.rb +1 -1
- data/lib/indexmap/writer.rb +89 -34
- data/lib/indexmap.rb +14 -1
- data/lib/tasks/indexmap_tasks.rake +21 -2
- data/test/indexmap/correctness_test.rb +153 -0
- data/test/indexmap/install_generator_test.rb +36 -4
- data/test/indexmap/parser_test.rb +77 -0
- data/test/indexmap/pinger/google_test.rb +22 -0
- data/test/indexmap/pinger/index_now_test.rb +168 -0
- data/test/indexmap/publication_test.rb +280 -0
- data/test/indexmap/robots_test.rb +73 -0
- data/test/indexmap/sitemaps_controller_test.rb +50 -0
- data/test/indexmap/storage_test.rb +27 -0
- data/test/indexmap/task_runner_test.rb +24 -0
- data/test/indexmap/validator_test.rb +14 -0
- data/test/indexmap/writer_test.rb +42 -0
- data/test/integration/queue_checks.rb +244 -0
- data/test/release_task_test.rb +153 -0
- metadata +22 -5
- data/lib/indexmap/storage/file.rb +0 -11
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "indexmap"
|
|
5
|
+
require "timeout"
|
|
6
|
+
require "rbconfig"
|
|
7
|
+
|
|
8
|
+
class QueueChecks < Minitest::Test
|
|
9
|
+
def setup
|
|
10
|
+
Indexmap::HostConfiguration.apply
|
|
11
|
+
PublicPage.delete_all
|
|
12
|
+
5.times { |id| PublicPage.create!(slug: "page-#{id}", published: true) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_good_job_executes_database_projection_and_surfaces_validation_failure
|
|
16
|
+
IntegrationRefreshJob.queue_adapter = :good_job
|
|
17
|
+
job = IntegrationRefreshJob.perform_later
|
|
18
|
+
GoodJob.perform_inline
|
|
19
|
+
assert GoodJob::Job.find(job.provider_job_id).finished_at
|
|
20
|
+
assert_equal 5, Indexmap::Parser.new.entries.size
|
|
21
|
+
previous = Indexmap.configuration.storage.read("sitemap.xml")
|
|
22
|
+
PublicPage.create!(slug: "page-0", published: true)
|
|
23
|
+
IntegrationRefreshJob.perform_later
|
|
24
|
+
assert_raises(Indexmap::ValidationError) { GoodJob.perform_inline }
|
|
25
|
+
assert_equal previous, Indexmap.configuration.storage.read("sitemap.xml")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_solid_queue_worker_executes_and_records_failed_generation
|
|
29
|
+
IntegrationRefreshJob.queue_adapter = :solid_queue
|
|
30
|
+
reader, writer = IO.pipe
|
|
31
|
+
pid = Process.spawn({"INDEXMAP_ADAPTER" => "solid_queue"}, RbConfig.ruby, "bin/jobs",
|
|
32
|
+
chdir: Rails.root, out: writer, err: writer, pgroup: true)
|
|
33
|
+
writer.close
|
|
34
|
+
job = IntegrationRefreshJob.perform_later
|
|
35
|
+
wait_for(reader) { SolidQueue::Job.find_by(active_job_id: job.job_id)&.finished_at }
|
|
36
|
+
assert_equal 5, Indexmap::Parser.new.entries.size
|
|
37
|
+
previous = Indexmap.configuration.storage.read("sitemap.xml")
|
|
38
|
+
PublicPage.create!(slug: "page-0", published: true)
|
|
39
|
+
failed = IntegrationRefreshJob.perform_later
|
|
40
|
+
wait_for(reader) { SolidQueue::FailedExecution.joins(:job).where(solid_queue_jobs: {active_job_id: failed.job_id}).exists? }
|
|
41
|
+
assert_equal previous, Indexmap.configuration.storage.read("sitemap.xml")
|
|
42
|
+
ensure
|
|
43
|
+
if pid
|
|
44
|
+
Process.kill("TERM", -pid)
|
|
45
|
+
Timeout.timeout(15) { Process.wait(pid) }
|
|
46
|
+
end
|
|
47
|
+
reader&.close
|
|
48
|
+
writer&.close unless writer&.closed?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_active_storage_atomic_publication_and_retention
|
|
52
|
+
config = Indexmap.configuration
|
|
53
|
+
config.storage = Indexmap::Storage::ActiveStorage.new(model: SitemapArtifact, public_url: config.base_url)
|
|
54
|
+
first = Indexmap.create
|
|
55
|
+
previous = config.storage.read("sitemap.xml")
|
|
56
|
+
SitemapArtifact.find_by!(filename: "sitemap.xml").file.blob.update!(created_at: 4.days.ago)
|
|
57
|
+
revision = config.storage.revision("sitemap.xml")
|
|
58
|
+
config.storage.define_singleton_method(:open) do |filename, &block|
|
|
59
|
+
raise "Root body must not be downloaded during activation" if filename == "sitemap.xml"
|
|
60
|
+
|
|
61
|
+
super(filename, &block)
|
|
62
|
+
end
|
|
63
|
+
second = Indexmap.create
|
|
64
|
+
config.storage.singleton_class.remove_method(:open)
|
|
65
|
+
refute_equal first, second
|
|
66
|
+
assert_empty config.storage.prune(root: "sitemap.xml", before: 2.days.ago)
|
|
67
|
+
assert first.all? { |filename| config.storage.exist?(filename) }
|
|
68
|
+
assert_raises(Indexmap::SupersededPublication) do
|
|
69
|
+
Dir.mktmpdir do |directory|
|
|
70
|
+
staged = Indexmap::Storage::Filesystem.new(path: directory)
|
|
71
|
+
staged.write("sitemap.xml", previous)
|
|
72
|
+
config.storage.publish(staged, files: ["sitemap.xml"], root: "sitemap.xml", expected: revision)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
assert Indexmap::Validator.new(configuration: config).validate!
|
|
76
|
+
config.storage.prune(root: "sitemap.xml", before: Time.now.utc + 1)
|
|
77
|
+
assert (first - ["sitemap.xml"]).none? { |filename| config.storage.exist?(filename) }
|
|
78
|
+
assert second.all? { |filename| config.storage.exist?(filename) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_active_storage_rejects_stale_builds_without_uploading
|
|
82
|
+
config = active_storage_configuration("stale.xml")
|
|
83
|
+
Indexmap.create(configuration: config)
|
|
84
|
+
expected = config.storage.revision(config.index_filename)
|
|
85
|
+
Indexmap.create(configuration: config)
|
|
86
|
+
blobs = ActiveStorage::Blob.order(:id).pluck(:id)
|
|
87
|
+
assert_raises(Indexmap::SupersededPublication) do
|
|
88
|
+
config.storage.publish(nil, files: [], root: config.index_filename, expected: expected)
|
|
89
|
+
end
|
|
90
|
+
assert_equal blobs, ActiveStorage::Blob.order(:id).pluck(:id)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_active_storage_cleans_partial_uploads_synchronously
|
|
94
|
+
config = active_storage_configuration("failed-upload.xml")
|
|
95
|
+
Indexmap.create(configuration: config)
|
|
96
|
+
previous = config.storage.read(config.index_filename)
|
|
97
|
+
blobs = ActiveStorage::Blob.order(:id).pluck(:id)
|
|
98
|
+
records = SitemapArtifact.order(:id).pluck(:id)
|
|
99
|
+
service = ActiveStorage::Blob.service
|
|
100
|
+
uploaded = []
|
|
101
|
+
service.define_singleton_method(:upload) do |key, io, **options|
|
|
102
|
+
super(key, io, **options)
|
|
103
|
+
uploaded << key
|
|
104
|
+
raise IOError, "upload interrupted after object write" if uploaded.size == 2
|
|
105
|
+
end
|
|
106
|
+
assert_raises(IOError) { Indexmap.create(configuration: config) }
|
|
107
|
+
assert_equal previous, config.storage.read(config.index_filename)
|
|
108
|
+
assert_equal blobs, ActiveStorage::Blob.order(:id).pluck(:id)
|
|
109
|
+
assert_equal records, SitemapArtifact.order(:id).pluck(:id)
|
|
110
|
+
assert uploaded.none? { |key| service.exist?(key) }
|
|
111
|
+
assert Indexmap::Validator.new(configuration: config).validate!
|
|
112
|
+
ensure
|
|
113
|
+
service&.singleton_class&.remove_method(:upload)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def test_active_storage_cleans_a_losing_build_without_pruning
|
|
117
|
+
config = active_storage_configuration("race.xml")
|
|
118
|
+
storage = config.storage
|
|
119
|
+
baseline = ActiveStorage::Blob.pluck(:id)
|
|
120
|
+
lost_files = []
|
|
121
|
+
lost_keys = []
|
|
122
|
+
winner = nil
|
|
123
|
+
storage.define_singleton_method(:publish) do |source, **options|
|
|
124
|
+
lost_files.concat(options.fetch(:files).reject { |name| name == options.fetch(:root) })
|
|
125
|
+
source.define_singleton_method(:open) do |filename, &block|
|
|
126
|
+
if filename == options.fetch(:root)
|
|
127
|
+
lost_keys.concat(ActiveStorage::Blob.where.not(id: baseline).pluck(:key))
|
|
128
|
+
competing = Indexmap::Configuration.new
|
|
129
|
+
competing.base_url = config.base_url
|
|
130
|
+
competing.index_filename = config.index_filename
|
|
131
|
+
competing.atomic = true
|
|
132
|
+
competing.sections = config.sections
|
|
133
|
+
competing.storage = Indexmap::Storage::ActiveStorage.new(model: SitemapArtifact, public_url: config.base_url)
|
|
134
|
+
winner = Indexmap.create(configuration: competing)
|
|
135
|
+
end
|
|
136
|
+
super(filename, &block)
|
|
137
|
+
end
|
|
138
|
+
super(source, **options)
|
|
139
|
+
end
|
|
140
|
+
assert_raises(Indexmap::SupersededPublication) { Indexmap.create(configuration: config) }
|
|
141
|
+
assert_equal winner.size, ActiveStorage::Blob.where.not(id: baseline).count
|
|
142
|
+
assert lost_files.none? { |filename| storage.exist?(filename) }
|
|
143
|
+
assert lost_keys.none? { |key| ActiveStorage::Blob.service.exist?(key) }
|
|
144
|
+
assert winner.all? { |filename| storage.exist?(filename) }
|
|
145
|
+
assert Indexmap::Validator.new(configuration: config).validate!
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def test_active_storage_does_not_activate_children_deleted_by_pruning
|
|
149
|
+
config = active_storage_configuration("prune-race.xml")
|
|
150
|
+
Indexmap.create(configuration: config)
|
|
151
|
+
previous = config.storage.read(config.index_filename)
|
|
152
|
+
blobs = ActiveStorage::Blob.pluck(:id).sort
|
|
153
|
+
config.storage.define_singleton_method(:publish) do |source, **options|
|
|
154
|
+
storage = self
|
|
155
|
+
source.define_singleton_method(:open) do |filename, &block|
|
|
156
|
+
storage.prune(root: options.fetch(:root), before: Time.now.utc + 1) if filename == options.fetch(:root)
|
|
157
|
+
super(filename, &block)
|
|
158
|
+
end
|
|
159
|
+
super(source, **options)
|
|
160
|
+
end
|
|
161
|
+
assert_raises(Indexmap::SupersededPublication) { Indexmap.create(configuration: config) }
|
|
162
|
+
assert_equal previous, config.storage.read(config.index_filename)
|
|
163
|
+
assert_equal blobs, ActiveStorage::Blob.pluck(:id).sort
|
|
164
|
+
assert Indexmap::Validator.new(configuration: config).validate!
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def test_active_storage_preserves_a_commit_when_a_host_callback_raises
|
|
168
|
+
config = active_storage_configuration("callback.xml")
|
|
169
|
+
callback = proc do |record|
|
|
170
|
+
if record.filename == "callback.xml" && record.file.attached?
|
|
171
|
+
raise IOError, "host callback failed after commit"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
SitemapArtifact.set_callback(:commit, :after, callback)
|
|
175
|
+
assert_raises(IOError) { Indexmap.create(configuration: config) }
|
|
176
|
+
assert config.storage.exist?(config.index_filename)
|
|
177
|
+
assert Indexmap::Validator.new(configuration: config).validate!
|
|
178
|
+
ensure
|
|
179
|
+
SitemapArtifact.skip_callback(:commit, :after, callback) if callback
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def test_active_storage_reports_cleanup_failures_and_attempts_remaining_objects
|
|
183
|
+
config = active_storage_configuration("cleanup-error.xml")
|
|
184
|
+
Indexmap.create(configuration: config)
|
|
185
|
+
previous = config.storage.read(config.index_filename)
|
|
186
|
+
service = ActiveStorage::Blob.service
|
|
187
|
+
uploaded = []
|
|
188
|
+
attempted = []
|
|
189
|
+
service.define_singleton_method(:upload) do |key, io, **options|
|
|
190
|
+
super(key, io, **options)
|
|
191
|
+
uploaded << key
|
|
192
|
+
raise IOError, "original upload failure" if uploaded.size == 2
|
|
193
|
+
end
|
|
194
|
+
service.define_singleton_method(:delete) do |key|
|
|
195
|
+
attempted << key
|
|
196
|
+
raise IOError, "cleanup unavailable" if key == uploaded.first
|
|
197
|
+
|
|
198
|
+
super(key)
|
|
199
|
+
end
|
|
200
|
+
error = assert_raises(Indexmap::PublicationCleanupError) { Indexmap.create(configuration: config) }
|
|
201
|
+
assert_equal "original upload failure", error.cause.message
|
|
202
|
+
assert_equal [uploaded.first], error.failures.map { |failure| failure.fetch(:artifact) }
|
|
203
|
+
assert_equal uploaded.sort, attempted.sort
|
|
204
|
+
assert_equal previous, config.storage.read(config.index_filename)
|
|
205
|
+
assert Indexmap::Validator.new(configuration: config).validate!
|
|
206
|
+
ensure
|
|
207
|
+
if service
|
|
208
|
+
service.singleton_class.remove_method(:upload)
|
|
209
|
+
service.singleton_class.remove_method(:delete)
|
|
210
|
+
uploaded&.each { |key| service.delete(key) }
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def test_active_storage_publishes_atomic_single_file_outputs
|
|
215
|
+
config = active_storage_configuration("single.xml")
|
|
216
|
+
config.format = :single_file
|
|
217
|
+
config.entries = [{loc: "https://example.com/page"}]
|
|
218
|
+
assert_equal ["single.xml"], Indexmap.create(configuration: config)
|
|
219
|
+
assert_equal ["single.xml"], Indexmap.create(configuration: config)
|
|
220
|
+
assert Indexmap::Validator.new(configuration: config).validate!
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
private
|
|
224
|
+
|
|
225
|
+
def active_storage_configuration(root)
|
|
226
|
+
Indexmap.configuration.tap do |config|
|
|
227
|
+
config.index_filename = root
|
|
228
|
+
config.storage = Indexmap::Storage::ActiveStorage.new(model: SitemapArtifact, public_url: config.base_url)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def wait_for(reader)
|
|
233
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30
|
|
234
|
+
log = +""
|
|
235
|
+
until yield
|
|
236
|
+
raise "Worker did not reach expected state: #{log[-2000, 2000]}" if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
|
|
237
|
+
|
|
238
|
+
if IO.select([reader], nil, nil, 0.1)
|
|
239
|
+
chunk = reader.read_nonblock(4096, exception: false)
|
|
240
|
+
log << chunk if chunk.is_a?(String)
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
data/test/release_task_test.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "test_helper"
|
|
4
4
|
require "rake"
|
|
5
|
+
require "open3"
|
|
6
|
+
require "rbconfig"
|
|
5
7
|
|
|
6
8
|
unless respond_to?(:release_version, true)
|
|
7
9
|
load File.expand_path("../Rakefile", __dir__)
|
|
@@ -74,8 +76,159 @@ class ReleaseTaskTest < Minitest::Test
|
|
|
74
76
|
)
|
|
75
77
|
end
|
|
76
78
|
|
|
79
|
+
def test_release_preparation_requires_main
|
|
80
|
+
["release/0.9.0", "master", ""].each do |branch|
|
|
81
|
+
error = assert_raises(ArgumentError) { validate_release_branch!(branch) }
|
|
82
|
+
assert_match(/Prepare releases from main/, error.message)
|
|
83
|
+
end
|
|
84
|
+
assert_nil validate_release_branch!("main")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_prepare_commits_and_pushes_from_main_with_release_hook_exception
|
|
88
|
+
with_release_repository do |repo, origin|
|
|
89
|
+
before = git!(repo, "rev-parse", "HEAD").strip
|
|
90
|
+
history = File.read(File.join(repo, "CHANGELOG.md"))
|
|
91
|
+
hook = File.join(repo, ".git/hooks/pre-commit")
|
|
92
|
+
File.write(hook, "#!/bin/sh\nexec ruby script/check_commit_branch.rb\n")
|
|
93
|
+
FileUtils.chmod(0o755, hook)
|
|
94
|
+
|
|
95
|
+
stdout, stderr, status = run_release_task(repo, "release:prepare[minor]")
|
|
96
|
+
assert status.success?, stdout + stderr
|
|
97
|
+
expected = release_version("minor")
|
|
98
|
+
refute_equal before, git!(repo, "rev-parse", "HEAD").strip
|
|
99
|
+
assert_equal git!(repo, "rev-parse", "HEAD"), git!(origin, "rev-parse", "main")
|
|
100
|
+
assert_equal "CHANGELOG.md\nlib/indexmap/version.rb\n",
|
|
101
|
+
git!(repo, "diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD")
|
|
102
|
+
assert_includes File.read(File.join(repo, "lib/indexmap/version.rb")), expected
|
|
103
|
+
assert_includes File.read(File.join(repo, "CHANGELOG.md")), "## [#{expected}]"
|
|
104
|
+
assert File.read(File.join(repo, "CHANGELOG.md")).end_with?(history[history.index("## [")..])
|
|
105
|
+
message = git!(repo, "log", "-1", "--format=%B")
|
|
106
|
+
assert_includes message, "chore(release): prepare v#{expected}"
|
|
107
|
+
%w[What: Why: Where:].each { |heading| assert_includes message, heading }
|
|
108
|
+
assert_empty git!(repo, "status", "--porcelain")
|
|
109
|
+
assert_empty git!(repo, "tag", "--list")
|
|
110
|
+
assert_empty git!(origin, "tag", "--list")
|
|
111
|
+
assert_includes stdout, "release:tag"
|
|
112
|
+
|
|
113
|
+
stdout, stderr, status = run_release_task(repo, "release:tag")
|
|
114
|
+
assert status.success?, stdout + stderr
|
|
115
|
+
assert_equal git!(repo, "rev-parse", "HEAD"), git!(origin, "rev-parse", "v#{expected}^{}")
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_prepare_keeps_commit_hooks_enabled_and_does_not_push_when_they_fail
|
|
120
|
+
with_release_repository do |repo, origin|
|
|
121
|
+
before = git!(origin, "rev-parse", "main")
|
|
122
|
+
hook = File.join(repo, ".git/hooks/commit-msg")
|
|
123
|
+
File.write(hook, "#!/bin/sh\necho 'commit hook rejected release' >&2\nexit 1\n")
|
|
124
|
+
FileUtils.chmod(0o755, hook)
|
|
125
|
+
|
|
126
|
+
stdout, stderr, status = run_release_task(repo, "release:prepare[minor]")
|
|
127
|
+
refute status.success?, stdout + stderr
|
|
128
|
+
assert_includes stderr, "commit hook rejected release"
|
|
129
|
+
assert_equal before, git!(repo, "rev-parse", "HEAD")
|
|
130
|
+
assert_equal before, git!(origin, "rev-parse", "main")
|
|
131
|
+
assert_empty git!(origin, "tag", "--list")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_branch_hook_rejects_ordinary_main_commits_even_with_release_flag
|
|
136
|
+
with_release_repository do |repo, _origin|
|
|
137
|
+
File.write(File.join(repo, "unrelated.txt"), "unrelated")
|
|
138
|
+
git!(repo, "add", "unrelated.txt")
|
|
139
|
+
[nil, "1"].each do |flag|
|
|
140
|
+
_stdout, stderr, status = Open3.capture3(
|
|
141
|
+
{"INDEXMAP_RELEASE_COMMIT" => flag},
|
|
142
|
+
RbConfig.ruby, "script/check_commit_branch.rb", chdir: repo
|
|
143
|
+
)
|
|
144
|
+
refute status.success?
|
|
145
|
+
assert_includes stderr, "Direct commits to 'main' are not allowed"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def test_tag_pushes_only_the_prepared_merged_commit
|
|
151
|
+
with_release_repository do |repo, origin|
|
|
152
|
+
before = git!(repo, "rev-parse", "HEAD").strip
|
|
153
|
+
stdout, stderr, status = run_release_task(repo, "release:tag")
|
|
154
|
+
assert status.success?, stdout + stderr
|
|
155
|
+
assert_equal before, git!(repo, "rev-parse", "HEAD").strip
|
|
156
|
+
assert_equal before, git!(repo, "rev-parse", "v#{Indexmap::VERSION}^{}").strip
|
|
157
|
+
assert_includes git!(origin, "tag", "--list"), "v#{Indexmap::VERSION}"
|
|
158
|
+
assert_empty git!(repo, "status", "--porcelain")
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def test_prepare_rejects_topic_and_detached_branches_without_changing_files
|
|
163
|
+
with_release_repository do |repo, origin|
|
|
164
|
+
before = git!(repo, "rev-parse", "HEAD").strip
|
|
165
|
+
git!(repo, "checkout", "-b", "release/next")
|
|
166
|
+
["release/next", before].each do |ref|
|
|
167
|
+
git!(repo, "checkout", ref)
|
|
168
|
+
stdout, stderr, status = run_release_task(repo, "release:prepare[minor]")
|
|
169
|
+
refute status.success?, stdout + stderr
|
|
170
|
+
assert_includes stderr, "Prepare releases from main"
|
|
171
|
+
assert_equal before, git!(repo, "rev-parse", "HEAD").strip
|
|
172
|
+
assert_empty git!(repo, "status", "--porcelain")
|
|
173
|
+
assert_empty git!(origin, "tag", "--list")
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def test_tag_rejects_main_that_has_not_been_merged_to_origin
|
|
179
|
+
with_release_repository do |repo, origin|
|
|
180
|
+
File.write(File.join(repo, "unmerged.txt"), "unmerged")
|
|
181
|
+
git!(repo, "add", "unmerged.txt")
|
|
182
|
+
git!(repo, "commit", "-m", "test: unmerged change")
|
|
183
|
+
%w[release:prepare[minor] release:tag].each do |task|
|
|
184
|
+
stdout, stderr, status = run_release_task(repo, task)
|
|
185
|
+
refute status.success?, stdout + stderr
|
|
186
|
+
assert_includes stderr, "Local main must match origin/main"
|
|
187
|
+
assert_empty git!(repo, "status", "--porcelain")
|
|
188
|
+
end
|
|
189
|
+
assert_empty git!(repo, "tag", "--list")
|
|
190
|
+
assert_empty git!(origin, "tag", "--list")
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
77
194
|
private
|
|
78
195
|
|
|
196
|
+
def with_release_repository
|
|
197
|
+
Dir.mktmpdir("indexmap-release-test") do |directory|
|
|
198
|
+
repo = File.join(directory, "repo")
|
|
199
|
+
origin = File.join(directory, "origin.git")
|
|
200
|
+
FileUtils.mkdir_p([repo, origin, File.join(repo, "lib/indexmap")])
|
|
201
|
+
git!(origin, "init", "--bare")
|
|
202
|
+
git!(repo, "init", "-b", "main")
|
|
203
|
+
git!(repo, "config", "user.name", "Release Test")
|
|
204
|
+
git!(repo, "config", "user.email", "release@example.test")
|
|
205
|
+
%w[Rakefile indexmap.gemspec CHANGELOG.md cliff.toml lib/indexmap/version.rb script/check_commit_branch.rb].each do |file|
|
|
206
|
+
FileUtils.mkdir_p(File.dirname(File.join(repo, file)))
|
|
207
|
+
FileUtils.cp(File.expand_path("../#{file}", __dir__), File.join(repo, file))
|
|
208
|
+
end
|
|
209
|
+
git!(repo, "add", ".")
|
|
210
|
+
git!(repo, "commit", "-m", "test: prepare release fixture")
|
|
211
|
+
git!(repo, "remote", "add", "origin", origin)
|
|
212
|
+
git!(repo, "push", "-u", "origin", "main")
|
|
213
|
+
yield repo, origin
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def git!(directory, *args)
|
|
218
|
+
stdout, stderr, status = Open3.capture3("git", *args, chdir: directory)
|
|
219
|
+
raise stderr unless status.success?
|
|
220
|
+
|
|
221
|
+
stdout
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def run_release_task(directory, task)
|
|
225
|
+
Open3.capture3(
|
|
226
|
+
{"BUNDLE_GEMFILE" => File.expand_path(ENV.fetch("BUNDLE_GEMFILE", "Gemfile"))},
|
|
227
|
+
RbConfig.ruby, "-rrake", "-e", 'load "Rakefile"; Rake.application.invoke_task(ARGV.fetch(0))', task,
|
|
228
|
+
chdir: directory
|
|
229
|
+
)
|
|
230
|
+
end
|
|
231
|
+
|
|
79
232
|
def local_release_tag_exists?(_tag)
|
|
80
233
|
@local_release_tag_exists || false
|
|
81
234
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: indexmap
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Paulo Fidalgo
|
|
@@ -175,9 +175,17 @@ files:
|
|
|
175
175
|
- CHANGELOG.md
|
|
176
176
|
- LICENSE.txt
|
|
177
177
|
- README.md
|
|
178
|
+
- UPGRADE.md
|
|
179
|
+
- docs/architecture.md
|
|
180
|
+
- docs/deployment.md
|
|
181
|
+
- docs/domain-terms.md
|
|
182
|
+
- docs/dynamic-sitemaps.md
|
|
183
|
+
- docs/search-engine-ping.md
|
|
184
|
+
- docs/storage.md
|
|
178
185
|
- lib/generators/indexmap/install_generator.rb
|
|
179
186
|
- lib/generators/indexmap/templates/initializer.rb.tt
|
|
180
187
|
- lib/indexmap.rb
|
|
188
|
+
- lib/indexmap/artifact.rb
|
|
181
189
|
- lib/indexmap/configuration.rb
|
|
182
190
|
- lib/indexmap/creator.rb
|
|
183
191
|
- lib/indexmap/entry.rb
|
|
@@ -188,28 +196,37 @@ files:
|
|
|
188
196
|
- lib/indexmap/pinger/base.rb
|
|
189
197
|
- lib/indexmap/pinger/google.rb
|
|
190
198
|
- lib/indexmap/pinger/index_now.rb
|
|
199
|
+
- lib/indexmap/publication.rb
|
|
191
200
|
- lib/indexmap/railtie.rb
|
|
201
|
+
- lib/indexmap/robots.rb
|
|
202
|
+
- lib/indexmap/robots_controller.rb
|
|
192
203
|
- lib/indexmap/section.rb
|
|
204
|
+
- lib/indexmap/sitemaps_controller.rb
|
|
193
205
|
- lib/indexmap/storage/active_storage.rb
|
|
194
|
-
- lib/indexmap/storage/file.rb
|
|
195
206
|
- lib/indexmap/storage/filesystem.rb
|
|
196
207
|
- lib/indexmap/storage/memory.rb
|
|
197
208
|
- lib/indexmap/task_runner.rb
|
|
198
209
|
- lib/indexmap/tasks.rb
|
|
199
210
|
- lib/indexmap/validator.rb
|
|
211
|
+
- lib/indexmap/validator/duplicates.rb
|
|
200
212
|
- lib/indexmap/version.rb
|
|
201
213
|
- lib/indexmap/writer.rb
|
|
202
214
|
- lib/tasks/indexmap_tasks.rake
|
|
203
215
|
- test/indexmap/configuration_test.rb
|
|
216
|
+
- test/indexmap/correctness_test.rb
|
|
204
217
|
- test/indexmap/install_generator_test.rb
|
|
205
218
|
- test/indexmap/lazy_loading_test.rb
|
|
206
219
|
- test/indexmap/parser_test.rb
|
|
207
220
|
- test/indexmap/pinger/google_test.rb
|
|
208
221
|
- test/indexmap/pinger/index_now_test.rb
|
|
222
|
+
- test/indexmap/publication_test.rb
|
|
223
|
+
- test/indexmap/robots_test.rb
|
|
224
|
+
- test/indexmap/sitemaps_controller_test.rb
|
|
209
225
|
- test/indexmap/storage_test.rb
|
|
210
226
|
- test/indexmap/task_runner_test.rb
|
|
211
227
|
- test/indexmap/validator_test.rb
|
|
212
228
|
- test/indexmap/writer_test.rb
|
|
229
|
+
- test/integration/queue_checks.rb
|
|
213
230
|
- test/release_task_test.rb
|
|
214
231
|
- test/test_helper.rb
|
|
215
232
|
homepage: https://www.ethos-link.com/opensource/indexmap
|
|
@@ -219,8 +236,8 @@ metadata:
|
|
|
219
236
|
homepage_uri: https://www.ethos-link.com/opensource/indexmap
|
|
220
237
|
source_code_uri: https://github.com/ethos-link/indexmap
|
|
221
238
|
bug_tracker_uri: https://github.com/ethos-link/indexmap/issues
|
|
222
|
-
changelog_uri: https://github.com/ethos-link/indexmap/blob/
|
|
223
|
-
documentation_uri: https://github.com/ethos-link/indexmap/blob/
|
|
239
|
+
changelog_uri: https://github.com/ethos-link/indexmap/blob/v0.9.0/CHANGELOG.md
|
|
240
|
+
documentation_uri: https://github.com/ethos-link/indexmap/blob/v0.9.0/README.md
|
|
224
241
|
funding_uri: https://www.reviato.com/
|
|
225
242
|
github_repo: ssh://github.com/ethos-link/indexmap
|
|
226
243
|
allowed_push_host: https://rubygems.org
|
|
@@ -239,7 +256,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
239
256
|
- !ruby/object:Gem::Version
|
|
240
257
|
version: '0'
|
|
241
258
|
requirements: []
|
|
242
|
-
rubygems_version: 4.0.
|
|
259
|
+
rubygems_version: 4.0.20
|
|
243
260
|
specification_version: 4
|
|
244
261
|
summary: Generate sitemap indexes and child sitemaps with plain Ruby
|
|
245
262
|
test_files: []
|