3
# scramble-tree.py: (See scramble-tree.py --help.)
5
# Makes multiple random file changes to a directory tree, for testing.
7
# This script will add some new files, remove some existing files, add
8
# text to some existing files, and delete text from some existing
9
# files. It will also leave some files completely untouched.
11
# The exact set of changes made is always the same for identical trees,
12
# where "identical" means the names of files and directories are the
13
# same, and they are arranged in the same tree structure (the actual
14
# contents of files may differ). If two are not identical, the sets of
15
# changes scramble-tree.py will make may differ arbitrarily.
17
# Directories named .svn/ and CVS/ are ignored.
19
# Example scenario, starting with a pristine Subversion working copy:
26
# $ scramble-tree.py foo
28
# [... see lots of scary status output ...]
29
# $ scramble-tree.py bar
30
# [... see the exact same scary status output ...]
31
# $ scramble-tree.py foo
32
# [... see a new bunch of scary status output ...]
46
def add_file(self, path):
47
"""Add an existing file to version control."""
49
def remove_file(self, path):
50
"""Remove an existing file from version control, and delete it."""
54
class NoVCActions(VCActions):
55
def remove_file(self, path):
59
class CVSActions(VCActions):
60
def add_file(self, path):
63
dirname, basename = os.path.split(path)
64
os.chdir(os.path.join(cwd, dirname))
65
os.system('cvs -Q add -m "Adding file to repository" "%s"' % (basename))
68
def remove_file(self, path):
71
dirname, basename = os.path.split(path)
72
os.chdir(os.path.join(cwd, dirname))
73
os.system('cvs -Q rm -f "%s"' % (basename))
78
class SVNActions(VCActions):
79
def add_file(self, path):
80
os.system('svn add --quiet "%s"' % (path))
81
def remove_file(self, path):
83
os.system('svn rm --quiet --force "%s"' % (path))
87
"""Given a directory, creates a string containing all directories
88
and files under that directory (sorted alphanumerically) and makes a
89
base64-encoded md5 hash of the resulting string. Call
90
hashDir.gen_seed() to generate a seed value for this tree."""
92
def __init__(self, rootdir):
94
os.path.walk(rootdir, self.walker_callback, len(rootdir))
97
# Return a base64-encoded (kinda ... strip the '==\n' from the
98
# end) MD5 hash of sorted tree listing.
100
return base64.encodestring(md5.md5(''.join(self.allfiles)).digest())[:-3]
102
def walker_callback(self, baselen, dirname, fnames):
103
if ((dirname == '.svn') or (dirname == 'CVS')):
105
self.allfiles.append(dirname[baselen:])
106
for filename in fnames:
107
path = os.path.join(dirname, filename)
108
if not os.path.isdir(path):
109
self.allfiles.append(path[baselen:])
113
def __init__(self, seed, vc_actions, dry_run, quiet):
115
print 'SEED: ' + seed
117
self.rand = random.Random(seed)
118
self.vc_actions = vc_actions
119
self.dry_run = dry_run
121
self.ops = [] ### ["add" | "munge", path]
123
======================================================================
124
This is some text that was inserted into this file by the lovely and
125
talented scramble-tree.py script.
126
======================================================================
130
def shrink_list(self, list, remove_count):
131
if len(list) <= remove_count:
133
for i in range(remove_count):
134
j = self.rand.randrange(len(list) - 1)
138
def _make_new_file(self, dir):
141
for i in range(99999):
142
path = os.path.join(dir, "newfile.%05d.txt" % i)
143
if not os.path.exists(path):
144
open(path, 'w').write(self.greeking)
146
raise Exception("Ran out of unique new filenames in directory '%s'" % dir)
149
def _mod_append_to_file(self, path):
151
print 'append_to_file:', path
155
fh.write(self.greeking)
158
def _mod_remove_from_file(self, path):
160
print 'remove_from_file:', path
163
lines = self.shrink_list(open(path, "r").readlines(), 5)
164
open(path, "w").writelines(lines)
166
def _mod_delete_file(self, path):
168
print 'delete_file:', path
171
self.vc_actions.remove_file(path)
173
### Public Interfaces
174
def get_randomizer(self):
177
def schedule_munge(self, path):
178
self.ops.append(tuple(["munge", path]))
180
def schedule_addition(self, dir):
181
self.ops.append(tuple(["add", dir]))
183
def enact(self, limit):
184
num_ops = len(self.ops)
187
elif limit > 0 and limit <= num_ops:
188
self.ops = self.shrink_list(self.ops, num_ops - limit)
189
for op, path in self.ops:
191
path = self._make_new_file(path)
193
print "add_file:", path
196
self.vc_actions.add_file(path)
198
file_mungers = [self._mod_append_to_file,
199
self._mod_append_to_file,
200
self._mod_append_to_file,
201
self._mod_remove_from_file,
202
self._mod_remove_from_file,
203
self._mod_remove_from_file,
204
self._mod_delete_file,
206
self.rand.choice(file_mungers)(path)
209
def usage(retcode=255):
210
print 'Usage: %s [OPTIONS] DIRECTORY' % (sys.argv[0])
213
print ' --help, -h : Show this usage message.'
214
print ' --seed ARG : Use seed ARG to scramble the tree.'
215
print ' --use-svn : Use Subversion (as "svn") to perform file additions'
216
print ' and removals.'
217
print ' --use-cvs : Use CVS (as "cvs") to perform file additions'
218
print ' and removals.'
219
print ' --dry-run : Don\'t actually change the disk.'
220
print ' --limit N : Limit the scrambling to a maximum of N operations.'
221
print ' --quiet, -q : Run in stealth mode!'
225
def walker_callback(scrambler, dirname, fnames):
226
if ((dirname.find('.svn') != -1) or dirname.find('CVS') != -1):
228
rand = scrambler.get_randomizer()
229
if rand.randrange(5) == 1:
230
scrambler.schedule_addition(dirname)
231
for filename in fnames:
232
path = os.path.join(dirname, filename)
233
if not os.path.isdir(path) and rand.randrange(3) == 1:
234
scrambler.schedule_munge(path)
239
vc_actions = NoVCActions()
244
# Mm... option parsing.
245
optlist, args = getopt.getopt(sys.argv[1:], "hq",
246
['seed=', 'use-svn', 'use-cvs',
247
'help', 'quiet', 'dry-run', 'limit='])
248
for opt, arg in optlist:
249
if opt == '--help' or opt == '-h':
253
if opt == '--use-svn':
254
vc_actions = SVNActions()
255
if opt == '--use-cvs':
256
vc_actions = CVSActions()
257
if opt == '--dry-run':
261
if opt == '--quiet' or opt == '-q':
264
# We need at least a path to work with, here.
266
if argc < 1 or argc > 1:
270
# If a seed wasn't provide, calculate one.
272
seed = hashDir(rootdir).gen_seed()
273
scrambler = Scrambler(seed, vc_actions, dry_run, quiet)
274
os.path.walk(rootdir, walker_callback, scrambler)
275
scrambler.enact(limit)
277
if __name__ == '__main__':