From 9e1f88391f7e566c6f67d033ebd19401c7bc4c42 Mon Sep 17 00:00:00 2001 From: Kamil Dudka Date: Apr 09 2013 13:48:57 +0000 Subject: sync with upstream 9b8f4e38 (fixes #928370) --- diff --git a/0000-pycurl-7.19.7-9b8f4e38.patch b/0000-pycurl-7.19.7-9b8f4e38.patch new file mode 100644 index 0000000..8666d1b --- /dev/null +++ b/0000-pycurl-7.19.7-9b8f4e38.patch @@ -0,0 +1,9416 @@ +From 15a1c96494611dafd6444655bce0b13c64a6172f Mon Sep 17 00:00:00 2001 +From: Kjetil Jacobsen +Date: Tue, 9 Sep 2008 18:49:59 +0000 +Subject: [PATCH 001/112] whitespace + +Signed-off-by: Kamil Dudka +--- + ChangeLog | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/ChangeLog b/ChangeLog +index 0fb7f8c..3d68424 100644 +--- a/ChangeLog ++++ b/ChangeLog +@@ -1,7 +1,7 @@ + Version 7.19.0 [requires libcurl-7.19.0 or better] + -------------- + +- * Added CURLFILE, ADDRESS_SCOPE and ISSUERCERT options, ++ * Added CURLFILE, ADDRESS_SCOPE and ISSUERCERT options, + as well as the APPCONNECT_TIME info. + + * Added PRIMARY_IP info (patch by +-- +1.7.1 + + +From f79d0ea41bad7ea25cf949ca5398ed61b87de722 Mon Sep 17 00:00:00 2001 +From: Kjetil Jacobsen +Date: Mon, 29 Sep 2008 10:56:57 +0000 +Subject: [PATCH 002/112] No longer keep copies of string options since this is managed by libcurl + +Signed-off-by: Kamil Dudka +--- + ChangeLog | 10 +++++++++- + src/pycurl.c | 58 +--------------------------------------------------------- + 2 files changed, 10 insertions(+), 58 deletions(-) + +diff --git a/ChangeLog b/ChangeLog +index 3d68424..618654d 100644 +--- a/ChangeLog ++++ b/ChangeLog +@@ -1,4 +1,12 @@ +-Version 7.19.0 [requires libcurl-7.19.0 or better] ++Version 7.19.1 [requires libcurl-7.19.0 or better] ++-------------- ++ ++ * No longer keep string options copies in the ++ Curl Python objects, since string options are ++ now managed by libcurl. ++ ++ ++Version 7.19.0 + -------------- + + * Added CURLFILE, ADDRESS_SCOPE and ISSUERCERT options, +diff --git a/src/pycurl.c b/src/pycurl.c +index 50ec9ce..189f5d6 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -97,12 +97,6 @@ static void pycurl_ssl_cleanup(void); + /* Calculate the number of OBJECTPOINT options we need to store */ + #define OPTIONS_SIZE ((int)CURLOPT_LASTENTRY % 10000) + #define MOPTIONS_SIZE ((int)CURLMOPT_LASTENTRY % 10000) +-static int OPT_INDEX(int o) +-{ +- assert(o >= CURLOPTTYPE_OBJECTPOINT); +- assert(o < CURLOPTTYPE_OBJECTPOINT + OPTIONS_SIZE); +- return o - CURLOPTTYPE_OBJECTPOINT; +-} + + /* Type objects */ + static PyObject *ErrorObject = NULL; +@@ -161,7 +155,6 @@ typedef struct { + PyObject *writedata_fp; + PyObject *writeheader_fp; + /* misc */ +- void *options[OPTIONS_SIZE]; /* for OBJECTPOINT options */ + char error[CURL_ERROR_SIZE+1]; + } CurlObject; + +@@ -741,7 +734,6 @@ util_curl_new(void) + self->writeheader_fp = NULL; + + /* Zero string pointer memory buffer used by setopt */ +- memset(self->options, 0, sizeof(self->options)); + memset(self->error, 0, sizeof(self->error)); + + return self; +@@ -804,7 +796,6 @@ do_curl_new(PyObject *dummy) + free(s); + goto error; + } +- self->options[ OPT_INDEX(CURLOPT_USERAGENT) ] = s; s = NULL; + + /* Success - return new object */ + return self; +@@ -872,7 +863,6 @@ static void + util_curl_close(CurlObject *self) + { + CURL *handle; +- int i; + + /* Zero handle and thread-state to disallow any operations to be run + * from now on */ +@@ -916,16 +906,6 @@ util_curl_close(CurlObject *self) + SFREE(self->postquote); + SFREE(self->prequote); + #undef SFREE +- +- /* Last, free the options. This must be done after the curl handle +- * is closed since libcurl assumes that some options are valid when +- * invoking curl_easy_cleanup(). */ +- for (i = 0; i < OPTIONS_SIZE; i++) { +- if (self->options[i] != NULL) { +- free(self->options[i]); +- self->options[i] = NULL; +- } +- } + } + + +@@ -1424,8 +1404,6 @@ verbose_error: + static PyObject* + do_curl_reset(CurlObject *self) + { +- unsigned int i; +- + curl_easy_reset(self->handle); + + /* Decref callbacks and file handles */ +@@ -1443,15 +1421,6 @@ do_curl_reset(CurlObject *self) + SFREE(self->postquote); + SFREE(self->prequote); + #undef SFREE +- +- /* Last, free the options */ +- for (i = 0; i < OPTIONS_SIZE; i++) { +- if (self->options[i] != NULL) { +- free(self->options[i]); +- self->options[i] = NULL; +- } +- } +- + return Py_None; + } + +@@ -1461,7 +1430,6 @@ static PyObject * + util_curl_unsetopt(CurlObject *self, int option) + { + int res; +- int opt_index = -1; + + #define SETOPT2(o,x) \ + if ((res = curl_easy_setopt(self->handle, (o), (x))) != CURLE_OK) goto error +@@ -1502,7 +1470,6 @@ util_curl_unsetopt(CurlObject *self, int option) + case CURLOPT_SSL_CIPHER_LIST: + case CURLOPT_USERPWD: + SETOPT((char *) 0); +- opt_index = OPT_INDEX(option); + break; + + /* info: we explicitly list unsupported options here */ +@@ -1512,11 +1479,6 @@ util_curl_unsetopt(CurlObject *self, int option) + return NULL; + } + +- if (opt_index >= 0 && self->options[opt_index] != NULL) { +- free(self->options[opt_index]); +- self->options[opt_index] = NULL; +- } +- + Py_INCREF(Py_None); + return Py_None; + +@@ -1587,8 +1549,6 @@ do_curl_setopt(CurlObject *self, PyObject *args) + if (PyString_Check(obj)) { + char *str = NULL; + Py_ssize_t len = -1; +- char *buf; +- int opt_index; + + /* Check that the option specified a string as well as the input */ + switch (option) { +@@ -1651,28 +1611,12 @@ do_curl_setopt(CurlObject *self, PyObject *args) + } + /* Allocate memory to hold the string */ + assert(str != NULL); +- if (len <= 0) +- buf = strdup(str); +- else { +- buf = (char *) malloc(len); +- if (buf) memcpy(buf, str, len); +- } +- if (buf == NULL) +- return PyErr_NoMemory(); + /* Call setopt */ +- res = curl_easy_setopt(self->handle, (CURLoption)option, buf); ++ res = curl_easy_setopt(self->handle, (CURLoption)option, str); + /* Check for errors */ + if (res != CURLE_OK) { +- free(buf); + CURLERROR_RETVAL(); + } +- /* Save allocated option buffer */ +- opt_index = OPT_INDEX(option); +- if (self->options[opt_index] != NULL) { +- free(self->options[opt_index]); +- self->options[opt_index] = NULL; +- } +- self->options[opt_index] = buf; + Py_INCREF(Py_None); + return Py_None; + } +-- +1.7.1 + + +From 5267a6278f4b3993811a5eacf068942e01416d29 Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Fri, 23 Apr 2010 16:06:41 +0000 +Subject: [PATCH 003/112] Fixes https://sourceforge.net/tracker/?func=detail&aid=2812016&group_id=28236&atid=392777 with applied patch from sourceforge user dbprice1. + +Signed-off-by: Kamil Dudka +--- + setup.py | 21 +++++++++++++++++---- + 1 files changed, 17 insertions(+), 4 deletions(-) + +diff --git a/setup.py b/setup.py +index 1c3831b..632399d 100644 +--- a/setup.py ++++ b/setup.py +@@ -9,7 +9,7 @@ PACKAGE = "pycurl" + PY_PACKAGE = "curl" + VERSION = "7.19.0" + +-import glob, os, re, sys, string ++import glob, os, re, sys, string, subprocess + import distutils + from distutils.core import setup + from distutils.extension import Extension +@@ -96,9 +96,22 @@ else: + include_dirs.append(e[2:]) + else: + extra_compile_args.append(e) +- libs = split_quoted( +- os.popen("'%s' --libs" % CURL_CONFIG).read()+\ +- os.popen("'%s' --static-libs" % CURL_CONFIG).read()) ++ ++ # Run curl-config --libs and --static-libs. Some platforms may not ++ # support one or the other of these curl-config options, so gracefully ++ # tolerate failure of either, but not both. ++ optbuf = "" ++ for option in ["--libs", "--static-libs"]: ++ p = subprocess.Popen("'%s' %s" % (CURL_CONFIG, option), shell=True, ++ stdout=subprocess.PIPE) ++ (stdout, stderr) = p.communicate() ++ if p.wait() == 0: ++ optbuf += stdout ++ if optbuf == "": ++ raise Exception, ("Neither of curl-config --libs or --static-libs" + ++ "produced output") ++ libs = split_quoted(optbuf) ++ + for e in libs: + if e[:2] == "-l": + libraries.append(e[2:]) +-- +1.7.1 + + +From efeb6581db4c6fd8980bb4007d14afce96ce4642 Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Wed, 28 Apr 2010 16:02:41 +0000 +Subject: [PATCH 004/112] Fixes refcount bug and provides better organization of PyCurl object. Submitted by dbprice1. + +https://sourceforge.net/tracker/?func=detail&aid=2893665&group_id=28236&atid=392777 + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 87 ++++++++++++++++++++++++++++++++++++++-------------------- + 1 files changed, 57 insertions(+), 30 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index 189f5d6..47de850 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -739,64 +739,80 @@ util_curl_new(void) + return self; + } + +- +-/* constructor - this is a module-level function returning a new instance */ +-static CurlObject * +-do_curl_new(PyObject *dummy) ++/* initializer - used to intialize curl easy handles for use with pycurl */ ++static int ++util_curl_init(CurlObject *self) + { +- CurlObject *self = NULL; + int res; + char *s = NULL; + +- UNUSED(dummy); +- +- /* Allocate python curl object */ +- self = util_curl_new(); +- if (self == NULL) +- return NULL; +- +- /* Initialize curl handle */ +- self->handle = curl_easy_init(); +- if (self->handle == NULL) +- goto error; +- + /* Set curl error buffer and zero it */ + res = curl_easy_setopt(self->handle, CURLOPT_ERRORBUFFER, self->error); +- if (res != CURLE_OK) +- goto error; ++ if (res != CURLE_OK) { ++ return (-1); ++ } + memset(self->error, 0, sizeof(self->error)); + + /* Set backreference */ + res = curl_easy_setopt(self->handle, CURLOPT_PRIVATE, (char *) self); +- if (res != CURLE_OK) +- goto error; ++ if (res != CURLE_OK) { ++ return (-1); ++ } + + /* Enable NOPROGRESS by default, i.e. no progress output */ + res = curl_easy_setopt(self->handle, CURLOPT_NOPROGRESS, (long)1); +- if (res != CURLE_OK) +- goto error; ++ if (res != CURLE_OK) { ++ return (-1); ++ } + + /* Disable VERBOSE by default, i.e. no verbose output */ + res = curl_easy_setopt(self->handle, CURLOPT_VERBOSE, (long)0); +- if (res != CURLE_OK) +- goto error; ++ if (res != CURLE_OK) { ++ return (-1); ++ } + + /* Set FTP_ACCOUNT to NULL by default */ + res = curl_easy_setopt(self->handle, CURLOPT_FTP_ACCOUNT, NULL); +- if (res != CURLE_OK) +- goto error; ++ if (res != CURLE_OK) { ++ return (-1); ++ } + + /* Set default USERAGENT */ + s = (char *) malloc(7 + strlen(LIBCURL_VERSION) + 1); +- if (s == NULL) +- goto error; ++ if (s == NULL) { ++ return (-1); ++ } + strcpy(s, "PycURL/"); strcpy(s+7, LIBCURL_VERSION); + res = curl_easy_setopt(self->handle, CURLOPT_USERAGENT, (char *) s); + if (res != CURLE_OK) { + free(s); +- goto error; ++ return (-1); + } ++ return (0); ++} ++ ++/* constructor - this is a module-level function returning a new instance */ ++static CurlObject * ++do_curl_new(PyObject *dummy) ++{ ++ CurlObject *self = NULL; ++ int res; ++ ++ UNUSED(dummy); ++ ++ /* Allocate python curl object */ ++ self = util_curl_new(); ++ if (self == NULL) ++ return NULL; ++ ++ /* Initialize curl handle */ ++ self->handle = curl_easy_init(); ++ if (self->handle == NULL) ++ goto error; + ++ res = util_curl_init(self); ++ if (res < 0) ++ goto error; + /* Success - return new object */ + return self; + +@@ -1404,6 +1420,8 @@ verbose_error: + static PyObject* + do_curl_reset(CurlObject *self) + { ++ int res; ++ + curl_easy_reset(self->handle); + + /* Decref callbacks and file handles */ +@@ -1421,10 +1439,19 @@ do_curl_reset(CurlObject *self) + SFREE(self->postquote); + SFREE(self->prequote); + #undef SFREE ++ res = util_curl_init(self); ++ if (res < 0) { ++ Py_DECREF(self); /* this also closes self->handle */ ++ PyErr_SetString(ErrorObject, "resetting curl failed"); ++ return NULL; ++ } ++ ++ Py_INCREF(Py_None); + return Py_None; + } + + /* --------------- unsetopt/setopt/getinfo --------------- */ ++ int res; + + static PyObject * + util_curl_unsetopt(CurlObject *self, int option) +-- +1.7.1 + + +From 6c8f676e53cf3a54836134ddc11d7a707793414d Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Wed, 28 Apr 2010 16:03:40 +0000 +Subject: [PATCH 005/112] Test for reset fixes refcount bug + +Signed-off-by: Kamil Dudka +--- + tests/test_reset.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 75 insertions(+), 0 deletions(-) + create mode 100644 tests/test_reset.py + +diff --git a/tests/test_reset.py b/tests/test_reset.py +new file mode 100644 +index 0000000..1addcfe +--- /dev/null ++++ b/tests/test_reset.py +@@ -0,0 +1,75 @@ ++#!/usr/bin/python ++ ++import sys ++import pycurl ++ ++saw_error = 1 ++ ++def main(): ++ global saw_error ++ ++ pycurl.global_init(pycurl.GLOBAL_DEFAULT) ++ ++ outf = file("/dev/null", "rb+") ++ cm = pycurl.CurlMulti() ++ ++ # Set multi handle's options ++ cm.setopt(pycurl.M_PIPELINING, 1) ++ ++ eh = pycurl.Curl() ++ ++ for x in range(1, 20): ++ ++ eh.setopt(pycurl.WRITEDATA, outf) ++ eh.setopt(pycurl.URL, sys.argv[1]) ++ cm.add_handle(eh) ++ ++ while 1: ++ ret, active_handles = cm.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ ++ while active_handles: ++ ret = cm.select(1.0) ++ if ret == -1: ++ continue ++ while 1: ++ ret, active_handles = cm.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ ++ count, good, bad = cm.info_read() ++ ++ for h, en, em in bad: ++ print "Transfer to %s failed with %d, %s\n" % \ ++ (h.getinfo(pycurl.EFFECTIVE_URL), en, em) ++ raise RuntimeError ++ ++ for h in good: ++ httpcode = h.getinfo(pycurl.RESPONSE_CODE) ++ if httpcode != 200: ++ print "Transfer to %s failed with code %d\n" %\ ++ (h.getinfo(pycurl.EFFECTIVE_URL), httpcode) ++ raise RuntimeError ++ ++ else: ++ print "Recd %d bytes from %s" % \ ++ (h.getinfo(pycurl.SIZE_DOWNLOAD), ++ h.getinfo(pycurl.EFFECTIVE_URL)) ++ ++ cm.remove_handle(eh) ++ eh.reset() ++ ++ eh.close() ++ cm.close() ++ outf.close() ++ ++ pycurl.global_cleanup() ++ ++ ++if __name__ == '__main__': ++ if len(sys.argv) != 2: ++ print "Usage: %s " % sys.argv[0] ++ sys.exit(2) ++ main() ++ +-- +1.7.1 + + +From 5c2023a7a9373d7ca816c2546fd2db63432bba0c Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Wed, 28 Apr 2010 16:06:35 +0000 +Subject: [PATCH 006/112] Added myself to the list of maintainers + +Signed-off-by: Kamil Dudka +--- + setup.py | 4 ++-- + 1 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/setup.py b/setup.py +index 632399d..33704ef 100644 +--- a/setup.py ++++ b/setup.py +@@ -197,8 +197,8 @@ setup_args = get_kw( + description="PycURL -- cURL library module for Python", + author="Kjetil Jacobsen, Markus F.X.J. Oberhumer", + author_email="kjetilja at gmail.com, markus at oberhumer.com", +- maintainer="Kjetil Jacobsen, Markus F.X.J. Oberhumer", +- maintainer_email="kjetilja at gmail.com, markus at oberhumer.com", ++ maintainer="Kjetil Jacobsen, Markus F.X.J. Oberhumer, Christopher Warner", ++ maintainer_email="kjetilja at gmail.com, markus at oberhumer.com, cwarner at kernelcode.com", + url="http://pycurl.sourceforge.net/", + license="LGPL/MIT", + data_files=get_data_files(), +-- +1.7.1 + + +From 86777baedfac6c10ec0edd4e1e68519be83740e1 Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Tue, 4 May 2010 18:47:08 +0000 +Subject: [PATCH 007/112] Updating ChangeLog with relevant changes + +Signed-off-by: Kamil Dudka +--- + ChangeLog | 11 +++++++++++ + 1 files changed, 11 insertions(+), 0 deletions(-) + +diff --git a/ChangeLog b/ChangeLog +index 618654d..885c8b0 100644 +--- a/ChangeLog ++++ b/ChangeLog +@@ -1,3 +1,14 @@ ++Version 7.19.2 ++-------------- ++ ++ * Cleaned up website ++ ++ * Fix pycurl.reset() (patch by ). ++ ++ * Fix install routine in setup.py where ++ certain platforms (Solaris, Mac OSX, etc) ++ would search for a static copy of libcurl (dbp) ++ + Version 7.19.1 [requires libcurl-7.19.0 or better] + -------------- + +-- +1.7.1 + + +From f42d34b86ce7c4d93bde71d7d1c56486bd98afa3 Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Wed, 13 Oct 2010 15:53:40 +0000 +Subject: [PATCH 008/112] Added CURLOPT_SEEKFUNCTION, CURLOPT_SEEKDATA + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- + 1 files changed, 88 insertions(+), 1 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index 47de850..ac448b0 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -150,6 +150,7 @@ typedef struct { + PyObject *debug_cb; + PyObject *ioctl_cb; + PyObject *opensocket_cb; ++ PyObject *seek_cb; + /* file objects */ + PyObject *readdata_fp; + PyObject *writedata_fp; +@@ -727,6 +728,7 @@ util_curl_new(void) + self->debug_cb = NULL; + self->ioctl_cb = NULL; + self->opensocket_cb = NULL; ++ self->seek_cb = NULL; + + /* Set file object pointers to NULL by default */ + self->readdata_fp = NULL; +@@ -1181,6 +1183,82 @@ verbose_error: + goto silent_error; + } + ++static int ++seek_callback(void *stream, curl_off_t offset, int origin) ++{ ++ CurlObject *self; ++ PyThreadState *tmp_state; ++ PyObject *arglist; ++ PyObject *result = NULL; ++ int ret = 2; /* assume error 2 (can't seek, libcurl free to work around). */ ++ PyObject *cb; ++ int source = 0; /* assume beginning */ ++ ++ /* acquire thread */ ++ self = (CurlObject *)stream; ++ tmp_state = get_thread_state(self); ++ if (tmp_state == NULL) ++ return ret; ++ PyEval_AcquireThread(tmp_state); ++ ++ /* check arguments */ ++ switch (origin) ++ { ++ case SEEK_SET: ++ source = 0; ++ break; ++ case SEEK_CUR: ++ source = 1; ++ break; ++ case SEEK_END: ++ source = 2; ++ break; ++ default: ++ source = origin; ++ break; ++ } ++ ++ /* run callback */ ++ cb = self->seek_cb; ++ if (cb == NULL) ++ goto silent_error; ++ arglist = Py_BuildValue("(i,i)", offset, source); ++ if (arglist == NULL) ++ goto verbose_error; ++ result = PyEval_CallObject(cb, arglist); ++ Py_DECREF(arglist); ++ if (result == NULL) ++ goto verbose_error; ++ ++ /* handle result */ ++ if (result == Py_None) { ++ ret = 0; /* None means success */ ++ } ++ else if (PyInt_Check(result)) { ++ int ret_code = PyInt_AsLong(result); ++ if (ret_code < 0 || ret_code > 2) { ++ PyErr_Format(ErrorObject, "invalid return value for seek callback %d not in (0, 1, 2)", ret_code); ++ goto verbose_error; ++ } ++ ret = ret_code; /* pass the return code from the callback */ ++ } ++ else { ++ PyErr_SetString(ErrorObject, "seek callback must return 0 (CURL_SEEKFUNC_OK), 1 (CURL_SEEKFUNC_FAIL), 2 (CURL_SEEKFUNC_CANTSEEK) or None"); ++ goto verbose_error; ++ } ++ ++silent_error: ++ Py_XDECREF(result); ++ PyEval_ReleaseThread(tmp_state); ++ return ret; ++verbose_error: ++ PyErr_Print(); ++ goto silent_error; ++} ++ ++ ++ ++ + static size_t + read_callback(char *ptr, size_t size, size_t nmemb, void *stream) + { +@@ -1988,7 +2066,8 @@ do_curl_setopt(CurlObject *self, PyObject *args) + const curl_progress_callback pro_cb = progress_callback; + const curl_debug_callback debug_cb = debug_callback; + const curl_ioctl_callback ioctl_cb = ioctl_callback; +- const curl_opensocket_callback opensocket_cb = opensocket_callback; ++ const curl_opensocket_callback opensocket_cb = opensocket_callback; ++ const curl_seek_callback seek_cb = seek_callback; + + switch(option) { + case CURLOPT_WRITEFUNCTION: +@@ -2046,6 +2125,13 @@ do_curl_setopt(CurlObject *self, PyObject *args) + curl_easy_setopt(self->handle, CURLOPT_OPENSOCKETFUNCTION, opensocket_cb); + curl_easy_setopt(self->handle, CURLOPT_OPENSOCKETDATA, self); + break; ++ case CURLOPT_SEEKFUNCTION: ++ Py_INCREF(obj); ++ ZAP(self->seek_cb); ++ self->seek_cb = obj; ++ curl_easy_setopt(self->handle, CURLOPT_SEEKFUNCTION, seek_cb); ++ curl_easy_setopt(self->handle, CURLOPT_SEEKDATA, self); ++ break; + + default: + /* None of the function options were recognized, throw exception */ +@@ -3616,6 +3702,7 @@ initpycurl(void) + insint_c(d, "PREQUOTE", CURLOPT_PREQUOTE); + insint_c(d, "WRITEHEADER", CURLOPT_WRITEHEADER); + insint_c(d, "HEADERFUNCTION", CURLOPT_HEADERFUNCTION); ++ insint_c(d, "SEEKFUNCTION", CURLOPT_SEEKFUNCTION); + insint_c(d, "COOKIEFILE", CURLOPT_COOKIEFILE); + insint_c(d, "SSLVERSION", CURLOPT_SSLVERSION); + insint_c(d, "TIMECONDITION", CURLOPT_TIMECONDITION); +-- +1.7.1 + + +From cd16f58fd7e2a089408ad0275bdcac6d6e5e1b7e Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Tue, 26 Feb 2013 09:13:24 +0100 +Subject: [PATCH 009/112] pycurl.c: remove a left-over global variable introduced in r150 + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 1 - + 1 files changed, 0 insertions(+), 1 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index ac448b0..094bc60 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -1529,7 +1529,6 @@ do_curl_reset(CurlObject *self) + } + + /* --------------- unsetopt/setopt/getinfo --------------- */ +- int res; + + static PyObject * + util_curl_unsetopt(CurlObject *self, int option) +-- +1.7.1 + + +From 802240d937867454026b6d8baaded841f6f32057 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Mon, 25 Feb 2013 19:50:18 +0100 +Subject: [PATCH 010/112] test_internals.py: add a test for ref-counting in reset() + +Signed-off-by: Kamil Dudka +--- + tests/test_internals.py | 5 +++++ + 1 files changed, 5 insertions(+), 0 deletions(-) + +diff --git a/tests/test_internals.py b/tests/test_internals.py +index a1a6533..3f5eefd 100644 +--- a/tests/test_internals.py ++++ b/tests/test_internals.py +@@ -245,6 +245,11 @@ if 1 and gc: + if opts.verbose >= 1: + print "Tracked objects:", len(gc.get_objects()) + ++if 1: ++ # Ensure that the refcounting error in "reset" is fixed: ++ for i in xrange(100000): ++ c = Curl() ++ c.reset() + + # /*********************************************************************** + # // done +-- +1.7.1 + + +From 2baf0fab555c9f0f292e400222c79b01f9211abf Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 01:15:00 -0500 +Subject: [PATCH 011/112] Add gitignore + +Signed-off-by: Kamil Dudka +--- + .gitignore | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + create mode 100644 .gitignore + +diff --git a/.gitignore b/.gitignore +new file mode 100644 +index 0000000..796b96d +--- /dev/null ++++ b/.gitignore +@@ -0,0 +1 @@ ++/build +-- +1.7.1 + + +From 53a2bfa7c8f381a4d4465d204fc3e4f7133a2db6 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 1 Mar 2013 00:11:47 -0500 +Subject: [PATCH 012/112] A much simpler request test, in unittest format + +Signed-off-by: Kamil Dudka +--- + tests/request_test.py | 31 +++++++++++++++++++++++++++++++ + 1 files changed, 31 insertions(+), 0 deletions(-) + create mode 100644 tests/request_test.py + +diff --git a/tests/request_test.py b/tests/request_test.py +new file mode 100644 +index 0000000..0761d6c +--- /dev/null ++++ b/tests/request_test.py +@@ -0,0 +1,31 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++try: ++ from cStringIO import StringIO ++except ImportError: ++ try: ++ from StringIO import StringIO ++ except ImportError: ++ from io import StringIO ++ ++class RequestTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_perform_get(self): ++ self.curl.setopt(pycurl.URL, 'http://localhost') ++ self.curl.perform() ++ ++ def test_perform_get_with_write_function(self): ++ self.curl.setopt(pycurl.URL, 'http://localhost') ++ sio = StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.perform() ++ print(sio.getvalue()) +-- +1.7.1 + + +From 127442bead3a274cc5239e6389682d239c1fcbca Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 1 Mar 2013 13:35:59 -0500 +Subject: [PATCH 013/112] init.py for test package + +Signed-off-by: Kamil Dudka +--- + 0 files changed, 0 insertions(+), 0 deletions(-) + create mode 100644 tests/__init__.py + +diff --git a/tests/__init__.py b/tests/__init__.py +new file mode 100644 +index 0000000..e69de29 +-- +1.7.1 + + +From 97ac029a9e925bd4903bd7a41e69110e158ec897 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 1 Mar 2013 13:36:08 -0500 +Subject: [PATCH 014/112] wsgi application runner + +Signed-off-by: Kamil Dudka +--- + tests/runwsgi.py | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 82 insertions(+), 0 deletions(-) + create mode 100644 tests/runwsgi.py + +diff --git a/tests/runwsgi.py b/tests/runwsgi.py +new file mode 100644 +index 0000000..d658614 +--- /dev/null ++++ b/tests/runwsgi.py +@@ -0,0 +1,82 @@ ++# Run a WSGI application in a daemon thread ++ ++import bottle ++import threading ++import socket ++import time as _time ++ ++class Server(bottle.WSGIRefServer): ++ def run(self, handler): # pragma: no cover ++ from wsgiref.simple_server import make_server, WSGIRequestHandler ++ if self.quiet: ++ base = self.options.get('handler_class', WSGIRequestHandler) ++ class QuietHandler(base): ++ def log_request(*args, **kw): pass ++ self.options['handler_class'] = QuietHandler ++ self.srv = make_server(self.host, self.port, handler, **self.options) ++ self.srv.serve_forever(poll_interval=0.1) ++ ++def start_bottle_server(app, port, **kwargs): ++ server_thread = ServerThread(app, port, kwargs) ++ server_thread.daemon = True ++ server_thread.start() ++ ++ ok = False ++ for i in range(10): ++ try: ++ conn = socket.create_connection(('127.0.0.1', port), 0.1) ++ except socket.error as e: ++ _time.sleep(0.1) ++ else: ++ conn.close() ++ ok = True ++ break ++ if not ok: ++ import warnings ++ warnings.warn('Server did not start after 1 second') ++ ++ return server_thread.server ++ ++class ServerThread(threading.Thread): ++ def __init__(self, app, port, server_kwargs): ++ threading.Thread.__init__(self) ++ self.app = app ++ self.port = port ++ self.server_kwargs = server_kwargs ++ self.server = Server(host='localhost', port=self.port, **self.server_kwargs) ++ ++ def run(self): ++ bottle.run(self.app, server=self.server, quiet=True) ++ ++def app_runner_setup(*specs): ++ '''Returns setup and teardown methods for running a list of WSGI ++ applications in a daemon thread. ++ ++ Each argument is an (app, port) pair. ++ ++ Return value is a (setup, teardown) function pair. ++ ++ The setup and teardown functions expect to be called with an argument ++ on which server state will be stored. ++ ++ Example usage with nose: ++ ++ >>> setup_module, teardown_module = \ ++ runwsgi.app_runner_setup((app_module.app, 8050)) ++ ''' ++ ++ def setup(self): ++ self.servers = [] ++ for spec in specs: ++ if len(spec) == 2: ++ app, port = spec ++ kwargs = {} ++ else: ++ app, port, kwargs = spec ++ self.servers.append(start_bottle_server(app, port, **kwargs)) ++ ++ def teardown(self): ++ for server in self.servers: ++ server.srv.shutdown() ++ ++ return [setup, teardown] +-- +1.7.1 + + +From 766e9a77708953c56b33d087561eddab853a08da Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 1 Mar 2013 14:06:42 -0500 +Subject: [PATCH 015/112] Test application to be on the server side + +Signed-off-by: Kamil Dudka +--- + tests/app.py | 7 +++++++ + 1 files changed, 7 insertions(+), 0 deletions(-) + create mode 100644 tests/app.py + +diff --git a/tests/app.py b/tests/app.py +new file mode 100644 +index 0000000..b173fd6 +--- /dev/null ++++ b/tests/app.py +@@ -0,0 +1,7 @@ ++import bottle ++ ++app = bottle.Bottle() ++ ++@app.route('/success') ++def ok(): ++ return 'success' +-- +1.7.1 + + +From a1449f4986686920904d7f781327e3ca520e4933 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 1 Mar 2013 14:07:04 -0500 +Subject: [PATCH 016/112] Use the test application in request test + +Signed-off-by: Kamil Dudka +--- + tests/request_test.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++-- + 1 files changed, 56 insertions(+), 3 deletions(-) + +diff --git a/tests/request_test.py b/tests/request_test.py +index 0761d6c..127fbeb 100644 +--- a/tests/request_test.py ++++ b/tests/request_test.py +@@ -2,8 +2,12 @@ + # -*- coding: iso-8859-1 -*- + # vi:ts=4:et + ++import os ++import sys ++import tempfile + import pycurl + import unittest ++import io + try: + from cStringIO import StringIO + except ImportError: +@@ -12,6 +16,11 @@ except ImportError: + except ImportError: + from io import StringIO + ++from . import app ++from . import runwsgi ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ + class RequestTest(unittest.TestCase): + def setUp(self): + self.curl = pycurl.Curl() +@@ -20,12 +29,56 @@ class RequestTest(unittest.TestCase): + self.curl.close() + + def test_perform_get(self): +- self.curl.setopt(pycurl.URL, 'http://localhost') ++ # This test performs a GET request without doing anything else. ++ # Unfortunately, the default curl behavior is to print response ++ # body to standard output, which spams test output. ++ # As a result this test is commented out. Uncomment for debugging. ++ # test_perform_get_with_default_write_function is the test ++ # which exercises default curl write handler. ++ return ++ ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.perform() + + def test_perform_get_with_write_function(self): +- self.curl.setopt(pycurl.URL, 'http://localhost') ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + sio = StringIO() + self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) + self.curl.perform() +- print(sio.getvalue()) ++ self.assertEqual('success', sio.getvalue()) ++ ++ def test_perform_get_with_default_write_function(self): ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') ++ #with tempfile.NamedTemporaryFile() as f: ++ with open('w', 'w+') as f: ++ # nose output capture plugin replaces sys.stdout with a StringIO ++ # instance. We want to redirect the underlying file descriptor ++ # anyway because underlying C code uses it. ++ # But keep track of whether we replace sys.stdout. ++ perform_dup = False ++ if hasattr(sys.stdout, 'fileno'): ++ try: ++ sys.stdout.fileno() ++ perform_dup = True ++ except io.UnsupportedOperation: ++ # stdout is a StringIO ++ pass ++ if perform_dup: ++ saved_stdout_fd = os.dup(sys.stdout.fileno()) ++ os.dup2(f.fileno(), sys.stdout.fileno()) ++ else: ++ saved_stdout = sys.stdout ++ sys.stdout = f ++ try: ++ self.curl.perform() ++ finally: ++ sys.stdout.flush() ++ if perform_dup: ++ os.fsync(sys.stdout.fileno()) ++ os.dup2(saved_stdout_fd, sys.stdout.fileno()) ++ os.close(saved_stdout_fd) ++ else: ++ sys.stdout = saved_stdout ++ f.seek(0) ++ body = f.read() ++ self.assertEqual('success', body) +-- +1.7.1 + + +From b332c2265bd3958a964bd2e69f9eeda96bafc814 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sat, 2 Mar 2013 02:23:27 -0500 +Subject: [PATCH 017/112] Put stringio import into util + +This way all tests can simply import stringio from util in one line. + +Signed-off-by: Kamil Dudka +--- + tests/util.py | 8 ++++++++ + 1 files changed, 8 insertions(+), 0 deletions(-) + +diff --git a/tests/util.py b/tests/util.py +index a1a9978..891da44 100644 +--- a/tests/util.py ++++ b/tests/util.py +@@ -4,6 +4,14 @@ + + import os, sys + ++try: ++ from cStringIO import StringIO ++except ImportError: ++ try: ++ from StringIO import StringIO ++ except ImportError: ++ from io import StringIO ++ + # + # prepare sys.path in case we are still in the build directory + # see also: distutils/command/build.py (build_platlib) +-- +1.7.1 + + +From c912395f00df7a91f3d9c0fcc16821c7cc25bf63 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sat, 2 Mar 2013 02:24:11 -0500 +Subject: [PATCH 018/112] Debug test, ported from the old debug test + +Signed-off-by: Kamil Dudka +--- + tests/debug_test.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 46 insertions(+), 0 deletions(-) + create mode 100644 tests/debug_test.py + +diff --git a/tests/debug_test.py b/tests/debug_test.py +new file mode 100644 +index 0000000..dd00719 +--- /dev/null ++++ b/tests/debug_test.py +@@ -0,0 +1,46 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++ ++from . import app ++from . import runwsgi ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++class DebugTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ self.debug_entries = [] ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def debug_function(self, t, b): ++ self.debug_entries.append((t, b)) ++ ++ def test_perform_get_with_debug_function(self): ++ self.curl.setopt(pycurl.VERBOSE, 1) ++ self.curl.setopt(pycurl.DEBUGFUNCTION, self.debug_function) ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') ++ self.curl.perform() ++ ++ # Some checks with no particular intent ++ self.check(0, 'About to connect') ++ self.check(0, 'Connected to localhost') ++ self.check(0, 'port 8380') ++ # request ++ self.check(2, 'GET /success HTTP/1.1') ++ # response ++ self.check(1, 'HTTP/1.0 200 OK') ++ self.check(1, 'Content-Length: 7') ++ # result ++ self.check(3, 'success') ++ ++ def check(self, wanted_t, wanted_b): ++ for t, b in self.debug_entries: ++ if t == wanted_t and wanted_b in b: ++ return ++ assert False, "%d: %s not found in debug entries" % (wanted_t, wanted_b) +-- +1.7.1 + + +From d3133de193791e731867c0ab02c49cd3f88422c0 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sat, 2 Mar 2013 02:43:57 -0500 +Subject: [PATCH 019/112] Write output to a stringio to avoid stdout spam + +Signed-off-by: Kamil Dudka +--- + tests/debug_test.py | 3 +++ + 1 files changed, 3 insertions(+), 0 deletions(-) + +diff --git a/tests/debug_test.py b/tests/debug_test.py +index dd00719..8005239 100644 +--- a/tests/debug_test.py ++++ b/tests/debug_test.py +@@ -7,6 +7,7 @@ import unittest + + from . import app + from . import runwsgi ++from . import util + + setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) + +@@ -25,6 +26,8 @@ class DebugTest(unittest.TestCase): + self.curl.setopt(pycurl.VERBOSE, 1) + self.curl.setopt(pycurl.DEBUGFUNCTION, self.debug_function) + self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) + self.curl.perform() + + # Some checks with no particular intent +-- +1.7.1 + + +From ff2df7b5ef851c29328dfb1ff501ff610690c63b Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sat, 2 Mar 2013 02:44:24 -0500 +Subject: [PATCH 020/112] Use stringio from util in request test + +Signed-off-by: Kamil Dudka +--- + tests/request_test.py | 10 ++-------- + 1 files changed, 2 insertions(+), 8 deletions(-) + +diff --git a/tests/request_test.py b/tests/request_test.py +index 127fbeb..afd8566 100644 +--- a/tests/request_test.py ++++ b/tests/request_test.py +@@ -8,16 +8,10 @@ import tempfile + import pycurl + import unittest + import io +-try: +- from cStringIO import StringIO +-except ImportError: +- try: +- from StringIO import StringIO +- except ImportError: +- from io import StringIO + + from . import app + from . import runwsgi ++from . import util + + setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) + +@@ -42,7 +36,7 @@ class RequestTest(unittest.TestCase): + + def test_perform_get_with_write_function(self): + self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') +- sio = StringIO() ++ sio = util.StringIO() + self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) + self.curl.perform() + self.assertEqual('success', sio.getvalue()) +-- +1.7.1 + + +From 8121df921a0615d1f5c6793ad86e17dc20e00902 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sat, 2 Mar 2013 04:06:00 -0500 +Subject: [PATCH 021/112] Add a test that writes response to a file + +Signed-off-by: Kamil Dudka +--- + tests/request_test.py | 9 +++++++++ + 1 files changed, 9 insertions(+), 0 deletions(-) + +diff --git a/tests/request_test.py b/tests/request_test.py +index afd8566..7386a51 100644 +--- a/tests/request_test.py ++++ b/tests/request_test.py +@@ -41,6 +41,15 @@ class RequestTest(unittest.TestCase): + self.curl.perform() + self.assertEqual('success', sio.getvalue()) + ++ def test_write_to_file(self): ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') ++ with tempfile.NamedTemporaryFile() as f: ++ self.curl.setopt(pycurl.WRITEFUNCTION, f.write) ++ self.curl.perform() ++ f.seek(0) ++ body = f.read() ++ self.assertEqual('success', body) ++ + def test_perform_get_with_default_write_function(self): + self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + #with tempfile.NamedTemporaryFile() as f: +-- +1.7.1 + + +From 5c59850b231cc0e9d38c7d56e67d55500e024577 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:14:26 -0500 +Subject: [PATCH 022/112] Account for the case of nose running no tests + +Signed-off-by: Kamil Dudka +--- + tests/runwsgi.py | 4 +++- + 1 files changed, 3 insertions(+), 1 deletions(-) + +diff --git a/tests/runwsgi.py b/tests/runwsgi.py +index d658614..7b39358 100644 +--- a/tests/runwsgi.py ++++ b/tests/runwsgi.py +@@ -77,6 +77,8 @@ def app_runner_setup(*specs): + + def teardown(self): + for server in self.servers: +- server.srv.shutdown() ++ # if no tests from module were run, there is no server to shut down ++ if hasattr(server, 'srv'): ++ server.srv.shutdown() + + return [setup, teardown] +-- +1.7.1 + + +From 48ac39062e8cd19ee4ddf590278aa6e35477a8d3 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:14:41 -0500 +Subject: [PATCH 023/112] Split request test + +Signed-off-by: Kamil Dudka +--- + tests/default_write_function_test.py | 70 +++++++++++++++++++++++++++ + tests/request_test.py | 87 ---------------------------------- + tests/write_to_file_test.py | 29 +++++++++++ + tests/write_to_stringio_test.py | 30 ++++++++++++ + 4 files changed, 129 insertions(+), 87 deletions(-) + create mode 100644 tests/default_write_function_test.py + delete mode 100644 tests/request_test.py + create mode 100644 tests/write_to_file_test.py + create mode 100644 tests/write_to_stringio_test.py + +diff --git a/tests/default_write_function_test.py b/tests/default_write_function_test.py +new file mode 100644 +index 0000000..2efc579 +--- /dev/null ++++ b/tests/default_write_function_test.py +@@ -0,0 +1,70 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import unittest ++import pycurl ++import sys ++import tempfile ++import io ++import os ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++class RequestTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_perform_get(self): ++ # This test performs a GET request without doing anything else. ++ # Unfortunately, the default curl behavior is to print response ++ # body to standard output, which spams test output. ++ # As a result this test is commented out. Uncomment for debugging. ++ # test_perform_get_with_default_write_function is the test ++ # which exercises default curl write handler. ++ ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') ++ self.curl.perform() ++ ++ def test_perform_get_with_default_write_function(self): ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') ++ #with tempfile.NamedTemporaryFile() as f: ++ with open('w', 'w+') as f: ++ # nose output capture plugin replaces sys.stdout with a StringIO ++ # instance. We want to redirect the underlying file descriptor ++ # anyway because underlying C code uses it. ++ # But keep track of whether we replace sys.stdout. ++ perform_dup = False ++ if hasattr(sys.stdout, 'fileno'): ++ try: ++ sys.stdout.fileno() ++ perform_dup = True ++ except io.UnsupportedOperation: ++ # stdout is a StringIO ++ pass ++ if perform_dup: ++ saved_stdout_fd = os.dup(sys.stdout.fileno()) ++ os.dup2(f.fileno(), sys.stdout.fileno()) ++ else: ++ saved_stdout = sys.stdout ++ sys.stdout = f ++ try: ++ self.curl.perform() ++ finally: ++ sys.stdout.flush() ++ if perform_dup: ++ os.fsync(sys.stdout.fileno()) ++ os.dup2(saved_stdout_fd, sys.stdout.fileno()) ++ os.close(saved_stdout_fd) ++ else: ++ sys.stdout = saved_stdout ++ f.seek(0) ++ body = f.read() ++ self.assertEqual('success', body) +diff --git a/tests/request_test.py b/tests/request_test.py +deleted file mode 100644 +index 7386a51..0000000 +--- a/tests/request_test.py ++++ /dev/null +@@ -1,87 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +- +-import os +-import sys +-import tempfile +-import pycurl +-import unittest +-import io +- +-from . import app +-from . import runwsgi +-from . import util +- +-setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) +- +-class RequestTest(unittest.TestCase): +- def setUp(self): +- self.curl = pycurl.Curl() +- +- def tearDown(self): +- self.curl.close() +- +- def test_perform_get(self): +- # This test performs a GET request without doing anything else. +- # Unfortunately, the default curl behavior is to print response +- # body to standard output, which spams test output. +- # As a result this test is commented out. Uncomment for debugging. +- # test_perform_get_with_default_write_function is the test +- # which exercises default curl write handler. +- return +- +- self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') +- self.curl.perform() +- +- def test_perform_get_with_write_function(self): +- self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') +- sio = util.StringIO() +- self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) +- self.curl.perform() +- self.assertEqual('success', sio.getvalue()) +- +- def test_write_to_file(self): +- self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') +- with tempfile.NamedTemporaryFile() as f: +- self.curl.setopt(pycurl.WRITEFUNCTION, f.write) +- self.curl.perform() +- f.seek(0) +- body = f.read() +- self.assertEqual('success', body) +- +- def test_perform_get_with_default_write_function(self): +- self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') +- #with tempfile.NamedTemporaryFile() as f: +- with open('w', 'w+') as f: +- # nose output capture plugin replaces sys.stdout with a StringIO +- # instance. We want to redirect the underlying file descriptor +- # anyway because underlying C code uses it. +- # But keep track of whether we replace sys.stdout. +- perform_dup = False +- if hasattr(sys.stdout, 'fileno'): +- try: +- sys.stdout.fileno() +- perform_dup = True +- except io.UnsupportedOperation: +- # stdout is a StringIO +- pass +- if perform_dup: +- saved_stdout_fd = os.dup(sys.stdout.fileno()) +- os.dup2(f.fileno(), sys.stdout.fileno()) +- else: +- saved_stdout = sys.stdout +- sys.stdout = f +- try: +- self.curl.perform() +- finally: +- sys.stdout.flush() +- if perform_dup: +- os.fsync(sys.stdout.fileno()) +- os.dup2(saved_stdout_fd, sys.stdout.fileno()) +- os.close(saved_stdout_fd) +- else: +- sys.stdout = saved_stdout +- f.seek(0) +- body = f.read() +- self.assertEqual('success', body) +diff --git a/tests/write_to_file_test.py b/tests/write_to_file_test.py +new file mode 100644 +index 0000000..6528f81 +--- /dev/null ++++ b/tests/write_to_file_test.py +@@ -0,0 +1,29 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import unittest ++import pycurl ++import tempfile ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++class RequestTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_get_to_file(self): ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') ++ with tempfile.NamedTemporaryFile() as f: ++ self.curl.setopt(pycurl.WRITEFUNCTION, f.write) ++ self.curl.perform() ++ f.seek(0) ++ body = f.read() ++ self.assertEqual('success', body) +diff --git a/tests/write_to_stringio_test.py b/tests/write_to_stringio_test.py +new file mode 100644 +index 0000000..fd1c28d +--- /dev/null ++++ b/tests/write_to_stringio_test.py +@@ -0,0 +1,30 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import os ++import sys ++import tempfile ++import pycurl ++import unittest ++import io ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++class WriteToStringioTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_get(self): ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.perform() ++ self.assertEqual('success', sio.getvalue()) +-- +1.7.1 + + +From 3f765182563497ed5dc0348474e8e9f30618576e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:22:05 -0500 +Subject: [PATCH 024/112] Need to flush when using default write function + +Signed-off-by: Kamil Dudka +--- + tests/default_write_function_test.py | 5 ++++- + 1 files changed, 4 insertions(+), 1 deletions(-) + +diff --git a/tests/default_write_function_test.py b/tests/default_write_function_test.py +index 2efc579..afbba33 100644 +--- a/tests/default_write_function_test.py ++++ b/tests/default_write_function_test.py +@@ -32,6 +32,9 @@ class RequestTest(unittest.TestCase): + + self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.perform() ++ # If this flush is not done, stdout output bleeds into the next test ++ # that is executed ++ sys.stdout.flush() + + def test_perform_get_with_default_write_function(self): + self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') +@@ -57,8 +60,8 @@ class RequestTest(unittest.TestCase): + sys.stdout = f + try: + self.curl.perform() +- finally: + sys.stdout.flush() ++ finally: + if perform_dup: + os.fsync(sys.stdout.fileno()) + os.dup2(saved_stdout_fd, sys.stdout.fileno()) +-- +1.7.1 + + +From 8515cd10d9ead70ce306fcf2de96b0d34a402d88 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:34:39 -0500 +Subject: [PATCH 025/112] Trying to get the default write function test to work with nose output capture, but no luck so far + +Signed-off-by: Kamil Dudka +--- + tests/default_write_function_test.py | 52 +++++++++++++++++---------------- + 1 files changed, 27 insertions(+), 25 deletions(-) + +diff --git a/tests/default_write_function_test.py b/tests/default_write_function_test.py +index afbba33..853992b 100644 +--- a/tests/default_write_function_test.py ++++ b/tests/default_write_function_test.py +@@ -15,6 +15,8 @@ from . import util + + setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) + ++STDOUT_FD_NUM = 1 ++ + class RequestTest(unittest.TestCase): + def setUp(self): + self.curl = pycurl.Curl() +@@ -33,41 +35,41 @@ class RequestTest(unittest.TestCase): + self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + self.curl.perform() + # If this flush is not done, stdout output bleeds into the next test +- # that is executed ++ # that is executed (without nose output capture) + sys.stdout.flush() ++ os.fsync(STDOUT_FD_NUM) + +- def test_perform_get_with_default_write_function(self): ++ # I have a really hard time getting this to work with nose output capture ++ def skip_test_perform_get_with_default_write_function(self): + self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') +- #with tempfile.NamedTemporaryFile() as f: +- with open('w', 'w+') as f: ++ with tempfile.NamedTemporaryFile() as f: ++ #with open('w', 'w+') as f: + # nose output capture plugin replaces sys.stdout with a StringIO + # instance. We want to redirect the underlying file descriptor + # anyway because underlying C code uses it. +- # But keep track of whether we replace sys.stdout. +- perform_dup = False +- if hasattr(sys.stdout, 'fileno'): +- try: +- sys.stdout.fileno() +- perform_dup = True +- except io.UnsupportedOperation: +- # stdout is a StringIO +- pass +- if perform_dup: +- saved_stdout_fd = os.dup(sys.stdout.fileno()) +- os.dup2(f.fileno(), sys.stdout.fileno()) +- else: +- saved_stdout = sys.stdout +- sys.stdout = f ++ # Therefore: ++ # 1. Use file descriptor 1 rather than sys.stdout.fileno() to ++ # reference the standard output file descriptor. ++ # 2. We do not touch sys.stdout. This means anything written to ++ # sys.stdout will be captured by nose, and not make it to our code. ++ # But the output we care about happens at libcurl level, below ++ # nose, therefore this is fine. ++ saved_stdout_fd = os.dup(STDOUT_FD_NUM) ++ os.dup2(f.fileno(), STDOUT_FD_NUM) ++ #os.dup2(1, 100) ++ #os.dup2(2, 1) ++ # We also need to flush the output that libcurl wrote to stdout. ++ # Since sys.stdout might be nose's StringIO instance, open the ++ # stdout file descriptor manually. ++ + try: + self.curl.perform() + sys.stdout.flush() + finally: +- if perform_dup: +- os.fsync(sys.stdout.fileno()) +- os.dup2(saved_stdout_fd, sys.stdout.fileno()) +- os.close(saved_stdout_fd) +- else: +- sys.stdout = saved_stdout ++ os.fsync(STDOUT_FD_NUM) ++ os.dup2(saved_stdout_fd, STDOUT_FD_NUM) ++ os.close(saved_stdout_fd) ++ #os.dup2(100, 1) + f.seek(0) + body = f.read() + self.assertEqual('success', body) +-- +1.7.1 + + +From a43c72743b7a191622da9b1a933600c92b892529 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:35:25 -0500 +Subject: [PATCH 026/112] Fix test class names + +Signed-off-by: Kamil Dudka +--- + tests/default_write_function_test.py | 2 +- + tests/write_to_file_test.py | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/tests/default_write_function_test.py b/tests/default_write_function_test.py +index 853992b..a0de66e 100644 +--- a/tests/default_write_function_test.py ++++ b/tests/default_write_function_test.py +@@ -17,7 +17,7 @@ setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) + + STDOUT_FD_NUM = 1 + +-class RequestTest(unittest.TestCase): ++class DefaultWriteFunctionTest(unittest.TestCase): + def setUp(self): + self.curl = pycurl.Curl() + +diff --git a/tests/write_to_file_test.py b/tests/write_to_file_test.py +index 6528f81..67c9c63 100644 +--- a/tests/write_to_file_test.py ++++ b/tests/write_to_file_test.py +@@ -12,7 +12,7 @@ from . import util + + setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) + +-class RequestTest(unittest.TestCase): ++class WriteToFileTest(unittest.TestCase): + def setUp(self): + self.curl = pycurl.Curl() + +-- +1.7.1 + + +From 748ffdb26baef8fa43e8da7e086325e5a77dafee Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:36:34 -0500 +Subject: [PATCH 027/112] Delete unused imports + +Signed-off-by: Kamil Dudka +--- + tests/write_to_stringio_test.py | 3 --- + 1 files changed, 0 insertions(+), 3 deletions(-) + +diff --git a/tests/write_to_stringio_test.py b/tests/write_to_stringio_test.py +index fd1c28d..018800d 100644 +--- a/tests/write_to_stringio_test.py ++++ b/tests/write_to_stringio_test.py +@@ -2,9 +2,6 @@ + # -*- coding: iso-8859-1 -*- + # vi:ts=4:et + +-import os +-import sys +-import tempfile + import pycurl + import unittest + import io +-- +1.7.1 + + +From da193274e430029a18b1f9b28729d9e165196f29 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:41:35 -0500 +Subject: [PATCH 028/112] Test for header function + +Signed-off-by: Kamil Dudka +--- + tests/header_function_test.py | 50 +++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 50 insertions(+), 0 deletions(-) + create mode 100644 tests/header_function_test.py + +diff --git a/tests/header_function_test.py b/tests/header_function_test.py +new file mode 100644 +index 0000000..bfe7173 +--- /dev/null ++++ b/tests/header_function_test.py +@@ -0,0 +1,50 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++import io ++import time as _time ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++class HeaderFunctionTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ self.header_lines = [] ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def header_function(self, line): ++ self.header_lines.append(line) ++ ++ def test_get(self): ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.setopt(pycurl.HEADERFUNCTION, self.header_function) ++ self.curl.perform() ++ self.assertEqual('success', sio.getvalue()) ++ ++ assert len(self.header_lines) > 0 ++ self.assertEqual("HTTP/1.0 200 OK\r\n", self.header_lines[0]) ++ # day of week ++ todays_day = _time.strftime('%a') ++ # Date: Sun, 03 Mar 2013 05:38:12 GMT\r\n ++ self.check('Date: %s' % todays_day) ++ # Server: WSGIServer/0.1 Python/2.7.3\r\n ++ self.check('Server: WSGIServer') ++ self.check('Content-Length: 7') ++ self.check('Content-Type: text/html') ++ ++ def check(self, wanted_text): ++ for line in self.header_lines: ++ if wanted_text in line: ++ return ++ assert False, "%s not found in header lines" % wanted_text +-- +1.7.1 + + +From 040b13d56a0eb2a9c8e5ee7e35142c4e240d48db Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 01:35:21 -0500 +Subject: [PATCH 029/112] FTP test + +Signed-off-by: Kamil Dudka +--- + tests/ftp_test.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 48 insertions(+), 0 deletions(-) + create mode 100644 tests/ftp_test.py + +diff --git a/tests/ftp_test.py b/tests/ftp_test.py +new file mode 100644 +index 0000000..d215b6e +--- /dev/null ++++ b/tests/ftp_test.py +@@ -0,0 +1,48 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++ ++from . import util ++ ++class FtpTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_get_ftp(self): ++ self.curl.setopt(pycurl.URL, 'ftp://localhost:8921') ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.perform() ++ ++ result = sio.getvalue() ++ assert 'README' in result ++ assert 'bin -> usr/bin' in result ++ ++ # XXX this test needs to be fixed ++ def test_quote(self): ++ self.curl.setopt(pycurl.URL, 'ftp://localhost:8921') ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.setopt(pycurl.QUOTE, ['CWD pub']) ++ self.curl.perform() ++ ++ result = sio.getvalue() ++ assert 'README' in result ++ assert 'bin -> usr/bin' in result ++ ++ def test_epsv(self): ++ self.curl.setopt(pycurl.URL, 'ftp://localhost:8921') ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.setopt(pycurl.FTP_USE_EPSV, 1) ++ self.curl.perform() ++ ++ result = sio.getvalue() ++ assert 'README' in result ++ assert 'bin -> usr/bin' in result +-- +1.7.1 + + +From eb36d56c850ed14a52d0a1f3afb62165444f9b84 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 01:44:22 -0500 +Subject: [PATCH 030/112] Getinfo test + +Signed-off-by: Kamil Dudka +--- + tests/getinfo_test.py | 43 +++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 43 insertions(+), 0 deletions(-) + create mode 100644 tests/getinfo_test.py + +diff --git a/tests/getinfo_test.py b/tests/getinfo_test.py +new file mode 100644 +index 0000000..39dff2f +--- /dev/null ++++ b/tests/getinfo_test.py +@@ -0,0 +1,43 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++class GetinfoTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_getinfo(self): ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.perform() ++ self.assertEqual('success', sio.getvalue()) ++ ++ self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE)) ++ assert type(self.curl.getinfo(pycurl.TOTAL_TIME)) is float ++ assert self.curl.getinfo(pycurl.TOTAL_TIME) > 0 ++ assert self.curl.getinfo(pycurl.TOTAL_TIME) < 1 ++ assert type(self.curl.getinfo(pycurl.SPEED_DOWNLOAD)) is float ++ assert self.curl.getinfo(pycurl.SPEED_DOWNLOAD) > 0 ++ self.assertEqual(7, self.curl.getinfo(pycurl.SIZE_DOWNLOAD)) ++ self.assertEqual('http://localhost:8380/success', self.curl.getinfo(pycurl.EFFECTIVE_URL)) ++ self.assertEqual('text/html; charset=utf-8', self.curl.getinfo(pycurl.CONTENT_TYPE).lower()) ++ assert type(self.curl.getinfo(pycurl.NAMELOOKUP_TIME)) is float ++ assert self.curl.getinfo(pycurl.NAMELOOKUP_TIME) > 0 ++ assert self.curl.getinfo(pycurl.NAMELOOKUP_TIME) < 1 ++ self.assertEqual(0, self.curl.getinfo(pycurl.REDIRECT_TIME)) ++ self.assertEqual(0, self.curl.getinfo(pycurl.REDIRECT_COUNT)) ++ # time not requested ++ self.assertEqual(-1, self.curl.getinfo(pycurl.INFO_FILETIME)) +-- +1.7.1 + + +From ca2118da041f15f5bbb2bf6a0e12b7bd57d16332 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 02:05:30 -0500 +Subject: [PATCH 031/112] Ported internals test + +Signed-off-by: Kamil Dudka +--- + tests/internals_test.py | 224 +++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 224 insertions(+), 0 deletions(-) + create mode 100644 tests/internals_test.py + +diff --git a/tests/internals_test.py b/tests/internals_test.py +new file mode 100644 +index 0000000..34d4eb8 +--- /dev/null ++++ b/tests/internals_test.py +@@ -0,0 +1,224 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++from .util import StringIO ++try: ++ import cPickle ++except ImportError: ++ cPickle = None ++import pickle ++import gc ++import copy ++ ++class InternalsTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ del self.curl ++ ++ # /*********************************************************************** ++ # // test misc ++ # ************************************************************************/ ++ ++ def test_constant_aliasing(self): ++ assert self.curl.URL is pycurl.URL ++ ++ # /*********************************************************************** ++ # // test handles ++ # ************************************************************************/ ++ ++ def test_remove_invalid_handle(self): ++ m = pycurl.CurlMulti() ++ try: ++ m.remove_handle(self.curl) ++ except pycurl.error: ++ pass ++ else: ++ assert False, "No exception when trying to remove a handle that is not in CurlMulti" ++ del m ++ ++ def test_remove_invalid_closed_handle(self): ++ m = pycurl.CurlMulti() ++ c = pycurl.Curl() ++ c.close() ++ m.remove_handle(c) ++ del m, c ++ ++ def test_add_closed_handle(self): ++ m = pycurl.CurlMulti() ++ c = pycurl.Curl() ++ c.close() ++ try: ++ m.add_handle(c) ++ except pycurl.error: ++ pass ++ else: ++ assert 0, "No exception when trying to add a close handle to CurlMulti" ++ m.close() ++ del m, c ++ ++ def test_add_handle_twice(self): ++ m = pycurl.CurlMulti() ++ m.add_handle(self.curl) ++ try: ++ m.add_handle(self.curl) ++ except pycurl.error: ++ pass ++ else: ++ assert 0, "No exception when trying to add the same handle twice" ++ del m ++ ++ def test_add_handle_on_multiple_stacks(self): ++ m1 = pycurl.CurlMulti() ++ m2 = pycurl.CurlMulti() ++ m1.add_handle(self.curl) ++ try: ++ m2.add_handle(self.curl) ++ except pycurl.error: ++ pass ++ else: ++ assert 0, "No exception when trying to add the same handle on multiple stacks" ++ del m1, m2 ++ ++ def test_move_handle(self): ++ m1 = pycurl.CurlMulti() ++ m2 = pycurl.CurlMulti() ++ m1.add_handle(self.curl) ++ m1.remove_handle(self.curl) ++ m2.add_handle(self.curl) ++ del m1, m2 ++ ++ # /*********************************************************************** ++ # // test copying and pickling - copying and pickling of ++ # // instances of Curl and CurlMulti is not allowed ++ # ************************************************************************/ ++ ++ def test_copy_curl(self): ++ try: ++ copy.copy(self.curl) ++ # python 2 raises copy.Error, python 3 raises TypeError ++ except (copy.Error, TypeError): ++ pass ++ else: ++ assert False, "No exception when trying to copy a Curl handle" ++ ++ def test_copy_multi(self): ++ m = pycurl.CurlMulti() ++ try: ++ copy.copy(m) ++ except (copy.Error, TypeError): ++ pass ++ else: ++ assert False, "No exception when trying to copy a CurlMulti handle" ++ ++ def test_pickle_curl(self): ++ fp = StringIO() ++ p = pickle.Pickler(fp, 1) ++ try: ++ p.dump(self.curl) ++ # python 2 raises pickle.PicklingError, python 3 raises TypeError ++ except (pickle.PicklingError, TypeError): ++ pass ++ else: ++ assert 0, "No exception when trying to pickle a Curl handle" ++ del fp, p ++ ++ def test_pickle_multi(self): ++ m = pycurl.CurlMulti() ++ fp = StringIO() ++ p = pickle.Pickler(fp, 1) ++ try: ++ p.dump(m) ++ except (pickle.PicklingError, TypeError): ++ pass ++ else: ++ assert 0, "No exception when trying to pickle a CurlMulti handle" ++ del m, fp, p ++ ++ if cPickle is not None: ++ def test_cpickle_curl(self): ++ fp = StringIO() ++ p = cPickle.Pickler(fp, 1) ++ try: ++ p.dump(self.curl) ++ except cPickle.PicklingError: ++ pass ++ else: ++ assert 0, "No exception when trying to pickle a Curl handle via cPickle" ++ del fp, p ++ ++ def test_cpickle_multi(self): ++ m = pycurl.CurlMulti() ++ fp = StringIO() ++ p = cPickle.Pickler(fp, 1) ++ try: ++ p.dump(m) ++ except cPickle.PicklingError: ++ pass ++ else: ++ assert 0, "No exception when trying to pickle a CurlMulti handle via cPickle" ++ del m, fp, p ++ ++ # /*********************************************************************** ++ # // test refcounts ++ # ************************************************************************/ ++ ++ # basic check of reference counting (use a memory checker like valgrind) ++ def test_reference_counting(self): ++ c = pycurl.Curl() ++ m = pycurl.CurlMulti() ++ m.add_handle(c) ++ del m ++ m = pycurl.CurlMulti() ++ c.close() ++ del m, c ++ ++ def test_cyclic_gc(self): ++ gc.collect() ++ c = pycurl.Curl() ++ c.m = pycurl.CurlMulti() ++ c.m.add_handle(c) ++ # create some nasty cyclic references ++ c.c = c ++ c.c.c1 = c ++ c.c.c2 = c ++ c.c.c3 = c.c ++ c.c.c4 = c.m ++ c.m.c = c ++ c.m.m = c.m ++ c.m.c = c ++ # delete ++ gc.collect() ++ flags = gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE ++ # python 3 has no DEBUG_OBJECTS ++ #if hasattr(gc, 'DEBUG_OBJECTS'): ++ #flags |= gc.DEBUG_OBJECTS ++ #if opts.verbose >= 1: ++ #flags = flags | gc.DEBUG_STATS ++ gc.set_debug(flags) ++ gc.collect() ++ ##print gc.get_referrers(c) ++ ##print gc.get_objects() ++ #if opts.verbose >= 1: ++ #print("Tracked objects:", len(gc.get_objects())) ++ # The `del' below should delete these 4 objects: ++ # Curl + internal dict, CurlMulti + internal dict ++ del c ++ gc.collect() ++ #if opts.verbose >= 1: ++ #print("Tracked objects:", len(gc.get_objects())) ++ ++ def test_refcounting_bug_in_reset(self): ++ try: ++ range_generator = xrange ++ except NameError: ++ range_generator = range ++ # Ensure that the refcounting error in "reset" is fixed: ++ for i in range_generator(100000): ++ c = pycurl.Curl() ++ c.reset() +-- +1.7.1 + + +From 5400bd486457b34d8c49ca3ad1f74f03143172bd Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 02:16:10 -0500 +Subject: [PATCH 032/112] Make cyclic gc test actually check that cycles are collected + +Signed-off-by: Kamil Dudka +--- + tests/internals_test.py | 9 +++++++++ + 1 files changed, 9 insertions(+), 0 deletions(-) + +diff --git a/tests/internals_test.py b/tests/internals_test.py +index 34d4eb8..fb451df 100644 +--- a/tests/internals_test.py ++++ b/tests/internals_test.py +@@ -12,6 +12,7 @@ except ImportError: + import pickle + import gc + import copy ++import re + + class InternalsTest(unittest.TestCase): + def setUp(self): +@@ -179,6 +180,7 @@ class InternalsTest(unittest.TestCase): + del m, c + + def test_cyclic_gc(self): ++ regexp = re.compile(r'at (0x\d+)') + gc.collect() + c = pycurl.Curl() + c.m = pycurl.CurlMulti() +@@ -206,10 +208,17 @@ class InternalsTest(unittest.TestCase): + ##print gc.get_objects() + #if opts.verbose >= 1: + #print("Tracked objects:", len(gc.get_objects())) ++ match = regexp.search(repr(c)) ++ assert match is not None ++ address = match.group(1) + # The `del' below should delete these 4 objects: + # Curl + internal dict, CurlMulti + internal dict + del c + gc.collect() ++ objects = gc.get_objects() ++ search = 'at %s' % address ++ for object in objects: ++ assert search not in repr(object) + #if opts.verbose >= 1: + #print("Tracked objects:", len(gc.get_objects())) + +-- +1.7.1 + + +From b0c490027c63d5f8eb5dfc81c646e5edfb79635c Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 03:00:20 -0500 +Subject: [PATCH 033/112] Ported memleak test + +Signed-off-by: Kamil Dudka +--- + tests/memleak_test.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 59 insertions(+), 0 deletions(-) + create mode 100644 tests/memleak_test.py + +diff --git a/tests/memleak_test.py b/tests/memleak_test.py +new file mode 100644 +index 0000000..6e9f76c +--- /dev/null ++++ b/tests/memleak_test.py +@@ -0,0 +1,59 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++import gc ++import re ++ ++class MemleakTest(unittest.TestCase): ++ def test_collection(self): ++ regexp = re.compile(r'at (0x\d+)') ++ ++ gc.collect() ++ flags = gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE ++ # python 3 has no DEBUG_OBJECTS ++ #if hasattr(gc, 'DEBUG_OBJECTS'): ++ #flags |= gc.DEBUG_OBJECTS ++ #if 1: ++ #flags = flags | gc.DEBUG_STATS ++ #gc.set_debug(flags) ++ gc.collect() ++ ++ #print("Tracked objects:", len(gc.get_objects())) ++ ++ multi = pycurl.CurlMulti() ++ t = [] ++ searches = [] ++ for a in range(100): ++ curl = pycurl.Curl() ++ multi.add_handle(curl) ++ t.append(curl) ++ ++ match = regexp.search(repr(curl)) ++ assert match is not None ++ searches.append(match.group(1)) ++ match = regexp.search(repr(multi)) ++ assert match ++ searches.append(match.group(1)) ++ ++ #print("Tracked objects:", len(gc.get_objects())) ++ ++ for curl in t: ++ curl.close() ++ multi.remove_handle(curl) ++ ++ #print("Tracked objects:", len(gc.get_objects())) ++ ++ del curl ++ del t ++ del multi ++ ++ #print("Tracked objects:", len(gc.get_objects())) ++ gc.collect() ++ #print("Tracked objects:", len(gc.get_objects())) ++ ++ objects = gc.get_objects() ++ for search in searches: ++ assert 'at %s' % search not in repr(object) +-- +1.7.1 + + +From dd5650bb58e60c27fa32c27b5d39dbf5ad13c132 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 03:06:26 -0500 +Subject: [PATCH 034/112] Ported the first multi test + +Signed-off-by: Kamil Dudka +--- + tests/multi_test.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 56 insertions(+), 0 deletions(-) + create mode 100644 tests/multi_test.py + +diff --git a/tests/multi_test.py b/tests/multi_test.py +new file mode 100644 +index 0000000..4c0bdaf +--- /dev/null ++++ b/tests/multi_test.py +@@ -0,0 +1,56 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module_1, teardown_module_1 = runwsgi.app_runner_setup((app.app, 8380)) ++setup_module_2, teardown_module_2 = runwsgi.app_runner_setup((app.app, 8381)) ++ ++def setup_module(mod): ++ setup_module_1(mod) ++ setup_module_2(mod) ++ ++def teardown_module(mod): ++ teardown_module_2(mod) ++ teardown_module_1(mod) ++ ++class MultiTest(unittest.TestCase): ++ def test_multi(self): ++ io1 = util.StringIO() ++ io2 = util.StringIO() ++ m = pycurl.CurlMulti() ++ m.handles = [] ++ c1 = pycurl.Curl() ++ c2 = pycurl.Curl() ++ c1.setopt(c1.URL, 'http://localhost:8380/success') ++ c1.setopt(c1.WRITEFUNCTION, io1.write) ++ c2.setopt(c2.URL, 'http://localhost:8381/success') ++ c2.setopt(c1.WRITEFUNCTION, io2.write) ++ m.add_handle(c1) ++ m.add_handle(c2) ++ m.handles.append(c1) ++ m.handles.append(c2) ++ ++ num_handles = len(m.handles) ++ while num_handles: ++ while 1: ++ ret, num_handles = m.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ m.select(1.0) ++ ++ m.remove_handle(c2) ++ m.remove_handle(c1) ++ del m.handles ++ m.close() ++ c1.close() ++ c2.close() ++ ++ self.assertEqual('success', io1.getvalue()) ++ self.assertEqual('success', io2.getvalue()) +-- +1.7.1 + + +From c651e8ab37cc21ab790b293ad00ad43a8a5e608f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 12:55:42 -0500 +Subject: [PATCH 035/112] Port multi2 test + +Signed-off-by: Kamil Dudka +--- + tests/app.py | 8 +++++++ + tests/multi_test.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 62 insertions(+), 0 deletions(-) + +diff --git a/tests/app.py b/tests/app.py +index b173fd6..3b9303b 100644 +--- a/tests/app.py ++++ b/tests/app.py +@@ -5,3 +5,11 @@ app = bottle.Bottle() + @app.route('/success') + def ok(): + return 'success' ++ ++@app.route('/status/403') ++def forbidden(): ++ bottle.abort(403, 'forbidden') ++ ++@app.route('/status/404') ++def not_found(): ++ bottle.abort(404, 'not found') +diff --git a/tests/multi_test.py b/tests/multi_test.py +index 4c0bdaf..fd96e51 100644 +--- a/tests/multi_test.py ++++ b/tests/multi_test.py +@@ -11,12 +11,15 @@ from . import util + + setup_module_1, teardown_module_1 = runwsgi.app_runner_setup((app.app, 8380)) + setup_module_2, teardown_module_2 = runwsgi.app_runner_setup((app.app, 8381)) ++setup_module_3, teardown_module_3 = runwsgi.app_runner_setup((app.app, 8382)) + + def setup_module(mod): + setup_module_1(mod) + setup_module_2(mod) ++ setup_module_3(mod) + + def teardown_module(mod): ++ teardown_module_3(mod) + teardown_module_2(mod) + teardown_module_1(mod) + +@@ -54,3 +57,54 @@ class MultiTest(unittest.TestCase): + + self.assertEqual('success', io1.getvalue()) + self.assertEqual('success', io2.getvalue()) ++ ++ def test_multi_status_codes(self): ++ # init ++ m = pycurl.CurlMulti() ++ m.handles = [] ++ urls = [ ++ 'http://localhost:8380/success', ++ 'http://localhost:8381/status/403', ++ 'http://localhost:8382/status/404', ++ ] ++ for url in urls: ++ c = pycurl.Curl() ++ # save info in standard Python attributes ++ c.url = url.rstrip() ++ c.body = util.StringIO() ++ c.http_code = -1 ++ m.handles.append(c) ++ # pycurl API calls ++ c.setopt(c.URL, c.url) ++ c.setopt(c.WRITEFUNCTION, c.body.write) ++ m.add_handle(c) ++ ++ # get data ++ num_handles = len(m.handles) ++ while num_handles: ++ while 1: ++ ret, num_handles = m.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ # currently no more I/O is pending, could do something in the meantime ++ # (display a progress bar, etc.) ++ m.select(0.1) ++ ++ # close handles ++ for c in m.handles: ++ # save info in standard Python attributes ++ c.http_code = c.getinfo(c.HTTP_CODE) ++ # pycurl API calls ++ m.remove_handle(c) ++ c.close() ++ m.close() ++ ++ # check result ++ self.assertEqual('success', m.handles[0].body.getvalue()) ++ self.assertEqual(200, m.handles[0].http_code) ++ # bottle generated response body ++ assert 'Error 403: Forbidden' in m.handles[1].body.getvalue() ++ self.assertEqual(403, m.handles[1].http_code) ++ # bottle generated response body ++ assert 'Error 404: Not Found' in m.handles[2].body.getvalue() ++ self.assertEqual(404, m.handles[2].http_code) +-- +1.7.1 + + +From 18212fb2181a57e67988e7a336ac4b9c90e17d1a Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 14:05:49 -0500 +Subject: [PATCH 036/112] Porting multi3 test + +Signed-off-by: Kamil Dudka +--- + tests/multi_test.py | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 71 insertions(+), 0 deletions(-) + +diff --git a/tests/multi_test.py b/tests/multi_test.py +index fd96e51..1c13f70 100644 +--- a/tests/multi_test.py ++++ b/tests/multi_test.py +@@ -108,3 +108,74 @@ class MultiTest(unittest.TestCase): + # bottle generated response body + assert 'Error 404: Not Found' in m.handles[2].body.getvalue() + self.assertEqual(404, m.handles[2].http_code) ++ ++ def test_adding_closed_handle(self): ++ # init ++ m = pycurl.CurlMulti() ++ m.handles = [] ++ urls = [ ++ 'http://localhost:8380/success', ++ 'http://localhost:8381/status/403', ++ 'http://localhost:8382/status/404', ++ ] ++ for url in urls: ++ c = pycurl.Curl() ++ # save info in standard Python attributes ++ c.url = url ++ c.body = util.StringIO() ++ c.http_code = -1 ++ c.debug = 0 ++ m.handles.append(c) ++ # pycurl API calls ++ c.setopt(c.URL, c.url) ++ c.setopt(c.WRITEFUNCTION, c.body.write) ++ m.add_handle(c) ++ ++ # debug - close a handle ++ c = m.handles[2] ++ c.debug = 1 ++ c.close() ++ ++ # get data ++ num_handles = len(m.handles) ++ while num_handles: ++ while 1: ++ ret, num_handles = m.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ # currently no more I/O is pending, could do something in the meantime ++ # (display a progress bar, etc.) ++ m.select(0.1) ++ ++ # close handles ++ for c in m.handles: ++ # save info in standard Python attributes ++ try: ++ c.http_code = c.getinfo(c.HTTP_CODE) ++ except pycurl.error: ++ # handle already closed - see debug above ++ assert c.debug ++ c.http_code = -1 ++ # pycurl API calls ++ if 0: ++ m.remove_handle(c) ++ c.close() ++ elif 0: ++ # in the C API this is the wrong calling order, but pycurl ++ # handles this automatically ++ c.close() ++ m.remove_handle(c) ++ else: ++ # actually, remove_handle is called automatically on close ++ c.close() ++ m.close() ++ ++ # check result ++ self.assertEqual('success', m.handles[0].body.getvalue()) ++ self.assertEqual(200, m.handles[0].http_code) ++ # bottle generated response body ++ assert 'Error 403: Forbidden' in m.handles[1].body.getvalue() ++ self.assertEqual(403, m.handles[1].http_code) ++ # bottle generated response body ++ self.assertEqual('', m.handles[2].body.getvalue()) ++ self.assertEqual(-1, m.handles[2].http_code) +-- +1.7.1 + + +From 55b38bfbba56f6179633dc90f7cf3e72c3433b9f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 14:15:13 -0500 +Subject: [PATCH 037/112] Exercise all 3 possibilites in ported multi3 test + +Signed-off-by: Kamil Dudka +--- + tests/multi_test.py | 37 +++++++++++++++++++++++++------------ + 1 files changed, 25 insertions(+), 12 deletions(-) + +diff --git a/tests/multi_test.py b/tests/multi_test.py +index 1c13f70..e1fec05 100644 +--- a/tests/multi_test.py ++++ b/tests/multi_test.py +@@ -109,7 +109,7 @@ class MultiTest(unittest.TestCase): + assert 'Error 404: Not Found' in m.handles[2].body.getvalue() + self.assertEqual(404, m.handles[2].http_code) + +- def test_adding_closed_handle(self): ++ def check_adding_closed_handle(self, close_fn): + # init + m = pycurl.CurlMulti() + m.handles = [] +@@ -157,17 +157,7 @@ class MultiTest(unittest.TestCase): + assert c.debug + c.http_code = -1 + # pycurl API calls +- if 0: +- m.remove_handle(c) +- c.close() +- elif 0: +- # in the C API this is the wrong calling order, but pycurl +- # handles this automatically +- c.close() +- m.remove_handle(c) +- else: +- # actually, remove_handle is called automatically on close +- c.close() ++ close_fn(m, c) + m.close() + + # check result +@@ -179,3 +169,26 @@ class MultiTest(unittest.TestCase): + # bottle generated response body + self.assertEqual('', m.handles[2].body.getvalue()) + self.assertEqual(-1, m.handles[2].http_code) ++ ++ def _remove_then_close(self, m, c): ++ m.remove_handle(c) ++ c.close() ++ ++ def _close_then_remove(self, m, c): ++ # in the C API this is the wrong calling order, but pycurl ++ # handles this automatically ++ c.close() ++ m.remove_handle(c) ++ ++ def _close_without_removing(self, m, c): ++ # actually, remove_handle is called automatically on close ++ c.close ++ ++ def test_adding_closed_handle_remove_then_close(self): ++ self.check_adding_closed_handle(self._remove_then_close) ++ ++ def test_adding_closed_handle_close_then_remove(self): ++ self.check_adding_closed_handle(self._close_then_remove) ++ ++ def test_adding_closed_handle_close_without_removing(self): ++ self.check_adding_closed_handle(self._close_without_removing) +-- +1.7.1 + + +From 6ec826f66a9a86a2c1701e60a094d1f0004f9dff Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 14:21:33 -0500 +Subject: [PATCH 038/112] Port mulit4 test + +Signed-off-by: Kamil Dudka +--- + tests/multi_test.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 50 insertions(+), 0 deletions(-) + +diff --git a/tests/multi_test.py b/tests/multi_test.py +index e1fec05..a508809 100644 +--- a/tests/multi_test.py ++++ b/tests/multi_test.py +@@ -4,6 +4,7 @@ + + import pycurl + import unittest ++import select + + from . import app + from . import runwsgi +@@ -58,6 +59,55 @@ class MultiTest(unittest.TestCase): + self.assertEqual('success', io1.getvalue()) + self.assertEqual('success', io2.getvalue()) + ++ def test_multi_select_fdset(self): ++ c1 = pycurl.Curl() ++ c2 = pycurl.Curl() ++ c3 = pycurl.Curl() ++ c1.setopt(c1.URL, "http://localhost:8380/success") ++ c2.setopt(c2.URL, "http://localhost:8381/success") ++ c3.setopt(c3.URL, "http://localhost:8382/success") ++ c1.body = util.StringIO() ++ c2.body = util.StringIO() ++ c3.body = util.StringIO() ++ c1.setopt(c1.WRITEFUNCTION, c1.body.write) ++ c2.setopt(c2.WRITEFUNCTION, c2.body.write) ++ c3.setopt(c3.WRITEFUNCTION, c3.body.write) ++ ++ m = pycurl.CurlMulti() ++ m.add_handle(c1) ++ m.add_handle(c2) ++ m.add_handle(c3) ++ ++ # Number of seconds to wait for a timeout to happen ++ SELECT_TIMEOUT = 0.1 ++ ++ # Stir the state machine into action ++ while 1: ++ ret, num_handles = m.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ ++ # Keep going until all the connections have terminated ++ while num_handles: ++ select.select(*m.fdset() + (SELECT_TIMEOUT,)) ++ while 1: ++ ret, num_handles = m.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ ++ # Cleanup ++ m.remove_handle(c3) ++ m.remove_handle(c2) ++ m.remove_handle(c1) ++ m.close() ++ c1.close() ++ c2.close() ++ c3.close() ++ ++ self.assertEqual('success', c1.body.getvalue()) ++ self.assertEqual('success', c2.body.getvalue()) ++ self.assertEqual('success', c3.body.getvalue()) ++ + def test_multi_status_codes(self): + # init + m = pycurl.CurlMulti() +-- +1.7.1 + + +From b29ff271843da14e9b008e8853a3b84b06ed6ecc Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 00:24:44 -0500 +Subject: [PATCH 039/112] Port multit6 test + +Signed-off-by: Kamil Dudka +--- + tests/multi_test.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 55 insertions(+), 0 deletions(-) + +diff --git a/tests/multi_test.py b/tests/multi_test.py +index a508809..e83ce48 100644 +--- a/tests/multi_test.py ++++ b/tests/multi_test.py +@@ -242,3 +242,58 @@ class MultiTest(unittest.TestCase): + + def test_adding_closed_handle_close_without_removing(self): + self.check_adding_closed_handle(self._close_without_removing) ++ ++ def test_multi_info_read(self): ++ c1 = pycurl.Curl() ++ c2 = pycurl.Curl() ++ c3 = pycurl.Curl() ++ c1.setopt(c1.URL, "http://localhost:8380/success") ++ c2.setopt(c2.URL, "http://localhost:8381/success") ++ c3.setopt(c3.URL, "http://localhost:8382/success") ++ c1.body = util.StringIO() ++ c2.body = util.StringIO() ++ c3.body = util.StringIO() ++ c1.setopt(c1.WRITEFUNCTION, c1.body.write) ++ c2.setopt(c2.WRITEFUNCTION, c2.body.write) ++ c3.setopt(c3.WRITEFUNCTION, c3.body.write) ++ ++ m = pycurl.CurlMulti() ++ m.add_handle(c1) ++ m.add_handle(c2) ++ m.add_handle(c3) ++ ++ # Number of seconds to wait for a timeout to happen ++ SELECT_TIMEOUT = 1.0 ++ ++ # Stir the state machine into action ++ while 1: ++ ret, num_handles = m.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ ++ # Keep going until all the connections have terminated ++ while num_handles: ++ # The select method uses fdset internally to determine which file descriptors ++ # to check. ++ m.select(SELECT_TIMEOUT) ++ while 1: ++ ret, num_handles = m.perform() ++ # Print the message, if any ++ while True: ++ info = m.info_read() ++ print info ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ ++ # Cleanup ++ m.remove_handle(c3) ++ m.remove_handle(c2) ++ m.remove_handle(c1) ++ m.close() ++ c1.close() ++ c2.close() ++ c3.close() ++ ++ self.assertEqual('success', c1.body.getvalue()) ++ self.assertEqual('success', c2.body.getvalue()) ++ self.assertEqual('success', c3.body.getvalue()) +-- +1.7.1 + + +From a14adcb02e78aa26e1f6b326815a7a0ae824fc4d Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 00:38:59 -0500 +Subject: [PATCH 040/112] Improve info_read test to test info_read + +Signed-off-by: Kamil Dudka +--- + tests/multi_test.py | 19 +++++++++++++++---- + 1 files changed, 15 insertions(+), 4 deletions(-) + +diff --git a/tests/multi_test.py b/tests/multi_test.py +index e83ce48..10af44c 100644 +--- a/tests/multi_test.py ++++ b/tests/multi_test.py +@@ -271,6 +271,7 @@ class MultiTest(unittest.TestCase): + if ret != pycurl.E_CALL_MULTI_PERFORM: + break + ++ infos = [] + # Keep going until all the connections have terminated + while num_handles: + # The select method uses fdset internally to determine which file descriptors +@@ -278,13 +279,23 @@ class MultiTest(unittest.TestCase): + m.select(SELECT_TIMEOUT) + while 1: + ret, num_handles = m.perform() +- # Print the message, if any +- while True: +- info = m.info_read() +- print info ++ info = m.info_read() ++ infos.append(info) + if ret != pycurl.E_CALL_MULTI_PERFORM: + break + ++ all_handles = [] ++ for info in infos: ++ handles = info[1] ++ # last info is an empty array ++ if handles: ++ all_handles.extend(handles) ++ ++ self.assertEqual(3, len(all_handles)) ++ assert c1 in all_handles ++ assert c2 in all_handles ++ assert c3 in all_handles ++ + # Cleanup + m.remove_handle(c3) + m.remove_handle(c2) +-- +1.7.1 + + +From ef1510a4b5426b2fb0458ae244be17a578407fc7 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 00:40:25 -0500 +Subject: [PATCH 041/112] Port multi5 test + +Signed-off-by: Kamil Dudka +--- + tests/multi_test.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 51 insertions(+), 0 deletions(-) + +diff --git a/tests/multi_test.py b/tests/multi_test.py +index 10af44c..d9c6174 100644 +--- a/tests/multi_test.py ++++ b/tests/multi_test.py +@@ -243,6 +243,57 @@ class MultiTest(unittest.TestCase): + def test_adding_closed_handle_close_without_removing(self): + self.check_adding_closed_handle(self._close_without_removing) + ++ def test_multi_select(self): ++ c1 = pycurl.Curl() ++ c2 = pycurl.Curl() ++ c3 = pycurl.Curl() ++ c1.setopt(c1.URL, "http://localhost:8380/success") ++ c2.setopt(c2.URL, "http://localhost:8381/success") ++ c3.setopt(c3.URL, "http://localhost:8382/success") ++ c1.body = util.StringIO() ++ c2.body = util.StringIO() ++ c3.body = util.StringIO() ++ c1.setopt(c1.WRITEFUNCTION, c1.body.write) ++ c2.setopt(c2.WRITEFUNCTION, c2.body.write) ++ c3.setopt(c3.WRITEFUNCTION, c3.body.write) ++ ++ m = pycurl.CurlMulti() ++ m.add_handle(c1) ++ m.add_handle(c2) ++ m.add_handle(c3) ++ ++ # Number of seconds to wait for a timeout to happen ++ SELECT_TIMEOUT = 1.0 ++ ++ # Stir the state machine into action ++ while 1: ++ ret, num_handles = m.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ ++ # Keep going until all the connections have terminated ++ while num_handles: ++ # The select method uses fdset internally to determine which file descriptors ++ # to check. ++ m.select(SELECT_TIMEOUT) ++ while 1: ++ ret, num_handles = m.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ ++ # Cleanup ++ m.remove_handle(c3) ++ m.remove_handle(c2) ++ m.remove_handle(c1) ++ m.close() ++ c1.close() ++ c2.close() ++ c3.close() ++ ++ self.assertEqual('success', c1.body.getvalue()) ++ self.assertEqual('success', c2.body.getvalue()) ++ self.assertEqual('success', c3.body.getvalue()) ++ + def test_multi_info_read(self): + c1 = pycurl.Curl() + c2 = pycurl.Curl() +-- +1.7.1 + + +From 2a6f3d5ff191f6709fddfac3a2e0a6794a66f75d Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 22:05:34 -0500 +Subject: [PATCH 042/112] Ported test_multi_socket.py + +Signed-off-by: Kamil Dudka +--- + tests/multi_socket_test.py | 110 ++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 110 insertions(+), 0 deletions(-) + create mode 100644 tests/multi_socket_test.py + +diff --git a/tests/multi_socket_test.py b/tests/multi_socket_test.py +new file mode 100644 +index 0000000..2586569 +--- /dev/null ++++ b/tests/multi_socket_test.py +@@ -0,0 +1,110 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++import select ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module_1, teardown_module_1 = runwsgi.app_runner_setup((app.app, 8380)) ++setup_module_2, teardown_module_2 = runwsgi.app_runner_setup((app.app, 8381)) ++setup_module_3, teardown_module_3 = runwsgi.app_runner_setup((app.app, 8382)) ++ ++def setup_module(mod): ++ setup_module_1(mod) ++ setup_module_2(mod) ++ setup_module_3(mod) ++ ++def teardown_module(mod): ++ teardown_module_3(mod) ++ teardown_module_2(mod) ++ teardown_module_1(mod) ++ ++class MultiSocketTest(unittest.TestCase): ++ def test_multi_socket(self): ++ urls = [ ++ 'http://localhost:8380/success', ++ 'http://localhost:8381/success', ++ 'http://localhost:8382/success', ++ ] ++ ++ timers = [] ++ ++ # timer callback ++ def timer(msecs): ++ #print('Timer callback msecs:', msecs) ++ timers.append(msecs) ++ ++ socket_events = [] ++ ++ # socket callback ++ def socket(event, socket, multi, data): ++ #print(event, socket, multi, data) ++ # multi.assign(socket, timer) ++ socket_events.append((event, multi)) ++ ++ # init ++ m = pycurl.CurlMulti() ++ m.setopt(pycurl.M_PIPELINING, 1) ++ m.setopt(pycurl.M_TIMERFUNCTION, timer) ++ m.setopt(pycurl.M_SOCKETFUNCTION, socket) ++ m.handles = [] ++ for url in urls: ++ c = pycurl.Curl() ++ # save info in standard Python attributes ++ c.url = url ++ c.body = util.StringIO() ++ c.http_code = -1 ++ m.handles.append(c) ++ # pycurl API calls ++ c.setopt(c.URL, c.url) ++ c.setopt(c.WRITEFUNCTION, c.body.write) ++ m.add_handle(c) ++ ++ # get data ++ num_handles = len(m.handles) ++ while num_handles: ++ while 1: ++ ret, num_handles = m.socket_all() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ # currently no more I/O is pending, could do something in the meantime ++ # (display a progress bar, etc.) ++ m.select(0.1) ++ ++ for c in m.handles: ++ # save info in standard Python attributes ++ c.http_code = c.getinfo(c.HTTP_CODE) ++ ++ # at least in and remove events per socket ++ assert len(socket_events) >= 6 ++ ++ # print result ++ for c in m.handles: ++ self.assertEqual('success', c.body.getvalue()) ++ self.assertEqual(200, c.http_code) ++ ++ # multi, not curl handle ++ self.check(pycurl.POLL_IN, m, socket_events) ++ self.check(pycurl.POLL_REMOVE, m, socket_events) ++ ++ assert len(timers) > 0 ++ assert timers[0] > 0 ++ self.assertEqual(-1, timers[-1]) ++ ++ # close handles ++ for c in m.handles: ++ # pycurl API calls ++ m.remove_handle(c) ++ c.close() ++ m.close() ++ ++ def check(self, event, multi, socket_events): ++ for event_, multi_ in socket_events: ++ if event == event_ and multi == multi_: ++ return ++ assert False, '%d %s not found in socket events' % (event, multi) +-- +1.7.1 + + +From 408772865075a389e89441afc44ae402bf87cae7 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 22:29:45 -0500 +Subject: [PATCH 043/112] Ported test_multi_socket_select.py + +Signed-off-by: Kamil Dudka +--- + tests/multi_socket_select_test.py | 130 +++++++++++++++++++++++++++++++++++++ + 1 files changed, 130 insertions(+), 0 deletions(-) + create mode 100644 tests/multi_socket_select_test.py + +diff --git a/tests/multi_socket_select_test.py b/tests/multi_socket_select_test.py +new file mode 100644 +index 0000000..b9ba950 +--- /dev/null ++++ b/tests/multi_socket_select_test.py +@@ -0,0 +1,130 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++import select ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module_1, teardown_module_1 = runwsgi.app_runner_setup((app.app, 8380)) ++setup_module_2, teardown_module_2 = runwsgi.app_runner_setup((app.app, 8381)) ++setup_module_3, teardown_module_3 = runwsgi.app_runner_setup((app.app, 8382)) ++ ++def setup_module(mod): ++ setup_module_1(mod) ++ setup_module_2(mod) ++ setup_module_3(mod) ++ ++def teardown_module(mod): ++ teardown_module_3(mod) ++ teardown_module_2(mod) ++ teardown_module_1(mod) ++ ++class MultiSocketSelectTest(unittest.TestCase): ++ def test_multi_socket_select(self): ++ sockets = set() ++ timeout = 0 ++ ++ urls = [ ++ 'http://localhost:8380/success', ++ 'http://localhost:8381/success', ++ 'http://localhost:8382/success', ++ ] ++ ++ timers = [] ++ ++ # timer callback ++ def timer(msecs): ++ #print('Timer callback msecs:', msecs) ++ timers.append(msecs) ++ ++ socket_events = [] ++ ++ # socket callback ++ def socket(event, socket, multi, data): ++ if event == pycurl.POLL_REMOVE: ++ #print("Remove Socket %d"%socket) ++ sockets.remove(socket) ++ else: ++ if socket not in sockets: ++ #print("Add socket %d"%socket) ++ sockets.add(socket) ++ socket_events.append((event, multi)) ++ ++ # init ++ m = pycurl.CurlMulti() ++ m.setopt(pycurl.M_PIPELINING, 1) ++ m.setopt(pycurl.M_TIMERFUNCTION, timer) ++ m.setopt(pycurl.M_SOCKETFUNCTION, socket) ++ m.handles = [] ++ for url in urls: ++ c = pycurl.Curl() ++ # save info in standard Python attributes ++ c.url = url ++ c.body = util.StringIO() ++ c.http_code = -1 ++ m.handles.append(c) ++ # pycurl API calls ++ c.setopt(c.URL, c.url) ++ c.setopt(c.WRITEFUNCTION, c.body.write) ++ m.add_handle(c) ++ ++ # get data ++ num_handles = len(m.handles) ++ ++ while (pycurl.E_CALL_MULTI_PERFORM==m.socket_all()[0]): ++ pass ++ ++ timeout = m.timeout() ++ ++ ++ while True: ++ (rr, wr, er) = select.select(sockets,sockets,sockets,timeout/1000.0) ++ socketSet = set(rr+wr+er) ++ if socketSet: ++ for s in socketSet: ++ while True: ++ (ret,running) = m.socket_action(s,0) ++ if ret!=pycurl.E_CALL_MULTI_PERFORM: ++ break ++ else: ++ (ret,running) = m.socket_action(pycurl.SOCKET_TIMEOUT,0) ++ if running==0: ++ break ++ ++ for c in m.handles: ++ # save info in standard Python attributes ++ c.http_code = c.getinfo(c.HTTP_CODE) ++ ++ # at least in and remove events per socket ++ assert len(socket_events) >= 6 ++ ++ # print result ++ for c in m.handles: ++ self.assertEqual('success', c.body.getvalue()) ++ self.assertEqual(200, c.http_code) ++ ++ # multi, not curl handle ++ self.check(pycurl.POLL_IN, m, socket_events) ++ self.check(pycurl.POLL_REMOVE, m, socket_events) ++ ++ assert len(timers) > 0 ++ assert timers[0] > 0 ++ self.assertEqual(-1, timers[-1]) ++ ++ # close handles ++ for c in m.handles: ++ # pycurl API calls ++ m.remove_handle(c) ++ c.close() ++ m.close() ++ ++ def check(self, event, multi, socket_events): ++ for event_, multi_ in socket_events: ++ if event == event_ and multi == multi_: ++ return ++ assert False, '%d %s not found in socket events' % (event, multi) +-- +1.7.1 + + +From b9fc3bbb01affc790d94801556275e853d03c2c0 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:15:47 -0500 +Subject: [PATCH 044/112] Ported test_multi_timer.py + +Signed-off-by: Kamil Dudka +--- + tests/multi_timer_test.py | 88 +++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 88 insertions(+), 0 deletions(-) + create mode 100644 tests/multi_timer_test.py + +diff --git a/tests/multi_timer_test.py b/tests/multi_timer_test.py +new file mode 100644 +index 0000000..e961780 +--- /dev/null ++++ b/tests/multi_timer_test.py +@@ -0,0 +1,88 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++import select ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module_1, teardown_module_1 = runwsgi.app_runner_setup((app.app, 8380)) ++setup_module_2, teardown_module_2 = runwsgi.app_runner_setup((app.app, 8381)) ++setup_module_3, teardown_module_3 = runwsgi.app_runner_setup((app.app, 8382)) ++ ++def setup_module(mod): ++ setup_module_1(mod) ++ setup_module_2(mod) ++ setup_module_3(mod) ++ ++def teardown_module(mod): ++ teardown_module_3(mod) ++ teardown_module_2(mod) ++ teardown_module_1(mod) ++ ++class MultiSocketTest(unittest.TestCase): ++ def test_multi_timer(self): ++ urls = [ ++ 'http://localhost:8380/success', ++ 'http://localhost:8381/success', ++ 'http://localhost:8382/success', ++ ] ++ ++ timers = [] ++ ++ # timer callback ++ def timer(msecs): ++ #print('Timer callback msecs:', msecs) ++ timers.append(msecs) ++ ++ # init ++ m = pycurl.CurlMulti() ++ m.setopt(pycurl.M_PIPELINING, 1) ++ m.setopt(pycurl.M_TIMERFUNCTION, timer) ++ m.handles = [] ++ for url in urls: ++ c = pycurl.Curl() ++ # save info in standard Python attributes ++ c.url = url ++ c.body = util.StringIO() ++ c.http_code = -1 ++ m.handles.append(c) ++ # pycurl API calls ++ c.setopt(c.URL, c.url) ++ c.setopt(c.WRITEFUNCTION, c.body.write) ++ m.add_handle(c) ++ ++ # get data ++ num_handles = len(m.handles) ++ while num_handles: ++ while 1: ++ ret, num_handles = m.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ # currently no more I/O is pending, could do something in the meantime ++ # (display a progress bar, etc.) ++ m.select(1.0) ++ ++ for c in m.handles: ++ # save info in standard Python attributes ++ c.http_code = c.getinfo(c.HTTP_CODE) ++ ++ # print result ++ for c in m.handles: ++ self.assertEqual('success', c.body.getvalue()) ++ self.assertEqual(200, c.http_code) ++ ++ assert len(timers) > 0 ++ assert timers[0] > 0 ++ self.assertEqual(-1, timers[-1]) ++ ++ # close handles ++ for c in m.handles: ++ # pycurl API calls ++ m.remove_handle(c) ++ c.close() ++ m.close() +-- +1.7.1 + + +From 1411a22bd02d30b7a0dc884ad406fdb4169e1307 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:17:14 -0500 +Subject: [PATCH 045/112] Timers are tested in multi_timer test, delete them from other multi tests + +Signed-off-by: Kamil Dudka +--- + tests/multi_socket_select_test.py | 12 ------------ + tests/multi_socket_test.py | 13 ------------- + 2 files changed, 0 insertions(+), 25 deletions(-) + +diff --git a/tests/multi_socket_select_test.py b/tests/multi_socket_select_test.py +index b9ba950..0c472cf 100644 +--- a/tests/multi_socket_select_test.py ++++ b/tests/multi_socket_select_test.py +@@ -35,13 +35,6 @@ class MultiSocketSelectTest(unittest.TestCase): + 'http://localhost:8382/success', + ] + +- timers = [] +- +- # timer callback +- def timer(msecs): +- #print('Timer callback msecs:', msecs) +- timers.append(msecs) +- + socket_events = [] + + # socket callback +@@ -58,7 +51,6 @@ class MultiSocketSelectTest(unittest.TestCase): + # init + m = pycurl.CurlMulti() + m.setopt(pycurl.M_PIPELINING, 1) +- m.setopt(pycurl.M_TIMERFUNCTION, timer) + m.setopt(pycurl.M_SOCKETFUNCTION, socket) + m.handles = [] + for url in urls: +@@ -112,10 +104,6 @@ class MultiSocketSelectTest(unittest.TestCase): + self.check(pycurl.POLL_IN, m, socket_events) + self.check(pycurl.POLL_REMOVE, m, socket_events) + +- assert len(timers) > 0 +- assert timers[0] > 0 +- self.assertEqual(-1, timers[-1]) +- + # close handles + for c in m.handles: + # pycurl API calls +diff --git a/tests/multi_socket_test.py b/tests/multi_socket_test.py +index 2586569..2cce7ae 100644 +--- a/tests/multi_socket_test.py ++++ b/tests/multi_socket_test.py +@@ -32,25 +32,16 @@ class MultiSocketTest(unittest.TestCase): + 'http://localhost:8382/success', + ] + +- timers = [] +- +- # timer callback +- def timer(msecs): +- #print('Timer callback msecs:', msecs) +- timers.append(msecs) +- + socket_events = [] + + # socket callback + def socket(event, socket, multi, data): + #print(event, socket, multi, data) +- # multi.assign(socket, timer) + socket_events.append((event, multi)) + + # init + m = pycurl.CurlMulti() + m.setopt(pycurl.M_PIPELINING, 1) +- m.setopt(pycurl.M_TIMERFUNCTION, timer) + m.setopt(pycurl.M_SOCKETFUNCTION, socket) + m.handles = [] + for url in urls: +@@ -92,10 +83,6 @@ class MultiSocketTest(unittest.TestCase): + self.check(pycurl.POLL_IN, m, socket_events) + self.check(pycurl.POLL_REMOVE, m, socket_events) + +- assert len(timers) > 0 +- assert timers[0] > 0 +- self.assertEqual(-1, timers[-1]) +- + # close handles + for c in m.handles: + # pycurl API calls +-- +1.7.1 + + +From dae1d75a3c083cc5c37cefff47d185ed0bf16af6 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:30:48 -0500 +Subject: [PATCH 046/112] Ported post test + +Signed-off-by: Kamil Dudka +--- + tests/app.py | 5 +++++ + tests/post_test.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 54 insertions(+), 0 deletions(-) + create mode 100644 tests/post_test.py + +diff --git a/tests/app.py b/tests/app.py +index 3b9303b..1fa09a6 100644 +--- a/tests/app.py ++++ b/tests/app.py +@@ -1,4 +1,5 @@ + import bottle ++import json + + app = bottle.Bottle() + +@@ -13,3 +14,7 @@ def forbidden(): + @app.route('/status/404') + def not_found(): + bottle.abort(404, 'not found') ++ ++@app.route('/postfields', method='post') ++def postfields(): ++ return json.dumps(dict(bottle.request.forms)) +diff --git a/tests/post_test.py b/tests/post_test.py +new file mode 100644 +index 0000000..183c4c9 +--- /dev/null ++++ b/tests/post_test.py +@@ -0,0 +1,49 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++import io ++import json ++try: ++ import urllib.parse as urllib_parse ++except ImportError: ++ import urllib as urllib_parse ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++class PostTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_post_single_field(self): ++ pf = {'field1': 'value1'} ++ self.check(pf) ++ ++ def test_post_multiple_fields(self): ++ pf = {'field1':'value1', 'field2':'value2 with blanks', 'field3':'value3'} ++ self.check(pf) ++ ++ def test_post_fields_with_ampersand(self): ++ pf = {'field1':'value1', 'field2':'value2 with blanks and & chars', ++ 'field3':'value3'} ++ self.check(pf) ++ ++ def check(self, pf): ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/postfields') ++ self.curl.setopt(pycurl.POSTFIELDS, urllib_parse.urlencode(pf)) ++ #self.curl.setopt(pycurl.VERBOSE, 1) ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.perform() ++ body = sio.getvalue() ++ returned_fields = json.loads(body) ++ self.assertEqual(pf, returned_fields) +-- +1.7.1 + + +From 453c605a6a0ab5b04d5d1e2cf70cd99a4fd9db18 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:35:02 -0500 +Subject: [PATCH 047/112] Port test_post2.py null byte test + +Signed-off-by: Kamil Dudka +--- + tests/post_test.py | 28 ++++++++++++++++++++++++---- + 1 files changed, 24 insertions(+), 4 deletions(-) + +diff --git a/tests/post_test.py b/tests/post_test.py +index 183c4c9..6f9cf80 100644 +--- a/tests/post_test.py ++++ b/tests/post_test.py +@@ -26,18 +26,18 @@ class PostTest(unittest.TestCase): + + def test_post_single_field(self): + pf = {'field1': 'value1'} +- self.check(pf) ++ self.urlencode_and_check(pf) + + def test_post_multiple_fields(self): + pf = {'field1':'value1', 'field2':'value2 with blanks', 'field3':'value3'} +- self.check(pf) ++ self.urlencode_and_check(pf) + + def test_post_fields_with_ampersand(self): + pf = {'field1':'value1', 'field2':'value2 with blanks and & chars', + 'field3':'value3'} +- self.check(pf) ++ self.urlencode_and_check(pf) + +- def check(self, pf): ++ def urlencode_and_check(self, pf): + self.curl.setopt(pycurl.URL, 'http://localhost:8380/postfields') + self.curl.setopt(pycurl.POSTFIELDS, urllib_parse.urlencode(pf)) + #self.curl.setopt(pycurl.VERBOSE, 1) +@@ -47,3 +47,23 @@ class PostTest(unittest.TestCase): + body = sio.getvalue() + returned_fields = json.loads(body) + self.assertEqual(pf, returned_fields) ++ ++ def test_post_with_null_byte(self): ++ send = [ ++ ('field3', (pycurl.FORM_CONTENTS, 'this is wei\000rd, but null-bytes are okay')) ++ ] ++ expect = { ++ 'field3': 'this is wei\000rd, but null-bytes are okay', ++ } ++ self.check_post(send, expect) ++ ++ def check_post(self, send, expect): ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/postfields') ++ self.curl.setopt(pycurl.HTTPPOST, send) ++ #self.curl.setopt(pycurl.VERBOSE, 1) ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.perform() ++ body = sio.getvalue() ++ returned_fields = json.loads(body) ++ self.assertEqual(expect, returned_fields) +-- +1.7.1 + + +From 7b1e54bf72d209964ba47f5bfa8f8b192b697632 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:50:51 -0500 +Subject: [PATCH 048/112] Port test_post2.py file upload test + +Signed-off-by: Kamil Dudka +--- + tests/app.py | 24 ++++++++++++++++++++++++ + tests/post_test.py | 22 +++++++++++++++++++--- + 2 files changed, 43 insertions(+), 3 deletions(-) + +diff --git a/tests/app.py b/tests/app.py +index 1fa09a6..a83e628 100644 +--- a/tests/app.py ++++ b/tests/app.py +@@ -18,3 +18,27 @@ def not_found(): + @app.route('/postfields', method='post') + def postfields(): + return json.dumps(dict(bottle.request.forms)) ++ ++# XXX file is not a bottle FileUpload instance, but FieldStorage? ++def convert_file(key, file): ++ return { ++ 'key': key, ++ 'name': file.name, ++ 'raw_filename': file.raw_filename, ++ 'headers': file.headers, ++ 'content_type': file.content_type, ++ 'content_length': file.content_length, ++ 'data': file.read(), ++ } ++ ++def convert_file(key, file): ++ return { ++ 'name': file.name, ++ 'filename': file.filename, ++ 'data': file.file.read(), ++ } ++ ++@app.route('/files', method='post') ++def files(): ++ files = [convert_file(key, bottle.request.files[key]) for key in bottle.request.files] ++ return json.dumps(files) +diff --git a/tests/post_test.py b/tests/post_test.py +index 6f9cf80..7df0f3b 100644 +--- a/tests/post_test.py ++++ b/tests/post_test.py +@@ -2,6 +2,7 @@ + # -*- coding: iso-8859-1 -*- + # vi:ts=4:et + ++import os.path + import pycurl + import unittest + import io +@@ -55,10 +56,25 @@ class PostTest(unittest.TestCase): + expect = { + 'field3': 'this is wei\000rd, but null-bytes are okay', + } +- self.check_post(send, expect) ++ self.check_post(send, expect, 'http://localhost:8380/postfields') + +- def check_post(self, send, expect): +- self.curl.setopt(pycurl.URL, 'http://localhost:8380/postfields') ++ def test_post_file(self): ++ path = os.path.join(os.path.dirname(__file__), '..', 'README') ++ with open(path) as f: ++ contents = f.read() ++ send = [ ++ #('field2', (pycurl.FORM_FILE, 'test_post.py', pycurl.FORM_FILE, 'test_post2.py')), ++ ('field2', (pycurl.FORM_FILE, path)), ++ ] ++ expect = [{ ++ 'name': 'field2', ++ 'filename': 'README', ++ 'data': contents, ++ }] ++ self.check_post(send, expect, 'http://localhost:8380/files') ++ ++ def check_post(self, send, expect, endpoint): ++ self.curl.setopt(pycurl.URL, endpoint) + self.curl.setopt(pycurl.HTTPPOST, send) + #self.curl.setopt(pycurl.VERBOSE, 1) + sio = util.StringIO() +-- +1.7.1 + + +From f48bdb27952ad5e38f8c259711a514e146ff454c Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:51:45 -0500 +Subject: [PATCH 049/112] Add a note that this test takes forever to run + +Signed-off-by: Kamil Dudka +--- + tests/post_test.py | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + +diff --git a/tests/post_test.py b/tests/post_test.py +index 7df0f3b..564d043 100644 +--- a/tests/post_test.py ++++ b/tests/post_test.py +@@ -73,6 +73,7 @@ class PostTest(unittest.TestCase): + }] + self.check_post(send, expect, 'http://localhost:8380/files') + ++ # XXX this test takes about a second to run, check keep-alives? + def check_post(self, send, expect, endpoint): + self.curl.setopt(pycurl.URL, endpoint) + self.curl.setopt(pycurl.HTTPPOST, send) +-- +1.7.1 + + +From e276465088c1bfa8141417c1d8375de2c5a969a2 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:59:04 -0500 +Subject: [PATCH 050/112] Ported test_post3.py + +Signed-off-by: Kamil Dudka +--- + tests/post_with_read_callback_test.py | 60 +++++++++++++++++++++++++++++++++ + 1 files changed, 60 insertions(+), 0 deletions(-) + create mode 100644 tests/post_with_read_callback_test.py + +diff --git a/tests/post_with_read_callback_test.py b/tests/post_with_read_callback_test.py +new file mode 100644 +index 0000000..a09e83a +--- /dev/null ++++ b/tests/post_with_read_callback_test.py +@@ -0,0 +1,60 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import os.path ++import pycurl ++import unittest ++import io ++import json ++try: ++ import urllib.parse as urllib_parse ++except ImportError: ++ import urllib as urllib_parse ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++POSTFIELDS = { ++ 'field1':'value1', ++ 'field2':'value2 with blanks', ++ 'field3':'value3', ++} ++POSTSTRING = urllib_parse.urlencode(POSTFIELDS) ++ ++class DataProvider(object): ++ def __init__(self): ++ self.finished = False ++ ++ def read_cb(self, size): ++ assert len(POSTSTRING) <= size ++ if not self.finished: ++ self.finished = True ++ return POSTSTRING ++ else: ++ # Nothing more to read ++ return "" ++ ++class PostWithReadCallbackTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_post_with_read_callback(self): ++ d = DataProvider() ++ self.curl.setopt(self.curl.URL, 'http://localhost:8380/postfields') ++ self.curl.setopt(self.curl.POST, 1) ++ self.curl.setopt(self.curl.POSTFIELDSIZE, len(POSTSTRING)) ++ self.curl.setopt(self.curl.READFUNCTION, d.read_cb) ++ #self.curl.setopt(self.curl.VERBOSE, 1) ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.perform() ++ ++ actual = json.loads(sio.getvalue()) ++ self.assertEqual(POSTFIELDS, actual) +-- +1.7.1 + + +From bff1fa53c2c59ee0a31e3bb73d20eb56cf0305d4 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:10:41 -0500 +Subject: [PATCH 051/112] Ported test_socketopen.py + +Signed-off-by: Kamil Dudka +--- + tests/socket_open_test.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 45 insertions(+), 0 deletions(-) + create mode 100644 tests/socket_open_test.py + +diff --git a/tests/socket_open_test.py b/tests/socket_open_test.py +new file mode 100644 +index 0000000..dff3f65 +--- /dev/null ++++ b/tests/socket_open_test.py +@@ -0,0 +1,45 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import socket ++import pycurl ++import unittest ++try: ++ import urllib.parse as urllib_parse ++except ImportError: ++ import urllib as urllib_parse ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++socket_open_called = False ++ ++def socket_open(family, socktype, protocol): ++ global socket_open_called ++ socket_open_called = True ++ ++ #print(family, socktype, protocol) ++ s = socket.socket(family, socktype, protocol) ++ s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) ++ return s ++ ++class SocketOpenTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_socket_open(self): ++ self.curl.setopt(pycurl.OPENSOCKETFUNCTION, socket_open) ++ self.curl.setopt(self.curl.URL, 'http://localhost:8380/success') ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.perform() ++ ++ assert socket_open_called ++ self.assertEqual('success', sio.getvalue()) +-- +1.7.1 + + +From 3336fa0310f6f030f8a9e27009c42e1b560d3a43 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:13:46 -0500 +Subject: [PATCH 052/112] Ported test_share.py + +Signed-off-by: Kamil Dudka +--- + tests/share_test.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 51 insertions(+), 0 deletions(-) + create mode 100644 tests/share_test.py + +diff --git a/tests/share_test.py b/tests/share_test.py +new file mode 100644 +index 0000000..7b5da77 +--- /dev/null ++++ b/tests/share_test.py +@@ -0,0 +1,51 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import threading ++import pycurl ++import unittest ++try: ++ import urllib.parse as urllib_parse ++except ImportError: ++ import urllib as urllib_parse ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++class WorkerThread(threading.Thread): ++ ++ def __init__(self, share): ++ threading.Thread.__init__(self) ++ self.curl = pycurl.Curl() ++ self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') ++ self.curl.setopt(pycurl.SHARE, share) ++ self.sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, self.sio.write) ++ ++ def run(self): ++ self.curl.perform() ++ self.curl.close() ++ ++class ShareTest(unittest.TestCase): ++ def test_share(self): ++ s = pycurl.CurlShare() ++ s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_COOKIE) ++ s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_DNS) ++ ++ t1 = WorkerThread(s) ++ t2 = WorkerThread(s) ++ ++ t1.start() ++ t2.start() ++ ++ t1.join() ++ t2.join() ++ ++ del s ++ ++ self.assertEqual('success', t1.sio.getvalue()) ++ self.assertEqual('success', t2.sio.getvalue()) +-- +1.7.1 + + +From 1a601a915a969434e1c707e3d17e302b1c7421a5 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:26:45 -0500 +Subject: [PATCH 053/112] Port test_reset.py, which appears to be broken; skip the ported version for now + +Signed-off-by: Kamil Dudka +--- + tests/reset_test.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 76 insertions(+), 0 deletions(-) + create mode 100644 tests/reset_test.py + +diff --git a/tests/reset_test.py b/tests/reset_test.py +new file mode 100644 +index 0000000..66b7108 +--- /dev/null ++++ b/tests/reset_test.py +@@ -0,0 +1,76 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import sys ++import pycurl ++import unittest ++try: ++ import urllib.parse as urllib_parse ++except ImportError: ++ import urllib as urllib_parse ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++class ResetTest(unittest.TestCase): ++ # XXX this test was broken when it was test_reset.py ++ def skip_test_reset(self): ++ outf = util.StringIO() ++ cm = pycurl.CurlMulti() ++ ++ # Set multi handle's options ++ cm.setopt(pycurl.M_PIPELINING, 1) ++ ++ eh = pycurl.Curl() ++ ++ for x in range(1, 20): ++ ++ eh.setopt(pycurl.WRITEFUNCTION, outf.write) ++ eh.setopt(pycurl.URL, 'http://localhost:8380/success') ++ cm.add_handle(eh) ++ ++ while 1: ++ ret, active_handles = cm.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ ++ while active_handles: ++ ret = cm.select(1.0) ++ if ret == -1: ++ continue ++ while 1: ++ ret, active_handles = cm.perform() ++ if ret != pycurl.E_CALL_MULTI_PERFORM: ++ break ++ ++ count, good, bad = cm.info_read() ++ ++ for h, en, em in bad: ++ print("Transfer to %s failed with %d, %s\n" % \ ++ (h.getinfo(pycurl.EFFECTIVE_URL), en, em)) ++ raise RuntimeError ++ ++ for h in good: ++ httpcode = h.getinfo(pycurl.RESPONSE_CODE) ++ if httpcode != 200: ++ print("Transfer to %s failed with code %d\n" %\ ++ (h.getinfo(pycurl.EFFECTIVE_URL), httpcode)) ++ raise RuntimeError ++ ++ else: ++ print("Recd %d bytes from %s" % \ ++ (h.getinfo(pycurl.SIZE_DOWNLOAD), ++ h.getinfo(pycurl.EFFECTIVE_URL))) ++ ++ cm.remove_handle(eh) ++ eh.reset() ++ ++ eh.close() ++ cm.close() ++ outf.close() ++ ++ pycurl.global_cleanup() +-- +1.7.1 + + +From 4c6a41a2156ef35f9f06f604101bc12422806eed Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:55:41 -0500 +Subject: [PATCH 054/112] Start a server on each port once, different servers should go on different ports + +Signed-off-by: Kamil Dudka +--- + tests/runwsgi.py | 9 ++++++++- + 1 files changed, 8 insertions(+), 1 deletions(-) + +diff --git a/tests/runwsgi.py b/tests/runwsgi.py +index 7b39358..6d3b69f 100644 +--- a/tests/runwsgi.py ++++ b/tests/runwsgi.py +@@ -48,6 +48,8 @@ class ServerThread(threading.Thread): + def run(self): + bottle.run(self.app, server=self.server, quiet=True) + ++started_servers = {} ++ + def app_runner_setup(*specs): + '''Returns setup and teardown methods for running a list of WSGI + applications in a daemon thread. +@@ -73,9 +75,14 @@ def app_runner_setup(*specs): + kwargs = {} + else: + app, port, kwargs = spec +- self.servers.append(start_bottle_server(app, port, **kwargs)) ++ if port in started_servers: ++ assert started_servers[port] == (app, kwargs) ++ else: ++ self.servers.append(start_bottle_server(app, port, **kwargs)) ++ started_servers[port] = (app, kwargs) + + def teardown(self): ++ return + for server in self.servers: + # if no tests from module were run, there is no server to shut down + if hasattr(server, 'srv'): +-- +1.7.1 + + +From 5e561de2fcf5cab18d29063b778ea24d61ce8b0e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:56:44 -0500 +Subject: [PATCH 055/112] Need to skip more aggressively it would seem + +Signed-off-by: Kamil Dudka +--- + tests/default_write_function_test.py | 2 +- + tests/reset_test.py | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/tests/default_write_function_test.py b/tests/default_write_function_test.py +index a0de66e..c0d256b 100644 +--- a/tests/default_write_function_test.py ++++ b/tests/default_write_function_test.py +@@ -40,7 +40,7 @@ class DefaultWriteFunctionTest(unittest.TestCase): + os.fsync(STDOUT_FD_NUM) + + # I have a really hard time getting this to work with nose output capture +- def skip_test_perform_get_with_default_write_function(self): ++ def skip_perform_get_with_default_write_function(self): + self.curl.setopt(pycurl.URL, 'http://localhost:8380/success') + with tempfile.NamedTemporaryFile() as f: + #with open('w', 'w+') as f: +diff --git a/tests/reset_test.py b/tests/reset_test.py +index 66b7108..cc55f86 100644 +--- a/tests/reset_test.py ++++ b/tests/reset_test.py +@@ -18,7 +18,7 @@ setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) + + class ResetTest(unittest.TestCase): + # XXX this test was broken when it was test_reset.py +- def skip_test_reset(self): ++ def skip_reset(self): + outf = util.StringIO() + cm = pycurl.CurlMulti() + +-- +1.7.1 + + +From 136485fea353fb6b2c63bf8ea313187835852d11 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 01:29:59 -0500 +Subject: [PATCH 056/112] Store urlencode result in a local variable, otherwise things break in a highly weird way + +Signed-off-by: Kamil Dudka +--- + tests/post_test.py | 24 +++++++++++++++++++++++- + 1 files changed, 23 insertions(+), 1 deletions(-) + +diff --git a/tests/post_test.py b/tests/post_test.py +index 564d043..e8b0675 100644 +--- a/tests/post_test.py ++++ b/tests/post_test.py +@@ -40,11 +40,33 @@ class PostTest(unittest.TestCase): + + def urlencode_and_check(self, pf): + self.curl.setopt(pycurl.URL, 'http://localhost:8380/postfields') +- self.curl.setopt(pycurl.POSTFIELDS, urllib_parse.urlencode(pf)) ++ postfields = urllib_parse.urlencode(pf) ++ self.curl.setopt(pycurl.POSTFIELDS, postfields) ++ ++ # But directly passing urlencode result into setopt call: ++ #self.curl.setopt(pycurl.POSTFIELDS, urllib_parse.urlencode(pf)) ++ # produces: ++ # {'\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00': ''} ++ # Traceback (most recent call last): ++ # File "/usr/local/bin/bottle.py", line 744, in _handle ++ # return route.call(**args) ++ # File "/usr/local/bin/bottle.py", line 1479, in wrapper ++ # rv = callback(*a, **ka) ++ # File "/home/pie/apps/pycurl/tests/app.py", line 21, in postfields ++ # return json.dumps(dict(bottle.request.forms)) ++ # File "/usr/local/lib/python2.7/json/__init__.py", line 231, in dumps ++ # return _default_encoder.encode(obj) ++ # File "/usr/local/lib/python2.7/json/encoder.py", line 201, in encode ++ # chunks = self.iterencode(o, _one_shot=True) ++ # File "/usr/local/lib/python2.7/json/encoder.py", line 264, in iterencode ++ # return _iterencode(o, 0) ++ # UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 4: invalid start byte ++ + #self.curl.setopt(pycurl.VERBOSE, 1) + sio = util.StringIO() + self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) + self.curl.perform() ++ self.assertEqual(200, self.curl.getinfo(pycurl.HTTP_CODE)) + body = sio.getvalue() + returned_fields = json.loads(body) + self.assertEqual(pf, returned_fields) +-- +1.7.1 + + +From f456a8e787078a333c2465a1bb4b4bae2a76be9f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 01:32:33 -0500 +Subject: [PATCH 057/112] Fix regular expression to allow all hex chars like it should + +Signed-off-by: Kamil Dudka +--- + tests/internals_test.py | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/tests/internals_test.py b/tests/internals_test.py +index fb451df..f628ab2 100644 +--- a/tests/internals_test.py ++++ b/tests/internals_test.py +@@ -180,7 +180,7 @@ class InternalsTest(unittest.TestCase): + del m, c + + def test_cyclic_gc(self): +- regexp = re.compile(r'at (0x\d+)') ++ regexp = re.compile(r'at (0x[\da-f]+)') + gc.collect() + c = pycurl.Curl() + c.m = pycurl.CurlMulti() +-- +1.7.1 + + +From c321100736f098850b3b6c2b75fbf7355ae635ce Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 01:36:48 -0500 +Subject: [PATCH 058/112] Rewrite cyclic gc test to use id() + +Signed-off-by: Kamil Dudka +--- + tests/internals_test.py | 9 ++------- + 1 files changed, 2 insertions(+), 7 deletions(-) + +diff --git a/tests/internals_test.py b/tests/internals_test.py +index f628ab2..0133da0 100644 +--- a/tests/internals_test.py ++++ b/tests/internals_test.py +@@ -12,7 +12,6 @@ except ImportError: + import pickle + import gc + import copy +-import re + + class InternalsTest(unittest.TestCase): + def setUp(self): +@@ -180,7 +179,6 @@ class InternalsTest(unittest.TestCase): + del m, c + + def test_cyclic_gc(self): +- regexp = re.compile(r'at (0x[\da-f]+)') + gc.collect() + c = pycurl.Curl() + c.m = pycurl.CurlMulti() +@@ -208,17 +206,14 @@ class InternalsTest(unittest.TestCase): + ##print gc.get_objects() + #if opts.verbose >= 1: + #print("Tracked objects:", len(gc.get_objects())) +- match = regexp.search(repr(c)) +- assert match is not None +- address = match.group(1) ++ c_id = id(c) + # The `del' below should delete these 4 objects: + # Curl + internal dict, CurlMulti + internal dict + del c + gc.collect() + objects = gc.get_objects() +- search = 'at %s' % address + for object in objects: +- assert search not in repr(object) ++ assert id(object) != c_id + #if opts.verbose >= 1: + #print("Tracked objects:", len(gc.get_objects())) + +-- +1.7.1 + + +From 5b644f3685a8af09b39a0b94b718a2fc7c33b22e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 03:25:34 -0500 +Subject: [PATCH 059/112] Adjust ftp test to work with vsftpd started from project root + +Signed-off-by: Kamil Dudka +--- + tests/ftp_test.py | 18 ++++++++++-------- + 1 files changed, 10 insertions(+), 8 deletions(-) + +diff --git a/tests/ftp_test.py b/tests/ftp_test.py +index d215b6e..1d382ed 100644 +--- a/tests/ftp_test.py ++++ b/tests/ftp_test.py +@@ -2,6 +2,8 @@ + # -*- coding: iso-8859-1 -*- + # vi:ts=4:et + ++# Note: this test is meant to be run from pycurl project root. ++ + import pycurl + import unittest + +@@ -15,29 +17,29 @@ class FtpTest(unittest.TestCase): + self.curl.close() + + def test_get_ftp(self): +- self.curl.setopt(pycurl.URL, 'ftp://localhost:8921') ++ self.curl.setopt(pycurl.URL, 'ftp://localhost:8321') + sio = util.StringIO() + self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) + self.curl.perform() + + result = sio.getvalue() + assert 'README' in result +- assert 'bin -> usr/bin' in result ++ assert 'INSTALL' in result + + # XXX this test needs to be fixed + def test_quote(self): +- self.curl.setopt(pycurl.URL, 'ftp://localhost:8921') ++ self.curl.setopt(pycurl.URL, 'ftp://localhost:8321') + sio = util.StringIO() + self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) +- self.curl.setopt(pycurl.QUOTE, ['CWD pub']) ++ self.curl.setopt(pycurl.QUOTE, ['CWD tests']) + self.curl.perform() + + result = sio.getvalue() +- assert 'README' in result +- assert 'bin -> usr/bin' in result ++ assert 'README' not in result ++ assert 'ftp_test.py' in result + + def test_epsv(self): +- self.curl.setopt(pycurl.URL, 'ftp://localhost:8921') ++ self.curl.setopt(pycurl.URL, 'ftp://localhost:8321') + sio = util.StringIO() + self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) + self.curl.setopt(pycurl.FTP_USE_EPSV, 1) +@@ -45,4 +47,4 @@ class FtpTest(unittest.TestCase): + + result = sio.getvalue() + assert 'README' in result +- assert 'bin -> usr/bin' in result ++ assert 'INSTALL' in result +-- +1.7.1 + + +From 758ab367deff461fdd9c631ee4128bd7a4a0b1b3 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:01:13 -0500 +Subject: [PATCH 060/112] Execute vsftpd as test ftp server + +Signed-off-by: Kamil Dudka +--- + tests/ftp_test.py | 3 ++ + tests/procmgr.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++ + tests/runwsgi.py | 20 ++++++++------ + tests/vsftpd.conf | 10 +++++++ + 4 files changed, 98 insertions(+), 8 deletions(-) + create mode 100644 tests/procmgr.py + create mode 100644 tests/vsftpd.conf + +diff --git a/tests/ftp_test.py b/tests/ftp_test.py +index 1d382ed..fa2ef79 100644 +--- a/tests/ftp_test.py ++++ b/tests/ftp_test.py +@@ -8,6 +8,9 @@ import pycurl + import unittest + + from . import util ++from . import procmgr ++ ++setup_module, teardown_module = procmgr.vsftpd_setup() + + class FtpTest(unittest.TestCase): + def setUp(self): +diff --git a/tests/procmgr.py b/tests/procmgr.py +new file mode 100644 +index 0000000..8d5c0cc +--- /dev/null ++++ b/tests/procmgr.py +@@ -0,0 +1,73 @@ ++import threading ++import subprocess ++import os ++import signal ++ ++from . import runwsgi ++ ++class ProcessManager(object): ++ def __init__(self, cmd): ++ self.cmd = cmd ++ ++ def start(self): ++ self.process = subprocess.Popen(self.cmd) ++ ++ self.thread = threading.Thread(target=self.run) ++ self.thread.daemon = True ++ self.thread.start() ++ ++ def run(self): ++ self.process.communicate() ++ ++managers = {} ++ ++def start(cmd): ++ if str(cmd) in managers: ++ # already started ++ return ++ ++ manager = ProcessManager(cmd) ++ managers[str(cmd)] = manager ++ manager.start() ++ ++def start_setup(cmd): ++ def do_start(): ++ start(cmd) ++ return do_start ++ ++# Example on FreeBSD: ++# PYCURL_VSFTPD_PATH=/usr/local/libexec/vsftpd nosetests ++ ++if 'PYCURL_VSFTPD_PATH' in os.environ: ++ vsftpd_path = os.environ['PYCURL_VSFTPD_PATH'] ++else: ++ vsftpd_path = 'vsftpd' ++ ++def vsftpd_setup(): ++ config_file_path = os.path.join(os.path.dirname(__file__), 'vsftpd.conf') ++ root_path = os.path.join(os.path.dirname(__file__), '..') ++ cmd = [ ++ vsftpd_path, ++ config_file_path, ++ '-oanon_root=%s' % root_path, ++ ] ++ setup_module = start_setup(cmd) ++ def do_setup_module(): ++ setup_module() ++ ok = runwsgi.wait_for_network_service(('127.0.0.1', 8321), 0.1, 10) ++ if not ok: ++ import warnings ++ warnings.warn('vsftpd did not start after 1 second') ++ ++ def teardown_module(): ++ try: ++ manager = managers[str(cmd)] ++ except KeyError: ++ pass ++ else: ++ try: ++ os.kill(manager.process.pid, signal.SIGTERM) ++ except OSError: ++ pass ++ ++ return do_setup_module, teardown_module +diff --git a/tests/runwsgi.py b/tests/runwsgi.py +index 6d3b69f..5217a3f 100644 +--- a/tests/runwsgi.py ++++ b/tests/runwsgi.py +@@ -16,21 +16,25 @@ class Server(bottle.WSGIRefServer): + self.srv = make_server(self.host, self.port, handler, **self.options) + self.srv.serve_forever(poll_interval=0.1) + +-def start_bottle_server(app, port, **kwargs): +- server_thread = ServerThread(app, port, kwargs) +- server_thread.daemon = True +- server_thread.start() +- ++def wait_for_network_service(netloc, check_interval, num_attempts): + ok = False +- for i in range(10): ++ for i in range(num_attempts): + try: +- conn = socket.create_connection(('127.0.0.1', port), 0.1) ++ conn = socket.create_connection(netloc, check_interval) + except socket.error as e: +- _time.sleep(0.1) ++ _time.sleep(check_interval) + else: + conn.close() + ok = True + break ++ return ok ++ ++def start_bottle_server(app, port, **kwargs): ++ server_thread = ServerThread(app, port, kwargs) ++ server_thread.daemon = True ++ server_thread.start() ++ ++ ok = wait_for_network_service(('127.0.0.1', port), 0.1, 10) + if not ok: + import warnings + warnings.warn('Server did not start after 1 second') +diff --git a/tests/vsftpd.conf b/tests/vsftpd.conf +new file mode 100644 +index 0000000..0abb39f +--- /dev/null ++++ b/tests/vsftpd.conf +@@ -0,0 +1,10 @@ ++anon_world_readable_only=yes ++anonymous_enable=yes ++# currently we only list files ++download_enable=no ++listen=yes ++run_as_launching_user=yes ++write_enable=no ++listen_port=8321 ++# should be supplied on command line ++anon_root=/var/empty +-- +1.7.1 + + +From 67495d01da3e8875a9b5882ecdca50c96ee47b15 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:37:19 -0500 +Subject: [PATCH 061/112] Move gtk and xmlrpc tests to examples as they do not test anything not already tested by the test suite + +Signed-off-by: Kamil Dudka +--- + examples/tests/test_gtk.py | 98 +++++++++++++++++++++++++++++++++++++++++ + examples/tests/test_xmlrpc.py | 29 ++++++++++++ + tests/test_gtk.py | 98 ----------------------------------------- + tests/test_xmlrpc.py | 29 ------------ + 4 files changed, 127 insertions(+), 127 deletions(-) + create mode 100644 examples/tests/test_gtk.py + create mode 100644 examples/tests/test_xmlrpc.py + delete mode 100644 tests/test_gtk.py + delete mode 100644 tests/test_xmlrpc.py + +diff --git a/examples/tests/test_gtk.py b/examples/tests/test_gtk.py +new file mode 100644 +index 0000000..7104439 +--- /dev/null ++++ b/examples/tests/test_gtk.py +@@ -0,0 +1,98 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++# $Id$ ++ ++import sys, threading ++import pycurl ++import pygtk ++pygtk.require('2.0') ++import gtk ++ ++# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see ++# the libcurl tutorial for more info. ++try: ++ import signal ++ from signal import SIGPIPE, SIG_IGN ++ signal.signal(signal.SIGPIPE, signal.SIG_IGN) ++except ImportError: ++ pass ++ ++ ++class ProgressBar: ++ def __init__(self, uri): ++ self.round = 0.0 ++ win = gtk.Window(gtk.WINDOW_TOPLEVEL) ++ win.set_title("PycURL progress") ++ win.show() ++ vbox = gtk.VBox(spacing=5) ++ vbox.set_border_width(10) ++ win.add(vbox) ++ win.set_default_size(200, 20) ++ vbox.show() ++ label = gtk.Label("Downloading %s" % uri) ++ label.set_alignment(0, 0.5) ++ vbox.pack_start(label) ++ label.show() ++ pbar = gtk.ProgressBar() ++ pbar.show() ++ self.pbar = pbar ++ vbox.pack_start(pbar) ++ win.connect("destroy", self.close_app) ++ ++ def progress(self, download_t, download_d, upload_t, upload_d): ++ if download_t == 0: ++ self.round = self.round + 0.1 ++ if self.round >= 1.0: self.round = 0.0 ++ else: ++ self.round = float(download_d) / float(download_t) ++ gtk.threads_enter() ++ self.pbar.set_fraction(self.round) ++ gtk.threads_leave() ++ ++ def mainloop(self): ++ gtk.threads_enter() ++ gtk.main() ++ gtk.threads_leave() ++ ++ def close_app(self, *args): ++ args[0].destroy() ++ gtk.main_quit() ++ ++ ++class Test(threading.Thread): ++ def __init__(self, url, target_file, progress): ++ threading.Thread.__init__(self) ++ self.target_file = target_file ++ self.progress = progress ++ self.curl = pycurl.Curl() ++ self.curl.setopt(pycurl.URL, url) ++ self.curl.setopt(pycurl.WRITEDATA, self.target_file) ++ self.curl.setopt(pycurl.FOLLOWLOCATION, 1) ++ self.curl.setopt(pycurl.NOPROGRESS, 0) ++ self.curl.setopt(pycurl.PROGRESSFUNCTION, self.progress) ++ self.curl.setopt(pycurl.MAXREDIRS, 5) ++ self.curl.setopt(pycurl.NOSIGNAL, 1) ++ ++ def run(self): ++ self.curl.perform() ++ self.curl.close() ++ self.target_file.close() ++ self.progress(1.0, 1.0, 0, 0) ++ ++ ++# Check command line args ++if len(sys.argv) < 3: ++ print "Usage: %s " % sys.argv[0] ++ raise SystemExit ++ ++# Make a progress bar window ++p = ProgressBar(sys.argv[1]) ++# Start thread for fetching url ++Test(sys.argv[1], open(sys.argv[2], 'wb'), p.progress).start() ++# Enter the GTK mainloop ++gtk.threads_init() ++try: ++ p.mainloop() ++except KeyboardInterrupt: ++ pass +diff --git a/examples/tests/test_xmlrpc.py b/examples/tests/test_xmlrpc.py +new file mode 100644 +index 0000000..bc5953e +--- /dev/null ++++ b/examples/tests/test_xmlrpc.py +@@ -0,0 +1,29 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++# $Id$ ++ ++## XML-RPC lib included in python2.2 ++import xmlrpclib ++import pycurl ++ ++# Header fields passed in request ++xmlrpc_header = [ ++ "User-Agent: PycURL XML-RPC Test", "Content-Type: text/xml" ++ ] ++ ++# XML-RPC request template ++xmlrpc_template = """ ++%s%s ++""" ++ ++# Engage ++c = pycurl.Curl() ++c.setopt(c.URL, 'http://betty.userland.com/RPC2') ++c.setopt(c.POST, 1) ++c.setopt(c.HTTPHEADER, xmlrpc_header) ++c.setopt(c.POSTFIELDS, xmlrpc_template % ("examples.getStateName", xmlrpclib.dumps((5,)))) ++ ++print 'Response from http://betty.userland.com/' ++c.perform() ++c.close() +diff --git a/tests/test_gtk.py b/tests/test_gtk.py +deleted file mode 100644 +index 7104439..0000000 +--- a/tests/test_gtk.py ++++ /dev/null +@@ -1,98 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import sys, threading +-import pycurl +-import pygtk +-pygtk.require('2.0') +-import gtk +- +-# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see +-# the libcurl tutorial for more info. +-try: +- import signal +- from signal import SIGPIPE, SIG_IGN +- signal.signal(signal.SIGPIPE, signal.SIG_IGN) +-except ImportError: +- pass +- +- +-class ProgressBar: +- def __init__(self, uri): +- self.round = 0.0 +- win = gtk.Window(gtk.WINDOW_TOPLEVEL) +- win.set_title("PycURL progress") +- win.show() +- vbox = gtk.VBox(spacing=5) +- vbox.set_border_width(10) +- win.add(vbox) +- win.set_default_size(200, 20) +- vbox.show() +- label = gtk.Label("Downloading %s" % uri) +- label.set_alignment(0, 0.5) +- vbox.pack_start(label) +- label.show() +- pbar = gtk.ProgressBar() +- pbar.show() +- self.pbar = pbar +- vbox.pack_start(pbar) +- win.connect("destroy", self.close_app) +- +- def progress(self, download_t, download_d, upload_t, upload_d): +- if download_t == 0: +- self.round = self.round + 0.1 +- if self.round >= 1.0: self.round = 0.0 +- else: +- self.round = float(download_d) / float(download_t) +- gtk.threads_enter() +- self.pbar.set_fraction(self.round) +- gtk.threads_leave() +- +- def mainloop(self): +- gtk.threads_enter() +- gtk.main() +- gtk.threads_leave() +- +- def close_app(self, *args): +- args[0].destroy() +- gtk.main_quit() +- +- +-class Test(threading.Thread): +- def __init__(self, url, target_file, progress): +- threading.Thread.__init__(self) +- self.target_file = target_file +- self.progress = progress +- self.curl = pycurl.Curl() +- self.curl.setopt(pycurl.URL, url) +- self.curl.setopt(pycurl.WRITEDATA, self.target_file) +- self.curl.setopt(pycurl.FOLLOWLOCATION, 1) +- self.curl.setopt(pycurl.NOPROGRESS, 0) +- self.curl.setopt(pycurl.PROGRESSFUNCTION, self.progress) +- self.curl.setopt(pycurl.MAXREDIRS, 5) +- self.curl.setopt(pycurl.NOSIGNAL, 1) +- +- def run(self): +- self.curl.perform() +- self.curl.close() +- self.target_file.close() +- self.progress(1.0, 1.0, 0, 0) +- +- +-# Check command line args +-if len(sys.argv) < 3: +- print "Usage: %s " % sys.argv[0] +- raise SystemExit +- +-# Make a progress bar window +-p = ProgressBar(sys.argv[1]) +-# Start thread for fetching url +-Test(sys.argv[1], open(sys.argv[2], 'wb'), p.progress).start() +-# Enter the GTK mainloop +-gtk.threads_init() +-try: +- p.mainloop() +-except KeyboardInterrupt: +- pass +diff --git a/tests/test_xmlrpc.py b/tests/test_xmlrpc.py +deleted file mode 100644 +index bc5953e..0000000 +--- a/tests/test_xmlrpc.py ++++ /dev/null +@@ -1,29 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-## XML-RPC lib included in python2.2 +-import xmlrpclib +-import pycurl +- +-# Header fields passed in request +-xmlrpc_header = [ +- "User-Agent: PycURL XML-RPC Test", "Content-Type: text/xml" +- ] +- +-# XML-RPC request template +-xmlrpc_template = """ +-%s%s +-""" +- +-# Engage +-c = pycurl.Curl() +-c.setopt(c.URL, 'http://betty.userland.com/RPC2') +-c.setopt(c.POST, 1) +-c.setopt(c.HTTPHEADER, xmlrpc_header) +-c.setopt(c.POSTFIELDS, xmlrpc_template % ("examples.getStateName", xmlrpclib.dumps((5,)))) +- +-print 'Response from http://betty.userland.com/' +-c.perform() +-c.close() +-- +1.7.1 + + +From 8519dd18dd06138ed48eebfef966e3740e7737ef Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:37:41 -0500 +Subject: [PATCH 062/112] Delete old tests + +Signed-off-by: Kamil Dudka +--- + tests/test.py | 74 ----------- + tests/test_cb.py | 28 ---- + tests/test_debug.py | 16 --- + tests/test_ftp.py | 13 -- + tests/test_getinfo.py | 49 ------- + tests/test_internals.py | 258 ------------------------------------ + tests/test_memleak.py | 53 -------- + tests/test_multi.py | 33 ----- + tests/test_multi2.py | 72 ---------- + tests/test_multi3.py | 87 ------------ + tests/test_multi4.py | 57 -------- + tests/test_multi5.py | 60 --------- + tests/test_multi6.py | 62 --------- + tests/test_multi_socket.py | 82 ------------ + tests/test_multi_socket_select.py | 105 --------------- + tests/test_multi_timer.py | 76 ----------- + tests/test_multi_vs_thread.py | 262 ------------------------------------- + tests/test_post.py | 24 ---- + tests/test_post2.py | 18 --- + tests/test_post3.py | 32 ----- + tests/test_reset.py | 75 ----------- + tests/test_share.py | 34 ----- + tests/test_socketopen.py | 17 --- + tests/test_stringio.py | 25 ---- + 24 files changed, 0 insertions(+), 1612 deletions(-) + delete mode 100644 tests/test.py + delete mode 100644 tests/test_cb.py + delete mode 100644 tests/test_debug.py + delete mode 100644 tests/test_ftp.py + delete mode 100644 tests/test_getinfo.py + delete mode 100644 tests/test_internals.py + delete mode 100644 tests/test_memleak.py + delete mode 100644 tests/test_multi.py + delete mode 100644 tests/test_multi2.py + delete mode 100644 tests/test_multi3.py + delete mode 100644 tests/test_multi4.py + delete mode 100644 tests/test_multi5.py + delete mode 100644 tests/test_multi6.py + delete mode 100644 tests/test_multi_socket.py + delete mode 100644 tests/test_multi_socket_select.py + delete mode 100644 tests/test_multi_timer.py + delete mode 100644 tests/test_multi_vs_thread.py + delete mode 100644 tests/test_post.py + delete mode 100644 tests/test_post2.py + delete mode 100644 tests/test_post3.py + delete mode 100644 tests/test_reset.py + delete mode 100644 tests/test_share.py + delete mode 100644 tests/test_socketopen.py + delete mode 100644 tests/test_stringio.py + +diff --git a/tests/test.py b/tests/test.py +deleted file mode 100644 +index 5cd9740..0000000 +--- a/tests/test.py ++++ /dev/null +@@ -1,74 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import sys, threading, time +-import pycurl +- +-# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see +-# the libcurl tutorial for more info. +-try: +- import signal +- from signal import SIGPIPE, SIG_IGN +- signal.signal(signal.SIGPIPE, signal.SIG_IGN) +-except ImportError: +- pass +- +- +-class Test(threading.Thread): +- def __init__(self, url, ofile): +- threading.Thread.__init__(self) +- self.curl = pycurl.Curl() +- self.curl.setopt(pycurl.URL, url) +- self.curl.setopt(pycurl.WRITEDATA, ofile) +- self.curl.setopt(pycurl.FOLLOWLOCATION, 1) +- self.curl.setopt(pycurl.MAXREDIRS, 5) +- self.curl.setopt(pycurl.NOSIGNAL, 1) +- +- def run(self): +- self.curl.perform() +- self.curl.close() +- sys.stdout.write(".") +- sys.stdout.flush() +- +- +-# Read list of URIs from file specified on commandline +-try: +- urls = open(sys.argv[1]).readlines() +-except IndexError: +- # No file was specified, show usage string +- print "Usage: %s " % sys.argv[0] +- raise SystemExit +- +-# Initialize thread array and the file number +-threads = [] +-fileno = 0 +- +-# Start one thread per URI in parallel +-t1 = time.time() +-for url in urls: +- f = open(str(fileno), "wb") +- t = Test(url.rstrip(), f) +- t.start() +- threads.append((t, f)) +- fileno = fileno + 1 +-# Wait for all threads to finish +-for thread, file in threads: +- thread.join() +- file.close() +-t2 = time.time() +-print "\n** Multithreading, %d seconds elapsed for %d uris" % (int(t2-t1), len(urls)) +- +-# Start one thread per URI in sequence +-fileno = 0 +-t1 = time.time() +-for url in urls: +- f = open(str(fileno), "wb") +- t = Test(url.rstrip(), f) +- t.start() +- fileno = fileno + 1 +- t.join() +- f.close() +-t2 = time.time() +-print "\n** Singlethreading, %d seconds elapsed for %d uris" % (int(t2-t1), len(urls)) +diff --git a/tests/test_cb.py b/tests/test_cb.py +deleted file mode 100644 +index 1be305c..0000000 +--- a/tests/test_cb.py ++++ /dev/null +@@ -1,28 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import sys +-import pycurl +- +-## Callback function invoked when body data is ready +-def body(buf): +- # Print body data to stdout +- sys.stdout.write(buf) +- +-## Callback function invoked when header data is ready +-def header(buf): +- # Print header data to stderr +- sys.stderr.write(buf) +- +-c = pycurl.Curl() +-c.setopt(pycurl.URL, 'http://www.python.org/') +-c.setopt(pycurl.WRITEFUNCTION, body) +-c.setopt(pycurl.HEADERFUNCTION, header) +-c.setopt(pycurl.FOLLOWLOCATION, 1) +-c.setopt(pycurl.MAXREDIRS, 5) +-c.perform() +-c.setopt(pycurl.URL, 'http://curl.haxx.se/') +-c.perform() +-c.close() +diff --git a/tests/test_debug.py b/tests/test_debug.py +deleted file mode 100644 +index d439b16..0000000 +--- a/tests/test_debug.py ++++ /dev/null +@@ -1,16 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import pycurl +- +-def test(t, b): +- print "debug(%d): %s" % (t, b) +- +-c = pycurl.Curl() +-c.setopt(pycurl.URL, 'http://curl.haxx.se/') +-c.setopt(pycurl.VERBOSE, 1) +-c.setopt(pycurl.DEBUGFUNCTION, test) +-c.perform() +-c.close() +diff --git a/tests/test_ftp.py b/tests/test_ftp.py +deleted file mode 100644 +index 2d4d358..0000000 +--- a/tests/test_ftp.py ++++ /dev/null +@@ -1,13 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import pycurl +- +-c = pycurl.Curl() +-c.setopt(c.URL, 'ftp://ftp.sunet.se/') +-c.setopt(c.FTP_USE_EPSV, 1) +-c.setopt(c.QUOTE, ['cwd pub', 'type i']) +-c.perform() +-c.close() +diff --git a/tests/test_getinfo.py b/tests/test_getinfo.py +deleted file mode 100644 +index ed64594..0000000 +--- a/tests/test_getinfo.py ++++ /dev/null +@@ -1,49 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import time +-import pycurl +- +- +-## Callback function invoked when progress information is updated +-def progress(download_t, download_d, upload_t, upload_d): +- print "Total to download %d bytes, have %d bytes so far" % \ +- (download_t, download_d) +- +-url = "http://www.cnn.com" +- +-print "Starting downloading", url +-print +-f = open("body", "wb") +-h = open("header", "wb") +-c = pycurl.Curl() +-c.setopt(c.URL, url) +-c.setopt(c.WRITEDATA, f) +-c.setopt(c.NOPROGRESS, 0) +-c.setopt(c.PROGRESSFUNCTION, progress) +-c.setopt(c.FOLLOWLOCATION, 1) +-c.setopt(c.MAXREDIRS, 5) +-c.setopt(c.WRITEHEADER, h) +-c.setopt(c.OPT_FILETIME, 1) +-c.perform() +- +-print +-print "HTTP-code:", c.getinfo(c.HTTP_CODE) +-print "Total-time:", c.getinfo(c.TOTAL_TIME) +-print "Download speed: %.2f bytes/second" % c.getinfo(c.SPEED_DOWNLOAD) +-print "Document size: %d bytes" % c.getinfo(c.SIZE_DOWNLOAD) +-print "Effective URL:", c.getinfo(c.EFFECTIVE_URL) +-print "Content-type:", c.getinfo(c.CONTENT_TYPE) +-print "Namelookup-time:", c.getinfo(c.NAMELOOKUP_TIME) +-print "Redirect-time:", c.getinfo(c.REDIRECT_TIME) +-print "Redirect-count:", c.getinfo(c.REDIRECT_COUNT) +-epoch = c.getinfo(c.INFO_FILETIME) +-print "Filetime: %d (%s)" % (epoch, time.ctime(epoch)) +-print +-print "Header is in file 'header', body is in file 'body'" +- +-c.close() +-f.close() +-h.close() +diff --git a/tests/test_internals.py b/tests/test_internals.py +deleted file mode 100644 +index 3f5eefd..0000000 +--- a/tests/test_internals.py ++++ /dev/null +@@ -1,258 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-# +-# a simple self-test +-# +- +-try: +- # need Python 2.2 or better for garbage collection +- from gc import get_objects +- import gc +- del get_objects +- gc.enable() +-except ImportError: +- gc = None +-import copy, os, sys +-from StringIO import StringIO +-try: +- import cPickle +-except ImportError: +- cPickle = None +-try: +- import pickle +-except ImportError: +- pickle = None +- +-# update sys.path when running in the build directory +-from util import get_sys_path +-sys.path = get_sys_path() +- +-import pycurl +-from pycurl import Curl, CurlMulti +- +- +-class opts: +- verbose = 1 +- +-if "-q" in sys.argv: +- opts.verbose = opts.verbose - 1 +- +- +-print "Python", sys.version +-print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM) +-print "PycURL version info", pycurl.version_info() +-print " %s, compiled %s" % (pycurl.__file__, pycurl.COMPILE_DATE) +- +- +-# /*********************************************************************** +-# // test misc +-# ************************************************************************/ +- +-if 1: +- c = Curl() +- assert c.URL is pycurl.URL +- del c +- +- +-# /*********************************************************************** +-# // test handles +-# ************************************************************************/ +- +-# remove an invalid handle: this should fail +-if 1: +- m = CurlMulti() +- c = Curl() +- try: +- m.remove_handle(c) +- except pycurl.error: +- pass +- else: +- assert 0, "internal error" +- del m, c +- +- +-# remove an invalid but closed handle +-if 1: +- m = CurlMulti() +- c = Curl() +- c.close() +- m.remove_handle(c) +- del m, c +- +- +-# add a closed handle: this should fail +-if 1: +- m = CurlMulti() +- c = Curl() +- c.close() +- try: +- m.add_handle(c) +- except pycurl.error: +- pass +- else: +- assert 0, "internal error" +- m.close() +- del m, c +- +- +-# add a handle twice: this should fail +-if 1: +- m = CurlMulti() +- c = Curl() +- m.add_handle(c) +- try: +- m.add_handle(c) +- except pycurl.error: +- pass +- else: +- assert 0, "internal error" +- del m, c +- +- +-# add a handle on multiple stacks: this should fail +-if 1: +- m1 = CurlMulti() +- m2 = CurlMulti() +- c = Curl() +- m1.add_handle(c) +- try: +- m2.add_handle(c) +- except pycurl.error: +- pass +- else: +- assert 0, "internal error" +- del m1, m2, c +- +- +-# move a handle +-if 1: +- m1 = CurlMulti() +- m2 = CurlMulti() +- c = Curl() +- m1.add_handle(c) +- m1.remove_handle(c) +- m2.add_handle(c) +- del m1, m2, c +- +- +-# /*********************************************************************** +-# // test copying and pickling - copying and pickling of +-# // instances of Curl and CurlMulti is not allowed +-# ************************************************************************/ +- +-if 1 and copy: +- c = Curl() +- m = CurlMulti() +- try: +- copy.copy(c) +- except copy.Error: +- pass +- else: +- assert 0, "internal error - copying should fail" +- try: +- copy.copy(m) +- except copy.Error: +- pass +- else: +- assert 0, "internal error - copying should fail" +- +-if 1 and pickle: +- c = Curl() +- m = CurlMulti() +- fp = StringIO() +- p = pickle.Pickler(fp, 1) +- try: +- p.dump(c) +- except pickle.PicklingError: +- pass +- else: +- assert 0, "internal error - pickling should fail" +- try: +- p.dump(m) +- except pickle.PicklingError: +- pass +- else: +- assert 0, "internal error - pickling should fail" +- del c, m, fp, p +- +-if 1 and cPickle: +- c = Curl() +- m = CurlMulti() +- fp = StringIO() +- p = cPickle.Pickler(fp, 1) +- try: +- p.dump(c) +- except cPickle.PicklingError: +- pass +- else: +- assert 0, "internal error - pickling should fail" +- try: +- p.dump(m) +- except cPickle.PicklingError: +- pass +- else: +- assert 0, "internal error - pickling should fail" +- del c, m, fp, p +- +- +-# /*********************************************************************** +-# // test refcounts +-# ************************************************************************/ +- +-# basic check of reference counting (use a memory checker like valgrind) +-if 1: +- c = Curl() +- m = CurlMulti() +- m.add_handle(c) +- del m +- m = CurlMulti() +- c.close() +- del m, c +- +-# basic check of cyclic garbage collection +-if 1 and gc: +- gc.collect() +- c = Curl() +- c.m = CurlMulti() +- c.m.add_handle(c) +- # create some nasty cyclic references +- c.c = c +- c.c.c1 = c +- c.c.c2 = c +- c.c.c3 = c.c +- c.c.c4 = c.m +- c.m.c = c +- c.m.m = c.m +- c.m.c = c +- # delete +- gc.collect() +- flags = gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_OBJECTS +- if opts.verbose >= 1: +- flags = flags | gc.DEBUG_STATS +- gc.set_debug(flags) +- gc.collect() +- ##print gc.get_referrers(c) +- ##print gc.get_objects() +- if opts.verbose >= 1: +- print "Tracked objects:", len(gc.get_objects()) +- # The `del' below should delete these 4 objects: +- # Curl + internal dict, CurlMulti + internal dict +- del c +- gc.collect() +- if opts.verbose >= 1: +- print "Tracked objects:", len(gc.get_objects()) +- +-if 1: +- # Ensure that the refcounting error in "reset" is fixed: +- for i in xrange(100000): +- c = Curl() +- c.reset() +- +-# /*********************************************************************** +-# // done +-# ************************************************************************/ +- +-print "All tests passed." +diff --git a/tests/test_memleak.py b/tests/test_memleak.py +deleted file mode 100644 +index 8577b97..0000000 +--- a/tests/test_memleak.py ++++ /dev/null +@@ -1,53 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-# +-# just a simple self-test +-# need Python 2.2 or better for garbage collection +-# +- +-import gc, pycurl, sys +-gc.enable() +- +- +-print "Python", sys.version +-print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM) +-##print "PycURL version info", pycurl.version_info() +-print " %s, compiled %s" % (pycurl.__file__, pycurl.COMPILE_DATE) +- +- +-gc.collect() +-flags = gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_OBJECTS +-if 1: +- flags = flags | gc.DEBUG_STATS +-gc.set_debug(flags) +-gc.collect() +- +-print "Tracked objects:", len(gc.get_objects()) +- +-multi = pycurl.CurlMulti() +-t = [] +-for a in range(100): +- curl = pycurl.Curl() +- multi.add_handle(curl) +- t.append(curl) +- +-print "Tracked objects:", len(gc.get_objects()) +- +-for curl in t: +- curl.close() +- multi.remove_handle(curl) +- +-print "Tracked objects:", len(gc.get_objects()) +- +-del curl +-del t +-del multi +- +-print "Tracked objects:", len(gc.get_objects()) +-gc.collect() +-print "Tracked objects:", len(gc.get_objects()) +- +- +diff --git a/tests/test_multi.py b/tests/test_multi.py +deleted file mode 100644 +index 9193986..0000000 +--- a/tests/test_multi.py ++++ /dev/null +@@ -1,33 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import pycurl +- +-m = pycurl.CurlMulti() +-m.handles = [] +-c1 = pycurl.Curl() +-c2 = pycurl.Curl() +-c1.setopt(c1.URL, 'http://curl.haxx.se') +-c2.setopt(c2.URL, 'http://cnn.com') +-c2.setopt(c2.FOLLOWLOCATION, 1) +-m.add_handle(c1) +-m.add_handle(c2) +-m.handles.append(c1) +-m.handles.append(c2) +- +-num_handles = len(m.handles) +-while num_handles: +- while 1: +- ret, num_handles = m.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- m.select(1.0) +- +-m.remove_handle(c2) +-m.remove_handle(c1) +-del m.handles +-m.close() +-c1.close() +-c2.close() +diff --git a/tests/test_multi2.py b/tests/test_multi2.py +deleted file mode 100644 +index 4b96789..0000000 +--- a/tests/test_multi2.py ++++ /dev/null +@@ -1,72 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import os, sys +-try: +- from cStringIO import StringIO +-except ImportError: +- from StringIO import StringIO +-import pycurl +- +- +-urls = ( +- "http://curl.haxx.se", +- "http://www.python.org", +- "http://pycurl.sourceforge.net", +- "http://pycurl.sourceforge.net/tests/403_FORBIDDEN", # that actually exists ;-) +- "http://pycurl.sourceforge.net/tests/404_NOT_FOUND", +-) +- +-# Read list of URIs from file specified on commandline +-try: +- urls = open(sys.argv[1], "rb").readlines() +-except IndexError: +- # No file was specified +- pass +- +-# init +-m = pycurl.CurlMulti() +-m.handles = [] +-for url in urls: +- c = pycurl.Curl() +- # save info in standard Python attributes +- c.url = url.rstrip() +- c.body = StringIO() +- c.http_code = -1 +- m.handles.append(c) +- # pycurl API calls +- c.setopt(c.URL, c.url) +- c.setopt(c.WRITEFUNCTION, c.body.write) +- m.add_handle(c) +- +-# get data +-num_handles = len(m.handles) +-while num_handles: +- while 1: +- ret, num_handles = m.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- # currently no more I/O is pending, could do something in the meantime +- # (display a progress bar, etc.) +- m.select(1.0) +- +-# close handles +-for c in m.handles: +- # save info in standard Python attributes +- c.http_code = c.getinfo(c.HTTP_CODE) +- # pycurl API calls +- m.remove_handle(c) +- c.close() +-m.close() +- +-# print result +-for c in m.handles: +- data = c.body.getvalue() +- if 0: +- print "**********", c.url, "**********" +- print data +- else: +- print "%-53s http_code %3d, %6d bytes" % (c.url, c.http_code, len(data)) +- +diff --git a/tests/test_multi3.py b/tests/test_multi3.py +deleted file mode 100644 +index 5b7ea6e..0000000 +--- a/tests/test_multi3.py ++++ /dev/null +@@ -1,87 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-# same as test_multi2.py, but enforce some debugging and strange API-calls +- +-import os, sys +-try: +- from cStringIO import StringIO +-except ImportError: +- from StringIO import StringIO +-import pycurl +- +- +-urls = ( +- "http://curl.haxx.se", +- "http://www.python.org", +- "http://pycurl.sourceforge.net", +- "http://pycurl.sourceforge.net/THIS_HANDLE_IS_CLOSED", +-) +- +-# init +-m = pycurl.CurlMulti() +-m.handles = [] +-for url in urls: +- c = pycurl.Curl() +- # save info in standard Python attributes +- c.url = url +- c.body = StringIO() +- c.http_code = -1 +- c.debug = 0 +- m.handles.append(c) +- # pycurl API calls +- c.setopt(c.URL, c.url) +- c.setopt(c.WRITEFUNCTION, c.body.write) +- m.add_handle(c) +- +-# debug - close a handle +-if 1: +- c = m.handles[3] +- c.debug = 1 +- c.close() +- +-# get data +-num_handles = len(m.handles) +-while num_handles: +- while 1: +- ret, num_handles = m.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- # currently no more I/O is pending, could do something in the meantime +- # (display a progress bar, etc.) +- m.select(1.0) +- +-# close handles +-for c in m.handles: +- # save info in standard Python attributes +- try: +- c.http_code = c.getinfo(c.HTTP_CODE) +- except pycurl.error: +- # handle already closed - see debug above +- assert c.debug +- c.http_code = -1 +- # pycurl API calls +- if 0: +- m.remove_handle(c) +- c.close() +- elif 0: +- # in the C API this is the wrong calling order, but pycurl +- # handles this automatically +- c.close() +- m.remove_handle(c) +- else: +- # actually, remove_handle is called automatically on close +- c.close() +-m.close() +- +-# print result +-for c in m.handles: +- data = c.body.getvalue() +- if 0: +- print "**********", c.url, "**********" +- print data +- else: +- print "%-53s http_code %3d, %6d bytes" % (c.url, c.http_code, len(data)) +- +diff --git a/tests/test_multi4.py b/tests/test_multi4.py +deleted file mode 100644 +index f37ea26..0000000 +--- a/tests/test_multi4.py ++++ /dev/null +@@ -1,57 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import sys, select, time +-import pycurl +- +-c1 = pycurl.Curl() +-c2 = pycurl.Curl() +-c3 = pycurl.Curl() +-c1.setopt(c1.URL, "http://www.python.org") +-c2.setopt(c2.URL, "http://curl.haxx.se") +-c3.setopt(c3.URL, "http://slashdot.org") +-c1.body = open("doc1", "wb") +-c2.body = open("doc2", "wb") +-c3.body = open("doc3", "wb") +-c1.setopt(c1.WRITEFUNCTION, c1.body.write) +-c2.setopt(c2.WRITEFUNCTION, c2.body.write) +-c3.setopt(c3.WRITEFUNCTION, c3.body.write) +- +-m = pycurl.CurlMulti() +-m.add_handle(c1) +-m.add_handle(c2) +-m.add_handle(c3) +- +-# Number of seconds to wait for a timeout to happen +-SELECT_TIMEOUT = 1.0 +- +-# Stir the state machine into action +-while 1: +- ret, num_handles = m.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- +-# Keep going until all the connections have terminated +-while num_handles: +- apply(select.select, m.fdset() + (SELECT_TIMEOUT,)) +- while 1: +- ret, num_handles = m.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- +-# Cleanup +-m.remove_handle(c3) +-m.remove_handle(c2) +-m.remove_handle(c1) +-m.close() +-c1.body.close() +-c2.body.close() +-c3.body.close() +-c1.close() +-c2.close() +-c3.close() +-print "http://www.python.org is in file doc1" +-print "http://curl.haxx.se is in file doc2" +-print "http://slashdot.org is in file doc3" +diff --git a/tests/test_multi5.py b/tests/test_multi5.py +deleted file mode 100644 +index 3f0c8df..0000000 +--- a/tests/test_multi5.py ++++ /dev/null +@@ -1,60 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import sys, select, time +-import pycurl +- +-c1 = pycurl.Curl() +-c2 = pycurl.Curl() +-c3 = pycurl.Curl() +-c1.setopt(c1.URL, "http://www.python.org") +-c2.setopt(c2.URL, "http://curl.haxx.se") +-c3.setopt(c3.URL, "http://slashdot.org") +-c1.body = open("doc1", "wb") +-c2.body = open("doc2", "wb") +-c3.body = open("doc3", "wb") +-c1.setopt(c1.WRITEFUNCTION, c1.body.write) +-c2.setopt(c2.WRITEFUNCTION, c2.body.write) +-c3.setopt(c3.WRITEFUNCTION, c3.body.write) +- +-m = pycurl.CurlMulti() +-m.add_handle(c1) +-m.add_handle(c2) +-m.add_handle(c3) +- +-# Number of seconds to wait for a timeout to happen +-SELECT_TIMEOUT = 1.0 +- +-# Stir the state machine into action +-while 1: +- ret, num_handles = m.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- +-# Keep going until all the connections have terminated +-while num_handles: +- # The select method uses fdset internally to determine which file descriptors +- # to check. +- m.select(SELECT_TIMEOUT) +- while 1: +- ret, num_handles = m.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- +-# Cleanup +-m.remove_handle(c3) +-m.remove_handle(c2) +-m.remove_handle(c1) +-m.close() +-c1.body.close() +-c2.body.close() +-c3.body.close() +-c1.close() +-c2.close() +-c3.close() +-print "http://www.python.org is in file doc1" +-print "http://curl.haxx.se is in file doc2" +-print "http://slashdot.org is in file doc3" +- +diff --git a/tests/test_multi6.py b/tests/test_multi6.py +deleted file mode 100644 +index 35a284f..0000000 +--- a/tests/test_multi6.py ++++ /dev/null +@@ -1,62 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import sys, select, time +-import pycurl +- +-c1 = pycurl.Curl() +-c2 = pycurl.Curl() +-c3 = pycurl.Curl() +-c1.setopt(c1.URL, "http://www.python.org") +-c2.setopt(c2.URL, "http://curl.haxx.se") +-c3.setopt(c3.URL, "http://slashdot.org") +-c1.body = open("doc1", "wb") +-c2.body = open("doc2", "wb") +-c3.body = open("doc3", "wb") +-c1.setopt(c1.WRITEFUNCTION, c1.body.write) +-c2.setopt(c2.WRITEFUNCTION, c2.body.write) +-c3.setopt(c3.WRITEFUNCTION, c3.body.write) +- +-m = pycurl.CurlMulti() +-m.add_handle(c1) +-m.add_handle(c2) +-m.add_handle(c3) +- +-# Number of seconds to wait for a timeout to happen +-SELECT_TIMEOUT = 1.0 +- +-# Stir the state machine into action +-while 1: +- ret, num_handles = m.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- +-# Keep going until all the connections have terminated +-while num_handles: +- # The select method uses fdset internally to determine which file descriptors +- # to check. +- m.select(SELECT_TIMEOUT) +- while 1: +- ret, num_handles = m.perform() +- # Print the message, if any +- print m.info_read(1) +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- +-# Cleanup +-m.remove_handle(c3) +-m.remove_handle(c2) +-m.remove_handle(c1) +-m.close() +-c1.body.close() +-c2.body.close() +-c3.body.close() +-c1.close() +-c2.close() +-c3.close() +-print "http://www.python.org is in file doc1" +-print "http://curl.haxx.se is in file doc2" +-print "http://slashdot.org is in file doc3" +- +diff --git a/tests/test_multi_socket.py b/tests/test_multi_socket.py +deleted file mode 100644 +index 6768061..0000000 +--- a/tests/test_multi_socket.py ++++ /dev/null +@@ -1,82 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import os, sys +-try: +- from cStringIO import StringIO +-except ImportError: +- from StringIO import StringIO +-import pycurl +- +- +-urls = ( +- "http://curl.haxx.se", +- "http://www.python.org", +- "http://pycurl.sourceforge.net", +-) +- +-# Read list of URIs from file specified on commandline +-try: +- urls = open(sys.argv[1], "rb").readlines() +-except IndexError: +- # No file was specified +- pass +- +-# timer callback +-def timer(msecs): +- print 'Timer callback msecs:', msecs +- +-# socket callback +-def socket(event, socket, multi, data): +- print event, socket, multi, data +-# multi.assign(socket, timer) +- +-# init +-m = pycurl.CurlMulti() +-m.setopt(pycurl.M_PIPELINING, 1) +-m.setopt(pycurl.M_TIMERFUNCTION, timer) +-m.setopt(pycurl.M_SOCKETFUNCTION, socket) +-m.handles = [] +-for url in urls: +- c = pycurl.Curl() +- # save info in standard Python attributes +- c.url = url +- c.body = StringIO() +- c.http_code = -1 +- m.handles.append(c) +- # pycurl API calls +- c.setopt(c.URL, c.url) +- c.setopt(c.WRITEFUNCTION, c.body.write) +- m.add_handle(c) +- +-# get data +-num_handles = len(m.handles) +-while num_handles: +- while 1: +- ret, num_handles = m.socket_all() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- # currently no more I/O is pending, could do something in the meantime +- # (display a progress bar, etc.) +- m.select(1.0) +- +-# close handles +-for c in m.handles: +- # save info in standard Python attributes +- c.http_code = c.getinfo(c.HTTP_CODE) +- # pycurl API calls +- m.remove_handle(c) +- c.close() +-m.close() +- +-# print result +-for c in m.handles: +- data = c.body.getvalue() +- if 0: +- print "**********", c.url, "**********" +- print data +- else: +- print "%-53s http_code %3d, %6d bytes" % (c.url, c.http_code, len(data)) +- +diff --git a/tests/test_multi_socket_select.py b/tests/test_multi_socket_select.py +deleted file mode 100644 +index 1f56d1d..0000000 +--- a/tests/test_multi_socket_select.py ++++ /dev/null +@@ -1,105 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import os, sys +-try: +- from cStringIO import StringIO +-except ImportError: +- from StringIO import StringIO +-import pycurl +-import select +- +-sockets = set() +-timeout = 0 +- +-urls = ( +- "http://curl.haxx.se", +- "http://www.python.org", +- "http://pycurl.sourceforge.net", +-) +- +-# Read list of URIs from file specified on commandline +-try: +- urls = open(sys.argv[1], "rb").readlines() +-except IndexError: +- # No file was specified +- pass +- +-# timer callback +-def timer(msecs): +- global timeout +- timeout = msecs +- print 'Timer callback msecs:', msecs +- +-# socket callback +-def socket(event, socket, multi, data): +- if event == pycurl.POLL_REMOVE: +- print "Remove Socket %d"%socket +- sockets.remove(socket) +- else: +- if socket not in sockets: +- print "Add socket %d"%socket +- sockets.add(socket) +- print event, socket, multi, data +- +-# init +-m = pycurl.CurlMulti() +-m.setopt(pycurl.M_PIPELINING, 1) +-m.setopt(pycurl.M_TIMERFUNCTION, timer) +-m.setopt(pycurl.M_SOCKETFUNCTION, socket) +-m.handles = [] +-for url in urls: +- c = pycurl.Curl() +- # save info in standard Python attributes +- c.url = url +- c.body = StringIO() +- c.http_code = -1 +- m.handles.append(c) +- # pycurl API calls +- c.setopt(c.URL, c.url) +- c.setopt(c.WRITEFUNCTION, c.body.write) +- m.add_handle(c) +- +-# get data +-num_handles = len(m.handles) +- +-while (pycurl.E_CALL_MULTI_PERFORM==m.socket_all()[0]): +- pass +- +-timeout = m.timeout() +- +- +-while True: +- (rr, wr, er) = select.select(sockets,sockets,sockets,timeout/1000.0) +- socketSet = set(rr+wr+er) +- if socketSet: +- for s in socketSet: +- while True: +- (ret,running) = m.socket_action(s,0) +- if ret!=pycurl.E_CALL_MULTI_PERFORM: +- break +- else: +- (ret,running) = m.socket_action(pycurl.SOCKET_TIMEOUT,0) +- if running==0: +- break +- +-# close handles +-for c in m.handles: +- # save info in standard Python attributes +- c.http_code = c.getinfo(c.HTTP_CODE) +- # pycurl API calls +- m.remove_handle(c) +- c.close() +-m.close() +- +-# print result +-for c in m.handles: +- data = c.body.getvalue() +- if 0: +- print "**********", c.url, "**********" +- print data +- else: +- print "%-53s http_code %3d, %6d bytes" % (c.url, c.http_code, len(data)) +- +diff --git a/tests/test_multi_timer.py b/tests/test_multi_timer.py +deleted file mode 100644 +index 17371d3..0000000 +--- a/tests/test_multi_timer.py ++++ /dev/null +@@ -1,76 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import os, sys +-try: +- from cStringIO import StringIO +-except ImportError: +- from StringIO import StringIO +-import pycurl +- +- +-urls = ( +- "http://curl.haxx.se", +- "http://www.python.org", +- "http://pycurl.sourceforge.net", +-) +- +-# Read list of URIs from file specified on commandline +-try: +- urls = open(sys.argv[1], "rb").readlines() +-except IndexError: +- # No file was specified +- pass +- +-# timer callback +-def timer(msecs): +- print 'Timer callback msecs:', msecs +- +-# init +-m = pycurl.CurlMulti() +-m.setopt(pycurl.M_PIPELINING, 1) +-m.setopt(pycurl.M_TIMERFUNCTION, timer) +-m.handles = [] +-for url in urls: +- c = pycurl.Curl() +- # save info in standard Python attributes +- c.url = url +- c.body = StringIO() +- c.http_code = -1 +- m.handles.append(c) +- # pycurl API calls +- c.setopt(c.URL, c.url) +- c.setopt(c.WRITEFUNCTION, c.body.write) +- m.add_handle(c) +- +-# get data +-num_handles = len(m.handles) +-while num_handles: +- while 1: +- ret, num_handles = m.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- # currently no more I/O is pending, could do something in the meantime +- # (display a progress bar, etc.) +- m.select(1.0) +- +-# close handles +-for c in m.handles: +- # save info in standard Python attributes +- c.http_code = c.getinfo(c.HTTP_CODE) +- # pycurl API calls +- m.remove_handle(c) +- c.close() +-m.close() +- +-# print result +-for c in m.handles: +- data = c.body.getvalue() +- if 0: +- print "**********", c.url, "**********" +- print data +- else: +- print "%-53s http_code %3d, %6d bytes" % (c.url, c.http_code, len(data)) +- +diff --git a/tests/test_multi_vs_thread.py b/tests/test_multi_vs_thread.py +deleted file mode 100644 +index 0caed60..0000000 +--- a/tests/test_multi_vs_thread.py ++++ /dev/null +@@ -1,262 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import os, sys, time +-from threading import Thread, RLock +-try: +- from cStringIO import StringIO +-except ImportError: +- from StringIO import StringIO +-import pycurl +- +-# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see +-# the libcurl tutorial for more info. +-try: +- import signal +- from signal import SIGPIPE, SIG_IGN +- signal.signal(signal.SIGPIPE, signal.SIG_IGN) +-except ImportError: +- pass +- +-# The conclusion is: the multi interface is fastest! +- +-NUM_PAGES = 30 +-NUM_THREADS = 10 +-assert NUM_PAGES % NUM_THREADS == 0 +- +-##URL = "http://pycurl.sourceforge.net/tests/testgetvars.php?%d" +-URL = "http://pycurl.sourceforge.net/tests/teststaticpage.html?%d" +- +- +-# +-# util +-# +- +-class Curl: +- def __init__(self, url): +- self.url = url +- self.body = StringIO() +- self.http_code = -1 +- # pycurl API calls +- self._curl = pycurl.Curl() +- self._curl.setopt(pycurl.URL, self.url) +- self._curl.setopt(pycurl.WRITEFUNCTION, self.body.write) +- self._curl.setopt(pycurl.NOSIGNAL, 1) +- +- def perform(self): +- self._curl.perform() +- +- def close(self): +- self.http_code = self._curl.getinfo(pycurl.HTTP_CODE) +- self._curl.close() +- +- +-def print_result(items): +- return # DO NOTHING +- # +- for c in items: +- data = c.body.getvalue() +- if 0: +- print "**********", c.url, "**********" +- print data +- elif 1: +- print "%-60s %3d %6d" % (c.url, c.http_code, len(data)) +- +- +-### +-### 1) multi +-### +- +-def test_multi(): +- clock1 = time.time() +- +- # init +- handles = [] +- m = pycurl.CurlMulti() +- for i in range(NUM_PAGES): +- c = Curl(URL %i) +- m.add_handle(c._curl) +- handles.append(c) +- +- clock2 = time.time() +- +- # stir state machine into action +- while 1: +- ret, num_handles = m.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- +- # get data +- while num_handles: +- m.select(1.0) +- while 1: +- ret, num_handles = m.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- +- clock3 = time.time() +- +- # close handles +- for c in handles: +- c.close() +- m.close() +- +- clock4 = time.time() +- print "multi interface: %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1) +- +- # print result +- print_result(handles) +- +- +- +-### +-### 2) thread +-### +- +-class Test(Thread): +- def __init__(self, lock=None): +- Thread.__init__(self) +- self.lock = lock +- self.items = [] +- +- def run(self): +- if self.lock: +- self.lock.acquire() +- self.lock.release() +- for c in self.items: +- c.perform() +- +- +-def test_threads(lock=None): +- clock1 = time.time() +- +- # create and start threads, but block them +- if lock: +- lock.acquire() +- +- # init (FIXME - this is ugly) +- threads = [] +- handles = [] +- t = None +- for i in range(NUM_PAGES): +- if i % (NUM_PAGES / NUM_THREADS) == 0: +- t = Test(lock) +- if lock: +- t.start() +- threads.append(t) +- c = Curl(URL % i) +- t.items.append(c) +- handles.append(c) +- assert len(handles) == NUM_PAGES +- assert len(threads) == NUM_THREADS +- +- clock2 = time.time() +- +- # +- if lock: +- # release lock to let the blocked threads run +- lock.release() +- else: +- # start threads +- for t in threads: +- t.start() +- # wait for threads to finish +- for t in threads: +- t.join() +- +- clock3 = time.time() +- +- # close handles +- for c in handles: +- c.close() +- +- clock4 = time.time() +- if lock: +- print "thread interface [lock]: %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1) +- else: +- print "thread interface: %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1) +- +- # print result +- print_result(handles) +- +- +- +-### +-### 3) thread - threads grab curl objects on demand from a shared pool +-### +- +-class TestPool(Thread): +- def __init__(self, lock, pool): +- Thread.__init__(self) +- self.lock = lock +- self.pool = pool +- +- def run(self): +- while 1: +- self.lock.acquire() +- c = None +- if self.pool: +- c = self.pool.pop() +- self.lock.release() +- if c is None: +- break +- c.perform() +- +- +-def test_thread_pool(lock): +- clock1 = time.time() +- +- # init +- handles = [] +- for i in range(NUM_PAGES): +- c = Curl(URL %i) +- handles.append(c) +- +- # create and start threads, but block them +- lock.acquire() +- threads = [] +- pool = handles[:] # shallow copy of the list, shared for pop() +- for i in range(NUM_THREADS): +- t = TestPool(lock, pool) +- t.start() +- threads.append(t) +- assert len(pool) == NUM_PAGES +- assert len(threads) == NUM_THREADS +- +- clock2 = time.time() +- +- # release lock to let the blocked threads run +- lock.release() +- +- # wait for threads to finish +- for t in threads: +- t.join() +- +- clock3 = time.time() +- +- # close handles +- for c in handles: +- c.close() +- +- clock4 = time.time() +- print "thread interface [pool]: %d pages: perform %5.2f secs, total %5.2f secs" % (NUM_PAGES, clock3 - clock2, clock4 - clock1) +- +- # print result +- print_result(handles) +- +- +- +-lock = RLock() +-if 1: +- test_multi() +- test_threads() +- test_threads(lock) +- test_thread_pool(lock) +-else: +- test_thread_pool(lock) +- test_threads(lock) +- test_threads() +- test_multi() +- +diff --git a/tests/test_post.py b/tests/test_post.py +deleted file mode 100644 +index f0a8ad0..0000000 +--- a/tests/test_post.py ++++ /dev/null +@@ -1,24 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import urllib +-import pycurl +- +-# simple +-pf = {'field1': 'value1'} +- +-# multiple fields +-pf = {'field1':'value1', 'field2':'value2 with blanks', 'field3':'value3'} +- +-# multiple fields with & in field +-pf = {'field1':'value1', 'field2':'value2 with blanks and & chars', +- 'field3':'value3'} +- +-c = pycurl.Curl() +-c.setopt(c.URL, 'http://pycurl.sourceforge.net/tests/testpostvars.php') +-c.setopt(c.POSTFIELDS, urllib.urlencode(pf)) +-c.setopt(c.VERBOSE, 1) +-c.perform() +-c.close() +diff --git a/tests/test_post2.py b/tests/test_post2.py +deleted file mode 100644 +index 74a6eca..0000000 +--- a/tests/test_post2.py ++++ /dev/null +@@ -1,18 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import pycurl +- +-pf = [('field1', 'this is a test using httppost & stuff'), +- ('field2', (pycurl.FORM_FILE, 'test_post.py', pycurl.FORM_FILE, 'test_post2.py')), +- ('field3', (pycurl.FORM_CONTENTS, 'this is wei\000rd, but null-bytes are okay')) +- ] +- +-c = pycurl.Curl() +-c.setopt(c.URL, 'http://www.contactor.se/~dast/postit.cgi') +-c.setopt(c.HTTPPOST, pf) +-c.setopt(c.VERBOSE, 1) +-c.perform() +-c.close() +diff --git a/tests/test_post3.py b/tests/test_post3.py +deleted file mode 100644 +index 617eba2..0000000 +--- a/tests/test_post3.py ++++ /dev/null +@@ -1,32 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import urllib +-POSTSTRING = urllib.urlencode({'field1':'value1', 'field2':'value2 with blanks', 'field3':'value3'}) +- +-class test: +- +- def __init__(self): +- self.finished = False +- +- def read_cb(self, size): +- assert len(POSTSTRING) <= size +- if not self.finished: +- self.finished = True +- return POSTSTRING +- else: +- # Nothing more to read +- return "" +- +-import pycurl +-c = pycurl.Curl() +-t = test() +-c.setopt(c.URL, 'http://pycurl.sourceforge.net/tests/testpostvars.php') +-c.setopt(c.POST, 1) +-c.setopt(c.POSTFIELDSIZE, len(POSTSTRING)) +-c.setopt(c.READFUNCTION, t.read_cb) +-c.setopt(c.VERBOSE, 1) +-c.perform() +-c.close() +diff --git a/tests/test_reset.py b/tests/test_reset.py +deleted file mode 100644 +index 1addcfe..0000000 +--- a/tests/test_reset.py ++++ /dev/null +@@ -1,75 +0,0 @@ +-#!/usr/bin/python +- +-import sys +-import pycurl +- +-saw_error = 1 +- +-def main(): +- global saw_error +- +- pycurl.global_init(pycurl.GLOBAL_DEFAULT) +- +- outf = file("/dev/null", "rb+") +- cm = pycurl.CurlMulti() +- +- # Set multi handle's options +- cm.setopt(pycurl.M_PIPELINING, 1) +- +- eh = pycurl.Curl() +- +- for x in range(1, 20): +- +- eh.setopt(pycurl.WRITEDATA, outf) +- eh.setopt(pycurl.URL, sys.argv[1]) +- cm.add_handle(eh) +- +- while 1: +- ret, active_handles = cm.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- +- while active_handles: +- ret = cm.select(1.0) +- if ret == -1: +- continue +- while 1: +- ret, active_handles = cm.perform() +- if ret != pycurl.E_CALL_MULTI_PERFORM: +- break +- +- count, good, bad = cm.info_read() +- +- for h, en, em in bad: +- print "Transfer to %s failed with %d, %s\n" % \ +- (h.getinfo(pycurl.EFFECTIVE_URL), en, em) +- raise RuntimeError +- +- for h in good: +- httpcode = h.getinfo(pycurl.RESPONSE_CODE) +- if httpcode != 200: +- print "Transfer to %s failed with code %d\n" %\ +- (h.getinfo(pycurl.EFFECTIVE_URL), httpcode) +- raise RuntimeError +- +- else: +- print "Recd %d bytes from %s" % \ +- (h.getinfo(pycurl.SIZE_DOWNLOAD), +- h.getinfo(pycurl.EFFECTIVE_URL)) +- +- cm.remove_handle(eh) +- eh.reset() +- +- eh.close() +- cm.close() +- outf.close() +- +- pycurl.global_cleanup() +- +- +-if __name__ == '__main__': +- if len(sys.argv) != 2: +- print "Usage: %s " % sys.argv[0] +- sys.exit(2) +- main() +- +diff --git a/tests/test_share.py b/tests/test_share.py +deleted file mode 100644 +index 3332cda..0000000 +--- a/tests/test_share.py ++++ /dev/null +@@ -1,34 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import sys +-import pycurl +-import threading +- +-print >>sys.stderr, 'Testing', pycurl.version +- +- +-class Test(threading.Thread): +- +- def __init__(self, share): +- threading.Thread.__init__(self) +- self.curl = pycurl.Curl() +- self.curl.setopt(pycurl.URL, 'http://curl.haxx.se') +- self.curl.setopt(pycurl.SHARE, share) +- +- def run(self): +- self.curl.perform() +- self.curl.close() +- +-s = pycurl.CurlShare() +-s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_COOKIE) +-s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_DNS) +- +-t1 = Test(s) +-t2 = Test(s) +- +-t1.start() +-t2.start() +-del s +diff --git a/tests/test_socketopen.py b/tests/test_socketopen.py +deleted file mode 100644 +index d3f0a62..0000000 +--- a/tests/test_socketopen.py ++++ /dev/null +@@ -1,17 +0,0 @@ +-import pycurl +-import StringIO +-import socket +- +-def socketopen(family, socktype, protocol): +- print family, socktype, protocol +- s = socket.socket(family, socktype, protocol) +- s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) +- return s +- +-sio = StringIO.StringIO() +- +-c = pycurl.Curl() +-c.setopt(pycurl.OPENSOCKETFUNCTION, socketopen) +-c.setopt(pycurl.URL, 'http://camvine.com') +-c.setopt(pycurl.WRITEFUNCTION, sio.write) +-c.perform() +diff --git a/tests/test_stringio.py b/tests/test_stringio.py +deleted file mode 100644 +index 25e639b..0000000 +--- a/tests/test_stringio.py ++++ /dev/null +@@ -1,25 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +-# $Id$ +- +-import sys +-try: +- from cStringIO import StringIO +-except ImportError: +- from StringIO import StringIO +-import pycurl +- +-url = "http://curl.haxx.se/dev/" +- +-print "Testing", pycurl.version +- +-body = StringIO() +-c = pycurl.Curl() +-c.setopt(c.URL, url) +-c.setopt(c.WRITEFUNCTION, body.write) +-c.perform() +-c.close() +- +-contents = body.getvalue() +-print contents +-- +1.7.1 + + +From 217287f1378ef018fae789f748470a170515a3ea Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 13:36:38 -0500 +Subject: [PATCH 063/112] Fix make test to run nosetests (closes #5) + +Signed-off-by: Kamil Dudka +--- + Makefile | 4 +++- + 1 files changed, 3 insertions(+), 1 deletions(-) + +diff --git a/Makefile b/Makefile +index 9b2369d..9475250 100644 +--- a/Makefile ++++ b/Makefile +@@ -7,6 +7,7 @@ SHELL = /bin/sh + + PYTHON = python2.3 + PYTHON = python ++NOSETESTS = nosetests + + all build: + $(PYTHON) setup.py build +@@ -15,7 +16,8 @@ build-7.10.8: + $(PYTHON) setup.py build --curl-config=/home/hosts/localhost/packages/curl-7.10.8/bin/curl-config + + test: build +- $(PYTHON) tests/test_internals.py -q ++ PYTHONPATH=$$(ls -d build/lib.*):$$PYTHONPATH \ ++ $(NOSETESTS) + + # (needs GNU binutils) + strip: build +-- +1.7.1 + + +From dd6cb3db28d4960e5a5902731bd989b918a018ce Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:25:47 -0500 +Subject: [PATCH 064/112] First stab at travis configuration + +Signed-off-by: Kamil Dudka +--- + .travis.yml | 11 +++++++++++ + requirements-dev.txt | 1 + + 2 files changed, 12 insertions(+), 0 deletions(-) + create mode 100644 .travis.yml + create mode 100644 requirements-dev.txt + +diff --git a/.travis.yml b/.travis.yml +new file mode 100644 +index 0000000..2350dfb +--- /dev/null ++++ b/.travis.yml +@@ -0,0 +1,11 @@ ++language: python ++python: ++ - "2.5" ++ - "2.6" ++ - "2.7" ++install: > ++ pip install -r requirements-dev.txt --use-mirrors && ++ sudo apt-get install vsftpd ++script: > ++ export PYCURL_VSFTPD_PATH=/usr/sbin/vsftpd && ++ nosetests +diff --git a/requirements-dev.txt b/requirements-dev.txt +new file mode 100644 +index 0000000..f3c7e8e +--- /dev/null ++++ b/requirements-dev.txt +@@ -0,0 +1 @@ ++nose +-- +1.7.1 + + +From 6d4f146d910400dfcfe9ea758024fbf0a118242f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:37:47 -0500 +Subject: [PATCH 065/112] Building the C module will help + +Signed-off-by: Kamil Dudka +--- + .travis.yml | 2 ++ + 1 files changed, 2 insertions(+), 0 deletions(-) + +diff --git a/.travis.yml b/.travis.yml +index 2350dfb..ae17929 100644 +--- a/.travis.yml ++++ b/.travis.yml +@@ -7,5 +7,7 @@ install: > + pip install -r requirements-dev.txt --use-mirrors && + sudo apt-get install vsftpd + script: > ++ make && ++ export PYTHONPATH=build/lib.* && + export PYCURL_VSFTPD_PATH=/usr/sbin/vsftpd && + nosetests +-- +1.7.1 + + +From 08975482a2e8f09128e261c352866049b1b3b928 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:45:46 -0500 +Subject: [PATCH 066/112] Go through more hoops (/bin/dash in play?) + +Signed-off-by: Kamil Dudka +--- + .travis.yml | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/.travis.yml b/.travis.yml +index ae17929..4afdff9 100644 +--- a/.travis.yml ++++ b/.travis.yml +@@ -8,6 +8,6 @@ install: > + sudo apt-get install vsftpd + script: > + make && +- export PYTHONPATH=build/lib.* && ++ export PYTHONPATH=$(ls -d build/lib.*) && + export PYCURL_VSFTPD_PATH=/usr/sbin/vsftpd && + nosetests +-- +1.7.1 + + +From b55b5ace06a87a7bd1464b3be888a9d71b204a58 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:50:21 -0500 +Subject: [PATCH 067/112] Forgot about bottle + +Signed-off-by: Kamil Dudka +--- + requirements-dev.txt | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + +diff --git a/requirements-dev.txt b/requirements-dev.txt +index f3c7e8e..36b0b24 100644 +--- a/requirements-dev.txt ++++ b/requirements-dev.txt +@@ -1 +1,2 @@ ++bottle + nose +-- +1.7.1 + + +From cbf6fd82722d6db1a6bf8c20b2a95bc04373a31d Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:20:25 -0500 +Subject: [PATCH 068/112] Python 2.5 needs simplejson + +Signed-off-by: Kamil Dudka +--- + .travis.yml | 6 +++++- + requirements-dev-2.5.txt | 2 ++ + 2 files changed, 7 insertions(+), 1 deletions(-) + create mode 100644 requirements-dev-2.5.txt + +diff --git a/.travis.yml b/.travis.yml +index 4afdff9..4268895 100644 +--- a/.travis.yml ++++ b/.travis.yml +@@ -4,7 +4,11 @@ python: + - "2.6" + - "2.7" + install: > +- pip install -r requirements-dev.txt --use-mirrors && ++ if test -e requirements-dev-$TRAVIS_PYTHON_VERSION.txt; then ++ pip install -r requirements-dev-$TRAVIS_PYTHON_VERSION.txt --use-mirrors ++ else ++ pip install -r requirements-dev.txt --use-mirrors ++ fi && + sudo apt-get install vsftpd + script: > + make && +diff --git a/requirements-dev-2.5.txt b/requirements-dev-2.5.txt +new file mode 100644 +index 0000000..52e3460 +--- /dev/null ++++ b/requirements-dev-2.5.txt +@@ -0,0 +1,2 @@ ++-r requirements-dev.txt ++simplejson +-- +1.7.1 + + +From c90d60edd47a2a3ef20b3a09f2c2c3dc59938746 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 7 Mar 2013 04:23:52 -0500 +Subject: [PATCH 069/112] Expand readme (mostly borrowed from @Lispython's fork) + +Signed-off-by: Kamil Dudka +--- + README | 13 -------- + README.rst | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 93 insertions(+), 13 deletions(-) + delete mode 100644 README + create mode 100644 README.rst + +diff --git a/README b/README +deleted file mode 100644 +index 6b3e1d4..0000000 +--- a/README ++++ /dev/null +@@ -1,13 +0,0 @@ +-License +-------- +- +-Copyright (C) 2001-2008 by Kjetil Jacobsen +-Copyright (C) 2001-2008 by Markus F.X.J. Oberhumer +- +-All rights reserved. +- +-PycURL is dual licensed under the LGPL and an MIT/X derivative license +-based on the cURL license. A full copy of the LGPL license is included +-in the file COPYING. A full copy of the MIT/X derivative license is +-included in the file COPYING2. You can redistribute and/or modify PycURL +-according to the terms of either license. +diff --git a/README.rst b/README.rst +new file mode 100644 +index 0000000..3518d9d +--- /dev/null ++++ b/README.rst +@@ -0,0 +1,93 @@ ++PycURL: Python interface to libcurl ++==================================== ++ ++PycURL is a Python interface to `libcurl`_. PycURL can be used to fetch objects ++identified by a URL from a Python program, similar to the `urllib`_ Python module. ++PycURL is mature, very fast, and supports a lot of features. ++ ++Overview ++-------- ++ ++- libcurl is a free and easy-to-use client-side URL transfer library, supporting ++ FTP, FTPS, HTTP, HTTPS, SCP, SFTP, TFTP, TELNET, DICT, LDAP, LDAPS, FILE, IMAP, ++ SMTP, POP3 and RTSP. libcurl supports SSL certificates, HTTP POST, HTTP PUT, ++ FTP uploading, HTTP form based upload, proxies, cookies, user+password ++ authentication (Basic, Digest, NTLM, Negotiate, Kerberos4), file transfer ++ resume, http proxy tunneling and more! ++ ++- libcurl is highly portable, it builds and works identically on numerous ++ platforms, including Solaris, NetBSD, FreeBSD, OpenBSD, Darwin, HPUX, IRIX, ++ AIX, Tru64, Linux, UnixWare, HURD, Windows, Amiga, OS/2, BeOs, Mac OS X, ++ Ultrix, QNX, OpenVMS, RISC OS, Novell NetWare, DOS and more... ++ ++- libcurl is `free`_, `thread-safe`_, `IPv6 compatible`_, `feature rich`_, ++ `well supported`_, `fast`_, `thoroughly documented`_ and is already used by ++ many known, big and successful `companies`_ and numerous `applications`_. ++ ++.. _free: http://curl.haxx.se/docs/copyright.html ++.. _thread-safe: http://curl.haxx.se/libcurl/features.html#thread ++.. _`IPv6 compatible`: http://curl.haxx.se/libcurl/features.html#ipv6 ++.. _`feature rich`: http://curl.haxx.se/libcurl/features.html#features ++.. _`well supported`: http://curl.haxx.se/libcurl/features.html#support ++.. _`fast`: http://curl.haxx.se/libcurl/features.html#fast ++.. _`thoroughly documented`: http://curl.haxx.se/libcurl/features.html#docs ++.. _companies: http://curl.haxx.se/docs/companies.html ++.. _applications: http://curl.haxx.se/libcurl/using/apps.html ++ ++Installation ++------------ ++ ++You can install the most recent PycURL version using `easy_install`_:: ++ ++ easy_install pycurl ++ ++or `pip`_:: ++ ++ pip install pycurl ++ ++ ++.. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall ++.. _pip: http://pypi.python.org/pypi/pip ++ ++Contribute ++---------- ++ ++For smaller changes: ++ ++#. Fork `the repository`_ on Github. ++#. Create a branch off **master**. ++#. Make your changes. ++#. Write a test which shows that the bug was fixed or that the feature ++ works as expected. ++#. Send a pull request. ++ ++For larger changes: ++ ++#. Join the `mailing list`_. ++#. Discuss your proposal on the mailing list. ++#. When consensus is reached, implement it as described above. ++ ++.. image:: https://api.travis-ci.org/p/pycurl.png ++ :target: https://travis-ci.org/p/pycurl ++ ++License ++------- ++ ++:: ++ ++ Copyright (C) 2001-2008 by Kjetil Jacobsen ++ Copyright (C) 2001-2008 by Markus F.X.J. Oberhumer ++ ++ All rights reserved. ++ ++ PycURL is dual licensed under the LGPL and an MIT/X derivative license ++ based on the cURL license. A full copy of the LGPL license is included ++ in the file COPYING. A full copy of the MIT/X derivative license is ++ included in the file COPYING2. You can redistribute and/or modify PycURL ++ according to the terms of either license. ++ ++.. _PycURL: http://pycurl.sourceforge.net/ ++.. _libcurl: http://curl.haxx.se/libcurl/ ++.. _urllib: http://docs.python.org/library/urllib.html ++.. _`the repository`: https://github.com/p/pycurl ++.. _`mailing list`: http://cool.haxx.se/mailman/listinfo/curl-and-python +-- +1.7.1 + + +From 27c1817d13b1773558d5a7b6b327c14c5b4ffdf9 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 7 Mar 2013 04:30:23 -0500 +Subject: [PATCH 070/112] Readme -> readme.rst elsewhere + +Signed-off-by: Kamil Dudka +--- + MANIFEST.in | 2 +- + setup.py | 2 +- + tests/ftp_test.py | 6 +++--- + tests/post_test.py | 4 ++-- + 4 files changed, 7 insertions(+), 7 deletions(-) + +diff --git a/MANIFEST.in b/MANIFEST.in +index 7d5aaf5..11ce1fe 100644 +--- a/MANIFEST.in ++++ b/MANIFEST.in +@@ -8,7 +8,7 @@ include COPYING + include COPYING2 + include INSTALL + include Makefile +-include README ++include README.rst + include TODO + include MANIFEST.in + include src/Makefile +diff --git a/setup.py b/setup.py +index 33704ef..235e4c9 100644 +--- a/setup.py ++++ b/setup.py +@@ -166,7 +166,7 @@ def get_data_files(): + else: + datadir = os.path.join("share", "doc", PACKAGE) + # +- files = ["ChangeLog", "COPYING", "COPYING2", "INSTALL", "README", "TODO",] ++ files = ["ChangeLog", "COPYING", "COPYING2", "INSTALL", "README.rst", "TODO",] + if files: + data_files.append((os.path.join(datadir), files)) + files = glob.glob(os.path.join("doc", "*.html")) +diff --git a/tests/ftp_test.py b/tests/ftp_test.py +index fa2ef79..5ee380c 100644 +--- a/tests/ftp_test.py ++++ b/tests/ftp_test.py +@@ -26,7 +26,7 @@ class FtpTest(unittest.TestCase): + self.curl.perform() + + result = sio.getvalue() +- assert 'README' in result ++ assert 'README.rst' in result + assert 'INSTALL' in result + + # XXX this test needs to be fixed +@@ -38,7 +38,7 @@ class FtpTest(unittest.TestCase): + self.curl.perform() + + result = sio.getvalue() +- assert 'README' not in result ++ assert 'README.rst' not in result + assert 'ftp_test.py' in result + + def test_epsv(self): +@@ -49,5 +49,5 @@ class FtpTest(unittest.TestCase): + self.curl.perform() + + result = sio.getvalue() +- assert 'README' in result ++ assert 'README.rst' in result + assert 'INSTALL' in result +diff --git a/tests/post_test.py b/tests/post_test.py +index e8b0675..6f9d8d4 100644 +--- a/tests/post_test.py ++++ b/tests/post_test.py +@@ -81,7 +81,7 @@ class PostTest(unittest.TestCase): + self.check_post(send, expect, 'http://localhost:8380/postfields') + + def test_post_file(self): +- path = os.path.join(os.path.dirname(__file__), '..', 'README') ++ path = os.path.join(os.path.dirname(__file__), '..', 'README.rst') + with open(path) as f: + contents = f.read() + send = [ +@@ -90,7 +90,7 @@ class PostTest(unittest.TestCase): + ] + expect = [{ + 'name': 'field2', +- 'filename': 'README', ++ 'filename': 'README.rst', + 'data': contents, + }] + self.check_post(send, expect, 'http://localhost:8380/files') +-- +1.7.1 + + +From b3a7a67683458b9b5f7d8a68df533319b6bfbcd6 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:50:34 -0500 +Subject: [PATCH 071/112] Python 2.5 compatibility: with statement + +Signed-off-by: Kamil Dudka +--- + tests/default_write_function_test.py | 2 ++ + tests/post_test.py | 2 ++ + tests/write_to_file_test.py | 2 ++ + 3 files changed, 6 insertions(+), 0 deletions(-) + +diff --git a/tests/default_write_function_test.py b/tests/default_write_function_test.py +index c0d256b..1d31e97 100644 +--- a/tests/default_write_function_test.py ++++ b/tests/default_write_function_test.py +@@ -2,6 +2,8 @@ + # -*- coding: iso-8859-1 -*- + # vi:ts=4:et + ++from __future__ import with_statement ++ + import unittest + import pycurl + import sys +diff --git a/tests/post_test.py b/tests/post_test.py +index 6f9d8d4..804104e 100644 +--- a/tests/post_test.py ++++ b/tests/post_test.py +@@ -2,6 +2,8 @@ + # -*- coding: iso-8859-1 -*- + # vi:ts=4:et + ++from __future__ import with_statement ++ + import os.path + import pycurl + import unittest +diff --git a/tests/write_to_file_test.py b/tests/write_to_file_test.py +index 67c9c63..c3c8822 100644 +--- a/tests/write_to_file_test.py ++++ b/tests/write_to_file_test.py +@@ -2,6 +2,8 @@ + # -*- coding: iso-8859-1 -*- + # vi:ts=4:et + ++from __future__ import with_statement ++ + import unittest + import pycurl + import tempfile +-- +1.7.1 + + +From 370806caa376039f9ba97b9e79e7565b54a4b6f0 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:10:22 -0500 +Subject: [PATCH 072/112] Python 2.5 compatibility: json/simplejson + +Signed-off-by: Kamil Dudka +--- + tests/app.py | 5 ++++- + tests/post_test.py | 5 ++++- + tests/post_with_read_callback_test.py | 5 ++++- + 3 files changed, 12 insertions(+), 3 deletions(-) + +diff --git a/tests/app.py b/tests/app.py +index a83e628..eb10668 100644 +--- a/tests/app.py ++++ b/tests/app.py +@@ -1,5 +1,8 @@ + import bottle +-import json ++try: ++ import json ++except ImportError: ++ import simplejson as json + + app = bottle.Bottle() + +diff --git a/tests/post_test.py b/tests/post_test.py +index 804104e..a36a677 100644 +--- a/tests/post_test.py ++++ b/tests/post_test.py +@@ -8,7 +8,10 @@ import os.path + import pycurl + import unittest + import io +-import json ++try: ++ import json ++except ImportError: ++ import simplejson as json + try: + import urllib.parse as urllib_parse + except ImportError: +diff --git a/tests/post_with_read_callback_test.py b/tests/post_with_read_callback_test.py +index a09e83a..4d8f261 100644 +--- a/tests/post_with_read_callback_test.py ++++ b/tests/post_with_read_callback_test.py +@@ -6,7 +6,10 @@ import os.path + import pycurl + import unittest + import io +-import json ++try: ++ import json ++except ImportError: ++ import simplejson as json + try: + import urllib.parse as urllib_parse + except ImportError: +-- +1.7.1 + + +From d1128e970d6427e16f9eb4909531427a0c799d2e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:18:02 -0500 +Subject: [PATCH 073/112] Python 2.5 compatibility: except as + +Signed-off-by: Kamil Dudka +--- + tests/runwsgi.py | 4 +++- + 1 files changed, 3 insertions(+), 1 deletions(-) + +diff --git a/tests/runwsgi.py b/tests/runwsgi.py +index 5217a3f..8a978ec 100644 +--- a/tests/runwsgi.py ++++ b/tests/runwsgi.py +@@ -1,5 +1,6 @@ + # Run a WSGI application in a daemon thread + ++import sys + import bottle + import threading + import socket +@@ -21,7 +22,8 @@ def wait_for_network_service(netloc, check_interval, num_attempts): + for i in range(num_attempts): + try: + conn = socket.create_connection(netloc, check_interval) +- except socket.error as e: ++ except socket.error: ++ e = sys.exc_info()[1] + _time.sleep(check_interval) + else: + conn.close() +-- +1.7.1 + + +From 64a832f50f7063cce2df5507b69ac2f216e22f59 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:23:08 -0500 +Subject: [PATCH 074/112] Delete unused io imports + +Signed-off-by: Kamil Dudka +--- + tests/default_write_function_test.py | 1 - + tests/header_function_test.py | 1 - + tests/post_test.py | 1 - + tests/post_with_read_callback_test.py | 1 - + tests/write_to_stringio_test.py | 1 - + 5 files changed, 0 insertions(+), 5 deletions(-) + +diff --git a/tests/default_write_function_test.py b/tests/default_write_function_test.py +index 1d31e97..27a3d04 100644 +--- a/tests/default_write_function_test.py ++++ b/tests/default_write_function_test.py +@@ -8,7 +8,6 @@ import unittest + import pycurl + import sys + import tempfile +-import io + import os + + from . import app +diff --git a/tests/header_function_test.py b/tests/header_function_test.py +index bfe7173..00080ba 100644 +--- a/tests/header_function_test.py ++++ b/tests/header_function_test.py +@@ -4,7 +4,6 @@ + + import pycurl + import unittest +-import io + import time as _time + + from . import app +diff --git a/tests/post_test.py b/tests/post_test.py +index a36a677..c11c3c8 100644 +--- a/tests/post_test.py ++++ b/tests/post_test.py +@@ -7,7 +7,6 @@ from __future__ import with_statement + import os.path + import pycurl + import unittest +-import io + try: + import json + except ImportError: +diff --git a/tests/post_with_read_callback_test.py b/tests/post_with_read_callback_test.py +index 4d8f261..f0776ea 100644 +--- a/tests/post_with_read_callback_test.py ++++ b/tests/post_with_read_callback_test.py +@@ -5,7 +5,6 @@ + import os.path + import pycurl + import unittest +-import io + try: + import json + except ImportError: +diff --git a/tests/write_to_stringio_test.py b/tests/write_to_stringio_test.py +index 018800d..e9ab0c7 100644 +--- a/tests/write_to_stringio_test.py ++++ b/tests/write_to_stringio_test.py +@@ -4,7 +4,6 @@ + + import pycurl + import unittest +-import io + + from . import app + from . import runwsgi +-- +1.7.1 + + +From 6c9103dd1769a91967dc9f5d2d0ac3a48fa9528f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:54:35 -0500 +Subject: [PATCH 075/112] Ignore fsync of stdout failures on travis + +Signed-off-by: Kamil Dudka +--- + tests/default_write_function_test.py | 13 +++++++++++-- + 1 files changed, 11 insertions(+), 2 deletions(-) + +diff --git a/tests/default_write_function_test.py b/tests/default_write_function_test.py +index 27a3d04..1c8ec16 100644 +--- a/tests/default_write_function_test.py ++++ b/tests/default_write_function_test.py +@@ -18,6 +18,15 @@ setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) + + STDOUT_FD_NUM = 1 + ++def try_fsync(fd): ++ try: ++ os.fsync(fd) ++ except OSError: ++ # On travis: ++ # OSError: [Errno 22] Invalid argument ++ # ignore ++ pass ++ + class DefaultWriteFunctionTest(unittest.TestCase): + def setUp(self): + self.curl = pycurl.Curl() +@@ -38,7 +47,7 @@ class DefaultWriteFunctionTest(unittest.TestCase): + # If this flush is not done, stdout output bleeds into the next test + # that is executed (without nose output capture) + sys.stdout.flush() +- os.fsync(STDOUT_FD_NUM) ++ try_fsync(STDOUT_FD_NUM) + + # I have a really hard time getting this to work with nose output capture + def skip_perform_get_with_default_write_function(self): +@@ -67,7 +76,7 @@ class DefaultWriteFunctionTest(unittest.TestCase): + self.curl.perform() + sys.stdout.flush() + finally: +- os.fsync(STDOUT_FD_NUM) ++ try_fsync(STDOUT_FD_NUM) + os.dup2(saved_stdout_fd, STDOUT_FD_NUM) + os.close(saved_stdout_fd) + #os.dup2(100, 1) +-- +1.7.1 + + +From 560621974748957f45da26461184ace7bbf05919 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:58:42 -0500 +Subject: [PATCH 076/112] My vps says timeout might be -1 there + +Signed-off-by: Kamil Dudka +--- + tests/multi_socket_select_test.py | 5 +++-- + 1 files changed, 3 insertions(+), 2 deletions(-) + +diff --git a/tests/multi_socket_select_test.py b/tests/multi_socket_select_test.py +index 0c472cf..6db8b44 100644 +--- a/tests/multi_socket_select_test.py ++++ b/tests/multi_socket_select_test.py +@@ -73,8 +73,9 @@ class MultiSocketSelectTest(unittest.TestCase): + + timeout = m.timeout() + +- +- while True: ++ # timeout might be -1, indicating that all work is done ++ # XXX make sure there is always work to be done here? ++ while timeout >= 0: + (rr, wr, er) = select.select(sockets,sockets,sockets,timeout/1000.0) + socketSet = set(rr+wr+er) + if socketSet: +-- +1.7.1 + + +From 9de0de72ea5134d0e3976b0044936ed42e422fc4 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:14:57 -0500 +Subject: [PATCH 077/112] Show pycurl versions in package setup + +Signed-off-by: Kamil Dudka +--- + tests/__init__.py | 4 ++++ + 1 files changed, 4 insertions(+), 0 deletions(-) + +diff --git a/tests/__init__.py b/tests/__init__.py +index e69de29..c1ff976 100644 +--- a/tests/__init__.py ++++ b/tests/__init__.py +@@ -0,0 +1,4 @@ ++import pycurl ++ ++def setup_package(): ++ print('Testing %s' % pycurl.version) +-- +1.7.1 + + +From 37b493365811d2809927163de3809ac80d1bfc9a Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:07:19 -0500 +Subject: [PATCH 078/112] Show what the entries are if assertion fails + +Signed-off-by: Kamil Dudka +--- + tests/debug_test.py | 3 ++- + 1 files changed, 2 insertions(+), 1 deletions(-) + +diff --git a/tests/debug_test.py b/tests/debug_test.py +index 8005239..d74bed9 100644 +--- a/tests/debug_test.py ++++ b/tests/debug_test.py +@@ -46,4 +46,5 @@ class DebugTest(unittest.TestCase): + for t, b in self.debug_entries: + if t == wanted_t and wanted_b in b: + return +- assert False, "%d: %s not found in debug entries" % (wanted_t, wanted_b) ++ assert False, "%d: %s not found in debug entries\nEntries are:\n%s" % \ ++ (wanted_t, wanted_b, repr(self.debug_entries)) +-- +1.7.1 + + +From 54c08110d132bcc1f79d0484154854f7fedbe6d1 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:09:22 -0500 +Subject: [PATCH 079/112] More informative failure message + +Signed-off-by: Kamil Dudka +--- + tests/multi_test.py | 5 ++++- + 1 files changed, 4 insertions(+), 1 deletions(-) + +diff --git a/tests/multi_test.py b/tests/multi_test.py +index d9c6174..8701649 100644 +--- a/tests/multi_test.py ++++ b/tests/multi_test.py +@@ -214,7 +214,10 @@ class MultiTest(unittest.TestCase): + self.assertEqual('success', m.handles[0].body.getvalue()) + self.assertEqual(200, m.handles[0].http_code) + # bottle generated response body +- assert 'Error 403: Forbidden' in m.handles[1].body.getvalue() ++ body = m.handles[1].body.getvalue() ++ search = 'Error 403: Forbidden' ++ if search not in body: ++ assert False, "'%s' not found in body:\n%s" % (search, body) + self.assertEqual(403, m.handles[1].http_code) + # bottle generated response body + self.assertEqual('', m.handles[2].body.getvalue()) +-- +1.7.1 + + +From 8870f4272ba3bb0042168e597a32e3c22b80dc6f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:34:39 -0500 +Subject: [PATCH 080/112] Return complete response bodies for 403 and 404 responses as it looks like exact wording varies between bottle versions + +Signed-off-by: Kamil Dudka +--- + tests/app.py | 4 ++-- + tests/multi_test.py | 9 +++------ + 2 files changed, 5 insertions(+), 8 deletions(-) + +diff --git a/tests/app.py b/tests/app.py +index eb10668..9b56ace 100644 +--- a/tests/app.py ++++ b/tests/app.py +@@ -12,11 +12,11 @@ def ok(): + + @app.route('/status/403') + def forbidden(): +- bottle.abort(403, 'forbidden') ++ return bottle.HTTPResponse('forbidden', 403) + + @app.route('/status/404') + def not_found(): +- bottle.abort(404, 'not found') ++ return bottle.HTTPResponse('not found', 404) + + @app.route('/postfields', method='post') + def postfields(): +diff --git a/tests/multi_test.py b/tests/multi_test.py +index 8701649..d540413 100644 +--- a/tests/multi_test.py ++++ b/tests/multi_test.py +@@ -153,10 +153,10 @@ class MultiTest(unittest.TestCase): + self.assertEqual('success', m.handles[0].body.getvalue()) + self.assertEqual(200, m.handles[0].http_code) + # bottle generated response body +- assert 'Error 403: Forbidden' in m.handles[1].body.getvalue() ++ self.assertEqual('forbidden', m.handles[1].body.getvalue()) + self.assertEqual(403, m.handles[1].http_code) + # bottle generated response body +- assert 'Error 404: Not Found' in m.handles[2].body.getvalue() ++ self.assertEqual('not found', m.handles[2].body.getvalue()) + self.assertEqual(404, m.handles[2].http_code) + + def check_adding_closed_handle(self, close_fn): +@@ -214,10 +214,7 @@ class MultiTest(unittest.TestCase): + self.assertEqual('success', m.handles[0].body.getvalue()) + self.assertEqual(200, m.handles[0].http_code) + # bottle generated response body +- body = m.handles[1].body.getvalue() +- search = 'Error 403: Forbidden' +- if search not in body: +- assert False, "'%s' not found in body:\n%s" % (search, body) ++ self.assertEqual('forbidden', m.handles[1].body.getvalue()) + self.assertEqual(403, m.handles[1].http_code) + # bottle generated response body + self.assertEqual('', m.handles[2].body.getvalue()) +-- +1.7.1 + + +From 567ab2309e67aecda5ba7e46069b1cb85b9bea73 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 22:15:47 -0500 +Subject: [PATCH 081/112] Debug messages originated by curl 7.22 are different + +Signed-off-by: Kamil Dudka +--- + tests/debug_test.py | 6 +++++- + 1 files changed, 5 insertions(+), 1 deletions(-) + +diff --git a/tests/debug_test.py b/tests/debug_test.py +index d74bed9..70e121c 100644 +--- a/tests/debug_test.py ++++ b/tests/debug_test.py +@@ -32,7 +32,11 @@ class DebugTest(unittest.TestCase): + + # Some checks with no particular intent + self.check(0, 'About to connect') +- self.check(0, 'Connected to localhost') ++ version = map(int, pycurl.version_info()[1].split('.')) ++ if version[0] < 7 or version[0] == 7 and version[1] <= 22: ++ self.check(0, 'connected') ++ else: ++ self.check(0, 'Connected to localhost') + self.check(0, 'port 8380') + # request + self.check(2, 'GET /success HTTP/1.1') +-- +1.7.1 + + +From d3e6168efb78f643677693afc607abd9e635dd07 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 22:17:48 -0500 +Subject: [PATCH 082/112] Times in http headers are in UTC + +Signed-off-by: Kamil Dudka +--- + tests/header_function_test.py | 3 ++- + 1 files changed, 2 insertions(+), 1 deletions(-) + +diff --git a/tests/header_function_test.py b/tests/header_function_test.py +index 00080ba..7ca564d 100644 +--- a/tests/header_function_test.py ++++ b/tests/header_function_test.py +@@ -34,7 +34,8 @@ class HeaderFunctionTest(unittest.TestCase): + assert len(self.header_lines) > 0 + self.assertEqual("HTTP/1.0 200 OK\r\n", self.header_lines[0]) + # day of week +- todays_day = _time.strftime('%a') ++ # important: must be in utc ++ todays_day = _time.strftime('%a', _time.gmtime()) + # Date: Sun, 03 Mar 2013 05:38:12 GMT\r\n + self.check('Date: %s' % todays_day) + # Server: WSGIServer/0.1 Python/2.7.3\r\n +-- +1.7.1 + + +From c223f392fde2b40bb5d88763bcfd9e6883de36c0 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 22:26:19 -0500 +Subject: [PATCH 083/112] Create a generalized function for curl version comparisons + +Signed-off-by: Kamil Dudka +--- + tests/util.py | 15 +++++++++++++++ + tests/version_comparison_test.py | 15 +++++++++++++++ + 2 files changed, 30 insertions(+), 0 deletions(-) + create mode 100644 tests/version_comparison_test.py + +diff --git a/tests/util.py b/tests/util.py +index 891da44..46ac59f 100644 +--- a/tests/util.py ++++ b/tests/util.py +@@ -3,6 +3,7 @@ + # $Id$ + + import os, sys ++import pycurl + + try: + from cStringIO import StringIO +@@ -12,6 +13,20 @@ except ImportError: + except ImportError: + from io import StringIO + ++def version_less_than_spec(version_tuple, spec_tuple): ++ # spec_tuple may have 2 elements, expect version_tuple to have 3 elements ++ assert len(version_tuple) >= len(spec_tuple) ++ for i in range(len(spec_tuple)): ++ if version_tuple[i] < spec_tuple[i]: ++ return True ++ if version_tuple[i] > spec_tuple[i]: ++ return False ++ return False ++ ++def pycurl_version_less_than(spec_tuple): ++ version = map(int, pycurl.version_info()[1].split('.')) ++ return version_less_than_spec(version, spec_tuple) ++ + # + # prepare sys.path in case we are still in the build directory + # see also: distutils/command/build.py (build_platlib) +diff --git a/tests/version_comparison_test.py b/tests/version_comparison_test.py +new file mode 100644 +index 0000000..80e780c +--- /dev/null ++++ b/tests/version_comparison_test.py +@@ -0,0 +1,15 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import unittest ++ ++from . import util ++ ++class VersionComparisonTest(unittest.TestCase): ++ def test_comparison(self): ++ assert util.version_less_than_spec((7, 22, 0), (7, 23, 0)) ++ assert util.version_less_than_spec((7, 22, 0), (7, 23)) ++ assert util.version_less_than_spec((7, 22, 0), (7, 22, 1)) ++ assert not util.version_less_than_spec((7, 22, 0), (7, 22, 0)) ++ assert not util.version_less_than_spec((7, 22, 0), (7, 22)) +-- +1.7.1 + + +From e7e7a0f047d88409d9355dab3fab2316b809e5ae Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 22:27:25 -0500 +Subject: [PATCH 084/112] Use version comparison helper in debug test + +Signed-off-by: Kamil Dudka +--- + tests/debug_test.py | 3 +-- + tests/util.py | 4 ++-- + 2 files changed, 3 insertions(+), 4 deletions(-) + +diff --git a/tests/debug_test.py b/tests/debug_test.py +index 70e121c..e81d653 100644 +--- a/tests/debug_test.py ++++ b/tests/debug_test.py +@@ -32,8 +32,7 @@ class DebugTest(unittest.TestCase): + + # Some checks with no particular intent + self.check(0, 'About to connect') +- version = map(int, pycurl.version_info()[1].split('.')) +- if version[0] < 7 or version[0] == 7 and version[1] <= 22: ++ if util.pycurl_version_less_than(7, 24): + self.check(0, 'connected') + else: + self.check(0, 'Connected to localhost') +diff --git a/tests/util.py b/tests/util.py +index 46ac59f..b8e22ec 100644 +--- a/tests/util.py ++++ b/tests/util.py +@@ -23,9 +23,9 @@ def version_less_than_spec(version_tuple, spec_tuple): + return False + return False + +-def pycurl_version_less_than(spec_tuple): ++def pycurl_version_less_than(*spec): + version = map(int, pycurl.version_info()[1].split('.')) +- return version_less_than_spec(version, spec_tuple) ++ return version_less_than_spec(version, spec) + + # + # prepare sys.path in case we are still in the build directory +-- +1.7.1 + + +From ee31a65de12bea5d00f0720eb8b5ec5be796c322 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 22:30:44 -0500 +Subject: [PATCH 085/112] libcurl 7.23.0 produces different results + +Signed-off-by: Kamil Dudka +--- + tests/multi_timer_test.py | 7 +++++-- + 1 files changed, 5 insertions(+), 2 deletions(-) + +diff --git a/tests/multi_timer_test.py b/tests/multi_timer_test.py +index e961780..c4b3df4 100644 +--- a/tests/multi_timer_test.py ++++ b/tests/multi_timer_test.py +@@ -77,8 +77,11 @@ class MultiSocketTest(unittest.TestCase): + self.assertEqual(200, c.http_code) + + assert len(timers) > 0 +- assert timers[0] > 0 +- self.assertEqual(-1, timers[-1]) ++ # libcurl 7.23.0 produces a 0 timer ++ assert timers[0] >= 0 ++ # this assertion does not appear to hold on older libcurls ++ if not util.pycurl_version_less_than(7, 24): ++ self.assertEqual(-1, timers[-1]) + + # close handles + for c in m.handles: +-- +1.7.1 + + +From f510b505c4e497904e3591696c7d8985baea409b Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 7 Mar 2013 00:16:21 -0500 +Subject: [PATCH 086/112] Change memleak test to use id() rather than regexp match of addresses (closes #12) + +This commit also fixes the test to check objects that GC tracks +rather than the object type repeatedly. + +Signed-off-by: Kamil Dudka +--- + tests/memleak_test.py | 16 ++++++---------- + 1 files changed, 6 insertions(+), 10 deletions(-) + +diff --git a/tests/memleak_test.py b/tests/memleak_test.py +index 6e9f76c..1b1bbd5 100644 +--- a/tests/memleak_test.py ++++ b/tests/memleak_test.py +@@ -5,12 +5,9 @@ + import pycurl + import unittest + import gc +-import re + + class MemleakTest(unittest.TestCase): + def test_collection(self): +- regexp = re.compile(r'at (0x\d+)') +- + gc.collect() + flags = gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE + # python 3 has no DEBUG_OBJECTS +@@ -31,12 +28,10 @@ class MemleakTest(unittest.TestCase): + multi.add_handle(curl) + t.append(curl) + +- match = regexp.search(repr(curl)) +- assert match is not None +- searches.append(match.group(1)) +- match = regexp.search(repr(multi)) +- assert match +- searches.append(match.group(1)) ++ c_id = id(curl) ++ searches.append(c_id) ++ m_id = id(multi) ++ searches.append(m_id) + + #print("Tracked objects:", len(gc.get_objects())) + +@@ -56,4 +51,5 @@ class MemleakTest(unittest.TestCase): + + objects = gc.get_objects() + for search in searches: +- assert 'at %s' % search not in repr(object) ++ for object in objects: ++ assert search != id(object) +-- +1.7.1 + + +From 70206193e5219fac1da3e3d27167ed070fdff7b9 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 22:19:14 -0400 +Subject: [PATCH 087/112] More informative exception message when vsftpd is missing or not in PATH (closes #6) + +Signed-off-by: Kamil Dudka +--- + tests/procmgr.py | 12 +++++++++++- + 1 files changed, 11 insertions(+), 1 deletions(-) + +diff --git a/tests/procmgr.py b/tests/procmgr.py +index 8d5c0cc..ce08da9 100644 +--- a/tests/procmgr.py ++++ b/tests/procmgr.py +@@ -1,6 +1,7 @@ + import threading + import subprocess + import os ++import sys + import signal + + from . import runwsgi +@@ -53,7 +54,16 @@ def vsftpd_setup(): + ] + setup_module = start_setup(cmd) + def do_setup_module(): +- setup_module() ++ try: ++ setup_module() ++ except OSError: ++ import errno ++ e = sys.exc_info()[1] ++ if e.errno == errno.ENOENT: ++ msg = "Tried to execute `%s`\nTry specifying path to vsftpd via PYCURL_VSFTPD_PATH environment variable\n" % vsftpd_path ++ raise OSError(e.errno, e.strerror + "\n" + msg) ++ else: ++ raise + ok = runwsgi.wait_for_network_service(('127.0.0.1', 8321), 0.1, 10) + if not ok: + import warnings +-- +1.7.1 + + +From c42d5555e33105b8ea2317e63ac4b80b0c152be9 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 22:22:02 -0400 +Subject: [PATCH 088/112] Delete -1 timer assertion from multi timer test (closes #19) + +Signed-off-by: Kamil Dudka +--- + tests/multi_timer_test.py | 6 ++++-- + 1 files changed, 4 insertions(+), 2 deletions(-) + +diff --git a/tests/multi_timer_test.py b/tests/multi_timer_test.py +index c4b3df4..ff856d7 100644 +--- a/tests/multi_timer_test.py ++++ b/tests/multi_timer_test.py +@@ -80,8 +80,10 @@ class MultiSocketTest(unittest.TestCase): + # libcurl 7.23.0 produces a 0 timer + assert timers[0] >= 0 + # this assertion does not appear to hold on older libcurls +- if not util.pycurl_version_less_than(7, 24): +- self.assertEqual(-1, timers[-1]) ++ # or apparently on any linuxes, see ++ # https://github.com/p/pycurl/issues/19 ++ #if not util.pycurl_version_less_than(7, 24): ++ # self.assertEqual(-1, timers[-1]) + + # close handles + for c in m.handles: +-- +1.7.1 + + +From ccc7c66b76cef1ad3a00448d08f4aec7e21eaf69 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Mon, 11 Mar 2013 14:00:04 +0100 +Subject: [PATCH 089/112] remove .cvsignore files, add *.pyc and *.pyo to .gitignore + +Signed-off-by: Kamil Dudka +--- + .gitignore | 2 ++ + 1 files changed, 2 insertions(+), 0 deletions(-) + +diff --git a/.gitignore b/.gitignore +index 796b96d..899daba 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1 +1,3 @@ ++*.pyc ++*.pyo + /build +-- +1.7.1 + + +From bed41e852cb37b23b2cfc4513b701f3463a97c86 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 22:40:12 -0400 +Subject: [PATCH 090/112] Readd cvsignore contents and empty directories + +Signed-off-by: Kamil Dudka +--- + .gitignore | 6 ++++++ + 1 files changed, 6 insertions(+), 0 deletions(-) + +diff --git a/.gitignore b/.gitignore +index 899daba..c873e32 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,9 @@ + *.pyc + *.pyo ++/MANIFEST + /build ++/dist ++/www/htdocs/download/*.bz2 ++/www/htdocs/download/*.exe ++/www/htdocs/download/*.gz ++/www/upload/* +-- +1.7.1 + + +From 49e560395f55efa3bb9ec09f44b5c893f0571404 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Mon, 11 Mar 2013 14:03:06 +0100 +Subject: [PATCH 091/112] vsftpd.conf: add background=no to allow for proper shutdown + +When the "background" directive in the vsftpd.conf configuration file is +set to "YES", the vsftpd startup script forks, creating a child process +(the vsftpd daemon) which immediately sends the SIGUSR1 signal to its +parent process, which exits upon receiving it. The teardown routine in +procmgr.py would then send the SIGTERM signal to a process that does not +exist anymore. + +Signed-off-by: Kamil Dudka +--- + tests/vsftpd.conf | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + +diff --git a/tests/vsftpd.conf b/tests/vsftpd.conf +index 0abb39f..b4e4972 100644 +--- a/tests/vsftpd.conf ++++ b/tests/vsftpd.conf +@@ -1,5 +1,6 @@ + anon_world_readable_only=yes + anonymous_enable=yes ++background=no + # currently we only list files + download_enable=no + listen=yes +-- +1.7.1 + + +From 2d1e91297f6d56286b9172090e6c16d9e509de0a Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Tue, 26 Feb 2013 14:49:47 +0100 +Subject: [PATCH 092/112] pycurl.c: eliminate duplicated code in util_write_callback() + +Suggested by Zdenek Pavlas . + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 10 +--------- + 1 files changed, 1 insertions(+), 9 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index 094bc60..f701543 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -1082,15 +1082,7 @@ util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *strea + if (result == Py_None) { + ret = total_size; /* None means success */ + } +- else if (PyInt_Check(result)) { +- long obj_size = PyInt_AsLong(result); +- if (obj_size < 0 || obj_size > total_size) { +- PyErr_Format(ErrorObject, "invalid return value for write callback %ld %ld", (long)obj_size, (long)total_size); +- goto verbose_error; +- } +- ret = (size_t) obj_size; /* success */ +- } +- else if (PyLong_Check(result)) { ++ else if (PyInt_Check(result) || PyLong_Check(result)) { + long obj_size = PyLong_AsLong(result); + if (obj_size < 0 || obj_size > total_size) { + PyErr_Format(ErrorObject, "invalid return value for write callback %ld %ld", (long)obj_size, (long)total_size); +-- +1.7.1 + + +From a3ab87495c91bf9b88ed5b8e8b4369d7adab96c2 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Tue, 26 Feb 2013 16:58:55 +0100 +Subject: [PATCH 093/112] pycurl.c: allow to return -1 from write callback + +... to abort the transfer and WRITEFUNC_PAUSE to pause the transfer + +Reported By: Zdenek Pavlas +Bug: https://bugzilla.redhat.com/857875 + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 11 +++++------ + 1 files changed, 5 insertions(+), 6 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index f701543..a30c339 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -1083,12 +1083,8 @@ util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *strea + ret = total_size; /* None means success */ + } + else if (PyInt_Check(result) || PyLong_Check(result)) { +- long obj_size = PyLong_AsLong(result); +- if (obj_size < 0 || obj_size > total_size) { +- PyErr_Format(ErrorObject, "invalid return value for write callback %ld %ld", (long)obj_size, (long)total_size); +- goto verbose_error; +- } +- ret = (size_t) obj_size; /* success */ ++ /* if the cast to long fails, PyLong_AsLong() returns -1L */ ++ ret = (size_t) PyLong_AsLong(result); + } + else { + PyErr_SetString(ErrorObject, "write callback must return int or None"); +@@ -3509,6 +3505,9 @@ initpycurl(void) + /* Abort curl_read_callback(). */ + insint_c(d, "READFUNC_ABORT", CURL_READFUNC_ABORT); + ++ /* Pause curl_write_callback(). */ ++ insint_c(d, "WRITEFUNC_PAUSE", CURL_WRITEFUNC_PAUSE); ++ + /* constants for ioctl callback return values */ + insint_c(d, "IOE_OK", CURLIOE_OK); + insint_c(d, "IOE_UNKNOWNCMD", CURLIOE_UNKNOWNCMD); +-- +1.7.1 + + +From 1ca0c365f45526d625fe79475c74b92818e9889d Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Wed, 6 Mar 2013 14:38:01 +0100 +Subject: [PATCH 094/112] write_abort_test.py: test returning -1 from write callback + +Signed-off-by: Kamil Dudka +--- + tests/write_abort_test.py | 35 +++++++++++++++++++++++++++++++++++ + 1 files changed, 35 insertions(+), 0 deletions(-) + create mode 100755 tests/write_abort_test.py + +diff --git a/tests/write_abort_test.py b/tests/write_abort_test.py +new file mode 100755 +index 0000000..73e8245 +--- /dev/null ++++ b/tests/write_abort_test.py +@@ -0,0 +1,35 @@ ++#!/usr/bin/python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import os.path ++import pycurl ++import sys ++import unittest ++ ++class WriteAbortTest(unittest.TestCase): ++ def setUp(self): ++ pycurl.global_init(pycurl.GLOBAL_DEFAULT) ++ ++ def tearDown(self): ++ pycurl.global_cleanup() ++ ++ def test_write_abort(self): ++ def write_cb(_): ++ # this should cause pycurl.WRITEFUNCTION (without any range errors) ++ return -1 ++ ++ # download the script itself through the file:// protocol into write_cb ++ c = pycurl.Curl() ++ c.setopt(pycurl.URL, 'file://' + os.path.abspath(sys.argv[0])) ++ c.setopt(pycurl.WRITEFUNCTION, write_cb) ++ try: ++ c.perform() ++ except pycurl.error, (err, msg): ++ # we expect pycurl.E_WRITE_ERROR as the response ++ assert pycurl.E_WRITE_ERROR == err ++ ++ # no additional errors should be reported ++ assert not hasattr(sys, 'last_value') ++ ++ c.close() +-- +1.7.1 + + +From 671bb961f3c16821684ac92f1ae6de9138b92d0f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 23:48:10 -0400 +Subject: [PATCH 095/112] setup/teardown consistent with other tests + +Signed-off-by: Kamil Dudka +--- + tests/write_abort_test.py | 13 +++++-------- + 1 files changed, 5 insertions(+), 8 deletions(-) + +diff --git a/tests/write_abort_test.py b/tests/write_abort_test.py +index 73e8245..f720882 100755 +--- a/tests/write_abort_test.py ++++ b/tests/write_abort_test.py +@@ -9,10 +9,10 @@ import unittest + + class WriteAbortTest(unittest.TestCase): + def setUp(self): +- pycurl.global_init(pycurl.GLOBAL_DEFAULT) ++ self.curl = pycurl.Curl() + + def tearDown(self): +- pycurl.global_cleanup() ++ self.curl.close() + + def test_write_abort(self): + def write_cb(_): +@@ -20,16 +20,13 @@ class WriteAbortTest(unittest.TestCase): + return -1 + + # download the script itself through the file:// protocol into write_cb +- c = pycurl.Curl() +- c.setopt(pycurl.URL, 'file://' + os.path.abspath(sys.argv[0])) +- c.setopt(pycurl.WRITEFUNCTION, write_cb) ++ self.curl.setopt(pycurl.URL, 'file://' + os.path.abspath(sys.argv[0])) ++ self.curl.setopt(pycurl.WRITEFUNCTION, write_cb) + try: +- c.perform() ++ self.curl.perform() + except pycurl.error, (err, msg): + # we expect pycurl.E_WRITE_ERROR as the response + assert pycurl.E_WRITE_ERROR == err + + # no additional errors should be reported + assert not hasattr(sys, 'last_value') +- +- c.close() +-- +1.7.1 + + +From 0ddb01d99c34fa5ae7581697edeb2de49decc442 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 23:48:41 -0400 +Subject: [PATCH 096/112] Correct shebang + +Signed-off-by: Kamil Dudka +--- + tests/write_abort_test.py | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/tests/write_abort_test.py b/tests/write_abort_test.py +index f720882..3272961 100755 +--- a/tests/write_abort_test.py ++++ b/tests/write_abort_test.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/python ++#! /usr/bin/env python + # -*- coding: iso-8859-1 -*- + # vi:ts=4:et + +-- +1.7.1 + + +From 165d6e5e332a20ceca178b105aa165aa6873b662 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 23:56:36 -0400 +Subject: [PATCH 097/112] Check that bogus return values from write callback are correctly handled (still) + +Signed-off-by: Kamil Dudka +--- + tests/write_cb_bogus_test.py | 44 ++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 44 insertions(+), 0 deletions(-) + create mode 100644 tests/write_cb_bogus_test.py + +diff --git a/tests/write_cb_bogus_test.py b/tests/write_cb_bogus_test.py +new file mode 100644 +index 0000000..4bec2ad +--- /dev/null ++++ b/tests/write_cb_bogus_test.py +@@ -0,0 +1,44 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import os.path ++import pycurl ++import sys ++import unittest ++ ++class WriteAbortTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def write_cb_returning_string(self, data): ++ return 'foo' ++ ++ def write_cb_returning_float(self, data): ++ return 0.5 ++ ++ def test_write_cb_returning_string(self): ++ self.check(self.write_cb_returning_string) ++ ++ def test_write_cb_returning_float(self): ++ self.check(self.write_cb_returning_float) ++ ++ def check(self, write_cb): ++ # download the script itself through the file:// protocol into write_cb ++ c = pycurl.Curl() ++ self.curl.setopt(pycurl.URL, 'file://' + os.path.abspath(sys.argv[0])) ++ self.curl.setopt(pycurl.WRITEFUNCTION, write_cb) ++ try: ++ self.curl.perform() ++ except pycurl.error, (err, msg): ++ # we expect pycurl.E_WRITE_ERROR as the response ++ assert pycurl.E_WRITE_ERROR == err ++ ++ # actual error ++ assert hasattr(sys, 'last_type') ++ self.assertEqual(pycurl.error, sys.last_type) ++ assert hasattr(sys, 'last_value') ++ self.assertEqual('write callback must return int or None', str(sys.last_value)) +-- +1.7.1 + + +From cafe4e74ebd12a2c571319e0cfeb6d64f48abe08 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 12 Mar 2013 00:00:40 -0400 +Subject: [PATCH 098/112] Fix mode on write abort test + +Signed-off-by: Kamil Dudka +--- + 0 files changed, 0 insertions(+), 0 deletions(-) + mode change 100755 => 100644 tests/write_abort_test.py + +diff --git a/tests/write_abort_test.py b/tests/write_abort_test.py +old mode 100755 +new mode 100644 +-- +1.7.1 + + +From c84e139df96307aff908f69949cbe123fc40a14f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 12 Mar 2013 00:02:56 -0400 +Subject: [PATCH 099/112] This test fails intermittently, add diagnostics + +Signed-off-by: Kamil Dudka +--- + tests/multi_socket_select_test.py | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/tests/multi_socket_select_test.py b/tests/multi_socket_select_test.py +index 6db8b44..9546169 100644 +--- a/tests/multi_socket_select_test.py ++++ b/tests/multi_socket_select_test.py +@@ -94,7 +94,7 @@ class MultiSocketSelectTest(unittest.TestCase): + c.http_code = c.getinfo(c.HTTP_CODE) + + # at least in and remove events per socket +- assert len(socket_events) >= 6 ++ assert len(socket_events) >= 6, 'Less than 6 socket events: %s' % repr(socket_events) + + # print result + for c in m.handles: +-- +1.7.1 + + +From 47db8853813888148a4bc91754de8a83de4e8c0b Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 12 Mar 2013 22:43:47 -0400 +Subject: [PATCH 100/112] Python 2.5 compatibility: socket.create_connection + +Signed-off-by: Kamil Dudka +--- + tests/runwsgi.py | 14 +++++++++++++- + 1 files changed, 13 insertions(+), 1 deletions(-) + +diff --git a/tests/runwsgi.py b/tests/runwsgi.py +index 8a978ec..be05dfa 100644 +--- a/tests/runwsgi.py ++++ b/tests/runwsgi.py +@@ -6,6 +6,18 @@ import threading + import socket + import time as _time + ++try: ++ create_connection = socket.create_connection ++except AttributeError: ++ # python 2.5 ++ def create_connection(netloc, timeout=None): ++ # XXX ipv4 only ++ s = socket.socket() ++ if timeout is not None: ++ s.settimeout(timeout) ++ s.connect(netloc) ++ return s ++ + class Server(bottle.WSGIRefServer): + def run(self, handler): # pragma: no cover + from wsgiref.simple_server import make_server, WSGIRequestHandler +@@ -21,7 +33,7 @@ def wait_for_network_service(netloc, check_interval, num_attempts): + ok = False + for i in range(num_attempts): + try: +- conn = socket.create_connection(netloc, check_interval) ++ conn = create_connection(netloc, check_interval) + except socket.error: + e = sys.exc_info()[1] + _time.sleep(check_interval) +-- +1.7.1 + + +From 94f689b61e1675f3ab9b4cc74ad53ae960d0a1b0 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 12 Mar 2013 22:55:08 -0400 +Subject: [PATCH 101/112] Python 2.5 compatibility: poll_interval in SocketServer + +Signed-off-by: Kamil Dudka +--- + tests/runwsgi.py | 17 +++++++++++++++-- + 1 files changed, 15 insertions(+), 2 deletions(-) + +diff --git a/tests/runwsgi.py b/tests/runwsgi.py +index be05dfa..114ce42 100644 +--- a/tests/runwsgi.py ++++ b/tests/runwsgi.py +@@ -18,6 +18,8 @@ except AttributeError: + s.connect(netloc) + return s + ++global_stop = False ++ + class Server(bottle.WSGIRefServer): + def run(self, handler): # pragma: no cover + from wsgiref.simple_server import make_server, WSGIRequestHandler +@@ -27,7 +29,13 @@ class Server(bottle.WSGIRefServer): + def log_request(*args, **kw): pass + self.options['handler_class'] = QuietHandler + self.srv = make_server(self.host, self.port, handler, **self.options) +- self.srv.serve_forever(poll_interval=0.1) ++ if sys.version_info[0] == 2 and sys.version_info[1] < 6: ++ # python 2.5 has no poll_interval ++ # and thus no way to stop the server ++ while not global_stop: ++ self.srv.handle_request() ++ else: ++ self.srv.serve_forever(poll_interval=0.1) + + def wait_for_network_service(netloc, check_interval, num_attempts): + ok = False +@@ -104,6 +112,11 @@ def app_runner_setup(*specs): + for server in self.servers: + # if no tests from module were run, there is no server to shut down + if hasattr(server, 'srv'): +- server.srv.shutdown() ++ if hasattr(server.srv, 'shutdown'): ++ server.srv.shutdown() ++ else: ++ # python 2.5 ++ global global_stop ++ global_stop = True + + return [setup, teardown] +-- +1.7.1 + + +From d8dda1c31521d338b9119dd679c25257232a8656 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 15 Mar 2013 12:28:11 -0400 +Subject: [PATCH 102/112] Tests do not call global cleanup for now + +Signed-off-by: Kamil Dudka +--- + tests/reset_test.py | 2 -- + 1 files changed, 0 insertions(+), 2 deletions(-) + +diff --git a/tests/reset_test.py b/tests/reset_test.py +index cc55f86..61a692a 100644 +--- a/tests/reset_test.py ++++ b/tests/reset_test.py +@@ -72,5 +72,3 @@ class ResetTest(unittest.TestCase): + eh.close() + cm.close() + outf.close() +- +- pycurl.global_cleanup() +-- +1.7.1 + + +From 8e307f81488c66a02516204cb7566f159b065371 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 15 Mar 2013 17:01:35 -0400 +Subject: [PATCH 103/112] Python 3 compatibility: except syntax + +Signed-off-by: Kamil Dudka +--- + tests/write_abort_test.py | 3 ++- + tests/write_cb_bogus_test.py | 3 ++- + 2 files changed, 4 insertions(+), 2 deletions(-) + +diff --git a/tests/write_abort_test.py b/tests/write_abort_test.py +index 3272961..957fe78 100644 +--- a/tests/write_abort_test.py ++++ b/tests/write_abort_test.py +@@ -24,7 +24,8 @@ class WriteAbortTest(unittest.TestCase): + self.curl.setopt(pycurl.WRITEFUNCTION, write_cb) + try: + self.curl.perform() +- except pycurl.error, (err, msg): ++ except pycurl.error: ++ err, msg = sys.exc_info()[1] + # we expect pycurl.E_WRITE_ERROR as the response + assert pycurl.E_WRITE_ERROR == err + +diff --git a/tests/write_cb_bogus_test.py b/tests/write_cb_bogus_test.py +index 4bec2ad..ef709db 100644 +--- a/tests/write_cb_bogus_test.py ++++ b/tests/write_cb_bogus_test.py +@@ -33,7 +33,8 @@ class WriteAbortTest(unittest.TestCase): + self.curl.setopt(pycurl.WRITEFUNCTION, write_cb) + try: + self.curl.perform() +- except pycurl.error, (err, msg): ++ except pycurl.error: ++ err, msg = sys.exc_info()[1] + # we expect pycurl.E_WRITE_ERROR as the response + assert pycurl.E_WRITE_ERROR == err + +-- +1.7.1 + + +From f1674b26ef4094d20c46dbfc0cb7ae3026fce525 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 15 Mar 2013 21:21:50 -0400 +Subject: [PATCH 104/112] Python 3 compatibility: map + +Signed-off-by: Kamil Dudka +--- + tests/util.py | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/tests/util.py b/tests/util.py +index b8e22ec..1e9f406 100644 +--- a/tests/util.py ++++ b/tests/util.py +@@ -24,7 +24,7 @@ def version_less_than_spec(version_tuple, spec_tuple): + return False + + def pycurl_version_less_than(*spec): +- version = map(int, pycurl.version_info()[1].split('.')) ++ version = [int(part) for part in pycurl.version_info()[1].split('.')] + return version_less_than_spec(version, spec) + + # +-- +1.7.1 + + +From 5797557b27a19c247967f8fc3cf8b1068139fecf Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 15 Mar 2013 21:27:58 -0400 +Subject: [PATCH 105/112] Python 3 compatibility: apply no longer exists + +Signed-off-by: Kamil Dudka +--- + python/curl/__init__.py | 4 ++-- + setup.py | 3 +-- + setup_win32_ssl.py | 2 +- + 3 files changed, 4 insertions(+), 5 deletions(-) + +diff --git a/python/curl/__init__.py b/python/curl/__init__.py +index b002618..5b6b549 100644 +--- a/python/curl/__init__.py ++++ b/python/curl/__init__.py +@@ -63,7 +63,7 @@ class Curl: + + def set_option(self, *args): + "Set an option on the retrieval." +- apply(self.handle.setopt, args) ++ self.handle.setopt(*args) + + def set_verbosity(self, level): + "Set verbosity to 1 to see transactions." +@@ -103,7 +103,7 @@ class Curl: + + def get_info(self, *args): + "Get information about retrieval." +- return apply(self.handle.getinfo, args) ++ return self.handle.getinfo(*args) + + def info(self): + "Return a dictionary with all info on the last response." +diff --git a/setup.py b/setup.py +index 235e4c9..013bd11 100644 +--- a/setup.py ++++ b/setup.py +@@ -221,5 +221,4 @@ if LooseVersion(distutils.__version__) < LooseVersion("1.0.3"): + if __name__ == "__main__": + for o in ext.extra_objects: + assert os.path.isfile(o), o +- # We can live with the deprecationwarning for a while +- apply(setup, (), setup_args) ++ setup(**setup_args) +diff --git a/setup_win32_ssl.py b/setup_win32_ssl.py +index 332c04c..0ecc399 100644 +--- a/setup_win32_ssl.py ++++ b/setup_win32_ssl.py +@@ -32,5 +32,5 @@ ext.extra_objects.append(r"c:\src\pool\libidn-0.5.15" + pool + "idn.lib") + if __name__ == "__main__": + for o in ext.extra_objects: + assert os.path.isfile(o), o +- apply(setup, (), setup_args) ++ setup(**setup_args) + +-- +1.7.1 + + +From 94c63e7fe049d893a455b944c0deebe0f0c3ed37 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 26 Feb 2013 16:40:28 -0500 +Subject: [PATCH 106/112] Python 3 compatibility: exception raising syntax + +Signed-off-by: Kamil Dudka +--- + setup.py | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/setup.py b/setup.py +index 013bd11..725007c 100644 +--- a/setup.py ++++ b/setup.py +@@ -108,7 +108,7 @@ else: + if p.wait() == 0: + optbuf += stdout + if optbuf == "": +- raise Exception, ("Neither of curl-config --libs or --static-libs" + ++ raise Exception("Neither of curl-config --libs or --static-libs" + + "produced output") + libs = split_quoted(optbuf) + +-- +1.7.1 + + +From 530d280486742612cdbf7b306ce20e0200195adc Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 27 Feb 2013 20:23:28 -0500 +Subject: [PATCH 107/112] Python 3 compatibility: print syntax (src) + +Signed-off-by: Kamil Dudka +--- + python/curl/__init__.py | 8 ++++---- + 1 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/python/curl/__init__.py b/python/curl/__init__.py +index 5b6b549..5617262 100644 +--- a/python/curl/__init__.py ++++ b/python/curl/__init__.py +@@ -164,10 +164,10 @@ if __name__ == "__main__": + url = sys.argv[1] + c = Curl() + c.get(url) +- print c.body() +- print '='*74 + '\n' ++ print(c.body()) ++ print('='*74 + '\n') + import pprint + pprint.pprint(c.info()) +- print c.get_info(pycurl.OS_ERRNO) +- print c.info()['os-errno'] ++ print(c.get_info(pycurl.OS_ERRNO)) ++ print(c.info()['os-errno']) + c.close() +-- +1.7.1 + + +From 8f47e45b2814f03400f5f42aa67527147981b67f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 26 Feb 2013 16:49:09 -0500 +Subject: [PATCH 108/112] Python 3 compatibility: print syntax (former tests) + +Signed-off-by: Kamil Dudka +--- + examples/tests/test_gtk.py | 2 +- + examples/tests/test_xmlrpc.py | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/examples/tests/test_gtk.py b/examples/tests/test_gtk.py +index 7104439..da8c22a 100644 +--- a/examples/tests/test_gtk.py ++++ b/examples/tests/test_gtk.py +@@ -83,7 +83,7 @@ class Test(threading.Thread): + + # Check command line args + if len(sys.argv) < 3: +- print "Usage: %s " % sys.argv[0] ++ print("Usage: %s " % sys.argv[0]) + raise SystemExit + + # Make a progress bar window +diff --git a/examples/tests/test_xmlrpc.py b/examples/tests/test_xmlrpc.py +index bc5953e..3a5469a 100644 +--- a/examples/tests/test_xmlrpc.py ++++ b/examples/tests/test_xmlrpc.py +@@ -24,6 +24,6 @@ c.setopt(c.POST, 1) + c.setopt(c.HTTPHEADER, xmlrpc_header) + c.setopt(c.POSTFIELDS, xmlrpc_template % ("examples.getStateName", xmlrpclib.dumps((5,)))) + +-print 'Response from http://betty.userland.com/' ++print('Response from http://betty.userland.com/') + c.perform() + c.close() +-- +1.7.1 + + +From a69c0133c05d1a8b059514b58c369f69db53faa9 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 27 Feb 2013 20:23:39 -0500 +Subject: [PATCH 109/112] Python 3 compatibility: print syntax (examples) + +Signed-off-by: Kamil Dudka +--- + examples/basicfirst.py | 4 +- + examples/file_upload.py | 6 +- + examples/linksys.py | 182 +++++++++++++++++++++--------------------- + examples/retriever-multi.py | 10 +- + examples/retriever.py | 6 +- + examples/sfquery.py | 2 +- + examples/xmlrpc_curl.py | 6 +- + 7 files changed, 108 insertions(+), 108 deletions(-) + +diff --git a/examples/basicfirst.py b/examples/basicfirst.py +index af59405..44060af 100644 +--- a/examples/basicfirst.py ++++ b/examples/basicfirst.py +@@ -13,7 +13,7 @@ class Test: + def body_callback(self, buf): + self.contents = self.contents + buf + +-print >>sys.stderr, 'Testing', pycurl.version ++sys.stderr.write("Testing %s\n" % pycurl.version) + + t = Test() + c = pycurl.Curl() +@@ -22,4 +22,4 @@ c.setopt(c.WRITEFUNCTION, t.body_callback) + c.perform() + c.close() + +-print t.contents ++print(t.contents) +diff --git a/examples/file_upload.py b/examples/file_upload.py +index a514c1c..7750865 100644 +--- a/examples/file_upload.py ++++ b/examples/file_upload.py +@@ -15,13 +15,13 @@ class FileReader: + + # Check commandline arguments + if len(sys.argv) < 3: +- print "Usage: %s " % sys.argv[0] ++ print("Usage: %s " % sys.argv[0]) + raise SystemExit + url = sys.argv[1] + filename = sys.argv[2] + + if not os.path.exists(filename): +- print "Error: the file '%s' does not exist" % filename ++ print("Error: the file '%s' does not exist" % filename) + raise SystemExit + + # Initialize pycurl +@@ -41,6 +41,6 @@ filesize = os.path.getsize(filename) + c.setopt(pycurl.INFILESIZE, filesize) + + # Start transfer +-print 'Uploading file %s to url %s' % (filename, url) ++print('Uploading file %s to url %s' % (filename, url)) + c.perform() + c.close() +diff --git a/examples/linksys.py b/examples/linksys.py +index 5304886..24cb2a9 100755 +--- a/examples/linksys.py ++++ b/examples/linksys.py +@@ -224,11 +224,11 @@ if __name__ == "__main__": + self.session = LinksysSession() + if os.isatty(0): + import readline +- print "Type ? or `help' for help." ++ print("Type ? or `help' for help.") + self.prompt = self.session.host + ": " + else: + self.prompt = "" +- print "Bar1" ++ print("Bar1") + + def flag_command(self, func, line): + if line.strip() in ("on", "enable", "yes"): +@@ -246,96 +246,96 @@ if __name__ == "__main__": + self.session.cache_flush() + self.prompt = self.session.host + ": " + else: +- print self.session.host ++ print(self.session.host) + return 0 + def help_connect(self): +- print "Usage: connect []" +- print "Connect to a Linksys by name or IP address." +- print "If no argument is given, print the current host." ++ print("Usage: connect []") ++ print("Connect to a Linksys by name or IP address.") ++ print("If no argument is given, print the current host.") + + def do_status(self, line): + self.session.cache_load("") + if "" in self.session.pagecache: +- print "Firmware:", self.session.get_firmware_version() +- print "LAN MAC:", self.session.get_LAN_MAC() +- print "Wireless MAC:", self.session.get_Wireless_MAC() +- print "WAN MAC:", self.session.get_WAN_MAC() +- print "." ++ print("Firmware:", self.session.get_firmware_version()) ++ print("LAN MAC:", self.session.get_LAN_MAC()) ++ print("Wireless MAC:", self.session.get_Wireless_MAC()) ++ print("WAN MAC:", self.session.get_WAN_MAC()) ++ print(".") + return 0 + def help_status(self): +- print "Usage: status" +- print "The status command shows the status of the Linksys." +- print "It is mainly useful as a sanity check to make sure" +- print "the box is responding correctly." ++ print("Usage: status") ++ print("The status command shows the status of the Linksys.") ++ print("It is mainly useful as a sanity check to make sure") ++ print("the box is responding correctly.") + + def do_verbose(self, line): + self.flag_command(self.session.set_verbosity, line) + def help_verbose(self): +- print "Usage: verbose {on|off|enable|disable|yes|no}" +- print "Enables display of HTTP requests." ++ print("Usage: verbose {on|off|enable|disable|yes|no}") ++ print("Enables display of HTTP requests.") + + def do_host(self, line): + self.session.set_host_name(line) + return 0 + def help_host(self): +- print "Usage: host " +- print "Sets the Host field to be queried by the ISP." ++ print("Usage: host ") ++ print("Sets the Host field to be queried by the ISP.") + + def do_domain(self, line): +- print "Usage: host " ++ print("Usage: host ") + self.session.set_domain_name(line) + return 0 + def help_domain(self): +- print "Sets the Domain field to be queried by the ISP." ++ print("Sets the Domain field to be queried by the ISP.") + + def do_lan_address(self, line): + self.session.set_LAN_IP(line) + return 0 + def help_lan_address(self): +- print "Usage: lan_address " +- print "Sets the LAN IP address." ++ print("Usage: lan_address ") ++ print("Sets the LAN IP address.") + + def do_lan_netmask(self, line): + self.session.set_LAN_netmask(line) + return 0 + def help_lan_netmask(self): +- print "Usage: lan_netmask " +- print "Sets the LAN subnetwork mask." ++ print("Usage: lan_netmask ") ++ print("Sets the LAN subnetwork mask.") + + def do_wireless(self, line): + self.flag_command(self.session.set_wireless, line) + return 0 + def help_wireless(self): +- print "Usage: wireless {on|off|enable|disable|yes|no}" +- print "Switch to enable or disable wireless features." ++ print("Usage: wireless {on|off|enable|disable|yes|no}") ++ print("Switch to enable or disable wireless features.") + + def do_ssid(self, line): + self.session.set_SSID(line) + return 0 + def help_ssid(self): +- print "Usage: ssid " +- print "Sets the SSID used to control wireless access." ++ print("Usage: ssid ") ++ print("Sets the SSID used to control wireless access.") + + def do_ssid_broadcast(self, line): + self.flag_command(self.session.set_SSID_broadcast, line) + return 0 + def help_ssid_broadcast(self): +- print "Usage: ssid_broadcast {on|off|enable|disable|yes|no}" +- print "Switch to enable or disable SSID broadcast." ++ print("Usage: ssid_broadcast {on|off|enable|disable|yes|no}") ++ print("Switch to enable or disable SSID broadcast.") + + def do_channel(self, line): + self.session.set_channel(line) + return 0 + def help_channel(self): +- print "Usage: channel " +- print "Sets the wireless channel." ++ print("Usage: channel ") ++ print("Sets the wireless channel.") + + def do_wep(self, line): + self.flag_command(self.session.set_WEP, line) + return 0 + def help_wep(self): +- print "Usage: wep {on|off|enable|disable|yes|no}" +- print "Switch to enable or disable WEP security." ++ print("Usage: wep {on|off|enable|disable|yes|no}") ++ print("Switch to enable or disable WEP security.") + + def do_wan_type(self, line): + try: +@@ -345,29 +345,29 @@ if __name__ == "__main__": + print >>sys.stderr, "linksys: unknown connection type." + return 0 + def help_wan_type(self): +- print "Usage: wan_type {auto|static|ppoe|ras|pptp|heartbeat}" +- print "Set the WAN connection type." ++ print("Usage: wan_type {auto|static|ppoe|ras|pptp|heartbeat}") ++ print("Set the WAN connection type.") + + def do_wan_address(self, line): + self.session.set_WAN_IP(line) + return 0 + def help_wan_address(self): +- print "Usage: wan_address " +- print "Sets the WAN IP address." ++ print("Usage: wan_address ") ++ print("Sets the WAN IP address.") + + def do_wan_netmask(self, line): + self.session.set_WAN_netmask(line) + return 0 + def help_wan_netmask(self): +- print "Usage: wan_netmask " +- print "Sets the WAN subnetwork mask." ++ print("Usage: wan_netmask ") ++ print("Sets the WAN subnetwork mask.") + + def do_wan_gateway(self, line): + self.session.set_WAN_gateway(line) + return 0 + def help_wan_gateway(self): +- print "Usage: wan_gateway " +- print "Sets the LAN subnetwork mask." ++ print("Usage: wan_gateway ") ++ print("Sets the LAN subnetwork mask.") + + def do_dns(self, line): + (index, address) = line.split() +@@ -377,52 +377,52 @@ if __name__ == "__main__": + print >>sys.stderr, "linksys: server index out of bounds." + return 0 + def help_dns(self): +- print "Usage: dns {1|2|3} " +- print "Sets a primary, secondary, or tertiary DNS server address." ++ print("Usage: dns {1|2|3} ") ++ print("Sets a primary, secondary, or tertiary DNS server address.") + + def do_password(self, line): + self.session.set_password(line) + return 0 + def help_password(self): +- print "Usage: password " +- print "Sets the router password." ++ print("Usage: password ") ++ print("Sets the router password.") + + def do_upnp(self, line): + self.flag_command(self.session.set_UPnP, line) + return 0 + def help_upnp(self): +- print "Usage: upnp {on|off|enable|disable|yes|no}" +- print "Switch to enable or disable Universal Plug and Play." ++ print("Usage: upnp {on|off|enable|disable|yes|no}") ++ print("Switch to enable or disable Universal Plug and Play.") + + def do_reset(self, line): + self.session.reset() + def help_reset(self): +- print "Usage: reset" +- print "Reset Linksys settings to factory defaults." ++ print("Usage: reset") ++ print("Reset Linksys settings to factory defaults.") + + def do_dhcp(self, line): + self.flag_command(self.session.set_DHCP, line) + def help_dhcp(self): +- print "Usage: dhcp {on|off|enable|disable|yes|no}" +- print "Switch to enable or disable DHCP features." ++ print("Usage: dhcp {on|off|enable|disable|yes|no}") ++ print("Switch to enable or disable DHCP features.") + + def do_dhcp_start(self, line): + self.session.set_DHCP_starting_IP(line) + def help_dhcp_start(self): +- print "Usage: dhcp_start " +- print "Set the start address of the DHCP pool." ++ print("Usage: dhcp_start ") ++ print("Set the start address of the DHCP pool.") + + def do_dhcp_users(self, line): + self.session.set_DHCP_users(line) + def help_dhcp_users(self): +- print "Usage: dhcp_users " +- print "Set number of address slots to allocate in the DHCP pool." ++ print("Usage: dhcp_users ") ++ print("Set number of address slots to allocate in the DHCP pool.") + + def do_dhcp_lease(self, line): + self.session.set_DHCP_lease(line) + def help_dhcp_lease(self): +- print "Usage: dhcp_lease " +- print "Set number of address slots to allocate in the DHCP pool." ++ print("Usage: dhcp_lease ") ++ print("Set number of address slots to allocate in the DHCP pool.") + + def do_dhcp_dns(self, line): + (index, address) = line.split() +@@ -432,46 +432,46 @@ if __name__ == "__main__": + print >>sys.stderr, "linksys: server index out of bounds." + return 0 + def help_dhcp_dns(self): +- print "Usage: dhcp_dns {1|2|3} " +- print "Sets primary, secondary, or tertiary DNS server address." ++ print("Usage: dhcp_dns {1|2|3} ") ++ print("Sets primary, secondary, or tertiary DNS server address.") + + def do_logging(self, line): + self.flag_command(self.session.set_logging, line) + def help_logging(self): +- print "Usage: logging {on|off|enable|disable|yes|no}" +- print "Switch to enable or disable session logging." ++ print("Usage: logging {on|off|enable|disable|yes|no}") ++ print("Switch to enable or disable session logging.") + + def do_log_address(self, line): + self.session.set_Log_address(line) + def help_log_address(self): +- print "Usage: log_address " +- print "Set the last quad of the address to which to log." ++ print("Usage: log_address ") ++ print("Set the last quad of the address to which to log.") + + def do_configure(self, line): + self.session.configure() + return 0 + def help_configure(self): +- print "Usage: configure" +- print "Writes the configuration to the Linksys." ++ print("Usage: configure") ++ print("Writes the configuration to the Linksys.") + + def do_cache(self, line): +- print self.session.pagecache ++ print(self.session.pagecache) + def help_cache(self): +- print "Usage: cache" +- print "Display the page cache." ++ print("Usage: cache") ++ print("Display the page cache.") + + def do_quit(self, line): + return 1 + def help_quit(self, line): +- print "The quit command ends your linksys session without" +- print "writing configuration changes to the Linksys." ++ print("The quit command ends your linksys session without") ++ print("writing configuration changes to the Linksys.") + def do_EOF(self, line): +- print "" ++ print("") + self.session.configure() + return 1 + def help_EOF(self): +- print "The EOF command writes the configuration to the linksys" +- print "and ends your session." ++ print("The EOF command writes the configuration to the linksys") ++ print("and ends your session.") + + def default(self, line): + """Pass the command through to be executed by the shell.""" +@@ -479,11 +479,11 @@ if __name__ == "__main__": + return 0 + + def help_help(self): +- print "On-line help is available through this command." +- print "? is a convenience alias for help." ++ print("On-line help is available through this command.") ++ print("? is a convenience alias for help.") + + def help_introduction(self): +- print """\ ++ print("""\ + + This program supports changing the settings on Linksys blue-box routers. This + capability may come in handy when they freeze up and have to be reset. Though +@@ -506,16 +506,16 @@ will be shipped to the Linksys at the end of session (e.g. when the program + running in batch mode encounters end-of-file or you type a control-D). If you + end the session with `quit', pending changes will be discarded. + +-For more help, read the topics 'wan', 'lan', and 'wireless'.""" ++For more help, read the topics 'wan', 'lan', and 'wireless'.""") + + def help_lan(self): +- print """\ ++ print("""\ + The `lan_address' and `lan_netmask' commands let you set the IP location of + the Linksys on your LAN, or inside. Normally you'll want to leave these +-untouched.""" ++untouched.""") + + def help_wan(self): +- print """\ ++ print("""\ + The WAN commands become significant if you are using the BEFSR41 or any of + the other Linksys boxes designed as DSL or cable-modem gateways. You will + need to use `wan_type' to declare how you expect to get your address. +@@ -527,22 +527,22 @@ need to use the `dns' command to declare which remote servers your DNS + requests should be forwarded to. + + Some ISPs may require you to set host and domain for use with dynamic-address +-allocation.""" ++allocation.""") + + def help_wireless(self): +- print """\ ++ print("""\ + The channel, ssid, ssid_broadcast, wep, and wireless commands control +-wireless routing.""" ++wireless routing.""") + + def help_switches(self): +- print "Switches may be turned on with 'on', 'enable', or 'yes'." +- print "Switches may be turned off with 'off', 'disable', or 'no'." +- print "Switch commands include: wireless, ssid_broadcast." ++ print("Switches may be turned on with 'on', 'enable', or 'yes'.") ++ print("Switches may be turned off with 'off', 'disable', or 'no'.") ++ print("Switch commands include: wireless, ssid_broadcast.") + + def help_addresses(self): +- print "An address argument must be a valid IP address;" +- print "four decimal numbers separated by dots, each " +- print "between 0 and 255." ++ print("An address argument must be a valid IP address;") ++ print("four decimal numbers separated by dots, each ") ++ print("between 0 and 255.") + + def emptyline(self): + pass +diff --git a/examples/retriever-multi.py b/examples/retriever-multi.py +index 1a941be..ad4cebd 100644 +--- a/examples/retriever-multi.py ++++ b/examples/retriever-multi.py +@@ -31,7 +31,7 @@ try: + if len(sys.argv) >= 3: + num_conn = int(sys.argv[2]) + except: +- print "Usage: %s [<# of concurrent connections>]" % sys.argv[0] ++ print("Usage: %s [<# of concurrent connections>]" % sys.argv[0]) + raise SystemExit + + +@@ -50,8 +50,8 @@ assert queue, "no URLs given" + num_urls = len(queue) + num_conn = min(num_conn, num_urls) + assert 1 <= num_conn <= 10000, "invalid number of concurrent connections" +-print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM) +-print "----- Getting", num_urls, "URLs using", num_conn, "connections -----" ++print("PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM)) ++print("----- Getting", num_urls, "URLs using", num_conn, "connections -----") + + + # Pre-allocate a list of curl objects +@@ -95,13 +95,13 @@ while num_processed < num_urls: + c.fp.close() + c.fp = None + m.remove_handle(c) +- print "Success:", c.filename, c.url, c.getinfo(pycurl.EFFECTIVE_URL) ++ print("Success:", c.filename, c.url, c.getinfo(pycurl.EFFECTIVE_URL)) + freelist.append(c) + for c, errno, errmsg in err_list: + c.fp.close() + c.fp = None + m.remove_handle(c) +- print "Failed: ", c.filename, c.url, errno, errmsg ++ print("Failed: ", c.filename, c.url, errno, errmsg) + freelist.append(c) + num_processed = num_processed + len(ok_list) + len(err_list) + if num_q == 0: +diff --git a/examples/retriever.py b/examples/retriever.py +index 2c91d07..be1b6ea 100644 +--- a/examples/retriever.py ++++ b/examples/retriever.py +@@ -31,7 +31,7 @@ try: + if len(sys.argv) >= 3: + num_conn = int(sys.argv[2]) + except: +- print "Usage: %s [<# of concurrent connections>]" % sys.argv[0] ++ print("Usage: %s [<# of concurrent connections>]" % sys.argv[0]) + raise SystemExit + + +@@ -50,8 +50,8 @@ assert queue.queue, "no URLs given" + num_urls = len(queue.queue) + num_conn = min(num_conn, num_urls) + assert 1 <= num_conn <= 10000, "invalid number of concurrent connections" +-print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM) +-print "----- Getting", num_urls, "URLs using", num_conn, "connections -----" ++print("PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM)) ++print("----- Getting", num_urls, "URLs using", num_conn, "connections -----") + + + class WorkerThread(threading.Thread): +diff --git a/examples/sfquery.py b/examples/sfquery.py +index 741feb0..16aa9d4 100644 +--- a/examples/sfquery.py ++++ b/examples/sfquery.py +@@ -40,7 +40,7 @@ if __name__ == "__main__": + name, account, password = auth + except: + if len(sys.argv) < 4: +- print "Usage: %s " % sys.argv[0] ++ print("Usage: %s " % sys.argv[0]) + raise SystemExit + name = sys.argv[2] + password = sys.argv[3] +diff --git a/examples/xmlrpc_curl.py b/examples/xmlrpc_curl.py +index ec0df50..a9a7d9e 100644 +--- a/examples/xmlrpc_curl.py ++++ b/examples/xmlrpc_curl.py +@@ -55,8 +55,8 @@ if __name__ == "__main__": + ## Test + server = xmlrpclib.ServerProxy("http://betty.userland.com", + transport=CURLTransport()) +- print server ++ print(server) + try: +- print server.examples.getStateName(41) ++ print(server.examples.getStateName(41)) + except xmlrpclib.Error, v: +- print "ERROR", v ++ print("ERROR", v) +-- +1.7.1 + + +From 41478f926b683d2141732936cad1855a7632552a Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 28 Feb 2013 00:30:00 -0500 +Subject: [PATCH 110/112] Python 3 compatibility: printing to stderr (linksys example) + +Signed-off-by: Kamil Dudka +--- + examples/linksys.py | 14 +++++++++----- + 1 files changed, 9 insertions(+), 5 deletions(-) + +diff --git a/examples/linksys.py b/examples/linksys.py +index 24cb2a9..f66ff0d 100755 +--- a/examples/linksys.py ++++ b/examples/linksys.py +@@ -34,6 +34,10 @@ + + import sys, re, copy, curl, exceptions + ++def print_stderr(arg): ++ sys.stderr.write(arg) ++ sys.stderr.write("\n") ++ + class LinksysError(exceptions.Exception): + def __init__(self, *args): + self.args = args +@@ -202,7 +206,7 @@ class LinksysSession: + for (page, field, value) in self.actions: + self.cache_load(page) + if self.pagecache[page].find(field) == -1: +- print >>sys.stderr, "linksys: field %s not found where expected in page %s!" % (field, os.path.join(self.host, page)) ++ print_stderr("linksys: field %s not found where expected in page %s!" % (field, os.path.join(self.host, page))) + continue + else: + fields.append((field, value)) +@@ -236,7 +240,7 @@ if __name__ == "__main__": + elif line.strip() in ("off", "disable", "no"): + func(False) + else: +- print >>sys.stderr, "linksys: unknown switch value" ++ print_stderr("linksys: unknown switch value") + return 0 + + def do_connect(self, line): +@@ -342,7 +346,7 @@ if __name__ == "__main__": + type=eval("LinksysSession.WAN_CONNECT_"+line.strip().upper()) + self.session.set_connection_type(type) + except ValueError: +- print >>sys.stderr, "linksys: unknown connection type." ++ print_stderr("linksys: unknown connection type.") + return 0 + def help_wan_type(self): + print("Usage: wan_type {auto|static|ppoe|ras|pptp|heartbeat}") +@@ -374,7 +378,7 @@ if __name__ == "__main__": + if index in ("1", "2", "3"): + self.session.set_DNS_server(eval(index), address) + else: +- print >>sys.stderr, "linksys: server index out of bounds." ++ print_stderr("linksys: server index out of bounds.") + return 0 + def help_dns(self): + print("Usage: dns {1|2|3} ") +@@ -429,7 +433,7 @@ if __name__ == "__main__": + if index in ("1", "2", "3"): + self.session.set_DHCP_DNS_server(eval(index), address) + else: +- print >>sys.stderr, "linksys: server index out of bounds." ++ print_stderr("linksys: server index out of bounds.") + return 0 + def help_dhcp_dns(self): + print("Usage: dhcp_dns {1|2|3} ") +-- +1.7.1 + + +From a20b726b10b5e1bc27ba95808ef11154973b6403 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Mon, 8 Apr 2013 14:25:26 +0200 +Subject: [PATCH 111/112] allow to unset a previously set RANGE value +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +... and add the unset_range_test.py unit test exercising it + +Reported by: Zdeněk Pavlas +Bug: https://bugzilla.redhat.com/928370 + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 1 + + tests/unset_range_test.py | 39 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 40 insertions(+), 0 deletions(-) + create mode 100644 tests/unset_range_test.py + +diff --git a/src/pycurl.c b/src/pycurl.c +index a30c339..bd28b81 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -1561,6 +1561,7 @@ util_curl_unsetopt(CurlObject *self, int option) + case CURLOPT_RANDOM_FILE: + case CURLOPT_SSL_CIPHER_LIST: + case CURLOPT_USERPWD: ++ case CURLOPT_RANGE: + SETOPT((char *) 0); + break; + +diff --git a/tests/unset_range_test.py b/tests/unset_range_test.py +new file mode 100644 +index 0000000..4400851 +--- /dev/null ++++ b/tests/unset_range_test.py +@@ -0,0 +1,39 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import os.path ++import pycurl ++import sys ++import unittest ++ ++class UnsetRangeTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_unset_range(self): ++ def write_cb(data): ++ self.read += len(data) ++ return None ++ ++ # download bytes 0-9 of the script itself through the file:// protocol ++ self.read = 0 ++ self.curl.setopt(pycurl.URL, 'file://' + os.path.abspath(sys.argv[0])) ++ self.curl.setopt(pycurl.WRITEFUNCTION, write_cb) ++ self.curl.setopt(pycurl.RANGE, '0-9') ++ self.curl.perform() ++ assert 10 == self.read ++ ++ # the RANGE setting should be preserved from the previous transfer ++ self.read = 0 ++ self.curl.perform() ++ assert 10 == self.read ++ ++ # drop the RANGE setting using unsetopt() and download entire script ++ self.read = 0 ++ self.curl.unsetopt(pycurl.RANGE) ++ self.curl.perform() ++ assert 10 < self.read +-- +1.7.1 + + +From 6d43dc15185016b0e2dd993e18bd124f87554445 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Mon, 8 Apr 2013 14:31:30 +0200 +Subject: [PATCH 112/112] allow to use setopt(..., None) as unsetopt() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +... and extend the unset_range_test.py unit test to exercise it + +Reported by: Zdeněk Pavlas +Bug: https://bugzilla.redhat.com/928370 + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 4 +--- + tests/unset_range_test.py | 12 ++++++++++++ + 2 files changed, 13 insertions(+), 3 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index bd28b81..619ca20 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -1631,12 +1631,10 @@ do_curl_setopt(CurlObject *self, PyObject *args) + if (option % 10000 >= OPTIONS_SIZE) + goto error; + +-#if 0 /* XXX - should we ??? */ +- /* Handle the case of None */ ++ /* Handle the case of None as the call of unsetopt() */ + if (obj == Py_None) { + return util_curl_unsetopt(self, option); + } +-#endif + + /* Handle the case of string arguments */ + if (PyString_Check(obj)) { +diff --git a/tests/unset_range_test.py b/tests/unset_range_test.py +index 4400851..10ee801 100644 +--- a/tests/unset_range_test.py ++++ b/tests/unset_range_test.py +@@ -37,3 +37,15 @@ class UnsetRangeTest(unittest.TestCase): + self.curl.unsetopt(pycurl.RANGE) + self.curl.perform() + assert 10 < self.read ++ ++ # now set the RANGE again and check that pycurl takes it into account ++ self.read = 0 ++ self.curl.setopt(pycurl.RANGE, '0-9') ++ self.curl.perform() ++ assert 10 == self.read ++ ++ # now drop the RANGE setting using setopt(..., None) ++ self.read = 0 ++ self.curl.setopt(pycurl.RANGE, None) ++ self.curl.perform() ++ assert 10 < self.read +-- +1.7.1 + diff --git a/0001-No-longer-keep-copies-of-string-options-since-this-i.patch b/0001-No-longer-keep-copies-of-string-options-since-this-i.patch deleted file mode 100644 index bfbe215..0000000 --- a/0001-No-longer-keep-copies-of-string-options-since-this-i.patch +++ /dev/null @@ -1,200 +0,0 @@ -From 05433632fb1ccdabc1d29d78f32bc35de0a8638b Mon Sep 17 00:00:00 2001 -From: kjetilja -Date: Mon, 29 Sep 2008 10:56:57 +0000 -Subject: [PATCH 1/5] No longer keep copies of string options since this is managed by libcurl - -Signed-off-by: Kamil Dudka ---- - ChangeLog | 12 +++++++++- - src/pycurl.c | 60 +-------------------------------------------------------- - 2 files changed, 12 insertions(+), 60 deletions(-) - -diff --git a/ChangeLog b/ChangeLog -index 0fb7f8c..618654d 100644 ---- a/ChangeLog -+++ b/ChangeLog -@@ -1,7 +1,15 @@ --Version 7.19.0 [requires libcurl-7.19.0 or better] -+Version 7.19.1 [requires libcurl-7.19.0 or better] - -------------- - -- * Added CURLFILE, ADDRESS_SCOPE and ISSUERCERT options, -+ * No longer keep string options copies in the -+ Curl Python objects, since string options are -+ now managed by libcurl. -+ -+ -+Version 7.19.0 -+-------------- -+ -+ * Added CURLFILE, ADDRESS_SCOPE and ISSUERCERT options, - as well as the APPCONNECT_TIME info. - - * Added PRIMARY_IP info (patch by -diff --git a/src/pycurl.c b/src/pycurl.c -index a17a23b..6de1514 100644 ---- a/src/pycurl.c -+++ b/src/pycurl.c -@@ -1,4 +1,4 @@ --/* $Id: pycurl.c,v 1.147 2008/09/09 17:40:34 kjetilja Exp $ */ -+/* $Id: pycurl.c,v 1.148 2008/09/29 10:56:57 kjetilja Exp $ */ - - /* PycURL -- cURL Python module - * -@@ -97,12 +97,6 @@ static void pycurl_ssl_cleanup(void); - /* Calculate the number of OBJECTPOINT options we need to store */ - #define OPTIONS_SIZE ((int)CURLOPT_LASTENTRY % 10000) - #define MOPTIONS_SIZE ((int)CURLMOPT_LASTENTRY % 10000) --static int OPT_INDEX(int o) --{ -- assert(o >= CURLOPTTYPE_OBJECTPOINT); -- assert(o < CURLOPTTYPE_OBJECTPOINT + OPTIONS_SIZE); -- return o - CURLOPTTYPE_OBJECTPOINT; --} - - /* Type objects */ - static PyObject *ErrorObject = NULL; -@@ -161,7 +155,6 @@ typedef struct { - PyObject *writedata_fp; - PyObject *writeheader_fp; - /* misc */ -- void *options[OPTIONS_SIZE]; /* for OBJECTPOINT options */ - char error[CURL_ERROR_SIZE+1]; - } CurlObject; - -@@ -741,7 +734,6 @@ util_curl_new(void) - self->writeheader_fp = NULL; - - /* Zero string pointer memory buffer used by setopt */ -- memset(self->options, 0, sizeof(self->options)); - memset(self->error, 0, sizeof(self->error)); - - return self; -@@ -804,7 +796,6 @@ do_curl_new(PyObject *dummy) - free(s); - goto error; - } -- self->options[ OPT_INDEX(CURLOPT_USERAGENT) ] = s; s = NULL; - - /* Success - return new object */ - return self; -@@ -872,7 +863,6 @@ static void - util_curl_close(CurlObject *self) - { - CURL *handle; -- int i; - - /* Zero handle and thread-state to disallow any operations to be run - * from now on */ -@@ -916,16 +906,6 @@ util_curl_close(CurlObject *self) - SFREE(self->postquote); - SFREE(self->prequote); - #undef SFREE -- -- /* Last, free the options. This must be done after the curl handle -- * is closed since libcurl assumes that some options are valid when -- * invoking curl_easy_cleanup(). */ -- for (i = 0; i < OPTIONS_SIZE; i++) { -- if (self->options[i] != NULL) { -- free(self->options[i]); -- self->options[i] = NULL; -- } -- } - } - - -@@ -1424,8 +1404,6 @@ verbose_error: - static PyObject* - do_curl_reset(CurlObject *self) - { -- unsigned int i; -- - curl_easy_reset(self->handle); - - /* Decref callbacks and file handles */ -@@ -1443,15 +1421,6 @@ do_curl_reset(CurlObject *self) - SFREE(self->postquote); - SFREE(self->prequote); - #undef SFREE -- -- /* Last, free the options */ -- for (i = 0; i < OPTIONS_SIZE; i++) { -- if (self->options[i] != NULL) { -- free(self->options[i]); -- self->options[i] = NULL; -- } -- } -- - return Py_None; - } - -@@ -1461,7 +1430,6 @@ static PyObject * - util_curl_unsetopt(CurlObject *self, int option) - { - int res; -- int opt_index = -1; - - #define SETOPT2(o,x) \ - if ((res = curl_easy_setopt(self->handle, (o), (x))) != CURLE_OK) goto error -@@ -1502,7 +1470,6 @@ util_curl_unsetopt(CurlObject *self, int option) - case CURLOPT_SSL_CIPHER_LIST: - case CURLOPT_USERPWD: - SETOPT((char *) 0); -- opt_index = OPT_INDEX(option); - break; - - /* info: we explicitly list unsupported options here */ -@@ -1512,11 +1479,6 @@ util_curl_unsetopt(CurlObject *self, int option) - return NULL; - } - -- if (opt_index >= 0 && self->options[opt_index] != NULL) { -- free(self->options[opt_index]); -- self->options[opt_index] = NULL; -- } -- - Py_INCREF(Py_None); - return Py_None; - -@@ -1587,8 +1549,6 @@ do_curl_setopt(CurlObject *self, PyObject *args) - if (PyString_Check(obj)) { - char *str = NULL; - Py_ssize_t len = -1; -- char *buf; -- int opt_index; - - /* Check that the option specified a string as well as the input */ - switch (option) { -@@ -1651,28 +1611,12 @@ do_curl_setopt(CurlObject *self, PyObject *args) - } - /* Allocate memory to hold the string */ - assert(str != NULL); -- if (len <= 0) -- buf = strdup(str); -- else { -- buf = (char *) malloc(len); -- if (buf) memcpy(buf, str, len); -- } -- if (buf == NULL) -- return PyErr_NoMemory(); - /* Call setopt */ -- res = curl_easy_setopt(self->handle, (CURLoption)option, buf); -+ res = curl_easy_setopt(self->handle, (CURLoption)option, str); - /* Check for errors */ - if (res != CURLE_OK) { -- free(buf); - CURLERROR_RETVAL(); - } -- /* Save allocated option buffer */ -- opt_index = OPT_INDEX(option); -- if (self->options[opt_index] != NULL) { -- free(self->options[opt_index]); -- self->options[opt_index] = NULL; -- } -- self->options[opt_index] = buf; - Py_INCREF(Py_None); - return Py_None; - } --- -1.7.1 - diff --git a/0002-Fixes-https-sourceforge.net-tracker-func-detail-aid-.patch b/0002-Fixes-https-sourceforge.net-tracker-func-detail-aid-.patch deleted file mode 100644 index 522087c..0000000 --- a/0002-Fixes-https-sourceforge.net-tracker-func-detail-aid-.patch +++ /dev/null @@ -1,61 +0,0 @@ -From 009e170d2838346461ff0b31b0afa44f3d6278f3 Mon Sep 17 00:00:00 2001 -From: zanee -Date: Fri, 23 Apr 2010 16:06:41 +0000 -Subject: [PATCH 2/5] Fixes https://sourceforge.net/tracker/?func=detail&aid=2812016&group_id=28236&atid=392777 with applied patch from sourceforge user dbprice1. - -Signed-off-by: Kamil Dudka ---- - setup.py | 23 ++++++++++++++++++----- - 1 files changed, 18 insertions(+), 5 deletions(-) - -diff --git a/setup.py b/setup.py -index 0ffd9d2..76b9d58 100644 ---- a/setup.py -+++ b/setup.py -@@ -1,7 +1,7 @@ - #! /usr/bin/env python - # -*- coding: iso-8859-1 -*- - # vi:ts=4:et --# $Id: setup.py,v 1.150 2008/09/09 17:40:34 kjetilja Exp $ -+# $Id: setup.py,v 1.151 2010/04/23 16:06:41 zanee Exp $ - - """Setup script for the PycURL module distribution.""" - -@@ -9,7 +9,7 @@ PACKAGE = "pycurl" - PY_PACKAGE = "curl" - VERSION = "7.19.0" - --import glob, os, re, sys, string -+import glob, os, re, sys, string, subprocess - import distutils - from distutils.core import setup - from distutils.extension import Extension -@@ -96,9 +96,22 @@ else: - include_dirs.append(e[2:]) - else: - extra_compile_args.append(e) -- libs = split_quoted( -- os.popen("'%s' --libs" % CURL_CONFIG).read()+\ -- os.popen("'%s' --static-libs" % CURL_CONFIG).read()) -+ -+ # Run curl-config --libs and --static-libs. Some platforms may not -+ # support one or the other of these curl-config options, so gracefully -+ # tolerate failure of either, but not both. -+ optbuf = "" -+ for option in ["--libs", "--static-libs"]: -+ p = subprocess.Popen("'%s' %s" % (CURL_CONFIG, option), shell=True, -+ stdout=subprocess.PIPE) -+ (stdout, stderr) = p.communicate() -+ if p.wait() == 0: -+ optbuf += stdout -+ if optbuf == "": -+ raise Exception, ("Neither of curl-config --libs or --static-libs" + -+ "produced output") -+ libs = split_quoted(optbuf) -+ - for e in libs: - if e[:2] == "-l": - libraries.append(e[2:]) --- -1.7.1 - diff --git a/0003-Fixes-refcount-bug-and-provides-better-organization-.patch b/0003-Fixes-refcount-bug-and-provides-better-organization-.patch deleted file mode 100644 index 80c600e..0000000 --- a/0003-Fixes-refcount-bug-and-provides-better-organization-.patch +++ /dev/null @@ -1,160 +0,0 @@ -From 4a377e2d60fb903e91a370595a6ea22cb7ee0e0e Mon Sep 17 00:00:00 2001 -From: zanee -Date: Wed, 28 Apr 2010 16:02:41 +0000 -Subject: [PATCH 3/5 v2] Fixes refcount bug and provides better organization of PyCurl object. Submitted by dbprice1. - -https://sourceforge.net/tracker/?func=detail&aid=2893665&group_id=28236&atid=392777 - -Signed-off-by: Kamil Dudka ---- - src/pycurl.c | 88 +++++++++++++++++++++++++++++++++++++-------------------- - 1 files changed, 57 insertions(+), 31 deletions(-) - -diff --git a/src/pycurl.c b/src/pycurl.c -index 6de1514..32c7ca5 100644 ---- a/src/pycurl.c -+++ b/src/pycurl.c -@@ -1,4 +1,4 @@ --/* $Id: pycurl.c,v 1.148 2008/09/29 10:56:57 kjetilja Exp $ */ -+/* $Id: pycurl.c,v 1.149 2010/04/28 16:02:41 zanee Exp $ */ - - /* PycURL -- cURL Python module - * -@@ -739,64 +739,80 @@ util_curl_new(void) - return self; - } - -- --/* constructor - this is a module-level function returning a new instance */ --static CurlObject * --do_curl_new(PyObject *dummy) -+/* initializer - used to intialize curl easy handles for use with pycurl */ -+static int -+util_curl_init(CurlObject *self) - { -- CurlObject *self = NULL; - int res; - char *s = NULL; - -- UNUSED(dummy); -- -- /* Allocate python curl object */ -- self = util_curl_new(); -- if (self == NULL) -- return NULL; -- -- /* Initialize curl handle */ -- self->handle = curl_easy_init(); -- if (self->handle == NULL) -- goto error; -- - /* Set curl error buffer and zero it */ - res = curl_easy_setopt(self->handle, CURLOPT_ERRORBUFFER, self->error); -- if (res != CURLE_OK) -- goto error; -+ if (res != CURLE_OK) { -+ return (-1); -+ } - memset(self->error, 0, sizeof(self->error)); - - /* Set backreference */ - res = curl_easy_setopt(self->handle, CURLOPT_PRIVATE, (char *) self); -- if (res != CURLE_OK) -- goto error; -+ if (res != CURLE_OK) { -+ return (-1); -+ } - - /* Enable NOPROGRESS by default, i.e. no progress output */ - res = curl_easy_setopt(self->handle, CURLOPT_NOPROGRESS, (long)1); -- if (res != CURLE_OK) -- goto error; -+ if (res != CURLE_OK) { -+ return (-1); -+ } - - /* Disable VERBOSE by default, i.e. no verbose output */ - res = curl_easy_setopt(self->handle, CURLOPT_VERBOSE, (long)0); -- if (res != CURLE_OK) -- goto error; -+ if (res != CURLE_OK) { -+ return (-1); -+ } - - /* Set FTP_ACCOUNT to NULL by default */ - res = curl_easy_setopt(self->handle, CURLOPT_FTP_ACCOUNT, NULL); -- if (res != CURLE_OK) -- goto error; -+ if (res != CURLE_OK) { -+ return (-1); -+ } - - /* Set default USERAGENT */ - s = (char *) malloc(7 + strlen(LIBCURL_VERSION) + 1); -- if (s == NULL) -- goto error; -+ if (s == NULL) { -+ return (-1); -+ } - strcpy(s, "PycURL/"); strcpy(s+7, LIBCURL_VERSION); - res = curl_easy_setopt(self->handle, CURLOPT_USERAGENT, (char *) s); - if (res != CURLE_OK) { - free(s); -- goto error; -+ return (-1); - } -+ return (0); -+} -+ -+/* constructor - this is a module-level function returning a new instance */ -+static CurlObject * -+do_curl_new(PyObject *dummy) -+{ -+ CurlObject *self = NULL; -+ int res; -+ -+ UNUSED(dummy); -+ -+ /* Allocate python curl object */ -+ self = util_curl_new(); -+ if (self == NULL) -+ return NULL; -+ -+ /* Initialize curl handle */ -+ self->handle = curl_easy_init(); -+ if (self->handle == NULL) -+ goto error; - -+ res = util_curl_init(self); -+ if (res < 0) -+ goto error; - /* Success - return new object */ - return self; - -@@ -1404,6 +1420,8 @@ verbose_error: - static PyObject* - do_curl_reset(CurlObject *self) - { -+ int res; -+ - curl_easy_reset(self->handle); - - /* Decref callbacks and file handles */ -@@ -1421,6 +1439,14 @@ do_curl_reset(CurlObject *self) - SFREE(self->postquote); - SFREE(self->prequote); - #undef SFREE -+ res = util_curl_init(self); -+ if (res < 0) { -+ Py_DECREF(self); /* this also closes self->handle */ -+ PyErr_SetString(ErrorObject, "resetting curl failed"); -+ return NULL; -+ } -+ -+ Py_INCREF(Py_None); - return Py_None; - } - --- -1.7.1 - diff --git a/0004-Test-for-reset-fixes-refcount-bug.patch b/0004-Test-for-reset-fixes-refcount-bug.patch deleted file mode 100644 index 6144842..0000000 --- a/0004-Test-for-reset-fixes-refcount-bug.patch +++ /dev/null @@ -1,94 +0,0 @@ -From 206c15ea32e05ae98827d00626a101e33a7bec70 Mon Sep 17 00:00:00 2001 -From: zanee -Date: Wed, 28 Apr 2010 16:03:40 +0000 -Subject: [PATCH 4/5] Test for reset fixes refcount bug - -Signed-off-by: Kamil Dudka ---- - tests/test_reset.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ - 1 files changed, 74 insertions(+), 0 deletions(-) - create mode 100644 tests/test_reset.py - -diff --git a/tests/test_reset.py b/tests/test_reset.py -new file mode 100644 -index 0000000..c350d17 ---- /dev/null -+++ b/tests/test_reset.py -@@ -0,0 +1,74 @@ -+#!/usr/bin/python -+ -+import sys -+import pycurl -+ -+saw_error = 1 -+ -+def main(): -+ global saw_error -+ -+ pycurl.global_init(pycurl.GLOBAL_DEFAULT) -+ -+ outf = file("/dev/null", "rb+") -+ cm = pycurl.CurlMulti() -+ -+ # Set multi handle's options -+ cm.setopt(pycurl.M_PIPELINING, 1) -+ -+ eh = pycurl.Curl() -+ -+ for x in range(1, 20): -+ -+ eh.setopt(pycurl.WRITEDATA, outf) -+ eh.setopt(pycurl.URL, sys.argv[1]) -+ cm.add_handle(eh) -+ -+ while 1: -+ ret, active_handles = cm.perform() -+ if ret != pycurl.E_CALL_MULTI_PERFORM: -+ break -+ -+ while active_handles: -+ ret = cm.select(1.0) -+ if ret == -1: -+ continue -+ while 1: -+ ret, active_handles = cm.perform() -+ if ret != pycurl.E_CALL_MULTI_PERFORM: -+ break -+ -+ count, good, bad = cm.info_read() -+ -+ for h, en, em in bad: -+ print "Transfer to %s failed with %d, %s\n" % \ -+ (h.getinfo(pycurl.EFFECTIVE_URL), en, em) -+ raise RuntimeError -+ -+ for h in good: -+ httpcode = h.getinfo(pycurl.RESPONSE_CODE) -+ if httpcode != 200: -+ print "Transfer to %s failed with code %d\n" %\ -+ (h.getinfo(pycurl.EFFECTIVE_URL), httpcode) -+ raise RuntimeError -+ -+ else: -+ print "Recd %d bytes from %s" % \ -+ (h.getinfo(pycurl.SIZE_DOWNLOAD), -+ h.getinfo(pycurl.EFFECTIVE_URL)) -+ -+ cm.remove_handle(eh) -+ eh.reset() -+ -+ eh.close() -+ cm.close() -+ outf.close() -+ -+ pycurl.global_cleanup() -+ -+ -+if __name__ == '__main__': -+ if len(sys.argv) != 2: -+ print "Usage: %s " % sys.argv[0] -+ sys.exit(2) -+ main() --- -1.7.1 - diff --git a/0005-Updating-ChangeLog-with-relevant-changes.patch b/0005-Updating-ChangeLog-with-relevant-changes.patch deleted file mode 100644 index dd8d803..0000000 --- a/0005-Updating-ChangeLog-with-relevant-changes.patch +++ /dev/null @@ -1,32 +0,0 @@ -From d075bfb8a5207f933a5a704becd7f64e8521dc28 Mon Sep 17 00:00:00 2001 -From: zanee -Date: Tue, 4 May 2010 18:47:08 +0000 -Subject: [PATCH 5/5] Updating ChangeLog with relevant changes - -Signed-off-by: Kamil Dudka ---- - ChangeLog | 11 +++++++++++ - 1 files changed, 11 insertions(+), 0 deletions(-) - -diff --git a/ChangeLog b/ChangeLog -index 618654d..885c8b0 100644 ---- a/ChangeLog -+++ b/ChangeLog -@@ -1,3 +1,14 @@ -+Version 7.19.2 -+-------------- -+ -+ * Cleaned up website -+ -+ * Fix pycurl.reset() (patch by ). -+ -+ * Fix install routine in setup.py where -+ certain platforms (Solaris, Mac OSX, etc) -+ would search for a static copy of libcurl (dbp) -+ - Version 7.19.1 [requires libcurl-7.19.0 or better] - -------------- - --- -1.7.1 - diff --git a/0101-test_internals.py-add-a-test-for-ref-counting-of-res.patch b/0101-test_internals.py-add-a-test-for-ref-counting-of-res.patch deleted file mode 100644 index 5e889ca..0000000 --- a/0101-test_internals.py-add-a-test-for-ref-counting-of-res.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 593cf090dacc230cd28aee1993d86b2b83b414f9 Mon Sep 17 00:00:00 2001 -From: Kamil Dudka -Date: Mon, 25 Feb 2013 19:50:18 +0100 -Subject: [PATCH 1/4] test_internals.py: add a test for ref-counting of reset() - ---- - tests/test_internals.py | 5 +++++ - 1 files changed, 5 insertions(+), 0 deletions(-) - -diff --git a/tests/test_internals.py b/tests/test_internals.py -index afcc53d..d026952 100644 ---- a/tests/test_internals.py -+++ b/tests/test_internals.py -@@ -245,6 +245,11 @@ if 1 and gc: - if opts.verbose >= 1: - print "Tracked objects:", len(gc.get_objects()) - -+if 1: -+ # Ensure that the refcounting error in "reset" is fixed: -+ for i in xrange(100000): -+ c = Curl() -+ c.reset() - - # /*********************************************************************** - # // done --- -1.7.1 - diff --git a/0102-pycurl.c-eliminate-duplicated-code-in-util_write_cal.patch b/0102-pycurl.c-eliminate-duplicated-code-in-util_write_cal.patch deleted file mode 100644 index b707a61..0000000 --- a/0102-pycurl.c-eliminate-duplicated-code-in-util_write_cal.patch +++ /dev/null @@ -1,34 +0,0 @@ -From c84c2a02a34031a951edeb5d3f81676d05e2765f Mon Sep 17 00:00:00 2001 -From: Kamil Dudka -Date: Tue, 26 Feb 2013 14:49:47 +0100 -Subject: [PATCH 2/4] pycurl.c: eliminate duplicated code in util_write_callback() - -Suggested by Zdenek Pavlas . ---- - src/pycurl.c | 10 +--------- - 1 files changed, 1 insertions(+), 9 deletions(-) - -diff --git a/src/pycurl.c b/src/pycurl.c -index 74f5248..f197145 100644 ---- a/src/pycurl.c -+++ b/src/pycurl.c -@@ -1080,15 +1080,7 @@ util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *strea - if (result == Py_None) { - ret = total_size; /* None means success */ - } -- else if (PyInt_Check(result)) { -- long obj_size = PyInt_AsLong(result); -- if (obj_size < 0 || obj_size > total_size) { -- PyErr_Format(ErrorObject, "invalid return value for write callback %ld %ld", (long)obj_size, (long)total_size); -- goto verbose_error; -- } -- ret = (size_t) obj_size; /* success */ -- } -- else if (PyLong_Check(result)) { -+ else if (PyInt_Check(result) || PyLong_Check(result)) { - long obj_size = PyLong_AsLong(result); - if (obj_size < 0 || obj_size > total_size) { - PyErr_Format(ErrorObject, "invalid return value for write callback %ld %ld", (long)obj_size, (long)total_size); --- -1.7.1 - diff --git a/0103-pycurl.c-allow-to-return-1-from-write-callback.patch b/0103-pycurl.c-allow-to-return-1-from-write-callback.patch deleted file mode 100644 index d3d7a3e..0000000 --- a/0103-pycurl.c-allow-to-return-1-from-write-callback.patch +++ /dev/null @@ -1,45 +0,0 @@ -From ca4705dab05371c40f398b97277024332ed44651 Mon Sep 17 00:00:00 2001 -From: Kamil Dudka -Date: Tue, 26 Feb 2013 16:58:55 +0100 -Subject: [PATCH 3/4] pycurl.c: allow to return -1 from write callback - -... to abort the transfer and WRITEFUNC_PAUSE to pause the transfer - -Reported By: Zdenek Pavlas -Bug: https://bugzilla.redhat.com/857875 ---- - src/pycurl.c | 11 +++++------ - 1 files changed, 5 insertions(+), 6 deletions(-) - -diff --git a/src/pycurl.c b/src/pycurl.c -index f197145..b59eeb8 100644 ---- a/src/pycurl.c -+++ b/src/pycurl.c -@@ -1081,12 +1081,8 @@ util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *strea - ret = total_size; /* None means success */ - } - else if (PyInt_Check(result) || PyLong_Check(result)) { -- long obj_size = PyLong_AsLong(result); -- if (obj_size < 0 || obj_size > total_size) { -- PyErr_Format(ErrorObject, "invalid return value for write callback %ld %ld", (long)obj_size, (long)total_size); -- goto verbose_error; -- } -- ret = (size_t) obj_size; /* success */ -+ /* if the cast to long fails, PyLong_AsLong() returns -1L */ -+ ret = (size_t) PyLong_AsLong(result); - } - else { - PyErr_SetString(ErrorObject, "write callback must return int or None"); -@@ -3423,6 +3419,9 @@ initpycurl(void) - /* Abort curl_read_callback(). */ - insint_c(d, "READFUNC_ABORT", CURL_READFUNC_ABORT); - -+ /* Pause curl_write_callback(). */ -+ insint_c(d, "WRITEFUNC_PAUSE", CURL_WRITEFUNC_PAUSE); -+ - /* constants for ioctl callback return values */ - insint_c(d, "IOE_OK", CURLIOE_OK); - insint_c(d, "IOE_UNKNOWNCMD", CURLIOE_UNKNOWNCMD); --- -1.7.1 - diff --git a/0104-test_write_abort.py-test-returning-1-from-write-call.patch b/0104-test_write_abort.py-test-returning-1-from-write-call.patch deleted file mode 100644 index b6d4168..0000000 --- a/0104-test_write_abort.py-test-returning-1-from-write-call.patch +++ /dev/null @@ -1,59 +0,0 @@ -From d8e4390da96d19a322a6fd3512ccac0200f15ddb Mon Sep 17 00:00:00 2001 -From: Kamil Dudka -Date: Wed, 6 Mar 2013 14:38:01 +0100 -Subject: [PATCH 4/4] test_write_abort.py: test returning -1 from write callback - ---- - Makefile | 1 + - tests/test_write_abort.py | 27 +++++++++++++++++++++++++++ - 2 files changed, 28 insertions(+), 0 deletions(-) - create mode 100755 tests/test_write_abort.py - -diff --git a/Makefile b/Makefile -index 9b2369d..10b95a7 100644 ---- a/Makefile -+++ b/Makefile -@@ -16,6 +16,7 @@ build-7.10.8: - - test: build - $(PYTHON) tests/test_internals.py -q -+ $(PYTHON) tests/test_write_abort.py -q - - # (needs GNU binutils) - strip: build -diff --git a/tests/test_write_abort.py b/tests/test_write_abort.py -new file mode 100755 -index 0000000..28e7d1f ---- /dev/null -+++ b/tests/test_write_abort.py -@@ -0,0 +1,27 @@ -+#!/usr/bin/python -+import os.path -+import pycurl -+import sys -+ -+pycurl.global_init(pycurl.GLOBAL_DEFAULT) -+ -+def write_cb(_): -+ # this should cause pycurl.WRITEFUNCTION (without any range errors) -+ return -1 -+ -+# download the script itself through the file:// protocol into write_cb -+c = pycurl.Curl() -+c.setopt(pycurl.URL, 'file://' + os.path.abspath(sys.argv[0])) -+c.setopt(pycurl.WRITEFUNCTION, write_cb) -+try: -+ c.perform() -+except pycurl.error, (err, msg): -+ # we expect pycurl.E_WRITE_ERROR as the response -+ assert pycurl.E_WRITE_ERROR == err -+ -+# no additional errors should be reported -+assert not hasattr(sys, 'last_value') -+ -+c.close() -+ -+pycurl.global_cleanup() --- -1.7.1 - diff --git a/python-pycurl.spec b/python-pycurl.spec index ca7c90d..d9be513 100644 --- a/python-pycurl.spec +++ b/python-pycurl.spec @@ -2,7 +2,7 @@ Name: python-pycurl Version: 7.19.0 -Release: 15%{?dist} +Release: 16.20120408git9b8f4e38%{?dist} Summary: A Python interface to libcurl Group: Development/Languages @@ -10,23 +10,16 @@ License: LGPLv2+ or MIT URL: http://pycurl.sourceforge.net/ Source0: http://pycurl.sourceforge.net/download/pycurl-%{version}.tar.gz -# upstream patches -Patch1: 0001-No-longer-keep-copies-of-string-options-since-this-i.patch -Patch2: 0002-Fixes-https-sourceforge.net-tracker-func-detail-aid-.patch -Patch3: 0003-Fixes-refcount-bug-and-provides-better-organization-.patch -Patch4: 0004-Test-for-reset-fixes-refcount-bug.patch -Patch5: 0005-Updating-ChangeLog-with-relevant-changes.patch - -# downstream patches -Patch101: 0101-test_internals.py-add-a-test-for-ref-counting-of-res.patch -Patch102: 0102-pycurl.c-eliminate-duplicated-code-in-util_write_cal.patch -Patch103: 0103-pycurl.c-allow-to-return-1-from-write-callback.patch -Patch104: 0104-test_write_abort.py-test-returning-1-from-write-call.patch +# sync with upstream's 9b8f4e38 +Patch0: 0000-pycurl-7.19.7-9b8f4e38.patch Requires: keyutils-libs BuildRequires: python-devel BuildRequires: curl-devel >= 7.19.0 BuildRequires: openssl-devel +BuildRequires: python-bottle +BuildRequires: python-nose +BuildRequires: vsftpd # During its initialization, PycURL checks that the actual libcurl version # is not lower than the one used when PycURL was built. @@ -48,16 +41,12 @@ of features. %prep %setup0 -q -n pycurl-%{version} -%patch1 -p1 -%patch2 -p1 -%patch3 -p1 -%patch4 -p1 -%patch5 -p1 -%patch101 -p1 -%patch102 -p1 -%patch103 -p1 -%patch104 -p1 -chmod a-x examples/* + +# drop CVS stuff that would prevent git patches from being applied +find -type f | xargs sed -i 's/\$Id: [^$]*\$/$Id$/' + +# upstream patches +%patch0 -p1 %build CFLAGS="$RPM_OPT_FLAGS -DHAVE_CURL_OPENSSL" %{__python} setup.py build @@ -71,10 +60,13 @@ make test PYTHON=%{__python} rm -rf %{buildroot}%{_datadir}/doc/pycurl %files -%doc COPYING COPYING2 ChangeLog README TODO examples doc tests +%doc COPYING COPYING2 ChangeLog README.rst TODO examples doc tests %{python_sitearch}/* %changelog +* Tue Apr 09 2013 Kamil Dudka - 7.19.0-16.20120408git9b8f4e38 +- sync with upstream 9b8f4e38 (fixes #928370) + * Wed Mar 06 2013 Kamil Dudka - 7.19.0-15 - allow to return -1 from the write callback (#857875) - remove the patch for curl-config --static-libs no longer needed