From 030f3b483f54dbef6e164194a1771ef5b346312b Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Sun, 8 Jan 2017 22:59:18 +0100 Subject: Support for caching values on filesystem Use USCXML_NOCACHE_FILES=YES to prevent, I will make this a build flag --- src/bindings/swig/python/uscxml.i | 4 +- src/uscxml/CMakeLists.txt | 10 ++ src/uscxml/debug/DebugSession.cpp | 147 ++++++++++++-------- src/uscxml/debug/DebugSession.h | 13 +- src/uscxml/debug/DebuggerServlet.cpp | 2 + src/uscxml/debug/DebuggerServlet.h | 2 + src/uscxml/debug/InterpreterIssue.cpp | 4 +- src/uscxml/interpreter/BasicEventQueue.cpp | 18 +-- src/uscxml/interpreter/FastMicroStep.cpp | 188 ++++++++++++++++++------- src/uscxml/interpreter/FastMicroStep.h | 33 +++-- src/uscxml/interpreter/InterpreterImpl.cpp | 39 +++++- src/uscxml/interpreter/InterpreterImpl.h | 7 + src/uscxml/interpreter/MicroStepImpl.h | 3 + src/uscxml/messages/Data.cpp | 2 - src/uscxml/messages/Data.h | 40 ++++-- src/uscxml/util/Base64.hpp | 4 +- src/uscxml/util/Convenience.cpp | 32 ++--- src/uscxml/util/Convenience.h | 2 +- src/uscxml/util/URL.cpp | 168 +++++++++++++++++++++- src/uscxml/util/URL.h | 16 +++ src/uscxml/util/URL.mm | 62 +++++++++ test/src/test-extensions.cpp | 10 +- test/src/test-http-debugger.pl | 118 +++++++++++----- test/src/test-url.cpp | 216 +++++++++++++---------------- 24 files changed, 805 insertions(+), 335 deletions(-) create mode 100644 src/uscxml/util/URL.mm diff --git a/src/bindings/swig/python/uscxml.i b/src/bindings/swig/python/uscxml.i index c501223..ee920e9 100644 --- a/src/bindings/swig/python/uscxml.i +++ b/src/bindings/swig/python/uscxml.i @@ -150,11 +150,9 @@ using namespace XERCESC_NS; %include "../wrapped/WrappedIOProcessor.h" %include "../wrapped/WrappedInterpreterMonitor.h" - -/*%template(IssueList) std::list; +%template(IssueList) std::list; %template(DataList) std::list; %template(StringList) std::list; -*/ %template(DataMap) std::map; %template(StringSet) std::set; %template(StringVector) std::vector; diff --git a/src/uscxml/CMakeLists.txt b/src/uscxml/CMakeLists.txt index 3497b18..459fe23 100644 --- a/src/uscxml/CMakeLists.txt +++ b/src/uscxml/CMakeLists.txt @@ -32,6 +32,15 @@ file(GLOB_RECURSE USCXML_MISC util/*.c util/*.h ) + +# Add URL.mm for MacOS / iOS +if(APPLE OR IOS) + file(GLOB USCXML_APPLE util/*.mm) + list (APPEND USCXML_MISC ${USCXML_APPLE}) + find_library(FOUNDATION_FRAMEWORK Foundation) + list (APPEND USCXML_CORE_LIBS ${FOUNDATION_FRAMEWORK}) +endif() + source_group("Interpreter" FILES ${USCXML_MISC}) list (APPEND USCXML_FILES ${USCXML_MISC}) @@ -84,4 +93,5 @@ SET(USCXML_DATAMODELS ${USCXML_DATAMODELS} PARENT_SCOPE) # set(USCXML_OPT_LIBS ${USCXML_OPT_LIBS} PARENT_SCOPE) set(USCXML_FILES ${USCXML_FILES} PARENT_SCOPE) set(USCXML_TRANSFORM_FILES ${USCXML_TRANSFORM_FILES} PARENT_SCOPE) +set(USCXML_CORE_LIBS ${USCXML_CORE_LIBS} PARENT_SCOPE) # SET(PLUMA ${PLUMA} PARENT_SCOPE) \ No newline at end of file diff --git a/src/uscxml/debug/DebugSession.cpp b/src/uscxml/debug/DebugSession.cpp index 9fa09eb..f6b3ae1 100644 --- a/src/uscxml/debug/DebugSession.cpp +++ b/src/uscxml/debug/DebugSession.cpp @@ -228,11 +228,13 @@ Data DebugSession::debugStop(const Data& data) { if (_isRunning && _interpreterThread != NULL) { _isRunning = false; + + // unblock + _resumeCond.notify_all(); + _interpreterThread->join(); delete(_interpreterThread); } - // unblock - _resumeCond.notify_all(); _skipTo = Breakpoint(); replyData.compound["status"] = Data("success", Data::VERBATIM); @@ -377,6 +379,7 @@ Data DebugSession::enableAllBreakPoints() { return replyData; } + Data DebugSession::disableAllBreakPoints() { Data replyData; @@ -386,6 +389,38 @@ Data DebugSession::disableAllBreakPoints() { return replyData; } +Data DebugSession::getIssues() { + Data replyData; + + std::list issues = _interpreter.validate(); + replyData.compound["status"] = Data("success", Data::VERBATIM); + for (auto issue : issues) { + Data issueData; + + issueData.compound["message"] = Data(issue.message, Data::VERBATIM); + issueData.compound["xPath"] = Data(issue.xPath, Data::VERBATIM); + issueData.compound["specRef"] = Data(issue.specRef, Data::VERBATIM); + + switch (issue.severity) { + case InterpreterIssue::USCXML_ISSUE_FATAL: + issueData.compound["severity"] = Data("FATAL", Data::VERBATIM); + break; + case InterpreterIssue::USCXML_ISSUE_WARNING: + issueData.compound["severity"] = Data("WARN", Data::VERBATIM); + break; + case InterpreterIssue::USCXML_ISSUE_INFO: + issueData.compound["severity"] = Data("INFO", Data::VERBATIM); + break; + default: + break; + } + + replyData.compound["issues"].array.push_back(issueData); + } + + return replyData; +} + Data DebugSession::debugEval(const Data& data) { Data replyData; @@ -416,69 +451,69 @@ Data DebugSession::debugEval(const Data& data) { } std::shared_ptr DebugSession::create() { - return shared_from_this(); + return shared_from_this(); } void DebugSession::log(LogSeverity severity, const Event& event) { - Data d; - d.compound["data"] = event.data; - d.compound["name"] = Data(event.name); - d.compound["origin"] = Data(event.origin); - d.compound["origintype"] = Data(event.origintype); - - switch (event.eventType) { - case Event::Type::INTERNAL: - d.compound["eventType"] = Data("INTERNAL"); - break; - case Event::Type::EXTERNAL: - d.compound["eventType"] = Data("EXTERNAL"); - break; - case Event::Type::PLATFORM: - d.compound["eventType"] = Data("PLATFORM"); - break; - default: - break; - } - if (!event.hideSendId) - d.compound["sendid"] = Data(event.sendid); - if (event.invokeid.size() > 0) - d.compound["invokeid"] = Data(event.invokeid); - - // handle params - Data& params = d.compound["params"]; - bool convertedToArray = false; - for (auto param : event.params) { - if (params.compound.find(param.first) != d.compound.end()) { - // no such key, add as literal data - d.compound[param.first] = param.second; - } else if (params.compound[param.first].array.size() > 0 && convertedToArray) { - // key is already an array - params.compound[param.first].array.push_back(param.second); - } else { - // key already given as literal data, move to array - Data& existingParam = params.compound[param.first]; - params.compound[param.first].array.push_back(existingParam); - params.compound[param.first].array.push_back(param.second); - params.compound[param.first].compound.clear(); - convertedToArray = true; - } - } - - // handle namelist - Data& namelist = d.compound["namelist"]; - for (auto name : event.namelist) { - namelist.compound[name.first] = name.second; - } - - _debugger->pushData(shared_from_this(), d); + Data d; + d.compound["data"] = event.data; + d.compound["name"] = Data(event.name); + d.compound["origin"] = Data(event.origin); + d.compound["origintype"] = Data(event.origintype); + + switch (event.eventType) { + case Event::Type::INTERNAL: + d.compound["eventType"] = Data("INTERNAL"); + break; + case Event::Type::EXTERNAL: + d.compound["eventType"] = Data("EXTERNAL"); + break; + case Event::Type::PLATFORM: + d.compound["eventType"] = Data("PLATFORM"); + break; + default: + break; + } + if (!event.hideSendId) + d.compound["sendid"] = Data(event.sendid); + if (event.invokeid.size() > 0) + d.compound["invokeid"] = Data(event.invokeid); + + // handle params + Data& params = d.compound["params"]; + bool convertedToArray = false; + for (auto param : event.params) { + if (params.compound.find(param.first) != d.compound.end()) { + // no such key, add as literal data + d.compound[param.first] = param.second; + } else if (params.compound[param.first].array.size() > 0 && convertedToArray) { + // key is already an array + params.compound[param.first].array.push_back(param.second); + } else { + // key already given as literal data, move to array + Data& existingParam = params.compound[param.first]; + params.compound[param.first].array.push_back(existingParam); + params.compound[param.first].array.push_back(param.second); + params.compound[param.first].compound.clear(); + convertedToArray = true; + } + } + + // handle namelist + Data& namelist = d.compound["namelist"]; + for (auto name : event.namelist) { + namelist.compound[name.first] = name.second; + } + + _debugger->pushData(shared_from_this(), d); } void DebugSession::log(LogSeverity severity, const Data& data) { - _debugger->pushData(shared_from_this(), data); + _debugger->pushData(shared_from_this(), data); } void DebugSession::log(LogSeverity severity, const std::string& message) { - _debugger->pushData(shared_from_this(), Data(message)); + _debugger->pushData(shared_from_this(), Data(message)); } } diff --git a/src/uscxml/debug/DebugSession.h b/src/uscxml/debug/DebugSession.h index 9224163..e258568 100644 --- a/src/uscxml/debug/DebugSession.h +++ b/src/uscxml/debug/DebugSession.h @@ -66,6 +66,7 @@ public: Data disableBreakPoint(const Data& data); Data enableAllBreakPoints(); Data disableAllBreakPoints(); + Data getIssues(); Data debugEval(const Data& data); void setDebugger(Debugger* debugger) { @@ -80,12 +81,12 @@ public: _markedForDeletion = mark; } - // Logger - virtual std::shared_ptr create(); - - virtual void log(LogSeverity severity, const Event& event); - virtual void log(LogSeverity severity, const Data& data); - virtual void log(LogSeverity severity, const std::string& message); + // Logger + virtual std::shared_ptr create(); + + virtual void log(LogSeverity severity, const Event& event); + virtual void log(LogSeverity severity, const Data& data); + virtual void log(LogSeverity severity, const std::string& message); protected: void breakExecution(Data replyData); diff --git a/src/uscxml/debug/DebuggerServlet.cpp b/src/uscxml/debug/DebuggerServlet.cpp index 016d67c..44255c0 100644 --- a/src/uscxml/debug/DebuggerServlet.cpp +++ b/src/uscxml/debug/DebuggerServlet.cpp @@ -134,6 +134,8 @@ bool DebuggerServlet::requestFromHTTP(const HTTPServer::Request& request) { } else if (boost::starts_with(request.data.at("path").atom, "/debug/disconnect")) { processDisconnect(request); + } else if (boost::starts_with(request.data.at("path").atom, "/debug/issues")) { + replyData = session->getIssues(); } else if (boost::starts_with(request.data.at("path").atom, "/debug/breakpoint/enable/all")) { replyData = session->enableAllBreakPoints(); } else if (boost::starts_with(request.data.at("path").atom, "/debug/breakpoint/disable/all")) { diff --git a/src/uscxml/debug/DebuggerServlet.h b/src/uscxml/debug/DebuggerServlet.h index 3b117bf..4cd04bb 100644 --- a/src/uscxml/debug/DebuggerServlet.h +++ b/src/uscxml/debug/DebuggerServlet.h @@ -50,6 +50,8 @@ public: void processConnect(const HTTPServer::Request& request); void processListSessions(const HTTPServer::Request& request); + void processIssues(const HTTPServer::Request& request); + // void processDebugPrepare(const HTTPServer::Request& request); // void processDebugAttach(const HTTPServer::Request& request); // void processDebugStart(const HTTPServer::Request& request); diff --git a/src/uscxml/debug/InterpreterIssue.cpp b/src/uscxml/debug/InterpreterIssue.cpp index 90e06b4..4ee4442 100644 --- a/src/uscxml/debug/InterpreterIssue.cpp +++ b/src/uscxml/debug/InterpreterIssue.cpp @@ -385,7 +385,7 @@ std::list InterpreterIssue::forInterpreter(InterpreterImpl* in // check whether state is reachable if (!DOMUtils::isMember(state, reachable) && areFromSameMachine(state, interpreter->_scxml)) { - issues.push_back(InterpreterIssue("State with id '" + stateId + "' is unreachable", state, InterpreterIssue::USCXML_ISSUE_FATAL)); + issues.push_back(InterpreterIssue("State with id '" + stateId + "' is unreachable", state, InterpreterIssue::USCXML_ISSUE_WARNING)); } // check for uniqueness of id attribute @@ -1044,4 +1044,4 @@ std::ostream& operator<< (std::ostream& os, const InterpreterIssue& issue) { } -} \ No newline at end of file +} diff --git a/src/uscxml/interpreter/BasicEventQueue.cpp b/src/uscxml/interpreter/BasicEventQueue.cpp index 2f8bd48..519754e 100644 --- a/src/uscxml/interpreter/BasicEventQueue.cpp +++ b/src/uscxml/interpreter/BasicEventQueue.cpp @@ -35,17 +35,17 @@ Event BasicEventQueue::dequeue(size_t blockMs) { std::lock_guard lock(_mutex); if (blockMs > 0) { - using namespace std::chrono; + using namespace std::chrono; - // TODO: do read http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2661.htm - system_clock::time_point now = system_clock::now(); - system_clock::time_point endTime = now + milliseconds(blockMs); + // TODO: do read http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2661.htm + system_clock::time_point now = system_clock::now(); + system_clock::time_point endTime = now + milliseconds(blockMs); + + // now + milliseconds(blockMs) may not have fitted into a duration type - limit to maximum duration + if (blockMs > system_clock::duration::max().count() - duration_cast(now.time_since_epoch()).count()) { + endTime = system_clock::time_point::max(); + } - // now + milliseconds(blockMs) may not have fitted into a duration type - limit to maximum duration - if (blockMs > system_clock::duration::max().count() - duration_cast(now.time_since_epoch()).count()) { - endTime = system_clock::time_point::max(); - } - // block for given milliseconds or until queue is non-empty while (endTime > std::chrono::system_clock::now() && _queue.empty()) { _cond.wait_until(_mutex, endTime); diff --git a/src/uscxml/interpreter/FastMicroStep.cpp b/src/uscxml/interpreter/FastMicroStep.cpp index ed11fca..e41e9f7 100644 --- a/src/uscxml/interpreter/FastMicroStep.cpp +++ b/src/uscxml/interpreter/FastMicroStep.cpp @@ -22,6 +22,7 @@ #include "FastMicroStep.h" #include "uscxml/util/DOM.h" #include "uscxml/util/String.h" +#include "uscxml/util/Base64.hpp" #include "uscxml/util/Predicates.h" #include "uscxml/util/Convenience.h" #include "uscxml/interpreter/InterpreterMonitor.h" @@ -102,6 +103,8 @@ void FastMicroStep::resortStates(DOMElement* element, const X& xmlPrefix) { everything else */ + // TODO: We can do this in one iteration + DOMElement* child = element->getFirstElementChild(); while(child) { resortStates(child, xmlPrefix); @@ -194,9 +197,11 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { _xmlPrefix = std::string(_xmlPrefix) + ":"; } + bool withCache = !envVarIsTrue("USCXML_NOCACHE_FILES"); + resortStates(_scxml, _xmlPrefix); -// assert(false); -// throw NULL; + + Data& cache = _callbacks->getCache().compound["FastMicroStep"]; /** -- All things states -- */ @@ -243,7 +248,20 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { _states[0]->data = DOMUtils::filterChildElements(_xmlPrefix.str() + "data", dataModels, false); } + auto currState = cache.compound["states"].array.begin(); + auto endState = cache.compound["states"].array.end(); + for (i = 0; i < _states.size(); i++) { + Data* cachedState = NULL; + if (withCache) { + if (currState != endState) { + cachedState = &(*currState); + currState++; + } else { + cache.compound["states"].array.push_back(Data()); + cachedState = &(*cache.compound["states"].array.rbegin()); + } + } // collect states with an id attribute if (HAS_ATTR(_states[i]->element, "id")) { _stateIds[ATTR(_states[i]->element, "id")] = i; @@ -273,7 +291,6 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { } } - // set the states type if (false) { } else if (iequals(TAGNAME(_states[i]->element), _xmlPrefix.str() + "initial")) { @@ -297,17 +314,22 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { } // establish the states' completion - std::list completion = getCompletion(_states[i]->element); - for (j = 0; j < _states.size(); j++) { - if (!completion.empty() && _states[j]->element == completion.front()) { - _states[i]->completion[j] = true; - completion.pop_front(); - } else { - _states[i]->completion[j] = false; + if (withCache && cachedState->compound.find("completion") != cachedState->compound.end()) { + _states[i]->completion = fromBase64(cachedState->compound["completion"]); + } else { + std::list completion = getCompletion(_states[i]->element); + for (j = 0; j < _states.size(); j++) { + if (!completion.empty() && _states[j]->element == completion.front()) { + _states[i]->completion[j] = true; + completion.pop_front(); + } else { + _states[i]->completion[j] = false; + } } + assert(completion.size() == 0); + if (withCache) + cachedState->compound["completion"] = Data(toBase64(_states[i]->completion)); } - assert(completion.size() == 0); - // this is set when establishing the completion if (_states[i]->element->getUserData(X("hasHistoryChild")) == _states[i]) { _states[i]->type |= USCXML_STATE_HAS_HISTORY; @@ -354,46 +376,78 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { } assert(tmp.size() == 0); + auto currTrans = cache.compound["transitions"].array.begin(); + auto endTrans = cache.compound["transitions"].array.end(); + for (i = 0; i < _transitions.size(); i++) { + Data* cachedTrans = NULL; + if (withCache) { + if (currTrans != endTrans) { + cachedTrans = &(*currTrans); + currTrans++; + } else { + cache.compound["transitions"].array.push_back(Data()); + cachedTrans = &(*cache.compound["transitions"].array.rbegin()); + } + } // establish the transitions' exit set assert(_transitions[i]->element != NULL); -// std::cout << "i: " << i << std::endl << std::flush; - std::list exitList = getExitSetCached(_transitions[i]->element, _scxml); - _cache.exitSet[_transitions[i]->element] = exitList; - - for (j = 0; j < _states.size(); j++) { - if (!exitList.empty() && _states[j]->element == exitList.front()) { - _transitions[i]->exitSet[j] = true; - exitList.pop_front(); - } else { - _transitions[i]->exitSet[j] = false; + + if (withCache && cachedTrans->compound.find("exitset") != cachedTrans->compound.end()) { + _transitions[i]->exitSet = fromBase64(cachedTrans->compound["exitset"]); + } else { + std::list exitList = getExitSetCached(_transitions[i]->element, _scxml); + + for (j = 0; j < _states.size(); j++) { + if (!exitList.empty() && _states[j]->element == exitList.front()) { + _transitions[i]->exitSet[j] = true; + exitList.pop_front(); + } else { + _transitions[i]->exitSet[j] = false; + } } + assert(exitList.size() == 0); + + if (withCache) + cachedTrans->compound["exitset"] = Data(toBase64(_transitions[i]->exitSet)); } - assert(exitList.size() == 0); // establish the transitions' conflict set - for (j = i; j < _transitions.size(); j++) { - if (conflictsCached(_transitions[i]->element, _transitions[j]->element, _scxml)) { - _transitions[i]->conflicts[j] = true; - } else { - _transitions[i]->conflicts[j] = false; + if (withCache && cachedTrans->compound.find("conflicts") != cachedTrans->compound.end()) { + _transitions[i]->conflicts = fromBase64(cachedTrans->compound["conflicts"]); + } else { + for (j = i; j < _transitions.size(); j++) { + if (conflictsCached(_transitions[i]->element, _transitions[j]->element, _scxml)) { + _transitions[i]->conflicts[j] = true; + } else { + _transitions[i]->conflicts[j] = false; + } + // std::cout << "."; } -// std::cout << "."; - } - // conflicts matrix is symmetric - for (j = 0; j < i; j++) { - _transitions[i]->conflicts[j] = _transitions[j]->conflicts[i]; - } + // conflicts matrix is symmetric + for (j = 0; j < i; j++) { + _transitions[i]->conflicts[j] = _transitions[j]->conflicts[i]; + } + if (withCache) + cachedTrans->compound["conflicts"] = Data(toBase64(_transitions[i]->conflicts)); + } // establish the transitions' target set - std::list targets = tokenize(ATTR(_transitions[i]->element, "target")); - for (auto tIter = targets.begin(); tIter != targets.end(); tIter++) { - if (_stateIds.find(*tIter) != _stateIds.end()) { - _transitions[i]->target[_stateIds[*tIter]] = true; + if (withCache && cachedTrans->compound.find("target") != cachedTrans->compound.end()) { + _transitions[i]->target = fromBase64(cachedTrans->compound["target"]); + } else { + std::list targets = tokenize(ATTR(_transitions[i]->element, "target")); + for (auto tIter = targets.begin(); tIter != targets.end(); tIter++) { + if (_stateIds.find(*tIter) != _stateIds.end()) { + _transitions[i]->target[_stateIds[*tIter]] = true; + } } + if (withCache) + cachedTrans->compound["target"] = Data(toBase64(_transitions[i]->target)); + } // the transition's source @@ -422,7 +476,6 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { _transitions[i]->type |= USCXML_TRANS_INITIAL; } - // the transitions event and condition _transitions[i]->event = (HAS_ATTR(_transitions[i]->element, "event") ? ATTR(_transitions[i]->element, "event") : ""); @@ -433,10 +486,39 @@ void FastMicroStep::init(XERCESC_NS::DOMElement* scxml) { if (_transitions[i]->element->getChildElementCount() > 0) { _transitions[i]->onTrans = _transitions[i]->element; } - } _cache.exitSet.clear(); _isInitialized = true; + +} + +std::string FastMicroStep::toBase64(const boost::dynamic_bitset& bitset) { + std::vector::block_type> bytes(bitset.num_blocks() + 1); + boost::to_block_range(bitset, bytes.begin()); + + std::string encoded; + encoded.resize(sizeof(boost::dynamic_bitset::block_type) * bytes.size()); + bytes[bytes.size() - 1] = static_cast::block_type>(bitset.size()); + + static_assert(sizeof(boost::dynamic_bitset::block_type) >= sizeof(size_t), "Block type too small to encode dynamic_bitset length"); + + memcpy(&encoded[0], &bytes[0], sizeof(boost::dynamic_bitset::block_type) * bytes.size()); + return base64Encode(encoded.c_str(), encoded.size(), true); +} + +boost::dynamic_bitset FastMicroStep::fromBase64(const std::string& encoded) { + + std::string decoded = base64Decode(encoded); + assert(decoded.size() % sizeof(boost::dynamic_bitset::block_type) == 0); + std::vector::block_type> bytes(decoded.size() / sizeof(boost::dynamic_bitset::block_type)); + + memcpy(&bytes[0], &decoded[0], decoded.size()); + + boost::dynamic_bitset bitset; + bitset.init_from_block_range(bytes.begin(), --bytes.end()); + bitset.resize(bytes[bytes.size() - 1]); + + return bitset; } void FastMicroStep::markAsCancelled() { @@ -451,13 +533,13 @@ InterpreterState FastMicroStep::step(size_t blockMs) { size_t i, j, k; - boost::dynamic_bitset<> exitSet(_states.size(), false); - boost::dynamic_bitset<> entrySet(_states.size(), false); - boost::dynamic_bitset<> targetSet(_states.size(), false); - boost::dynamic_bitset<> tmpStates(_states.size(), false); + boost::dynamic_bitset exitSet(_states.size(), false); + boost::dynamic_bitset entrySet(_states.size(), false); + boost::dynamic_bitset targetSet(_states.size(), false); + boost::dynamic_bitset tmpStates(_states.size(), false); - boost::dynamic_bitset<> conflicts(_transitions.size(), false); - boost::dynamic_bitset<> transSet(_transitions.size(), false); + boost::dynamic_bitset conflicts(_transitions.size(), false); + boost::dynamic_bitset transSet(_transitions.size(), false); #ifdef USCXML_VERBOSE std::cerr << "Config: "; @@ -675,14 +757,14 @@ ESTABLISH_ENTRYSET: /* iterate for ancestors */ i = entrySet.find_first(); - while(i != boost::dynamic_bitset<>::npos) { + while(i != boost::dynamic_bitset::npos) { entrySet |= USCXML_GET_STATE(i).ancestors; i = entrySet.find_next(i); } /* iterate for descendants */ i = entrySet.find_first(); - while(i != boost::dynamic_bitset<>::npos) { + while(i != boost::dynamic_bitset::npos) { switch (USCXML_STATE_MASK(USCXML_GET_STATE(i).type)) { @@ -813,7 +895,7 @@ ESTABLISH_ENTRYSET: /* TAKE_TRANSITIONS: */ i = transSet.find_first(); - while(i != boost::dynamic_bitset<>::npos) { + while(i != boost::dynamic_bitset::npos) { if ((USCXML_GET_TRANS(i).type & (USCXML_TRANS_HISTORY | USCXML_TRANS_INITIAL)) == 0) { USCXML_MONITOR_CALLBACK1(_callbacks->getMonitors(), beforeTakingTransition, USCXML_GET_TRANS(i).element); @@ -841,7 +923,7 @@ ESTABLISH_ENTRYSET: /* ENTER_STATES: */ i = entrySet.find_first(); - while(i != boost::dynamic_bitset<>::npos) { + while(i != boost::dynamic_bitset::npos) { if (BIT_HAS(i, _configuration)) { // already active @@ -924,7 +1006,7 @@ ESTABLISH_ENTRYSET: BIT_HAS(j, USCXML_GET_STATE(i).ancestors)) { tmpStates.reset(); k = _configuration.find_first(); - while (k != boost::dynamic_bitset<>::npos) { + while (k != boost::dynamic_bitset::npos) { if (BIT_HAS(j, USCXML_GET_STATE(k).ancestors)) { if (USCXML_STATE_MASK(USCXML_GET_STATE(k).type) == USCXML_STATE_FINAL) { tmpStates ^= USCXML_GET_STATE(k).ancestors; @@ -981,7 +1063,7 @@ bool FastMicroStep::isInState(const std::string& stateId) { std::list FastMicroStep::getConfiguration() { std::list config; size_t i = _configuration.find_first(); - while(i != boost::dynamic_bitset<>::npos) { + while(i != boost::dynamic_bitset::npos) { config.push_back(_states[i]->element); i = _configuration.find_next(i); } @@ -1038,7 +1120,7 @@ std::list FastMicroStep::getHistoryCompletion(const DOMElement* his /** * Print name of states contained in a (debugging). */ -void FastMicroStep::printStateNames(const boost::dynamic_bitset<>& a) { +void FastMicroStep::printStateNames(const boost::dynamic_bitset& a) { size_t i; const char* seperator = ""; for (i = 0; i < a.size(); i++) { diff --git a/src/uscxml/interpreter/FastMicroStep.h b/src/uscxml/interpreter/FastMicroStep.h index 810b793..db3e724 100644 --- a/src/uscxml/interpreter/FastMicroStep.h +++ b/src/uscxml/interpreter/FastMicroStep.h @@ -33,6 +33,12 @@ #include +#ifdef _WIN32 +#define BITSET_BLOCKTYPE size_t +#else +#define BITSET_BLOCKTYPE +#endif + namespace uscxml { /** @@ -57,11 +63,11 @@ protected: Transition() : element(NULL), source(0), onTrans(NULL), type(0) {} XERCESC_NS::DOMElement* element; - boost::dynamic_bitset<> conflicts; - boost::dynamic_bitset<> exitSet; + boost::dynamic_bitset conflicts; + boost::dynamic_bitset exitSet; uint32_t source; - boost::dynamic_bitset<> target; + boost::dynamic_bitset target; XERCESC_NS::DOMElement* onTrans; @@ -77,9 +83,9 @@ protected: State() : element(NULL), parent(0), documentOrder(0), doneData(NULL), type(0) {} XERCESC_NS::DOMElement* element; - boost::dynamic_bitset<> completion; - boost::dynamic_bitset<> children; - boost::dynamic_bitset<> ancestors; + boost::dynamic_bitset completion; + boost::dynamic_bitset children; + boost::dynamic_bitset ancestors; uint32_t parent; uint32_t documentOrder; @@ -108,12 +114,12 @@ protected: std::vector _transitions; std::list _globalScripts; - boost::dynamic_bitset<> _configuration; - boost::dynamic_bitset<> _invocations; - boost::dynamic_bitset<> _history; - boost::dynamic_bitset<> _initializedData; + boost::dynamic_bitset _configuration; + boost::dynamic_bitset _invocations; + boost::dynamic_bitset _history; + boost::dynamic_bitset _initializedData; - std::set > _microstepConfigurations; + std::set > _microstepConfigurations; Binding _binding; XERCESC_NS::DOMElement* _scxml; @@ -130,13 +136,16 @@ private: bool conflictsCached(const XERCESC_NS::DOMElement* t1, const XERCESC_NS::DOMElement* t2, const XERCESC_NS::DOMElement* root); ///< overrides implementation Predicates::conflicts for speed + std::string toBase64(const boost::dynamic_bitset& bitset); + boost::dynamic_bitset fromBase64(const std::string& encoded); + std::list getExitSetCached(const XERCESC_NS::DOMElement* transition, const XERCESC_NS::DOMElement* root); CachedPredicates _cache; #ifdef USCXML_VERBOSE - void printStateNames(const boost::dynamic_bitset<>& bitset); + void printStateNames(const boost::dynamic_bitset& bitset); #endif }; diff --git a/src/uscxml/interpreter/InterpreterImpl.cpp b/src/uscxml/interpreter/InterpreterImpl.cpp index 43059bf..8762b95 100644 --- a/src/uscxml/interpreter/InterpreterImpl.cpp +++ b/src/uscxml/interpreter/InterpreterImpl.cpp @@ -27,11 +27,13 @@ #include "uscxml/messages/Event.h" #include "uscxml/util/String.h" #include "uscxml/util/Predicates.h" +#include "uscxml/util/MD5.hpp" #include "uscxml/plugins/InvokerImpl.h" #include "uscxml/interpreter/Logging.h" #include +#include #include #include @@ -108,6 +110,15 @@ InterpreterImpl::~InterpreterImpl() { // assert(_invokers.size() == 0); // ::xercesc_3_1::XMLPlatformUtils::Terminate(); + if (!envVarIsTrue("USCXML_NOCACHE_FILES")) { + // save our cache + std::string sharedTemp = URL::getTempDir(true); + std::ofstream dataFS(sharedTemp + PATH_SEPERATOR + md5(_baseURL) + ".uscxml.cache"); + if (dataFS) { + dataFS << _cache; + dataFS.close(); + } + } } void InterpreterImpl::cancel() { @@ -159,7 +170,6 @@ SCXML_STOP_SEARCH: _binding = (HAS_ATTR(_scxml, "binding") && iequals(ATTR(_scxml, "binding"), "late") ? LATE : EARLY); } - } void InterpreterImpl::init() { @@ -169,6 +179,33 @@ void InterpreterImpl::init() { setupDOM(); + if (!envVarIsTrue("USCXML_NOCACHE_FILES")) { + // try to open chached data from resource directory + std::string sharedTemp = URL::getTempDir(true); + std::ifstream dataFS(sharedTemp + PATH_SEPERATOR + md5(_baseURL) + ".uscxml.cache"); + if (dataFS.is_open()) { + std::string cacheStr((std::istreambuf_iterator(dataFS)), + std::istreambuf_iterator()); + _cache = Data::fromJSON(cacheStr); + } + + // get md5 of current document + std::stringstream ss; + ss << *_document; + _md5 = md5(ss.str()); + + if (_cache.compound.find("InterpreterImpl") != _cache.compound.end() && + _cache.compound["InterpreterImpl"].compound.find("md5") != _cache.compound["InterpreterImpl"].compound.end() && + _cache.compound["InterpreterImpl"].compound["md5"].atom != _md5) { + + // that's not our cache! + _cache.clear(); + } + + _cache.compound["InterpreterImpl"].compound["baseURL"] = Data(std::string(_baseURL)); + _cache.compound["InterpreterImpl"].compound["md5"] = Data(_md5); + } + // register io processors std::map ioProcs = _factory->getIOProcessors(); for (auto ioProcIter = ioProcs.begin(); ioProcIter != ioProcs.end(); ioProcIter++) { diff --git a/src/uscxml/interpreter/InterpreterImpl.h b/src/uscxml/interpreter/InterpreterImpl.h index 1243f7c..0efc70a 100644 --- a/src/uscxml/interpreter/InterpreterImpl.h +++ b/src/uscxml/interpreter/InterpreterImpl.h @@ -143,6 +143,10 @@ public: return Interpreter(shared_from_this()); } + virtual Data& getCache() { + return _cache; + } + /** DataModelCallbacks */ @@ -284,6 +288,7 @@ protected: Factory* _factory; URL _baseURL; + std::string _md5; MicroStep _microStepper; DataModel _dataModel; @@ -305,6 +310,8 @@ protected: std::set _autoForwarders; std::set _monitors; + Data _cache; + private: void setupDOM(); }; diff --git a/src/uscxml/interpreter/MicroStepImpl.h b/src/uscxml/interpreter/MicroStepImpl.h index 1544e78..e3f8299 100644 --- a/src/uscxml/interpreter/MicroStepImpl.h +++ b/src/uscxml/interpreter/MicroStepImpl.h @@ -60,6 +60,9 @@ public: virtual std::set getMonitors() = 0; virtual Interpreter getInterpreter() = 0; virtual Logger getLogger() = 0; + + /** Saved State */ + virtual Data& getCache() = 0; }; /** diff --git a/src/uscxml/messages/Data.cpp b/src/uscxml/messages/Data.cpp index b2d08e4..de9644b 100644 --- a/src/uscxml/messages/Data.cpp +++ b/src/uscxml/messages/Data.cpp @@ -283,8 +283,6 @@ std::string Data::toJSON(const Data& data) { } else { if (data.type == Data::VERBATIM) { os << "\"\""; // empty string - } else { - os << "null"; } } return os.str(); diff --git a/src/uscxml/messages/Data.h b/src/uscxml/messages/Data.h index 2cf1945..17d37cb 100644 --- a/src/uscxml/messages/Data.h +++ b/src/uscxml/messages/Data.h @@ -73,6 +73,16 @@ public: ~Data() {} + void clear() { + type = VERBATIM; + compound.clear(); + array.clear(); + atom.clear(); + adoptedDoc.reset(); + binary = Blob(); + node = NULL; + } + bool empty() const { bool hasContent = (atom.length() > 0 || !compound.empty() || !array.empty() || binary || node); return !hasContent; @@ -101,18 +111,20 @@ public: return (!compound.empty() && compound.find(key) != compound.end()); } - Data& operator[](const std::string& key) { - return operator[](key.c_str()); - } +#ifndef SWIGIMPORTED - const Data& operator[](const std::string& key) const { - return operator[](key.c_str()); + Data& operator[](const std::string& key) { + return compound[key]; } Data& operator[](const char* key) { return compound[key]; } + const Data& operator[](const std::string& key) const { + return compound.at(key); + } + const Data& operator[](const char* key) const { return compound.at(key); } @@ -125,6 +137,7 @@ public: for (size_t i = 0; i < index; i++, arrayIter++) {} return *arrayIter; } +#endif const Data at(const std::string& key) const { return at(key.c_str()); @@ -179,14 +192,6 @@ public: static std::string toJSON(const Data& data); std::string asJSON() const; - - std::map getCompound() { - return compound; - } - void setCompound(const std::map& compound) { - this->compound = compound; - } - std::list getArray() { return array; } @@ -215,6 +220,15 @@ public: this->type = type; } + // Bug in SWIG 3.0.8 Python: Data in a map has to be fully qualified! + std::map getCompound() { + return compound; + } + + void setCompound(const std::map& compound) { + this->compound = compound; + } + #ifdef SWIGIMPORTED protected: #endif diff --git a/src/uscxml/util/Base64.hpp b/src/uscxml/util/Base64.hpp index e3cbe03..d9f9ac1 100644 --- a/src/uscxml/util/Base64.hpp +++ b/src/uscxml/util/Base64.hpp @@ -40,9 +40,9 @@ USCXML_API inline std::string base64Decode(const std::string& data) { base64_init_decodestate(ctx); char* out = (char*)malloc(data.size()); - base64_decode_block(data.data(), data.size(), out, ctx); + size_t size = base64_decode_block(data.data(), data.size(), out, ctx); free(ctx); - std::string result(out); + std::string result(out, size); free(out); return result; } diff --git a/src/uscxml/util/Convenience.cpp b/src/uscxml/util/Convenience.cpp index a0263d2..4f47449 100644 --- a/src/uscxml/util/Convenience.cpp +++ b/src/uscxml/util/Convenience.cpp @@ -24,22 +24,22 @@ namespace uscxml { NumAttr::NumAttr(const std::string& str) { - size_t valueStart = str.find_first_of("0123456789."); - if (valueStart != std::string::npos) { - size_t valueEnd = str.find_last_of("0123456789."); - if (valueEnd != std::string::npos) { - value = str.substr(valueStart, (valueEnd - valueStart) + 1); - size_t unitStart = str.find_first_not_of(" \t", valueEnd + 1); - if (unitStart != std::string::npos) { - size_t unitEnd = str.find_last_of(" \t"); - if (unitEnd != std::string::npos && unitEnd > unitStart) { - unit = str.substr(unitStart, unitEnd - unitStart); - } else { - unit = str.substr(unitStart, str.length() - unitStart); - } - } - } - } + size_t valueStart = str.find_first_of("0123456789."); + if (valueStart != std::string::npos) { + size_t valueEnd = str.find_last_of("0123456789."); + if (valueEnd != std::string::npos) { + value = str.substr(valueStart, (valueEnd - valueStart) + 1); + size_t unitStart = str.find_first_not_of(" \t", valueEnd + 1); + if (unitStart != std::string::npos) { + size_t unitEnd = str.find_last_of(" \t"); + if (unitEnd != std::string::npos && unitEnd > unitStart) { + unit = str.substr(unitStart, unitEnd - unitStart); + } else { + unit = str.substr(unitStart, str.length() - unitStart); + } + } + } + } } diff --git a/src/uscxml/util/Convenience.h b/src/uscxml/util/Convenience.h index cb4416d..c12e0e4 100644 --- a/src/uscxml/util/Convenience.h +++ b/src/uscxml/util/Convenience.h @@ -45,7 +45,7 @@ template T strTo(std::string tmp) { class USCXML_API NumAttr { public: - NumAttr(const std::string& str); + NumAttr(const std::string& str); std::string value; std::string unit; }; diff --git a/src/uscxml/util/URL.cpp b/src/uscxml/util/URL.cpp index 0885b47..845a972 100644 --- a/src/uscxml/util/URL.cpp +++ b/src/uscxml/util/URL.cpp @@ -29,14 +29,17 @@ #include #include +#include +#include #ifdef _WIN32 #include +#include #define getcwd _getcwd #else #include // getcwd -//#include +#include // getpwuid #endif #define DOWNLOAD_IF_NECESSARY if (!_isDownloaded) { download(true); } @@ -44,6 +47,157 @@ namespace uscxml { +std::string URL::currTmpDir; + +std::string URL::getTempDir(bool shared) { + +#ifdef _WIN32 + struct stat st = { 0 }; + TCHAR tmpPrefix [MAX_PATH]; + + // retrieve and optionally create temporary directory + if (!GetTempPath(MAX_PATH, tmpPrefix)) { + ERROR_EXECUTION_THROW(std::string("Cannot retrieve temporary directory")); + } + if (stat(tmpPrefix, &st) == -1) { + CreateDirectory(tmpPrefix, NULL); + if (GetLastError() == ERROR_PATH_NOT_FOUND) { + ERROR_EXECUTION_THROW(std::string("Cannot create a temporary directory at '") + tmpPrefix + "'"); + } + } + + if (shared) { + // create uscxml folder in temporary directory + std::string sharedTmpDir = std::string(tmpPrefix) + PATH_SEPERATOR + "uscxml"; + + if (stat(sharedTmpDir.c_str(), &st) == -1) { + CreateDirectory (sharedTmpDir.c_str(), NULL); + if (GetLastError() == ERROR_PATH_NOT_FOUND) { + ERROR_EXECUTION_THROW(std::string("Cannot create a temporary directory at '") + sharedTmpDir + "'"); + } + } + return sharedTmpDir; + + } else { + if (currTmpDir.size() == 0) { + // create random folder in temporary directory + // http://stackoverflow.com/questions/6287475/creating-a-unique-temporary-directory-from-pure-c-in-windows + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363875(v=vs.85).aspx + TCHAR tempFileName[MAX_PATH]; + if (GetTempFileName(tmpPrefix, // directory for tmp files + TEXT("uscxml."), // temp file name prefix + 0, // create unique name + tempFileName)) { + DeleteFile(tempFileName); + + if (stat(tempFileName, &st) == -1) { + CreateDirectory(tempFileName, NULL); + if (GetLastError() == ERROR_PATH_NOT_FOUND) { + ERROR_EXECUTION_THROW(std::string("Cannot create a temporary directory at '") + tempFileName + "'"); + } + } + + currTmpDir = tempFileName; + } else { + ERROR_EXECUTION_THROW(std::string("Cannot create a temporary directory at '") + tmpPrefix + "'"); + } + } + return currTmpDir; + } + +#else + std::string tmpPrefix = "/tmp"; + const char* tmpEnv = getenv("TMPDIR"); + if (tmpEnv != NULL) + tmpPrefix = tmpEnv; + + if (tmpPrefix[tmpPrefix.size() - 1] != PATH_SEPERATOR) + tmpPrefix += PATH_SEPERATOR; + + if (shared) { + struct stat st = {0}; + + std::string sharedTmpDir = tmpPrefix + "uscxml"; + if (stat(sharedTmpDir.c_str(), &st) == -1) { + int err = mkdir(sharedTmpDir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO); // 777 + if (err) + ERROR_EXECUTION_THROW(std::string("Cannot create a temporary directory at '") + sharedTmpDir + "':" + strerror(errno)); + + } + return sharedTmpDir; + + } else { + if (currTmpDir.size() == 0) { + std::string tmpPrefix = "/tmp"; + const char* tmpEnv = getenv("TMPDIR"); + if (tmpEnv != NULL) + tmpPrefix = tmpEnv; + const char* tmpName = mkdtemp((char*)std::string(tmpPrefix + "uscxml.XXXXXX").c_str()); // can we merely drop the constness? + if (tmpName != NULL) { + currTmpDir = tmpName; + } else { + ERROR_EXECUTION_THROW(std::string("Cannot create a temporary directory:") + strerror(errno)); + } + } + return currTmpDir; + } +#endif +} + +// Version for MacOSX in URL.mm +#if (!defined APPLE && !defined IOS) +std::string URL::getResourceDir() { +#ifdef _WIN32 + TCHAR szPath[MAX_PATH]; + std::string resourceDir; + if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, szPath) == S_OK) { + resourceDir = std::string(szPath) + PATH_SEPERATOR + "uscxml"; + } else { + char* envData = getenv("APPDATA"); + if (envData) { + resourceDir = std::string(envData) + PATH_SEPERATOR + "uscxml"; + } else { + ERROR_EXECUTION_THROW("Could not get resource directory"); + } + } + + struct stat st = { 0 }; + + if (stat(resourceDir.c_str(), &st) == -1) { + CreateDirectory(resourceDir.c_str(), NULL); + if (GetLastError() == ERROR_PATH_NOT_FOUND) { + ERROR_EXECUTION_THROW(std::string("Cannot create a resource directory at '") + resourceDir + "'"); + } + } + return resourceDir; + +#else + struct passwd* pw = getpwuid(getuid()); + std::string homedir(pw->pw_dir); + struct stat dirStat; + int err = 0; + + err = stat(std::string(homedir + PATH_SEPERATOR + ".config").c_str(), &dirStat); + if (err == ENOENT) { + err = mkdir(std::string(homedir + PATH_SEPERATOR + ".config").c_str(), S_IWUSR | S_IRUSR | S_IROTH); + } + + err = stat(std::string(homedir + PATH_SEPERATOR + ".config" + PATH_SEPERATOR + "uscxml").c_str(), &dirStat); + if (err != 0) { + // std::cout << std::string(homedir + PATH_SEPERATOR + ".config" + PATH_SEPERATOR + "uscxml") << std::endl; + err = mkdir(std::string(homedir + PATH_SEPERATOR + ".config" + PATH_SEPERATOR + "uscxml").c_str(), + S_IWUSR | S_IRUSR | S_IROTH | S_IRGRP | S_IXUSR | S_IXOTH | S_IXGRP); + } + + err = stat(std::string(homedir + PATH_SEPERATOR + ".config" + PATH_SEPERATOR + "uscxml").c_str(), &dirStat); + if (err == 0) { + return homedir + PATH_SEPERATOR + ".config" + PATH_SEPERATOR + "uscxml"; + } + return ""; +#endif +} +#endif + void URLImpl::prepareException(ErrorEvent& exception, int errorCode, const std::string& origUri, void* parser) { exception.data.compound["uri"].atom = origUri; @@ -808,9 +962,19 @@ void URLFetcher::perform() { rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout); switch(rc) { - case -1: + case -1: { /* select error */ +#ifdef _WIN32 + char *s = NULL; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, WSAGetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&s, 0, NULL); + LOGD(USCXML_WARN) << "select: " << s; + LocalFree(s); +#endif break; + } case 0: /* timeout */ default: { /* action */ std::lock_guard lock(_mutex); diff --git a/src/uscxml/util/URL.h b/src/uscxml/util/URL.h index c83e3b1..9ad2f8e 100644 --- a/src/uscxml/util/URL.h +++ b/src/uscxml/util/URL.h @@ -136,6 +136,19 @@ public: URL(const std::string url) : _impl(new URLImpl(url)) {} + /** + * Get a persistant, shared directory for resources + * @return A path to an existing directory for resources. + */ + static std::string getResourceDir(); + + /** + * Get a temporary, shared or private directory for resources + * @param shared Whether the temporary directory is shared among instances. + * @return A path to an existing directory for temporary files. + */ + static std::string getTempDir(bool shared = true); + bool isAbsolute() { return _impl->isAbsolute(); } @@ -241,6 +254,8 @@ public: protected: std::shared_ptr _impl; friend class URLFetcher; + static std::string currTmpDir; + }; class USCXML_API URLFetcher { @@ -270,6 +285,7 @@ protected: std::map _handlesToHeaders; void* _multiHandle = NULL; char* _envProxy = NULL; + }; } diff --git a/src/uscxml/util/URL.mm b/src/uscxml/util/URL.mm new file mode 100644 index 0000000..881b3e2 --- /dev/null +++ b/src/uscxml/util/URL.mm @@ -0,0 +1,62 @@ +/** + * @file + * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#include "URL.h" +#include "Foundation/Foundation.h" + +#ifdef __has_feature +# if __has_feature(objc_arc) +# define(HAS_AUTORELEASE_POOL) +# endif +#endif + +namespace uscxml { + +std::string URL::getResourceDir() { + std::string path; +#if HAS_AUTORELEASE_POOL + @autoreleasepool { +#else + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +#endif + NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier]; + if (bundleId != nil) { + NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; + path = [resourcePath cStringUsingEncoding:NSUTF8StringEncoding]; + } else { + // we are not actually in a bundle, fall back to /tmp + NSFileManager *fm = [NSFileManager defaultManager]; + NSURL* resURL = [[fm homeDirectoryForCurrentUser] URLByAppendingPathComponent:@".uscxml"]; + NSString* resPath = [NSString stringWithUTF8String:[resURL fileSystemRepresentation]]; + + if (![fm fileExistsAtPath:resPath]) { + [fm createDirectoryAtPath:resPath withIntermediateDirectories:YES attributes:nil error:nil]; + } + path = [resPath cStringUsingEncoding:NSUTF8StringEncoding]; + } + +#if HAS_AUTORELEASE_POOL + } +#else + [pool drain]; +#endif + return path; +} + +} diff --git a/test/src/test-extensions.cpp b/test/src/test-extensions.cpp index d73065c..e3dfbb6 100644 --- a/test/src/test-extensions.cpp +++ b/test/src/test-extensions.cpp @@ -54,9 +54,9 @@ std::shared_ptr nestedDelayQueue; class PausableDelayedEventQueue : public BasicDelayedEventQueue { public: PausableDelayedEventQueue(DelayedEventQueueCallbacks* callbacks) : BasicDelayedEventQueue(callbacks) { - _pausedAt.tv_sec = 0; - _pausedAt.tv_usec = 0; - } + _pausedAt.tv_sec = 0; + _pausedAt.tv_usec = 0; + } std::shared_ptr create(DelayedEventQueueCallbacks* callbacks) { // remember as nestedDelayQueue in global scope @@ -100,8 +100,8 @@ public: evutil_gettimeofday(&now, NULL); evutil_timersub(&now, &_pausedAt, &pausedFor); - _pausedAt.tv_sec = 0; - _pausedAt.tv_usec = 0; + _pausedAt.tv_sec = 0; + _pausedAt.tv_usec = 0; for(auto& callbackData : _callbackData) { // add the time we were paused to all due times diff --git a/test/src/test-http-debugger.pl b/test/src/test-http-debugger.pl index 14b2468..a7a1d0a 100755 --- a/test/src/test-http-debugger.pl +++ b/test/src/test-http-debugger.pl @@ -39,6 +39,42 @@ sub assertSuccess { from_json($response->content())->{'status'} eq "success" or die($message); } +sub prepareSession { + my $xml = shift; + + ### Get a session + my $request = GET $baseURL.'/connect'; + $response = $ua->request($request); + assertSuccess($response, "Could not connect"); + + my $session = from_json($response->content())->{'session'}; + die("Cannot acquire session from server") if (!$session); + + ### Prepare an SCXML interpreter + $request = POST $baseURL.'/prepare', + [ + 'session' => $session, + 'url' => 'http://localhost/anonymous.scxml', + 'xml' => $xml + ]; + $response = $ua->request($request); + assertSuccess($response, "Could not prepare SCXML"); + + return $session; +} + +sub finishSession { + my $session = shift; + + ### Prepare an SCXML interpreter + $request = POST $baseURL.'/disconnect', + [ + 'session' => $session, + ]; + $response = $ua->request($request); + assertSuccess($response, "Could not disconnect session"); +} + sub popAndCompare { my $qualified = shift; my $bp = shift(@breakpointSeq); @@ -97,24 +133,8 @@ END_SCXML ); - ### Get a session - $request = GET $baseURL.'/connect'; - $response = $ua->request($request); - assertSuccess($response, "Could not connect"); - - my $session = from_json($response->content())->{'session'}; - die("Cannot acquire session from server") if (!$session); - - ### Prepare an SCXML interpreter - $request = POST $baseURL.'/prepare', - [ - 'session' => $session, - 'url' => 'http://localhost/test152.scxml', - 'xml' => $xml - ]; - $response = $ua->request($request); - assertSuccess($response, "Could not prepare SCXML"); - + my $session = &prepareSession($xml); + while(@breakpointSeq > 0) { ### Take a step $request = POST $baseURL.'/step', ['session' => $session]; @@ -145,6 +165,7 @@ END_SCXML $data = from_json($response->content()); die("Machine not yet finished") if ($data->{'replyType'} ne "finished"); + &finishSession($session); } sub testBreakpoint { @@ -163,23 +184,7 @@ sub testBreakpoint { END_SCXML - ### Get a session - $request = GET $baseURL.'/connect'; - $response = $ua->request($request); - assertSuccess($response, "Could not connect"); - - my $session = from_json($response->content())->{'session'}; - die("Cannot acquire session from server") if (!$session); - - ### Prepare an SCXML interpreter - $request = POST $baseURL.'/prepare', - [ - 'session' => $session, - 'url' => 'http://localhost/test154.scxml', - 'xml' => $xml - ]; - $response = $ua->request($request); - assertSuccess($response, "Could not prepare SCXML"); + my $session = prepareSession($xml); ### Skip to breakpoint $request = POST $baseURL.'/breakpoint/skipto', @@ -200,9 +205,50 @@ END_SCXML $data = from_json($response->content()); print Dumper($data); + + &finishSession($session); +} + +sub testIssueReporting { + my $xml = << 'END_SCXML'; + + + + + + + + + + + + + + + + + +END_SCXML + + my $session = prepareSession($xml); + + ### Get a list of issues + $request = POST $baseURL.'/issues', + [ + 'session' => $session + ]; + $response = $ua->request($request); + assertSuccess($response, "Could not get issues for prepared SCXML document"); + + $data = from_json($response->content()); + print Dumper($data); + + &finishSession($session); + } -# &testSimpleStepping(); +&testSimpleStepping(); &testBreakpoint(); +&testIssueReporting(); kill('TERM', $pid); \ No newline at end of file diff --git a/test/src/test-url.cpp b/test/src/test-url.cpp index 4d6cee8..c1a840f 100644 --- a/test/src/test-url.cpp +++ b/test/src/test-url.cpp @@ -9,6 +9,9 @@ #include #include +#include +#include + using namespace uscxml; using namespace XERCESC_NS; @@ -46,6 +49,29 @@ bool canResolve(const std::string& url) { } } +bool dirExists(const std::string& path) { +#ifdef _WIN32 + DWORD ftyp = GetFileAttributesA(dirName_in.c_str()); + if (ftyp == INVALID_FILE_ATTRIBUTES) + return false; //something is wrong with your path! + + if (ftyp & FILE_ATTRIBUTE_DIRECTORY) + return true; // this is a directory! + + return false; // this is not a directory! +#else + struct stat info; + + if(stat( path.c_str(), &info ) != 0) { + return false; + } else if(info.st_mode & S_IFDIR) { + return true; + } else { + return false; + } +#endif +} + void testFileURLs() { // absolute @@ -125,33 +151,47 @@ void testFileURLs() { } -int main(int argc, char** argv) { -#ifdef _WIN32 - WSADATA wsaData; - WSAStartup(MAKEWORD(2, 2), &wsaData); -#endif - - // some URLs from http://www-archive.mozilla.org/quality/networking/testing/filetests.html - -// URL foo("file:/"); -// assert(foo.isAbsolute()); - +void testData() { + { + Data data = Data::fromJSON("{\"shiftKey\":false,\"toElement\":{\"id\":\"\",\"localName\":\"body\"},\"clientY\":38,\"y\":38,\"x\":66,\"ctrlKey\":false,\"relatedTarget\":{\"id\":\"\",\"localName\":\"body\"},\"clientX\":66,\"screenY\":288,\"metaKey\":false,\"offsetX\":58,\"altKey\":false,\"offsetY\":30,\"fromElement\":{\"id\":\"foo\",\"localName\":\"div\"},\"screenX\":-1691,\"dataTransfer\":null,\"button\":0,\"pageY\":38,\"layerY\":38,\"pageX\":66,\"charCode\":0,\"which\":0,\"keyCode\":0,\"detail\":0,\"layerX\":66,\"returnValue\":true,\"timeStamp\":1371223991895,\"eventPhase\":2,\"target\":{\"id\":\"foo\",\"localName\":\"div\"},\"defaultPrevented\":false,\"srcElement\":{\"id\":\"foo\",\"localName\":\"div\"},\"type\":\"mouseout\",\"cancelable\":true,\"currentTarget\":{\"id\":\"foo\",\"localName\":\"div\"},\"bubbles\":true,\"cancelBubble\":false}"); + std::cout << data << std::endl; + } - HTTPServer::getInstance(8199, 8200); + { + Data data = Data::fromJSON("asdf"); + std::cout << data << std::endl; + } + { + Data data = Data::fromJSON("[ '1', '2', '3', '4' ]"); + std::cout << data << std::endl; + } + { + Data data = Data::fromJSON("{'foo1': 'bar2', 'foo3': { 'foo4': 'bar5' }, 'foo6': 'bar7', 'foo8': { 'foo9': 'foo10': { 'foo11': 'bar12' } } }"); + std::cout << data << std::endl; + } + { + Data data = Data::fromJSON("{\"firstName\": \"John\", \"lastName\": \"Smith\", \"age\": 25, \"address\": { \"streetAddress\": \"21 2nd Street\", \"city\": \"New York\",\"state\": \"NY\",\"postalCode\": 10021},\"phoneNumber\": [{\"type\": \"home\",\"number\": \"212 555-1234\"},{ \"type\": \"fax\",\"number\": \"646 555-4567\"}]}"); + std::cout << data << std::endl; + } - std::string exeName = argv[0]; - exeName = exeName.substr(exeName.find_last_of("\\/") + 1); +} - try { - testFileURLs(); - } catch (Event e) { - LOGD(USCXML_ERROR) << e; - exit(EXIT_FAILURE); +void testHTTPURLs() { + { + URL url("http://www.heise.de/de/index.html"); + std::cout << std::string(url) << std::endl; + assert(url.isAbsolute()); + assert(iequals(std::string(url), "http://www.heise.de/de/index.html")); + assert(iequals(url.scheme(), "http")); + assert(iequals(url.host(), "www.heise.de")); + assert(iequals(url.path(), "/de/index.html")); + url.download(); } { try { URL url("http://asdfasdfasdfasdf.wgferg"); + std::cout << std::string(url) << std::endl; url.download(true); assert(false); } catch (Event e) { @@ -161,6 +201,7 @@ int main(int argc, char** argv) { { try { URL url("http://uscxml.tk.informatik.tu-darmstadt.de/foobarfoo.html"); + std::cout << std::string(url) << std::endl; url.download(true); assert(false); } catch (Event e) { @@ -168,40 +209,20 @@ int main(int argc, char** argv) { } } -#if 0 +#ifndef _WIN32 + // no SSL support on windows { - Interpreter interpreter = Interpreter::fromURI("/Users/sradomski/Desktop/application_small.scxml"); - assert(interpreter); - std::list states; - states.push_back("b"); - interpreter.setConfiguration(states); - interpreter.interpret(); + URL url("https://raw.github.com/tklab-tud/uscxml/master/test/samples/uscxml/test-ecmascript.scxml"); + std::cout << std::string(url) << std::endl; + assert(url.isAbsolute()); + assert(iequals(url.scheme(), "https")); + url.download(); } #endif -#if 0 - { - try { - - URL url(argv[0]); - assert(url.isAbsolute()); - assert(canResolve(argv[0])); - assert(canResolve(url.asString())); - - URL baseUrl = URL::asBaseURL(url); - URL exeUrl(exeName); - exeUrl.toAbsolute(baseUrl); - assert(canResolve(exeUrl.asString())); - std::cout << exeUrl.asString() << std::endl; - exeUrl.download(true); - assert(exeUrl.getInContent().length() > 0); - - } catch (Event e) { - std::cout << e << std::endl; - } - } -#endif +} +void testServlets() { { TestServlet* testServlet1 = new TestServlet(false); TestServlet* testServlet2 = new TestServlet(false); @@ -218,81 +239,44 @@ int main(int argc, char** argv) { HTTPServer::unregisterServlet(testServlet2); } -#if 0 - { - TestServlet* testServlet1 = new TestServlet(true); - TestServlet* testServlet2 = new TestServlet(true); - TestServlet* testServlet3 = new TestServlet(true); +} - assert(HTTPServer::registerServlet("/foo", testServlet1)); - assert(HTTPServer::registerServlet("/foo", testServlet2)); - assert(HTTPServer::registerServlet("/foo", testServlet3)); - assert(boost::ends_with(testServlet1->_actualUrl, "foo")); - assert(boost::ends_with(testServlet2->_actualUrl, "foo2")); - assert(boost::ends_with(testServlet3->_actualUrl, "foo3")); +void testPaths() { + std::string resourceDir = URL::getResourceDir(); + std::cout << resourceDir << std::endl; + assert(dirExists(resourceDir)); - HTTPServer::unregisterServlet(testServlet1); - HTTPServer::unregisterServlet(testServlet2); - HTTPServer::unregisterServlet(testServlet3); - } + std::string tmpPrivateDir = URL::getTempDir(false); + std::cout << tmpPrivateDir << std::endl; + assert(dirExists(tmpPrivateDir)); + + std::string tmpSharedDir = URL::getTempDir(true); + std::cout << tmpSharedDir << std::endl; + assert(dirExists(tmpSharedDir)); +} + +int main(int argc, char** argv) { +#ifdef _WIN32 + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); #endif - { - Data data = Data::fromJSON("{\"shiftKey\":false,\"toElement\":{\"id\":\"\",\"localName\":\"body\"},\"clientY\":38,\"y\":38,\"x\":66,\"ctrlKey\":false,\"relatedTarget\":{\"id\":\"\",\"localName\":\"body\"},\"clientX\":66,\"screenY\":288,\"metaKey\":false,\"offsetX\":58,\"altKey\":false,\"offsetY\":30,\"fromElement\":{\"id\":\"foo\",\"localName\":\"div\"},\"screenX\":-1691,\"dataTransfer\":null,\"button\":0,\"pageY\":38,\"layerY\":38,\"pageX\":66,\"charCode\":0,\"which\":0,\"keyCode\":0,\"detail\":0,\"layerX\":66,\"returnValue\":true,\"timeStamp\":1371223991895,\"eventPhase\":2,\"target\":{\"id\":\"foo\",\"localName\":\"div\"},\"defaultPrevented\":false,\"srcElement\":{\"id\":\"foo\",\"localName\":\"div\"},\"type\":\"mouseout\",\"cancelable\":true,\"currentTarget\":{\"id\":\"foo\",\"localName\":\"div\"},\"bubbles\":true,\"cancelBubble\":false}"); - std::cout << data << std::endl; - } - { - Data data = Data::fromJSON("asdf"); - std::cout << data << std::endl; - } - { - Data data = Data::fromJSON("[ '1', '2', '3', '4' ]"); - std::cout << data << std::endl; - } - { - Data data = Data::fromJSON("{'foo1': 'bar2', 'foo3': { 'foo4': 'bar5' }, 'foo6': 'bar7', 'foo8': { 'foo9': 'foo10': { 'foo11': 'bar12' } } }"); - std::cout << data << std::endl; - } - { - Data data = Data::fromJSON("{\"firstName\": \"John\", \"lastName\": \"Smith\", \"age\": 25, \"address\": { \"streetAddress\": \"21 2nd Street\", \"city\": \"New York\",\"state\": \"NY\",\"postalCode\": 10021},\"phoneNumber\": [{\"type\": \"home\",\"number\": \"212 555-1234\"},{ \"type\": \"fax\",\"number\": \"646 555-4567\"}]}"); - std::cout << data << std::endl; - } + // some URLs from http://www-archive.mozilla.org/quality/networking/testing/filetests.html - { - URL url("http://www.heise.de/de/index.html"); - std::cout << std::string(url) << std::endl; - assert(url.isAbsolute()); - assert(iequals(std::string(url), "http://www.heise.de/de/index.html")); - assert(iequals(url.scheme(), "http")); - assert(iequals(url.host(), "www.heise.de")); - assert(iequals(url.path(), "/de/index.html")); - url.download(); - } + HTTPServer::getInstance(8199, 8200); -#ifndef _WIN32 - { - URL url("https://raw.github.com/tklab-tud/uscxml/master/test/samples/uscxml/test-ecmascript.scxml"); - std::cout << std::string(url) << std::endl; - assert(url.isAbsolute()); - assert(iequals(url.scheme(), "https")); - url.download(); + try { + testPaths(); + testFileURLs(); + testHTTPURLs(); + testData(); + testServlets(); + } catch (Event e) { + LOGD(USCXML_ERROR) << e; + exit(EXIT_FAILURE); } -#endif -#if 0 - { - URL url("test/index.html"); - assert(iequals(url.scheme(), "")); - url.toAbsoluteCwd(); - assert(iequals(url.scheme(), "file")); - std::cout << std::string(url) << std::endl; - } - { - URL url = URL::toLocalFile("this is quite some content!", "txt"); - std::cout << url.asLocalFile("txt"); - assert(url.isAbsolute()); - assert(iequals(url.scheme(), "file")); - } -#endif + + } -- cgit v0.12