summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/.gitignore8
-rw-r--r--test/Makefile.am25
-rw-r--r--test/test-args.cc213
-rw-r--r--test/test-http.cc477
-rw-r--r--test/test-paths.cc55
-rw-r--r--test/test-strings.cc55
-rw-r--r--test/test-url.cc212
-rw-r--r--test/test-xdg.cc60
-rw-r--r--test/test.hh49
9 files changed, 1154 insertions, 0 deletions
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 0000000..3876425
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1,8 @@
+/*.log
+/*.trs
+/test-args
+/test-http
+/test-paths
+/test-strings
+/test-url
+/test-xdg \ No newline at end of file
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..c23bd9f
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,25 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CXXFLAGS = @DEFINES@
+
+TESTS = test-url test-http test-args test-xdg test-paths test-strings
+
+check_PROGRAMS = $(TESTS)
+
+test_url_SOURCES = test-url.cc
+test_url_LDADD = $(top_builddir)/src/libtp.a
+
+test_http_SOURCES = test-http.cc
+test_http_LDADD = $(top_builddir)/src/libtp.a
+
+test_args_SOURCES = test-args.cc
+test_args_LDADD = $(top_builddir)/src/libtp.a
+
+test_xdg_SOURCES = test-xdg.cc
+test_xdg_LDADD = $(top_builddir)/src/libtp.a
+
+test_strings_SOURCES = test-strings.cc
+test_strings_LDADD = $(top_builddir)/src/libtp.a
+
+test_paths_SOURCES = test-paths.cc
+test_paths_LDADD = $(top_builddir)/src/libtp.a
diff --git a/test/test-args.cc b/test/test-args.cc
new file mode 100644
index 0000000..30057e4
--- /dev/null
+++ b/test/test-args.cc
@@ -0,0 +1,213 @@
+// -*- mode: c++; c-basic-offset: 2; -*-
+
+#include "common.hh"
+#include "test.hh"
+
+#include "args.hh"
+
+#include <algorithm>
+#include <memory>
+#include <sstream>
+#include <stdarg.h>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace {
+
+char const* basic_so[] = { "h", "C", "X", NULL };
+char const* basic_lo[] = { "help", "config", "verbose", "output", NULL };
+
+std::unique_ptr<Args> setup_basic() {
+ auto ret = Args::create();
+ ret->add('h', "help", "help");
+ ret->add('C', "config", "FILE", "config FILE");
+ ret->add("verbose", "be verbose");
+ ret->add('X', "", "TEST", "test");
+ ret->add("output", "OUT", "output");
+ return std::unique_ptr<Args>(ret);
+}
+
+bool test_basic(char const* first, ...) {
+ auto args = setup_basic();
+ std::vector<std::string> so;
+ std::vector<std::string> lo;
+ std::vector<std::string> a;
+ std::vector<std::string> argv;
+ va_list tmp;
+ char const* arg;
+ va_start(tmp, first);
+ if (first) {
+ so.push_back(first);
+ while (true) {
+ arg = va_arg(tmp, char const*);
+ if (!arg) break;
+ so.push_back(arg);
+ }
+ }
+ while (true) {
+ arg = va_arg(tmp, char const*);
+ if (!arg) break;
+ lo.push_back(arg);
+ }
+ while (true) {
+ arg = va_arg(tmp, char const*);
+ if (!arg) break;
+ a.push_back(arg);
+ }
+ argv.push_back("program");
+ while (true) {
+ arg = va_arg(tmp, char const*);
+ if (!arg) break;
+ argv.push_back(arg);
+ }
+ va_end(tmp);
+
+ auto tmp_argv = std::unique_ptr<char*[]>(new char*[argv.size()]);
+ for (size_t i = 0; i < argv.size(); ++i) {
+ tmp_argv[i] = &argv[i][0];
+ }
+ ASSERT_EQ(true, args->run(argv.size(), tmp_argv.get(), std::cerr));
+ ASSERT_EQ(true, args->good());
+ for (auto i = basic_so; *i; ++i) {
+ ASSERT_EQ(std::count(so.begin(), so.end(), *i) > 0, args->is_set(**i));
+ }
+ for (auto i = basic_lo; *i; ++i) {
+ ASSERT_EQ(std::count(lo.begin(), lo.end(), *i) > 0, args->is_set(*i));
+ }
+ ASSERT_EQ(a.size(), args->arguments().size());
+ for (size_t i = 0; i < a.size(); ++i) {
+ ASSERT_EQ(a[i], args->arguments()[i]);
+ }
+ return true;
+}
+
+bool test_basic_arg(char const* first, ...) {
+ auto args = setup_basic();
+ std::unordered_map<std::string, char const*> so;
+ std::unordered_map<std::string, char const*> lo;
+ std::vector<std::string> a;
+ std::vector<std::string> argv;
+ va_list tmp;
+ char const* arg;
+ va_start(tmp, first);
+ if (first) {
+ so.insert(std::make_pair(first, va_arg(tmp, char const*)));
+ while (true) {
+ arg = va_arg(tmp, char const*);
+ if (!arg) break;
+ so.insert(std::make_pair(arg, va_arg(tmp, char const*)));
+ }
+ }
+ while (true) {
+ arg = va_arg(tmp, char const*);
+ if (!arg) break;
+ lo.insert(std::make_pair(arg, va_arg(tmp, char const*)));
+ }
+ while (true) {
+ arg = va_arg(tmp, char const*);
+ if (!arg) break;
+ a.push_back(arg);
+ }
+ argv.push_back("/program");
+ while (true) {
+ arg = va_arg(tmp, char const*);
+ if (!arg) break;
+ argv.push_back(arg);
+ }
+ va_end(tmp);
+
+ auto tmp_argv = std::unique_ptr<char*[]>(new char*[argv.size()]);
+ for (size_t i = 0; i < argv.size(); ++i) {
+ tmp_argv[i] = &argv[i][0];
+ }
+ ASSERT_EQ(true, args->run(argv.size(), tmp_argv.get(), std::cerr));
+ ASSERT_EQ(true, args->good());
+ for (auto i = basic_so; *i; ++i) {
+ ASSERT_EQ(so.count(*i) > 0, args->is_set(**i));
+ }
+ for (auto& i : so) {
+ auto tmp = args->arg(i.first[0], nullptr);
+ ASSERT_STREQ(i.second, tmp);
+ }
+ for (auto i = basic_lo; *i; ++i) {
+ ASSERT_EQ(lo.count(*i) > 0, args->is_set(*i));
+ }
+ for (auto& i : lo) {
+ auto tmp = args->arg(i.first, nullptr);
+ ASSERT_STREQ(i.second, tmp);
+ }
+ ASSERT_EQ(a.size(), args->arguments().size());
+ for (size_t i = 0; i < a.size(); ++i) {
+ ASSERT_EQ(a[i], args->arguments()[i]);
+ }
+ return true;
+}
+
+bool test_basic_fail(char const* first, ...) {
+ auto args = setup_basic();
+ std::vector<std::string> argv;
+ va_list tmp;
+ char const* arg;
+ va_start(tmp, first);
+ argv.push_back("/usr/bin/program");
+ if (first) {
+ argv.push_back(first);
+ while (true) {
+ arg = va_arg(tmp, char const*);
+ if (!arg) break;
+ argv.push_back(arg);
+ }
+ }
+ va_end(tmp);
+
+ auto tmp_argv = std::unique_ptr<char*[]>(new char*[argv.size()]);
+ for (size_t i = 0; i < argv.size(); ++i) {
+ tmp_argv[i] = &argv[i][0];
+ }
+ std::stringstream out;
+ ASSERT_EQ(false, args->run(argv.size(), tmp_argv.get(), out));
+ ASSERT_EQ(false, args->good());
+ return true;
+}
+
+} // namespace
+
+int main(void) {
+ BEFORE;
+ RUN(test_basic(NULL, NULL, NULL, NULL)); // no arguments
+ RUN(test_basic("h", NULL, "help", NULL, NULL, "--help", NULL));
+ RUN(test_basic("h", NULL, "help", NULL, NULL, "-h", NULL));
+ RUN(test_basic("h", NULL, "help", NULL, "arg", NULL, "--help", "arg", NULL));
+ RUN(test_basic("h", NULL, "help", NULL, "arg", NULL, "arg", "--help", NULL));
+ RUN(test_basic("h", NULL, "help", "verbose", NULL, NULL, "--verbose", "--help", NULL));
+ RUN(test_basic("h", NULL, "help", "verbose", NULL, NULL, "--verbose", "-h", NULL));
+ RUN(test_basic(NULL, NULL, "arg", "--help", NULL, "--", "arg", "--help", NULL));
+ RUN(test_basic(NULL, NULL, "arg", "--help", NULL, "arg", "--", "--help", NULL));
+ RUN(test_basic_arg("C", "configfile", NULL, "config", "configfile", NULL,
+ NULL, "-C", "configfile", NULL));
+ RUN(test_basic_arg("C", "configfile", NULL, "config", "configfile", NULL,
+ NULL, "--config", "configfile", NULL));
+ RUN(test_basic_arg("C", "configfile", NULL, "config", "configfile", NULL,
+ NULL, "--config=configfile", NULL));
+ RUN(test_basic_arg("C", "configfile", "h", NULL,
+ NULL,
+ "config", "configfile", "help", NULL,
+ NULL,
+ NULL, "-Ch", "configfile", NULL));
+ RUN(test_basic_arg("C", "configfile", "X", "test",
+ NULL,
+ "config", "configfile",
+ NULL,
+ NULL, "-CX", "configfile", "test", NULL));
+ RUN(test_basic_arg("X", "-C", NULL, NULL, NULL, "-X", "-C", NULL));
+ RUN(test_basic_fail("-C", NULL));
+ RUN(test_basic_fail("--config", NULL));
+ RUN(test_basic_fail("-X", NULL));
+ RUN(test_basic_fail("-Y", NULL));
+ RUN(test_basic_fail("-XC", NULL));
+ RUN(test_basic_fail("--output", NULL));
+ RUN(test_basic_fail("--outp", NULL));
+ RUN(test_basic_fail("--help=", NULL));
+ AFTER;
+}
diff --git a/test/test-http.cc b/test/test-http.cc
new file mode 100644
index 0000000..4b1b62f
--- /dev/null
+++ b/test/test-http.cc
@@ -0,0 +1,477 @@
+// -*- mode: c++; c-basic-offset: 2; -*-
+
+#include "common.hh"
+#include "test.hh"
+
+#include <cstdarg>
+#include <cstring>
+
+#include "http.hh"
+
+namespace {
+
+bool find_header(HeaderIterator* iter, std::string const& name,
+ std::string* value) {
+ for (; iter->valid(); iter->next()) {
+ if (iter->name_equal(name)) {
+ if (value) value->assign(iter->value());
+ return true;
+ }
+ }
+ return false;
+}
+
+bool good_resp(std::string const& name,
+ std::string const& in,
+ std::string const& proto,
+ uint16_t major, uint16_t minor,
+ uint16_t status, std::string const& status_msg,
+ char const* content,
+ ...) {
+ std::unique_ptr<HttpResponse> resp;
+ size_t test = 0;
+ while (test <= in.size()) {
+ resp.reset(HttpResponse::parse(in.substr(0, test)));
+ if (resp) break;
+ ++test;
+ }
+ if (!resp || !resp->good()) {
+ std::cerr << "good_resp:" << name << ": Invalid response" << std::endl;
+ return false;
+ }
+ resp.reset(HttpResponse::parse(in));
+ if (!resp || !resp->good()) {
+ std::cerr << "good_resp:"
+ << name << ": Incomplete/invalid response" << std::endl;
+ return false;
+ }
+ if (proto != resp->proto() || !resp->proto_equal(proto)) {
+ std::cerr << "good_resp:" << name << ":protocol: Expected " << proto
+ << " got " << resp->proto() << std::endl;
+ return false;
+ }
+ if (major != resp->proto_version().major
+ || minor != resp->proto_version().minor) {
+ std::cerr << "good_resp:" << name << ":protocol_version: Expected "
+ << major << "." << minor << " got "
+ << resp->proto_version().major << "."
+ << resp->proto_version().minor << std::endl;
+ return false;
+ }
+ if (status != resp->status_code()) {
+ std::cerr << "good_resp:" << name << ":status: Expected " << status
+ << " got " << resp->status_code() << std::endl;
+ return false;
+ }
+ if (status_msg != resp->status_message()) {
+ std::cerr << "good_resp:" << name << ":status: Expected " << status_msg
+ << " got " << resp->status_message() << std::endl;
+ return false;
+ }
+ va_list headers;
+ va_start(headers, content);
+ while (true) {
+ auto header = va_arg(headers, char const*);
+ if (!header) break;
+ auto value = va_arg(headers, char const*);
+ auto iter = resp->header();
+ std::string tmp;
+ if (find_header(iter.get(), header, &tmp)) {
+ if (tmp.compare(value)) {
+ std::cerr << "good_resp:" << name << ":header" << header
+ << ": Expected " << value << " got " << tmp
+ << std::endl;
+ return false;
+ }
+ } else {
+ std::cerr << "good_resp:" << name << ":header:" << header
+ << ": Expected " << value << " got no value" << std::endl;
+ return false;
+ }
+ iter = resp->header(header);
+ if (!iter->valid()) {
+ std::cerr << "good_resp:" << name << ":header2:" << header
+ << ": Expected " << value << " got no value" << std::endl;
+ return false;
+ }
+ if (!iter->name_equal(header)) {
+ std::cerr << "good_resp:" << name << ":header2:" << header
+ << ": Expected " << header << " got " << iter->name()
+ << std::endl;
+ return false;
+ }
+ if (iter->value().compare(value)) {
+ std::cerr << "good_resp:" << name << ":header2:" << header
+ << ": Expected " << value << " got " << iter->value()
+ << std::endl;
+ return false;
+ }
+ iter->next();
+ if (iter->valid()) {
+ std::cerr << "good_resp:" << name << ":header2:" << header
+ << ": Expected " << value << " got multiple values"
+ << std::endl;
+ return false;
+ }
+ }
+ va_end(headers);
+ if (resp->size() != test) {
+ std::cerr << "good_resp:" << name << ":size: Expected " << test
+ << " got " << resp->size() << std::endl;
+ return false;
+ }
+ auto size = strlen(content);
+ std::string rest = in.substr(resp->size());
+ if (rest.size() != size) {
+ std::cerr << "good_resp:" << name << ":content: Expected "
+ << size << " bytes got " << rest.size() << std::endl;
+ return false;
+ }
+ if (memcmp(content, rest.data(), size)) {
+ std::cerr << "good_resp:" << name << ":content: Expected "
+ << content << " got " << rest << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool good_req(std::string const& name,
+ std::string const& in,
+ std::string const& method,
+ std::string const& url,
+ std::string const& proto,
+ uint16_t major, uint16_t minor,
+ char const* content,
+ ...) {
+ std::unique_ptr<HttpRequest> req;
+ size_t test = 0;
+ while (test <= in.size()) {
+ req.reset(HttpRequest::parse(in.substr(0, test)));
+ if (req) break;
+ ++test;
+ }
+ if (!req || !req->good()) {
+ std::cerr << "good_req:" << name << ": Invalid request" << std::endl;
+ return false;
+ }
+ req.reset(HttpRequest::parse(in));
+ if (!req || !req->good()) {
+ std::cerr << "good_req:"
+ << name << ": Incomplete/invalid request" << std::endl;
+ return false;
+ }
+ if (method != req->method() || !req->method_equal(method)) {
+ std::cerr << "good_req:" << name << ":protocol: Expected " << proto
+ << " got " << req->proto() << std::endl;
+ return false;
+ }
+ if (url != req->url()) {
+ std::cerr << "good_req:" << name << ":url: Expected " << url
+ << " got " << req->url() << std::endl;
+ return false;
+ }
+ if (proto != req->proto() || !req->proto_equal(proto)) {
+ std::cerr << "good_req:" << name << ":protocol: Expected '" << proto
+ << "' got '" << req->proto() << "'" << std::endl;
+ return false;
+ }
+ if (major != req->proto_version().major
+ || minor != req->proto_version().minor) {
+ std::cerr << "good_req:" << name << ":protocol_version: Expected "
+ << major << "." << minor << " got "
+ << req->proto_version().major << "."
+ << req->proto_version().minor << std::endl;
+ return false;
+ }
+ va_list headers;
+ va_start(headers, content);
+ while (true) {
+ auto header = va_arg(headers, char const*);
+ if (!header) break;
+ auto value = va_arg(headers, char const*);
+ auto iter = req->header();
+ std::string tmp;
+ if (find_header(iter.get(), header, &tmp)) {
+ if (tmp.compare(value)) {
+ std::cerr << "good_req:" << name << ":header" << header
+ << ": Expected " << value << " got " << tmp
+ << std::endl;
+ return false;
+ }
+ } else {
+ std::cerr << "good_req:" << name << ":header:" << header
+ << ": Expected " << value << " got no value" << std::endl;
+ return false;
+ }
+ iter = req->header(header);
+ if (!iter->valid()) {
+ std::cerr << "good_req:" << name << ":header2:" << header
+ << ": Expected " << value << " got no value" << std::endl;
+ return false;
+ }
+ if (!iter->name_equal(header)) {
+ std::cerr << "good_req:" << name << ":header2:" << header
+ << ": Expected " << header << " got " << iter->name()
+ << std::endl;
+ return false;
+ }
+ if (iter->value().compare(value)) {
+ std::cerr << "good_req:" << name << ":header2:" << header
+ << ": Expected " << value << " got " << iter->value()
+ << std::endl;
+ return false;
+ }
+ iter->next();
+ if (iter->valid()) {
+ std::cerr << "good_req:" << name << ":header2:" << header
+ << ": Expected " << value << " got multiple values"
+ << std::endl;
+ return false;
+ }
+ }
+ va_end(headers);
+ if (req->size() != test) {
+ std::cerr << "good_req:" << name << ":size: Expected " << test
+ << " got " << req->size() << std::endl;
+ return false;
+ }
+ auto size = strlen(content);
+ std::string rest = in.substr(req->size());
+ if (rest.size() != size) {
+ std::cerr << "good_req:" << name << ":content: Expected "
+ << size << " bytes got " << rest.size() << std::endl;
+ return false;
+ }
+ if (memcmp(content, rest.data(), size)) {
+ std::cerr << "good_req:" << name << ":content: Expected "
+ << content << " got " << rest << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool bad_resp(std::string const& name, std::string const& in) {
+ std::unique_ptr<HttpResponse> resp(HttpResponse::parse(in));
+ if (!resp || resp->good()) {
+ std::cerr << "bad_resp:" << name << ": Expected invalid response"
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool short_resp(std::string const& name, std::string const& in) {
+ std::unique_ptr<HttpResponse> resp(HttpResponse::parse(in));
+ if (resp) {
+ std::cerr << "short_resp:" << name << ": Expected incomplete response"
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool req(std::string const& name, std::string const& out,
+ std::string const& method, std::string const& url,
+ std::string const& proto, uint16_t major, uint16_t minor,
+ char const* content, ...) {
+ Version ver;
+ ver.major = major;
+ ver.minor = minor;
+ std::unique_ptr<HttpRequestBuilder> r(
+ HttpRequestBuilder::create(method, url, proto, ver));
+ if (content) r->set_content(content);
+ va_list headers;
+ va_start(headers, content);
+ while (true) {
+ auto header = va_arg(headers, const char*);
+ if (!header) break;
+ auto value = va_arg(headers, const char*);
+ r->add_header(header, value);
+ }
+ va_end(headers);
+ auto tmp = r->build();
+ if (tmp != out) {
+ std::cerr << "req:" << name << ": Expected:\n"
+ << out << "Got:\n" << tmp << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool resp(std::string const& name, std::string const& out,
+ std::string const& proto, uint16_t major, uint16_t minor,
+ uint16_t status_code, std::string const& status_message,
+ char const* content, ...) {
+ Version ver;
+ ver.major = major;
+ ver.minor = minor;
+ std::unique_ptr<HttpResponseBuilder> r(
+ HttpResponseBuilder::create(proto, ver, status_code, status_message));
+ if (content) r->set_content(content);
+ va_list headers;
+ va_start(headers, content);
+ while (true) {
+ auto header = va_arg(headers, const char*);
+ if (!header) break;
+ auto value = va_arg(headers, const char*);
+ r->add_header(header, value);
+ }
+ va_end(headers);
+ auto tmp = r->build();
+ if (tmp != out) {
+ std::cerr << "resp:" << name << ": Expected:\n"
+ << out << "Got:\n" << tmp << std::endl;
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+int main() {
+ BEFORE;
+ RUN(good_resp("index_200_html",
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "ETag: \"1908074049\"\r\n"
+ "Last-Modified: Sat, 21 Apr 2012 09:42:46 GMT\r\n"
+ "Content-Length: 54\r\n"
+ "Date: Sat, 18 Feb 2017 14:30:13 GMT\r\n"
+ "Server: lighttpd/1.4.33\r\n"
+ "\r\n"
+ "<html><head><title></title></head><body></body></html>",
+ "HTTP", 1, 1, 200, "OK",
+ "<html><head><title></title></head><body></body></html>",
+ "content-type", "text/html",
+ "accept-ranges", "bytes",
+ "etag", "\"1908074049\"",
+ "last-modified", "Sat, 21 Apr 2012 09:42:46 GMT",
+ "content-length", "54",
+ "date", "Sat, 18 Feb 2017 14:30:13 GMT",
+ "Server", "lighttpd/1.4.33",
+ nullptr));
+ RUN(good_resp("linux_newline",
+ "HTTP/1.0 200 OK\n"
+ "OS: Unix\n"
+ "\n",
+ "HTTP", 1, 0, 200, "OK",
+ "",
+ "os", "Unix",
+ nullptr));
+ RUN(good_resp("mac_newline",
+ "HTTP/1.0 200 OK\r"
+ "OS: Unix\r"
+ "\r",
+ "HTTP", 1, 0, 200, "OK",
+ "",
+ "os", "Unix",
+ nullptr));
+ RUN(good_resp("empty_status_msg",
+ "HTTP/1.0 200 \r\n"
+ "\r\n",
+ "HTTP", 1, 0, 200, "",
+ "",
+ nullptr));
+ RUN(good_resp("empty_status_msg_without_separator",
+ "HTTP/1.0 200\r\n"
+ "\r\n",
+ "HTTP", 1, 0, 200, "",
+ "",
+ nullptr));
+ RUN(good_resp("continue_header",
+ "HTTP/1.0 200 OK\r\n"
+ "X: foo\r\n"
+ " bar\r\n"
+ "Y: test\r\n"
+ "\r\n",
+ "HTTP", 1, 0, 200, "OK",
+ "",
+ "X", "foo,bar",
+ "Y", "test",
+ nullptr));
+ RUN(good_resp("header_missing_space",
+ "HTTP/1.0 200 OK\r\n"
+ "X:foo\r\n"
+ "\r\n",
+ "HTTP", 1, 0, 200, "OK",
+ "",
+ "X", "foo",
+ nullptr));
+ RUN(good_req("minimal_request",
+ "GET / HTTP/1.0\r\n"
+ "\r\n",
+ "GET", "/", "HTTP", 1, 0,
+ "",
+ nullptr));
+ RUN(short_resp("empty", ""));
+ RUN(bad_resp("missing_version", "HTTP 200 OK\r\n\r\n"));
+ RUN(bad_resp("missing_minor", "HTTP/1 200 OK\r\n\r\n"));
+ RUN(bad_resp("missing_bad_major", "HTTP/X.1 200 OK\r\n\r\n"));
+ RUN(bad_resp("missing_bad_minor", "HTTP/1.Y 200 OK\r\n\r\n"));
+ RUN(bad_resp("missing_bad_status", "HTTP/1.0 000 OK\r\n\r\n"));
+ RUN(short_resp("missing_end", "HTTP/1.0 200 OK\r\n"));
+
+ RUN(req("basic",
+ "GET / HTTP/1.1\r\n"
+ "Host: example.org\r\n"
+ "User-Agent: curl/7.52.1\r\n"
+ "Accept: */*\r\n"
+ "\r\n",
+ "GET", "/", "HTTP", 1, 1, nullptr,
+ "host", "example.org",
+ "user-agent", "curl/7.52.1",
+ "accept", "*/*",
+ nullptr));
+ RUN(req("short",
+ "GET / HTTP/1.0\r\n"
+ "\r\n",
+ "GET", "/", "HTTP", 1, 0, nullptr,
+ nullptr));
+ RUN(req("post",
+ "POST / HTTP/1.1\r\n"
+ "Host: example.org\r\n"
+ "Content-Length: 3\r\n"
+ "\r\n"
+ "foo",
+ "POST", "/", "HTTP", 1, 1, "foo",
+ "host", "example.org",
+ nullptr));
+ RUN(req("post_with_set_content_length",
+ "POST / HTTP/1.1\r\n"
+ "Host: example.org\r\n"
+ "Content-Length: 6\r\n"
+ "\r\n"
+ "foo",
+ "POST", "/", "HTTP", 1, 1, "foo",
+ "host", "example.org",
+ "content-length", "6",
+ nullptr));
+ RUN(req("proxy_get",
+ "GET http://example.org/foo HTTP/1.1\r\n"
+ "Host: example.org\r\n"
+ "\r\n",
+ "GET", "http://example.org/foo", "HTTP", 1, 1, nullptr,
+ "host", "example.org",
+ nullptr));
+ RUN(req("multiline_header",
+ "GET / HTTP/1.1\r\n"
+ "Host: example.org\r\n"
+ "X: foo\r\n"
+ " bar\r\n"
+ "\r\n",
+ "GET", "/", "HTTP", 1, 1, nullptr,
+ "host", "example.org",
+ "x", "foo",
+ "", "bar",
+ nullptr));
+ RUN(resp("minimal_response",
+ "HTTP/1.0 500 Server error\r\n"
+ "Connection: close\r\n"
+ "\r\n",
+ "HTTP", 1, 0, 500, "Server error", nullptr,
+ "connection", "close",
+ nullptr));
+
+ AFTER;
+}
diff --git a/test/test-paths.cc b/test/test-paths.cc
new file mode 100644
index 0000000..c049165
--- /dev/null
+++ b/test/test-paths.cc
@@ -0,0 +1,55 @@
+// -*- mode: c++; c-basic-offset: 2; -*-
+
+#include "common.hh"
+#include "test.hh"
+
+#include "strings.hh"
+
+namespace {
+
+bool test_trim(char const* expected, char const* input) {
+ ASSERT_EQ(expected, Strings::trim(input));
+ return true;
+}
+
+bool test_quote(char const* expected, char const* input) {
+ ASSERT_EQ(expected, Strings::quote(input));
+ return true;
+}
+
+bool test_unquote(char const* expected, char const* input) {
+ ASSERT_EQ(expected, Strings::unquote(input));
+ return true;
+}
+
+} // namespace
+
+int main(void) {
+ BEFORE;
+ RUN(test_trim("", ""));
+ RUN(test_trim("foo", "foo"));
+ RUN(test_trim("foo", " foo"));
+ RUN(test_trim("foo", " foo "));
+ RUN(test_trim("foo", " foo "));
+ RUN(test_trim("", " "));
+ RUN(test_trim("", " "));
+ RUN(test_trim("foo bar", "foo bar"));
+ RUN(test_trim("foo bar", " foo bar "));
+ RUN(test_quote("\"\"", ""));
+ RUN(test_quote("\"'\"", "'"));
+ RUN(test_quote("\"\\\"\"", "\""));
+ RUN(test_quote("\"\\\\\"", "\\"));
+ RUN(test_quote("\"foo\"", "foo"));
+ RUN(test_quote("\"\\\"fo\\\"o\"", "\"fo\"o"));
+ RUN(test_quote("\"f\\\"o\\\"o\"", "f\"o\"o"));
+ RUN(test_quote("\"\\\"foo\\\"\"", "\"foo\""));
+ RUN(test_unquote("", "\"\""));
+ RUN(test_unquote("'", "\"'\""));
+ RUN(test_unquote("\"", "\"\\\"\""));
+ RUN(test_unquote("\\", "\"\\\\\""));
+ RUN(test_unquote("foo", "\"foo\""));
+ RUN(test_unquote("\"fo\"o", "\"\\\"fo\\\"o\""));
+ RUN(test_unquote("f\"o\"o", "\"f\\\"o\\\"o\""));
+ RUN(test_unquote("\"foo\"", "\"\\\"foo\\\"\""));
+ AFTER;
+}
diff --git a/test/test-strings.cc b/test/test-strings.cc
new file mode 100644
index 0000000..c049165
--- /dev/null
+++ b/test/test-strings.cc
@@ -0,0 +1,55 @@
+// -*- mode: c++; c-basic-offset: 2; -*-
+
+#include "common.hh"
+#include "test.hh"
+
+#include "strings.hh"
+
+namespace {
+
+bool test_trim(char const* expected, char const* input) {
+ ASSERT_EQ(expected, Strings::trim(input));
+ return true;
+}
+
+bool test_quote(char const* expected, char const* input) {
+ ASSERT_EQ(expected, Strings::quote(input));
+ return true;
+}
+
+bool test_unquote(char const* expected, char const* input) {
+ ASSERT_EQ(expected, Strings::unquote(input));
+ return true;
+}
+
+} // namespace
+
+int main(void) {
+ BEFORE;
+ RUN(test_trim("", ""));
+ RUN(test_trim("foo", "foo"));
+ RUN(test_trim("foo", " foo"));
+ RUN(test_trim("foo", " foo "));
+ RUN(test_trim("foo", " foo "));
+ RUN(test_trim("", " "));
+ RUN(test_trim("", " "));
+ RUN(test_trim("foo bar", "foo bar"));
+ RUN(test_trim("foo bar", " foo bar "));
+ RUN(test_quote("\"\"", ""));
+ RUN(test_quote("\"'\"", "'"));
+ RUN(test_quote("\"\\\"\"", "\""));
+ RUN(test_quote("\"\\\\\"", "\\"));
+ RUN(test_quote("\"foo\"", "foo"));
+ RUN(test_quote("\"\\\"fo\\\"o\"", "\"fo\"o"));
+ RUN(test_quote("\"f\\\"o\\\"o\"", "f\"o\"o"));
+ RUN(test_quote("\"\\\"foo\\\"\"", "\"foo\""));
+ RUN(test_unquote("", "\"\""));
+ RUN(test_unquote("'", "\"'\""));
+ RUN(test_unquote("\"", "\"\\\"\""));
+ RUN(test_unquote("\\", "\"\\\\\""));
+ RUN(test_unquote("foo", "\"foo\""));
+ RUN(test_unquote("\"fo\"o", "\"\\\"fo\\\"o\""));
+ RUN(test_unquote("f\"o\"o", "\"f\\\"o\\\"o\""));
+ RUN(test_unquote("\"foo\"", "\"\\\"foo\\\"\""));
+ AFTER;
+}
diff --git a/test/test-url.cc b/test/test-url.cc
new file mode 100644
index 0000000..79e6564
--- /dev/null
+++ b/test/test-url.cc
@@ -0,0 +1,212 @@
+// -*- mode: c++; c-basic-offset: 2; -*-
+
+#include "common.hh"
+#include "test.hh"
+
+#include <cstring>
+#include <memory>
+#include <sstream>
+
+#include "url.hh"
+
+namespace {
+
+bool good(std::string const& url,
+ std::string const& scheme,
+ char const* userinfo,
+ std::string const& host,
+ uint16_t port,
+ std::string const& path,
+ char const* query,
+ char const* fragment) {
+ std::unique_ptr<Url> u(Url::parse(url));
+ if (!u) {
+ std::cerr << "good:" << url << ": Invalid url" << std::endl;
+ return false;
+ }
+ if (scheme != u->scheme()) {
+ std::cerr << "good:" << url << ":scheme: Expected " << scheme
+ << " got " << u->scheme() << std::endl;
+ return false;
+ }
+ if (userinfo) {
+ if (u->userinfo()) {
+ if (strcmp(userinfo, u->userinfo())) {
+ std::cerr << "good:" << url << ":authority: Expected " << userinfo
+ << " got " << u->userinfo() << std::endl;
+ return false;
+ }
+ } else {
+ std::cerr << "good:" << url << ":authority: Expected " << userinfo
+ << " got no authority" << std::endl;
+ return false;
+ }
+ } else {
+ if (u->userinfo()) {
+ std::cerr << "good:" << url << ":authority: Expected no authority"
+ << " got " << u->userinfo() << std::endl;
+ return false;
+ }
+ }
+ if (host != u->host()) {
+ std::cerr << "good:" << url << ":host: Expected " << host
+ << " got " << u->host() << std::endl;
+ return false;
+ }
+ if (port != u->port()) {
+ std::cerr << "good:" << url << ":port: Expected " << port
+ << " got " << u->port() << std::endl;
+ return false;
+ }
+ if (path != u->path()) {
+ std::cerr << "good:" << url << ":path: Expected " << path
+ << " got " << u->path() << std::endl;
+ return false;
+ }
+ if (query) {
+ if (u->full_query()) {
+ if (strcmp(query, u->full_query())) {
+ std::cerr << "good:" << url << ":query: Expected " << query
+ << " got " << u->full_query() << std::endl;
+ return false;
+ }
+ } else {
+ std::cerr << "good:" << url << ":query: Expected " << query
+ << " got no query" << std::endl;
+ return false;
+ }
+ } else {
+ if (u->full_query()) {
+ std::cerr << "good:" << url << ":query: Expected no query"
+ << " got " << u->full_query() << std::endl;
+ return false;
+ }
+ }
+ if (fragment) {
+ if (u->fragment()) {
+ if (strcmp(fragment, u->fragment())) {
+ std::cerr << "good:" << url << ":fragment: Expected " << fragment
+ << " got " << u->fragment() << std::endl;
+ return false;
+ }
+ } else {
+ std::cerr << "good:" << url << ":fragment: Expected " << fragment
+ << " got no fragment" << std::endl;
+ return false;
+ }
+ } else {
+ if (u->fragment()) {
+ std::cerr << "good:" << url << ":fragment: Expected no fragment"
+ << " got " << u->fragment() << std::endl;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool query(std::string const& url, std::string const& name,
+ char const* value) {
+ std::unique_ptr<Url> u(Url::parse(url));
+ if (!u) {
+ std::cerr << "query:" << url << ":" << name
+ << ": Invalid url" << std::endl;
+ return false;
+ }
+ std::string tmp;
+ if (u->query(name, &tmp)) {
+ if (!value) {
+ std::cerr << "query:" << url << ":" << name
+ << ": Expected no value got " << tmp << std::endl;
+ return false;
+ }
+ if (tmp.compare(value)) {
+ std::cerr << "query:" << url << ":" << name
+ << ": Expected " << value << " got " << tmp << std::endl;
+ return false;
+ }
+ } else {
+ if (value) {
+ std::cerr << "query:" << url << ":" << name
+ << ": Expected " << value << " got no value" << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool bad(std::string const& url) {
+ std::unique_ptr<Url> u(Url::parse(url));
+ if (u) {
+ std::cerr << "bad:" << url << ": Expected invalid url got ";
+ u->print(std::cerr);
+ std::cerr << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool relative(std::string const& url, std::string const& base,
+ std::string const& full) {
+ std::unique_ptr<Url> b(Url::parse(base));
+ if (!b) {
+ std::cerr << "relative:" << url << ":" << base
+ << ": Invalid base url" << std::endl;
+ return false;
+ }
+ std::unique_ptr<Url> u(Url::parse(url, b.get()));
+ if (!u) {
+ std::cerr << "relative:" << url << ":" << base
+ << ": Invalid url" << std::endl;
+ return false;
+ }
+ std::stringstream ss;
+ u->print(ss);
+ if (full != ss.str()) {
+ std::cerr << "relative:" << url << ":" << base
+ << ": Expected " << full << " got " << ss.str() << std::endl;
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+int main() {
+ BEFORE;
+ RUN(good("http://example.org",
+ "http", nullptr, "example.org", 0, "", nullptr, nullptr));
+ RUN(good("http://example.org/",
+ "http", nullptr, "example.org", 0, "/", nullptr, nullptr));
+ RUN(good("http://example.org/index.htm",
+ "http", nullptr, "example.org", 0, "/index.htm", nullptr, nullptr));
+ RUN(good("http://example.org:80?foo",
+ "http", nullptr, "example.org", 80, "", "foo", nullptr));
+ RUN(good("http://user@example.org#foo",
+ "http", "user", "example.org", 0, "", nullptr, "foo"));
+ RUN(good("http://user:pw@example.org?foo#bar",
+ "http", "user:pw", "example.org", 0, "", "foo", "bar"));
+ RUN(good("http://%40user@example.org/foo%2fbar?a=%25+%20+%23#frag%25",
+ "http", "@user", "example.org", 0, "/foo/bar", "a=% #", "frag%"));
+ RUN(query("http://host?foo=bar", "foo", "bar"));
+ RUN(query("http://host?foo=bar", "bar", nullptr));
+ RUN(query("http://host?foo=bar", "fo", nullptr));
+ RUN(query("http://host?foo=bar", "", nullptr));
+ RUN(query("http://host?foo=bar&foo=k", "foo", "bar"));
+ RUN(query("http://host?foo=bar&bar=foo", "bar", "foo"));
+ RUN(bad(""));
+ RUN(bad("/"));
+ RUN(bad("http"));
+ RUN(bad("http://"));
+ RUN(bad("http:///"));
+ RUN(bad("http://meh@/"));
+ RUN(bad("http://.meh@/"));
+ RUN(bad("http://.meh@/foo#frag?q"));
+ RUN(relative("/foo", "http://host/bar", "http://host/foo"));
+ RUN(relative("foo", "http://host/bar", "http://host/bar/foo"));
+ RUN(relative("foo?a=c", "http://host/?a=b", "http://host/foo?a=c"));
+ RUN(relative("foo#c", "http://host/#b", "http://host/foo#c"));
+ RUN(relative("foo#c", "http://host/?b", "http://host/foo#c"));
+ RUN(relative("foo", "http://host/?b", "http://host/foo"));
+ AFTER;
+}
diff --git a/test/test-xdg.cc b/test/test-xdg.cc
new file mode 100644
index 0000000..54ff597
--- /dev/null
+++ b/test/test-xdg.cc
@@ -0,0 +1,60 @@
+// -*- mode: c++; c-basic-offset: 2; -*-
+
+#include "common.hh"
+#include "test.hh"
+
+#include "paths.hh"
+#include "xdg.hh"
+
+namespace {
+
+bool test_fallback() {
+ unsetenv("XDG_DATA_HOME");
+ unsetenv("XDG_DATA_DIRS");
+ std::string def = Paths::join(XDG::home(), ".local/share");
+ ASSERT_EQ(def, XDG::data_home());
+ auto dirs = XDG::data_dirs();
+ ASSERT_EQ(3UL, dirs.size());
+ ASSERT_EQ(def, dirs[0]);
+ ASSERT_EQ("/usr/local/share", dirs[1]);
+ ASSERT_EQ("/usr/share", dirs[2]);
+ return true;
+}
+
+bool test_basic() {
+ setenv("XDG_DATA_HOME", "/home/user/share", 1);
+ setenv("XDG_DATA_DIRS", "/sw/share:/usr/share/enlightenment:/usr/share", 1);
+ ASSERT_EQ("/home/user/share", XDG::data_home());
+ auto dirs = XDG::data_dirs();
+ ASSERT_EQ(4UL, dirs.size());
+ ASSERT_EQ("/home/user/share", dirs[0]);
+ ASSERT_EQ("/sw/share", dirs[1]);
+ ASSERT_EQ("/usr/share/enlightenment", dirs[2]);
+ ASSERT_EQ("/usr/share", dirs[3]);
+ return true;
+}
+
+bool test_evil() {
+ unsetenv("XDG_DATA_HOME");
+ setenv("XDG_DATA_DIRS", "::/tmp/share:/usr/share:/usr/share:/other:/usr/share:/tmp/share", 1);
+ std::string def = Paths::join(XDG::home(), ".local/share");
+ ASSERT_EQ(def, XDG::data_home());
+ auto dirs = XDG::data_dirs();
+ ASSERT_EQ(4UL, dirs.size());
+ ASSERT_EQ(def, dirs[0]);
+ ASSERT_EQ("/tmp/share", dirs[1]);
+ ASSERT_EQ("/usr/share", dirs[2]);
+ ASSERT_EQ("/other", dirs[3]);
+ return true;
+}
+
+} // namespace
+
+int main(void) {
+ BEFORE;
+ RUN(test_fallback());
+ RUN(test_basic());
+ RUN(test_evil());
+ AFTER;
+}
+
diff --git a/test/test.hh b/test/test.hh
new file mode 100644
index 0000000..8d1f661
--- /dev/null
+++ b/test/test.hh
@@ -0,0 +1,49 @@
+// -*- mode: c++; c-basic-offset: 2; -*-
+
+#ifndef TEST_HH
+#define TEST_HH
+
+#include <cstring>
+#include <iostream>
+
+#define BEFORE \
+ unsigned int tot = 0, ok = 0
+
+#define AFTER \
+ do { \
+ std::cout << "PASS: " << ok << '\n' \
+ << "FAIL: " << (tot - ok) << std::endl; \
+ return ok == tot ? EXIT_SUCCESS : EXIT_FAILURE; \
+ } while (false)
+
+#define RUN(test) \
+ do { \
+ ++tot; \
+ if ((test)) ++ok; \
+ } while (false)
+
+#define ASSERT_EQ(expected, actual) \
+ do { \
+ auto e_ = (expected); \
+ auto a_ = (actual); \
+ if (e_ != a_) { \
+ std::cerr << __FILE__ << ':' << __LINE__ << ": " \
+ << __FUNCTION__ << ": Expected " << e_ \
+ << " got " << a_ << std::endl; \
+ return false; \
+ } \
+ } while (false)
+
+#define ASSERT_STREQ(expected, actual) \
+ do { \
+ auto e_ = (expected); \
+ auto a_ = (actual); \
+ if (!(a_ == e_ || (a_ && e_ && strcmp(a_, e_) == 0))) { \
+ std::cerr << __FILE__ << ':' << __LINE__ << ": " \
+ << __FUNCTION__ << ": Expected " << (e_ ? e_ : "null") \
+ << " got " << (a_ ? a_ : "null") << std::endl; \
+ return false; \
+ } \
+ } while (false)
+
+#endif // TEST_HH