"${PROJECT_BINARY_DIR}/simgear/simgear_config.h"
)
+# enable CTest / make test target
+
+include (Dart)
+enable_testing()
+
install (FILES ${PROJECT_BINARY_DIR}/simgear/simgear_config.h DESTINATION include/simgear/)
add_subdirectory(simgear)
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
+
sg_serial.hxx
sg_socket.hxx
sg_socket_udp.hxx
+ HTTPClient.hxx
+ HTTPRequest.hxx
)
set(SOURCES
sg_serial.cxx
sg_socket.cxx
sg_socket_udp.cxx
+ HTTPClient.cxx
+ HTTPRequest.cxx
)
-simgear_component(io io "${SOURCES}" "${HEADERS}")
\ No newline at end of file
+simgear_component(io io "${SOURCES}" "${HEADERS}")
+
+add_executable(test_sock socktest.cxx)
+target_link_libraries(test_sock sgio sgstructure sgdebug)
+
+add_executable(test_http test_HTTP.cxx)
+target_link_libraries(test_http sgio sgstructure sgtiming sgmisc sgdebug)
+
--- /dev/null
+#include "HTTPClient.hxx"
+
+#include <sstream>
+#include <cassert>
+#include <list>
+
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string/case_conv.hpp>
+
+#include <simgear/io/sg_netChat.hxx>
+#include <simgear/misc/strutils.hxx>
+#include <simgear/compiler.h>
+#include <simgear/debug/logstream.hxx>
+#include <simgear/version.h>
+
+using std::string;
+using std::stringstream;
+using std::vector;
+
+#include <iostream>
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+namespace simgear
+{
+
+namespace HTTP
+{
+
+
+class Connection : public NetChat
+{
+public:
+ Connection(Client* pr) :
+ client(pr),
+ state(STATE_IDLE)
+ {
+ setTerminator("\r\n");
+ }
+
+ void connectToHost(const string& host)
+ {
+ open();
+
+ int colonPos = host.find(':');
+ if (colonPos > 0) {
+ string h = host.substr(0, colonPos);
+ int port = strutils::to_int(host.substr(colonPos + 1));
+ connect(h.c_str(), port);
+ } else {
+ connect(host.c_str(), 80 /* default port */);
+ }
+ }
+
+ void queueRequest(const Request_ptr& r)
+ {
+ if (!activeRequest) {
+ startRequest(r);
+ } else {
+ queuedRequests.push_back(r);
+ }
+ }
+
+ void startRequest(const Request_ptr& r)
+ {
+ activeRequest = r;
+ state = STATE_IDLE;
+ bodyTransferSize = 0;
+
+ stringstream headerData;
+ string path = r->path();
+ if (!client->proxyHost().empty()) {
+ path = "http://" + r->host() + path;
+ }
+
+ int requestTime;
+ headerData << r->method() << " " << path << " HTTP/1.1 " << client->userAgent() << "\r\n";
+ headerData << "Host: " << r->host() << "\r\n";
+ headerData << "X-Time: " << requestTime << "\r\n";
+
+ if (!client->proxyAuth().empty()) {
+ headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n";
+ }
+
+ BOOST_FOREACH(string h, r->requestHeaders()) {
+ headerData << h << ": " << r->header(h) << "\r\n";
+ }
+
+ headerData << "\r\n"; // final CRLF to terminate the headers
+
+ // TODO - add request body support for PUT, etc operations
+
+ push(headerData.str().c_str());
+ cout << "sent request" << endl;
+ }
+
+ virtual void collectIncomingData(const char* s, int n)
+ {
+ if (state == STATE_GETTING_BODY) {
+ activeRequest->gotBodyData(s, n);
+ } else {
+ buffer += string(s, n);
+ }
+ }
+
+ virtual void foundTerminator(void)
+ {
+ switch (state) {
+ case STATE_IDLE:
+ activeRequest->responseStart(buffer);
+ state = STATE_GETTING_HEADERS;
+ buffer.clear();
+ break;
+
+ case STATE_GETTING_HEADERS:
+ processHeader();
+ buffer.clear();
+ break;
+
+ case STATE_GETTING_BODY:
+ responseComplete();
+ state = STATE_IDLE;
+ setTerminator("\r\n");
+
+ if (!queuedRequests.empty()) {
+ Request_ptr next = queuedRequests.front();
+ queuedRequests.pop_front();
+ startRequest(next);
+ }
+
+ break;
+ }
+ }
+
+private:
+ void processHeader()
+ {
+ string h = strutils::simplify(buffer);
+ if (h.empty()) { // blank line terminates headers
+ headersComplete();
+
+ if (bodyTransferSize > 0) {
+ state = STATE_GETTING_BODY;
+ cout << "getting body:" << bodyTransferSize << endl;
+ setByteCount(bodyTransferSize);
+ } else {
+ responseComplete();
+ state = STATE_IDLE; // no response body, we're done
+ }
+ return;
+ }
+
+ int colonPos = buffer.find(':');
+ if (colonPos < 0) {
+ SG_LOG(SG_IO, SG_WARN, "malformed HTTP response header:" << h);
+ return;
+ }
+
+ string key = strutils::simplify(buffer.substr(0, colonPos));
+ string lkey = boost::to_lower_copy(key);
+ string value = strutils::strip(buffer.substr(colonPos + 1));
+
+ if (lkey == "content-length" && (bodyTransferSize <= 0)) {
+ bodyTransferSize = strutils::to_int(value);
+ } else if (lkey == "transfer-length") {
+ bodyTransferSize = strutils::to_int(value);
+ }
+
+ activeRequest->responseHeader(lkey, value);
+ }
+
+ void headersComplete()
+ {
+ activeRequest->responseHeadersComplete();
+ }
+
+ void responseComplete()
+ {
+ activeRequest->responseComplete();
+ client->requestFinished(this);
+ activeRequest = NULL;
+ }
+
+ enum ConnectionState {
+ STATE_IDLE = 0,
+ STATE_GETTING_HEADERS,
+ STATE_GETTING_BODY
+ };
+
+ Client* client;
+ Request_ptr activeRequest;
+ ConnectionState state;
+ std::string buffer;
+ int bodyTransferSize;
+
+ std::list<Request_ptr> queuedRequests;
+};
+
+Client::Client()
+{
+ setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
+}
+
+void Client::makeRequest(const Request_ptr& r)
+{
+ string host = r->host();
+ if (!_proxy.empty()) {
+ host = _proxy;
+ }
+
+ if (_connections.find(host) == _connections.end()) {
+ Connection* con = new Connection(this);
+ con->connectToHost(host);
+ _connections[host] = con;
+ }
+
+ _connections[host]->queueRequest(r);
+}
+
+void Client::requestFinished(Connection* con)
+{
+
+}
+
+void Client::setUserAgent(const string& ua)
+{
+ _userAgent = ua;
+}
+
+void Client::setProxy(const string& proxy, const string& auth)
+{
+ _proxy = proxy;
+ _proxyAuth = auth;
+}
+
+} // of namespace HTTP
+
+} // of namespace simgear
--- /dev/null
+#ifndef SG_HTTP_CLIENT_HXX
+#define SG_HTTP_CLIENT_HXX
+
+#include <map>
+
+#include <simgear/io/HTTPRequest.hxx>
+
+namespace simgear
+{
+
+namespace HTTP
+{
+
+class Connection;
+
+class Client
+{
+public:
+ Client();
+
+ void makeRequest(const Request_ptr& r);
+
+ void setUserAgent(const std::string& ua);
+ void setProxy(const std::string& proxy, const std::string& auth = "");
+
+ const std::string& userAgent() const
+ { return _userAgent; }
+
+ const std::string& proxyHost() const
+ { return _proxy; }
+
+ const std::string& proxyAuth() const
+ { return _proxyAuth; }
+private:
+ void requestFinished(Connection* con);
+
+ friend class Connection;
+
+ std::string _userAgent;
+ std::string _proxy;
+ std::string _proxyAuth;
+
+// connections by host
+ std::map<std::string, Connection*> _connections;
+};
+
+} // of namespace HTTP
+
+} // of namespace simgear
+
+#endif // of SG_HTTP_CLIENT_HXX
--- /dev/null
+#include "HTTPRequest.hxx"
+
+#include <simgear/misc/strutils.hxx>
+#include <simgear/compiler.h>
+#include <simgear/debug/logstream.hxx>
+
+using std::string;
+using std::map;
+
+#include <iostream>
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+namespace simgear
+{
+
+namespace HTTP
+{
+
+Request::Request(const string& url, const string method) :
+ _method(method),
+ _url(url)
+{
+
+}
+
+Request::~Request()
+{
+
+}
+
+string_list Request::requestHeaders() const
+{
+ string_list r;
+ return r;
+}
+
+string Request::header(const std::string& name) const
+{
+ return string();
+}
+
+void Request::responseStart(const string& r)
+{
+ const int maxSplit = 2; // HTTP/1.1 nnn status code
+ string_list parts = strutils::split(r, NULL, maxSplit);
+ _responseStatus = strutils::to_int(parts[1]);
+ _responseReason = parts[2];
+}
+
+void Request::responseHeader(const string& key, const string& value)
+{
+ _responseHeaders[key] = value;
+}
+
+void Request::responseHeadersComplete()
+{
+ // no op
+}
+
+void Request::gotBodyData(const char* s, int n)
+{
+
+}
+
+void Request::responseComplete()
+{
+
+}
+
+string Request::scheme() const
+{
+ int firstColon = url().find(":");
+ if (firstColon > 0) {
+ return url().substr(0, firstColon);
+ }
+
+ return ""; // couldn't parse scheme
+}
+
+string Request::path() const
+{
+ string u(url());
+ int schemeEnd = u.find("://");
+ if (schemeEnd < 0) {
+ return ""; // couldn't parse scheme
+ }
+
+ int hostEnd = u.find('/', schemeEnd + 3);
+ if (hostEnd < 0) {
+ return ""; // couldn't parse host
+ }
+
+ int query = u.find('?', hostEnd + 1);
+ if (query < 0) {
+ // all remainder of URL is path
+ return u.substr(hostEnd);
+ }
+
+ return u.substr(hostEnd, query - hostEnd);
+}
+
+string Request::host() const
+{
+ string u(url());
+ int schemeEnd = u.find("://");
+ if (schemeEnd < 0) {
+ return ""; // couldn't parse scheme
+ }
+
+ int hostEnd = u.find('/', schemeEnd + 3);
+ if (hostEnd < 0) { // all remainder of URL is host
+ return u.substr(schemeEnd + 3);
+ }
+
+ return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
+}
+
+int Request::contentLength() const
+{
+ HeaderDict::const_iterator it = _responseHeaders.find("content-length");
+ if (it == _responseHeaders.end()) {
+ return 0;
+ }
+
+ return strutils::to_int(it->second);
+}
+
+} // of namespace HTTP
+
+} // of namespace simgear
--- /dev/null
+#ifndef SG_HTTP_REQUEST_HXX
+#define SG_HTTP_REQUEST_HXX
+
+#include <map>
+
+#include <simgear/structure/SGReferenced.hxx>
+#include <simgear/structure/SGSharedPtr.hxx>
+#include <simgear/math/sg_types.hxx>
+
+namespace simgear
+{
+
+namespace HTTP
+{
+
+class Request : public SGReferenced
+{
+public:
+ virtual ~Request();
+
+ virtual std::string method() const
+ { return _method; }
+ virtual std::string url() const
+ { return _url; }
+
+ virtual std::string scheme() const;
+ virtual std::string path() const;
+ virtual std::string host() const; // host, including port
+
+ virtual string_list requestHeaders() const;
+ virtual std::string header(const std::string& name) const;
+
+ virtual int responseCode() const
+ { return _responseStatus; }
+
+ virtual std::string resposeReason() const
+ { return _responseReason; }
+
+ virtual int contentLength() const;
+protected:
+ friend class Connection;
+
+ Request(const std::string& url, const std::string method = "get");
+
+ virtual void responseStart(const std::string& r);
+ virtual void responseHeader(const std::string& key, const std::string& value);
+ virtual void responseHeadersComplete();
+ virtual void responseComplete();
+
+ virtual void gotBodyData(const char* s, int n);
+private:
+
+ std::string _method;
+ std::string _url;
+ int _responseStatus;
+ std::string _responseReason;
+
+ typedef std::map<std::string, std::string> HeaderDict;
+ HeaderDict _responseHeaders;
+};
+
+typedef SGSharedPtr<Request> Request_ptr;
+
+} // of namespace HTTP
+
+} // of namespace simgear
+
+#endif // of SG_HTTP_REQUEST_HXX
+
#include <simgear/io/sg_netChat.hxx>
#include <cstring> // for strdup
-
+
namespace simgear {
void
NetChat::setTerminator (const char* t)
{
- if (terminator) delete[] terminator;
+ if (terminator) free(terminator);
terminator = strdup(t);
+ bytesToCollect = -1;
}
const char*
return terminator;
}
+
+void
+NetChat::setByteCount(int count)
+{
+ if (terminator) free(terminator);
+ terminator = NULL;
+ bytesToCollect = count;
+}
+
// return the size of the largest prefix of needle at the end
// of haystack
// necessary because we might read several data+terminator combos
// with a single recv().
- while (in_buffer.getLength()) {
-
+ while (in_buffer.getLength()) {
// special case where we're not using a terminator
- if (terminator == 0 || *terminator == 0) {
- collectIncomingData (in_buffer.getData(),in_buffer.getLength());
- in_buffer.remove ();
+ if (terminator == 0 || *terminator == 0) {
+ if ( bytesToCollect > 0) {
+ const int toRead = std::min(in_buffer.getLength(), bytesToCollect);
+ collectIncomingData(in_buffer.getData(), toRead);
+ in_buffer.remove(0, toRead);
+ bytesToCollect -= toRead;
+ if (bytesToCollect == 0) { // read all requested bytes
+ foundTerminator();
+ }
+ } else { // read the whole lot
+ collectIncomingData (in_buffer.getData(),in_buffer.getLength());
+ in_buffer.remove ();
+ }
+
return;
}
#ifndef SG_NET_CHAT_H
#define SG_NET_CHAT_H
+#include <memory>
#include <simgear/io/sg_netBuffer.hxx>
namespace simgear
class NetChat : public NetBufferChannel
{
char* terminator;
-
+ int bytesToCollect;
virtual void handleBufferRead (NetBuffer& buffer) ;
public:
- NetChat () : terminator (0) {}
+ NetChat () :
+ terminator (NULL),
+ bytesToCollect(-1)
+ {}
void setTerminator (const char* t);
const char* getTerminator (void);
+ /**
+ * set byte count to collect - 'foundTerminator' will be called once
+ * this many bytes have been collected
+ */
+ void setByteCount(int bytes);
+
bool push (const char* s);
virtual void collectIncomingData (const char* s, int n) {}
--- /dev/null
+#include <cstdlib>
+
+#include <iostream>
+#include <map>
+#include <sstream>
+
+#include <boost/algorithm/string/case_conv.hpp>
+
+#include "HTTPClient.hxx"
+#include "HTTPRequest.hxx"
+
+#include <simgear/io/sg_netChat.hxx>
+#include <simgear/misc/strutils.hxx>
+#include <simgear/timing/timestamp.hxx>
+
+using std::cout;
+using std::cerr;
+using std::endl;
+using std::string;
+using std::stringstream;
+
+using namespace simgear;
+
+const char* BODY1 = "The quick brown fox jumps over a lazy dog.";
+
+const int body2Size = 8 * 1024;
+char body2[body2Size];
+
+#define COMPARE(a, b) \
+ if ((a) != (b)) { \
+ cerr << "failed:" << #a << " != " << #b << endl; \
+ cerr << "\tgot:" << a << endl; \
+ exit(1); \
+ }
+
+#define VERIFY(a) \
+ if (!(a)) { \
+ cerr << "failed:" << #a << endl; \
+ exit(1); \
+ }
+
+class TestRequest : public HTTP::Request
+{
+public:
+ bool complete;
+ string bodyData;
+
+ TestRequest(const std::string& url) :
+ HTTP::Request(url),
+ complete(false)
+ {
+
+ }
+
+protected:
+ virtual void responseHeadersComplete()
+ {
+ }
+
+ virtual void responseComplete()
+ {
+ complete = true;
+ }
+
+ virtual void gotBodyData(const char* s, int n)
+ {
+ bodyData += string(s, n);
+ }
+};
+
+class TestServerChannel : public NetChat
+{
+public:
+ enum State
+ {
+ STATE_IDLE = 0,
+ STATE_HEADERS,
+ STATE_REQUEST_BODY
+ };
+
+ TestServerChannel()
+ {
+ state = STATE_IDLE;
+ setTerminator("\r\n");
+ }
+
+ virtual void collectIncomingData(const char* s, int n)
+ {
+ buffer += string(s, n);
+ }
+
+ virtual void foundTerminator(void)
+ {
+ if (state == STATE_IDLE) {
+ state = STATE_HEADERS;
+ string_list line = strutils::split(buffer, NULL, 3);
+ if (line.size() < 4) {
+ cerr << "malformed request:" << buffer << endl;
+ exit(-1);
+ }
+
+ method = line[0];
+ path = line[1];
+ httpVersion = line[2];
+ userAgent = line[3];
+ requestHeaders.clear();
+ buffer.clear();
+ } else if (state == STATE_HEADERS) {
+ string s = strutils::simplify(buffer);
+ if (s.empty()) {
+ buffer.clear();
+ receivedRequestHeaders();
+ return;
+ }
+
+ int colonPos = buffer.find(':');
+ if (colonPos < 0) {
+ cerr << "malformed HTTP response header:" << buffer << endl;
+ buffer.clear();
+ return;
+ }
+
+ string key = strutils::simplify(buffer.substr(0, colonPos));
+ string lkey = boost::to_lower_copy(key);
+ string value = strutils::strip(buffer.substr(colonPos + 1));
+ requestHeaders[lkey] = value;
+ buffer.clear();
+ } else if (state == STATE_REQUEST_BODY) {
+
+ }
+ }
+
+ void receivedRequestHeaders()
+ {
+ state = STATE_IDLE;
+ if (path == "/test1") {
+ string contentStr(BODY1);
+ stringstream d;
+ d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
+ d << "Content-Length:" << contentStr.size() << "\r\n";
+ d << "\r\n"; // final CRLF to terminate the headers
+ d << contentStr;
+ push(d.str().c_str());
+ } else if (path == "/test2") {
+ stringstream d;
+ d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
+ d << "Content-Length:" << body2Size << "\r\n";
+ d << "\r\n"; // final CRLF to terminate the headers
+ push(d.str().c_str());
+ bufferSend(body2, body2Size);
+ cout << "sent body2" << endl;
+ } else {
+ sendErrorResponse(404);
+ }
+ }
+
+ void sendErrorResponse(int code)
+ {
+ cerr << "sending error " << code << " for " << path << endl;
+ stringstream headerData;
+ headerData << "HTTP1.1 " << code << " " << reasonForCode(code) << "\r\n";
+ headerData << "\r\n"; // final CRLF to terminate the headers
+ push(headerData.str().c_str());
+ }
+
+ string reasonForCode(int code)
+ {
+ switch (code) {
+ case 200: return "OK";
+ case 404: return "not found";
+ default: return "unknown code";
+ }
+ }
+
+ State state;
+ string buffer;
+ string method;
+ string path;
+ string httpVersion;
+ string userAgent;
+ std::map<string, string> requestHeaders;
+};
+
+class TestServer : public NetChannel
+{
+public:
+ TestServer()
+ {
+ open();
+ bind(NULL, 2000); // localhost, any port
+ listen(5);
+ }
+
+ virtual ~TestServer()
+ {
+ }
+
+ virtual bool writable (void) { return false ; }
+
+ virtual void handleAccept (void)
+ {
+ simgear::IPAddress addr ;
+ int handle = accept ( &addr ) ;
+
+ TestServerChannel* chan = new TestServerChannel();
+ chan->setHandle(handle);
+ }
+};
+
+void waitForComplete(TestRequest* tr)
+{
+ SGTimeStamp start(SGTimeStamp::now());
+ while (start.elapsedMSec() < 1000) {
+ NetChannel::poll(10);
+ if (tr->complete) {
+ return;
+ }
+ }
+
+ cerr << "timed out" << endl;
+}
+
+int main(int argc, char* argv[])
+{
+ TestServer s;
+
+ HTTP::Client cl;
+
+// test URL parsing
+ TestRequest* tr1 = new TestRequest("http://localhost:2000/test1?foo=bar");
+ COMPARE(tr1->scheme(), "http");
+ COMPARE(tr1->host(), "localhost:2000");
+ COMPARE(tr1->path(), "/test1");
+
+// basic get request
+ {
+ TestRequest* tr = new TestRequest("http://localhost:2000/test1");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+
+ waitForComplete(tr);
+ COMPARE(tr->responseCode(), 200);
+ COMPARE(tr->contentLength(), strlen(BODY1));
+ COMPARE(tr->bodyData, string(BODY1));
+ }
+
+// larger get request
+ for (int i=0; i<body2Size; ++i) {
+ body2[i] = (i << 4) | (i >> 2);
+ }
+
+ {
+ TestRequest* tr = new TestRequest("http://localhost:2000/test2");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+ waitForComplete(tr);
+ COMPARE(tr->responseCode(), 200);
+ COMPARE(tr->contentLength(), body2Size);
+ COMPARE(tr->bodyData, string(body2, body2Size));
+ }
+
+// test 404
+ {
+ TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
+ HTTP::Request_ptr own(tr);
+ cl.makeRequest(tr);
+ waitForComplete(tr);
+ COMPARE(tr->responseCode(), 404);
+ COMPARE(tr->contentLength(), 0);
+ }
+
+ cout << "all tests passed ok" << endl;
+ return EXIT_SUCCESS;
+}
)
simgear_component(misc misc "${SOURCES}" "${HEADERS}")
+
+add_executable(test_tabbed_values tabbed_values_test.cxx)
+add_test(tabbed_values ${EXECUTABLE_OUTPUT_PATH}/test_tabbed_values)
+target_link_libraries(test_tabbed_values sgmisc)
+
+add_executable(test_strings strutils_test.cxx )
+add_test(test_strings ${EXECUTABLE_OUTPUT_PATH}/test_strings)
+target_link_libraries(test_strings sgmisc)
return (n != string::npos) && (n == s.length() - substr.length());
}
+ string simplify(const string& s)
+ {
+ string result; // reserve size of 's'?
+ string::const_iterator it = s.begin(),
+ end = s.end();
+
+ // advance to first non-space char - simplifes logic in main loop,
+ // since we can always prepend a single space when we see a
+ // space -> non-space transition
+ for (; (it != end) && isspace(*it); ++it) { /* nothing */ }
+
+ bool lastWasSpace = false;
+ for (; it != end; ++it) {
+ char c = *it;
+ if (isspace(c)) {
+ lastWasSpace = true;
+ continue;
+ }
+
+ if (lastWasSpace) {
+ result.push_back(' ');
+ }
+
+ lastWasSpace = false;
+ result.push_back(c);
+ }
+
+ return result;
+ }
+
+ int to_int(const std::string& s)
+ {
+ return atoi(s.c_str());
+ }
+
} // end namespace strutils
+
} // end namespace simgear
*/
bool ends_with( const std::string & s, const std::string & substr );
+ /**
+ * Strip all leading/trailing whitespace, and transform all interal
+ * whitespace into a single ' ' character - i.e newlines/carriage returns/
+ * tabs/multiple spaces will be condensed.
+ */
+ std::string simplify(const std::string& s);
+
+ int to_int(const std::string& s);
} // end namespace strutils
} // end namespace simgear
--- /dev/null
+////////////////////////////////////////////////////////////////////////
+// Test harness.
+////////////////////////////////////////////////////////////////////////
+
+#include <simgear/compiler.h>
+
+#include <iostream>
+#include "strutils.hxx"
+
+using std::cout;
+using std::cerr;
+using std::endl;
+
+using namespace simgear::strutils;
+
+#define COMPARE(a, b) \
+ if ((a) != (b)) { \
+ cerr << "failed:" << #a << " != " << #b << endl; \
+ exit(1); \
+ }
+
+#define VERIFY(a) \
+ if (!(a)) { \
+ cerr << "failed:" << #a << endl; \
+ exit(1); \
+ }
+
+int main (int ac, char ** av)
+{
+ std::string a("abcd");
+ COMPARE(strip(a), a);
+ COMPARE(strip(" a "), "a");
+ COMPARE(lstrip(" a "), "a ");
+ COMPARE(rstrip("\ta "), "\ta");
+ // check internal spacing is preserved
+ COMPARE(strip("\t \na \t b\r \n "), "a \t b");
+
+
+ VERIFY(starts_with("banana", "ban"));
+ VERIFY(!starts_with("abanana", "ban"));
+ VERIFY(starts_with("banana", "banana")); // pass - string starts with itself
+ VERIFY(!starts_with("ban", "banana")); // fail - original string is prefix of
+
+ VERIFY(ends_with("banana", "ana"));
+ VERIFY(ends_with("foo.text", ".text"));
+ VERIFY(!ends_with("foo.text", ".html"));
+
+ COMPARE(simplify("\ta\t b \nc\n\r \r\n"), "a b c");
+ COMPARE(simplify("The quick - brown dog!"), "The quick - brown dog!");
+ COMPARE(simplify("\r\n \r\n \t \r"), "");
+
+ COMPARE(to_int("999"), 999);
+ COMPARE(to_int("0000000"), 0);
+ COMPARE(to_int("-10000"), -10000);
+
+ cout << "all tests passed successfully!" << endl;
+ return 0;
+}
)
simgear_component(props props "${SOURCES}" "${HEADERS}")
+
+add_executable(test_props props_test.cxx)
+target_link_libraries(test_props sgprops sgxml sgstructure sgmisc sgdebug)
+add_test(test_props ${EXECUTABLE_OUTPUT_PATH}/test_props)
+
+add_executable(test_propertyObject propertyObject_test.cxx)
+target_link_libraries(test_propertyObject sgprops sgstructure sgdebug)
+add_test(test_propertyObject ${EXECUTABLE_OUTPUT_PATH}/test_propertyObject)
#endif
}
+int SGTimeStamp::elapsedMSec() const
+{
+ SGTimeStamp now;
+ now.stamp();
+
+ return static_cast<int>((now - *this).toMSecs());
+}
static SGTimeStamp now()
{ SGTimeStamp ts; ts.stamp(); return ts; }
+ /**
+ * elapsed time since the stamp was taken, in msec
+ */
+ int elapsedMSec() const;
private:
SGTimeStamp(sec_type sec, nsec_type nsec)
{ setTime(sec, nsec); }