diff --git a/0000-pycurl-7.19.7-8d654296.patch b/0000-pycurl-7.19.7-8d654296.patch new file mode 100644 index 0000000..c802a64 --- /dev/null +++ b/0000-pycurl-7.19.7-8d654296.patch @@ -0,0 +1,12436 @@ +From 9a426178c83f756ec248e9fd0010030ae7c62f2a Mon Sep 17 00:00:00 2001 +From: Kjetil Jacobsen +Date: Tue, 9 Sep 2008 18:49:59 +0000 +Subject: [PATCH 001/149] 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 b5f6dd9fd839e54db567d0451483a400edad60a4 Mon Sep 17 00:00:00 2001 +From: Kjetil Jacobsen +Date: Mon, 29 Sep 2008 10:56:57 +0000 +Subject: [PATCH 002/149] 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 f241a60acc67cc8cbf3d36a2f3d55e1abf88bc33 Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Fri, 23 Apr 2010 16:06:41 +0000 +Subject: [PATCH 003/149] 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 70114226c76ee06c6bacb488734796551ac9b2f9 Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Wed, 28 Apr 2010 16:02:41 +0000 +Subject: [PATCH 004/149] 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 a607b0c9f4676858fcf6335e9fb0c9ed6a9c3069 Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Wed, 28 Apr 2010 16:03:40 +0000 +Subject: [PATCH 005/149] 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 f773340ec40d5988d4a5b75e64b898dbe4a89800 Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Wed, 28 Apr 2010 16:06:35 +0000 +Subject: [PATCH 006/149] 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 67e0b9ac7c521b39d7dc779a79d21009f5619fa1 Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Tue, 4 May 2010 18:47:08 +0000 +Subject: [PATCH 007/149] 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 310638226dbe77b71a8eb9f8da58cf15d236337a Mon Sep 17 00:00:00 2001 +From: Christopher Warner +Date: Wed, 13 Oct 2010 15:53:40 +0000 +Subject: [PATCH 008/149] 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 bd8a7fa3279613f41c503f5946d2576d4df70865 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Tue, 26 Feb 2013 09:13:24 +0100 +Subject: [PATCH 009/149] 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 de45f3fc04323c619a879e835f8f212c8c887364 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Mon, 25 Feb 2013 19:50:18 +0100 +Subject: [PATCH 010/149] 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 be80a76e848ea77764da165d517ae0a8aca64604 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 01:15:00 -0500 +Subject: [PATCH 011/149] 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 c0bee23c728e61874fc2f89d800685c32b331f80 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 1 Mar 2013 00:11:47 -0500 +Subject: [PATCH 012/149] 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 c178c8f0b6ca9a839cd344b4ebe9e561d804d297 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 1 Mar 2013 13:35:59 -0500 +Subject: [PATCH 013/149] 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 a2a23e3e89e9d4cf7e61bf0259534fb0117ddf09 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 1 Mar 2013 13:36:08 -0500 +Subject: [PATCH 014/149] 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 72ac92b738b9709e84ebd56fd055286c173e5fee Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 1 Mar 2013 14:06:42 -0500 +Subject: [PATCH 015/149] 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 83482f1c46d57074fa985def4f0aa62cc0df15d6 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 1 Mar 2013 14:07:04 -0500 +Subject: [PATCH 016/149] 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 9f92a0e68c031e26012db508c2a9482f8a466f6c Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sat, 2 Mar 2013 02:23:27 -0500 +Subject: [PATCH 017/149] 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 0289b4917a8510e174fe2237c28bdd219be3ef3f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sat, 2 Mar 2013 02:24:11 -0500 +Subject: [PATCH 018/149] 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 972cd4d7322aaa68074e44d5c9200a220fbb2b7e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sat, 2 Mar 2013 02:43:57 -0500 +Subject: [PATCH 019/149] 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 ece77e7257bea16a9a9d4dd5efd948358974bc4a Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sat, 2 Mar 2013 02:44:24 -0500 +Subject: [PATCH 020/149] 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 aa359d2da90735e06d9c5dcf387e472e4b11f60d Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sat, 2 Mar 2013 04:06:00 -0500 +Subject: [PATCH 021/149] 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 517c8f2dd4dc519fd733e049abab704e052866b6 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:14:26 -0500 +Subject: [PATCH 022/149] 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 cc68fffc8365c4f8e761a586d5e74beb5fd71473 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:14:41 -0500 +Subject: [PATCH 023/149] 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 f7055358a57d0fa9bcef3f0ad38a63ff5422178c Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:22:05 -0500 +Subject: [PATCH 024/149] 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 bb6edcd11b75949aa4c7b7513ce70f8a16a8a857 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:34:39 -0500 +Subject: [PATCH 025/149] 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 83a7d49e7e27f499e9d400a1af09e07fcc595c2f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:35:25 -0500 +Subject: [PATCH 026/149] 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 44ff45d8644789a098ea68b196b5f5cea5fbfd35 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:36:34 -0500 +Subject: [PATCH 027/149] 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 f14cdea639d6976f8dd8077be28c4452d4c8d1cf Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 00:41:35 -0500 +Subject: [PATCH 028/149] 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 2491ddd3f028059b4e86338ad64413de75db28af Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 01:35:21 -0500 +Subject: [PATCH 029/149] 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 ead7f2743a7bd6b497d994ca11dc823a144d50d7 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 01:44:22 -0500 +Subject: [PATCH 030/149] 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 7c48535bef19bf13a9d8aef377d4465490a588f2 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 02:05:30 -0500 +Subject: [PATCH 031/149] 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 c7b8f39437e953deb8af1b17f366a6242db28d5d Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 02:16:10 -0500 +Subject: [PATCH 032/149] 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 6fa36d3a070e784fc679427a6c56725ee30375e8 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 03:00:20 -0500 +Subject: [PATCH 033/149] 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 cef807dfde5e1ca931947ec37d0171227899e72f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 03:06:26 -0500 +Subject: [PATCH 034/149] 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 5a9488e0e5f75e70faf37b419819ce18878f29ed Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 12:55:42 -0500 +Subject: [PATCH 035/149] 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 ddeba20f213aea07a2491df171b899b2453de70b Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 14:05:49 -0500 +Subject: [PATCH 036/149] 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 bd01383371db110534ea39c4ce35563d63a21f73 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 14:15:13 -0500 +Subject: [PATCH 037/149] 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 587c4b3403bf16ce5ab8a72e738ac26dab4a31c8 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 3 Mar 2013 14:21:33 -0500 +Subject: [PATCH 038/149] 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 cd0e96615c24173e38177395931899aaa566c270 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 00:24:44 -0500 +Subject: [PATCH 039/149] 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 d59cc0dffc1f70545317c03307211559afe46b6d Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 00:38:59 -0500 +Subject: [PATCH 040/149] 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 9b239643fd425e600b467f91ce7ea0560dd88b79 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 00:40:25 -0500 +Subject: [PATCH 041/149] 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 c16f8df53e9f97d2ef4c0acb19e4abcda5825260 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 22:05:34 -0500 +Subject: [PATCH 042/149] 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 9a0ae278c27b16fa7dc745c2add39101a13fc9ec Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 22:29:45 -0500 +Subject: [PATCH 043/149] 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 7631c7c047354c1dacb09459fefd6ea9ca11a28b Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:15:47 -0500 +Subject: [PATCH 044/149] 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 eb523b0b4e3729d6613e1841d3d3715ad6e58109 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:17:14 -0500 +Subject: [PATCH 045/149] 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 c4ad7c4b2cf2107fb4dfcf468fef554bb8f1a38f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:30:48 -0500 +Subject: [PATCH 046/149] 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 789b647eff13181fa527cb218dd487695abf3515 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:35:02 -0500 +Subject: [PATCH 047/149] 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 2f56e4844e78280410f781b04d43ac49ce08129e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:50:51 -0500 +Subject: [PATCH 048/149] 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 c8f91c64ad1eb067907e9b97bd5f8bfa27c29062 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:51:45 -0500 +Subject: [PATCH 049/149] 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 9ac9dce97226d34e6bb077a0357063fd23056158 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 5 Mar 2013 23:59:04 -0500 +Subject: [PATCH 050/149] 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 8a4babacffc0bbeacdfcfb3f9888f26efd6c072c Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:10:41 -0500 +Subject: [PATCH 051/149] 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 bf65a3bac3446950421927da8230216acefebf86 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:13:46 -0500 +Subject: [PATCH 052/149] 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 c5f46d8247e3cbf3d2a0a2cf74c7daeade97c1d8 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:26:45 -0500 +Subject: [PATCH 053/149] 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 4aba306862de76d584af790bf2dd90f0d55fda0e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:55:41 -0500 +Subject: [PATCH 054/149] 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 8fc24d93e2188d68c738e602c75753cec503de7d Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:56:44 -0500 +Subject: [PATCH 055/149] 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 d3a77eabe019b1707a7bdf8cbae32d1eddd12f9c Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 01:29:59 -0500 +Subject: [PATCH 056/149] 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 3ea332ffec21dfe6ddf3e0bc66a101259f88f216 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 01:32:33 -0500 +Subject: [PATCH 057/149] 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 990569ee36eac37c03f28c86b2a8cfc7a29752ab Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 01:36:48 -0500 +Subject: [PATCH 058/149] 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 3b7bb39d6b33031ff1efd03abdc174caa32ef3bc Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 03:25:34 -0500 +Subject: [PATCH 059/149] 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 ce0c221f9db95029982619afd5cc154d68a97334 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:01:13 -0500 +Subject: [PATCH 060/149] 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 0457d78a5bd77c11ac67698f125c596f16f05da6 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:37:19 -0500 +Subject: [PATCH 061/149] 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 25e7d646b9d5c80e8987528bf1b0031622ab440b Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 00:37:41 -0500 +Subject: [PATCH 062/149] 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 f19bc32a8351b67ab3690b3ee0d4fe6843749a9b Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 13:36:38 -0500 +Subject: [PATCH 063/149] 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 61d61649b7687c85bdcef3da3650bcce03c4735d Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:25:47 -0500 +Subject: [PATCH 064/149] 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 8ceafd381dd7bca305686e33529a207b6544e2b4 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:37:47 -0500 +Subject: [PATCH 065/149] 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 1365d5ca2d70074d588cd23392862264494f0eeb Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:45:46 -0500 +Subject: [PATCH 066/149] 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 99896926cfdfc180f8c330cf2371c6cb023b4bac Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:50:21 -0500 +Subject: [PATCH 067/149] 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 ce37bf646c42352895ba83c6bf7364012666c5c7 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:20:25 -0500 +Subject: [PATCH 068/149] 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 bc7eb4aad84f8249356c9d221fd5f404943f6699 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 7 Mar 2013 04:23:52 -0500 +Subject: [PATCH 069/149] 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 0d6217a0204fe48fae9b6db0b5b73f2a71cdb90c Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 7 Mar 2013 04:30:23 -0500 +Subject: [PATCH 070/149] 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 1bcb3684a9ba30411c675ddc2e75e9a5a5def311 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:50:34 -0500 +Subject: [PATCH 071/149] 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 05d4fbe859d886ec0f0d104b5114a0f416021097 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:10:22 -0500 +Subject: [PATCH 072/149] 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 f29edfdd24ee3524a2674ba149c8a8d25741263c Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:18:02 -0500 +Subject: [PATCH 073/149] 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 28f459a96fcc6c74afa50b7b3c01a2dd75bd59f8 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:23:08 -0500 +Subject: [PATCH 074/149] 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 8e2ee4ec10741ae7373f3c490df6696267871c12 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:54:35 -0500 +Subject: [PATCH 075/149] 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 e7da1d864d955f21f31bdf880ed903abbee7c2ba Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 04:58:42 -0500 +Subject: [PATCH 076/149] 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 ee685a967f67e26ae32ec0c1d34fbad461fce2e7 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:14:57 -0500 +Subject: [PATCH 077/149] 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 359fc6e31a21620ea564288ae43ae23db35d1b2e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:07:19 -0500 +Subject: [PATCH 078/149] 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 4c65d037fce349811d4954db137d0b3d5b3abe85 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:09:22 -0500 +Subject: [PATCH 079/149] 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 831b7a919d47b55e787c8af457b6ec815fca2035 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 06:34:39 -0500 +Subject: [PATCH 080/149] 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 6730160a7ede5c1bb490836cb42269d06a497dbe Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 22:15:47 -0500 +Subject: [PATCH 081/149] 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 283ebc888e8016f575457db70a81eb46f219467c Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 22:17:48 -0500 +Subject: [PATCH 082/149] 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 0034724dd8ec8526f361c067d72e87c930f6eeb3 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 22:26:19 -0500 +Subject: [PATCH 083/149] 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 d794edf43a119d4f600d3be8536c22419c9f4abb Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 22:27:25 -0500 +Subject: [PATCH 084/149] 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 c1143004a0f27190fc4aec1228396db992f5c5de Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 6 Mar 2013 22:30:44 -0500 +Subject: [PATCH 085/149] 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 abc0199b4b2517a82c492cd3f64f10883a5ec257 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 7 Mar 2013 00:16:21 -0500 +Subject: [PATCH 086/149] 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 14dd7fdbe5697f7e7349cbe44258f41f74455fc0 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 22:19:14 -0400 +Subject: [PATCH 087/149] 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 1505197b9eb8d5ccbc3d039866e87d522455d9d0 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 22:22:02 -0400 +Subject: [PATCH 088/149] 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 041b4666519f3fa2594f5ee919e62006cb8ae2f1 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Mon, 11 Mar 2013 14:00:04 +0100 +Subject: [PATCH 089/149] 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 3dc27e280021512e84cd77f68c7ff3d4e5cb2ae0 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 22:40:12 -0400 +Subject: [PATCH 090/149] 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 d6c800cdc963e349921cae8ac10df9f9f23cac79 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Mon, 11 Mar 2013 14:03:06 +0100 +Subject: [PATCH 091/149] 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 8acdaaf5c311038a5dd7fcd81f13c0bb984b66c7 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Tue, 26 Feb 2013 14:49:47 +0100 +Subject: [PATCH 092/149] 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 b55c2cab56733a86b4ebc5ccccdf8c5530bca85a Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Tue, 26 Feb 2013 16:58:55 +0100 +Subject: [PATCH 093/149] 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 32664e552084fad471de98f219f58c52c95653f4 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Wed, 6 Mar 2013 14:38:01 +0100 +Subject: [PATCH 094/149] 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 808c5b785ccd2f3bcca15146ae998a8609229b38 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 23:48:10 -0400 +Subject: [PATCH 095/149] 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 c662b504c202ca434d723de3984c12a35358e9f8 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 23:48:41 -0400 +Subject: [PATCH 096/149] 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 43d385231293a22fb65490db8348c9a18592c7b0 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Mon, 11 Mar 2013 23:56:36 -0400 +Subject: [PATCH 097/149] 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 c04e16173e67b91c47018a5f023d703f751ba23e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 12 Mar 2013 00:00:40 -0400 +Subject: [PATCH 098/149] 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 a14d3afd57c9a402e8256a53cdcc389008356f3e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 12 Mar 2013 00:02:56 -0400 +Subject: [PATCH 099/149] 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 2af85c62b266127bd9e58d1df2b1fb079a0e9654 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 12 Mar 2013 22:43:47 -0400 +Subject: [PATCH 100/149] 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 d1c79723cecd7ee75181f29b46081fc49ea28790 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 12 Mar 2013 22:55:08 -0400 +Subject: [PATCH 101/149] 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 cb24d7050133cbed97c7f05ef6f649c7a7bfede3 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 15 Mar 2013 12:28:11 -0400 +Subject: [PATCH 102/149] 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 7a74c1e39377d4220ffafe335a71487515bede8e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 15 Mar 2013 17:01:35 -0400 +Subject: [PATCH 103/149] 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 84bb12176059f28c0cc030d6b620656a00fff3a4 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 15 Mar 2013 21:21:50 -0400 +Subject: [PATCH 104/149] 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 d30ec2cf96c66cd176bb999fb7a3a39d13ddf0dd Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 15 Mar 2013 21:27:58 -0400 +Subject: [PATCH 105/149] 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 c903dc9eebdf010bfc8142b5f47cd564e04baa19 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 26 Feb 2013 16:40:28 -0500 +Subject: [PATCH 106/149] 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 2643ab0cd015f09b1a0fd928c8e1102eee8e203e Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 27 Feb 2013 20:23:28 -0500 +Subject: [PATCH 107/149] 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 c2f5306b77da24f1cb1cf45b9dbda93b817bf6e6 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 26 Feb 2013 16:49:09 -0500 +Subject: [PATCH 108/149] 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 0106dfe86ca2a5620f07c2687cb05502636298ce Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 27 Feb 2013 20:23:39 -0500 +Subject: [PATCH 109/149] 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 bb811ad5b2d2819d502c84c735b7efec84d4d557 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 28 Feb 2013 00:30:00 -0500 +Subject: [PATCH 110/149] 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 2ea28565bcf16bb2e792467650012081e2268cf1 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Mon, 8 Apr 2013 14:25:26 +0200 +Subject: [PATCH 111/149] 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 576e87580356497b601ec16a9a794db959d43f0e Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Mon, 8 Apr 2013 14:31:30 +0200 +Subject: [PATCH 112/149] 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 + + +From 5c27a721fc3aaeaeddabb394ae6c9502391bdea6 Mon Sep 17 00:00:00 2001 +From: Zdenek Pavlas +Date: Wed, 13 Mar 2013 16:55:58 +0100 +Subject: [PATCH 113/149] add the GLOBAL_ACK_EINTR constant to the list of exported symbols + +... if built against a new enough version of libcurl + +Bug: https://bugzilla.redhat.com/920589 + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 19 +++++++++++++++---- + 1 files changed, 15 insertions(+), 4 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index 619ca20..9950e00 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -3210,6 +3210,16 @@ static PyTypeObject CurlMulti_Type = { + */ + }; + ++static int ++are_global_init_flags_valid(int flags) ++{ ++#ifdef CURL_GLOBAL_ACK_EINTR ++ /* CURL_GLOBAL_ACK_EINTR was introduced in libcurl-7.30.0 */ ++ return !(flags & ~(CURL_GLOBAL_ALL | CURL_GLOBAL_ACK_EINTR)); ++#else ++ return !(flags & ~(CURL_GLOBAL_ALL)); ++#endif ++} + + /************************************************************************* + // module level +@@ -3227,10 +3237,7 @@ do_global_init(PyObject *dummy, PyObject *args) + return NULL; + } + +- if (!(option == CURL_GLOBAL_SSL || +- option == CURL_GLOBAL_WIN32 || +- option == CURL_GLOBAL_ALL || +- option == CURL_GLOBAL_NOTHING)) { ++ if (!are_global_init_flags_valid(option)) { + PyErr_SetString(PyExc_ValueError, "invalid option to global_init"); + return NULL; + } +@@ -3866,6 +3873,10 @@ initpycurl(void) + insint(d, "GLOBAL_ALL", CURL_GLOBAL_ALL); + insint(d, "GLOBAL_NOTHING", CURL_GLOBAL_NOTHING); + insint(d, "GLOBAL_DEFAULT", CURL_GLOBAL_DEFAULT); ++#ifdef CURL_GLOBAL_ACK_EINTR ++ /* CURL_GLOBAL_ACK_EINTR was introduced in libcurl-7.30.0 */ ++ insint(d, "GLOBAL_ACK_EINTR", CURL_GLOBAL_ACK_EINTR); ++#endif + + + /* constants for curl_multi_socket interface */ +-- +1.7.1 + + +From 5a165baaa2b275293c1e045d8c0023d55bd0cbfc Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Wed, 3 Apr 2013 15:06:32 +0200 +Subject: [PATCH 114/149] tests/global_init_ack_eintr.py: test GLOBAL_ACK_EINTR + +... if we have a new enough version of libcurl + +Signed-off-by: Kamil Dudka +--- + tests/global_init_ack_eintr.py | 22 ++++++++++++++++++++++ + 1 files changed, 22 insertions(+), 0 deletions(-) + create mode 100644 tests/global_init_ack_eintr.py + +diff --git a/tests/global_init_ack_eintr.py b/tests/global_init_ack_eintr.py +new file mode 100644 +index 0000000..429fc3f +--- /dev/null ++++ b/tests/global_init_ack_eintr.py +@@ -0,0 +1,22 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++ ++from . import util ++ ++class GlobalInitAckEintrTest(unittest.TestCase): ++ def test_global_init_default(self): ++ # initialize libcurl with DEFAULT flags ++ pycurl.global_init(pycurl.GLOBAL_DEFAULT) ++ pycurl.global_cleanup() ++ ++ def test_global_init_ack_eintr(self): ++ # the GLOBAL_ACK_EINTR flag was introduced in libcurl-7.30, but can also ++ # be backported for older versions of libcurl at the distribution level ++ if not util.pycurl_version_less_than(7, 30) or hasattr(pycurl, 'GLOBAL_ACK_EINTR'): ++ # initialize libcurl with the GLOBAL_ACK_EINTR flag ++ pycurl.global_init(pycurl.GLOBAL_ACK_EINTR) ++ pycurl.global_cleanup() +-- +1.7.1 + + +From 788e012e64250cfb473d201a836e1a92c7772154 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 9 Apr 2013 18:52:41 -0400 +Subject: [PATCH 115/149] Add a test for bogus init flags and fix test name + +Signed-off-by: Kamil Dudka +--- + tests/global_init_ack_eintr.py | 22 ---------------------- + tests/global_init_test.py | 28 ++++++++++++++++++++++++++++ + 2 files changed, 28 insertions(+), 22 deletions(-) + delete mode 100644 tests/global_init_ack_eintr.py + create mode 100644 tests/global_init_test.py + +diff --git a/tests/global_init_ack_eintr.py b/tests/global_init_ack_eintr.py +deleted file mode 100644 +index 429fc3f..0000000 +--- a/tests/global_init_ack_eintr.py ++++ /dev/null +@@ -1,22 +0,0 @@ +-#! /usr/bin/env python +-# -*- coding: iso-8859-1 -*- +-# vi:ts=4:et +- +-import pycurl +-import unittest +- +-from . import util +- +-class GlobalInitAckEintrTest(unittest.TestCase): +- def test_global_init_default(self): +- # initialize libcurl with DEFAULT flags +- pycurl.global_init(pycurl.GLOBAL_DEFAULT) +- pycurl.global_cleanup() +- +- def test_global_init_ack_eintr(self): +- # the GLOBAL_ACK_EINTR flag was introduced in libcurl-7.30, but can also +- # be backported for older versions of libcurl at the distribution level +- if not util.pycurl_version_less_than(7, 30) or hasattr(pycurl, 'GLOBAL_ACK_EINTR'): +- # initialize libcurl with the GLOBAL_ACK_EINTR flag +- pycurl.global_init(pycurl.GLOBAL_ACK_EINTR) +- pycurl.global_cleanup() +diff --git a/tests/global_init_test.py b/tests/global_init_test.py +new file mode 100644 +index 0000000..b76254b +--- /dev/null ++++ b/tests/global_init_test.py +@@ -0,0 +1,28 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++import nose.tools ++ ++from . import util ++ ++class GlobalInitTest(unittest.TestCase): ++ def test_global_init_default(self): ++ # initialize libcurl with DEFAULT flags ++ pycurl.global_init(pycurl.GLOBAL_DEFAULT) ++ pycurl.global_cleanup() ++ ++ def test_global_init_ack_eintr(self): ++ # the GLOBAL_ACK_EINTR flag was introduced in libcurl-7.30, but can also ++ # be backported for older versions of libcurl at the distribution level ++ if not util.pycurl_version_less_than(7, 30) or hasattr(pycurl, 'GLOBAL_ACK_EINTR'): ++ # initialize libcurl with the GLOBAL_ACK_EINTR flag ++ pycurl.global_init(pycurl.GLOBAL_ACK_EINTR) ++ pycurl.global_cleanup() ++ ++ @nose.tools.raises(ValueError) ++ def test_global_init_bogus(self): ++ # initialize libcurl with bogus flags ++ pycurl.global_init(0xffff) +-- +1.7.1 + + +From 91992e2ef3664d68c8e98269b78911b929ad991a Mon Sep 17 00:00:00 2001 +From: anonymous +Date: Tue, 26 Feb 2013 16:32:20 -0500 +Subject: [PATCH 116/149] Python 3 support. + +http://sourceforge.net/tracker/?func=detail&aid=2917775&group_id=28236&atid=392779 + +Signed-off-by: Kamil Dudka +--- + setup.py | 18 +++++++++--------- + 1 files changed, 9 insertions(+), 9 deletions(-) + +diff --git a/setup.py b/setup.py +index 725007c..e0d0379 100644 +--- a/setup.py ++++ b/setup.py +@@ -31,7 +31,7 @@ def scan_argv(s, default): + i = 1 + while i < len(sys.argv): + arg = sys.argv[i] +- if string.find(arg, s) == 0: ++ if str.find(arg, s) == 0: + p = arg[len(s):] + assert p, arg + del sys.argv[i] +@@ -46,8 +46,8 @@ def add_libdirs(envvar, sep, fatal=0): + v = os.environ.get(envvar) + if not v: + return +- for dir in string.split(v, sep): +- dir = string.strip(dir) ++ for dir in str.split(v, sep): ++ dir = str.strip(dir) + if not dir: + continue + dir = os.path.normpath(dir) +@@ -55,7 +55,7 @@ def add_libdirs(envvar, sep, fatal=0): + if not dir in library_dirs: + library_dirs.append(dir) + elif fatal: +- print "FATAL: bad directory %s in environment variable %s" % (dir, envvar) ++ print("FATAL: bad directory %s in environment variable %s" % (dir, envvar)) + sys.exit(1) + + +@@ -65,13 +65,13 @@ if sys.platform == "win32": + # and thus unlikely to match your installation. + CURL_DIR = r"c:\src\build\pycurl\curl-7.16.2.1" + CURL_DIR = scan_argv("--curl-dir=", CURL_DIR) +- print "Using curl directory:", CURL_DIR ++ print("Using curl directory:", CURL_DIR) + assert os.path.isdir(CURL_DIR), "please check CURL_DIR in setup.py" + include_dirs.append(os.path.join(CURL_DIR, "include")) + extra_objects.append(os.path.join(CURL_DIR, "lib", "libcurl.lib")) + extra_link_args.extend(["gdi32.lib", "wldap32.lib", "winmm.lib", "ws2_32.lib",]) + add_libdirs("LIB", ";") +- if string.find(sys.version, "MSC") >= 0: ++ if str.find(sys.version, "MSC") >= 0: + extra_compile_args.append("-O2") + extra_compile_args.append("-GF") # enable read-only string pooling + extra_compile_args.append("-WX") # treat warnings as errors +@@ -85,10 +85,10 @@ else: + CURL_CONFIG = scan_argv("--curl-config=", CURL_CONFIG) + d = os.popen("'%s' --version" % CURL_CONFIG).read() + if d: +- d = string.strip(d) ++ d = str.strip(d) + if not d: +- raise Exception, ("`%s' not found -- please install the libcurl development files" % CURL_CONFIG) +- print "Using %s (%s)" % (CURL_CONFIG, d) ++ raise Exception("`%s' not found -- please install the libcurl development files" % CURL_CONFIG) ++ print("Using %s (%s)" % (CURL_CONFIG, d)) + for e in split_quoted(os.popen("'%s' --cflags" % CURL_CONFIG).read()): + if e[:2] == "-I": + # do not add /usr/include +-- +1.7.1 + + +From 6f6649b79ea66493e878eda3e3cfd87cba18a02f Mon Sep 17 00:00:00 2001 +From: decitre +Date: Tue, 26 Feb 2013 16:36:32 -0500 +Subject: [PATCH 117/149] Corrected Python 3 support. + +http://sourceforge.net/tracker/?func=detail&aid=3188495&group_id=28236&atid=392779 + +Signed-off-by: Kamil Dudka +--- + setup.py | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/setup.py b/setup.py +index e0d0379..385facb 100644 +--- a/setup.py ++++ b/setup.py +@@ -106,7 +106,7 @@ else: + stdout=subprocess.PIPE) + (stdout, stderr) = p.communicate() + if p.wait() == 0: +- optbuf += stdout ++ optbuf += stdout.decode() + if optbuf == "": + raise Exception("Neither of curl-config --libs or --static-libs" + + "produced output") +-- +1.7.1 + + +From d256095c74857a87fa7e3c9dbb0df58cdeded17f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 28 Feb 2013 00:14:03 -0500 +Subject: [PATCH 118/149] Python 3 compatibility: StringIO (src) + +Signed-off-by: Kamil Dudka +--- + python/curl/__init__.py | 5 ++++- + 1 files changed, 4 insertions(+), 1 deletions(-) + +diff --git a/python/curl/__init__.py b/python/curl/__init__.py +index 5617262..3ab019f 100644 +--- a/python/curl/__init__.py ++++ b/python/curl/__init__.py +@@ -10,7 +10,10 @@ import os, sys, urllib, exceptions, mimetools, pycurl + try: + from cStringIO import StringIO + except ImportError: +- from StringIO import StringIO ++ try: ++ from StringIO import StringIO ++ except ImportError: ++ from io import StringIO + + try: + import signal +-- +1.7.1 + + +From 04ebd364f9b027e0bbdaeecfca02493244efcfbd Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 28 Feb 2013 00:11:58 -0500 +Subject: [PATCH 119/149] Python 3 compatibility: StringIO (examples) + +Signed-off-by: Kamil Dudka +--- + examples/xmlrpc_curl.py | 5 ++++- + 1 files changed, 4 insertions(+), 1 deletions(-) + +diff --git a/examples/xmlrpc_curl.py b/examples/xmlrpc_curl.py +index a9a7d9e..bb6799e 100644 +--- a/examples/xmlrpc_curl.py ++++ b/examples/xmlrpc_curl.py +@@ -14,7 +14,10 @@ except ImportError: + try: + from cStringIO import StringIO + except ImportError: +- from StringIO import StringIO ++ try: ++ from StringIO import StringIO ++ except ImportError: ++ from io import StringIO + import xmlrpclib, pycurl + + +-- +1.7.1 + + +From 936cceff39402efa5b91b2ff4d4d36c04c1675c8 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 28 Feb 2013 00:23:29 -0500 +Subject: [PATCH 120/149] Python 3 compatibility: xmlrpclib + +Signed-off-by: Kamil Dudka +--- + examples/tests/test_xmlrpc.py | 5 ++++- + examples/xmlrpc_curl.py | 6 +++++- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/examples/tests/test_xmlrpc.py b/examples/tests/test_xmlrpc.py +index 3a5469a..d64794e 100644 +--- a/examples/tests/test_xmlrpc.py ++++ b/examples/tests/test_xmlrpc.py +@@ -4,7 +4,10 @@ + # $Id$ + + ## XML-RPC lib included in python2.2 +-import xmlrpclib ++try: ++ import xmlrpclib ++except ImportError: ++ import xmlrpc.client as xmlrpclib + import pycurl + + # Header fields passed in request +diff --git a/examples/xmlrpc_curl.py b/examples/xmlrpc_curl.py +index bb6799e..21418b5 100644 +--- a/examples/xmlrpc_curl.py ++++ b/examples/xmlrpc_curl.py +@@ -18,7 +18,11 @@ except ImportError: + from StringIO import StringIO + except ImportError: + from io import StringIO +-import xmlrpclib, pycurl ++try: ++ import xmlrpclib ++except ImportError: ++ import xmlrpc.client as xmlrpclib ++import pycurl + + + class CURLTransport(xmlrpclib.Transport): +-- +1.7.1 + + +From 00ce4e4176540bc899713bcd2c364ad8d3887a8b Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 28 Feb 2013 00:34:01 -0500 +Subject: [PATCH 121/149] Python 3 compatibility: urllib (src) + +Signed-off-by: Kamil Dudka +--- + python/curl/__init__.py | 10 +++++++--- + 1 files changed, 7 insertions(+), 3 deletions(-) + +diff --git a/python/curl/__init__.py b/python/curl/__init__.py +index 3ab019f..fd179fc 100644 +--- a/python/curl/__init__.py ++++ b/python/curl/__init__.py +@@ -6,7 +6,11 @@ + # + # By Eric S. Raymond, April 2003. + +-import os, sys, urllib, exceptions, mimetools, pycurl ++import os, sys, exceptions, mimetools, pycurl ++try: ++ import urllib.parse as urllib_parse ++except ImportError: ++ import urllib as urllib_parse + try: + from cStringIO import StringIO + except ImportError: +@@ -86,14 +90,14 @@ class Curl: + def get(self, url="", params=None): + "Ship a GET request for a specified URL, capture the response." + if params: +- url += "?" + urllib.urlencode(params) ++ url += "?" + urllib_parse.urlencode(params) + self.set_option(pycurl.HTTPGET, 1) + return self.__request(url) + + def post(self, cgi, params): + "Ship a POST request to a specified CGI, capture the response." + self.set_option(pycurl.POST, 1) +- self.set_option(pycurl.POSTFIELDS, urllib.urlencode(params)) ++ self.set_option(pycurl.POSTFIELDS, urllib_parse.urlencode(params)) + return self.__request(cgi) + + def body(self): +-- +1.7.1 + + +From 59cde00a2b6f4329d19579c3e8982d194c71eb66 Mon Sep 17 00:00:00 2001 +From: Yuri Ushakov +Date: Thu, 18 Apr 2013 12:29:07 -0400 +Subject: [PATCH 122/149] Correctly handle big timeout values + +http://curl.haxx.se/mail/curlpython-2013-04/0000.html + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index 9950e00..388595c 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -2567,7 +2567,7 @@ do_multi_timeout(CurlMultiObject *self) + } + + /* Return number of millisecs until timeout */ +- return Py_BuildValue("i", timeout); ++ return Py_BuildValue("l", timeout); + } + + +-- +1.7.1 + + +From 575f40dd24cea343980b0c628327d9a1008fa021 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 30 May 2013 13:55:35 -0400 +Subject: [PATCH 123/149] Add support for CURLOPT_USERNAME / _PASSWORD + +Original patch by Wim Lewis (https://sourceforge.net/u/wiml/profile/). + +Original patch description: + +This is a really trivial patch to add support for the CURLOPT_USERNAME and +CURLOPT_PASSWORD options (and their _PROXY equivalents), which affect +the same parts of libcurl's state that CURLOPT_USERPWD does but which +don't require you to combine the username and password into one string first. +(Libcurl just immediately parses it apart again anyway.) + +I've tested against libcurl-7.21.0, and looked through the source for +libcurl 7.20.0 and 7.19.5 to verify that it looks like it should still do +the right things there (in particular that curl_easy_setopt(..., NULL) +does the reasonable thing with these options). + +https://sourceforge.net/p/pycurl/patches/10/ + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 31 +++++++++++++++++++++++++++++++ + tests/curlopt_test.py | 18 ++++++++++++++++++ + 2 files changed, 49 insertions(+), 0 deletions(-) + create mode 100644 tests/curlopt_test.py + +diff --git a/src/pycurl.c b/src/pycurl.c +index 388595c..9c76609 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -61,6 +61,13 @@ + # error "Need libcurl version 7.19.0 or greater to compile pycurl." + #endif + ++#if LIBCURL_VERSION_MAJOR >= 8 || \ ++ LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 20 || \ ++ LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR == 19 && LIBCURL_VERSION_PATCH >= 1 ++#define HAVE_CURLOPT_USERNAME ++#define HAVE_CURLOPT_PROXYUSERNAME ++#endif ++ + /* Python < 2.5 compat for Py_ssize_t */ + #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) + typedef int Py_ssize_t; +@@ -1558,9 +1565,17 @@ util_curl_unsetopt(CurlObject *self, int option) + case CURLOPT_EGDSOCKET: + case CURLOPT_FTPPORT: + case CURLOPT_PROXYUSERPWD: ++#ifdef HAVE_CURLOPT_PROXYUSERNAME ++ case CURLOPT_PROXYUSERNAME: ++ case CURLOPT_PROXYPASSWORD: ++#endif + case CURLOPT_RANDOM_FILE: + case CURLOPT_SSL_CIPHER_LIST: + case CURLOPT_USERPWD: ++#ifdef HAVE_CURLOPT_USERNAME ++ case CURLOPT_USERNAME: ++ case CURLOPT_PASSWORD: ++#endif + case CURLOPT_RANGE: + SETOPT((char *) 0); + break; +@@ -1658,6 +1673,10 @@ do_curl_setopt(CurlObject *self, PyObject *args) + case CURLOPT_NETRC_FILE: + case CURLOPT_PROXY: + case CURLOPT_PROXYUSERPWD: ++#ifdef HAVE_CURLOPT_PROXYUSERNAME ++ case CURLOPT_PROXYUSERNAME: ++ case CURLOPT_PROXYPASSWORD: ++#endif + case CURLOPT_RANDOM_FILE: + case CURLOPT_RANGE: + case CURLOPT_REFERER: +@@ -1671,6 +1690,10 @@ do_curl_setopt(CurlObject *self, PyObject *args) + case CURLOPT_URL: + case CURLOPT_USERAGENT: + case CURLOPT_USERPWD: ++#ifdef HAVE_CURLOPT_USERNAME ++ case CURLOPT_USERNAME: ++ case CURLOPT_PASSWORD: ++#endif + case CURLOPT_FTP_ALTERNATIVE_TO_USER: + case CURLOPT_SSH_PUBLIC_KEYFILE: + case CURLOPT_SSH_PRIVATE_KEYFILE: +@@ -3654,7 +3677,15 @@ initpycurl(void) + insint_c(d, "PORT", CURLOPT_PORT); + insint_c(d, "PROXY", CURLOPT_PROXY); + insint_c(d, "USERPWD", CURLOPT_USERPWD); ++#ifdef HAVE_CURLOPT_USERNAME ++ insint_c(d, "USERNAME", CURLOPT_USERNAME); ++ insint_c(d, "PASSWORD", CURLOPT_PASSWORD); ++#endif + insint_c(d, "PROXYUSERPWD", CURLOPT_PROXYUSERPWD); ++#ifdef HAVE_CURLOPT_PROXYUSERNAME ++ insint_c(d, "PROXYUSERNAME", CURLOPT_PROXYUSERNAME); ++ insint_c(d, "PROXYPASSWORD", CURLOPT_PROXYPASSWORD); ++#endif + insint_c(d, "RANGE", CURLOPT_RANGE); + insint_c(d, "INFILE", CURLOPT_READDATA); + /* ERRORBUFFER is not supported */ +diff --git a/tests/curlopt_test.py b/tests/curlopt_test.py +new file mode 100644 +index 0000000..9392a39 +--- /dev/null ++++ b/tests/curlopt_test.py +@@ -0,0 +1,18 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++import nose.tools ++ ++from . import util ++ ++class CurloptTest(unittest.TestCase): ++ def test_username(self): ++ # CURLOPT_USERNAME was introduced in libcurl-7.19.1 ++ if not util.pycurl_version_less_than(7, 19, 1): ++ assert hasattr(pycurl, 'USERNAME') ++ assert hasattr(pycurl, 'PASSWORD') ++ assert hasattr(pycurl, 'PROXYUSERNAME') ++ assert hasattr(pycurl, 'PROXYPASSWORD') +-- +1.7.1 + + +From db2f9a10ef399f0a69b2e4a215fe44752f780b96 Mon Sep 17 00:00:00 2001 +From: Adam Guthrie +Date: Thu, 30 May 2013 14:10:44 -0400 +Subject: [PATCH 124/149] Fixes invalid XHTML in documentation + +doc/callbacks.html and doc/curlshareobject.html were not valid xhtml. + +Thanks to Ivo Timmermans for the original report and patch to Ubuntu (LP: #364168) + +Signed-off-by: Kamil Dudka +--- + doc/callbacks.html | 2 +- + doc/curlshareobject.html | 3 ++- + 2 files changed, 3 insertions(+), 2 deletions(-) + +diff --git a/doc/callbacks.html b/doc/callbacks.html +index b98e0a9..a117acc 100644 +--- a/doc/callbacks.html ++++ b/doc/callbacks.html +@@ -120,7 +120,7 @@ VERBOSE option must be enabled for this callback to be invoked.

+ + +

Other examples

+-The pycurl distribution also contains a number of test scripts and ++

The pycurl distribution also contains a number of test scripts and + examples which show how to use the various callbacks in libcurl. + For instance, the file 'examples/file_upload.py' in the distribution contains + example code for using READFUNCTION, 'tests/test_cb.py' shows +diff --git a/doc/curlshareobject.html b/doc/curlshareobject.html +index c11cafb..2043e48 100644 +--- a/doc/curlshareobject.html ++++ b/doc/curlshareobject.html +@@ -14,6 +14,7 @@ + +

CurlShare objects have the following methods:

+ ++
+
setopt(option, value) -> None
+
+ +@@ -22,7 +23,7 @@ + href="http://curl.haxx.se/libcurl/c/curl_share_setopt.html">curl_share_setopt in libcurl, where + option is specified with the CURLSHOPT_* constants in libcurl, + except that the CURLSHOPT_ prefix has been changed to SH_. Currently, +-value must be either LOCK_DATA_COOKIE or LOCK_DATA_DNS. ++value must be either LOCK_DATA_COOKIE or LOCK_DATA_DNS.

+ +

Example usage:

+ +-- +1.7.1 + + +From 87aa7c722ddfefd38faef5a8a038c2e8e64db003 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 30 May 2013 15:27:02 -0400 +Subject: [PATCH 125/149] Use nose test skipping functionality + +Signed-off-by: Kamil Dudka +--- + tests/curlopt_test.py | 14 ++++++++------ + tests/global_init_test.py | 11 +++++++---- + 2 files changed, 15 insertions(+), 10 deletions(-) + +diff --git a/tests/curlopt_test.py b/tests/curlopt_test.py +index 9392a39..fd30394 100644 +--- a/tests/curlopt_test.py ++++ b/tests/curlopt_test.py +@@ -4,15 +4,17 @@ + + import pycurl + import unittest +-import nose.tools ++import nose.plugins.skip + + from . import util + + class CurloptTest(unittest.TestCase): + def test_username(self): + # CURLOPT_USERNAME was introduced in libcurl-7.19.1 +- if not util.pycurl_version_less_than(7, 19, 1): +- assert hasattr(pycurl, 'USERNAME') +- assert hasattr(pycurl, 'PASSWORD') +- assert hasattr(pycurl, 'PROXYUSERNAME') +- assert hasattr(pycurl, 'PROXYPASSWORD') ++ if util.pycurl_version_less_than(7, 19, 1): ++ raise nose.plugins.skip.SkipTest('libcurl < 7.19.1') ++ ++ assert hasattr(pycurl, 'USERNAME') ++ assert hasattr(pycurl, 'PASSWORD') ++ assert hasattr(pycurl, 'PROXYUSERNAME') ++ assert hasattr(pycurl, 'PROXYPASSWORD') +diff --git a/tests/global_init_test.py b/tests/global_init_test.py +index b76254b..443ca49 100644 +--- a/tests/global_init_test.py ++++ b/tests/global_init_test.py +@@ -5,6 +5,7 @@ + import pycurl + import unittest + import nose.tools ++import nose.plugins.skip + + from . import util + +@@ -17,10 +18,12 @@ class GlobalInitTest(unittest.TestCase): + def test_global_init_ack_eintr(self): + # the GLOBAL_ACK_EINTR flag was introduced in libcurl-7.30, but can also + # be backported for older versions of libcurl at the distribution level +- if not util.pycurl_version_less_than(7, 30) or hasattr(pycurl, 'GLOBAL_ACK_EINTR'): +- # initialize libcurl with the GLOBAL_ACK_EINTR flag +- pycurl.global_init(pycurl.GLOBAL_ACK_EINTR) +- pycurl.global_cleanup() ++ if util.pycurl_version_less_than(7, 30) or hasattr(pycurl, 'GLOBAL_ACK_EINTR'): ++ raise nose.plugins.skip.SkipTest('libcurl < 7.30.0 or no GLOBAL_ACK_EINTR') ++ ++ # initialize libcurl with the GLOBAL_ACK_EINTR flag ++ pycurl.global_init(pycurl.GLOBAL_ACK_EINTR) ++ pycurl.global_cleanup() + + @nose.tools.raises(ValueError) + def test_global_init_bogus(self): +-- +1.7.1 + + +From 7e8144d0d83062de394582207197e1081eafcc24 Mon Sep 17 00:00:00 2001 +From: Wim Lewis +Date: Thu, 21 Jul 2011 12:00:00 -0400 +Subject: [PATCH 126/149] Fix a tiny memory leak in util_curl_init() + +Each time a new Curl object is created util_curl_init() leaked +a small string buffer. The attached patch fixes the leak -- +curl_easy_setopt() strdup's its argument; the caller does not need +to keep the buffer around for it. + +https://sourceforge.net/p/pycurl/patches/13/ + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index 9c76609..ef03747 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -793,8 +793,8 @@ util_curl_init(CurlObject *self) + } + strcpy(s, "PycURL/"); strcpy(s+7, LIBCURL_VERSION); + res = curl_easy_setopt(self->handle, CURLOPT_USERAGENT, (char *) s); ++ free(s); + if (res != CURLE_OK) { +- free(s); + return (-1); + } + return (0); +-- +1.7.1 + + +From 14e3edf2f77368af4f3838202527c8a48a359d6a Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Fri, 31 May 2013 13:37:39 -0400 +Subject: [PATCH 127/149] Use correct logic when checking for GLOBAL_ACK_EINTR (#34) + +Signed-off-by: Kamil Dudka +--- + tests/global_init_test.py | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/tests/global_init_test.py b/tests/global_init_test.py +index 443ca49..b0d1986 100644 +--- a/tests/global_init_test.py ++++ b/tests/global_init_test.py +@@ -18,7 +18,7 @@ class GlobalInitTest(unittest.TestCase): + def test_global_init_ack_eintr(self): + # the GLOBAL_ACK_EINTR flag was introduced in libcurl-7.30, but can also + # be backported for older versions of libcurl at the distribution level +- if util.pycurl_version_less_than(7, 30) or hasattr(pycurl, 'GLOBAL_ACK_EINTR'): ++ if util.pycurl_version_less_than(7, 30) and not hasattr(pycurl, 'GLOBAL_ACK_EINTR'): + raise nose.plugins.skip.SkipTest('libcurl < 7.30.0 or no GLOBAL_ACK_EINTR') + + # initialize libcurl with the GLOBAL_ACK_EINTR flag +-- +1.7.1 + + +From b2157864dc9b80222d04e6a681d923c1bdc6c64f Mon Sep 17 00:00:00 2001 +From: Roland Sommer +Date: Wed, 8 May 2013 15:13:14 +0300 +Subject: [PATCH 128/149] Use urlparse.urljoin instead of os.path.join + +Using a base_url like "http://www.google.de" and calling +get("/") on a curl.Curl-instance does not work. + +os.path.join("http://www.google.de", "/") does not yield +the expected "http://www.google.de/" but "/" which leads to: + +error: (3, ' malformed') + +Using urljoin fixes this. + +https://github.com/christophwarner/PyCurl/pull/2 + +Signed-off-by: Kamil Dudka +--- + python/curl/__init__.py | 6 ++++-- + 1 files changed, 4 insertions(+), 2 deletions(-) + +diff --git a/python/curl/__init__.py b/python/curl/__init__.py +index fd179fc..31b9d49 100644 +--- a/python/curl/__init__.py ++++ b/python/curl/__init__.py +@@ -6,11 +6,13 @@ + # + # By Eric S. Raymond, April 2003. + +-import os, sys, exceptions, mimetools, pycurl ++import sys, exceptions, mimetools, pycurl + try: + import urllib.parse as urllib_parse ++ from urllib.parse import urljoin + except ImportError: + import urllib as urllib_parse ++ from urlparse import urljoin + try: + from cStringIO import StringIO + except ImportError: +@@ -81,7 +83,7 @@ class Curl: + if self.fakeheaders: + self.set_option(pycurl.HTTPHEADER, self.fakeheaders) + if relative_url: +- self.set_option(pycurl.URL,os.path.join(self.base_url,relative_url)) ++ self.set_option(pycurl.URL, urljoin(self.base_url, relative_url)) + self.payload = "" + self.hdr = "" + self.handle.perform() +-- +1.7.1 + + +From b12be12fc45fc786e2d537a3bbd3044488f1e367 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 4 Jun 2013 13:30:39 -0400 +Subject: [PATCH 129/149] Test coverage for relative urls in curl module + +Signed-off-by: Kamil Dudka +--- + tests/relative_url_test.py | 24 ++++++++++++++++++++++++ + 1 files changed, 24 insertions(+), 0 deletions(-) + create mode 100644 tests/relative_url_test.py + +diff --git a/tests/relative_url_test.py b/tests/relative_url_test.py +new file mode 100644 +index 0000000..ddff2b6 +--- /dev/null ++++ b/tests/relative_url_test.py +@@ -0,0 +1,24 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++# uses the high level interface ++import curl ++import unittest ++ ++from . import app ++from . import runwsgi ++from . import util ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++class RelativeUrlTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = curl.Curl('http://localhost:8380/') ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_get_relative(self): ++ self.curl.get('/success') ++ self.assertEqual('success', self.curl.body()) +-- +1.7.1 + + +From d29d339d892c52aad6e0be0b22b75dc52e9d9450 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sun, 2 Jun 2013 01:57:16 -0400 +Subject: [PATCH 130/149] Avoid dynamically allocating memory for default pycurl user agent. + +This implementation requires LIBCURL_VERSION to be a #define. + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 11 ++++------- + 1 files changed, 4 insertions(+), 7 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index ef03747..b9d797e 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -105,6 +105,9 @@ static void pycurl_ssl_cleanup(void); + #define OPTIONS_SIZE ((int)CURLOPT_LASTENTRY % 10000) + #define MOPTIONS_SIZE ((int)CURLMOPT_LASTENTRY % 10000) + ++/* Keep some default variables around */ ++static char *g_pycurl_useragent = "PycURL/" LIBCURL_VERSION; ++ + /* Type objects */ + static PyObject *ErrorObject = NULL; + static PyTypeObject *p_Curl_Type = NULL; +@@ -787,13 +790,7 @@ util_curl_init(CurlObject *self) + } + + /* Set default USERAGENT */ +- s = (char *) malloc(7 + strlen(LIBCURL_VERSION) + 1); +- if (s == NULL) { +- return (-1); +- } +- strcpy(s, "PycURL/"); strcpy(s+7, LIBCURL_VERSION); +- res = curl_easy_setopt(self->handle, CURLOPT_USERAGENT, (char *) s); +- free(s); ++ res = curl_easy_setopt(self->handle, CURLOPT_USERAGENT, (char *) g_pycurl_useragent); + if (res != CURLE_OK) { + return (-1); + } +-- +1.7.1 + + +From f8d2f64764ee25572453317371ff58fe048a8a8a Mon Sep 17 00:00:00 2001 +From: Marien Zwart +Date: Wed, 13 Jun 2012 00:00:00 -0400 +Subject: [PATCH 131/149] Mark NSS as supported. + +Mark NSS as supported, as it does not require the application to +initialize threading. + +If curl is using nss we do not seem to need to initialize threading +explicitly: the only threading-related bit I see in the nss +headers/documentation has to do with simultaneous non-nss use of +pkcs11 modules, nss itself seems to be thread-safe by default. +So silence the build warning. + +http://sourceforge.net/p/pycurl/patches/15/ + +Signed-off-by: Kamil Dudka +--- + setup.py | 2 ++ + src/pycurl.c | 4 ++-- + 2 files changed, 4 insertions(+), 2 deletions(-) + +diff --git a/setup.py b/setup.py +index 385facb..40da07a 100644 +--- a/setup.py ++++ b/setup.py +@@ -119,6 +119,8 @@ else: + define_macros.append(('HAVE_CURL_OPENSSL', 1)) + if e[2:] == 'gnutls': + define_macros.append(('HAVE_CURL_GNUTLS', 1)) ++ if e[2:] == 'ssl3': ++ define_macros.append(('HAVE_CURL_NSS', 1)) + elif e[:2] == "-L": + library_dirs.append(e[2:]) + else: +diff --git a/src/pycurl.c b/src/pycurl.c +index b9d797e..a913c47 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -88,12 +88,12 @@ typedef int Py_ssize_t; + # define PYCURL_NEED_SSL_TSL + # define PYCURL_NEED_GNUTLS_TSL + # include +-# else ++# elif !defined(HAVE_CURL_NSS) + # warning \ + "libcurl was compiled with SSL support, but configure could not determine which " \ + "library was used; thus no SSL crypto locking callbacks will be set, which may " \ + "cause random crashes on SSL requests" +-# endif /* HAVE_CURL_OPENSSL || HAVE_CURL_GNUTLS */ ++# endif /* HAVE_CURL_OPENSSL || HAVE_CURL_GNUTLS || HAVE_CURL_NSS */ + #endif /* HAVE_CURL_SSL */ + + #if defined(PYCURL_NEED_SSL_TSL) +-- +1.7.1 + + +From 256f4bece80ee579daf18cbccc6007c0fa517903 Mon Sep 17 00:00:00 2001 +From: Marien Zwart +Date: Wed, 13 Jun 2012 00:00:00 -0400 +Subject: [PATCH 132/149] Only initialize gcrypt if we are using an older gnutls that needs this. + +This is necessary to support newer gnutls linked to libnettle instead +of libgcrypt. + +If curl is using a recent version of gnutls we do not need to initialize +libgcrypt threading: gnutls does that for us. And because recent versions +of gnutls may be using nettle instead of gcrypt it is important that we +don't: those functions are not necessarily defined (and linking to +libgcrypt explicitly to get them is stupid, as they're not used). The +modification is based on instructions from the gnutls NEWS file. + +http://sourceforge.net/p/pycurl/patches/15/ + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 9 ++++++--- + 1 files changed, 6 insertions(+), 3 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index a913c47..5d4e5b0 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -85,9 +85,12 @@ typedef int Py_ssize_t; + # define PYCURL_NEED_OPENSSL_TSL + # include + # elif defined(HAVE_CURL_GNUTLS) +-# define PYCURL_NEED_SSL_TSL +-# define PYCURL_NEED_GNUTLS_TSL +-# include ++# include ++# if GNUTLS_VERSION_NUMBER <= 0x020b00 ++# define PYCURL_NEED_SSL_TSL ++# define PYCURL_NEED_GNUTLS_TSL ++# include ++# endif + # elif !defined(HAVE_CURL_NSS) + # warning \ + "libcurl was compiled with SSL support, but configure could not determine which " \ +-- +1.7.1 + + +From f4ec6883c9d5ebfa961aa6063313903b2ca7ce1e Mon Sep 17 00:00:00 2001 +From: Romulo A. Ceccon +Date: Sun, 9 Jun 2013 17:34:41 +0000 +Subject: [PATCH 133/149] Allow pycURL to be used with Python binaries built without thread support + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 215 ++++++++++++++++++++++++++++++++++++++------------------- + 1 files changed, 143 insertions(+), 72 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index 5d4e5b0..a9923ed 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -104,6 +104,30 @@ static void pycurl_ssl_init(void); + static void pycurl_ssl_cleanup(void); + #endif + ++#ifdef WITH_THREAD ++# define PYCURL_DECLARE_THREAD_STATE PyThreadState *tmp_state ++# define PYCURL_ACQUIRE_THREAD() acquire_thread(self, &tmp_state) ++# define PYCURL_ACQUIRE_THREAD_MULTI() acquire_thread_multi(self, &tmp_state) ++# define PYCURL_RELEASE_THREAD() release_thread(tmp_state) ++/* Replacement for Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS when python ++ callbacks are expected during blocking i/o operations: self->state will hold ++ the handle to current thread to be used as context */ ++# define PYCURL_BEGIN_ALLOW_THREADS \ ++ self->state = PyThreadState_Get(); \ ++ assert(self->state != NULL); \ ++ Py_BEGIN_ALLOW_THREADS ++# define PYCURL_END_ALLOW_THREADS \ ++ Py_END_ALLOW_THREADS \ ++ self->state = NULL; ++#else ++# define PYCURL_DECLARE_THREAD_STATE ++# define PYCURL_ACQUIRE_THREAD() (1) ++# define PYCURL_ACQUIRE_THREAD_MULTI() (1) ++# define PYCURL_RELEASE_THREAD() ++# define PYCURL_BEGIN_ALLOW_THREADS ++# define PYCURL_END_ALLOW_THREADS ++#endif ++ + /* Calculate the number of OBJECTPOINT options we need to store */ + #define OPTIONS_SIZE ((int)CURLOPT_LASTENTRY % 10000) + #define MOPTIONS_SIZE ((int)CURLMOPT_LASTENTRY % 10000) +@@ -126,14 +150,18 @@ typedef struct { + PyObject_HEAD + PyObject *dict; /* Python attributes dictionary */ + CURLSH *share_handle; ++#ifdef WITH_THREAD + ShareLock *lock; /* lock object to implement CURLSHOPT_LOCKFUNC */ ++#endif + } CurlShareObject; + + typedef struct { + PyObject_HEAD + PyObject *dict; /* Python attributes dictionary */ + CURLM *multi_handle; ++#ifdef WITH_THREAD + PyThreadState *state; ++#endif + fd_set read_fd_set; + fd_set write_fd_set; + fd_set exc_fd_set; +@@ -146,7 +174,9 @@ typedef struct { + PyObject_HEAD + PyObject *dict; /* Python attributes dictionary */ + CURL *handle; ++#ifdef WITH_THREAD + PyThreadState *state; ++#endif + CurlMultiObject *multi_stack; + CurlShareObject *share; + struct curl_httppost *httppost; +@@ -261,6 +291,7 @@ error: + } + + ++#ifdef WITH_THREAD + /************************************************************************* + // static utility functions + **************************************************************************/ +@@ -317,13 +348,42 @@ get_thread_state_multi(const CurlMultiObject *self) + } + + ++static int acquire_thread(const CurlObject *self, PyThreadState **state) ++{ ++ *state = get_thread_state(self); ++ if (*state == NULL) ++ return 0; ++ PyEval_AcquireThread(*state); ++ return 1; ++} ++ ++ ++static int acquire_thread_multi(const CurlMultiObject *self, PyThreadState **state) ++{ ++ *state = get_thread_state_multi(self); ++ if (*state == NULL) ++ return 0; ++ PyEval_AcquireThread(*state); ++ return 1; ++} ++ ++ ++static void release_thread(PyThreadState *state) ++{ ++ PyEval_ReleaseThread(state); ++} ++#endif ++ ++ + /* assert some CurlShareObject invariants */ + static void + assert_share_state(const CurlShareObject *self) + { + assert(self != NULL); + assert(self->ob_type == p_CurlShare_Type); ++#ifdef WITH_THREAD + assert(self->lock != NULL); ++#endif + } + + +@@ -333,7 +393,9 @@ assert_curl_state(const CurlObject *self) + { + assert(self != NULL); + assert(self->ob_type == p_Curl_Type); ++#ifdef WITH_THREAD + (void) get_thread_state(self); ++#endif + } + + +@@ -343,9 +405,11 @@ assert_multi_state(const CurlMultiObject *self) + { + assert(self != NULL); + assert(self->ob_type == p_CurlMulti_Type); ++#ifdef WITH_THREAD + if (self->state != NULL) { + assert(self->multi_handle != NULL); + } ++#endif + } + + +@@ -358,10 +422,12 @@ check_curl_state(const CurlObject *self, int flags, const char *name) + PyErr_Format(ErrorObject, "cannot invoke %s() - no curl handle", name); + return -1; + } ++#ifdef WITH_THREAD + if ((flags & 2) && get_thread_state(self) != NULL) { + PyErr_Format(ErrorObject, "cannot invoke %s() - perform() is currently running", name); + return -1; + } ++#endif + return 0; + } + +@@ -373,10 +439,12 @@ check_multi_state(const CurlMultiObject *self, int flags, const char *name) + PyErr_Format(ErrorObject, "cannot invoke %s() - no multi handle", name); + return -1; + } ++#ifdef WITH_THREAD + if ((flags & 2) && self->state != NULL) { + PyErr_Format(ErrorObject, "cannot invoke %s() - multi_perform() is currently running", name); + return -1; + } ++#endif + return 0; + } + +@@ -391,6 +459,7 @@ check_share_state(const CurlShareObject *self, int flags, const char *name) + // SSL TSL + **************************************************************************/ + ++#ifdef WITH_THREAD + #ifdef PYCURL_NEED_OPENSSL_TSL + + static PyThread_type_lock *pycurl_openssl_tsl = NULL; +@@ -560,6 +629,19 @@ share_unlock_callback(CURL *handle, curl_lock_data data, void *userptr) + share_lock_unlock(share->lock, data); + } + ++#else /* WITH_THREAD */ ++ ++static void pycurl_ssl_init(void) ++{ ++ return; ++} ++ ++static void pycurl_ssl_cleanup(void) ++{ ++ return; ++} ++ ++#endif /* WITH_THREAD */ + + /* constructor - this is a module-level function returning a new instance */ + static CurlShareObject * +@@ -567,8 +649,10 @@ do_share_new(PyObject *dummy) + { + int res; + CurlShareObject *self; ++#ifdef WITH_THREAD + const curl_lock_function lock_cb = share_lock_callback; + const curl_unlock_function unlock_cb = share_unlock_callback; ++#endif + + UNUSED(dummy); + +@@ -583,8 +667,10 @@ do_share_new(PyObject *dummy) + + /* Initialize object attributes */ + self->dict = NULL; ++#ifdef WITH_THREAD + self->lock = share_lock_new(); + assert(self->lock != NULL); ++#endif + + /* Allocate libcurl share handle */ + self->share_handle = curl_share_init(); +@@ -594,6 +680,7 @@ do_share_new(PyObject *dummy) + return NULL; + } + ++#ifdef WITH_THREAD + /* Set locking functions and data */ + res = curl_share_setopt(self->share_handle, CURLSHOPT_LOCKFUNC, lock_cb); + assert(res == CURLE_OK); +@@ -601,6 +688,7 @@ do_share_new(PyObject *dummy) + assert(res == CURLE_OK); + res = curl_share_setopt(self->share_handle, CURLSHOPT_UNLOCKFUNC, unlock_cb); + assert(res == CURLE_OK); ++#endif + + return self; + } +@@ -632,7 +720,9 @@ do_share_clear(CurlShareObject *self) + static void + util_share_close(CurlShareObject *self){ + curl_share_cleanup(self->share_handle); ++#ifdef WITH_THREAD + share_lock_destroy(self->lock); ++#endif + } + + +@@ -723,7 +813,9 @@ util_curl_new(void) + /* Set python curl object initial values */ + self->dict = NULL; + self->handle = NULL; ++#ifdef WITH_THREAD + self->state = NULL; ++#endif + self->share = NULL; + self->multi_stack = NULL; + self->httppost = NULL; +@@ -898,12 +990,16 @@ util_curl_close(CurlObject *self) + if (handle == NULL) { + /* Some paranoia assertions just to make sure the object + * deallocation problem is finally really fixed... */ ++#ifdef WITH_THREAD + assert(self->state == NULL); ++#endif + assert(self->multi_stack == NULL); + assert(self->share == NULL); + return; /* already closed */ + } ++#ifdef WITH_THREAD + self->state = NULL; ++#endif + + /* Decref multi stuff which uses this handle */ + util_curl_xdecref(self, 2, handle); +@@ -977,7 +1073,9 @@ do_curl_errstr(CurlObject *self) + static int + do_curl_clear(CurlObject *self) + { ++#ifdef WITH_THREAD + assert(get_thread_state(self) == NULL); ++#endif + util_curl_xdecref(self, 1 | 2 | 4 | 8 | 16, self->handle); + return 0; + } +@@ -1020,17 +1118,9 @@ do_curl_perform(CurlObject *self) + return NULL; + } + +- /* Save handle to current thread (used as context for python callbacks) */ +- self->state = PyThreadState_Get(); +- assert(self->state != NULL); +- +- /* Release global lock and start */ +- Py_BEGIN_ALLOW_THREADS ++ PYCURL_BEGIN_ALLOW_THREADS + res = curl_easy_perform(self->handle); +- Py_END_ALLOW_THREADS +- +- /* Zero thread-state to disallow callbacks to be run from now on */ +- self->state = NULL; ++ PYCURL_END_ALLOW_THREADS + + if (res != CURLE_OK) { + CURLERROR_RETVAL(); +@@ -1050,7 +1140,7 @@ static size_t + util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *stream) + { + CurlObject *self; +- PyThreadState *tmp_state; ++ PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + size_t ret = 0; /* assume error */ +@@ -1059,10 +1149,8 @@ util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *strea + + /* acquire thread */ + self = (CurlObject *)stream; +- tmp_state = get_thread_state(self); +- if (tmp_state == NULL) ++ if (!PYCURL_ACQUIRE_THREAD()) + return ret; +- PyEval_AcquireThread(tmp_state); + + /* check args */ + cb = flags ? self->h_cb : self->w_cb; +@@ -1101,7 +1189,7 @@ util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *strea + done: + silent_error: + Py_XDECREF(result); +- PyEval_ReleaseThread(tmp_state); ++ PYCURL_RELEASE_THREAD(); + return ret; + verbose_error: + PyErr_Print(); +@@ -1131,13 +1219,12 @@ opensocket_callback(void *clientp, curlsocktype purpose, + PyObject *result = NULL; + PyObject *fileno_result = NULL; + CurlObject *self; +- PyThreadState *tmp_state; ++ PYCURL_DECLARE_THREAD_STATE; + int ret = CURL_SOCKET_BAD; + + self = (CurlObject *)clientp; +- tmp_state = get_thread_state(self); ++ PYCURL_ACQUIRE_THREAD(); + +- PyEval_AcquireThread(tmp_state); + arglist = Py_BuildValue("(iii)", address->family, address->socktype, address->protocol); + if (arglist == NULL) + goto verbose_error; +@@ -1171,7 +1258,7 @@ silent_error: + done: + Py_XDECREF(result); + Py_XDECREF(fileno_result); +- PyEval_ReleaseThread(tmp_state); ++ PYCURL_RELEASE_THREAD(); + return ret; + verbose_error: + PyErr_Print(); +@@ -1182,7 +1269,7 @@ static int + seek_callback(void *stream, curl_off_t offset, int origin) + { + CurlObject *self; +- PyThreadState *tmp_state; ++ PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + int ret = 2; /* assume error 2 (can't seek, libcurl free to work around). */ +@@ -1191,10 +1278,8 @@ seek_callback(void *stream, curl_off_t offset, int origin) + + /* acquire thread */ + self = (CurlObject *)stream; +- tmp_state = get_thread_state(self); +- if (tmp_state == NULL) ++ if (!PYCURL_ACQUIRE_THREAD()) + return ret; +- PyEval_AcquireThread(tmp_state); + + /* check arguments */ + switch (origin) +@@ -1244,7 +1329,7 @@ seek_callback(void *stream, curl_off_t offset, int origin) + + silent_error: + Py_XDECREF(result); +- PyEval_ReleaseThread(tmp_state); ++ PYCURL_RELEASE_THREAD(); + return ret; + verbose_error: + PyErr_Print(); +@@ -1258,7 +1343,7 @@ static size_t + read_callback(char *ptr, size_t size, size_t nmemb, void *stream) + { + CurlObject *self; +- PyThreadState *tmp_state; ++ PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + +@@ -1267,10 +1352,8 @@ read_callback(char *ptr, size_t size, size_t nmemb, void *stream) + + /* acquire thread */ + self = (CurlObject *)stream; +- tmp_state = get_thread_state(self); +- if (tmp_state == NULL) ++ if (!PYCURL_ACQUIRE_THREAD()) + return ret; +- PyEval_AcquireThread(tmp_state); + + /* check args */ + if (self->r_cb == NULL) +@@ -1328,7 +1411,7 @@ read_callback(char *ptr, size_t size, size_t nmemb, void *stream) + done: + silent_error: + Py_XDECREF(result); +- PyEval_ReleaseThread(tmp_state); ++ PYCURL_RELEASE_THREAD(); + return ret; + verbose_error: + PyErr_Print(); +@@ -1341,17 +1424,15 @@ progress_callback(void *stream, + double dltotal, double dlnow, double ultotal, double ulnow) + { + CurlObject *self; +- PyThreadState *tmp_state; ++ PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + int ret = 1; /* assume error */ + + /* acquire thread */ + self = (CurlObject *)stream; +- tmp_state = get_thread_state(self); +- if (tmp_state == NULL) ++ if (!PYCURL_ACQUIRE_THREAD()) + return ret; +- PyEval_AcquireThread(tmp_state); + + /* check args */ + if (self->pro_cb == NULL) +@@ -1379,7 +1460,7 @@ progress_callback(void *stream, + + silent_error: + Py_XDECREF(result); +- PyEval_ReleaseThread(tmp_state); ++ PYCURL_RELEASE_THREAD(); + return ret; + verbose_error: + PyErr_Print(); +@@ -1392,7 +1473,7 @@ debug_callback(CURL *curlobj, curl_infotype type, + char *buffer, size_t total_size, void *stream) + { + CurlObject *self; +- PyThreadState *tmp_state; ++ PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + int ret = 0; /* always success */ +@@ -1401,10 +1482,8 @@ debug_callback(CURL *curlobj, curl_infotype type, + + /* acquire thread */ + self = (CurlObject *)stream; +- tmp_state = get_thread_state(self); +- if (tmp_state == NULL) ++ if (!PYCURL_ACQUIRE_THREAD()) + return ret; +- PyEval_AcquireThread(tmp_state); + + /* check args */ + if (self->debug_cb == NULL) +@@ -1427,7 +1506,7 @@ debug_callback(CURL *curlobj, curl_infotype type, + + silent_error: + Py_XDECREF(result); +- PyEval_ReleaseThread(tmp_state); ++ PYCURL_RELEASE_THREAD(); + return ret; + verbose_error: + PyErr_Print(); +@@ -1439,7 +1518,7 @@ static curlioerr + ioctl_callback(CURL *curlobj, int cmd, void *stream) + { + CurlObject *self; +- PyThreadState *tmp_state; ++ PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + int ret = CURLIOE_FAILRESTART; /* assume error */ +@@ -1448,10 +1527,8 @@ ioctl_callback(CURL *curlobj, int cmd, void *stream) + + /* acquire thread */ + self = (CurlObject *)stream; +- tmp_state = get_thread_state(self); +- if (tmp_state == NULL) ++ if (!PYCURL_ACQUIRE_THREAD()) + return (curlioerr) ret; +- PyEval_AcquireThread(tmp_state); + + /* check args */ + if (self->ioctl_cb == NULL) +@@ -1480,7 +1557,7 @@ ioctl_callback(CURL *curlobj, int cmd, void *stream) + + silent_error: + Py_XDECREF(result); +- PyEval_ReleaseThread(tmp_state); ++ PYCURL_RELEASE_THREAD(); + return (curlioerr) ret; + verbose_error: + PyErr_Print(); +@@ -2324,7 +2401,9 @@ do_multi_new(PyObject *dummy) + + /* Initialize object attributes */ + self->dict = NULL; ++#ifdef WITH_THREAD + self->state = NULL; ++#endif + self->t_cb = NULL; + self->s_cb = NULL; + +@@ -2342,7 +2421,9 @@ static void + util_multi_close(CurlMultiObject *self) + { + assert(self != NULL); ++#ifdef WITH_THREAD + self->state = NULL; ++#endif + if (self->multi_handle != NULL) { + CURLM *multi_handle = self->multi_handle; + self->multi_handle = NULL; +@@ -2411,7 +2492,7 @@ int multi_socket_callback(CURL *easy, + { + CurlMultiObject *self; + CurlObject *easy_self; +- PyThreadState *tmp_state; ++ PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + int ret; +@@ -2419,10 +2500,8 @@ int multi_socket_callback(CURL *easy, + /* acquire thread */ + self = (CurlMultiObject *)userp; + ret = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &easy_self); +- tmp_state = get_thread_state_multi(self); +- if (tmp_state == NULL) ++ if (!PYCURL_ACQUIRE_THREAD_MULTI()) + return 0; +- PyEval_AcquireThread(tmp_state); + + /* check args */ + if (self->s_cb == NULL) +@@ -2446,7 +2525,7 @@ int multi_socket_callback(CURL *easy, + + silent_error: + Py_XDECREF(result); +- PyEval_ReleaseThread(tmp_state); ++ PYCURL_RELEASE_THREAD(); + return 0; + verbose_error: + PyErr_Print(); +@@ -2460,7 +2539,7 @@ int multi_timer_callback(CURLM *multi, + void *userp) + { + CurlMultiObject *self; +- PyThreadState *tmp_state; ++ PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + int ret = 0; /* always success */ +@@ -2469,10 +2548,8 @@ int multi_timer_callback(CURLM *multi, + + /* acquire thread */ + self = (CurlMultiObject *)userp; +- tmp_state = get_thread_state_multi(self); +- if (tmp_state == NULL) ++ if (!PYCURL_ACQUIRE_THREAD_MULTI()) + return ret; +- PyEval_AcquireThread(tmp_state); + + /* check args */ + if (self->t_cb == NULL) +@@ -2491,7 +2568,7 @@ int multi_timer_callback(CURLM *multi, + + silent_error: + Py_XDECREF(result); +- PyEval_ReleaseThread(tmp_state); ++ PYCURL_RELEASE_THREAD(); + return ret; + verbose_error: + PyErr_Print(); +@@ -2634,14 +2711,10 @@ do_multi_socket_action(CurlMultiObject *self, PyObject *args) + if (check_multi_state(self, 1 | 2, "socket_action") != 0) { + return NULL; + } +- /* Release global lock and start */ +- self->state = PyThreadState_Get(); +- assert(self->state != NULL); +- Py_BEGIN_ALLOW_THREADS + ++ PYCURL_BEGIN_ALLOW_THREADS + res = curl_multi_socket_action(self->multi_handle, socket, ev_bitmask, &running); +- Py_END_ALLOW_THREADS +- self->state = NULL; ++ PYCURL_END_ALLOW_THREADS + + if (res != CURLM_OK) { + CURLERROR_MSG("multi_socket_action failed"); +@@ -2662,13 +2735,9 @@ do_multi_socket_all(CurlMultiObject *self) + return NULL; + } + +- /* Release global lock and start */ +- self->state = PyThreadState_Get(); +- assert(self->state != NULL); +- Py_BEGIN_ALLOW_THREADS ++ PYCURL_BEGIN_ALLOW_THREADS + res = curl_multi_socket_all(self->multi_handle, &running); +- Py_END_ALLOW_THREADS +- self->state = NULL; ++ PYCURL_END_ALLOW_THREADS + + /* We assume these errors are ok, otherwise throw exception */ + if (res != CURLM_OK && res != CURLM_CALL_MULTI_PERFORM) { +@@ -2692,13 +2761,9 @@ do_multi_perform(CurlMultiObject *self) + return NULL; + } + +- /* Release global lock and start */ +- self->state = PyThreadState_Get(); +- assert(self->state != NULL); +- Py_BEGIN_ALLOW_THREADS ++ PYCURL_BEGIN_ALLOW_THREADS + res = curl_multi_perform(self->multi_handle, &running); +- Py_END_ALLOW_THREADS +- self->state = NULL; ++ PYCURL_END_ALLOW_THREADS + + /* We assume these errors are ok, otherwise throw exception */ + if (res != CURLM_OK && res != CURLM_CALL_MULTI_PERFORM) { +@@ -2722,16 +2787,20 @@ check_multi_add_remove(const CurlMultiObject *self, const CurlObject *obj) + PyErr_SetString(ErrorObject, "cannot add/remove handle - multi-stack is closed"); + return -1; + } ++#ifdef WITH_THREAD + if (self->state != NULL) { + PyErr_SetString(ErrorObject, "cannot add/remove handle - multi_perform() already running"); + return -1; + } ++#endif + /* check CurlObject status */ + assert_curl_state(obj); ++#ifdef WITH_THREAD + if (obj->state != NULL) { + PyErr_SetString(ErrorObject, "cannot add/remove handle - perform() of curl object already running"); + return -1; + } ++#endif + if (obj->multi_stack != NULL && obj->multi_stack != self) { + PyErr_SetString(ErrorObject, "cannot add/remove handle - curl object already on another multi-stack"); + return -1; +@@ -3989,8 +4058,10 @@ initpycurl(void) + pycurl_ssl_init(); + #endif + ++#ifdef WITH_THREAD + /* Finally initialize global interpreter lock */ + PyEval_InitThreads(); ++#endif + + } + +-- +1.7.1 + + +From 9c06c6f29f4fcb5b6be9200f96c19b54b71e4b3b Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 12 Jun 2013 00:48:14 -0400 +Subject: [PATCH 134/149] Test for seek function + +Original patch by Jonas . + +http://sourceforge.net/p/pycurl/patches/8/ + +Signed-off-by: Kamil Dudka +--- + .gitignore | 1 + + Makefile | 1 + + tests/seek_function_test.py | 72 +++++++++++++++++++++++++++++++++++++++++++ + tests/vsftpd.conf | 4 ++- + 4 files changed, 77 insertions(+), 1 deletions(-) + create mode 100644 tests/seek_function_test.py + +diff --git a/.gitignore b/.gitignore +index c873e32..abc741a 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -3,6 +3,7 @@ + /MANIFEST + /build + /dist ++/tests/tmp + /www/htdocs/download/*.bz2 + /www/htdocs/download/*.exe + /www/htdocs/download/*.gz +diff --git a/Makefile b/Makefile +index 9475250..cb12aaf 100644 +--- a/Makefile ++++ b/Makefile +@@ -16,6 +16,7 @@ build-7.10.8: + $(PYTHON) setup.py build --curl-config=/home/hosts/localhost/packages/curl-7.10.8/bin/curl-config + + test: build ++ mkdir -p tests/tmp + PYTHONPATH=$$(ls -d build/lib.*):$$PYTHONPATH \ + $(NOSETESTS) + +diff --git a/tests/seek_function_test.py b/tests/seek_function_test.py +new file mode 100644 +index 0000000..2a28078 +--- /dev/null ++++ b/tests/seek_function_test.py +@@ -0,0 +1,72 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++# Note: this test is meant to be run from pycurl project root. ++ ++import pycurl ++import unittest ++import os.path ++ ++from . import util ++from . import procmgr ++ ++setup_module, teardown_module = procmgr.vsftpd_setup() ++ ++class PartialFileSource: ++ def __init__(self): ++ self.__buf = '1234567890.1234567890' ++ self.__maxread = None ++ self.__bufptr = 0 ++ ++ def read(self, size): ++ p = self.__bufptr ++ end = p+size ++ if self.__maxread: ++ end = min(self.__maxread, end) ++ ret = self.__buf[p:end] ++ self.__bufptr+= len(ret) ++ #print 20*">>>", "read(%s) ==> %s" % (size, len(ret)) ++ return ret ++ ++ def seek(self, offset, origin): ++ #print 20*">>>", "seek(%s, %s)" % (offset, origin) ++ self.__bufptr = offset ++ ++ def set_maxread(self, maxread): ++ self.__maxread = maxread ++ ++class SeekFunctionTest(unittest.TestCase): ++ def test_seek_function(self): ++ c = pycurl.Curl() ++ c.setopt(pycurl.UPLOAD, 1) ++ c.setopt(pycurl.URL, "ftp://localhost:8321/tests/tmp/upload.txt") ++ c.setopt(pycurl.RESUME_FROM, 0) ++ #c.setopt(pycurl.VERBOSE, 1) ++ upload_file = PartialFileSource() ++ c.setopt(pycurl.READFUNCTION, upload_file.read) ++ upload_file.set_maxread(10) ++ c.perform() ++ ++ with open(os.path.join(os.path.dirname(__file__), 'tmp', 'upload.txt')) as f: ++ content = f.read() ++ self.assertEqual('1234567890', content) ++ ++ c.close() ++ del c ++ del upload_file ++ ++ c = pycurl.Curl() ++ c.setopt(pycurl.URL, "ftp://localhost:8321/tests/tmp/upload.txt") ++ c.setopt(pycurl.RESUME_FROM, -1) ++ c.setopt(pycurl.UPLOAD, 1) ++ #c.setopt(pycurl.VERBOSE, 1) ++ upload_file = PartialFileSource() ++ c.setopt(pycurl.READFUNCTION, upload_file.read) ++ c.setopt(pycurl.SEEKFUNCTION, upload_file.seek) ++ c.perform() ++ c.close() ++ ++ with open(os.path.join(os.path.dirname(__file__), 'tmp', 'upload.txt')) as f: ++ content = f.read() ++ self.assertEqual('1234567890.1234567890', content) +diff --git a/tests/vsftpd.conf b/tests/vsftpd.conf +index b4e4972..787da0e 100644 +--- a/tests/vsftpd.conf ++++ b/tests/vsftpd.conf +@@ -5,7 +5,9 @@ background=no + download_enable=no + listen=yes + run_as_launching_user=yes +-write_enable=no ++write_enable=yes ++anon_upload_enable=yes ++anon_other_write_enable=yes + listen_port=8321 + # should be supplied on command line + anon_root=/var/empty +-- +1.7.1 + + +From 7445cc96205e9c2b734ec07955519db224aa3ab7 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 12 Jun 2013 18:01:16 -0400 +Subject: [PATCH 135/149] Allow multiple ftp tests to work in the suite + +Signed-off-by: Kamil Dudka +--- + tests/procmgr.py | 15 ++++++++++----- + 1 files changed, 10 insertions(+), 5 deletions(-) + +diff --git a/tests/procmgr.py b/tests/procmgr.py +index ce08da9..18c3dd2 100644 +--- a/tests/procmgr.py ++++ b/tests/procmgr.py +@@ -12,6 +12,7 @@ class ProcessManager(object): + + def start(self): + self.process = subprocess.Popen(self.cmd) ++ self.running = True + + self.thread = threading.Thread(target=self.run) + self.thread.daemon = True +@@ -19,11 +20,18 @@ class ProcessManager(object): + + def run(self): + self.process.communicate() ++ ++ def stop(self): ++ try: ++ os.kill(self.process.pid, signal.SIGTERM) ++ except OSError: ++ pass ++ self.running = False + + managers = {} + + def start(cmd): +- if str(cmd) in managers: ++ if str(cmd) in managers and managers[str(cmd)].running: + # already started + return + +@@ -75,9 +83,6 @@ def vsftpd_setup(): + except KeyError: + pass + else: +- try: +- os.kill(manager.process.pid, signal.SIGTERM) +- except OSError: +- pass ++ manager.stop() + + return do_setup_module, teardown_module +-- +1.7.1 + + +From 4f8e4def8eddac6d5e5239602ef00230a2be3690 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Wed, 12 Jun 2013 19:26:25 -0400 +Subject: [PATCH 136/149] Do not run vsftpd tests by default due to security implications + +Signed-off-by: Kamil Dudka +--- + README.rst | 18 ++++++++++++++++++ + tests/procmgr.py | 14 +++++++++++++- + 2 files changed, 31 insertions(+), 1 deletions(-) + +diff --git a/README.rst b/README.rst +index 3518d9d..c4427aa 100644 +--- a/README.rst ++++ b/README.rst +@@ -49,6 +49,24 @@ or `pip`_:: + .. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall + .. _pip: http://pypi.python.org/pypi/pip + ++Automated Tests ++--------------- ++ ++PycURL comes with an automated test suite. To run the tests, execute:: ++ ++ make test ++ ++Some tests use vsftpd configured to accept anonymous uploads. These tests ++are not run by default. As configured, vsftpd will allow reads and writes to ++anything the user running the tests has read and write access. To run ++vsftpd tests you must explicitly set PYCURL_VSFTPD_PATH variable like so:: ++ ++ # use vsftpd in PATH ++ export PYCURL_VSFTPD_PATH=vsftpd ++ ++ # specify full path to vsftpd ++ export PYCURL_VSFTPD_PATH=/usr/local/libexec/vsftpd ++ + Contribute + ---------- + +diff --git a/tests/procmgr.py b/tests/procmgr.py +index 18c3dd2..9309aa8 100644 +--- a/tests/procmgr.py ++++ b/tests/procmgr.py +@@ -3,6 +3,7 @@ import subprocess + import os + import sys + import signal ++import nose.plugins.skip + + from . import runwsgi + +@@ -50,7 +51,16 @@ def start_setup(cmd): + if 'PYCURL_VSFTPD_PATH' in os.environ: + vsftpd_path = os.environ['PYCURL_VSFTPD_PATH'] + else: +- vsftpd_path = 'vsftpd' ++ vsftpd_path = None ++ ++try: ++ # python 2 ++ exception_base = StandardError ++except NameError: ++ # python 3 ++ exception_base = Exception ++class VsftpdNotConfigured(exception_base): ++ pass + + def vsftpd_setup(): + config_file_path = os.path.join(os.path.dirname(__file__), 'vsftpd.conf') +@@ -62,6 +72,8 @@ def vsftpd_setup(): + ] + setup_module = start_setup(cmd) + def do_setup_module(): ++ if vsftpd_path is None: ++ raise nose.plugins.skip.SkipTest('PYCURL_VSFTPD_PATH environment variable not set') + try: + setup_module() + except OSError: +-- +1.7.1 + + +From 079b262507e77a61772255aea969471b2d81f16c Mon Sep 17 00:00:00 2001 +From: Romulo A. Ceccon +Date: Tue, 16 Jul 2013 11:05:54 -0300 +Subject: [PATCH 137/149] Adjusted declaration order for C89 compilers + +PYCURL_DECLARE_THREAD_STATE looks like an +executable statement for some non-C99 compilers +(e.g. armcc), so it must appear at the end of +the declaration list. + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 19 ++++++++++--------- + 1 files changed, 10 insertions(+), 9 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index a9923ed..cdb934e 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -1140,12 +1140,12 @@ static size_t + util_write_callback(int flags, char *ptr, size_t size, size_t nmemb, void *stream) + { + CurlObject *self; +- PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + size_t ret = 0; /* assume error */ + PyObject *cb; + int total_size; ++ PYCURL_DECLARE_THREAD_STATE; + + /* acquire thread */ + self = (CurlObject *)stream; +@@ -1219,8 +1219,8 @@ opensocket_callback(void *clientp, curlsocktype purpose, + PyObject *result = NULL; + PyObject *fileno_result = NULL; + CurlObject *self; +- PYCURL_DECLARE_THREAD_STATE; + int ret = CURL_SOCKET_BAD; ++ PYCURL_DECLARE_THREAD_STATE; + + self = (CurlObject *)clientp; + PYCURL_ACQUIRE_THREAD(); +@@ -1269,12 +1269,12 @@ static int + seek_callback(void *stream, curl_off_t offset, int origin) + { + CurlObject *self; +- PYCURL_DECLARE_THREAD_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 */ ++ PYCURL_DECLARE_THREAD_STATE; + + /* acquire thread */ + self = (CurlObject *)stream; +@@ -1343,13 +1343,14 @@ static size_t + read_callback(char *ptr, size_t size, size_t nmemb, void *stream) + { + CurlObject *self; +- PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + + size_t ret = CURL_READFUNC_ABORT; /* assume error, this actually works */ + int total_size; + ++ PYCURL_DECLARE_THREAD_STATE; ++ + /* acquire thread */ + self = (CurlObject *)stream; + if (!PYCURL_ACQUIRE_THREAD()) +@@ -1424,10 +1425,10 @@ progress_callback(void *stream, + double dltotal, double dlnow, double ultotal, double ulnow) + { + CurlObject *self; +- PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + int ret = 1; /* assume error */ ++ PYCURL_DECLARE_THREAD_STATE; + + /* acquire thread */ + self = (CurlObject *)stream; +@@ -1473,10 +1474,10 @@ debug_callback(CURL *curlobj, curl_infotype type, + char *buffer, size_t total_size, void *stream) + { + CurlObject *self; +- PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + int ret = 0; /* always success */ ++ PYCURL_DECLARE_THREAD_STATE; + + UNUSED(curlobj); + +@@ -1518,10 +1519,10 @@ static curlioerr + ioctl_callback(CURL *curlobj, int cmd, void *stream) + { + CurlObject *self; +- PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + int ret = CURLIOE_FAILRESTART; /* assume error */ ++ PYCURL_DECLARE_THREAD_STATE; + + UNUSED(curlobj); + +@@ -2492,10 +2493,10 @@ int multi_socket_callback(CURL *easy, + { + CurlMultiObject *self; + CurlObject *easy_self; +- PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + int ret; ++ PYCURL_DECLARE_THREAD_STATE; + + /* acquire thread */ + self = (CurlMultiObject *)userp; +@@ -2539,10 +2540,10 @@ int multi_timer_callback(CURLM *multi, + void *userp) + { + CurlMultiObject *self; +- PYCURL_DECLARE_THREAD_STATE; + PyObject *arglist; + PyObject *result = NULL; + int ret = 0; /* always success */ ++ PYCURL_DECLARE_THREAD_STATE; + + UNUSED(multi); + +-- +1.7.1 + + +From 350016b90e38505eeee46358d5303de2a7faca5e Mon Sep 17 00:00:00 2001 +From: Romulo A. Ceccon +Date: Tue, 16 Jul 2013 11:13:52 -0300 +Subject: [PATCH 138/149] Silence compiler hints about unused variables + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 4 +--- + 1 files changed, 1 insertions(+), 3 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index cdb934e..7b96b01 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -851,7 +851,6 @@ static int + util_curl_init(CurlObject *self) + { + int res; +- char *s = NULL; + + /* Set curl error buffer and zero it */ + res = curl_easy_setopt(self->handle, CURLOPT_ERRORBUFFER, self->error); +@@ -2495,12 +2494,11 @@ int multi_socket_callback(CURL *easy, + CurlObject *easy_self; + PyObject *arglist; + PyObject *result = NULL; +- int ret; + PYCURL_DECLARE_THREAD_STATE; + + /* acquire thread */ + self = (CurlMultiObject *)userp; +- ret = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &easy_self); ++ curl_easy_getinfo(easy, CURLINFO_PRIVATE, &easy_self); + if (!PYCURL_ACQUIRE_THREAD_MULTI()) + return 0; + +-- +1.7.1 + + +From 19af5bbbc98dc2198cc7541b81ed313a75faeb9c Mon Sep 17 00:00:00 2001 +From: Romulo A. Ceccon +Date: Tue, 16 Jul 2013 11:44:36 -0300 +Subject: [PATCH 139/149] Added support for CURLOPT_RESOLVE + +http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTRESOLVE + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 36 ++++++++++++++++++++++++++++++++---- + 1 files changed, 32 insertions(+), 4 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index 7b96b01..bb6056e 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -68,6 +68,10 @@ + #define HAVE_CURLOPT_PROXYUSERNAME + #endif + ++#if LIBCURL_VERSION_NUM >= 0x071503 /* check for 7.21.3 or greater */ ++#define HAVE_CURLOPT_RESOLVE ++#endif ++ + /* Python < 2.5 compat for Py_ssize_t */ + #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) + typedef int Py_ssize_t; +@@ -185,6 +189,9 @@ typedef struct { + struct curl_slist *quote; + struct curl_slist *postquote; + struct curl_slist *prequote; ++#ifdef HAVE_CURLOPT_RESOLVE ++ struct curl_slist *resolve; ++#endif + /* callbacks */ + PyObject *w_cb; + PyObject *h_cb; +@@ -824,6 +831,9 @@ util_curl_new(void) + self->quote = NULL; + self->postquote = NULL; + self->prequote = NULL; ++#ifdef HAVE_CURLOPT_RESOLVE ++ self->resolve = NULL; ++#endif + + /* Set callback pointers to NULL by default */ + self->w_cb = NULL; +@@ -1025,6 +1035,9 @@ util_curl_close(CurlObject *self) + SFREE(self->quote); + SFREE(self->postquote); + SFREE(self->prequote); ++#ifdef HAVE_CURLOPT_RESOLVE ++ SFREE(self->resolve); ++#endif + #undef SFREE + } + +@@ -1588,6 +1601,9 @@ do_curl_reset(CurlObject *self) + SFREE(self->quote); + SFREE(self->postquote); + SFREE(self->prequote); ++#ifdef HAVE_CURLOPT_RESOLVE ++ SFREE(self->resolve); ++#endif + #undef SFREE + res = util_curl_init(self); + if (res < 0) { +@@ -1933,6 +1949,11 @@ do_curl_setopt(CurlObject *self, PyObject *args) + case CURLOPT_PREQUOTE: + old_slist = &self->prequote; + break; ++#ifdef HAVE_CURLOPT_RESOLVE ++ case CURLOPT_RESOLVE: ++ old_slist = &self->resolve; ++ break; ++#endif + case CURLOPT_HTTPPOST: + break; + default: +@@ -1944,10 +1965,14 @@ do_curl_setopt(CurlObject *self, PyObject *args) + len = PyList_Size(obj); + if (len == 0) { + /* Empty list - do nothing */ +- if (!(option == CURLOPT_HTTPHEADER || +- option == CURLOPT_QUOTE || +- option == CURLOPT_POSTQUOTE || +- option == CURLOPT_PREQUOTE)) { ++ if (!(option == CURLOPT_HTTPHEADER ++ || option == CURLOPT_QUOTE ++ || option == CURLOPT_POSTQUOTE ++ || option == CURLOPT_PREQUOTE ++#ifdef HAVE_CURLOPT_RESOLVE ++ || option == CURLOPT_RESOLVE ++#endif ++ )) { + /* Empty list - do nothing */ + Py_INCREF(Py_None); + return Py_None; +@@ -3888,6 +3913,9 @@ initpycurl(void) + insint_c(d, "CRLFILE", CURLOPT_CRLFILE); + insint_c(d, "ISSUERCERT", CURLOPT_ISSUERCERT); + insint_c(d, "ADDRESS_SCOPE", CURLOPT_ADDRESS_SCOPE); ++#ifdef HAVE_CURLOPT_RESOLVE ++ insint_c(d, "RESOLVE", CURLOPT_RESOLVE); ++#endif + + insint_c(d, "M_TIMERFUNCTION", CURLMOPT_TIMERFUNCTION); + insint_c(d, "M_SOCKETFUNCTION", CURLMOPT_SOCKETFUNCTION); +-- +1.7.1 + + +From 576efa57797d878e23dd97b1f5ec2393167b30c6 Mon Sep 17 00:00:00 2001 +From: Romulo A. Ceccon +Date: Fri, 19 Jul 2013 11:36:43 -0300 +Subject: [PATCH 140/149] Added test-case for CURLOPT_RESOLVE + +Signed-off-by: Kamil Dudka +--- + tests/resolve_test.py | 22 ++++++++++++++++++++++ + 1 files changed, 22 insertions(+), 0 deletions(-) + create mode 100644 tests/resolve_test.py + +diff --git a/tests/resolve_test.py b/tests/resolve_test.py +new file mode 100644 +index 0000000..fcee830 +--- /dev/null ++++ b/tests/resolve_test.py +@@ -0,0 +1,22 @@ ++# -*- coding: iso-8859-1 -*- ++ ++import pycurl ++import unittest ++ ++from . import app ++from . import runwsgi ++ ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8380)) ++ ++class ResolveTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_resolve(self): ++ self.curl.setopt(pycurl.URL, 'http://p.localhost:8380/success') ++ self.curl.setopt(pycurl.RESOLVE, ['p.localhost:8380:127.0.0.1']) ++ self.curl.perform() ++ self.assertEqual(200, self.curl.getinfo(pycurl.RESPONSE_CODE)) +-- +1.7.1 + + +From 2ebc6b6097df0a83a6982e8941af3e880e18a74f Mon Sep 17 00:00:00 2001 +From: Romulo A. Ceccon +Date: Fri, 19 Jul 2013 11:24:40 -0300 +Subject: [PATCH 141/149] Test suite documentation + +Briefly clarified documentation about dependencies +of the test suite. + +Signed-off-by: Kamil Dudka +--- + README.rst | 5 +++++ + 1 files changed, 5 insertions(+), 0 deletions(-) + +diff --git a/README.rst b/README.rst +index c4427aa..b416b97 100644 +--- a/README.rst ++++ b/README.rst +@@ -56,6 +56,8 @@ PycURL comes with an automated test suite. To run the tests, execute:: + + make test + ++The suite depends on packages `nose`_ and `bottle`_. ++ + Some tests use vsftpd configured to accept anonymous uploads. These tests + are not run by default. As configured, vsftpd will allow reads and writes to + anything the user running the tests has read and write access. To run +@@ -67,6 +69,9 @@ vsftpd tests you must explicitly set PYCURL_VSFTPD_PATH variable like so:: + # specify full path to vsftpd + export PYCURL_VSFTPD_PATH=/usr/local/libexec/vsftpd + ++.. _nose: https://nose.readthedocs.org/ ++.. _bottle: http://bottlepy.org/ ++ + Contribute + ---------- + +-- +1.7.1 + + +From 59fd64c38e4adf28242dca3fd3d3a0167016945a Mon Sep 17 00:00:00 2001 +From: Wim Lewis +Date: Thu, 21 Jul 2011 12:00:00 -0400 +Subject: [PATCH 142/149] Add suport for CURLINFO_CERTINFO and CURLOPT_CERTINFO. + +This patch adds support for CURLINFO_CERTINFO (and CURLOPT_CERTINFO), +as well as documentation for them and a couple of minor documentation fixes. +These options appeared in libcurl 7.19.1. + +The format of the returned information was chosen to be reasonably close +to the underlying libcurl data structure, while also allowing a +Python programmer to pass the cerificate info to dict() in order to get +a more convenient representation. + +https://sourceforge.net/p/pycurl/patches/14/ + +Signed-off-by: Kamil Dudka +--- + doc/curlobject.html | 19 ++++++++++- + src/pycurl.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 100 insertions(+), 2 deletions(-) + +diff --git a/doc/curlobject.html b/doc/curlobject.html +index a3d421f..394b950 100644 +--- a/doc/curlobject.html ++++ b/doc/curlobject.html +@@ -43,9 +43,11 @@ href="http://curl.haxx.se/libcurl/c/curl_easy_reset.html">curl_easy_reset< +

Corresponds to + curl_easy_setopt in libcurl, where + option is specified with the CURLOPT_* constants in libcurl, +-except that the CURLOPT_ prefix has been removed. The type for ++except that the CURLOPT_ prefix has been removed. ++(See below for exceptions.) ++The type for + value depends on the option, and can be either a string, +-integer, long integer, file objects, lists, or functions.

++integer, long integer, file object, list, or function.

+ +

Example usage:

+ +@@ -72,6 +74,7 @@ print b.getvalue() + curl_easy_getinfo in libcurl, where + option is the same as the CURLINFO_* constants in libcurl, + except that the CURLINFO_ prefix has been removed. ++(See below for exceptions.) + Result contains an integer, float or string, depending on + which option is given. The getinfo method should + not be called unless perform has been called and +@@ -97,6 +100,18 @@ print c.getinfo(pycurl.HTTP_CODE), c.getinfo(pycurl.EFFECTIVE_URL) +
+
+ ++

In order to distinguish between similarly-named CURLOPT and ++CURLINFO constants, some have OPT_ ++and INFO_ prefixes. These are ++INFO_FILETIME, OPT_FILETIME, ++INFO_COOKIELIST (but setopt uses COOKIELIST!), ++INFO_CERTINFO, and OPT_CERTINFO.

++ ++

The value returned by getinfo(INFO_CERTINFO) is a list ++with one element per certificate in the chain, starting with the leaf; ++each element is a sequence ++of (key, value) ++tuples.

+ +
+

+diff --git a/src/pycurl.c b/src/pycurl.c +index bb6056e..5da354d 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -297,6 +297,71 @@ error: + return NULL; + } + ++/* Convert a struct curl_certinfo into a Python data structure. ++ * In case of error return NULL with an exception set. ++ */ ++static PyObject *convert_certinfo(struct curl_certinfo *cinfo) ++{ ++ PyObject *certs; ++ int cert_index; ++ ++ if (!cinfo) { ++ certs = Py_None; ++ Py_INCREF(certs); ++ return certs; ++ } ++ ++ certs = PyList_New((Py_ssize_t)(cinfo->num_of_certs)); ++ if (!certs) ++ return NULL; ++ ++ for (cert_index = 0; cert_index < cinfo->num_of_certs; cert_index ++) { ++ struct curl_slist *fields = cinfo->certinfo[cert_index]; ++ struct curl_slist *field_cursor; ++ int field_count, field_index; ++ PyObject *cert; ++ ++ field_count = 0; ++ field_cursor = fields; ++ while (field_cursor != NULL) { ++ field_cursor = field_cursor->next; ++ field_count ++; ++ } ++ ++ ++ cert = PyTuple_New((Py_ssize_t)field_count); ++ if (!cert) ++ goto error; ++ PyList_SetItem(certs, cert_index, cert); /* Eats the ref from New() */ ++ ++ for(field_index = 0, field_cursor = fields; ++ field_cursor != NULL; ++ field_index ++, field_cursor = field_cursor->next) { ++ const char *field = field_cursor->data; ++ PyObject *field_tuple; ++ ++ if (!field) { ++ field_tuple = Py_None; Py_INCREF(field_tuple); ++ } else { ++ const char *sep = strchr(field, ':'); ++ if (!sep) { ++ field_tuple = PyString_FromString(field); ++ } else { ++ field_tuple = Py_BuildValue("s#s", field, (int)(sep - field), sep+1); ++ } ++ if (!field_tuple) ++ goto error; ++ } ++ PyTuple_SET_ITEM(cert, field_index, field_tuple); /* Eats the ref */ ++ } ++ } ++ ++ return certs; ++ ++ error: ++ Py_XDECREF(certs); ++ return NULL; ++} + + #ifdef WITH_THREAD + /************************************************************************* +@@ -1673,6 +1738,10 @@ util_curl_unsetopt(CurlObject *self, int option) + SETOPT((char *) 0); + break; + ++ case CURLOPT_CERTINFO: ++ SETOPT((long) 0); ++ break; ++ + /* info: we explicitly list unsupported options here */ + case CURLOPT_COOKIEFILE: + default: +@@ -2394,6 +2463,18 @@ do_curl_getinfo(CurlObject *self, PyObject *args) + } + return convert_slist(slist, 1 | 2); + } ++ ++ case CURLINFO_CERTINFO: ++ { ++ /* Return a list of lists of 2-tuples */ ++ struct curl_certinfo *clist = NULL; ++ res = curl_easy_getinfo(self->handle, CURLINFO_CERTINFO, &clist); ++ if (res != CURLE_OK) { ++ CURLERROR_RETVAL(); ++ } else { ++ return convert_certinfo(clist); ++ } ++ } + } + + /* Got wrong option on the method call */ +@@ -3916,6 +3997,7 @@ initpycurl(void) + #ifdef HAVE_CURLOPT_RESOLVE + insint_c(d, "RESOLVE", CURLOPT_RESOLVE); + #endif ++ insint_c(d, "OPT_CERTINFO", CURLOPT_CERTINFO); + + insint_c(d, "M_TIMERFUNCTION", CURLMOPT_TIMERFUNCTION); + insint_c(d, "M_SOCKETFUNCTION", CURLMOPT_SOCKETFUNCTION); +@@ -3993,6 +4075,7 @@ initpycurl(void) + insint_c(d, "INFO_COOKIELIST", CURLINFO_COOKIELIST); + insint_c(d, "LASTSOCKET", CURLINFO_LASTSOCKET); + insint_c(d, "FTP_ENTRY_PATH", CURLINFO_FTP_ENTRY_PATH); ++ insint_c(d, "INFO_CERTINFO", CURLINFO_CERTINFO); + + /* options for global_init() */ + insint(d, "GLOBAL_SSL", CURL_GLOBAL_SSL); +-- +1.7.1 + + +From aa82321393ebc813d326cb305a1e2f73bbe45abe Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 30 May 2013 14:42:38 -0400 +Subject: [PATCH 143/149] Conditional compilation for CERTINFO bits + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 11 +++++++++++ + 1 files changed, 11 insertions(+), 0 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index 5da354d..c0fec4b 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -66,6 +66,7 @@ + LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR == 19 && LIBCURL_VERSION_PATCH >= 1 + #define HAVE_CURLOPT_USERNAME + #define HAVE_CURLOPT_PROXYUSERNAME ++#define HAVE_CURLOPT_CERTINFO + #endif + + #if LIBCURL_VERSION_NUM >= 0x071503 /* check for 7.21.3 or greater */ +@@ -297,6 +298,7 @@ error: + return NULL; + } + ++#ifdef HAVE_CURLOPT_CERTINFO + /* Convert a struct curl_certinfo into a Python data structure. + * In case of error return NULL with an exception set. + */ +@@ -362,6 +364,7 @@ static PyObject *convert_certinfo(struct curl_certinfo *cinfo) + Py_XDECREF(certs); + return NULL; + } ++#endif + + #ifdef WITH_THREAD + /************************************************************************* +@@ -1738,9 +1741,11 @@ util_curl_unsetopt(CurlObject *self, int option) + SETOPT((char *) 0); + break; + ++#ifdef HAVE_CURLOPT_CERTINFO + case CURLOPT_CERTINFO: + SETOPT((long) 0); + break; ++#endif + + /* info: we explicitly list unsupported options here */ + case CURLOPT_COOKIEFILE: +@@ -2464,6 +2469,7 @@ do_curl_getinfo(CurlObject *self, PyObject *args) + return convert_slist(slist, 1 | 2); + } + ++#ifdef HAVE_CURLOPT_CERTINFO + case CURLINFO_CERTINFO: + { + /* Return a list of lists of 2-tuples */ +@@ -2476,6 +2482,7 @@ do_curl_getinfo(CurlObject *self, PyObject *args) + } + } + } ++#endif + + /* Got wrong option on the method call */ + PyErr_SetString(PyExc_ValueError, "invalid argument to getinfo"); +@@ -3997,7 +4004,9 @@ initpycurl(void) + #ifdef HAVE_CURLOPT_RESOLVE + insint_c(d, "RESOLVE", CURLOPT_RESOLVE); + #endif ++#ifdef HAVE_CURLOPT_CERTINFO + insint_c(d, "OPT_CERTINFO", CURLOPT_CERTINFO); ++#endif + + insint_c(d, "M_TIMERFUNCTION", CURLMOPT_TIMERFUNCTION); + insint_c(d, "M_SOCKETFUNCTION", CURLMOPT_SOCKETFUNCTION); +@@ -4075,7 +4084,9 @@ initpycurl(void) + insint_c(d, "INFO_COOKIELIST", CURLINFO_COOKIELIST); + insint_c(d, "LASTSOCKET", CURLINFO_LASTSOCKET); + insint_c(d, "FTP_ENTRY_PATH", CURLINFO_FTP_ENTRY_PATH); ++#ifdef HAVE_CURLOPT_CERTINFO + insint_c(d, "INFO_CERTINFO", CURLINFO_CERTINFO); ++#endif + + /* options for global_init() */ + insint(d, "GLOBAL_SSL", CURL_GLOBAL_SSL); +-- +1.7.1 + + +From 68713450eef4fd4252babafefe24d42faebc6de9 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 30 May 2013 14:49:44 -0400 +Subject: [PATCH 144/149] Use an ordinary DECREF + +Signed-off-by: Kamil Dudka +--- + src/pycurl.c | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index c0fec4b..c8b8402 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -361,7 +361,7 @@ static PyObject *convert_certinfo(struct curl_certinfo *cinfo) + return certs; + + error: +- Py_XDECREF(certs); ++ Py_DECREF(certs); + return NULL; + } + #endif +-- +1.7.1 + + +From 53999f0ca88f6a71ff0b9331ce9da89a5f07fb0f Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Thu, 30 May 2013 15:35:31 -0400 +Subject: [PATCH 145/149] SSL certinfo test using github + +Signed-off-by: Kamil Dudka +--- + tests/certinfo_test.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 52 insertions(+), 0 deletions(-) + create mode 100644 tests/certinfo_test.py + +diff --git a/tests/certinfo_test.py b/tests/certinfo_test.py +new file mode 100644 +index 0000000..25c05af +--- /dev/null ++++ b/tests/certinfo_test.py +@@ -0,0 +1,52 @@ ++#! /usr/bin/env python ++# -*- coding: iso-8859-1 -*- ++# vi:ts=4:et ++ ++import pycurl ++import unittest ++import nose.plugins.skip ++ ++from . import util ++ ++class CertinfoTest(unittest.TestCase): ++ def setUp(self): ++ self.curl = pycurl.Curl() ++ ++ def tearDown(self): ++ self.curl.close() ++ ++ def test_certinfo_option(self): ++ # CURLOPT_CERTINFO was introduced in libcurl-7.19.1 ++ if util.pycurl_version_less_than(7, 19, 1): ++ raise nose.plugins.skip.SkipTest('libcurl < 7.19.1') ++ ++ assert hasattr(pycurl, 'OPT_CERTINFO') ++ ++ def test_request_without_certinfo(self): ++ # CURLOPT_CERTINFO was introduced in libcurl-7.19.1 ++ if util.pycurl_version_less_than(7, 19, 1): ++ raise nose.plugins.skip.SkipTest('libcurl < 7.19.1') ++ ++ self.curl.setopt(pycurl.URL, 'https://github.com/') ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.perform() ++ assert 'GitHub' in sio.getvalue() ++ ++ certinfo = self.curl.getinfo(pycurl.INFO_CERTINFO) ++ self.assertEqual([], certinfo) ++ ++ def test_request_with_certinfo(self): ++ # CURLOPT_CERTINFO was introduced in libcurl-7.19.1 ++ if util.pycurl_version_less_than(7, 19, 1): ++ raise nose.plugins.skip.SkipTest('libcurl < 7.19.1') ++ ++ self.curl.setopt(pycurl.URL, 'https://github.com/') ++ sio = util.StringIO() ++ self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ self.curl.setopt(pycurl.OPT_CERTINFO, 1) ++ self.curl.perform() ++ assert 'GitHub' in sio.getvalue() ++ ++ certinfo = self.curl.getinfo(pycurl.INFO_CERTINFO) ++ assert len(certinfo) > 0 +-- +1.7.1 + + +From 6ca51826965392ed3ac2338b5da7dac5215bd840 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 23 Jul 2013 20:10:47 -0400 +Subject: [PATCH 146/149] Self signed certificate for ssl tests + +Signed-off-by: Kamil Dudka +--- + tests/certs/server.crt | 14 ++++++++++++++ + tests/certs/server.key | 15 +++++++++++++++ + 2 files changed, 29 insertions(+), 0 deletions(-) + create mode 100644 tests/certs/server.crt + create mode 100644 tests/certs/server.key + +diff --git a/tests/certs/server.crt b/tests/certs/server.crt +new file mode 100644 +index 0000000..4a8decc +--- /dev/null ++++ b/tests/certs/server.crt +@@ -0,0 +1,14 @@ ++-----BEGIN CERTIFICATE----- ++MIICJTCCAY4CCQDfQAHGuFkN2zANBgkqhkiG9w0BAQUFADBXMQswCQYDVQQGEwJB ++VTETMBEGA1UECBMKU29tZS1TdGF0ZTEaMBgGA1UEChMRcHljdXJsIHRlc3Qgc3Vp ++dGUxFzAVBgNVBAMTDmxvY2FsaG9zdDo4MzgzMB4XDTEzMDcyNDAwMDgxNVoXDTE0 ++MDcyNDAwMDgxNVowVzELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx ++GjAYBgNVBAoTEXB5Y3VybCB0ZXN0IHN1aXRlMRcwFQYDVQQDEw5sb2NhbGhvc3Q6 ++ODM4MzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxE0+59Kf2z9ccZyUAuKG ++QpkQaXtEJC13exY4SWIfr78FfCStdqpZdfmm66djFENhmaAZYGsPHGXrEIHQqja2 ++7KYkHo4cXLxksR4Db01yPMtMU9xHzg37OTIS2lGRmMxLduKc5XKsxA98PV/D1k4k ++sqLcLDH//YdLR0iYUYLOIgMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQAppFdMNMHe ++68uQA1y2xAYW7faUH8/g+XAuH9WjLL2QfWGXgWey/pwofsrTO2Hl+D9y8Rey4eJ/ ++BDv3OV2cBWBYBOxZv/kqyDHQc38tho9gdaPQnD4ttFk2TSgaOs1W39pGY1On0Ejd ++O6CXEGV7p8C613AgEkbdudnn+ChvyH/Shw== ++-----END CERTIFICATE----- +diff --git a/tests/certs/server.key b/tests/certs/server.key +new file mode 100644 +index 0000000..5bdbbf9 +--- /dev/null ++++ b/tests/certs/server.key +@@ -0,0 +1,15 @@ ++-----BEGIN RSA PRIVATE KEY----- ++MIICXAIBAAKBgQDETT7n0p/bP1xxnJQC4oZCmRBpe0QkLXd7FjhJYh+vvwV8JK12 ++qll1+abrp2MUQ2GZoBlgaw8cZesQgdCqNrbspiQejhxcvGSxHgNvTXI8y0xT3EfO ++Dfs5MhLaUZGYzEt24pzlcqzED3w9X8PWTiSyotwsMf/9h0tHSJhRgs4iAwIDAQAB ++AoGAMLNFTvgnJpqikaEZ61lNm8ojkze8oQkSolvR3QrV96D5eGIVEuKSTT2Blucx ++In7RAO8CPLRyzEXQuoiqPwBSAxY2Xswd+zcAgN2Zi8uqWTmPNsW6451BJRemgLjK ++OxLxCdVTOTxHfttj/CnwYQ6zn55oyZJGGmaVGykbvH/AgukCQQD3HfhOPExsI/6X ++Bp7CeZOhmM+LBOQGQGDjRnBdRp0s3HiUfaDxU2mbEafGPI2OyuzpYAqxHVTJLai6 ++CQlJGuQXAkEAy1upObz2bcN2dUCHNufk2qdfRSCRkmKemuqznwCW3fSoRKB+qOu3 ++xyTLEkTvLBNnAFjoyd6B75QzL/7//qvo9QJAE0xV3dY7qZ5N/YFY2Jsh+layroqd ++PBe++UDA+afQEnbNO9trvCzlbGS+k26bJ3GVeswzSY2e128nZA/cl8bv1QJAfTEO ++uybjpqtAj+qL03drYljLw+jK9Y2VCtYWgnqAZmAp/yW3FBMZbpXuFm8ttrqzHHmf ++xjcfUvivkoqv2n7GyQJBAKxbBVx/LQiSVpOTnXTEA1NJF8NS2NCF+3sm3kGhFKql ++Hi/cCAFrhBl9MoPJF/6noukfIkq0SzjkWrYIcoBDoVg= ++-----END RSA PRIVATE KEY----- +-- +1.7.1 + + +From 2bc1535519faf3e574bac514b634d29ed7e28c20 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Sat, 1 Jun 2013 05:23:41 -0400 +Subject: [PATCH 147/149] Support for testing against a local ssl server + +Signed-off-by: Kamil Dudka +--- + tests/runwsgi.py | 34 +++++++++++++++++++++++++++++----- + 1 files changed, 29 insertions(+), 5 deletions(-) + +diff --git a/tests/runwsgi.py b/tests/runwsgi.py +index 114ce42..c9bc236 100644 +--- a/tests/runwsgi.py ++++ b/tests/runwsgi.py +@@ -4,6 +4,7 @@ import sys + import bottle + import threading + import socket ++import os.path + import time as _time + + try: +@@ -37,6 +38,21 @@ class Server(bottle.WSGIRefServer): + else: + self.srv.serve_forever(poll_interval=0.1) + ++class SslServer(bottle.CherryPyServer): ++ def run(self, handler): ++ import cherrypy.wsgiserver, cherrypy.wsgiserver.ssl_builtin ++ server = cherrypy.wsgiserver.CherryPyWSGIServer((self.host, self.port), handler) ++ cert_dir = os.path.join(os.path.dirname(__file__), 'certs') ++ ssl_adapter = cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter( ++ os.path.join(cert_dir, 'server.crt'), ++ os.path.join(cert_dir, 'server.key'), ++ ) ++ server.ssl_adapter = ssl_adapter ++ try: ++ server.start() ++ finally: ++ server.stop() ++ + def wait_for_network_service(netloc, check_interval, num_attempts): + ok = False + for i in range(num_attempts): +@@ -51,8 +67,8 @@ def wait_for_network_service(netloc, check_interval, num_attempts): + break + return ok + +-def start_bottle_server(app, port, **kwargs): +- server_thread = ServerThread(app, port, kwargs) ++def start_bottle_server(app, port, server, **kwargs): ++ server_thread = ServerThread(app, port, server, kwargs) + server_thread.daemon = True + server_thread.start() + +@@ -64,12 +80,12 @@ def start_bottle_server(app, port, **kwargs): + return server_thread.server + + class ServerThread(threading.Thread): +- def __init__(self, app, port, server_kwargs): ++ def __init__(self, app, port, server, 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) ++ self.server = server(host='localhost', port=self.port, **self.server_kwargs) + + def run(self): + bottle.run(self.app, server=self.server, quiet=True) +@@ -104,7 +120,15 @@ def app_runner_setup(*specs): + if port in started_servers: + assert started_servers[port] == (app, kwargs) + else: +- self.servers.append(start_bottle_server(app, port, **kwargs)) ++ server = Server ++ if 'server' in kwargs: ++ server = kwargs['server'] ++ del kwargs['server'] ++ elif 'ssl' in kwargs: ++ if kwargs['ssl']: ++ server = SslServer ++ del kwargs['ssl'] ++ self.servers.append(start_bottle_server(app, port, server, **kwargs)) + started_servers[port] = (app, kwargs) + + def teardown(self): +-- +1.7.1 + + +From 1bae541fbeb5d7554c29d76f7e57dc3bc6cb4985 Mon Sep 17 00:00:00 2001 +From: Oleg Pudeyev +Date: Tue, 23 Jul 2013 20:11:25 -0400 +Subject: [PATCH 148/149] Test against a local ssl server + +Signed-off-by: Kamil Dudka +--- + tests/certinfo_test.py | 26 +++++++++++++++++++++----- + 1 files changed, 21 insertions(+), 5 deletions(-) + +diff --git a/tests/certinfo_test.py b/tests/certinfo_test.py +index 25c05af..1473d39 100644 +--- a/tests/certinfo_test.py ++++ b/tests/certinfo_test.py +@@ -6,8 +6,12 @@ import pycurl + import unittest + import nose.plugins.skip + ++from . import app ++from . import runwsgi + from . import util + ++setup_module, teardown_module = runwsgi.app_runner_setup((app.app, 8383, dict(ssl=True))) ++ + class CertinfoTest(unittest.TestCase): + def setUp(self): + self.curl = pycurl.Curl() +@@ -27,11 +31,13 @@ class CertinfoTest(unittest.TestCase): + if util.pycurl_version_less_than(7, 19, 1): + raise nose.plugins.skip.SkipTest('libcurl < 7.19.1') + +- self.curl.setopt(pycurl.URL, 'https://github.com/') ++ self.curl.setopt(pycurl.URL, 'https://localhost:8383/success') + sio = util.StringIO() + self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) ++ # self signed certificate ++ self.curl.setopt(pycurl.SSL_VERIFYPEER, 0) + self.curl.perform() +- assert 'GitHub' in sio.getvalue() ++ assert sio.getvalue() == 'success' + + certinfo = self.curl.getinfo(pycurl.INFO_CERTINFO) + self.assertEqual([], certinfo) +@@ -41,12 +47,22 @@ class CertinfoTest(unittest.TestCase): + if util.pycurl_version_less_than(7, 19, 1): + raise nose.plugins.skip.SkipTest('libcurl < 7.19.1') + +- self.curl.setopt(pycurl.URL, 'https://github.com/') ++ self.curl.setopt(pycurl.URL, 'https://localhost:8383/success') + sio = util.StringIO() + self.curl.setopt(pycurl.WRITEFUNCTION, sio.write) + self.curl.setopt(pycurl.OPT_CERTINFO, 1) ++ # self signed certificate ++ self.curl.setopt(pycurl.SSL_VERIFYPEER, 0) + self.curl.perform() +- assert 'GitHub' in sio.getvalue() ++ assert sio.getvalue() == 'success' + + certinfo = self.curl.getinfo(pycurl.INFO_CERTINFO) +- assert len(certinfo) > 0 ++ # self signed certificate, one certificate in chain ++ assert len(certinfo) == 1 ++ certinfo = certinfo[0] ++ # convert to a dictionary ++ certinfo_dict = {} ++ for entry in certinfo: ++ certinfo_dict[entry[0]] = entry[1] ++ assert 'Subject' in certinfo_dict ++ assert 'pycurl test suite' in certinfo_dict['Subject'] +-- +1.7.1 + + +From bb2b2e6f5a7dd7a343e2c1207ca152dbeb4cf14b Mon Sep 17 00:00:00 2001 +From: Romulo A. Ceccon +Date: Fri, 26 Jul 2013 10:23:26 -0300 +Subject: [PATCH 149/149] Added documentation for dependency on CherryPy + +Signed-off-by: Kamil Dudka +--- + README.rst | 3 ++- + requirements-dev.txt | 1 + + 2 files changed, 3 insertions(+), 1 deletions(-) + +diff --git a/README.rst b/README.rst +index b416b97..e0641d0 100644 +--- a/README.rst ++++ b/README.rst +@@ -56,7 +56,7 @@ PycURL comes with an automated test suite. To run the tests, execute:: + + make test + +-The suite depends on packages `nose`_ and `bottle`_. ++The suite depends on packages `nose`_, `bottle`_ and `cherrypy`_. + + Some tests use vsftpd configured to accept anonymous uploads. These tests + are not run by default. As configured, vsftpd will allow reads and writes to +@@ -71,6 +71,7 @@ vsftpd tests you must explicitly set PYCURL_VSFTPD_PATH variable like so:: + + .. _nose: https://nose.readthedocs.org/ + .. _bottle: http://bottlepy.org/ ++.. _cherrypy: http://www.cherrypy.org/ + + Contribute + ---------- +diff --git a/requirements-dev.txt b/requirements-dev.txt +index 36b0b24..ea11ac9 100644 +--- a/requirements-dev.txt ++++ b/requirements-dev.txt +@@ -1,2 +1,3 @@ + bottle + nose ++cherrypy +-- +1.7.1 + diff --git a/0000-pycurl-7.19.7-9b8f4e38.patch b/0000-pycurl-7.19.7-9b8f4e38.patch deleted file mode 100644 index 8666d1b..0000000 --- a/0000-pycurl-7.19.7-9b8f4e38.patch +++ /dev/null @@ -1,9416 +0,0 @@ -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-add-the-GLOBAL_ACK_EINTR-constant-to-the-list-of-exp.patch b/0001-add-the-GLOBAL_ACK_EINTR-constant-to-the-list-of-exp.patch deleted file mode 100644 index 40a0347..0000000 --- a/0001-add-the-GLOBAL_ACK_EINTR-constant-to-the-list-of-exp.patch +++ /dev/null @@ -1,61 +0,0 @@ -From a2fb13434cf975b2e9b19067c8968f91e190de5b Mon Sep 17 00:00:00 2001 -From: Zdenek Pavlas -Date: Wed, 13 Mar 2013 16:55:58 +0100 -Subject: [PATCH 1/2] add the GLOBAL_ACK_EINTR constant to the list of exported symbols - -... if built against a new enough version of libcurl - -Bug: https://bugzilla.redhat.com/920589 - -Signed-off-by: Kamil Dudka ---- - src/pycurl.c | 19 +++++++++++++++---- - 1 files changed, 15 insertions(+), 4 deletions(-) - -diff --git a/src/pycurl.c b/src/pycurl.c -index 619ca20..9950e00 100644 ---- a/src/pycurl.c -+++ b/src/pycurl.c -@@ -3210,6 +3210,16 @@ static PyTypeObject CurlMulti_Type = { - */ - }; - -+static int -+are_global_init_flags_valid(int flags) -+{ -+#ifdef CURL_GLOBAL_ACK_EINTR -+ /* CURL_GLOBAL_ACK_EINTR was introduced in libcurl-7.30.0 */ -+ return !(flags & ~(CURL_GLOBAL_ALL | CURL_GLOBAL_ACK_EINTR)); -+#else -+ return !(flags & ~(CURL_GLOBAL_ALL)); -+#endif -+} - - /************************************************************************* - // module level -@@ -3227,10 +3237,7 @@ do_global_init(PyObject *dummy, PyObject *args) - return NULL; - } - -- if (!(option == CURL_GLOBAL_SSL || -- option == CURL_GLOBAL_WIN32 || -- option == CURL_GLOBAL_ALL || -- option == CURL_GLOBAL_NOTHING)) { -+ if (!are_global_init_flags_valid(option)) { - PyErr_SetString(PyExc_ValueError, "invalid option to global_init"); - return NULL; - } -@@ -3866,6 +3873,10 @@ initpycurl(void) - insint(d, "GLOBAL_ALL", CURL_GLOBAL_ALL); - insint(d, "GLOBAL_NOTHING", CURL_GLOBAL_NOTHING); - insint(d, "GLOBAL_DEFAULT", CURL_GLOBAL_DEFAULT); -+#ifdef CURL_GLOBAL_ACK_EINTR -+ /* CURL_GLOBAL_ACK_EINTR was introduced in libcurl-7.30.0 */ -+ insint(d, "GLOBAL_ACK_EINTR", CURL_GLOBAL_ACK_EINTR); -+#endif - - - /* constants for curl_multi_socket interface */ --- -1.7.1 - diff --git a/0001-do_curl_getinfo-fix-misplaced-endif.patch b/0001-do_curl_getinfo-fix-misplaced-endif.patch new file mode 100644 index 0000000..0e8261b --- /dev/null +++ b/0001-do_curl_getinfo-fix-misplaced-endif.patch @@ -0,0 +1,26 @@ +From dc431c729b9639485a45dbd91e020c5c95508c90 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Thu, 8 Aug 2013 12:51:48 +0200 +Subject: [PATCH 1/2] do_curl_getinfo: fix misplaced #endif + +--- + src/pycurl.c | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/src/pycurl.c b/src/pycurl.c +index c8b8402..87dac7e 100644 +--- a/src/pycurl.c ++++ b/src/pycurl.c +@@ -2481,8 +2481,8 @@ do_curl_getinfo(CurlObject *self, PyObject *args) + return convert_certinfo(clist); + } + } +- } + #endif ++ } + + /* Got wrong option on the method call */ + PyErr_SetString(PyExc_ValueError, "invalid argument to getinfo"); +-- +1.7.1 + diff --git a/0002-runwsgi.py-start-the-server-explicitly-at-127.0.0.1.patch b/0002-runwsgi.py-start-the-server-explicitly-at-127.0.0.1.patch new file mode 100644 index 0000000..e8a7591 --- /dev/null +++ b/0002-runwsgi.py-start-the-server-explicitly-at-127.0.0.1.patch @@ -0,0 +1,26 @@ +From 9540e6097563ea754b8619fe85ba010071a67244 Mon Sep 17 00:00:00 2001 +From: Kamil Dudka +Date: Thu, 8 Aug 2013 12:54:53 +0200 +Subject: [PATCH 2/2] runwsgi.py: start the server explicitly at 127.0.0.1 + +Otherwise it may start on ::1, which would consequently break self-test. +--- + tests/runwsgi.py | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/tests/runwsgi.py b/tests/runwsgi.py +index c9bc236..096038c 100644 +--- a/tests/runwsgi.py ++++ b/tests/runwsgi.py +@@ -85,7 +85,7 @@ class ServerThread(threading.Thread): + self.app = app + self.port = port + self.server_kwargs = server_kwargs +- self.server = server(host='localhost', port=self.port, **self.server_kwargs) ++ self.server = server(host='127.0.0.1', port=self.port, **self.server_kwargs) + + def run(self): + bottle.run(self.app, server=self.server, quiet=True) +-- +1.7.1 + diff --git a/0002-tests-global_init_ack_eintr.py-test-GLOBAL_ACK_EINTR.patch b/0002-tests-global_init_ack_eintr.py-test-GLOBAL_ACK_EINTR.patch deleted file mode 100644 index 4768b7f..0000000 --- a/0002-tests-global_init_ack_eintr.py-test-GLOBAL_ACK_EINTR.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 3fba1b63a99f68bedb9a3d60326d22bd8f10f83b Mon Sep 17 00:00:00 2001 -From: Kamil Dudka -Date: Wed, 3 Apr 2013 15:06:32 +0200 -Subject: [PATCH 2/2] tests/global_init_ack_eintr.py: test GLOBAL_ACK_EINTR - -... if we have a new enough version of libcurl ---- - tests/global_init_ack_eintr.py | 22 ++++++++++++++++++++++ - 1 files changed, 22 insertions(+), 0 deletions(-) - create mode 100644 tests/global_init_ack_eintr.py - -diff --git a/tests/global_init_ack_eintr.py b/tests/global_init_ack_eintr.py -new file mode 100644 -index 0000000..429fc3f ---- /dev/null -+++ b/tests/global_init_ack_eintr.py -@@ -0,0 +1,22 @@ -+#! /usr/bin/env python -+# -*- coding: iso-8859-1 -*- -+# vi:ts=4:et -+ -+import pycurl -+import unittest -+ -+from . import util -+ -+class GlobalInitAckEintrTest(unittest.TestCase): -+ def test_global_init_default(self): -+ # initialize libcurl with DEFAULT flags -+ pycurl.global_init(pycurl.GLOBAL_DEFAULT) -+ pycurl.global_cleanup() -+ -+ def test_global_init_ack_eintr(self): -+ # the GLOBAL_ACK_EINTR flag was introduced in libcurl-7.30, but can also -+ # be backported for older versions of libcurl at the distribution level -+ if not util.pycurl_version_less_than(7, 30) or hasattr(pycurl, 'GLOBAL_ACK_EINTR'): -+ # initialize libcurl with the GLOBAL_ACK_EINTR flag -+ pycurl.global_init(pycurl.GLOBAL_ACK_EINTR) -+ pycurl.global_cleanup() --- -1.7.1 - diff --git a/python-pycurl.spec b/python-pycurl.spec index 6064254..8f16961 100644 --- a/python-pycurl.spec +++ b/python-pycurl.spec @@ -2,7 +2,7 @@ Name: python-pycurl Version: 7.19.0 -Release: 17.20120408git9b8f4e38%{?dist} +Release: 18.20130315git8d654296%{?dist} Summary: A Python interface to libcurl Group: Development/Languages @@ -10,18 +10,19 @@ License: LGPLv2+ or MIT URL: http://pycurl.sourceforge.net/ Source0: http://pycurl.sourceforge.net/download/pycurl-%{version}.tar.gz -# sync with upstream's 9b8f4e38 -Patch0: 0000-pycurl-7.19.7-9b8f4e38.patch +# sync with upstream's 8d654296 +Patch0: 0000-pycurl-7.19.7-8d654296.patch -# bz #920589 - add the GLOBAL_ACK_EINTR constant to the list of exported symbols -Patch1: 0001-add-the-GLOBAL_ACK_EINTR-constant-to-the-list-of-exp.patch -Patch2: 0002-tests-global_init_ack_eintr.py-test-GLOBAL_ACK_EINTR.patch +# get the test-suite running +Patch1: 0001-do_curl_getinfo-fix-misplaced-endif.patch +Patch2: 0002-runwsgi.py-start-the-server-explicitly-at-127.0.0.1.patch Requires: keyutils-libs BuildRequires: python-devel BuildRequires: curl-devel >= 7.19.0 BuildRequires: openssl-devel BuildRequires: python-bottle +BuildRequires: python-cherrypy BuildRequires: python-nose BuildRequires: vsftpd @@ -56,6 +57,9 @@ find -type f | xargs sed -i 's/\$Id: [^$]*\$/$Id$/' %patch1 -p1 %patch2 -p1 +# remove a test specific to OpenSSL-powered libcurl +rm -f tests/certinfo_test.py + # temporarily disable intermittently failing test-case rm -f tests/multi_socket_select_test.py @@ -75,6 +79,9 @@ rm -rf %{buildroot}%{_datadir}/doc/pycurl %{python_sitearch}/* %changelog +* Thu Aug 08 2013 Kamil Dudka - 7.19.0-18.20130315git8d654296 +- sync with upstream 8d654296 + * Sun Aug 04 2013 Fedora Release Engineering - 7.19.0-17.20120408git9b8f4e38 - Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild