summaryrefslogtreecommitdiff
path: root/rdiff-backup/testing/killtest.py
blob: 837dfada2536ea9e6df8dc5115528bd7885f60ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
import unittest, os, signal, sys, random, time
from commontest import *
from rdiff_backup.log import *
from rdiff_backup import Globals, Main

"""Test consistency by killing rdiff-backup as it is backing up"""

Log.setverbosity(3)

class Local:
	"""Hold some local RPaths"""
	def get_local_rp(ext):
		return RPath(Globals.local_connection, "testfiles/" + ext)

	kt1rp = get_local_rp('killtest1')
	kt2rp = get_local_rp('killtest2')
	kt3rp = get_local_rp('killtest3')
	kt4rp = get_local_rp('killtest4')

	rpout = get_local_rp('output')
	rpout_inc = get_local_rp('output_inc')
	rpout1 = get_local_rp('restoretarget1')
	rpout2 = get_local_rp('restoretarget2')
	rpout3 = get_local_rp('restoretarget3')
	rpout4 = get_local_rp('restoretarget4')
	rpout5 = get_local_rp('restoretarget5')

	back1 = get_local_rp('backup1')
	back2 = get_local_rp('backup2')
	back3 = get_local_rp('backup3')
	back4 = get_local_rp('backup4')
	back5 = get_local_rp('backup5')

class TimingError(Exception):
	"""Indicates timing error - process killed too soon or too late"""
	pass


class ProcessFuncs(unittest.TestCase):
	"""Subclassed by Resume and NoResume"""
	def delete_tmpdirs(self):
		"""Remove any temp directories created by previous tests"""
		assert not os.system(MiscDir + '/myrm testfiles/output* '
							 'testfiles/restoretarget* testfiles/vft_out '
							 'timbar.pyc testfiles/vft2_out')

	def is_aborted_backup(self):
		"""True if there are signs of aborted backup in output/"""
		try: dirlist = os.listdir("testfiles/output/rdiff-backup-data")
		except OSError:
			raise TimingError("No data dir found, give process more time")
		dirlist = filter(lambda f: f.startswith("last-file-incremented"),
						 dirlist)
		return len(dirlist) != 0

	def exec_rb(self, time, wait, *args):
		"""Run rdiff-backup return pid"""
		arglist = ['python', '../rdiff-backup', '-v3']
		if time:
			arglist.append("--current-time")
			arglist.append(str(time))
		arglist.extend(args)

		print "Running ", arglist
		if wait: return os.spawnvp(os.P_WAIT, 'python', arglist)
		else: return os.spawnvp(os.P_NOWAIT, 'python', arglist)

	def exec_and_kill(self, mintime, maxtime, backup_time, resume, arg1, arg2):
		"""Run rdiff-backup, then kill and run again

		Kill after a time between mintime and maxtime.  First process
		should not terminate before maxtime.

		"""
		pid = self.exec_rb(backup_time, None, arg1, arg2)
		time.sleep(random.uniform(mintime, maxtime))
		if os.waitpid(pid, os.WNOHANG)[0] != 0:
			raise TimingError("Timing Error on %s, %s:\n"
							  "Process already quit - try lowering max time"
							  % (arg1, arg2))
		os.kill(pid, self.killsignal)
		while 1:
			pid, exitstatus = os.waitpid(pid, os.WNOHANG)
			if pid:
				assert exitstatus != 0
				break
			time.sleep(0.2)
		if not self.is_aborted_backup():
			raise TimingError("Timing Error on %s, %s:\n"
							  "Process already finished or didn't "
							  "get a chance to start" % (arg1, arg2))
		print "---------------------- killed"
		os.system("ls -l %s/rdiff-backup-data" % arg1)
		if resume: self.exec_rb(backup_time + 5, 1, '--resume', arg1, arg2)
		else: self.exec_rb(backup_time + 5000, 1, '--no-resume', arg1, arg2)

	def create_killtest_dirs(self):
		"""Create testfiles/killtest? directories

		They are similar to the testfiles/increment? directories but
		have more files in them so they take a significant time to
		back up.

		"""
		def copy_thrice(input, output):
			"""Copy input directory to output directory three times"""
			assert not os.system("cp -a %s %s" % (input, output))
			assert not os.system("cp -a %s %s/killtest1" % (input, output))
			assert not os.system("cp -a %s %s/killtest2" % (input, output))

		if (Local.kt1rp.lstat() and Local.kt2rp.lstat() and
			Local.kt3rp.lstat() and Local.kt4rp.lstat()): return
		
		assert not os.system("rm -rf testfiles/killtest?")
		for i in [1, 2, 3, 4]:
			copy_thrice("testfiles/increment%d" % i,
						"testfiles/killtest%d" % i)

	def verify_back_dirs(self):
		"""Make sure testfiles/output/back? dirs exist"""
		if (Local.back1.lstat() and Local.back2.lstat() and
			Local.back3.lstat() and Local.back4.lstat() and
			Local.back5.lstat()): return

		os.system(MiscDir + "/myrm testfiles/backup[1-5]")

		self.exec_rb(10000, 1, 'testfiles/killtest3', 'testfiles/backup1')
		Local.back1.setdata()

		self.exec_rb(10000, 1, 'testfiles/killtest3', 'testfiles/backup2')
		self.exec_rb(20000, 1, 'testfiles/killtest1', 'testfiles/backup2')
		Local.back2.setdata()
		
		self.exec_rb(10000, 1, 'testfiles/killtest3', 'testfiles/backup3')
		self.exec_rb(20000, 1, 'testfiles/killtest1', 'testfiles/backup3')
		self.exec_rb(30000, 1, 'testfiles/killtest2', 'testfiles/backup3')
		Local.back3.setdata()
		
		self.exec_rb(10000, 1, 'testfiles/killtest3', 'testfiles/backup4')
		self.exec_rb(20000, 1, 'testfiles/killtest1', 'testfiles/backup4')
		self.exec_rb(30000, 1, 'testfiles/killtest2', 'testfiles/backup4')
		self.exec_rb(40000, 1, 'testfiles/killtest3', 'testfiles/backup4')
		Local.back4.setdata()

		self.exec_rb(10000, 1, 'testfiles/killtest3', 'testfiles/backup5')
		self.exec_rb(20000, 1, 'testfiles/killtest1', 'testfiles/backup5')
		self.exec_rb(30000, 1, 'testfiles/killtest2', 'testfiles/backup5')
		self.exec_rb(40000, 1, 'testfiles/killtest3', 'testfiles/backup5')
		self.exec_rb(50000, 1, 'testfiles/killtest4', 'testfiles/backup5')
		Local.back5.setdata()

	def runtest_sequence(self, total_tests,
						 exclude_rbdir, ignore_tmp, compare_links,
						 stop_on_error = None):
		timing_problems, failures = 0, 0
		for i in range(total_tests):
			try:
				result = self.runtest(exclude_rbdir, ignore_tmp, compare_links)
			except TimingError, te:
				print te
				timing_problems += 1
				continue
			if result != 1:
				if stop_on_error: assert 0, "Compare Failure"
				else: failures += 1

		print total_tests, "tests attempted total"
		print "%s setup problems, %s failures, %s successes" % \
			  (timing_problems, failures,
			   total_tests - timing_problems - failures)		


class Resume(ProcessFuncs):
	"""Test for graceful recovery after resumed backup"""
	def setUp(self):
		"""Create killtest? and backup? directories"""
		self.create_killtest_dirs()
		self.verify_back_dirs()

	def runtest(self, exclude_rbdir, ignore_tmp_files, compare_links):
		"""Run the actual test, returning 1 if passed and 0 otherwise"""
		self.delete_tmpdirs()
		
		# Backing up killtest3

		# Start with killtest3 because it is big and the first case
		# is kind of special (there's no incrementing, so different
		# code)
		self.exec_and_kill(0.7, 4.0, 10000, 1,
						   'testfiles/killtest3', 'testfiles/output')
		if not CompareRecursive(Local.back1, Local.rpout, compare_links,
								None, exclude_rbdir, ignore_tmp_files):
			return 0

		# Backing up killtest1
		self.exec_and_kill(0.8, 5.0, 20000, 1,
						   'testfiles/killtest1', 'testfiles/output')
		if not CompareRecursive(Local.back2, Local.rpout, compare_links,
								None, exclude_rbdir, ignore_tmp_files):
			return 0

		# Backing up killtest2
		self.exec_and_kill(0.7, 0.8, 30000, 1,
						   'testfiles/killtest2', 'testfiles/output')
		if not CompareRecursive(Local.back3, Local.rpout, compare_links,
								None, exclude_rbdir, ignore_tmp_files):
			return 0

		# Backing up killtest3
		self.exec_and_kill(0.7, 4.0, 40000, 1,
						   'testfiles/killtest3', 'testfiles/output')
		if not CompareRecursive(Local.back4, Local.rpout, compare_links,
								None, exclude_rbdir, ignore_tmp_files):
			return 0

		# Backing up killtest4
		self.exec_and_kill(1.0, 8.0, 50000, 1,
						   'testfiles/killtest4', 'testfiles/output')
		if not CompareRecursive(Local.back5, Local.rpout, compare_links,
								None, exclude_rbdir, ignore_tmp_files):
			return 0
		return 1

	def testTERM(self, total_tests = 3):
		"""Test sending local processes a TERM signal"""
		self.killsignal = signal.SIGTERM
		self.runtest_sequence(total_tests, None, None, 1)

	def testKILL(self, total_tests = 10):
		"""Send local backup process a KILL signal"""
		self.killsignal = signal.SIGKILL
		self.runtest_sequence(total_tests, None, 1, None)


class NoResume(ProcessFuncs):
	"""Test for consistent backup after abort and then no resume"""
	def runtest(self, exclude_rbdir, ignore_tmp_files, compare_links):
		self.delete_tmpdirs()

		# Back up each killtest to output
		self.exec_and_kill(0.7, 1.5, 10000, 1,
						   'testfiles/killtest3', 'testfiles/output')
		self.exec_and_kill(0.6, 0.6, 20000, 1,
						   'testfiles/killtest1', 'testfiles/output')
		self.exec_and_kill(0.7, 1.0, 30000, 1,
						   'testfiles/killtest2', 'testfiles/output')
		self.exec_and_kill(0.7, 2.0, 40000, 1,
						   'testfiles/killtest3', 'testfiles/output')
		self.exec_and_kill(1.0, 5.0, 50000, 1,
						   'testfiles/killtest4', 'testfiles/output')

		# Now restore each and compare
		InternalRestore(1, 1, "testfiles/output", "testfiles/restoretarget1",
						15000)
		if not CompareRecursive(Local.kt3rp, Local.rpout1, compare_links,
								None, exclude_rbdir, ignore_tmp_files):
			return 0
		
		InternalRestore(1, 1, "testfiles/output", "testfiles/restoretarget2",
						25000)
		if not CompareRecursive(Local.kt1rp, Local.rpout2, compare_links,
								None, exclude_rbdir, ignore_tmp_files):
			return 0

		InternalRestore(1, 1, "testfiles/output", "testfiles/restoretarget3",
						35000)
		if not CompareRecursive(Local.kt2rp, Local.rpout3, compare_links,
								None, exclude_rbdir, ignore_tmp_files):
			return 0
		InternalRestore(1, 1, "testfiles/output", "testfiles/restoretarget4",
						45000)
		
		if not CompareRecursive(Local.kt3rp, Local.rpout4, compare_links,
								None, exclude_rbdir, ignore_tmp_files):
			return 0
		InternalRestore(1, 1, "testfiles/output", "testfiles/restoretarget5",
						55000)

		if not CompareRecursive(Local.kt4rp, Local.rpout5, compare_links,
								None, exclude_rbdir, ignore_tmp_files):
			return 0
		return 1

	def testTERM(self, total_tests = 5):
		self.killsignal = signal.SIGTERM
		self.runtest_sequence(total_tests, 1, None, 1)

	def testKILL(self, total_tests = 5):
		self.killsignal = signal.SIGKILL
		self.runtest_sequence(total_tests, 1, 1, None)


if __name__ == "__main__": unittest.main()