diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/.gitignore | 8 | ||||
| -rw-r--r-- | test/Makefile.am | 25 | ||||
| -rw-r--r-- | test/test-args.cc | 213 | ||||
| -rw-r--r-- | test/test-http.cc | 477 | ||||
| -rw-r--r-- | test/test-paths.cc | 55 | ||||
| -rw-r--r-- | test/test-strings.cc | 55 | ||||
| -rw-r--r-- | test/test-url.cc | 212 | ||||
| -rw-r--r-- | test/test-xdg.cc | 60 | ||||
| -rw-r--r-- | test/test.hh | 49 |
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 |
