transferTest.py #9

  • //
  • guest/
  • sven_erik_knop/
  • P4Pythonlib/
  • scripts/
  • transferTest.py
  • View
  • Commits
  • Open Download .zip Download (19 KB)
# -*- encoding: UTF8 -*-

# tests missing
# - maximum
# - preflight check

from __future__ import print_function

import sys
import time
import P4
import unittest, os, types, shutil, stat
from subprocess import Popen, PIPE

if sys.version_info[0] >= 3:
	from configparser import ConfigParser
else:
	from ConfigParser import ConfigParser

P4D="p4d"
P4USER="testuser"
P4CLIENT="test_ws"
TRANSFER_CLIENT="transfer"
TRANSFER_CONFIG="transfer.cfg"
PYTHON="python"
PERFORCE_TRANSFER="PerforceTransfer.py"
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.mkdir(directory)

class P4Server:
	def __init__(self, root):
		self.root = root
		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.p4.run_depots() # triggers creation of the user
		self.p4.run_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()
	
class TestPerforceTransfer(unittest.TestCase):

	def setUp(self):
		self.setDirectories()
		
	def tearDown(self):
		self.source.shutDown()
		self.target.shutDown()
		time.sleep( 1 )
		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.target = P4Server(os.path.join(self.transfer_root, 'target'))
		
		self.transfer_client_root = os.path.join(self.transfer_root, 'transfer_client')
		ensureDirectory(self.transfer_client_root)
	
	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"""
		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)
		
		# create the config file
		
		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)
		self.parser.set('source', 'counter', '0')
		
		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)
		self.parser.set('target', 'counter', '0')
		
		self.parser.add_section('general')
		self.parser.set('general','logfile','/tmp/test.log')
		
		# 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_PerforceTransfer(self):
		process = Popen([PYTHON, PERFORCE_TRANSFER, '-c', self.transfer_cfg], stdout=PIPE, stderr=PIPE)
		err, out = process.stderr, process.stdout
		
		result = out.readlines()
		out.close()
		errors = err.readlines()
		err.close()
		
		if errors:
			print(errors, file=sys.stderr)
		
		return result
		
	def assertCounters(self, sourceValue, targetValue):
		with open(self.transfer_cfg) as f:
			try:
				self.parser.read_file(f)
			except AttributeError: 
				self.parser.readfp(f)
		
		sourceCounter = self.parser.getint("source", 'counter')
		targetCounter = self.parser.getint("target", 'counter')
		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 testAdd(self):
		self.setupTransfer()
		
		inside = os.path.join(self.source.client_root, "inside")
		ensureDirectory(inside)
		file1 = os.path.join(inside, "file1")

		with open(file1, 'w') as f:
			f.write('Test content')
		self.source.p4.run_add(file1)
		self.source.p4.run_submit('-d', 'File1 added')
		
		self.run_PerforceTransfer()
		
		changes = self.target.p4.run_changes()
		self.assertEqual(len(changes), 1, "Target does not have exactly one change")
		self.assertEqual(changes[0]['change'], "1", "Target change is not 1")
		
		files = self.target.p4.run_files('//depot/...')
		self.assertEqual(len(files), 1, "Target does not have exactly one file")
		self.assertEqual(files[0]['depotFile'], '//depot/import/file1', "File not transferred to correct place")
		
		self.assertCounters(1,1)
 	
	def testAddWildcardChars(self):
		self.setupTransfer()
		
		inside = os.path.join(self.source.client_root, "inside")
		ensureDirectory(inside)
		file1 = os.path.join(inside, "@file1")

		with open(file1, 'w') as f:
			f.write('Test content')
		self.source.p4.run_add('-f',file1)
		self.source.p4.run_submit('-d', 'File1 added')
		
		self.run_PerforceTransfer()
		
		changes = self.target.p4.run_changes()
		self.assertEqual(len(changes), 1, "Target does not have exactly one change")
		self.assertEqual(changes[0]['change'], "1", "Target change is not 1")
		
		files = self.target.p4.run_files('//depot/...')
		self.assertEqual(len(files), 1, "Target does not have exactly one file")
		self.assertEqual(files[0]['depotFile'], '//depot/import/%40file1', "File not transferred to correct place")
		
		self.assertCounters(1,1)
		
	def testEditAndDelete(self):
		self.setupTransfer()
		
		# add
 		
		inside = os.path.join(self.source.client_root, "inside")
		ensureDirectory(inside)
		file1 = os.path.join(inside, "file1")
		with open(file1, 'w') as f:
			f.write('Test content')
		self.source.p4.run_add(file1)
		self.source.p4.run_submit('-d', "File1 added")
		
		# edit
		
		self.source.p4.run_edit(file1)
		with open(file1, 'a+') as f:
			f.write('More content')
		self.source.p4.run_submit('-d', "File1 edited")
		
		self.run_PerforceTransfer()
		
		changes = self.target.p4.run_changes()
		self.assertEqual(len(changes), 2, "Target does not have exactly two changes")
		self.assertEqual(changes[0]['change'], "2", "Highest target change is not 2")
		
		self.assertCounters(2,2)
		
		# delete
		
		self.source.p4.run_delete(file1)
		self.source.p4.run_submit('-d', "File1 deleted")
		
		self.run_PerforceTransfer()
		
		self.assertCounters(3,3)

		changes = self.target.p4.run_changes()
		self.assertEqual(len(changes), 3, "Target does not have exactly three changes")
		filelog = self.target.p4.run_filelog('//depot/import/file1')
		self.assertEqual(filelog[0].revisions[0].action, 'delete', "Target has not been deleted")
		
		# re-add
		
		with open(file1, 'w') as f:
			f.write('New content')
		self.source.p4.run_add(file1)
		self.source.p4.run_submit('-d', "Re-added")
		
		self.run_PerforceTransfer()
		
		self.assertCounters(4,4)

		filelog = self.target.p4.run_filelog('//depot/import/file1')
		self.assertEqual(filelog[0].revisions[0].action, 'add', "Target has not been re-added")
		
	def testFileTypes(self):
		self.setupTransfer()
		
		inside = os.path.join(self.source.client_root, "inside")
		ensureDirectory(inside)
 		
		file1 = os.path.join(inside, "file1")
		with open(file1, 'w') as f:
			print("Test content", file=f)
		self.source.p4.run_add('-tbinary', file1)
		self.source.p4.run_submit('-d', "File1 added")
		
		self.run_PerforceTransfer()
		
		self.assertCounters(1,1)
		
		filelog = self.target.p4.run_filelog('//depot/import/file1')
		self.assertEqual(filelog[0].revisions[0].type, 'binary', "File type is not binary, but %s" % filelog[0].revisions[0].type)
		
		# change type to binary+x
		
		self.source.p4.run_edit('-t+x', file1)
		with open(file1, 'a+') as f:
			print("More content", file=f)
		self.source.p4.run_submit('-d', "Type changed")
		
		self.run_PerforceTransfer()
		
		self.assertCounters(2, 2)
		
		filelog = self.target.p4.run_filelog('//depot/import/file1')
		self.assertEqual(filelog[0].revisions[0].type, 'xbinary', "File type is not xbinary, but %s" % filelog[0].revisions[0].type)
		
		# add ktext file
		
		file2 = os.path.join(inside, "file2")
		with open(file2, 'w') as f:
			print("$Id$", file=f)
			print("$DateTime$", file=f)
		self.source.p4.run_add('-t+k', file2)
		self.source.p4.run_submit('-d', "Ktext added")
		
		self.run_PerforceTransfer()
		
		self.assertCounters(3,3)
		
		filelog = self.target.p4.run_filelog('//depot/import/file2')
		self.assertEqual(filelog[0].revisions[0].type, 'ktext', "File type is not ktext, but %s" % filelog[0].revisions[0].type)
		verifyResult = self.target.p4.run_verify('-q', '//depot/import/file2')
		self.assertEqual(len(verifyResult), 0) # just to see that ktext gets transferred properly
		
		content = self.target.p4.run_print('//depot/import/file2')[1]
		lines = content.split("\n")
		self.assertEqual(lines[0], '$Id: //depot/import/file2#1 $', "Content does not match : %s" % lines[0])
		
	def testSimpleIntegrate(self):
		self.setupTransfer()
		
		# seed the integration
		
		inside = os.path.join(self.source.client_root, "inside")
		ensureDirectory(inside)
		file1 = os.path.join(inside, "file1")

		with open(file1, 'w') as f:
			print("Test content", file=f)
		self.source.p4.run_add(file1)
		self.source.p4.run_submit('-d', 'File1 added')

		file2 = os.path.join(inside, "file2")
		self.source.p4.run_integrate(file1, file2)
		self.source.p4.run_submit('-d', 'File1 -> File2')
		
		result = self.run_PerforceTransfer()
		
		# test integration
		
		self.assertCounters(2,2)

		changes = self.target.p4.run_changes()
		self.assertEqual(len(changes), 2, "Target does not have exactly two changes")

		# seed edit/copy
		 		
		self.source.p4.run_edit(file1)
		with open(file1, 'a+') as f:
			print("More content", file=f)
		self.source.p4.run_submit('-d', 'File1 edited')
		
		self.source.p4.run_integrate(file1, file2)
		self.source.p4.run_resolve('-at')
		self.source.p4.run_submit('-d', 'File1 -> File2 (copy)')
		
		result = self.run_PerforceTransfer()
		
		self.assertCounters(4,4)
		filelog = self.target.p4.run_filelog('//depot/import/file2')
		self.assertEqual(len(filelog[0].revisions), 2, "Not exactly 2 target revisions")
		self.assertEqual(len(filelog[0].revisions[1].integrations), 1, "Not exactly 1 integration into target")
		self.assertEqual(filelog[0].revisions[0].integrations[0].how, "copy from", "'How' is not copy from")
		
	def testComplexIntegrate(self):
		self.setupTransfer()
		
		# seed the integration
		
		content = """
		Line 1
		Line 2
		Line 3
		"""
		
		content1 = """
		Line 1
		Line 2 - changed
		Line 3
		"""
		
		content2 = """
		Line 1
		Line 2
		Line 3 - changed
		"""
		
		content4 = """
		Line 1
		Line 2 - changed
		Line 3 - differs
		"""

		content5 = """
		Line 1
		Line 2 - changed
		Line 3 - edited
		"""
		
		inside = os.path.join(self.source.client_root, "inside")
		ensureDirectory(inside)
		file1 = os.path.join(inside, "file1")

		with open(file1, 'w') as f:
			print(content, file=f)
		self.source.p4.run_add(file1)
		self.source.p4.run_submit('-d', 'File1 added')

		file2 = os.path.join(inside, "file2")
		self.source.p4.run_integrate(file1, file2)
		self.source.p4.run_submit('-d', 'File1 -> File2')

		# Prepare merge
		
		self.source.p4.run_edit(file1, file2)
		with open(file1, 'w') as f:
			print(content1, file=f)
		with open(file2, 'w') as f:
			print(content2, file=f)
		self.source.p4.run_submit('-d', "Changed both contents")
		
		# Integrate with merge
		
		self.source.p4.run_integrate(file1, file2)
		self.source.p4.run_resolve('-am')
		self.source.p4.run_submit('-d', "Merged contents")
		
		contentMerged = self.source.p4.run_print(file2)[1] 
		
		sourceCounter = 4
		targetCounter = 4
		
		self.run_PerforceTransfer()
		self.assertCounters(sourceCounter, targetCounter)
		
		filelog = self.target.p4.run_filelog('//depot/import/file2')
		self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'merge from', "How is not merge from")
		self.assertEqual(self.target.p4.run_print('//depot/import/file2')[1], contentMerged, "Content not the same")
		
		# Prepare integrate with edit
		
		self.source.p4.run_edit(file1, file2)
		with open(file1, 'w') as f:
			print(content1, file=f)
		self.source.p4.run_submit('-d', "Created a conflict")

		# Integrate with edit
		
		self.source.p4.run_integrate(file1, file2)
		
		class EditResolve(P4.Resolver):
			def resolve(self, mergeData):
				with open(mergeData.result_path, 'w') as f:
					f.write(content5)
					return 'ae'

		self.source.p4.run_resolve(resolver=EditResolve())
		self.source.p4.run_submit('-d', "Merge with edit")
		
		sourceCounter += 2
		targetCounter += 2
		
		self.run_PerforceTransfer()
		self.assertCounters(sourceCounter, targetCounter)

		# Prepare ignore
		
		self.source.p4.run_edit(file1)
		with open(file1, 'a+') as f:
			print("For your eyes only", file=f)
		self.source.p4.run_submit('-d', "Edit source again")
		
		self.source.p4.run_integrate(file1, file2)
		self.source.p4.run_resolve('-ay') # ignore
		self.source.p4.run_submit('-d', "Ignored change in file1")
		
		sourceCounter += 2
		targetCounter += 2
		
		self.run_PerforceTransfer()
		self.assertCounters(sourceCounter, targetCounter)
		
		filelog = self.target.p4.run_filelog('//depot/import/file2')
		self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'ignored', "How is not ignored")
		content = self.target.p4.run_print('-a', '//depot/import/file2')
		self.assertEqual(content[1], content[3], "Content of #1 not equal to #2")
		
		# Prepare delete
		
		self.source.p4.run_delete(file1)
		self.source.p4.run_submit('-d', "Delete file 1")
		
		self.source.p4.run_merge(file1, file2) # to trigger resolve
		self.source.p4.run_resolve('-at')
		self.source.p4.run_submit('-d', "Propagated delete")

		sourceCounter += 2
		targetCounter += 2

		self.run_PerforceTransfer()
		self.assertCounters(sourceCounter, targetCounter)
		
		filelog = self.target.p4.run_filelog('//depot/import/file2')
		self.assertEqual(len(filelog[0].revisions[0].integrations), 1, "No integration for delete")
		self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'delete from', "How is not delete from")
		
		# Prepare re-add
		
		with open(file1, 'w') as f:
			print(content1, file=f)
		self.source.p4.run_add(file1)
		self.source.p4.run_submit('-d', 'File1 re-added')
		
		self.source.p4.run_integrate(file1, file2)
		self.source.p4.run_submit('-d', "File2 re-added")

		sourceCounter += 2
		targetCounter += 2
		
		self.run_PerforceTransfer()
		self.assertCounters(sourceCounter, targetCounter)
		
		filelog = self.target.p4.run_filelog('//depot/import/file2')
		self.assertEqual(filelog[0].revisions[0].integrations[0].how, 'branch from' , "How is not branch from")
		
	def testMultipleIntegrate(self):
		self.setupTransfer()

		inside = os.path.join(self.source.client_root, "inside")
		ensureDirectory(inside)
		file1 = os.path.join(inside, "file1")

		with open(file1, 'w') as f:
			print("Some content", file=f)
		self.source.p4.run_add(file1)
		self.source.p4.run_submit('-d', 'File1 added')

		file2 = os.path.join(inside, "file2")
		self.source.p4.run_integrate(file1, file2)
		self.source.p4.run_submit('-d', 'File1 -> File2')

		file3 = os.path.join(inside, "file3")
		self.source.p4.run_integrate(file2, file3)
		self.source.p4.run_submit('-d', 'File2 -> File3')

		self.run_PerforceTransfer()
		self.assertCounters(3, 3)
		
		filelog1 = self.target.p4.run_filelog('//depot/import/file1')
		filelog2 = self.target.p4.run_filelog('//depot/import/file2')
		filelog3 = self.target.p4.run_filelog('//depot/import/file3')
		
		self.assertEqual(len(filelog1[0].revisions), 1, "Not exactly one file1 revision")
		self.assertEqual(len(filelog2[0].revisions), 1, "Not exactly one file2 revision")
		self.assertEqual(len(filelog3[0].revisions), 1, "Not exactly one file3 revision")

		self.assertEqual(len(filelog1[0].revisions[0].integrations), 1, "Not exactly one file1 integ record")
		self.assertEqual(len(filelog2[0].revisions[0].integrations), 2, "Not exactly two file2 integ records")
		self.assertEqual(len(filelog3[0].revisions[0].integrations), 1, "Not exactly one file3 integ record")

	def testInsideOutside(self):
		self.setupTransfer()

		inside = os.path.join(self.source.client_root, "inside")
		ensureDirectory(inside)
		outside = os.path.join(self.source.client_root, "outside")
		ensureDirectory(outside)
	
		# add from outside, integrate in
		
		file1 = os.path.join(outside, 'file1')
		with open(file1,'w') as f:
			print("Some content", file=f)
		self.source.p4.run_add(file1)
		self.source.p4.run_submit('-d', "Outside file1")
		
		file2 = os.path.join(inside, 'file2')
		self.source.p4.run_integrate(file1, file2)
		self.source.p4.run_submit('-d', "Integrated from outside to inside")
		
		self.run_PerforceTransfer()
		self.assertCounters(2,1)
		
		changes = self.target.p4.run_changes()
		self.assertEqual(len(changes),1, "Not exactly one change on target")
		filelog = self.target.p4.run_filelog('//depot/import/file2')
		self.assertEqual(filelog[0].revisions[0].action, "add", "File2 action is not add")
		
		# edit from outside, integrated in
		
		self.source.p4.run_edit(file1)
		with open(file1, 'a+') as f:
			print("More content", file=f)
		self.source.p4.run_submit('-d', "Outside file1 edited")
		
		self.run_PerforceTransfer()
		self.assertCounters(2,1) # counters will not move, no change within the client workspace's scope
		
		self.source.p4.run_integrate(file1, file2)
		self.source.p4.run_resolve('-at')
		self.source.p4.run_submit('-d', "Copied file1 -> file2")
		
		self.run_PerforceTransfer()
		self.assertCounters(4, 2)

		changes = self.target.p4.run_changes()
		self.assertEqual(len(changes),2, "Not exactly two changes on target")
		filelog = self.target.p4.run_filelog('//depot/import/file2')
		self.assertEqual(filelog[0].revisions[0].action, "edit", "File2 action is not edit")
		
		# delete from outside, integrate in
		
		self.source.p4.run_delete(file1)
		self.source.p4.run_submit('-d', "File1 deleted")
		
		self.source.p4.run_integrate(file1, file2)
		self.source.p4.run_submit('-d', "File2 deleted from file1")
		
		self.run_PerforceTransfer()
		self.assertCounters(6, 3)
		
		changes = self.target.p4.run_changes()
		self.assertEqual(len(changes),3, "Not exactly three changes on target")
		filelog = self.target.p4.run_filelog('//depot/import/file2')
		self.assertEqual(filelog[0].revisions[0].action, "delete", "File2 action is not delete")
		
if __name__ == '__main__':
	unittest.main()
# Change User Description Committed
#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.