# ==================================================================== # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ==================================================================== require "tempfile" require "my-assertions" require "util" require "svn/core" require "svn/fs" require "svn/repos" require "svn/client" class SvnReposTest < Test::Unit::TestCase include SvnTestUtil def setup setup_basic end def teardown teardown_basic end def test_version assert_equal(Svn::Core.subr_version, Svn::Repos.version) end def test_path assert_equal(@repos_path, @repos.path) assert_equal(File.join(@repos_path, "db"), @repos.db_env) assert_equal(File.join(@repos_path, "conf"), @repos.conf_dir) assert_equal(File.join(@repos_path, "conf", "svnserve.conf"), @repos.svnserve_conf) locks_dir = File.join(@repos_path, "locks") assert_equal(locks_dir, @repos.lock_dir) assert_equal(File.join(locks_dir, "db.lock"), @repos.db_lockfile) assert_equal(File.join(locks_dir, "db-logs.lock"), @repos.db_logs_lockfile) hooks_dir = File.join(@repos_path, "hooks") assert_equal(hooks_dir, @repos.hook_dir) assert_equal(File.join(hooks_dir, "start-commit"), @repos.start_commit_hook) assert_equal(File.join(hooks_dir, "pre-commit"), @repos.pre_commit_hook) assert_equal(File.join(hooks_dir, "post-commit"), @repos.post_commit_hook) assert_equal(File.join(hooks_dir, "pre-revprop-change"), @repos.pre_revprop_change_hook) assert_equal(File.join(hooks_dir, "post-revprop-change"), @repos.post_revprop_change_hook) assert_equal(File.join(hooks_dir, "pre-lock"), @repos.pre_lock_hook) assert_equal(File.join(hooks_dir, "post-lock"), @repos.post_lock_hook) assert_equal(File.join(hooks_dir, "pre-unlock"), @repos.pre_unlock_hook) assert_equal(File.join(hooks_dir, "post-unlock"), @repos.post_unlock_hook) search_path = @repos_path assert_equal(@repos_path, Svn::Repos.find_root_path(search_path)) search_path = "#{@repos_path}/XXX" assert_equal(@repos_path, Svn::Repos.find_root_path(search_path)) search_path = "not-found" assert_equal(nil, Svn::Repos.find_root_path(search_path)) end def test_create tmp_repos_path = File.join(@tmp_path, "repos") fs_type = Svn::Fs::TYPE_FSFS fs_config = {Svn::Fs::CONFIG_FS_TYPE => fs_type} repos = nil Svn::Repos.create(tmp_repos_path, {}, fs_config) do |repos| assert(File.exist?(tmp_repos_path)) fs_type_path = File.join(repos.fs.path, Svn::Fs::CONFIG_FS_TYPE) assert_equal(fs_type, File.open(fs_type_path) {|f| f.read.chop}) repos.fs.set_warning_func(&warning_func) end assert(repos.closed?) assert_raises(Svn::Error::ReposAlreadyClose) do repos.fs end Svn::Repos.delete(tmp_repos_path) assert(!File.exist?(tmp_repos_path)) end def test_logs log1 = "sample log1" log2 = "sample log2" log3 = "sample log3" file = "file" src = "source" props = {"myprop" => "value"} path = File.join(@wc_path, file) ctx1 = make_context(log1) File.open(path, "w") {|f| f.print(src)} ctx1.add(path) info1 = ctx1.ci(@wc_path) start_rev = info1.revision ctx2 = make_context(log2) File.open(path, "a") {|f| f.print(src)} info2 = ctx2.ci(@wc_path) ctx3 = make_context(log3) File.open(path, "a") {|f| f.print(src)} props.each do |key, value| ctx3.prop_set(key, value, path) end info3 = ctx3.ci(@wc_path) end_rev = info3.revision logs = @repos.logs(file, start_rev, end_rev, end_rev - start_rev + 1) logs = logs.collect do |changed_paths, revision, author, date, message| paths = {} changed_paths.each do |key, changed_path| paths[key] = changed_path.action end [paths, revision, author, date, message] end assert_equal([ [ {"/#{file}" => "A"}, info1.revision, @author, info1.date, log1, ], [ {"/#{file}" => "M"}, info2.revision, @author, info2.date, log2, ], [ {"/#{file}" => "M"}, info3.revision, @author, info3.date, log3, ], ], logs) revs = [] args = [file, start_rev, end_rev] @repos.file_revs(*args) do |path, rev, rev_props, prop_diffs| hashed_prop_diffs = {} prop_diffs.each do |prop| hashed_prop_diffs[prop.name] = prop.value end revs << [path, rev, hashed_prop_diffs] end assert_equal([ ["/#{file}", info1.revision, {}], ["/#{file}", info2.revision, {}], ["/#{file}", info3.revision, props], ], revs) revs = [] @repos.file_revs2(*args) do |path, rev, rev_props, prop_diffs| revs << [path, rev, prop_diffs] end assert_equal([ ["/#{file}", info1.revision, {}], ["/#{file}", info2.revision, {}], ["/#{file}", info3.revision, props], ], revs) rev, date, author = @repos.fs.root.committed_info("/") assert_equal(info3.revision, rev) assert_equal(info3.date, date) assert_equal(info3.author, author) ensure ctx3.destroy unless ctx3.nil? ctx2.destroy unless ctx2.nil? ctx1.destroy unless ctx1.nil? end def test_hotcopy log = "sample log" file = "hello.txt" path = File.join(@wc_path, file) FileUtils.touch(path) # So we can later rename files when running the tests on # Windows, close access to the repos created by the test setup. test_repos_path = @repos.path @repos.close @repos = nil @fs.close @fs = nil rev = make_context(log) do |ctx| ctx.add(path) commit_info = ctx.commit(@wc_path) rev = commit_info.revision assert_equal(log, ctx.log_message(path, rev)) rev end dest_path = File.join(@tmp_path, "dest") backup_path = File.join(@tmp_path, "back") config = {} fs_config = {} dest_repos = Svn::Repos.create(dest_path, config, fs_config) dest_repos.fs.set_warning_func(&warning_func) dest_repos_path = dest_repos.path dest_repos.close FileUtils.mv(test_repos_path, backup_path) FileUtils.mv(dest_repos_path, test_repos_path) make_context(log) do |ctx| assert_raises(Svn::Error::FsNoSuchRevision) do assert_equal(log, ctx.log_message(path, rev)) end FileUtils.rm_r(test_repos_path) Svn::Repos.hotcopy(backup_path, test_repos_path) assert_equal(log, ctx.log_message(path, rev)) end end def assert_transaction log = "sample log" make_context(log) do |ctx| ctx.checkout(@repos_uri, @wc_path) ctx.mkdir(["#{@wc_path}/new_dir"]) prev_rev = @repos.youngest_rev past_date = Time.now args = { :author => @author, :log => log, :revision => prev_rev, } callback = Proc.new do |txn| txn.abort end yield(:commit, @repos, args, callback) assert_equal(prev_rev, @repos.youngest_rev) assert_equal(prev_rev, @repos.dated_revision(past_date)) prev_rev = @repos.youngest_rev @repos.transaction_for_commit(@author, log) do |txn| end assert_equal(prev_rev + 1, @repos.youngest_rev) assert_equal(prev_rev, @repos.dated_revision(past_date)) assert_equal(prev_rev + 1, @repos.dated_revision(Time.now)) prev_rev = @repos.youngest_rev args = { :author => @author, :revision => prev_rev, } callback = Proc.new do |txn| end yield(:update, @repos, args, callback) assert_equal(prev_rev, @repos.youngest_rev) end end def test_transaction assert_transaction do |type, repos, args, callback| case type when :commit repos.transaction_for_commit(args[:author], args[:log], &callback) when :update repos.transaction_for_update(args[:author], &callback) end end end def test_transaction_with_revision assert_transaction do |type, repos, args, callback| case type when :commit repos.transaction_for_commit(args[:author], args[:log], args[:revision], &callback) when :update repos.transaction_for_update(args[:author], args[:revision], &callback) end end end def test_transaction2 assert_transaction do |type, repos, args, callback| case type when :commit props = { Svn::Core::PROP_REVISION_AUTHOR => args[:author], Svn::Core::PROP_REVISION_LOG => args[:log], } repos.transaction_for_commit(props, &callback) when :update repos.transaction_for_update(args[:author], &callback) end end end def test_transaction2_with_revision assert_transaction do |type, repos, args, callback| case type when :commit props = { Svn::Core::PROP_REVISION_AUTHOR => args[:author], Svn::Core::PROP_REVISION_LOG => args[:log], } repos.transaction_for_commit(props, args[:revision], &callback) when :update repos.transaction_for_update(args[:author], args[:revision], &callback) end end end def test_trace_node_locations file1 = "file1" file2 = "file2" file3 = "file3" path1 = File.join(@wc_path, file1) path2 = File.join(@wc_path, file2) path3 = File.join(@wc_path, file3) log = "sample log" make_context(log) do |ctx| FileUtils.touch(path1) ctx.add(path1) rev1 = ctx.ci(@wc_path).revision ctx.mv(path1, path2) rev2 = ctx.ci(@wc_path).revision ctx.cp(path2, path3) rev3 = ctx.ci(@wc_path).revision assert_equal({ rev1 => "/#{file1}", rev2 => "/#{file2}", rev3 => "/#{file2}", }, @repos.fs.trace_node_locations("/#{file2}", [rev1, rev2, rev3])) end end def assert_report file = "file" fs_base = "/" path = File.join(@wc_path, file) source = "sample source" log = "sample log" make_context(log) do |ctx| File.open(path, "w") {|f| f.print(source)} ctx.add(path) rev = ctx.ci(@wc_path).revision assert_equal(Svn::Core::NODE_FILE, @repos.fs.root.stat(file).kind) editor = TestEditor.new args = { :revision => rev, :user_name => @author, :fs_base => fs_base, :target => "", :target_path => nil, :editor => editor, :text_deltas => true, :recurse => true, :ignore_ancestry => false, } callback = Proc.new do |baton| end yield(@repos, args, callback) editor_seq = editor.sequence.collect{|meth, *args| meth} # Delete change_file_prop() and change_dir_prop() calls from the # actual editor sequence, as they can vary in ways not easily # determined by a black-box test author. editor_seq.delete(:change_file_prop) editor_seq.delete(:change_dir_prop) assert_equal([ :set_target_revision, :open_root, :close_directory, :close_edit, ], editor_seq) end end def test_report assert_report do |repos, args, callback| @repos.report(args[:revision], args[:user_name], args[:fs_base], args[:target], args[:target_path], args[:editor], args[:text_deltas], args[:recurse], args[:ignore_ancestry], &callback) end end def test_report2 assert_report do |repos, args, callback| if args[:recurse] depth = Svn::Core::DEPTH_INFINITY else depth = Svn::Core::DEPTH_FILES end @repos.report2(args[:revision], args[:fs_base], args[:target], args[:target_path], args[:editor], args[:text_deltas], args[:ignore_ancestry], depth, &callback) end end def assert_commit_editor trunk = "trunk" tags = "tags" tags_sub = "sub" file = "file" source = "sample source" user = "user" log_message = "log" trunk_dir_path = File.join(@wc_path, trunk) tags_dir_path = File.join(@wc_path, tags) tags_sub_dir_path = File.join(tags_dir_path, tags_sub) trunk_path = File.join(trunk_dir_path, file) tags_path = File.join(tags_dir_path, file) tags_sub_path = File.join(tags_sub_dir_path, file) trunk_repos_uri = "#{@repos_uri}/#{trunk}" rev1 = @repos.youngest_rev commit_callback_result = {} args = { :repos_url => @repos_uri, :base_path => "/", :user => user, :log_message => log_message, } editor = yield(@repos, commit_callback_result, args) root_baton = editor.open_root(rev1) dir_baton = editor.add_directory(trunk, root_baton, nil, rev1) file_baton = editor.add_file("#{trunk}/#{file}", dir_baton, nil, -1) ret = editor.apply_textdelta(file_baton, nil) ret.send(source) editor.close_edit assert_equal(rev1 + 1, @repos.youngest_rev) assert_equal({ :revision => @repos.youngest_rev, :date => @repos.prop(Svn::Core::PROP_REVISION_DATE), :author => user, }, commit_callback_result) rev2 = @repos.youngest_rev make_context("") do |ctx| ctx.up(@wc_path) assert_equal(source, File.open(trunk_path) {|f| f.read}) commit_callback_result = {} editor = yield(@repos, commit_callback_result, args) root_baton = editor.open_root(rev2) dir_baton = editor.add_directory(tags, root_baton, nil, rev2) subdir_baton = editor.add_directory("#{tags}/#{tags_sub}", dir_baton, trunk_repos_uri, rev2) editor.close_edit assert_equal(rev2 + 1, @repos.youngest_rev) assert_equal({ :revision => @repos.youngest_rev, :date => @repos.prop(Svn::Core::PROP_REVISION_DATE), :author => user, }, commit_callback_result) rev3 = @repos.youngest_rev ctx.up(@wc_path) assert_equal([ ["/#{tags}/#{tags_sub}/#{file}", rev3], ["/#{trunk}/#{file}", rev2], ], @repos.fs.history("#{tags}/#{tags_sub}/#{file}", rev1, rev3, rev2)) commit_callback_result = {} editor = yield(@repos, commit_callback_result, args) root_baton = editor.open_root(rev3) dir_baton = editor.delete_entry(tags, rev3, root_baton) editor.close_edit assert_equal({ :revision => @repos.youngest_rev, :date => @repos.prop(Svn::Core::PROP_REVISION_DATE), :author => user, }, commit_callback_result) ctx.up(@wc_path) assert(!File.exist?(tags_path)) end end def test_commit_editor assert_commit_editor do |receiver, commit_callback_result, args| commit_callback = Proc.new do |revision, date, author| commit_callback_result[:revision] = revision commit_callback_result[:date] = date commit_callback_result[:author] = author end receiver.commit_editor(args[:repos_url], args[:base_path], args[:txn], args[:user], args[:log_message], commit_callback) end end def test_commit_editor2 assert_commit_editor do |receiver, commit_callback_result, args| commit_callback = Proc.new do |info| commit_callback_result[:revision] = info.revision commit_callback_result[:date] = info.date commit_callback_result[:author] = info.author end receiver.commit_editor2(args[:repos_url], args[:base_path], args[:txn], args[:user], args[:log_message], commit_callback) end end def test_commit_editor3 assert_commit_editor do |receiver, commit_callback_result, args| props = { Svn::Core::PROP_REVISION_AUTHOR => args[:user], Svn::Core::PROP_REVISION_LOG => args[:log_message], } commit_callback = Proc.new do |info| commit_callback_result[:revision] = info.revision commit_callback_result[:date] = info.date commit_callback_result[:author] = info.author end receiver.commit_editor3(args[:repos_url], args[:base_path], args[:txn], props, commit_callback) end end def test_prop file = "file" path = File.join(@wc_path, file) source = "sample source" log = "sample log" make_context(log) do |ctx| File.open(path, "w") {|f| f.print(source)} ctx.add(path) ctx.ci(@wc_path) assert_equal([ Svn::Core::PROP_REVISION_AUTHOR, Svn::Core::PROP_REVISION_LOG, Svn::Core::PROP_REVISION_DATE, ].sort, @repos.proplist.keys.sort) assert_equal(log, @repos.prop(Svn::Core::PROP_REVISION_LOG)) @repos.set_prop(@author, Svn::Core::PROP_REVISION_LOG, nil) assert_nil(@repos.prop(Svn::Core::PROP_REVISION_LOG)) assert_equal([ Svn::Core::PROP_REVISION_AUTHOR, Svn::Core::PROP_REVISION_DATE, ].sort, @repos.proplist.keys.sort) assert_raises(Svn::Error::ReposHookFailure) do @repos.set_prop(@author, Svn::Core::PROP_REVISION_DATE, nil) end assert_not_nil(@repos.prop(Svn::Core::PROP_REVISION_DATE)) assert_nothing_raised do @repos.set_prop(@author, Svn::Core::PROP_REVISION_DATE, nil, nil, nil, false) end assert_nil(@repos.prop(Svn::Core::PROP_REVISION_DATE)) assert_equal([ Svn::Core::PROP_REVISION_AUTHOR, ].sort, @repos.proplist.keys.sort) end end def test_dump file = "file" path = File.join(@wc_path, file) source = "sample source" log = "sample log" make_context(log) do |ctx| File.open(path, "w") {|f| f.print(source)} ctx.add(path) rev1 = ctx.ci(@wc_path).revision File.open(path, "a") {|f| f.print(source)} rev2 = ctx.ci(@wc_path).revision assert_nothing_raised do @repos.dump_fs(nil, nil, rev1, rev2) end dump = StringIO.new("") feedback = StringIO.new("") @repos.dump_fs(dump, feedback, rev1, rev2) dump_unless_feedback = StringIO.new("") @repos.dump_fs(dump_unless_feedback, nil, rev1, rev2) dump.rewind dump_unless_feedback.rewind assert_equal(dump.read, dump_unless_feedback.read) end end def test_load file = "file" path = File.join(@wc_path, file) source = "sample source" log = "sample log" make_context(log) do |ctx| File.open(path, "w") {|f| f.print(source)} ctx.add(path) rev1 = ctx.ci(@wc_path).revision File.open(path, "a") {|f| f.print(source)} rev2 = ctx.ci(@wc_path).revision dump = StringIO.new("") @repos.dump_fs(dump, nil, rev1, rev2) dest_path = File.join(@tmp_path, "dest") Svn::Repos.create(dest_path) do |repos| assert_raises(NoMethodError) do repos.load_fs(nil) end end [ [StringIO.new(""), Svn::Repos::LOAD_UUID_DEFAULT, "/"], [StringIO.new("")], [], ].each_with_index do |args, i| dest_path = File.join(@tmp_path, "dest#{i}") Svn::Repos.create(dest_path) do |repos| assert_not_equal(@repos.fs.root.committed_info("/"), repos.fs.root.committed_info("/")) dump.rewind repos.load_fs(dump, *args) assert_equal(@repos.fs.root.committed_info("/"), repos.fs.root.committed_info("/")) end end end end def test_node_editor file = "file" dir1 = "dir1" dir2 = "dir2" dir3 = "dir3" dir1_path = File.join(@wc_path, dir1) dir2_path = File.join(dir1_path, dir2) dir3_path = File.join(dir2_path, dir3) path = File.join(dir3_path, file) source = "sample source" log = "sample log" make_context(log) do |ctx| FileUtils.mkdir_p(dir3_path) FileUtils.touch(path) ctx.add(dir1_path) rev1 = ctx.ci(@wc_path).revision ctx.rm(dir3_path) rev2 = ctx.ci(@wc_path).revision rev1_root = @repos.fs.root(rev1) rev2_root = @repos.fs.root(rev2) editor = @repos.node_editor(rev1_root, rev2_root) rev2_root.replay(editor) tree = editor.baton.node assert_equal("", tree.name) assert_equal(dir1, tree.child.name) assert_equal(dir2, tree.child.child.name) end end def test_lock file = "file" log = "sample log" path = File.join(@wc_path, file) path_in_repos = "/#{file}" make_context(log) do |ctx| FileUtils.touch(path) ctx.add(path) rev = ctx.ci(@wc_path).revision access = Svn::Fs::Access.new(@author) @repos.fs.access = access lock = @repos.lock(file) locks = @repos.get_locks(file) assert_equal([path_in_repos], locks.keys) assert_equal(lock.token, locks[path_in_repos].token) @repos.unlock(file, lock.token) assert_equal({}, @repos.get_locks(file)) end end def test_authz name = "REPOS" conf_path = File.join(@tmp_path, "authz_file") File.open(conf_path, "w") do |f| f.print(<<-EOF) [/] #{@author} = r EOF end authz = Svn::Repos::Authz.read(conf_path) assert(authz.can_access?(name, "/", @author, Svn::Repos::AUTHZ_READ)) assert(!authz.can_access?(name, "/", @author, Svn::Repos::AUTHZ_WRITE)) assert(!authz.can_access?(name, "/", "FOO", Svn::Repos::AUTHZ_READ)) end def test_recover started = false Svn::Repos.recover(@repos_path, false) do started = true end assert(started) end def test_mergeinfo log = "sample log" file = "sample.txt" src = "sample\n" trunk = File.join(@wc_path, "trunk") branch = File.join(@wc_path, "branch") trunk_path = File.join(trunk, file) branch_path = File.join(branch, file) trunk_path_in_repos = "/trunk/#{file}" branch_path_in_repos = "/branch/#{file}" make_context(log) do |ctx| ctx.mkdir(trunk, branch) File.open(trunk_path, "w") {} File.open(branch_path, "w") {} ctx.add(trunk_path) ctx.add(branch_path) original_rev = ctx.commit(@wc_path).revision File.open(branch_path, "w") {|f| f.print(src)} merged_rev = ctx.commit(@wc_path).revision ctx.merge(branch, original_rev, branch, merged_rev, trunk) ctx.commit(@wc_path) mergeinfo = Svn::Core::MergeInfo.parse("#{branch_path_in_repos}:#{merged_rev}") assert_equal({trunk_path_in_repos => mergeinfo}, @repos.mergeinfo([trunk_path_in_repos])) assert_equal(mergeinfo, @repos.mergeinfo(trunk_path_in_repos)) end end private def warning_func Proc.new do |err| STDERR.puts err if $DEBUG end end class TestEditor < Svn::Delta::BaseEditor attr_reader :sequence def initialize @sequence = [] end def set_target_revision(target_revision) @sequence << [:set_target_revision, target_revision] end def open_root(base_revision) @sequence << [:open_root, base_revision] end def delete_entry(path, revision, parent_baton) @sequence << [:delete_entry, path, revision, parent_baton] end def add_directory(path, parent_baton, copyfrom_path, copyfrom_revision) @sequence << [:add_directory, path, parent_baton, copyfrom_path, copyfrom_revision] end def open_directory(path, parent_baton, base_revision) @sequence << [:open_directory, path, parent_baton, base_revision] end def change_dir_prop(dir_baton, name, value) @sequence << [:change_dir_prop, dir_baton, name, value] end def close_directory(dir_baton) @sequence << [:close_directory, dir_baton] end def absent_directory(path, parent_baton) @sequence << [:absent_directory, path, parent_baton] end def add_file(path, parent_baton, copyfrom_path, copyfrom_revision) @sequence << [:add_file, path, parent_baton, copyfrom_path, copyfrom_revision] end def open_file(path, parent_baton, base_revision) @sequence << [:open_file, path, parent_baton, base_revision] end # return nil or object which has `call' method. def apply_textdelta(file_baton, base_checksum) @sequence << [:apply_textdelta, file_baton, base_checksum] nil end def change_file_prop(file_baton, name, value) @sequence << [:change_file_prop, file_baton, name, value] end def close_file(file_baton, text_checksum) @sequence << [:close_file, file_baton, text_checksum] end def absent_file(path, parent_baton) @sequence << [:absent_file, path, parent_baton] end def close_edit(baton) @sequence << [:close_edit, baton] end def abort_edit(baton) @sequence << [:abort_edit, baton] end end end