From: Zooko O'Whielacronx Date: Thu, 19 Apr 2007 00:11:13 +0000 (-0700) Subject: zfec: finish up some renaming of pyfec to zfec X-Git-Tag: tahoe_v0.1.0-0-UNSTABLE~62 X-Git-Url: https://git.rkrishnan.org/pf?a=commitdiff_plain;h=0da053eda253532565eb039ead8c0020152f05ab;p=tahoe-lafs%2Ftahoe-lafs.git zfec: finish up some renaming of pyfec to zfec --- diff --git a/zfec/README.txt b/zfec/README.txt index 70d1b5b4..f7c76da4 100644 --- a/zfec/README.txt +++ b/zfec/README.txt @@ -19,7 +19,7 @@ which is a mature and optimized implementation of erasure coding. The zfec package makes several changes from the original "fec" package, including addition of the Python API, refactoring of the C API to support zero-copy operation, a few clean-ups and micro-optimizations of the core code itself, -and the addition of a command-line tool named "fec". +and the addition of a command-line tool named "zfec". * Community @@ -52,7 +52,7 @@ and k is required to be at least 1 and at most m. degenerates to the equivalent of the Unix "split" utility which simply splits the input into successive segments. Similarly, when k == 1 it degenerates to the equivalent of the unix "cp" utility -- each block is a complete copy of the -input data. The "fec" command-line tool does not implement these degenerate +input data. The "zfec" command-line tool does not implement these degenerate cases.) Note that each "primary block" is a segment of the original data, so its size @@ -63,9 +63,9 @@ the same size as all the others). In addition to the data contained in the blocks themselves there are also a few pieces of metadata which are necessary for later reconstruction. Those pieces are: 1. the value of K, 2. the value of M, 3. the sharenum of each block, 4. the number of bytes of padding -that were used. The "fec" command-line tool compresses these pieces of data +that were used. The "zfec" command-line tool compresses these pieces of data and prepends them to the beginning of each share, so each the sharefile -produced by the "fec" command-line tool is between one and four bytes larger +produced by the "zfec" command-line tool is between one and four bytes larger than the share data alone. The decoding step requires as input k of the blocks which were produced by the @@ -75,12 +75,12 @@ input to the encoding step. * Command-Line Tool -The bin/ directory contains two Unix-style, command-line tools "fec" and -"unfec". Execute "fec --help" or "unfec --help" for usage instructions. +The bin/ directory contains two Unix-style, command-line tools "zfec" and +"zunfec". Execute "zfec --help" or "zunfec --help" for usage instructions. -Note: a Unix-style tool like "fec" does only one thing -- in this case +Note: a Unix-style tool like "zfec" does only one thing -- in this case erasure coding -- and leaves other tasks to other tools. Other Unix-style -tools that go well with fec include "GNU tar" for archiving multiple files and +tools that go well with zfec include "GNU tar" for archiving multiple files and directories into one file, "rzip" for compression, "GNU Privacy Guard" for encryption, and "sha256sum" for integrity. It is important to do things in order: first archive, then compress, then either encrypt or sha256sum, then @@ -90,10 +90,10 @@ also ensure integrity, so the use of sha256sum is unnecessary in that case. * Performance Measurements -On my Athlon 64 2.4 GHz workstation (running Linux), the "fec" command-line +On my Athlon 64 2.4 GHz workstation (running Linux), the "zfec" command-line tool encoded a 160 MB file with m=100, k=94 (about 6% redundancy) in 3.9 seconds, where the "par2" tool encoded the file with about 6% redundancy in -27 seconds. "fec" encoded the same file with m=12, k=6 (100% redundancy) in +27 seconds. zfec encoded the same file with m=12, k=6 (100% redundancy) in 4.1 seconds, where par2 encoded it with about 100% redundancy in 7 minutes and 56 seconds. @@ -182,8 +182,8 @@ objects (e.g. Python strings) to hold the data that you pass to zfec. * Utilities The filefec.py module has a utility function for efficiently reading a file -and encoding it piece by piece. This module is used by the "fec" and "unfec" -command-line tools from the bin/ directory. +and encoding it piece by piece. This module is used by the "zfec" and +"zunfec" command-line tools from the bin/ directory. * Dependencies diff --git a/zfec/zfec/_fecmodule.c b/zfec/zfec/_fecmodule.c index c437e8be..01e57c5e 100644 --- a/zfec/zfec/_fecmodule.c +++ b/zfec/zfec/_fecmodule.c @@ -277,7 +277,7 @@ static PyMemberDef Encoder_members[] = { static PyTypeObject Encoder_type = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ - "fec.Encoder", /*tp_name*/ + "_fec.Encoder", /*tp_name*/ sizeof(Encoder), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)Encoder_dealloc, /*tp_dealloc*/ @@ -541,7 +541,7 @@ static PyMemberDef Decoder_members[] = { static PyTypeObject Decoder_type = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ - "fec.Decoder", /*tp_name*/ + "_fec.Decoder", /*tp_name*/ sizeof(Decoder), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)Decoder_dealloc, /*tp_dealloc*/ diff --git a/zfec/zfec/test/bench_pyfec.py b/zfec/zfec/test/bench_pyfec.py deleted file mode 100644 index 6b86e8ca..00000000 --- a/zfec/zfec/test/bench_pyfec.py +++ /dev/null @@ -1,103 +0,0 @@ -# zfec -- fast forward error correction library with Python interface -# -# Copyright (C) 2007 Allmydata, Inc. -# Author: Zooko Wilcox-O'Hearn -# mailto:zooko@zooko.com -# -# This file is part of zfec. -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. This program also comes with the added permission that, in the case -# that you are obligated to release a derived work under this licence (as per -# section 2.b of the GPL), you may delay the fulfillment of this obligation for -# up to 12 months. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -import fec - -import array, random - -def f_easyfec(filesize): - return bench_encode_to_files_shuffle_decode_from_files(filesize, verbose=False, encodefunc=fec.filefec.encode_to_files_easyfec) - -def f_fec_stringy(filesize): - return bench_encode_to_files_shuffle_decode_from_files(filesize, verbose=False, encodefunc=fec.filefec.encode_to_files_stringy) - -def f_fec(filesize): - return bench_encode_to_files_shuffle_decode_from_files(filesize, verbose=False, encodefunc=fec.filefec.encode_to_files) - -def bench_encode_to_files_shuffle_decode_from_files(filesize=1000000, verbose=False, encodefunc=fec.filefec.encode_to_files): - CHUNKSIZE=4096 - PREFIX="testshare" - K=25 - M=100 - import os, time - left=filesize - outfile = open("tmpranddata", "wb") - try: - while left: - d = os.urandom(min(left, CHUNKSIZE)) - outfile.write(d) - left -= len(d) - outfile.flush() - outfile = None - infile = open("tmpranddata", "rb") - st = time.time() - encodefunc(infile, PREFIX, K, M) - so = time.time() - if verbose: - print "Encoded %s byte file into %d share files in %0.2f seconds, or %0.2f million bytes per second" % (filesize, M, so-st, filesize/((so-st)*filesize),) - enctime = so-st - # Now delete m-k of the tempfiles at random. - tempfs = [ f for f in os.listdir(".") if f.startswith(PREFIX) ] - random.shuffle(tempfs) - for victimtempf in tempfs[:M-K]: - os.remove(victimtempf) - recoveredfile = open("tmpranddata-recovered", "wb") - st = time.time() - fec.filefec.decode_from_files(recoveredfile, filesize, PREFIX, K, M) - so = time.time() - if verbose: - print "Decoded %s byte file from %d share files in %0.2f seconds, or %0.2f million bytes per second" % (filesize, K, so-st, filesize/((so-st)*filesize),) - return enctime + (so-st) - finally: - # os.remove("tmpranddata") - pass - -def bench_read_encode_and_drop(): - FILESIZE=1000000 - CHUNKSIZE=4096 - import os, time - left=FILESIZE - outfile = open("tmpranddata", "wb") - try: - while left: - d = os.urandom(min(left, CHUNKSIZE)) - outfile.write(d) - left -= len(d) - outfile.flush() - outfile = None - infile = open("tmpranddata", "rb") - def cb(s, l): - pass - st = time.time() - fec.filefec.encode_file(infile, cb, 25, 100, 4096) - so = time.time() - print "Encoded %s byte file in %0.2f seconds, or %0.2f million bytes per second" % (FILESIZE, so-st, FILESIZE/((so-st)*1000000),) - return so-st - finally: - os.remove("tmpranddata") - -if __name__ == "__main__": - bench_encode_to_files_shuffle_decode_from_files() - diff --git a/zfec/zfec/test/test_pyfec.py b/zfec/zfec/test/test_pyfec.py deleted file mode 100644 index e080e83c..00000000 --- a/zfec/zfec/test/test_pyfec.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env python - -# import bindann -# import bindann.monkeypatch.all - -# zfec -- fast forward error correction library with Python interface -# -# Copyright (C) 2007 Allmydata, Inc. -# Author: Zooko Wilcox-O'Hearn -# mailto:zooko@zooko.com -# -# This file is part of zfec. -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. This program also comes with the added permission that, in the case -# that you are obligated to release a derived work under this licence (as per -# section 2.b of the GPL), you may delay the fulfillment of this obligation for -# up to 12 months. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -import cStringIO, os, random, re, sys - -import zfec - -try: - from twisted.trial import unittest -except ImportError: - # trial is unavailable, oh well - import unittest - -global VERBOSE -VERBOSE=False -if '-v' in sys.argv: - sys.argv.pop(sys.argv.index('-v')) - VERBOSE=True - -from base64 import b32encode -def ab(x): # debuggery - if len(x) >= 3: - return "%s:%s" % (len(x), b32encode(x[-3:]),) - elif len(x) == 2: - return "%s:%s" % (len(x), b32encode(x[-2:]),) - elif len(x) == 1: - return "%s:%s" % (len(x), b32encode(x[-1:]),) - elif len(x) == 0: - return "%s:%s" % (len(x), "--empty--",) - -def _h(k, m, ss): - encer = zfec.Encoder(k, m) - nums_and_blocks = list(enumerate(encer.encode(ss))) - assert isinstance(nums_and_blocks, list), nums_and_blocks - assert len(nums_and_blocks) == m, (len(nums_and_blocks), m,) - nums_and_blocks = random.sample(nums_and_blocks, k) - blocks = [ x[1] for x in nums_and_blocks ] - nums = [ x[0] for x in nums_and_blocks ] - decer = zfec.Decoder(k, m) - decoded = decer.decode(blocks, nums) - assert len(decoded) == len(ss), (len(decoded), len(ss),) - assert tuple([str(s) for s in decoded]) == tuple([str(s) for s in ss]), (tuple([ab(str(s)) for s in decoded]), tuple([ab(str(s)) for s in ss]),) - -def randstr(n): - return ''.join(map(chr, map(random.randrange, [0]*n, [256]*n))) - -def _help_test_random(): - m = random.randrange(1, 257) - k = random.randrange(1, m+1) - l = random.randrange(0, 2**10) - ss = [ randstr(l/k) for x in range(k) ] - _h(k, m, ss) - -def _help_test_random_with_l(l): - m = 83 - k = 19 - ss = [ randstr(l/k) for x in range(k) ] - _h(k, m, ss) - -class ZFec(unittest.TestCase): - def test_random(self): - for i in range(3): - _help_test_random() - if VERBOSE: - print "%d randomized tests pass." % (i+1) - - def test_bad_args_enc(self): - encer = zfec.Encoder(2, 4) - try: - encer.encode(["a", "b", ], ["c", "I am not an integer blocknum",]) - except zfec.Error, e: - assert "Precondition violation: second argument is required to contain int" in str(e), e - else: - raise "Should have gotten zfec.Error for wrong type of second argument." - - try: - encer.encode(["a", "b", ], 98) # not a sequence at all - except TypeError, e: - assert "Second argument (optional) was not a sequence" in str(e), e - else: - raise "Should have gotten TypeError for wrong type of second argument." - - def test_bad_args_dec(self): - decer = zfec.Decoder(2, 4) - - try: - decer.decode(98, [0, 1]) # first argument is not a sequence - except TypeError, e: - assert "First argument was not a sequence" in str(e), e - else: - raise "Should have gotten TypeError for wrong type of second argument." - - try: - decer.decode(["a", "b", ], ["c", "d",]) - except zfec.Error, e: - assert "Precondition violation: second argument is required to contain int" in str(e), e - else: - raise "Should have gotten zfec.Error for wrong type of second argument." - - try: - decer.decode(["a", "b", ], 98) # not a sequence at all - except TypeError, e: - assert "Second argument was not a sequence" in str(e), e - else: - raise "Should have gotten TypeError for wrong type of second argument." - -class FileFec(unittest.TestCase): - def test_filefec_header(self): - for m in [3, 5, 7, 9, 11, 17, 19, 33, 35, 65, 66, 67, 129, 130, 131, 254, 255, 256,]: - for k in [2, 3, 5, 9, 17, 33, 65, 129, 255,]: - if k >= m: - continue - for pad in [0, 1, k-1,]: - if pad >= k: - continue - for sh in [0, 1, m-1,]: - if sh >= m: - continue - h = zfec.filefec._build_header(m, k, pad, sh) - hio = cStringIO.StringIO(h) - (rm, rk, rpad, rsh,) = zfec.filefec._parse_header(hio) - assert (rm, rk, rpad, rsh,) == (m, k, pad, sh,), h - - def _help_test_filefec(self, teststr, k, m, numshs=None): - if numshs == None: - numshs = m - - TESTFNAME = "testfile.txt" - PREFIX = "test" - SUFFIX = ".fec" - - tempdir = zfec.util.fileutil.NamedTemporaryDirectory(cleanup=False) - try: - tempfn = os.path.join(tempdir.name, TESTFNAME) - tempf = open(tempfn, 'wb') - tempf.write(teststr) - tempf.close() - fsize = os.path.getsize(tempfn) - assert fsize == len(teststr) - - # encode the file - zfec.filefec.encode_to_files(open(tempfn, 'rb'), fsize, tempdir.name, PREFIX, k, m, SUFFIX, verbose=VERBOSE) - - # select some share files - RE=re.compile(zfec.filefec.RE_FORMAT % (PREFIX, SUFFIX,)) - fns = os.listdir(tempdir.name) - sharefs = [ open(os.path.join(tempdir.name, fn), "rb") for fn in fns if RE.match(fn) ] - random.shuffle(sharefs) - del sharefs[numshs:] - - # decode from the share files - outf = open(os.path.join(tempdir.name, 'recovered-testfile.txt'), 'wb') - zfec.filefec.decode_from_files(outf, sharefs, verbose=VERBOSE) - outf.close() - - tempfn = open(os.path.join(tempdir.name, 'recovered-testfile.txt'), 'rb') - recovereddata = tempfn.read() - assert recovereddata == teststr - finally: - tempdir.shutdown() - - def test_filefec_all_shares(self): - return self._help_test_filefec("Yellow Whirled!", 3, 8) - - def test_filefec_all_shares_with_padding(self, noisy=VERBOSE): - return self._help_test_filefec("Yellow Whirled!A", 3, 8) - - def test_filefec_min_shares_with_padding(self, noisy=VERBOSE): - return self._help_test_filefec("Yellow Whirled!A", 3, 8, numshs=3) - -if __name__ == "__main__": - if hasattr(unittest, 'main'): - unittest.main() - else: - sys.path.append(os.getcwd()) - mods = [] - fullname = os.path.realpath(os.path.abspath(__file__)) - for pathel in sys.path: - fullnameofpathel = os.path.realpath(os.path.abspath(pathel)) - if fullname.startswith(fullnameofpathel): - relname = fullname[len(fullnameofpathel):] - mod = (os.path.splitext(relname)[0]).replace(os.sep, '.').strip('.') - mods.append(mod) - - mods.sort(cmp=lambda x, y: cmp(len(x), len(y))) - mods.reverse() - for mod in mods: - cmdstr = "trial %s %s" % (' '.join(sys.argv[1:]), mod) - print cmdstr - if os.system(cmdstr) == 0: - break diff --git a/zfec/zfec/test/test_zfec.py b/zfec/zfec/test/test_zfec.py new file mode 100644 index 00000000..e080e83c --- /dev/null +++ b/zfec/zfec/test/test_zfec.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python + +# import bindann +# import bindann.monkeypatch.all + +# zfec -- fast forward error correction library with Python interface +# +# Copyright (C) 2007 Allmydata, Inc. +# Author: Zooko Wilcox-O'Hearn +# mailto:zooko@zooko.com +# +# This file is part of zfec. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. This program also comes with the added permission that, in the case +# that you are obligated to release a derived work under this licence (as per +# section 2.b of the GPL), you may delay the fulfillment of this obligation for +# up to 12 months. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import cStringIO, os, random, re, sys + +import zfec + +try: + from twisted.trial import unittest +except ImportError: + # trial is unavailable, oh well + import unittest + +global VERBOSE +VERBOSE=False +if '-v' in sys.argv: + sys.argv.pop(sys.argv.index('-v')) + VERBOSE=True + +from base64 import b32encode +def ab(x): # debuggery + if len(x) >= 3: + return "%s:%s" % (len(x), b32encode(x[-3:]),) + elif len(x) == 2: + return "%s:%s" % (len(x), b32encode(x[-2:]),) + elif len(x) == 1: + return "%s:%s" % (len(x), b32encode(x[-1:]),) + elif len(x) == 0: + return "%s:%s" % (len(x), "--empty--",) + +def _h(k, m, ss): + encer = zfec.Encoder(k, m) + nums_and_blocks = list(enumerate(encer.encode(ss))) + assert isinstance(nums_and_blocks, list), nums_and_blocks + assert len(nums_and_blocks) == m, (len(nums_and_blocks), m,) + nums_and_blocks = random.sample(nums_and_blocks, k) + blocks = [ x[1] for x in nums_and_blocks ] + nums = [ x[0] for x in nums_and_blocks ] + decer = zfec.Decoder(k, m) + decoded = decer.decode(blocks, nums) + assert len(decoded) == len(ss), (len(decoded), len(ss),) + assert tuple([str(s) for s in decoded]) == tuple([str(s) for s in ss]), (tuple([ab(str(s)) for s in decoded]), tuple([ab(str(s)) for s in ss]),) + +def randstr(n): + return ''.join(map(chr, map(random.randrange, [0]*n, [256]*n))) + +def _help_test_random(): + m = random.randrange(1, 257) + k = random.randrange(1, m+1) + l = random.randrange(0, 2**10) + ss = [ randstr(l/k) for x in range(k) ] + _h(k, m, ss) + +def _help_test_random_with_l(l): + m = 83 + k = 19 + ss = [ randstr(l/k) for x in range(k) ] + _h(k, m, ss) + +class ZFec(unittest.TestCase): + def test_random(self): + for i in range(3): + _help_test_random() + if VERBOSE: + print "%d randomized tests pass." % (i+1) + + def test_bad_args_enc(self): + encer = zfec.Encoder(2, 4) + try: + encer.encode(["a", "b", ], ["c", "I am not an integer blocknum",]) + except zfec.Error, e: + assert "Precondition violation: second argument is required to contain int" in str(e), e + else: + raise "Should have gotten zfec.Error for wrong type of second argument." + + try: + encer.encode(["a", "b", ], 98) # not a sequence at all + except TypeError, e: + assert "Second argument (optional) was not a sequence" in str(e), e + else: + raise "Should have gotten TypeError for wrong type of second argument." + + def test_bad_args_dec(self): + decer = zfec.Decoder(2, 4) + + try: + decer.decode(98, [0, 1]) # first argument is not a sequence + except TypeError, e: + assert "First argument was not a sequence" in str(e), e + else: + raise "Should have gotten TypeError for wrong type of second argument." + + try: + decer.decode(["a", "b", ], ["c", "d",]) + except zfec.Error, e: + assert "Precondition violation: second argument is required to contain int" in str(e), e + else: + raise "Should have gotten zfec.Error for wrong type of second argument." + + try: + decer.decode(["a", "b", ], 98) # not a sequence at all + except TypeError, e: + assert "Second argument was not a sequence" in str(e), e + else: + raise "Should have gotten TypeError for wrong type of second argument." + +class FileFec(unittest.TestCase): + def test_filefec_header(self): + for m in [3, 5, 7, 9, 11, 17, 19, 33, 35, 65, 66, 67, 129, 130, 131, 254, 255, 256,]: + for k in [2, 3, 5, 9, 17, 33, 65, 129, 255,]: + if k >= m: + continue + for pad in [0, 1, k-1,]: + if pad >= k: + continue + for sh in [0, 1, m-1,]: + if sh >= m: + continue + h = zfec.filefec._build_header(m, k, pad, sh) + hio = cStringIO.StringIO(h) + (rm, rk, rpad, rsh,) = zfec.filefec._parse_header(hio) + assert (rm, rk, rpad, rsh,) == (m, k, pad, sh,), h + + def _help_test_filefec(self, teststr, k, m, numshs=None): + if numshs == None: + numshs = m + + TESTFNAME = "testfile.txt" + PREFIX = "test" + SUFFIX = ".fec" + + tempdir = zfec.util.fileutil.NamedTemporaryDirectory(cleanup=False) + try: + tempfn = os.path.join(tempdir.name, TESTFNAME) + tempf = open(tempfn, 'wb') + tempf.write(teststr) + tempf.close() + fsize = os.path.getsize(tempfn) + assert fsize == len(teststr) + + # encode the file + zfec.filefec.encode_to_files(open(tempfn, 'rb'), fsize, tempdir.name, PREFIX, k, m, SUFFIX, verbose=VERBOSE) + + # select some share files + RE=re.compile(zfec.filefec.RE_FORMAT % (PREFIX, SUFFIX,)) + fns = os.listdir(tempdir.name) + sharefs = [ open(os.path.join(tempdir.name, fn), "rb") for fn in fns if RE.match(fn) ] + random.shuffle(sharefs) + del sharefs[numshs:] + + # decode from the share files + outf = open(os.path.join(tempdir.name, 'recovered-testfile.txt'), 'wb') + zfec.filefec.decode_from_files(outf, sharefs, verbose=VERBOSE) + outf.close() + + tempfn = open(os.path.join(tempdir.name, 'recovered-testfile.txt'), 'rb') + recovereddata = tempfn.read() + assert recovereddata == teststr + finally: + tempdir.shutdown() + + def test_filefec_all_shares(self): + return self._help_test_filefec("Yellow Whirled!", 3, 8) + + def test_filefec_all_shares_with_padding(self, noisy=VERBOSE): + return self._help_test_filefec("Yellow Whirled!A", 3, 8) + + def test_filefec_min_shares_with_padding(self, noisy=VERBOSE): + return self._help_test_filefec("Yellow Whirled!A", 3, 8, numshs=3) + +if __name__ == "__main__": + if hasattr(unittest, 'main'): + unittest.main() + else: + sys.path.append(os.getcwd()) + mods = [] + fullname = os.path.realpath(os.path.abspath(__file__)) + for pathel in sys.path: + fullnameofpathel = os.path.realpath(os.path.abspath(pathel)) + if fullname.startswith(fullnameofpathel): + relname = fullname[len(fullnameofpathel):] + mod = (os.path.splitext(relname)[0]).replace(os.sep, '.').strip('.') + mods.append(mod) + + mods.sort(cmp=lambda x, y: cmp(len(x), len(y))) + mods.reverse() + for mod in mods: + cmdstr = "trial %s %s" % (' '.join(sys.argv[1:]), mod) + print cmdstr + if os.system(cmdstr) == 0: + break