Skip to content

Commit ff66548

Browse files
committed
tests: Add functional tests for 'bugzilla attach'
1 parent 89c6424 commit ff66548

5 files changed

Lines changed: 201 additions & 65 deletions

File tree

bin/bugzilla

Lines changed: 92 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ handler.setFormatter(logging.Formatter(
3737
log.addHandler(handler)
3838

3939

40+
################
41+
# Util helpers #
42+
################
43+
4044
def to_encoding(ustring):
4145
if isinstance(ustring, basestring):
4246
if isinstance(ustring, unicode):
@@ -45,6 +49,27 @@ def to_encoding(ustring):
4549
return u''
4650

4751

52+
def open_without_clobber(name, *args):
53+
'''Try to open the given file with the given mode; if that filename exists,
54+
try "name.1", "name.2", etc. until we find an unused filename.'''
55+
fd = None
56+
count = 1
57+
orig_name = name
58+
while fd is None:
59+
try:
60+
fd = os.open(name, os.O_CREAT|os.O_EXCL, 0666)
61+
except OSError as e:
62+
if e.errno == os.errno.EEXIST:
63+
name = "%s.%i" % (orig_name, count)
64+
count += 1
65+
else: # raise IOError like regular open()
66+
raise IOError, (e.errno, e.strerror, e.filename)
67+
fobj = open(name, *args)
68+
if fd != fobj.fileno():
69+
os.close(fd)
70+
return fobj
71+
72+
4873
##################
4974
# Option parsing #
5075
##################
@@ -79,6 +104,7 @@ def setup_parser():
79104

80105
return p
81106

107+
82108
def setup_action_parser(action):
83109
p = optparse.OptionParser(usage="%%prog %s [options]" % action)
84110

@@ -230,7 +256,8 @@ def setup_action_parser(action):
230256
help='Add an email to the cc list')
231257
p.add_option('-F','--fixed_in',metavar="VERSION",
232258
help='"Fixed in version" field')
233-
# TODO: --keyword, --tag, --cc, ...
259+
# TODO: --keyword, --tag, ...
260+
234261
elif action == 'attach':
235262
p.set_usage('''
236263
%prog attach --file=FILE --desc=DESC [--type=TYPE] BUGID [BUGID...]
@@ -247,6 +274,7 @@ def setup_action_parser(action):
247274
default=[], help="Download the attachment with the given ID")
248275
p.add_option("--getall","--get-all", metavar="BUGID", action="append",
249276
default=[], help="Download all attachments on the given bug")
277+
250278
elif action == 'login':
251279
p.set_usage('%prog login [username [password]]')
252280
p.set_description("Log into bugzilla and save a login cookie.")
@@ -718,6 +746,66 @@ def _do_modify(bz, opt, args):
718746
bz._update_bugs(bugid_list, data)
719747

720748

749+
def _do_attach(bz, opt, parser, args):
750+
# Getting attachments
751+
if opt.get or opt.getall:
752+
if args:
753+
parser.error("Extra args '%s' not used for getting attachments" %
754+
args)
755+
756+
for bug in bz.getbugs(opt.getall):
757+
opt.get += [a['attach_id'] for a in bug.attachments]
758+
759+
for attid in set(opt.get):
760+
att = bz.openattachment(attid)
761+
outfile = open_without_clobber(att.name, "wb")
762+
data = att.read(4096)
763+
while data:
764+
outfile.write(data)
765+
data = att.read(4096)
766+
print "Wrote %s" % outfile.name
767+
768+
return
769+
770+
# Setting attachments
771+
if not args:
772+
parser.error("Bug ID must be specified for setting attachments")
773+
774+
if not opt.type:
775+
try:
776+
import magic
777+
except ImportError:
778+
RuntimeError("specify --type or install python-magic")
779+
mimemagic = magic.open(magic.MAGIC_MIME_TYPE)
780+
mimemagic.load()
781+
782+
if sys.stdin.isatty():
783+
# stdin is a tty -> normal CLI
784+
if not opt.type:
785+
opt.type = mimemagic.file(opt.file)
786+
fileobj = open(opt.file)
787+
788+
else:
789+
# piped input on stdin
790+
# write it to a tempfile - and get the filetype if we need it
791+
fileobj = NamedTemporaryFile(prefix="bugzilla-attach.")
792+
data = sys.stdin.read(4096)
793+
if not opt.type:
794+
opt.type = mimemagic.buffer(data)
795+
while data:
796+
fileobj.write(data)
797+
data = sys.stdin.read(4096)
798+
fileobj.seek(0)
799+
800+
# Upload attachments
801+
for bugid in args:
802+
attid = bz.attachfile(bugid, fileobj, opt.desc,
803+
filename=opt.file,
804+
contenttype=opt.type,
805+
ispatch=(opt.type == "text/x-patch"))
806+
print "Created attachment %i on bug %s" % (attid, bugid)
807+
808+
721809
#################
722810
# Main function #
723811
#################
@@ -845,47 +933,10 @@ def main(bzinstance=None):
845933
buglist += newbugs
846934

847935
elif action == 'attach':
848-
if opt.get or opt.getall:
849-
for bug in bz.getbugs(opt.getall):
850-
opt.get += [a['attach_id'] for a in bug.attachments]
851-
for attid in set(opt.get):
852-
att = bz.openattachment(attid)
853-
outfile = bugzilla.util.open_without_clobber(att.name, "wb")
854-
data = att.read(4096)
855-
while data:
856-
outfile.write(data)
857-
data = att.read(4096)
858-
print "Wrote %s" % outfile.name
859-
return
860936
if not opt.file and opt.desc:
861-
parser.error("attaching a file requires --file and --desc")
862-
if not opt.type:
863-
try:
864-
import magic
865-
except ImportError:
866-
parser.error("specify --type or install python-magic")
867-
mimemagic = magic.open(magic.MAGIC_MIME_TYPE)
868-
mimemagic.load()
869-
if sys.stdin.isatty(): # stdin is a tty -> normal CLI
870-
if not opt.type:
871-
opt.type = mimemagic.file(opt.file)
872-
fileobj = open(opt.file)
873-
else: # piped input on stdin
874-
# write it to a tempfile - and get the filetype if we need it
875-
fileobj = NamedTemporaryFile(prefix="bugzilla-attach.")
876-
data = sys.stdin.read(4096)
877-
if not opt.type:
878-
opt.type = mimemagic.buffer(data)
879-
while data:
880-
fileobj.write(data)
881-
data = sys.stdin.read(4096)
882-
fileobj.seek(0)
883-
for bugid in args:
884-
attid = bz.attachfile(bugid, fileobj, opt.desc,
885-
filename=opt.file,
886-
contenttype=opt.type,
887-
ispatch=(opt.type == "text/x-patch"))
888-
print "Created attachment %i on bug %s" % (attid, bugid)
937+
parser.error("attaching a file requires --file and --description")
938+
939+
_do_attach(bz, opt, parser, args)
889940

890941
elif action == 'modify':
891942
if opt.dupeid is not None:

bugzilla/util.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,3 @@ def url_to_query(url):
1818
oldv = q[k]
1919
q[k] = [oldv, v]
2020
return q
21-
22-
def open_without_clobber(name, *args):
23-
'''Try to open the given file with the given mode; if that filename exists,
24-
try "name.1", "name.2", etc. until we find an unused filename.'''
25-
fd = None
26-
count = 1
27-
orig_name = name
28-
while fd is None:
29-
try:
30-
fd = os.open(name, os.O_CREAT|os.O_EXCL, 0666)
31-
except OSError as e:
32-
if e.errno == os.errno.EEXIST:
33-
name = "%s.%i" % (orig_name, count)
34-
count += 1
35-
else: # raise IOError like regular open()
36-
raise IOError, (e.errno, e.strerror, e.filename)
37-
fobj = open(name, *args)
38-
if fd != fobj.fileno():
39-
os.close(fd)
40-
return fobj

tests/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def difffile(expect, filename):
2222
if ret:
2323
raise AssertionError("Output was different:\n%s" % ret)
2424

25-
def clicomm(argv, bzinstance, returnmain=False, printcliout=False):
25+
def clicomm(argv, bzinstance, returnmain=False, printcliout=False,
26+
stdin=None):
2627
"""
2728
Run bin/bugzilla.main() directly with passed argv
2829
"""
@@ -39,7 +40,8 @@ def clicomm(argv, bzinstance, returnmain=False, printcliout=False):
3940
out = StringIO.StringIO()
4041
sys.stdout = out
4142
sys.stderr = out
42-
sys.stdin = None
43+
if stdin:
44+
sys.stdin = stdin
4345
sys.argv = argv
4446

4547
ret = 0

tests/data/bz-attach-get1.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--- base.py.old 2010-12-16 12:15:09.932010659 +0100
2+
+++ base.py 2010-12-16 16:04:18.995185933 +0100
3+
@@ -19,6 +19,8 @@
4+
import tempfile
5+
import logging
6+
import locale
7+
+import email.header
8+
+import re
9+
10+
log = logging.getLogger('bugzilla')
11+
12+
@@ -677,10 +679,17 @@
13+
# RFC 2183 defines the content-disposition header, if you're curious
14+
disp = att.headers['content-disposition'].split(';')
15+
[filename_parm] = [i for i in disp if i.strip().startswith('filename=')]
16+
- (dummy,filename) = filename_parm.split('=')
17+
- # RFC 2045/822 defines the grammar for the filename value, but
18+
- # I think we just need to remove the quoting. I hope.
19+
- att.name = filename.strip('"')
20+
+ (dummy,filename) = filename_parm.split('=',1)
21+
+ # RFC 2045/822 defines the grammar for the filename value
22+
+ filename = filename.strip('"')
23+
+ # email.header.decode_header cannot handle strings not ending with '?=',
24+
+ # so let's transform one =?...?= part at a time
25+
+ while True:
26+
+ match = re.search("=\?.*?\?=", filename)
27+
+ if match is None:
28+
+ break
29+
+ filename = filename[:match.start()] + email.header.decode_header(match.group(0))[0][0] + filename[match.end():]
30+
+ att.name = filename
31+
# Hooray, now we have a file-like object with .read() and .name
32+
return att
33+

tests/rw_functional.py

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def test3NewBugBasic(self):
6363
"""
6464
Create a bug with minimal amount of fields, then close it
6565
"""
66-
66+
bz = self.bzclass(url=self.url, cookiefile=self._getCookiefile())
6767
component = "python-bugzilla"
6868
version = "16"
6969
summary = ("python-bugzilla test basic bug %s" %
@@ -122,7 +122,6 @@ def test4NewBugAllFields(self):
122122
bug = bz.getbug(bugid)
123123
print "\nCreated bugid: %s" % bugid
124124

125-
# XXX: check full output for comment?
126125
self.assertEquals(bug.summary, summary)
127126
self.assertEquals(bug.bug_file_loc, url)
128127
self.assertEquals(bug.op_sys, osval)
@@ -332,3 +331,74 @@ def cleardict(b):
332331
bug2.refresh()
333332
self.assertEquals(bug1.fixed_in, "-")
334333
self.assertEquals(bug2.fixed_in, "-")
334+
335+
336+
def test8Attachments(self):
337+
"""
338+
Get and set attachments for a bug
339+
"""
340+
bz = self.bzclass(url=self.url, cookiefile=self._getCookiefile())
341+
getbugid = "663674"
342+
setbugid = "461686"
343+
attachid = "469147"
344+
cmd = "bugzilla attach "
345+
testfile = "../tests/data/bz-attach-get1.txt"
346+
347+
tmpdir = "__test_attach_output"
348+
if tmpdir in os.listdir("."):
349+
os.system("rm -r %s" % tmpdir)
350+
os.mkdir(tmpdir)
351+
os.chdir(tmpdir)
352+
353+
# Get first attachment
354+
out = tests.clicomm(cmd + "--get %s" % attachid, bz).splitlines()
355+
356+
# Expect format:
357+
# Wrote <filename>
358+
fname = out[2].split()[1].strip()
359+
360+
self.assertEquals(len(out), 3)
361+
self.assertEquals(fname, "bugzilla-filename.patch")
362+
self.assertEquals(file(fname).read(),
363+
file(testfile).read())
364+
365+
# Get all attachments
366+
getbug = bz.getbug(getbugid)
367+
numattach = len(getbug.attachments)
368+
out = tests.clicomm(cmd + "--getall %s" % getbugid, bz).splitlines()
369+
370+
self.assertEquals(len(out), numattach + 2)
371+
fnames = [l.split(" ", 1)[1].strip() for l in out[2:]]
372+
self.assertEquals(len(fnames), numattach)
373+
for f in fnames:
374+
if not os.path.exists(f):
375+
raise AssertionError("filename '%s' not found" % f)
376+
os.unlink(f)
377+
378+
# Add attachment as CLI option
379+
setbug = bz.getbug(setbugid)
380+
orignumattach = len(setbug.attachments)
381+
382+
# Add attachment from CLI with mime guessing
383+
desc1 = "python-bugzilla cli upload %s" % datetime.datetime.today()
384+
out1 = tests.clicomm(cmd + "%s --description \"%s\" --file %s" %
385+
(setbugid, desc1, testfile), bz)
386+
387+
desc2 = "python-bugzilla cli upload %s" % datetime.datetime.today()
388+
out2 = tests.clicomm(cmd + "%s --file test --description \"%s\"" %
389+
(setbugid, desc2), bz, stdin=open(testfile))
390+
391+
# Expected output format:
392+
# Created attachment <attachid> on bug <bugid>
393+
394+
setbug.refresh()
395+
self.assertEquals(len(setbug.attachments), orignumattach + 2)
396+
self.assertEquals(setbug.attachments[-2]["description"], desc1)
397+
self.assertEquals(setbug.attachments[-2]["id"],
398+
int(out1.splitlines()[2].split()[2]))
399+
self.assertEquals(setbug.attachments[-1]["description"], desc2)
400+
self.assertEquals(setbug.attachments[-1]["id"],
401+
int(out2.splitlines()[2].split()[2]))
402+
403+
os.chdir("..")
404+
os.system("rm -r %s" % tmpdir)

0 commit comments

Comments
 (0)