2 # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
4 # See LICENSE.txt for permissions.
8 require File.join(File.expand_path(File.dirname(__FILE__)), 'gemutilities')
9 require 'rubygems/source_index'
10 require 'rubygems/config_file'
12 class Gem::SourceIndex
13 public :fetcher, :fetch_bulk_index, :fetch_quick_index,
14 :find_missing, :gems, :remove_extra,
15 :update_with_missing, :unzip
18 class TestGemSourceIndex < RubyGemTestCase
23 util_setup_fake_fetcher
26 def test_create_from_directory
31 assert_equal @fetcher, @source_index.fetcher
34 def test_fetch_bulk_index_compressed
35 util_setup_bulk_fetch true
38 fetched_index = @source_index.fetch_bulk_index @uri
39 assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
40 @c1_2.full_name].sort,
41 fetched_index.gems.map { |n,s| n }.sort
44 paths = @fetcher.paths
46 assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift
48 assert paths.empty?, paths.join(', ')
51 def test_fetch_bulk_index_error
52 @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}.Z"] = proc { raise SocketError }
53 @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = proc { raise SocketError }
54 @fetcher.data["#{@gem_repo}/yaml.Z"] = proc { raise SocketError }
55 @fetcher.data["#{@gem_repo}/yaml"] = proc { raise SocketError }
57 e = assert_raise Gem::RemoteSourceException do
59 @source_index.fetch_bulk_index @uri
63 paths = @fetcher.paths
65 assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift
66 assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}", paths.shift
67 assert_equal "#{@gem_repo}/yaml.Z", paths.shift
68 assert_equal "#{@gem_repo}/yaml", paths.shift
70 assert paths.empty?, paths.join(', ')
72 assert_equal 'Error fetching remote gem cache: SocketError',
76 def test_fetch_bulk_index_fallback
77 @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}.Z"] =
78 proc { raise SocketError }
79 @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] =
80 proc { raise SocketError }
81 @fetcher.data["#{@gem_repo}/yaml.Z"] = proc { raise SocketError }
82 @fetcher.data["#{@gem_repo}/yaml"] = @source_index.to_yaml
85 fetched_index = @source_index.fetch_bulk_index @uri
86 assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
87 @c1_2.full_name].sort,
88 fetched_index.gems.map { |n,s| n }.sort
91 paths = @fetcher.paths
93 assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift
94 assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}", paths.shift
95 assert_equal "#{@gem_repo}/yaml.Z", paths.shift
96 assert_equal "#{@gem_repo}/yaml", paths.shift
98 assert paths.empty?, paths.join(', ')
101 def test_fetch_bulk_index_marshal_mismatch
102 marshal = @source_index.dump
103 marshal[0] = (Marshal::MAJOR_VERSION - 1).chr
105 @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = marshal
106 @fetcher.data["#{@gem_repo}/yaml"] = @source_index.to_yaml
109 fetched_index = @source_index.fetch_bulk_index @uri
110 assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
111 @c1_2.full_name].sort,
112 fetched_index.gems.map { |n,s| n }.sort
115 paths = @fetcher.paths
117 assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift
118 assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}", paths.shift
119 assert_equal "#{@gem_repo}/yaml.Z", paths.shift
120 assert_equal "#{@gem_repo}/yaml", paths.shift
122 assert paths.empty?, paths.join(', ')
125 def test_fetch_bulk_index_uncompressed
126 util_setup_bulk_fetch false
128 fetched_index = @source_index.fetch_bulk_index @uri
129 assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
130 @c1_2.full_name].sort,
131 fetched_index.gems.map { |n,s| n }.sort
134 paths = @fetcher.paths
136 assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift
137 assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}", paths.shift
139 assert paths.empty?, paths.join(', ')
142 def test_fetch_quick_index
143 index = util_zip @gem_names
144 latest_index = util_zip [@a2.full_name, @b2.full_name].join("\n")
146 @fetcher.data["#{@gem_repo}/quick/index.rz"] = index
147 @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = latest_index
149 quick_index = @source_index.fetch_quick_index @uri, false
150 assert_equal [@a2.full_name, @b2.full_name].sort,
153 paths = @fetcher.paths
155 assert_equal "#{@gem_repo}/quick/latest_index.rz", paths.shift
157 assert paths.empty?, paths.join(', ')
160 def test_fetch_quick_index_all
161 index = util_zip @gem_names
162 latest_index = util_zip [@a2.full_name, @b2.full_name].join("\n")
164 @fetcher.data["#{@gem_repo}/quick/index.rz"] = index
165 @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = latest_index
167 quick_index = @source_index.fetch_quick_index @uri, true
168 assert_equal [@a1.full_name, @a2.full_name, @b2.full_name].sort,
171 paths = @fetcher.paths
173 assert_equal "#{@gem_repo}/quick/index.rz", paths.shift
175 assert paths.empty?, paths.join(', ')
178 def test_fetch_quick_index_error
179 @fetcher.data["#{@gem_repo}/quick/index.rz"] =
180 proc { raise Exception }
182 e = assert_raise Gem::OperationNotSupportedError do
183 @source_index.fetch_quick_index @uri, true
186 assert_equal 'No quick index found: Exception', e.message
188 paths = @fetcher.paths
190 assert_equal "#{@gem_repo}/quick/index.rz", paths.shift
192 assert paths.empty?, paths.join(', ')
195 def test_fetch_quick_index_fallback
196 index = util_zip @gem_names
198 @fetcher.data["#{@gem_repo}/quick/index.rz"] = index
200 quick_index = @source_index.fetch_quick_index @uri, false
201 assert_equal @gem_names.split, quick_index.sort
203 paths = @fetcher.paths
205 assert_equal "#{@gem_repo}/quick/latest_index.rz", paths.shift
206 assert_equal "#{@gem_repo}/quick/index.rz", paths.shift
208 assert paths.empty?, paths.join(', ')
211 def test_fetch_quick_index_subdir
212 latest_index = util_zip [@a2.full_name, @b2.full_name].join("\n")
213 repo = URI.parse "#{@gem_repo}/~nobody/mirror/"
215 @fetcher.data["#{repo}quick/latest_index.rz"] = latest_index
217 quick_index = @source_index.fetch_quick_index repo, false
218 assert_equal [@a2.full_name, @b2.full_name].sort,
221 paths = @fetcher.paths
223 assert_equal "#{repo}quick/latest_index.rz", paths.shift
225 assert paths.empty?, paths.join(', ')
228 def test_fetch_single_spec
229 a1_spec_url = "#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz"
230 @fetcher.data[a1_spec_url] = util_zip Marshal.dump(@a1)
232 spec = @source_index.send :fetch_single_spec, URI.parse(@gem_repo),
235 assert_equal @a1.full_name, spec.full_name
237 paths = @fetcher.paths
239 assert_equal a1_spec_url, paths.shift
241 assert paths.empty?, paths.join(', ')
244 def test_fetch_single_spec_subdir
245 repo = URI.parse "#{@gem_repo}/~nobody/mirror/"
247 a1_spec_url = "#{repo}quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz"
248 @fetcher.data[a1_spec_url] = util_zip Marshal.dump(@a1)
250 spec = @source_index.send :fetch_single_spec, repo, @a1.full_name
252 assert_equal @a1.full_name, spec.full_name
254 paths = @fetcher.paths
256 assert_equal a1_spec_url, paths.shift
258 assert paths.empty?, paths.join(', ')
261 def test_fetch_single_spec_yaml
262 a1_spec_url = "#{@gem_repo}/quick/#{@a1.full_name}.gemspec.rz"
263 @fetcher.data[a1_spec_url] = util_zip @a1.to_yaml
265 repo = URI.parse @gem_repo
267 spec = @source_index.send :fetch_single_spec, repo, @a1.full_name
269 assert_equal @a1.full_name, spec.full_name
271 paths = @fetcher.paths
273 assert_equal "#{@gem_repo}/quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz", paths.shift
274 assert_equal a1_spec_url, paths.shift
276 assert paths.empty?, paths.join(', ')
279 def test_fetch_single_spec_yaml_subdir
280 repo = URI.parse "#{@gem_repo}/~nobody/mirror/"
282 a1_spec_url = "#{repo}quick/#{@a1.full_name}.gemspec.rz"
283 @fetcher.data[a1_spec_url] = util_zip @a1.to_yaml
285 spec = @source_index.send :fetch_single_spec, repo, @a1.full_name
287 assert_equal @a1.full_name, spec.full_name
289 paths = @fetcher.paths
291 assert_equal "#{repo}quick/Marshal.#{Gem.marshal_version}/#{@a1.full_name}.gemspec.rz", paths.shift
292 assert_equal a1_spec_url, paths.shift
294 assert paths.empty?, paths.join(', ')
297 def test_find_missing
298 missing = @source_index.find_missing [@b2.full_name]
299 assert_equal [@b2.full_name], missing
302 def test_find_missing_none_missing
303 missing = @source_index.find_missing [
304 @a1.full_name, @a2.full_name, @c1_2.full_name
307 assert_equal [], missing
310 def test_latest_specs
311 p1_ruby = quick_gem 'p', '1'
312 p1_platform = quick_gem 'p', '1' do |spec|
313 spec.platform = Gem::Platform::CURRENT
316 a1_platform = quick_gem @a1.name, (@a1.version) do |s|
317 s.platform = Gem::Platform.new 'x86-my_platform1'
320 a2_platform = quick_gem @a2.name, (@a2.version) do |s|
321 s.platform = Gem::Platform.new 'x86-my_platform1'
324 a2_platform_other = quick_gem @a2.name, (@a2.version) do |s|
325 s.platform = Gem::Platform.new 'x86-other_platform1'
328 a3_platform_other = quick_gem @a2.name, (@a2.version.bump) do |s|
329 s.platform = Gem::Platform.new 'x86-other_platform1'
332 @source_index.add_spec p1_ruby
333 @source_index.add_spec p1_platform
334 @source_index.add_spec a1_platform
335 @source_index.add_spec a2_platform
336 @source_index.add_spec a2_platform_other
337 @source_index.add_spec a3_platform_other
341 a2_platform.full_name,
342 a3_platform_other.full_name,
346 p1_platform.full_name,
349 latest_specs = @source_index.latest_specs.map { |s| s.full_name }.sort
351 assert_equal expected, latest_specs
354 def test_load_gems_in
355 spec_dir1 = File.join @gemhome, 'specifications'
356 spec_dir2 = File.join @tempdir, 'gemhome2', 'specifications'
358 FileUtils.rm_r spec_dir1
360 FileUtils.mkdir_p spec_dir1
361 FileUtils.mkdir_p spec_dir2
363 a1 = quick_gem 'a', '1' do |spec| spec.author = 'author 1' end
364 a2 = quick_gem 'a', '1' do |spec| spec.author = 'author 2' end
366 File.open File.join(spec_dir1, "#{a1.full_name}.gemspec"), 'w' do |fp|
370 File.open File.join(spec_dir2, "#{a2.full_name}.gemspec"), 'w' do |fp|
374 @source_index.load_gems_in spec_dir1, spec_dir2
376 assert_equal a1.author, @source_index.specification(a1.full_name).author
380 util_setup_source_info_cache
382 assert_equal [], @source_index.outdated
384 updated = quick_gem @a2.name, (@a2.version.bump)
385 util_setup_source_info_cache updated
387 assert_equal [updated.name], @source_index.outdated
389 updated_platform = quick_gem @a2.name, (updated.version.bump) do |s|
390 s.platform = Gem::Platform.new 'x86-other_platform1'
393 util_setup_source_info_cache updated, updated_platform
395 assert_equal [updated_platform.name], @source_index.outdated
398 def test_refresh_bang
399 a1_spec = File.join @gemhome, "specifications", "#{@a1.full_name}.gemspec"
401 FileUtils.mv a1_spec, @tempdir
403 source_index = Gem::SourceIndex.from_installed_gems
405 assert !source_index.gems.include?(@a1.full_name)
407 FileUtils.mv File.join(@tempdir, "#{@a1.full_name}.gemspec"), a1_spec
409 source_index.refresh!
411 assert source_index.gems.include?(@a1.full_name)
414 def test_remove_extra
415 @source_index.add_spec @a1
416 @source_index.add_spec @a2
417 @source_index.add_spec @pl1
419 @source_index.remove_extra [@a1.full_name, @pl1.full_name]
421 assert_equal [@a1.full_name],
422 @source_index.gems.map { |n,s| n }.sort
425 def test_remove_extra_no_changes
426 gems = [@a1.full_name, @a2.full_name]
427 @source_index.add_spec @a1
428 @source_index.add_spec @a2
430 @source_index.remove_extra gems
432 assert_equal gems, @source_index.gems.map { |n,s| n }.sort
436 assert_equal [@a1, @a2, @a_evil9], @source_index.search('a')
437 assert_equal [@a2], @source_index.search('a', '= 2')
439 assert_equal [], @source_index.search('bogusstring')
440 assert_equal [], @source_index.search('a', '= 3')
442 source_index = Gem::SourceIndex.new
443 source_index.add_spec @a1
444 source_index.add_spec @a2
446 assert_equal [@a1], source_index.search(@a1.name, '= 1')
448 r1 = Gem::Requirement.create '= 1'
449 assert_equal [@a1], source_index.search(@a1.name, r1)
451 dep = Gem::Dependency.new @a1.name, r1
452 assert_equal [@a1], source_index.search(dep)
455 def test_search_empty_cache
456 empty_source_index = Gem::SourceIndex.new({})
457 assert_equal [], empty_source_index.search("foo")
460 def test_search_platform
461 util_set_arch 'x86-my_platform1'
463 a1 = quick_gem 'a', '1'
464 a1_mine = quick_gem 'a', '1' do |s|
465 s.platform = Gem::Platform.new 'x86-my_platform1'
467 a1_other = quick_gem 'a', '1' do |s|
468 s.platform = Gem::Platform.new 'x86-other_platform1'
471 si = Gem::SourceIndex.new(a1.full_name => a1, a1_mine.full_name => a1_mine,
472 a1_other.full_name => a1_other)
474 dep = Gem::Dependency.new 'a', Gem::Requirement.new('1')
476 gems = si.search dep, true
478 assert_equal [a1, a1_mine], gems.sort
482 sig = @source_index.gem_signature('foo-1.2.3')
483 assert_equal 64, sig.length
484 assert_match(/^[a-f0-9]{64}$/, sig)
487 def test_specification
488 assert_equal @a1, @source_index.specification(@a1.full_name)
490 assert_nil @source_index.specification("foo-1.2.4")
493 def test_index_signature
494 sig = @source_index.index_signature
495 assert_match(/^[a-f0-9]{64}$/, sig)
499 input = "x\234+\316\317MU(I\255(\001\000\021\350\003\232"
500 assert_equal 'some text', @source_index.unzip(input)
504 util_setup_bulk_fetch true
506 @source_index.gems.replace({})
507 assert_equal [], @source_index.gems.keys.sort
510 @source_index.update @uri, true
512 assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
514 @source_index.gems.keys.sort
517 paths = @fetcher.paths
519 assert_equal "#{@gem_repo}/quick/index.rz", paths.shift
520 assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift
522 assert paths.empty?, paths.join(', ')
525 def test_update_incremental
526 old_gem_conf = Gem.configuration
527 Gem.configuration = Gem::ConfigFile.new([])
529 latest_names = [@a2, @a_evil9, @b2, @c1_2].map { |s| s.full_name }
530 latest_index = util_zip latest_names.join("\n")
531 @fetcher.data["#{@gem_repo}/quick/latest_index.rz"] = latest_index
533 marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}",
534 "#{@b2.full_name}.gemspec.rz"
535 @fetcher.data[marshal_uri] = util_zip Marshal.dump(@b2)
538 @source_index.update @uri, false
540 assert_equal latest_names, @source_index.gems.keys.sort
543 paths = @fetcher.paths
544 assert_equal "#{@gem_repo}/quick/latest_index.rz", paths.shift
545 assert_equal marshal_uri, paths.shift
547 assert paths.empty?, paths.join(', ')
549 Gem.configuration = old_gem_conf
552 def test_update_incremental_all
553 old_gem_conf = Gem.configuration
554 Gem.configuration = Gem::ConfigFile.new([])
556 quick_index = util_zip @all_gem_names.join("\n")
557 @fetcher.data["#{@gem_repo}/quick/index.rz"] = quick_index
559 marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}",
560 "#{@b2.full_name}.gemspec.rz"
561 @fetcher.data[marshal_uri] = util_zip Marshal.dump(@b2)
564 @source_index.update @uri, true
566 assert_equal @all_gem_names, @source_index.gems.keys.sort
569 paths = @fetcher.paths
570 assert_equal "#{@gem_repo}/quick/index.rz", paths.shift
571 assert_equal marshal_uri, paths.shift
573 assert paths.empty?, paths.join(', ')
575 Gem.configuration = old_gem_conf
578 def test_update_incremental_fallback
579 old_gem_conf = Gem.configuration
580 Gem.configuration = Gem::ConfigFile.new([])
582 quick_index = util_zip @all_gem_names.join("\n")
583 @fetcher.data["#{@gem_repo}/quick/index.rz"] = quick_index
585 marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}",
586 "#{@b2.full_name}.gemspec.rz"
588 yaml_uri = "#{@gem_repo}/quick/#{@b2.full_name}.gemspec.rz"
589 @fetcher.data[yaml_uri] = util_zip @b2.to_yaml
592 @source_index.update @uri, true
594 assert_equal @all_gem_names, @source_index.gems.keys.sort
597 paths = @fetcher.paths
598 assert_equal "#{@gem_repo}/quick/index.rz", paths.shift
599 assert_equal marshal_uri, paths.shift
600 assert_equal yaml_uri, paths.shift
602 assert paths.empty?, paths.join(', ')
604 Gem.configuration = old_gem_conf
607 def test_update_incremental_marshal_mismatch
608 old_gem_conf = Gem.configuration
609 Gem.configuration = Gem::ConfigFile.new([])
611 quick_index = util_zip @all_gem_names.join("\n")
612 @fetcher.data["#{@gem_repo}/quick/index.rz"] = quick_index
614 marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}",
615 "#{@b2.full_name}.gemspec.rz"
616 marshal_data = Marshal.dump(@b2)
617 marshal_data[0] = (Marshal::MAJOR_VERSION - 1).chr
618 @fetcher.data[marshal_uri] = util_zip marshal_data
620 yaml_uri = "#{@gem_repo}/quick/#{@b2.full_name}.gemspec.rz"
621 @fetcher.data[yaml_uri] = util_zip @b2.to_yaml
624 @source_index.update @uri, true
626 assert_equal @all_gem_names, @source_index.gems.keys.sort
629 paths = @fetcher.paths
630 assert_equal "#{@gem_repo}/quick/index.rz", paths.shift
631 assert_equal marshal_uri, paths.shift
632 assert_equal yaml_uri, paths.shift
634 assert paths.empty?, paths.join(', ')
636 Gem.configuration = old_gem_conf
639 def test_update_subdir
640 @gem_repo = @gem_repo + "/subdir"
642 util_setup_bulk_fetch true
644 @source_index.gems.replace({})
645 assert_equal [], @source_index.gems.keys.sort
647 uri = @uri.to_s + "/subdir"
650 @source_index.update uri, true
652 assert_equal [@a1.full_name, @a2.full_name, @a_evil9.full_name,
654 @source_index.gems.keys.sort
657 paths = @fetcher.paths
659 assert_equal "#{@gem_repo}/quick/index.rz", paths.shift
660 assert_equal "#{@gem_repo}/Marshal.#{@marshal_version}.Z", paths.shift
662 assert paths.empty?, paths.join(', ')
665 def test_update_with_missing
666 marshal_uri = File.join @gem_repo, "quick", "Marshal.#{@marshal_version}",
667 "#{@c1_2.full_name}.gemspec.rz"
668 dumped = Marshal.dump @c1_2
669 @fetcher.data[marshal_uri] = util_zip(dumped)
672 @source_index.update_with_missing @uri, [@c1_2.full_name]
675 spec = @source_index.specification(@c1_2.full_name)
676 # We don't care about the equality of undumped attributes
677 @c1_2.files = spec.files
678 @c1_2.loaded_from = spec.loaded_from
680 assert_equal @c1_2, spec
683 def util_setup_bulk_fetch(compressed)
684 source_index = @source_index.dump
687 @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}.Z"] = util_zip source_index
689 @fetcher.data["#{@gem_repo}/Marshal.#{@marshal_version}"] = source_index