TestP4Transfer.py #42

  • //
  • guest/
  • perforce_software/
  • p4transfer/
  • TestP4Transfer.py
  • View
  • Commits
  • Open Download .zip Download (82 KB)
# -*- encoding: UTF8 -*-

# tests missing
# - maximum
# - preflight check

from __future__ import print_function

import sys
import time
import P4
import logutils
import subprocess
import inspect
import unittest, os, shutil, stat, re
from subprocess import Popen, PIPE

python3 = sys.version_info[0] >= 3
if sys.hexversion < 0x02070000 or (0x0300000 < sys.hexversion < 0x0303000):
    sys.exit("Python 2.7 or 3.3 or newer is required to run this program.")

if python3:
    from configparser import ConfigParser
    from io import StringIO
else:
    from ConfigParser import ConfigParser
    from StringIO import StringIO

import P4Transfer

P4D = "p4d"
P4USER = "testuser"
P4CLIENT = "test_ws"
TRANSFER_CLIENT = "transfer"
TRANSFER_CONFIG = "transfer.cfg"

TEST_COUNTER_NAME = "P4Transfer"
INTEG_ENGINE = 3

def onRmTreeError(function, path, exc_info):
    os.chmod(path, stat.S_IWRITE)
    os.remove(path)

def ensureDirectory(directory):
    if not os.path.isdir(directory):
        os.makedirs(directory)

def localDirectory(root, *dirs):
    "Create and ensure it exists"
    dir_path = os.path.join(root, *dirs)
    ensureDirectory(dir_path)
    return dir_path

def create_file(file_name, contents):
    "Create file with specified contents"
    ensureDirectory(os.path.dirname(file_name))
    with open(file_name, 'w') as f:
        print(contents, file=f)

def append_to_file(file_name, contents):
    "Append contents to file"
    with open(file_name, 'a+') as f:
        f.write(contents)

class P4Server:
    def __init__(self, root, logger):
        self.root = root
        self.logger = logger
        self.server_root = os.path.join(root, "server")
        self.client_root = os.path.join(root, "client")

        ensureDirectory(self.root)
        ensureDirectory(self.server_root)
        ensureDirectory(self.client_root)

        self.p4d = P4D
        self.port = "rsh:%s -r \"%s\" -L log -i" % (self.p4d, self.server_root)
        self.p4 = P4.P4()
        self.p4.port = self.port
        self.p4.user = P4USER
        self.p4.client = P4CLIENT
        self.p4.connect()

        self.p4cmd('depots') # triggers creation of the user
        self.p4cmd('configure', 'set', 'dm.integ.engine=%d' % INTEG_ENGINE)

        self.p4.disconnect() # required to pick up the configure changes
        self.p4.connect()

        self.client_name = P4CLIENT
        client = self.p4.fetch_client(self.client_name)
        client._root = self.client_root
        self.p4.save_client(client)

    def shutDown(self):
        if self.p4.connected():
            self.p4.disconnect()

    def createTransferClient(self, name, root):
        pass

    def enableUnicode(self):
        cmd = [self.p4d, "-r", self.server_root, "-L", "log", "-vserver=3", "-xi"]
        f = Popen(cmd, stdout=PIPE).stdout
        for s in f.readlines():
            pass
        f.close()

    def getCounter(self):
        "Returns value of counter as integer"
        result = self.p4.run('counter', TEST_COUNTER_NAME)
        if result and 'counter' in result[0]:
            return int(result[0]['value'])
        return 0

    def p4cmd(self, *args):
        "Execute p4 cmd while logging arguments and results"
        if not self.logger:
            self.logger = logutils.getLogger(P4Transfer.LOGGER_NAME)
        self.logger.debug('testp4:', args)
        output = self.p4.run(args)
        self.logger.debug('testp4r:', output)
        return output

class TestP4Transfer(unittest.TestCase):

    def __init__(self, methodName='runTest'):
        self.stdoutput = StringIO()
        logutils.getLogger(P4Transfer.LOGGER_NAME, self.stdoutput)
        self.logger = logutils.getLogger(P4Transfer.LOGGER_NAME)
        super(TestP4Transfer, self).__init__(methodName=methodName)

    def assertRegex(self, *args, **kwargs):
        if python3:
            return super(TestP4Transfer, self).assertRegex(*args, **kwargs)
        else:
            return super(TestP4Transfer, self).assertRegexpMatches(*args, **kwargs)

    def setUp(self):
        self.setDirectories()

    def tearDown(self):
        self.source.shutDown()
        self.target.shutDown()
        time.sleep(0.02)
        #self.cleanupTestTree()

    def setDirectories(self):
        self.startdir = os.getcwd()
        self.transfer_root = os.path.join(self.startdir, 'transfer')
        self.cleanupTestTree()

        ensureDirectory(self.transfer_root)

        self.source = P4Server(os.path.join(self.transfer_root, 'source'), self.logger)
        self.target = P4Server(os.path.join(self.transfer_root, 'target'), self.logger)

        self.transfer_client_root = localDirectory(self.transfer_root, 'transfer_client')
        self.writeP4Config()

    def writeP4Config(self):
        "Write appropriate files - useful for occasional manual debugging"
        srcP4Config = os.path.join(self.transfer_root, 'source', 'p4config.txt')
        targP4Config = os.path.join(self.transfer_root, 'target', 'p4config.txt')
        with open(srcP4Config, "w") as fh:
            fh.write('P4PORT=%s\n' % self.source.port)
            fh.write('P4USER=%s\n' % self.source.p4.user)
            fh.write('P4CLIENT=%s\n' % self.source.p4.client)
        with open(targP4Config, "w") as fh:
            fh.write('P4PORT=%s\n' % self.target.port)
            fh.write('P4USER=%s\n' % self.target.p4.user)
            fh.write('P4CLIENT=%s\n' % self.target.p4.client)

    def cleanupTestTree(self):
        os.chdir(self.startdir)
        if os.path.isdir(self.transfer_root):
            shutil.rmtree(self.transfer_root, False, onRmTreeError)

    def setupTransfer(self):
        """Creates client workspaces on source and target and a config file"""
        msg = "Test: %s ======================" % inspect.stack()[1][3]
        self.logger.debug(msg)
        source_client = self.source.p4.fetch_client(TRANSFER_CLIENT)
        source_client._root = self.transfer_client_root
        source_client._view = ['//depot/inside/... //%s/...' % TRANSFER_CLIENT]
        self.source.p4.save_client(source_client)

        target_client = self.target.p4.fetch_client('transfer')
        target_client._root = self.transfer_client_root
        target_client._view = ['//depot/import/... //%s/...' % TRANSFER_CLIENT]
        self.target.p4.save_client(target_client)
        self.createConfigFile()

    def createConfigFile(self, srcOptions=None, targOptions=None, options=None):
        "Creates config file with extras if appropriate"
        if options is None:
            options = {}
        if srcOptions is None:
            srcOptions = {}
        if targOptions is None:
            targOptions = {}

        self.parser = ConfigParser()
        self.parser.add_section('source')
        self.parser.set('source', 'p4port', self.source.port)
        self.parser.set('source', 'p4user', P4USER)
        self.parser.set('source', 'p4client', TRANSFER_CLIENT)
        for opt in srcOptions.keys():
            self.parser.set('source', opt, srcOptions[opt])

        self.parser.add_section('target')
        self.parser.set('target', 'p4port', self.target.port)
        self.parser.set('target', 'p4user', P4USER)
        self.parser.set('target', 'p4client', TRANSFER_CLIENT)
        for opt in targOptions.keys():
            self.parser.set('target', opt, targOptions[opt])

        self.parser.add_section('general')
        self.parser.set('general', 'logfile', os.path.join(self.transfer_root, 'temp', 'test.log'))
        if not os.path.exists(os.path.join(self.transfer_root, 'temp')):
            os.mkdir(os.path.join(self.transfer_root, 'temp'))
        self.parser.set('general', 'counter_name', TEST_COUNTER_NAME)

        for opt in options.keys():
            self.parser.set('general', opt, options[opt])

        # write the config file

        self.transfer_cfg = os.path.join(self.transfer_root, TRANSFER_CONFIG)
        with open(self.transfer_cfg, 'w') as f:
            self.parser.write(f)

    def run_P4Transfer(self, *args):
        base_args = ['-c', self.transfer_cfg, '-s']
        if args:
            base_args.extend(args)
        pt = P4Transfer.P4Transfer(*base_args)
        result = pt.replicate()
        return result

    def assertCounters(self, sourceValue, targetValue):
        sourceCounter = self.target.getCounter()
        targetCounter = len(self.target.p4.run("changes"))
        self.assertEqual(sourceCounter, sourceValue, "Source counter is not {} but {}".format(sourceValue, sourceCounter))
        self.assertEqual(targetCounter, targetValue, "Target counter is not {} but {}".format(targetValue, targetCounter))

    def testArgParsing(self):
        "Basic argparsing for the module"
        self.setupTransfer()
        args = ['-c', self.transfer_cfg, '-s']
        pt = P4Transfer.P4Transfer(*args)
        self.assertEqual(pt.options.config, self.transfer_cfg)
        self.assertTrue(pt.options.stoponerror)
        self.assertFalse(pt.options.preflight)
        args = ['-c', self.transfer_cfg]
        pt = P4Transfer.P4Transfer(*args)

    def testClientsMatch(self):
        "Make sure clients match the right hand side"
        msg = "Test: %s ======================" % inspect.stack()[0][3]
        self.logger.debug(msg)
        source_name = "src-%s" % TRANSFER_CLIENT
        source_client = self.source.p4.fetch_client(source_name)
        source_client._root = self.transfer_client_root
        source_client._view = ['//depot/inside/... //%s/source/...' % source_name]
        self.source.p4.save_client(source_client)

        target_name = "targ-%s" % TRANSFER_CLIENT
        target_client = self.target.p4.fetch_client(target_name)
        target_client._root = self.transfer_client_root
        target_client._view = ['//depot/import/... //%s/target/...' % target_name]
        self.target.p4.save_client(target_client)

        srcOptions = {"p4client": source_name}
        targOptions = {"p4client": target_name}
        self.createConfigFile(srcOptions=srcOptions, targOptions=targOptions)

        msg = ""
        try:
            base_args = ['-c', self.transfer_cfg, '-s']
            pt = P4Transfer.P4Transfer(*base_args)
            result = pt.setupReplicate()
        except Exception as e:
            msg = str(e)
        self.assertRegex(msg, "workspace mappings have different right hand sides")

        source_client = self.source.p4.fetch_client(source_name)
        source_client._root = self.transfer_client_root
        source_client._view = ['//depot/inside/... //%s/root/...' % source_name]
        self.source.p4.save_client(source_client)

        target_client = self.target.p4.fetch_client(target_name)
        target_client._root = "%s-tmp" % self.transfer_client_root
        target_client._view = ['//depot/import/... //%s/root/...' % target_name]
        self.target.p4.save_client(target_client)

        msg = ""
        try:
            base_args = ['-c', self.transfer_cfg, '-s']
            pt = P4Transfer.P4Transfer(*base_args)
            result = pt.setupReplicate()
        except Exception as e:
            msg = str(e)
        self.assertRegex(msg, "server workspace root directories must be the same")

        target_client = self.target.p4.fetch_client(target_name)
        target_client._root = self.transfer_client_root
        target_client._view = ['//depot/import/... //%s/root/...' % target_name]
        self.target.p4.save_client(target_client)

        msg = ""
        pt = P4Transfer.P4Transfer(*base_args)
        result = pt.setupReplicate()

        self.assertCounters(0, 0)

    def testChangeFormatting(self):
        "Formatting options for change descriptions"
        self.setupTransfer()
        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        create_file(inside_file1, 'Test content')

        self.source.p4cmd('add', inside_file1)
        desc = 'inside_file1 added'
        self.source.p4cmd('submit', '-d', desc)
        self.run_P4Transfer()
        self.assertCounters(1, 1)
        changes = self.target.p4cmd('changes', '-l', '-m1')
        self.assertRegex(changes[0]['desc'], "%s\n\nTransferred from p4://rsh:.*@1\n$" % desc)

        options = {"change_description_format": "Originally $sourceChange by $sourceUser"}
        self.createConfigFile(options=options)
        self.source.p4cmd('edit', inside_file1)
        desc = 'inside_file1 edited'
        self.source.p4cmd('submit', '-d', desc)
        self.run_P4Transfer()
        self.assertCounters(2, 2)
        changes = self.target.p4cmd('changes', '-l', '-m1')
        self.assertRegex(changes[0]['desc'], "Originally 2 by %s" % P4USER)

        options = {"change_description_format": "Was $sourceChange by $sourceUser $fred\n$sourceDescription"}
        self.createConfigFile(options=options)
        self.source.p4cmd('edit', inside_file1)
        desc = 'inside_file1 edited again'
        self.source.p4cmd('submit', '-d', desc)
        self.run_P4Transfer()
        self.assertCounters(3, 3)
        changes = self.target.p4cmd('changes', '-l', '-m1')
        self.assertEqual(changes[0]['desc'], "Was 3 by %s $fred\n%s\n" % (P4USER, desc))

    def testChangeMapFile(self):
        "How a change map file is written"
        self.setupTransfer()
        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        create_file(inside_file1, 'Test content')

        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'file added')
        self.run_P4Transfer()
        self.assertCounters(1, 1)
        changes = self.target.p4cmd('changes', '-l', '-m1')

        options = {"change_map_file": "change_map.csv"}
        change_map_file = '//depot/import/change_map.csv'
        self.createConfigFile(options=options)

        self.source.p4cmd('edit', inside_file1)
        self.source.p4cmd('submit', '-d', 'edited')
        self.run_P4Transfer()
        self.assertCounters(2, 3)
        change = self.target.p4.run_describe('3')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], change_map_file)
        content = self.target.p4.run_print('-q', change_map_file)[1].split("\n")
        self.logger.debug("content:", content)
        self.assertRegex(content[0], "sourceP4Port,sourceChangeNo,targetChangeNo")
        self.assertRegex(content[1], "rsh.*,2,2")

        self.source.p4cmd('edit', inside_file1)
        self.source.p4cmd('submit', '-d', 'edited again')
        self.source.p4cmd('edit', inside_file1)
        self.source.p4cmd('submit', '-d', 'and again')
        self.run_P4Transfer()
        self.assertCounters(4, 6)
        change = self.target.p4.run_describe('6')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], change_map_file)
        content = self.target.p4.run_print('-q', change_map_file)[1].split("\n")
        self.logger.debug("content:", content)
        self.assertRegex(content[1], "rsh.*,2,2")
        self.assertRegex(content[2], "rsh.*,3,4")

    def testArchive(self):
        "Archive a file"
        self.setupTransfer()

        d = self.source.p4.fetch_depot('archive')
        d['Type'] = 'archive'
        self.source.p4.save_depot(d)

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        inside_file2 = os.path.join(inside, "inside_file2")
        create_file(inside_file1, 'Test content')
        create_file(inside_file2, 'Test content')
        self.source.p4cmd('add', '-tbinary', inside_file1)
        self.source.p4cmd('add', inside_file2)
        self.source.p4cmd('submit', '-d', 'files added')

        self.source.p4cmd('edit', inside_file1)
        self.source.p4cmd('edit', inside_file2)
        append_to_file(inside_file1, "Some text")
        append_to_file(inside_file2, "More text")
        self.source.p4cmd('submit', '-d', 'files edited')

        self.source.p4cmd('archive', '-D', 'archive', inside_file1)
        filelog = self.source.p4.run_filelog('//depot/inside/inside_file1')
        self.assertEqual(filelog[0].revisions[0].action, 'archive')
        self.assertEqual(filelog[0].revisions[1].action, 'archive')

        self.run_P4Transfer()
        self.assertCounters(2, 2)

        change = self.target.p4.run_describe('1')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], '//depot/import/inside_file2')
        change = self.target.p4.run_describe('2')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], '//depot/import/inside_file2')

    def testAdd(self):
        "Basic file add"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        create_file(inside_file1, 'Test content')

        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        self.run_P4Transfer()

        changes = self.target.p4cmd('changes')
        self.assertEqual(len(changes), 1, "Target does not have exactly one change")
        self.assertEqual(changes[0]['change'], "1")

        files = self.target.p4cmd('files', '//depot/...')
        self.assertEqual(len(files), 1)
        self.assertEqual(files[0]['depotFile'], '//depot/import/inside_file1')

        self.assertCounters(1, 1)

    @unittest.skipIf(python3, "Unicode not supported in Python3 yet...")
    def testUnicode(self):
        "Adding of files with Unicode filenames"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        if python3:
            inside_file1 = "inside_file1uåäö"

        else:
            inside_file1 = u"inside_file1uåäö".encode(sys.getfilesystemencoding())
        inside_file2 = "Am\xE8lioration.txt"
        localinside_file1 = os.path.join(inside, inside_file1)
        localinside_file2 = os.path.join(inside, inside_file2)

        create_file(localinside_file1, 'Test content')
        create_file(localinside_file2, 'Some Test content')
        self.source.p4cmd('add', '-f', localinside_file1)
        self.source.p4cmd('add', '-f', localinside_file2)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        self.run_P4Transfer()

        changes = self.target.p4cmd('changes')
        self.assertEqual(len(changes), 1)
        self.assertEqual(changes[0]['change'], "1")

        files = self.target.p4cmd('files', '//depot/...')
        self.assertEqual(len(files), 2)
        self.assertEqual(files[1]['depotFile'], '//depot/import/%s' % inside_file1)

        self.assertCounters(1, 1)

    def testAddWildcardChars(self):
        "Add where filenames contain Perforce wildcards"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "@inside_file1")
        inside_file2 = os.path.join(inside, "%25inside_file2")
        inside_file3 = os.path.join(inside, "#inside_file3")

        create_file(inside_file1, 'Test content')
        create_file(inside_file2, 'Test content')
        create_file(inside_file3, 'Test content')
        self.source.p4cmd('add', '-f', inside_file1)
        self.source.p4cmd('add', '-f', inside_file2)
        self.source.p4cmd('add', '-f', inside_file3)
        self.source.p4cmd('submit', '-d', 'files added')

        self.run_P4Transfer()
        self.assertCounters(1, 1)

        changes = self.target.p4cmd('changes', )
        self.assertEqual(len(changes), 1)
        self.assertEqual(changes[0]['change'], "1")

        files = self.target.p4cmd('files', '//depot/...')
        self.assertEqual(len(files), 3)
        self.assertEqual(files[0]['depotFile'], '//depot/import/%23inside_file3')
        self.assertEqual(files[1]['depotFile'], '//depot/import/%2525inside_file2')
        self.assertEqual(files[2]['depotFile'], '//depot/import/%40inside_file1')

        inside_file1Fixed = inside_file1.replace("@", "%40")
        inside_file2Fixed = inside_file2.replace("%", "%25")
        inside_file3Fixed = inside_file3.replace("#", "%23")
        self.source.p4cmd('edit', inside_file1Fixed)
        self.source.p4cmd('edit', inside_file2Fixed)
        self.source.p4cmd('edit', inside_file3Fixed)
        append_to_file(inside_file1, 'Different stuff')
        append_to_file(inside_file2, 'Different stuff')
        append_to_file(inside_file3, 'Different stuff')
        self.source.p4cmd('submit', '-d', 'files modified')

        self.run_P4Transfer()
        self.assertCounters(2, 2)

        changes = self.target.p4cmd('changes')
        self.assertEqual(len(changes), 2)
        self.assertEqual(changes[0]['change'], "2")

        self.source.p4cmd('integrate', "//depot/inside/*", "//depot/inside/new/*")
        self.source.p4cmd('submit', '-d', 'files branched')

        self.run_P4Transfer()
        self.assertCounters(3, 3)


    def testEditAndDelete(self):
        "Edits and Deletes"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        create_file(inside_file1, 'Test content')
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', "inside_file1 added")

        self.source.p4cmd('edit', inside_file1)
        append_to_file(inside_file1, 'More content')
        self.source.p4cmd('submit', '-d', "inside_file1 edited")

        self.run_P4Transfer()

        changes = self.target.p4cmd('changes', )
        self.assertEqual(len(changes), 2)
        self.assertEqual(changes[0]['change'], "2")

        self.assertCounters(2, 2)

        self.source.p4cmd('delete', inside_file1)
        self.source.p4cmd('submit', '-d', "inside_file1 deleted")

        self.run_P4Transfer()
        self.assertCounters(3, 3)

        changes = self.target.p4cmd('changes', )
        self.assertEqual(len(changes), 3, "Target does not have exactly three changes")
        filelog = self.target.p4.run_filelog('//depot/import/inside_file1')
        self.assertEqual(filelog[0].revisions[0].action, 'delete', "Target has not been deleted")

        create_file(inside_file1, 'New content')
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', "Re-added")

        self.run_P4Transfer()
        self.assertCounters(4, 4)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file1')
        self.assertEqual(filelog[0].revisions[0].action, 'add')

    def testFileTypes(self):
        "File types are transferred appropriately"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        create_file(inside_file1, "Test content")
        self.source.p4cmd('add', '-tbinary', inside_file1)
        self.source.p4cmd('submit', '-d', "inside_file1 added")

        self.run_P4Transfer()
        self.assertCounters(1, 1)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file1')
        self.assertEqual(filelog[0].revisions[0].type, 'binary')

        self.source.p4cmd('edit', '-t+x', inside_file1)
        append_to_file(inside_file1, "More content")
        self.source.p4cmd('submit', '-d', "Type changed")

        self.run_P4Transfer()
        self.assertCounters(2, 2)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file1')
        self.assertEqual(filelog[0].revisions[0].type, 'xbinary')

        inside_file2 = os.path.join(inside, "inside_file2")
        create_file(inside_file2, "$Id$\n$DateTime$")
        self.source.p4cmd('add', '-t+k', inside_file2)
        self.source.p4cmd('submit', '-d', "Ktext added")

        self.run_P4Transfer()
        self.assertCounters(3, 3)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')
        self.assertEqual(filelog[0].revisions[0].type, 'ktext')
        verifyResult = self.target.p4.run_verify('-q', '//depot/import/inside_file2')
        self.assertEqual(len(verifyResult), 0) # just to see that ktext gets transferred properly

        content = self.target.p4.run_print('//depot/import/inside_file2')[1]
        lines = content.split("\n")
        self.assertEqual(lines[0], '$Id: //depot/import/inside_file2#1 $', "Content does not match : %s" % lines[0])

    def testSimpleIntegrate(self):
        "Simple integration options - inside client workspace view"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        create_file(inside_file1, "Test content")
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        inside_file2 = os.path.join(inside, "inside_file2")
        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'inside_file1 -> inside_file2')

        self.run_P4Transfer()
        self.assertCounters(2, 2)

        changes = self.target.p4cmd('changes')
        self.assertEqual(len(changes), 2)

        self.source.p4cmd('edit', inside_file1)
        append_to_file(inside_file1, "More content")
        self.source.p4cmd('submit', '-d', 'inside_file1 edited')

        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4.run_resolve('-at')
        self.source.p4cmd('submit', '-d', 'inside_file1 -> inside_file2 (copy)')

        self.run_P4Transfer()
        self.assertCounters(4, 4)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')
        self.assertEqual(len(filelog[0].revisions), 2)
        self.assertEqual(len(filelog[0].revisions[1].integrations), 1)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, "copy from")

        # Now make 2 changes and integrate them one at a time.
        self.source.p4cmd('edit', inside_file1)
        append_to_file(inside_file1, "More content2")
        self.source.p4cmd('submit', '-d', 'inside_file1 edited')

        self.source.p4cmd('edit', inside_file1)
        append_to_file(inside_file1, "More content3")
        self.source.p4cmd('submit', '-d', 'inside_file1 edited')

        self.source.p4cmd('integrate', inside_file1 + "#3", inside_file2)
        self.source.p4.run_resolve('-at')
        self.source.p4cmd('submit', '-d', 'inside_file1 -> inside_file2 (copy)')

        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4.run_resolve('-at')
        self.source.p4cmd('submit', '-d', 'inside_file1 -> inside_file2 (copy)')

        self.run_P4Transfer()
        self.assertCounters(8, 8)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')
        self.logger.debug(filelog)
        self.assertEqual(len(filelog[0].revisions), 4)
        self.assertEqual(len(filelog[0].revisions[1].integrations), 1)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, "copy from")

    def testComplexIntegrate(self):
        "More complex integrations with various resolve options"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")

        content1 = """
        Line 1
        Line 2 - changed
        Line 3
        """

        create_file(inside_file1, """
        Line 1
        Line 2
        Line 3
        """)
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        inside_file2 = os.path.join(inside, "inside_file2")
        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'inside_file1 -> inside_file2')

        # Prepare merge
        self.source.p4cmd('edit', inside_file1, inside_file2)
        create_file(inside_file1, content1)
        create_file(inside_file2, """
        Line 1
        Line 2
        Line 3 - changed
        """)
        self.source.p4cmd('submit', '-d', "Changed both contents")

        # Integrate with merge
        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4.run_resolve('-am')
        self.source.p4cmd('submit', '-d', "Merged contents")

        contentMerged = self.source.p4.run_print(inside_file2)[1]

        sourceCounter = 4
        targetCounter = 4

        self.run_P4Transfer()
        self.assertCounters(sourceCounter, targetCounter)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')
        self.logger.debug('test:', filelog)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'edit from')
        self.assertEqual(self.target.p4.run_print('//depot/import/inside_file2')[1], contentMerged)

        # Prepare integrate with edit
        self.source.p4cmd('edit', inside_file1, inside_file2)
        create_file(inside_file1, content1)
        self.source.p4cmd('submit', '-d', "Created a conflict")

        # Integrate with edit
        self.source.p4cmd('integrate', inside_file1, inside_file2)

        class EditResolve(P4.Resolver):
            def resolve(self, mergeData):
                create_file(mergeData.result_path, """
        Line 1
        Line 2 - changed
        Line 3 - edited
        """)
                return 'ae'

        self.source.p4.run_resolve(resolver=EditResolve())
        self.source.p4cmd('submit', '-d', "Merge with edit")

        sourceCounter += 2
        targetCounter += 2

        self.run_P4Transfer()
        self.assertCounters(sourceCounter, targetCounter)

        # Prepare ignore
        self.source.p4cmd('edit', inside_file1)
        append_to_file(inside_file1, "For your eyes only")
        self.source.p4cmd('submit', '-d', "Edit source again")

        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4.run_resolve('-ay') # ignore
        self.source.p4cmd('submit', '-d', "Ignored change in inside_file1")

        sourceCounter += 2
        targetCounter += 2

        self.run_P4Transfer()
        self.assertCounters(sourceCounter, targetCounter)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'ignored')
        content = self.target.p4.run_print('-a', '//depot/import/inside_file2')
        self.assertEqual(content[1], content[3])

        # Prepare delete
        self.source.p4cmd('delete', inside_file1)
        self.source.p4cmd('submit', '-d', "Delete file 1")

        self.source.p4.run_merge(inside_file1, inside_file2) # to trigger resolve
        self.source.p4.run_resolve('-at')
        self.source.p4cmd('submit', '-d', "Propagated delete")

        sourceCounter += 2
        targetCounter += 2

        self.run_P4Transfer()
        self.assertCounters(sourceCounter, targetCounter)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')
        self.assertEqual(len(filelog[0].revisions[0].integrations), 1)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'delete from')

        # Prepare re-add
        create_file(inside_file1, content1)
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 re-added')

        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', "inside_file2 re-added")

        sourceCounter += 2
        targetCounter += 2

        self.run_P4Transfer()
        self.assertCounters(sourceCounter, targetCounter)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'branch from')

    def testForcedIntegrate(self):
        "Integration requiring -f"
        self.setupTransfer()
        self.source.p4cmd("configure", "set", "dm.integ.engine=2")
        self.target.p4cmd("configure", "set", "dm.integ.engine=2")

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        inside_file2 = os.path.join(inside, "inside_file2")
        create_file(inside_file1, "Test content")
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'inside_file1 -> inside_file2')

        self.source.p4cmd('edit', inside_file1)
        append_to_file(inside_file1, "more content")
        self.source.p4cmd('submit', '-d', 'added content')

        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('resolve', '-as')
        self.source.p4cmd('submit', '-d', 'inside_file1 -> inside_file2 again')

        # Backout the change which was integrated in, and then force integrate
        self.source.p4cmd('sync', "%s#1" % inside_file2)
        self.source.p4cmd('edit', inside_file2)
        self.source.p4cmd('sync', inside_file2)
        self.source.p4cmd('resolve', '-at', inside_file2)
        self.source.p4cmd('submit', '-d', 'backed out inside_file2')

        self.source.p4cmd('integrate', '-f', inside_file1, inside_file2)
        self.source.p4cmd('resolve', '-at')
        self.source.p4cmd('submit', '-d', 'Force integrate')

        self.run_P4Transfer()
        self.assertCounters(6, 6)

    def testDirtyMerge(self):
        """A merge which is supposedly clean but in reality is a dirty one - this is possible when transferring with
        old servers"""
        self.setupTransfer()

        content = """
        Line 1
        Line 2
        Line 3
        """

        content2 = """
        Line 1
        Line 2
        Line 3 - changed
        """

        content3 = """
        Line 1
        Line 2 - changed
        Line 3 - edited
        """

        content4 = """
        Line 1
        Line 2 - changed
        Line 3 - edited and changed
        """

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")

        create_file(inside_file1, content)
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        inside_file2 = os.path.join(inside, "inside_file2")
        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'inside_file1 -> inside_file2')

        # Prepare merge with edit
        self.source.p4cmd('edit', inside_file1, inside_file2)
        create_file(inside_file1, content2)
        create_file(inside_file2, content3)
        self.source.p4cmd('submit', '-d', "Changed both contents")

        self.source.p4cmd('integrate', inside_file1, inside_file2)

        class EditResolve(P4.Resolver):
            def resolve(self, mergeData):
                create_file(mergeData.result_path, content4)
                return 'ae'

        self.source.p4.run_resolve(resolver=EditResolve())
        self.source.p4cmd('submit', '-d', "Merge with edit")

        filelog = self.source.p4.run_filelog(inside_file2)
        self.assertEqual(len(filelog[0].revisions[0].integrations), 1)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'edit from')

        # Convert dirty merge to pretend clean merge.
        #
        # Dirty merge (fields 12/10)
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file2@ @//depot/inside/inside_file1@ 1 2 2 3 12 4
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file1@ @//depot/inside/inside_file2@ 2 3 1 2 10 4
        #
        # Clean merge (fields 0/1)
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file2@ @//depot/inside/inside_ file1@ 1 2 2 3 0 4
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file1@ @//depot/inside/inside_file2@ 2 3 1 2 1 4
        jnl_rec = "@rv@ 0 @db.integed@ @//depot/inside/inside_file2@ @//depot/inside/inside_file1@ 1 2 2 3 0 4\n" + \
            "@rv@ 0 @db.integed@ @//depot/inside/inside_file1@ @//depot/inside/inside_file2@ 2 3 1 2 1 4\n"
        self.applyJournalPatch(jnl_rec)

        self.run_P4Transfer()
        self.assertCounters(4, 4)

    def testDodgyMerge(self):
        """Like testDirtyMerge but user has cherry picked and then hand edited - clean merge is different on disk"""
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")

        create_file(inside_file1, """
        Line 1
        Line 2
        Line 3
        """)
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        inside_file2 = os.path.join(inside, "inside_file2")
        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'inside_file1 -> inside_file2')

        self.source.p4cmd('edit', inside_file1)
        create_file(inside_file1, """
        Line 1 - changed file1
        Line 2
        Line 3
        """)
        self.source.p4cmd('submit', '-d', "Changed inside_file1")

        self.source.p4cmd('edit', inside_file1)
        create_file(inside_file1, """
        Line 1 - changed file1
        Line 2
        Line 3 - changed file1
        """)
        self.source.p4cmd('submit', '-d', "Changed inside_file1")

        self.source.p4cmd('edit', inside_file2)
        create_file(inside_file2, """
        Line 1
        Line 2 - changed file2
        Line 3
        """)
        self.source.p4cmd('submit', '-d', "Changed inside_file1")

        # Merge with edit - but cherry picked
        self.source.p4cmd('integrate', "%s#3,3" % inside_file1, inside_file2)

        class EditResolve(P4.Resolver):
            def resolve(self, mergeData):
                create_file(mergeData.result_path, """
        Line 1 - edited
        Line 2 - changed file2
        Line 3 - changed file1
        """)
                return 'ae'

        self.source.p4.run_resolve(resolver=EditResolve())
        self.source.p4cmd('submit', '-d', "Merge with edit")

        filelog = self.source.p4.run_filelog(inside_file2)
        self.assertEqual(len(filelog[0].revisions[0].integrations), 1)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'edit from')
        self.logger.debug("print:", self.source.p4.run_print(inside_file2))

        # Convert dirty merge to clean merge
        #
        # Dirty merge (fields 12/10)
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file2@ @//depot/inside/inside_file1@ 2 3 2 3 12 6
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file1@ @//depot/inside/inside_file2@ 2 3 2 3 10 6
        #
        # Clean merge (fields 0/1)
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file2@ @//depot/inside/inside_ file1@ 1 2 2 3 0 4
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file1@ @//depot/inside/inside_file2@ 2 3 1 2 1 4
        jnl_rec = "@rv@ 0 @db.integed@ @//depot/inside/inside_file2@ @//depot/inside/inside_file1@ 2 3 2 3 0 6\n" + \
            "@pv@ 0 @db.integed@ @//depot/inside/inside_file1@ @//depot/inside/inside_file2@ 2 3 2 3 1 6"
        self.applyJournalPatch(jnl_rec)

        filelog = self.source.p4.run_filelog(inside_file2)
        self.assertEqual(len(filelog[0].revisions[0].integrations), 1)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'merge from')
        self.logger.debug("print:", self.source.p4.run_print(inside_file2))

        self.run_P4Transfer()
        self.assertCounters(6, 6)
        self.logger.debug("print:", self.target.p4.run_print("//depot/import/inside_file2"))

    def testRemoteIntegs(self):
        """An integrate from a remote depot - gives the 'import' action """
        self.setupTransfer()
        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")

        create_file(inside_file1, "Some content")
        self.source.p4cmd("add", inside_file1)
        self.source.p4cmd("submit", '-d', 'inside_file1 added')

        filelog = self.source.p4.run_filelog(inside_file1)
        self.assertEqual(filelog[0].revisions[0].action, 'add')

        recs = self.dumpDBFiles("db.rev,db.revcx,db.revhx")
        self.logger.debug(recs)
        # '@pv@ 9 @db.rev@ @//depot/inside/inside_file1@ 1 0 0 1 1420649505 1420649505 581AB2D89F05C294D4FE69C623BDEF83 13 0 0 @//depot/inside/inside_file1@ @1.1@ 0 ',
        # @pv@ 0 @db.revcx@ 1 @//depot/inside/inside_file1@ 1 0 ',
        # '@pv@ 9 @db.revhx@ @//depot/inside/inside_file1@ 1 0 0 1 1420649505 1420649505 581AB2D89F05C294D4FE69C623BDEF83 13 0 0 @//depot/inside/inside_file1@ @1.1@ 0 '
        recs[0] = recs[0].replace("@ 1 0 0 1 ", "@ 1 0 5 1 ")
        recs[1] = recs[1].replace("@ 1 0", "@ 1 5")
        recs[2] = recs[2].replace("@ 1 0 0 1 ", "@ 1 0 5 1 ")
        recs[0] = recs[0].replace("@pv@", "@rv@")
        recs[1] = recs[1].replace("@pv@", "@rv@")
        recs[2] = recs[2].replace("@pv@", "@rv@")

        self.applyJournalPatch("\n".join(recs))

        filelog = self.source.p4.run_filelog(inside_file1)
        self.assertEqual(filelog[0].revisions[0].action, 'import')
        self.run_P4Transfer()
        self.assertCounters(1, 1)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file1')
        self.assertEqual(filelog[0].revisions[0].action, 'add')

    def testDirtyBranch(self):
        """A copy which is supposedly clean but in reality has been edited"""
        self.setupTransfer()
        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")

        create_file(inside_file1, "Some content")
        self.source.p4cmd("add", inside_file1)
        self.source.p4cmd("submit", '-d', 'inside_file1 added')

        inside_file2 = os.path.join(inside, "inside_file2")
        self.source.p4cmd("integrate", inside_file1, inside_file2)
        self.source.p4cmd("edit", inside_file2)
        append_to_file(inside_file2, "New content")
        self.source.p4cmd("submit", '-d', 'inside_file1 -> inside_file2 with edit')

        filelog = self.source.p4.run_filelog(inside_file1)
        self.assertEqual(len(filelog[0].revisions[0].integrations), 1)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'add into')

        # Needs to be created via journal patch
        #
        # Branch as edit (fields 2/11)
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file2@ @//depot/inside/inside_file1@ 0 1 0 1 2 2
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file1@ @//depot/inside/inside_file2@ 0 1 0 1 11 2
        #
        # Clean branch (fields 2/3)
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file2@ @//depot/inside/inside_file1@ 0 1 0 1 2 2
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file1@ @//depot/inside/inside_file2@ 0 1 0 1 3 2
        jnl_rec = "@rv@ 0 @db.integed@ @//depot/inside/inside_file2@ @//depot/inside/inside_file1@ 0 1 0 1 2 2\n" + \
            "@rv@ 0 @db.integed@ @//depot/inside/inside_file1@ @//depot/inside/inside_file2@ 0 1 0 1 3 2\n"
        self.applyJournalPatch(jnl_rec)

        filelog = self.source.p4.run_filelog(inside_file1)
        self.assertEqual(len(filelog[0].revisions[0].integrations), 1)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'branch into')
        filelog = self.source.p4.run_filelog(inside_file2)
        self.assertEqual(len(filelog[0].revisions[0].integrations), 1)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'branch from')

        self.run_P4Transfer()
        self.assertCounters(2, 2)

    def applyJournalPatch(self, jnl_rec):
        "Apply journal patch"
        jnl_fix = os.path.join(self.source.server_root, "jnl_fix")
        create_file(jnl_fix, jnl_rec)
        cmd = '%s -r "%s" -jr "%s"' % (self.source.p4d, self.source.server_root, jnl_fix)
        self.logger.debug("Cmd: %s" % cmd)
        output = subprocess.check_output(cmd, shell=True)

    def dumpDBFiles(self, tables):
        "Extract journal records"
        cmd = '%s -r "%s" -k %s -jd -' % (self.source.p4d, self.source.server_root, tables)
        self.logger.debug("Cmd: %s" % cmd)
        output = subprocess.check_output(cmd, shell=True, universal_newlines=True)
        self.logger.debug("Output: %s" % output)
        results = [r for r in output.split("\n") if re.search("^@pv@", r)]
        return results

    def testMultipleIntegrate(self):
        "Multipled integrations transferred in one go"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")

        create_file(inside_file1, "Some content")
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        inside_file2 = os.path.join(inside, "inside_file2")
        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'inside_file1 -> inside_file2')

        file3 = os.path.join(inside, "file3")
        self.source.p4cmd('integrate', inside_file2, file3)
        self.source.p4cmd('submit', '-d', 'inside_file2 -> File3')

        self.run_P4Transfer()
        self.assertCounters(3, 3)

        filelog1 = self.target.p4.run_filelog('//depot/import/inside_file1')
        filelog2 = self.target.p4.run_filelog('//depot/import/inside_file2')
        filelog3 = self.target.p4.run_filelog('//depot/import/file3')

        self.assertEqual(len(filelog1[0].revisions), 1)
        self.assertEqual(len(filelog2[0].revisions), 1)
        self.assertEqual(len(filelog3[0].revisions), 1)

        self.assertEqual(len(filelog1[0].revisions[0].integrations), 1)
        self.assertEqual(len(filelog2[0].revisions[0].integrations), 2)
        self.assertEqual(len(filelog3[0].revisions[0].integrations), 1)

    def testInsideOutside(self):
        "Test integrations between the inside<->outside where only one element is thus transferred"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        outside = localDirectory(self.source.client_root, "outside")

        # add from outside, integrate in
        outside_file = os.path.join(outside, 'outside_file')
        create_file(outside_file, "Some content")
        self.source.p4cmd('add', outside_file)
        self.source.p4cmd('submit', '-d', "Outside outside_file")

        inside_file2 = os.path.join(inside, 'inside_file2')
        self.source.p4cmd('integrate', outside_file, inside_file2)
        self.source.p4cmd('submit', '-d', "Integrated from outside to inside")

        self.run_P4Transfer()
        self.assertCounters(2, 1)

        changes = self.target.p4cmd('changes', )
        self.assertEqual(len(changes), 1, "Not exactly one change on target")
        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')
        self.assertEqual(filelog[0].revisions[0].action, "add")

        # edit from outside, integrated in
        self.source.p4cmd('edit', outside_file)
        append_to_file(outside_file, "More content")
        self.source.p4cmd('submit', '-d', "Outside outside_file edited")

        self.run_P4Transfer()
        self.assertCounters(2, 1) # counters will not move, no change within the client workspace's scope

        self.source.p4cmd('integrate', outside_file, inside_file2)
        self.source.p4.run_resolve('-at')
        self.source.p4cmd('submit', '-d', "Copied outside_file -> inside_file2")

        self.run_P4Transfer()
        self.assertCounters(4, 2)

        changes = self.target.p4cmd('changes', )
        self.assertEqual(len(changes), 2)
        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')
        self.assertEqual(filelog[0].revisions[0].action, "edit")

        # delete from outside, integrate in
        self.source.p4cmd('delete', outside_file)
        self.source.p4cmd('submit', '-d', "outside_file deleted")

        self.source.p4cmd('integrate', outside_file, inside_file2)
        self.source.p4cmd('submit', '-d', "inside_file2 deleted from outside_file")

        self.run_P4Transfer()
        self.assertCounters(6, 3)

        changes = self.target.p4cmd('changes', )
        self.assertEqual(len(changes), 3, "Not exactly three changes on target")
        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')
        self.assertEqual(filelog[0].revisions[0].action, "delete")

        # Add to both inside and outside in the same changelist - check only inside transferred
        create_file(inside_file2, "Different content")
        create_file(outside_file, "Different content")
        self.source.p4cmd('add', inside_file2, outside_file)
        self.source.p4cmd('submit', '-d', "adding inside and outside")

        self.run_P4Transfer()
        self.assertCounters(7, 4)

        change = self.target.p4.run_describe('4')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], '//depot/import/inside_file2')

    def testOutsideInsideDirtyCopy(self):
        "Test integrations between the inside<->outside where copy is not actually clean"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        outside = localDirectory(self.source.client_root, "outside")

        # Add and delete inside file
        inside_file = os.path.join(inside, 'inside_file')
        create_file(inside_file, "Inside content")
        self.source.p4cmd('add', inside_file)
        self.source.p4cmd('submit', '-d', "Added inside_file")
        self.source.p4cmd('delete', inside_file)
        self.source.p4cmd('submit', '-d', "Deleted inside_file")

        # add from outside, integrate in
        outside_file = os.path.join(outside, 'outside_file')
        create_file(outside_file, "Outside content")
        self.source.p4cmd('add', outside_file)
        self.source.p4cmd('submit', '-d', "Outside outside_file")

        self.source.p4cmd('integrate', outside_file, inside_file)
        self.source.p4cmd('edit', inside_file)
        append_to_file(inside_file, "extra stuff")
        self.source.p4cmd('submit', '-d', "Integrated from outside to inside")

        filelog = self.source.p4.run_filelog(outside_file)
        self.assertEqual(len(filelog[0].revisions[0].integrations), 1)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'add into')

        # Branch as edit (fields 2/11)
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file@ @//depot/outside/outside_file@ 0 1 2 3 2 4
        # @pv@ 0 @db.integed@ @//depot/outside/outside_file@ @//depot/inside/inside_file@ 2 3 0 1 11 4
        #
        # Clean branch (fields 2/3)
        # @pv@ 0 @db.integed@ @//depot/inside/inside_file@ @//depot/outside/outside_file@ 0 1 2 3 2 4
        # @pv@ 0 @db.integed@ @//depot/outside/outside_file@ @//depot/inside/inside_file@ 2 3 0 1 3 4
        jnl_rec = "@rv@ 0 @db.integed@ @//depot/inside/inside_file@ @//depot/outside/outside_file@ 0 1 2 3 2 4\n" + \
            "@rv@ 0 @db.integed@ @//depot/outside/outside_file@ @//depot/inside/inside_file@ 2 3 0 1 3 4\n"
        self.applyJournalPatch(jnl_rec)

        filelog = self.source.p4.run_filelog(inside_file)
        self.assertEqual(len(filelog[0].revisions[0].integrations), 1)
        self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'branch from')

        self.run_P4Transfer()
        self.assertCounters(4, 3)

    def testFailedSubmit(self):
        """Test what happens if submits fail, e.g. due to trigger"""
        self.setupTransfer()

        protect = self.source.p4.fetch_protect()
        self.logger.debug('protect:', protect)
        self.logger.debug(self.target.p4.save_protect(protect))
        self.target.p4cmd('admin', 'restart')
        self.target.p4.disconnect()
        self.target.p4.connect()

        triggers = self.target.p4.fetch_triggers()
        triggers['Triggers'] = ['test-trigger change-submit //depot/... "fail No submits allowed at this time"']
        self.target.p4.save_triggers(triggers)

        self.target.p4.disconnect()
        self.target.p4.connect()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file = os.path.join(inside, 'inside_file')
        create_file(inside_file, "Some content")
        self.source.p4cmd('add', inside_file)
        self.source.p4cmd('submit', '-d', "adding inside, hidden")

        result = self.run_P4Transfer()
        self.assertEqual(result, 1)

        self.assertCounters(0, 1)

    @unittest.skip("Not properly working test yet - issues around fetching protect...")
    def testHiddenFiles(self):
        """Test for adding and integrating from hidden files - not visible to transfer user"""
        self.setupTransfer()
        inside = localDirectory(self.source.client_root, "inside")

        inside_file = os.path.join(inside, 'inside_file')
        create_file(inside_file, "Some content")
        hidden_file = os.path.join(inside, 'hidden_file')
        create_file(hidden_file, "Some content")
        self.source.p4cmd('add', inside_file, hidden_file)
        self.source.p4cmd('submit', '-d', "adding inside, hidden")

        # Now change permissions

        self.source.p4cmd('edit', inside_file, hidden_file)
        append_to_file(inside_file, 'More content')
        append_to_file(hidden_file, 'More content')
        self.source.p4cmd('submit', '-d', "changing inside, hidden")

        p4superuser = 'p4newadmin'
        self.source.p4.user = p4superuser
        protect = self.source.p4.fetch_protect()
        protect['Protections'].append("write user %s * -//depot/...hidden_file" % P4USER)
        self.source.p4.save_protect(protect)
        self.logger.debug("protect:", self.source.p4.run_protect("-o"))

        self.source.p4.user = P4USER
        self.source.p4.disconnect()
        self.source.p4.connect()
        p = self.source.p4.run_protects('//depot/...')
        self.logger.debug('protects:', p)

        self.run_P4Transfer()
        self.assertCounters(2, 2)

        change = self.target.p4.run_describe('1')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], '//depot/import/inside_file')

        change = self.target.p4.run_describe('2')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], '//depot/import/inside_file')

        # Edit and integrate in
        self.source.p4.user = p4superuser
        self.source.p4cmd('edit', hidden_file)
        append_to_file(hidden_file, 'Yet More content')
        self.source.p4cmd('submit', '-d', "Edited")

        self.source.p4.user = P4USER
        self.run_P4Transfer()
        self.assertCounters(2, 2)

        self.source.p4.user = p4superuser
        inside_file2 = os.path.join(inside, 'inside_file2')
        self.source.p4cmd('integrate', hidden_file, inside_file2)
        self.source.p4cmd('submit', '-d', "Copied outside_file -> inside_file2")
        self.logger.debug(self.source.p4.run_print(inside_file2))

        self.source.p4.user = P4USER
        self.run_P4Transfer()
        self.assertCounters(4, 3)

        change = self.target.p4.run_describe('3')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], '//depot/import/inside_file2')
        self.assertEqual(change['action'][0], 'branch')

    def testMoves(self):
        """Test for Move and then a file being moved back, also move inside<->outside"""
        self.setupTransfer()
        inside = localDirectory(self.source.client_root, "inside")
        outside = localDirectory(self.source.client_root, "outside")

        original_file = os.path.join(inside, 'original', 'original_file')
        renamed_file = os.path.join(inside, 'new', 'new_file')
        create_file(original_file, "Some content")
        self.source.p4cmd('add', original_file)
        self.source.p4cmd('submit', '-d', "adding original file")

        self.source.p4cmd('edit', original_file)
        self.source.p4.run_move(original_file, renamed_file)
        self.source.p4cmd('submit', '-d', "renaming file")

        self.run_P4Transfer()
        self.assertCounters(2, 2)

        change = self.target.p4.run_describe('1')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], '//depot/import/original/original_file')

        change = self.target.p4.run_describe('2')[0]
        self.assertEqual(len(change['depotFile']), 2)
        self.assertEqual(change['depotFile'][0], '//depot/import/new/new_file')
        self.assertEqual(change['depotFile'][1], '//depot/import/original/original_file')
        self.assertEqual(change['action'][0], 'move/add')
        self.assertEqual(change['action'][1], 'move/delete')

        self.source.p4cmd('edit', renamed_file)
        self.source.p4.run_move(renamed_file, original_file)
        self.source.p4cmd('submit', '-d', "renaming file back")

        self.run_P4Transfer()
        self.assertCounters(3, 3)

        change = self.target.p4.run_describe('3')[0]
        self.assertEqual(len(change['depotFile']), 2)
        self.assertEqual(change['depotFile'][0], '//depot/import/new/new_file')
        self.assertEqual(change['depotFile'][1], '//depot/import/original/original_file')
        self.assertEqual(change['action'][0], 'move/delete')
        self.assertEqual(change['action'][1], 'move/add')

        # Now move inside to outside
        outside_file = os.path.join(outside, 'outside_file')
        self.source.p4cmd('edit', original_file)
        self.source.p4.run_move(original_file, outside_file)
        self.source.p4cmd('submit', '-d', "moving file outside")

        self.run_P4Transfer()
        self.assertCounters(4, 4)

        change = self.target.p4.run_describe('4')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], '//depot/import/original/original_file')
        self.assertEqual(change['action'][0], 'delete')

        # Now move outside to inside
        self.source.p4cmd('edit', outside_file)
        self.source.p4.run_move(outside_file, original_file)
        self.source.p4cmd('submit', '-d', "moving file from outside back to inside")

        self.run_P4Transfer()
        self.assertCounters(5, 5)

        change = self.target.p4.run_describe('5')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], '//depot/import/original/original_file')
        self.assertEqual(change['action'][0], 'add')

    def testMoveAfterDelete(self):
        """Test for Move after a Delete"""
        self.setupTransfer()
        inside = localDirectory(self.source.client_root, "inside")
        file1 = os.path.join(inside, 'file1')
        file2 = os.path.join(inside, 'file2')
        create_file(file1, "Some content")
        self.source.p4cmd("add", file1)
        self.source.p4cmd("submit", '-d', "adding original file")

        self.source.p4cmd("delete", file1)
        self.source.p4cmd("submit", '-d', "deleting original file")

        self.source.p4cmd("sync", "%s#1" % file1)
        self.source.p4cmd("edit", file1)
        self.source.p4cmd("move", file1, file2)
        try:
            self.source.p4cmd("resolve")
        except:
            pass
        try:
            self.source.p4cmd("submit", '-d', "renaming old version of original file")
        except:
            pass

        self.source.p4cmd("sync")
        self.source.p4cmd("submit", "-c3")

        self.run_P4Transfer()
        self.assertCounters(3, 3)

        change = self.target.p4.run_describe('3')[0]
        self.assertEqual(len(change['depotFile']), 2)
        self.assertEqual(change['depotFile'][0], '//depot/import/file1')
        self.assertEqual(change['depotFile'][1], '//depot/import/file2')
        self.assertEqual(change['action'][0], 'move/delete')
        self.assertEqual(change['action'][1], 'move/add')

    def testMoveAfterDeleteAndEdit(self):
        """Test for Move after a Delete when file content is also changed"""
        self.setupTransfer()
        inside = localDirectory(self.source.client_root, "inside")
        file1 = os.path.join(inside, 'file1')
        file2 = os.path.join(inside, 'file2')
        create_file(file1, "Some content")
        self.source.p4cmd("add", file1)
        self.source.p4cmd("submit", '-d', "adding original file")

        self.source.p4cmd("delete", file1)
        self.source.p4cmd("submit", '-d', "deleting original file")

        self.source.p4cmd("sync", "%s#1" % file1)
        self.source.p4cmd("edit", file1)
        self.source.p4cmd("move", file1, file2)
        try:
            self.source.p4cmd("resolve")
        except:
            pass
        try:
            self.source.p4cmd("submit", '-d', "renaming old version of original file")
        except:
            pass

        self.source.p4cmd("sync")
        append_to_file(file2, "A change")
        self.source.p4cmd("submit", "-c3")

        self.run_P4Transfer()
        self.assertCounters(3, 3)

        change = self.target.p4.run_describe('3')[0]
        self.assertEqual(len(change['depotFile']), 2)
        self.assertEqual(change['depotFile'][0], '//depot/import/file1')
        self.assertEqual(change['depotFile'][1], '//depot/import/file2')
        self.assertEqual(change['action'][0], 'move/delete')
        self.assertEqual(change['action'][1], 'move/add')

    def testMoveAndCopy(self):
        """Test for Move with subsequent copy of a file"""
        self.setupTransfer()
        inside = localDirectory(self.source.client_root, "inside")

        original_file = os.path.join(inside, 'original', 'original_file')
        renamed_file = os.path.join(inside, 'new', 'new_file')
        branched_file = os.path.join(inside, 'branch', 'new_file')
        create_file(original_file, "Some content")
        self.source.p4cmd('add', original_file)
        self.source.p4cmd('submit', '-d', "adding original file")

        self.source.p4cmd('edit', original_file)
        self.source.p4cmd('move', original_file, renamed_file)
        self.source.p4cmd('submit', '-d', "renaming file")

        self.source.p4cmd('integrate', '-Di', renamed_file, branched_file)
        self.source.p4cmd('submit', '-d', "copying files")

        self.run_P4Transfer()
        self.assertCounters(3, 3)

        change = self.target.p4.run_describe('2')[0]
        self.assertEqual(len(change['depotFile']), 2)
        self.assertEqual(change['depotFile'][0], '//depot/import/new/new_file')
        self.assertEqual(change['depotFile'][1], '//depot/import/original/original_file')
        self.assertEqual(change['action'][0], 'move/add')
        self.assertEqual(change['action'][1], 'move/delete')

        change = self.target.p4.run_describe('3')[0]
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], '//depot/import/branch/new_file')
        self.assertEqual(change['action'][0], 'branch')

    def testIntegDt(self):
        """Test for integ -Dt being required - only necessary with older integ.engine"""
        self.setupTransfer()
        self.target.p4cmd("configure", "set", "dm.integ.engine=2")
        self.source.p4cmd("configure", "set", "dm.integ.engine=2")

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        inside_file2 = os.path.join(inside, "inside_file2")
        create_file(inside_file1, "Test content")
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'inside_file2 added')

        self.source.p4cmd('delete', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 deleted')

        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'inside_file2 deleted')

        create_file(inside_file1, "Test content again")
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'inside_file2 added')

        self.run_P4Transfer()
        self.assertCounters(6, 6)

    def testInteg2to1(self):
        """Test for integrating 2 versions into single target."""
        self.setupTransfer()
        # self.target.p4cmd("configure", "set", "dm.integ.engine=2")
        # self.source.p4cmd("configure", "set", "dm.integ.engine=2")

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        inside_file2 = os.path.join(inside, "inside_file2")
        create_file(inside_file1, "Test content")
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        self.source.p4cmd('edit', inside_file1)
        append_to_file(inside_file1, "more stuff")
        self.source.p4cmd('submit', '-d', 'inside_file1 edited')

        class EditResolve(P4.Resolver):
            def resolve(self, mergeData):
                create_file(mergeData.result_path, "new contents\nsome more")
                return 'ae'

        self.source.p4cmd('integrate', "%s#1" % inside_file1, inside_file2)
        self.source.p4cmd('add', inside_file2)
        self.source.p4cmd('integrate', "%s#2,2" % inside_file1, inside_file2)
        self.source.p4cmd('edit', inside_file2)
        self.source.p4.run_resolve(resolver=EditResolve())
        self.source.p4cmd('submit', '-d', 'inside_file2 added with multiple integrates')

        self.source.p4cmd('filelog', inside_file2)
        
        self.run_P4Transfer()
        self.assertCounters(3, 3)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')[0]
        self.logger.debug(filelog)
        self.assertEqual(len(filelog.revisions[0].integrations), 2)
        self.assertEqual(filelog.revisions[0].integrations[0].how, "edit from")
        self.assertEqual(filelog.revisions[0].integrations[1].how, "add from")

    def testAddFrom(self):
        """Test for adding a file which has in itself then branched."""
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        inside_file2 = os.path.join(inside, "inside_file2")
        inside_file3 = os.path.join(inside, "inside_file3")
        create_file(inside_file1, "Test content")
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        self.source.p4cmd('delete', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 deleted')

        self.source.p4cmd('sync', "%s#1" % inside_file1)
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('move', inside_file1, inside_file2)
        append_to_file(inside_file2, "more stuff")
        self.source.p4cmd('submit', '-d', 'inside_file2 created by branching with add')

        filelog = self.source.p4.run_filelog(inside_file2)[0]
        self.logger.debug(filelog)
        self.assertEqual(len(filelog.revisions[0].integrations), 1)
        self.assertEqual(filelog.revisions[0].integrations[0].how, "add from")

        self.source.p4cmd('integrate', inside_file2, inside_file3)
        self.source.p4cmd('submit', '-d', 'inside_file3 created as copy')

        self.run_P4Transfer()
        self.assertCounters(4, 4)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')[0]
        self.logger.debug(filelog)
        self.assertEqual(len(filelog.revisions[0].integrations), 2)
        self.assertEqual(filelog.revisions[0].integrations[0].how, "add from")

    def testDeleteDelete(self):
        """Test for a delete on top of a delete."""
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        create_file(inside_file1, "Test content")
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        self.source.p4cmd('delete', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 deleted')

        with self.source.p4.at_exception_level(P4.P4.RAISE_ERRORS):
            self.source.p4cmd('sync', "%s#1" % '//depot/inside/inside_file1')
            self.source.p4cmd('delete', '//depot/inside/inside_file1')
        self.source.p4cmd('opened')
        try:
            self.source.p4cmd('submit', '-d', 'inside_file1 deleted again')
        except Exception as e:
            self.logger.info(str(e))
            err = self.source.p4.errors[0]
            if re.search("Submit failed -- fix problems above then", err):
                m = re.search("p4 submit -c (\d+)", err)
                if m:
                    self.source.p4cmd('submit', '-c', m.group(1))

        filelog = self.source.p4.run_filelog('//depot/inside/inside_file1')[0]
        self.logger.debug(filelog)
        self.assertEqual(filelog.revisions[0].action, 'delete')
        self.assertEqual(filelog.revisions[1].action, 'delete')

        self.run_P4Transfer()
        self.assertCounters(3, 3)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file1')[0]
        self.logger.debug(filelog)
        self.assertEqual(filelog.revisions[0].action, 'delete')
        self.assertEqual(filelog.revisions[1].action, 'delete')

    def testIntegDeleteIgnore(self):
        """Test for an integrated delete with ignore."""
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        outside = localDirectory(self.source.client_root, "outside")
        inside_file1 = os.path.join(inside, "inside_file1")
        inside_file2 = os.path.join(inside, "inside_file2")
        inside_file3 = os.path.join(inside, "inside_file3")
        outside_file1 = os.path.join(outside, 'outside_file1')
        create_file(inside_file1, "Test content")
        create_file(outside_file1, "Some content")
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('add', outside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        self.source.p4cmd('integ', '-Rb', inside_file1, inside_file2)
        self.source.p4cmd('integ', '-Rb', outside_file1, inside_file3)
        self.source.p4cmd('resolve', '-ay')
        self.source.p4cmd('submit', '-d', 'inside_files deleted')

        filelog = self.source.p4.run_filelog('//depot/inside/inside_file2')[0]
        self.logger.debug(filelog)
        self.assertEqual(filelog.revisions[0].action, 'delete')
        filelog = self.source.p4.run_filelog('//depot/inside/inside_file3')[0]
        self.logger.debug(filelog)
        self.assertEqual(filelog.revisions[0].action, 'delete')

        self.run_P4Transfer()
        self.assertCounters(2, 2)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')[0]
        self.logger.debug(filelog)
        self.assertEqual(filelog.revisions[0].action, 'delete')
        files = self.target.p4.run_files('//depot/import/...')
        self.logger.debug(files)
        self.assertEqual(len(files), 2)
        self.assertEqual(files[0]['depotFile'], '//depot/import/inside_file1')
        self.assertEqual(files[1]['depotFile'], '//depot/import/inside_file2')

    def testMultipleIntegrates(self):
        """Test for more than one integration into same target revision"""
        self.setupTransfer()
        self.target.p4cmd("configure", "set", "dm.integ.engine=2")
        self.source.p4cmd("configure", "set", "dm.integ.engine=2")

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        inside_file2 = os.path.join(inside, "inside_file2")
        create_file(inside_file1, "Test content")
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'inside_file2 added')

        self.source.p4cmd('edit', inside_file1)
        append_to_file(inside_file1, "more stuff")
        self.source.p4cmd('submit', '-d', 'inside_file1 edited')

        self.source.p4cmd('edit', inside_file1)
        append_to_file(inside_file1, "/nyet more stuff")
        self.source.p4cmd('submit', '-d', 'inside_file1 edited again')

        self.source.p4cmd('integrate', "%s#2" % inside_file1, inside_file2)
        self.source.p4cmd('resolve', '-as')

        self.source.p4cmd('integrate', "%s#3,3" % inside_file1, inside_file2)
        self.source.p4cmd('resolve', '-ay') # Ignore

        self.source.p4cmd('submit', '-d', 'integrated twice seperately into file2')

        self.run_P4Transfer()
        self.assertCounters(5, 5)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file2')[0]
        self.logger.debug(filelog)
        self.assertEqual(len(filelog.revisions[0].integrations), 2)
        self.assertEqual(filelog.revisions[0].integrations[0].how, "ignored")
        self.assertEqual(filelog.revisions[0].integrations[1].how, "copy from")

    def testIntegI(self):
        """Test for integ -i required - only necessary with older integ.engine"""
        self.setupTransfer()
        self.target.p4cmd("configure", "set", "dm.integ.engine=2")

        inside = localDirectory(self.source.client_root, "inside")
        original_file = os.path.join(inside, 'original', 'original_file')
        new_file = os.path.join(inside, 'branch', 'new_file')
        create_file(original_file, "Some content")
        create_file(new_file, "Some new content")

        self.source.p4cmd('add', original_file)
        self.source.p4cmd('submit', '-d', "adding file1")

        self.source.p4cmd('add', new_file)
        self.source.p4cmd('submit', '-d', "adding file2")

        self.source.p4cmd('edit', original_file)
        create_file(original_file, "Some content addition")
        self.source.p4cmd('submit', '-d', "adding file1")

        self.source.p4cmd('integ', original_file, new_file)
        self.source.p4cmd('resolve', '-at')
        self.source.p4cmd('submit', '-d', "adding file2")

        self.run_P4Transfer()
        self.assertCounters(4, 4)

        change = self.target.p4.run_describe('4')[0]
        self.logger.debug(change)
        self.assertEqual(len(change['depotFile']), 1)
        self.assertEqual(change['depotFile'][0], '//depot/import/branch/new_file')
        self.assertEqual(change['action'][0], 'integrate')

    def testObliteratedSource(self):
        "File has been integrated and then source obliterated"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        outside = localDirectory(self.source.client_root, "outside")

        # add from outside, integrate in

        outside_file1 = os.path.join(outside, 'outside_file1')
        create_file(outside_file1, "Some content")
        self.source.p4cmd('add', outside_file1)
        self.source.p4cmd('submit', '-d', "Outside outside_file1")

        inside_file2 = os.path.join(inside, 'inside_file2')
        self.source.p4cmd('integrate', outside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', "Integrated from outside to inside")
        self.source.p4.run_obliterate('-y', outside_file1)

        self.run_P4Transfer('--stoponerror')
        self.assertCounters(2, 1)

    def testKeywords(self):
        "Look for files with keyword expansion"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")

        files = []
        for f in range(1, 5):
            fname = "file{}".format(f)
            files.append(os.path.join(inside, fname))

        for fname in files:
            create_file(fname, 'Test content')
            self.source.p4cmd('add', fname)

        self.source.p4.run_reopen("-t", "ktext", files[0])
        self.source.p4.run_reopen("-t", "kxtext", files[1])
        self.source.p4.run_reopen("-t", "text+kmx", files[2])
        self.source.p4.run_reopen("-t", "ktext+xkm", files[3])

        self.source.p4cmd('submit', '-d', 'File(s) added')

        self.run_P4Transfer("--nokeywords")

        newFiles = self.target.p4cmd('files', "//...")
        self.assertEqual(len(newFiles), 4)
        self.assertEqual(newFiles[0]['type'], "text")
        self.assertEqual(newFiles[1]['type'], "xtext")
        self.assertEqual(newFiles[2]['type'], "text+mx")
        self.assertEqual(newFiles[3]['type'], "text+mx")

        fname = files[0]
        self.source.p4cmd('edit', "-t", "+k", fname)
        append_to_file(fname, "More stuff")

        self.source.p4cmd('submit', '-d', 'File edited')

        self.run_P4Transfer("--nokeywords")

        files = self.target.p4cmd('files', "//depot/import/file1")
        self.assertEqual(files[0]['type'], "text")

    def testTempobjFiletype(self):
        """Tests for files with no content"""
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        inside_file2 = os.path.join(inside, "inside_file2")
        inside_file3 = os.path.join(inside, "inside_file3")
        inside_file4 = os.path.join(inside, "inside_file4")

        create_file(inside_file1, "Test content")
        create_file(inside_file2, "Test content")
        create_file(inside_file3, "Test content")
        self.source.p4cmd('add', '-t', 'text+S2', inside_file1)
        self.source.p4cmd('add', '-t', 'binary+S', inside_file2)
        self.source.p4cmd('add', '-t', 'binary+S', inside_file3)
        self.source.p4cmd('submit', '-d', 'files added')

        self.source.p4cmd('integrate', inside_file3, inside_file4)
        self.source.p4cmd('submit', '-d', 'integrated')

        self.source.p4cmd('edit', inside_file1)
        append_to_file(inside_file1, 'New text')
        self.source.p4cmd('delete', inside_file2)
        self.source.p4cmd('submit', '-d', 'version 2')

        self.source.p4cmd('edit', inside_file1)
        append_to_file(inside_file1, 'More text')
        self.source.p4cmd('edit', inside_file3)
        append_to_file(inside_file1, 'More textasdf')
        self.source.p4cmd('submit', '-d', 'version 3')

        self.source.p4cmd('integrate', inside_file3, inside_file4)
        self.source.p4cmd('resolve', '-as')
        self.source.p4cmd('submit', '-d', 'integrated')

        self.run_P4Transfer()
        self.assertCounters(5, 5)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file1')
        revisions = filelog[0].revisions
        self.logger.debug('test:', revisions)
        self.assertEqual(len(revisions), 3)
        for rev in revisions:
            self.logger.debug('test:', rev.rev, rev.action, rev.digest)
            self.logger.debug(self.target.p4.run_print('//depot/import/inside_file1#%s' % rev.rev))
        filelog = self.target.p4.run_filelog('//depot/import/inside_file4')
        self.assertEqual(filelog[0].revisions[0].action, 'integrate')
        self.assertEqual(filelog[0].revisions[1].action, 'purge')

    def testBackoutMove(self):
        """In this test we move a file and then rollback to the previous changelist
        way that P4V does - this does an 'add -d'"""
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")

        create_file(inside_file1, "Test content")
        self.source.p4cmd('add', inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        inside_file2 = os.path.join(inside, "inside_file2")
        self.source.p4cmd('edit', inside_file1)
        self.source.p4.run_move(inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'moved inside_file1 to inside_file2')

        self.source.p4.run_sync('-f', '%s#1' % inside_file1)
        self.source.p4cmd('add', '-d', inside_file1)
        self.source.p4cmd('submit', '-d', 'new inside_file1')

        self.source.p4cmd('edit', inside_file1)
        create_file(inside_file1, "Different test content")
        self.source.p4cmd('submit', '-d', 'changed inside_file1 again')

        self.run_P4Transfer()
        self.assertCounters(4, 4)

        filelog = self.target.p4.run_filelog('//depot/import/inside_file1')
        revisions = filelog[0].revisions
        self.logger.debug('test:', revisions)
        self.assertEqual(len(revisions), 4)
        for rev in revisions:
            self.logger.debug('test:', rev.rev, rev.action, rev.digest)
            self.logger.debug(self.target.p4.run_print('//depot/import/inside_file1#%s' % rev.rev))
        for rev in self.source.p4.run_filelog('//depot/inside/inside_file1')[0].revisions:
            self.logger.debug('test-src:', rev.rev, rev.action, rev.digest)
            self.logger.debug(self.source.p4.run_print('//depot/inside/inside_file1#%s' % rev.rev))

        self.assertEqual(revisions[3].action, "add")
        self.assertEqual(revisions[1].action, "add")
        self.assertEqual(revisions[0].action, "edit")
        # 2 latest revisions should not be the same, but the revisions before and after back out
        # should be.
        self.assertEqual(revisions[1].digest, revisions[3].digest)
        self.assertNotEqual(revisions[0].digest, revisions[1].digest)

    def testAddProblem(self):
        "Trying to reproduce problem reported where a branch required a resolve"
        self.setupTransfer()

        inside = localDirectory(self.source.client_root, "inside")
        inside_file1 = os.path.join(inside, "inside_file1")
        create_file(inside_file1, 'Test content')

        self.source.p4cmd('add', "-t", "xtext", inside_file1)
        self.source.p4cmd('submit', '-d', 'inside_file1 added')

        self.source.p4cmd('edit', inside_file1)
        with open(inside_file1, 'a') as f:
            print("more content", file=f)
        self.source.p4cmd('submit', '-d', 'inside_file1 updated')

        self.source.p4cmd('edit', inside_file1)
        with open(inside_file1, 'a') as f:
            print("even more content", file=f)
        self.source.p4cmd('submit', '-d', 'inside_file1 updated (again)')

        inside_file2 = os.path.join(inside, "inside_file2")
        self.source.p4cmd('integrate', inside_file1, inside_file2)
        self.source.p4cmd('submit', '-d', 'branched into inside_file2')

        self.run_P4Transfer()

        changes = self.target.p4cmd('changes', )
        self.assertEqual(len(changes), 4, "Target does not have exactly four changes")

        files = self.target.p4cmd('files', '//depot/...')
        self.assertEqual(len(files), 2, "Target does not have exactly two files")

        self.assertCounters(4, 4)

if __name__ == '__main__':
    unittest.main()
# Change User Description Committed
#98 27669 Robert Cowham Retired (deleted) this version of the script - and include a reference to its replacement:

https://github.com/perforce/p4transfer
#97 26350 Robert Cowham Handle move/delete where previous rev is obliterated
#96 26349 Robert Cowham Handle move/add where source obliterated
#95 26344 Robert Cowham New parameter --end-datetime added to allow unattended runs within a window of opportunity.
#94 26277 Robert Cowham Extra undo test
#93 26276 Robert Cowham Fix problem with replicating basic undo
#92 23271 Robert Cowham Allow transfers to be done by non-superusers (only requiring write and review privilege).
In non-super mode, changelist date/time and author fields are not updated.
#91 22794 Robert Cowham Allow change_map to be in a subdir
#90 22515 Robert Cowham Removed preview option which was confusing as it wasn't well tested.
#89 22466 Robert Cowham Improved preview option handling - with test.
#88 22465 Robert Cowham Added tests for r99.2 created depot (no filesize/digest stored)
Fixed problem with preview failing comparison
Add --p4d option to tests to use different p4d version
#87 22461 Robert Cowham Tweak extraction of db.* data to support 12.2 server
#86 22278 Robert Cowham Fix problem transferring into 2012.2 server - need to convert types to canonical type
#85 19691 Robert Cowham Fix problems with selective integrations (cherry picking).
Has changed behaviour of a few tests.
#84 18701 Robert Cowham Handle the case of an edit after an archived version.
#83 18674 Robert Cowham Fix tests failing due to new canonical form of filetypes with 2015.2 (xtext -> text+x).
Unicode works for Mac/Linux with 2015.2+ P4Python installed so don't skip test
and update doc.
#82 16568 Robert Cowham Require clobber option in both source/target to avoid sync errors.
As a result need to improve change_map.csv handling.
#81 16425 Robert Cowham Remove preflight option and add in test for maximum option.
#80 15827 Robert Cowham Avoid unnecessary resyncs of edited files due to incorrect digest
calculation and comparison.
Update failing test.
#79 15529 Robert Cowham Fix problem when integrating cherry pick with different filetype.
#78 14139 Robert Cowham Fix strange problem with copy from & branch from to same revision.
#77 13959 Robert Cowham Changed utf16 sync problem handling to raise an exception recommending a retype and linking
to KB: http://answers.perforce.com/articles/KB/3117
#76 13921 Robert Cowham Handle unsyncable utf16 files:
- note the sync errors
- add them to ignore list
- if and edit fails and previous version was utf16 then convert to add
#75 13576 Robert Cowham Fix for when filetypes are not integrated on purpose.
#74 12942 Robert Cowham Make test env more cross platform re P4CONFIG settings.
#73 12941 Robert Cowham Make sure filetype changes are propagated via integrate even when integ.engine=2.
Fix a couple of tests failing on OS X.
#72 12570 Robert Cowham Fix link handling problem.
#71 12549 Robert Cowham Handle symlinks (on Unix at least)
#70 12548 Robert Cowham Optimize slightly by checking whole file first before doing line by line search for RCS keywords.
#69 12547 Robert Cowham Handle RCS keyword files which have been merged in a dodgy manner (what should be a dirty merge pretending to be a clean merge).
#68 12532 Robert Cowham Handle edit failing when doing old style rename.
#67 12524 Robert Cowham Handled integrating a delete ontop of a deleted.
#66 12495 Robert Cowham Fix problem of missing move/add records where source was outside transfer workspace.
#65 12489 Robert Cowham Handle the multiple overlapping integrates with strange resolve -ay
#64 12486 Robert Cowham Test for overlapping integrates which passes in this form.
#63 12472 Robert Cowham Steps along the way to testing a move from outside.
#62 12409 Robert Cowham Handle utf16 with faulty BOM
#61 11882 Robert Cowham Test for new revision with 2 integrates into it, including basic copy.
Reduce number of forced integrates if there are multiple.
#60 11881 Robert Cowham Handled a multiple integrate including a copy after which the file was modified.
#59 11863 Robert Cowham Use 'unix' instead of 'share' in transfer client line-endings.
To avoid issues with MD5 checksums and the like.
#58 11839 Robert Cowham Check for edited files changed.
Remove unused parameter from read/write content calls (filetype).
Fix problem with purged files being read-only.
#57 11835 Robert Cowham Reordered tests into more logical groups.
No functional change.
#56 11728 Robert Cowham Handle very old logic (pre-tamper protection) where an integrate with resolve -ay could occur and the content
of the file be edited in the same change.
The result is demoted from an integrate to and edit, but still has the integraiton record.
#55 11548 Robert Cowham Remove unused logging calls.
#54 11518 Robert Cowham Handle text filetypes properly with MD5 calcuations and line endings.
Require source and client workspaces to have LineEnd='share' (with test).
#53 11478 Robert Cowham Add the ability to batch changes up (default 20,000)
Includes tests and adjustments to loggin.
#52 11445 Robert Cowham Handle delete integration requiring the force flag.
#51 11430 Robert Cowham Expanded testIntegDeleteProblem and fixed the resulting test failure.
#50 11426 Robert Cowham Rename a test for clarity.
#49 11425 Robert Cowham Handle strange case of 3 seperate integrations into a single (new) revision.
#48 11407 Robert Cowham Fix problem where an integrate defaults to resolving an add as a delete and requires
a second step to be resolved as an add (with integ.engine=3).
#47 11342 Robert Cowham Handle renames which were badly propagated via individual integs of the move/add and move/delete using the
integ.engine=2
#46 11304 Robert Cowham Fix files with perforce wildcars in their names when they are branched from outside.
#45 11300 Robert Cowham Create specific exception classes.
Handle a integrate -Rb with ignore which comes from "outside" the source workspace.
#44 11296 Robert Cowham Add another file to the Wildcards test with a C# directory in its name
#43 11295 Robert Cowham Handle an integrate where the source has been obliterated (fixes an index error).
The target action will be an edit.
#42 11293 Robert Cowham Handle integ -Rb which is ignored where the source is not in the source client
Just ignore the revision in this case.
#41 11282 Robert Cowham Reduce sleep time between tests (required to avoid files being locked).
Run all tests down from 50s -> 15s!
#40 11276 Robert Cowham Remove unnecessary comments for assertions - if they fail then the default error message shown is sufficient.
#39 11275 Robert Cowham Handle files with action 'archive' - they have been archived in an archive depot and
are ignored by P4Transfer (with a warning in the log file).
#38 11271 Robert Cowham Handle files with action 'import' which means they came from remote depots.
This action is changed to an 'add' in the target.
#37 11268 Robert Cowham Add tests for other wildcards.
#36 11266 Robert Cowham Ensure that workspaces have a matching right hand side.
#35 11265 Robert Cowham Handle an integrate with ignore to a deleted first revision
Tweak tests to work with Python2.7/3.3 as regards unittest.assertRegex
#34 11257 Robert Cowham Fix handling of purged files (+Sn).
#33 11237 Robert Cowham Added an (optional) change_map file which is appended to after submits.
It shows:
sourceP4Port,sourceChangeNo,targetChangeNo

Makes for easy searching (offline or via p4 grep) as opposed to looking through change
list descriptions.
#32 11234 Robert Cowham Implement configurable change descriptions
#31 11230 Robert Cowham Handle a delete on top of a delete.
#30 11049 Robert Cowham Handled strange 'add from' single integration (which is a backout of an earlier change followed by a move)
#29 10739 Robert Cowham Fix for integrating 2 versions into single target where the target is revision 1.
#28 10509 Robert Cowham Deal with multiple integrates into the same target.
#27 10504 Robert Cowham Test for integrate with -Dt being required (previous change)
#26 10502 Robert Cowham Handle need to do forced integrates
#25 10496 Robert Cowham Test for failed submit.
When running sizes command, restrict to workspace view for performance (avoid spec depot!)
#24 10485 Robert Cowham Refactored tests to log basically all p4 commands.
#23 10484 Robert Cowham Test for file content changed when adding a file.
Minor refactor for the above.
Added testOutsideInsideDirtyCopy
#22 10481 Robert Cowham Refactored applying journal patches.
Include test for branch where file is edited even though it shouldn't be.
#21 10479 Robert Cowham Resync after move/add if necessary due to file content changes
#20 10475 Robert Cowham Handle the rename of a deleted file - rather esoteric but possible!
#19 10469 Robert Cowham Check file size and digest after a clean merge
Handled dodgy merges - cherry picked where they were edited afterwards
#18 10468 Robert Cowham Proper test for dirty merge which is changed to be a supposed clean merge.
#17 10467 Robert Cowham Fix problem requiring integ -i (with integ.engine=2)
Improved validation after submission.
#16 10438 Robert Cowham Fixed bug where effectively selective integrations were being done on the source.
Started better comparison preparation for changelist validation.
#15 10147 Robert Cowham Fix problem with move/add where there are other integration records on the file that is being added.
#14 10141 Robert Cowham Enhanced logging - added specifid ids for different actions.
Log a few extra items, including script version and client mappings.
Fixed bug regarding the branching of a move file.
#13 10136 Robert Cowham Added comments to test cases.
Remove unneeded whitespace.
#12 10135 Robert Cowham Added test for moving from outside to inside
#11 10134 Robert Cowham Fixed problem where the target of a move is outside the client view - converts move/delete to a delete
#10 10110 Robert Cowham Remove unused vars as per pyflakes warnings
Add some extra debug logging of paths executed
Remove unused code when processing 'add from'
#9 10099 Sven Erik Knop Added additional test case after reported problem
#8 10098 Robert Cowham Fixed bug when a move (rename) by P4V was backed out via 'revert to revision'
Removed python3 warning
#7 10005 Sven Erik Knop Added move test.
Also fixed Unicode test to use platform specific codec:
'mbcs' on Windows, 'UTF-8' on Mac.
#6 9731 Robert Cowham Added tests for protections - hide something that is in the client view and check that changes are correctly handled.
#5 9730 Robert Cowham Fixed problem where a changelist to both files inside and outside the workspace view in the same changelist are handled
#4 9729 Robert Cowham More small refactoring
#3 9728 Robert Cowham Minor refactoring.
Fixed a couple of pylint warnings.
#2 9727 Robert Cowham Refactored names of files for clarity
#1 9726 Robert Cowham Rename test harness
//guest/perforce_software/p4transfer/P4TransferTest.py
#5 9644 Robert Cowham Renamed PerforceTransfer to P4Transfer to make tests work.
#4 9641 Robert Cowham Latest changes by Robert.
Added new options:
--repeat for continuous operation
--sample-config to produce sample config
-Improved logging and notification options (via emails if configured)
-Retries in case of error.
#3 9474 Sven Erik Knop Added test to verify that edited files will also have their +k removed.
#2 9473 Sven Erik Knop Added the ability to remove +k from the target

Currently tested for add, need to test for edit and integrate as well

invoked by using option -k or --nokeywords
#1 9170 Sven Erik Knop Branched PerforceTransfer from private area to perforce_software
This tool will now get back its original name P4Transfer.
//guest/sven_erik_knop/P4Pythonlib/scripts/transferTest.py
#10 8463 Sven Erik Knop Fixed further problem with files that have an illegal file name containing
@,#,* or %. Now it is possible to re-edit the file again as well.

Added test case to prove the point.
#9 8461 Sven Erik Knop Fixed adding files with illegal chars like '@'.

Also added test case.
#8 8432 Sven Erik Knop Added pre-flight checks (-p) to avoid overwriting existing files.
#7 8425 Sven Erik Knop Make PerforceTransfer unidirectional from source to target.

Adjusted test cases accordingly.

Still missing:
   Update change user and timestamp to the source user and timestamp
   Reverify ktext files affected by the change update.
   Add proper logging
#6 8216 Sven Erik Knop Added test cases for integration from outside transfer scope.
Fixed bug for integrated deletes from the outside.
#5 8215 Sven Erik Knop Upgraded test to include merge w/ edit
Fixed a bug in PerforceTransfer.py avoiding a tamper check error.
#4 8213 Sven Erik Knop Test case for re-add added
#3 8212 Sven Erik Knop Added integrate-delete test case
Solved integrate-delete problem in PerforceTransfer
#2 8211 Sven Erik Knop Additional test cases for integrate
Fixed a bug with "ignore", can now be replicated.
#1 8210 Sven Erik Knop Fixed a bug in PerforceTransfer where an add followed by an integ to another branch would break the add.

Also added the beginning of a test framework to catch those kind of problems in the future. Currently the test framework only checks add, edit, delete and simple integrates.