summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2016-06-18 22:43:00 +0100
committerDan Goodliffe <dan@randomdan.homeip.net>2016-06-18 22:43:00 +0100
commit9b40e1ec2b84e0442bc6ef6466d5a8a523c82250 (patch)
treeff3566336ac19298f5e21895c49d81ceba3c828a
parentBuild most things by default (diff)
downloadicespider-9b40e1ec2b84e0442bc6ef6466d5a8a523c82250.tar.bz2
icespider-9b40e1ec2b84e0442bc6ef6466d5a8a523c82250.tar.xz
icespider-9b40e1ec2b84e0442bc6ef6466d5a8a523c82250.zip
Initial commit of IceSpider, implements basic route compiler
-rw-r--r--icespider/Jamfile.jam9
-rw-r--r--icespider/common/Jamfile.jam12
-rw-r--r--icespider/common/routes.ice29
-rw-r--r--icespider/compile/Jamfile.jam37
-rw-r--r--icespider/compile/main.cpp20
-rw-r--r--icespider/compile/routeCompiler.cpp246
-rw-r--r--icespider/compile/routeCompiler.h36
-rw-r--r--icespider/core/Jamfile.jam9
-rw-r--r--icespider/core/ihttpRequest.cpp10
-rw-r--r--icespider/core/ihttpRequest.h25
-rw-r--r--icespider/core/irouteHandler.cpp19
-rw-r--r--icespider/core/irouteHandler.h32
-rw-r--r--icespider/fcgi/Jamfile.jam7
-rw-r--r--icespider/fcgi/main.cpp22
-rw-r--r--icespider/unittests/Jamfile.jam44
-rw-r--r--icespider/unittests/test-api.ice13
-rw-r--r--icespider/unittests/testCompile.cpp105
-rw-r--r--icespider/unittests/testRoutes.json44
18 files changed, 719 insertions, 0 deletions
diff --git a/icespider/Jamfile.jam b/icespider/Jamfile.jam
new file mode 100644
index 0000000..20ca41b
--- /dev/null
+++ b/icespider/Jamfile.jam
@@ -0,0 +1,9 @@
+build-project core ;
+build-project compile ;
+build-project unittests ;
+build-project fcgi ;
+
+lib Ice ;
+lib IceUtil ;
+lib pthread ;
+
diff --git a/icespider/common/Jamfile.jam b/icespider/common/Jamfile.jam
new file mode 100644
index 0000000..081ebd1
--- /dev/null
+++ b/icespider/common/Jamfile.jam
@@ -0,0 +1,12 @@
+lib icespider-common :
+ [ glob-tree *.ice *.cpp : bin ]
+ :
+ <library>..//pthread
+ <library>..//Ice
+ <library>..//IceUtil
+ : :
+ <library>..//pthread
+ <library>..//Ice
+ <library>..//IceUtil
+ ;
+
diff --git a/icespider/common/routes.ice b/icespider/common/routes.ice
new file mode 100644
index 0000000..6ec1d50
--- /dev/null
+++ b/icespider/common/routes.ice
@@ -0,0 +1,29 @@
+module UserIceSpider {
+ enum HttpMethod {
+ GET, POST, DELETE
+ };
+ enum ParameterSource {
+ URL, Body, QueryString, Header
+ };
+ class Parameter {
+ string name;
+ ParameterSource source;
+ optional(0) string key;
+ };
+ sequence<Parameter> Parameters;
+ class Route {
+ string name;
+ string path;
+ HttpMethod method;
+ string operation;
+ Parameters params;
+ };
+ sequence<Route> Routes;
+ sequence<string> Slices;
+ class RouteConfiguration {
+ string name;
+ Routes routes;
+ Slices slices;
+ };
+};
+
diff --git a/icespider/compile/Jamfile.jam b/icespider/compile/Jamfile.jam
new file mode 100644
index 0000000..5d31fb1
--- /dev/null
+++ b/icespider/compile/Jamfile.jam
@@ -0,0 +1,37 @@
+lib adhocutil : : : : <include>/usr/include/adhocutil ;
+lib slicer : : : : <include>/usr/include/slicer <include>/usr/include/adhocutil ;
+lib slicer-json ;
+lib boost_program_options ;
+lib boost_system ;
+lib boost_filesystem ;
+lib Slice ;
+
+lib icespider-compile :
+ [ glob-tree *.cpp : bin main.cpp ]
+ [ glob ../common/*.ice ]
+ :
+ <slicer>pure
+ <library>slicer
+ <library>adhocutil
+ <library>slicer-json
+ <library>boost_system
+ <library>boost_filesystem
+ <library>Slice
+ <library>../common
+ <implicit-dependency>../common
+ <library>..//Ice
+ <library>..//IceUtil
+ <library>..//pthread
+ : :
+ <library>boost_system
+ <library>boost_filesystem
+ <library>../common
+ ;
+
+exe icespider :
+ main.cpp
+ :
+ <library>boost_program_options
+ <library>icespider-compile
+ ;
+
diff --git a/icespider/compile/main.cpp b/icespider/compile/main.cpp
new file mode 100644
index 0000000..e96574e
--- /dev/null
+++ b/icespider/compile/main.cpp
@@ -0,0 +1,20 @@
+#include <boost/program_options.hpp>
+namespace po = boost::program_options;
+
+int
+main(int c, char ** v)
+{
+ std::string input, output;
+ po::options_description opts("IceSpider compile options");
+ opts.add_options()
+ ("input", po::value(&input), "Input .json file")
+ ("output", po::value(&output), "Output .cpp file")
+ ;
+ po::positional_options_description pod;
+ pod.add("input", 1).add("output", 2);
+ po::variables_map vm;
+ po::store(po::command_line_parser(c, v).options(opts).positional(pod).run(), vm);
+ po::notify(vm);
+ return 0;
+}
+
diff --git a/icespider/compile/routeCompiler.cpp b/icespider/compile/routeCompiler.cpp
new file mode 100644
index 0000000..bce8095
--- /dev/null
+++ b/icespider/compile/routeCompiler.cpp
@@ -0,0 +1,246 @@
+#include "routeCompiler.h"
+#include <slicer/slicer.h>
+#include <slicer/modelPartsTypes.h>
+#include <Slice/Preprocessor.h>
+#include <scopeExit.h>
+#include <fprintbf.h>
+#include <boost/filesystem/convenience.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/join.hpp>
+#include <Slice/CPlusPlusUtil.h>
+
+namespace IceSpider {
+ namespace Compile {
+ class StringValue : public Slicer::ValueTarget {
+ public:
+ StringValue(std::string & t) : target(t) { }
+ void get(const bool &) const override { }
+ void get(const Ice::Byte &) const override { }
+ void get(const Ice::Short &) const override { }
+ void get(const Ice::Int &) const override { }
+ void get(const Ice::Long &) const override { }
+ void get(const Ice::Float &) const override { }
+ void get(const Ice::Double &) const override { }
+ void get(const std::string & v) const override { target = v; }
+ std::string & target;
+ };
+
+ RouteCompiler::RouteCompiler()
+ {
+ searchPath.insert(boost::filesystem::current_path());
+ }
+
+ UserIceSpider::RouteConfigurationPtr
+ RouteCompiler::loadConfiguration(const boost::filesystem::path & input) const
+ {
+ auto deserializer = Slicer::DeserializerPtr(Slicer::FileDeserializerFactory::createNew(input.extension().string(), input));
+ return Slicer::DeserializeAnyWith<UserIceSpider::RouteConfigurationPtr>(deserializer);
+ }
+
+ Ice::StringSeq operator+(Ice::StringSeq ss, const std::string & s)
+ {
+ ss.push_back(s);
+ return ss;
+ }
+
+ Slice::OperationPtr
+ RouteCompiler::findOperation(UserIceSpider::RoutePtr r, const Slice::ContainerPtr & c, const Ice::StringSeq & ns)
+ {
+ for (const auto & cls : c->classes()) {
+ auto fqcn = ns + cls->name();
+ for (const auto & op : cls->allOperations()) {
+ auto fqon = boost::algorithm::join(fqcn + op->name(), ".");
+ if (fqon == r->operation) {
+ return op;
+ }
+ }
+ }
+ for (const auto & m : c->modules()) {
+ auto op = findOperation(r, m, ns + m->name());
+ if (op) return op;
+ }
+ return NULL;
+ }
+
+ Slice::OperationPtr
+ RouteCompiler::findOperation(UserIceSpider::RoutePtr r, const Units & us)
+ {
+ for (const auto & u : us) {
+ auto op = findOperation(r, u.second);
+ if (op) return op;
+ }
+ throw std::runtime_error("Find operator failed for " + r->name);
+ }
+
+ void
+ RouteCompiler::applyDefaults(UserIceSpider::RouteConfigurationPtr c, const Units & u) const
+ {
+ for (const auto & r : c->routes) {
+ auto o = findOperation(r, u);
+ for (const auto & p : o->parameters()) {
+ auto defined = std::find_if(r->params.begin(), r->params.end(), [p](const auto & rp) {
+ return p->name() == rp->name;
+ });
+ if (defined != r->params.end()) {
+ auto d = *defined;
+ if (!d->key) d->key = d->name;
+ continue;
+ }
+ r->params.push_back(new UserIceSpider::Parameter(p->name(), UserIceSpider::ParameterSource::URL, p->name()));
+ }
+ }
+ }
+
+ void
+ RouteCompiler::compile(const boost::filesystem::path & input, const boost::filesystem::path & output) const
+ {
+ auto configuration = loadConfiguration(input);
+ auto units = loadUnits(configuration);
+ applyDefaults(configuration, units);
+
+ AdHoc::ScopeExit uDestroy([&units]() {
+ for (auto & u : units) {
+ u.second->destroy();
+ }
+ });
+
+ FILE * out = fopen(output.c_str(), "w");
+ if (!out) {
+ throw std::runtime_error("Failed to open output file");
+ }
+ AdHoc::ScopeExit outClose(
+ [out]() { fclose(out); },
+ NULL,
+ [&output]() { boost::filesystem::remove(output); });
+ processConfiguration(out, configuration, units);
+ }
+
+ RouteCompiler::Units
+ RouteCompiler::loadUnits(UserIceSpider::RouteConfigurationPtr c) const
+ {
+ RouteCompiler::Units units;
+ AdHoc::ScopeExit uDestroy;
+ for (const auto & slice : c->slices) {
+ boost::filesystem::path realSlice;
+ for (const auto & p : searchPath) {
+ if (boost::filesystem::exists(p / slice)) {
+ realSlice = p / slice;
+ break;
+ }
+ }
+ if (realSlice.empty()) {
+ throw std::runtime_error("Could not find slice file");
+ }
+ std::vector<std::string> cppArgs;
+ Slice::PreprocessorPtr icecpp = Slice::Preprocessor::create("IceSpider", realSlice.string(), cppArgs);
+ FILE * cppHandle = icecpp->preprocess(false);
+
+ if (cppHandle == NULL) {
+ throw std::runtime_error("Preprocess failed");
+ }
+
+ Slice::UnitPtr u = Slice::Unit::createUnit(false, false, false, false);
+ uDestroy.onFailure.push_back([u]() { u->destroy(); });
+
+ int parseStatus = u->parse(realSlice.string(), cppHandle, false);
+
+ if (!icecpp->close()) {
+ throw std::runtime_error("Preprocess close failed");
+ }
+
+ if (parseStatus == EXIT_FAILURE) {
+ throw std::runtime_error("Unit parse failed");
+ }
+
+ units[slice] = u;
+ }
+ return units;
+ }
+
+ template<typename ... X>
+ void
+ fprintbf(unsigned int indent, FILE * output, const X & ... x)
+ {
+ for (; indent > 0; --indent) {
+ fprintf(output, "\t");
+ }
+ fprintbf(output, x...);
+ }
+
+ template<typename Enum>
+ std::string getEnumString(Enum & e)
+ {
+ std::string rtn;
+ Slicer::ModelPart::CreateFor<Enum>(e)->GetValue(new StringValue(rtn));
+ return rtn;
+ }
+
+ void
+ RouteCompiler::processConfiguration(FILE * output, UserIceSpider::RouteConfigurationPtr c, const Units & units) const
+ {
+ fprintf(output, "// This source files was generated by IceSpider.\n");
+ fprintbf(output, "// Configuration name: %s\n\n", c->name);
+
+ fprintf(output, "// Standard headers.\n");
+ fprintf(output, "#include <irouteHandler.h>\n");
+
+ fprintf(output, "\n// Interface headers.\n");
+ for (const auto & s : c->slices) {
+ boost::filesystem::path slicePath(s);
+ slicePath.replace_extension(".h");
+ fprintbf(output, "#include <%s>\n", slicePath.string());
+ }
+
+ fprintf(output, "\n");
+ fprintbf(output, "namespace %s {\n", c->name);
+ fprintbf(1, output, "// Implementation classes.\n\n");
+ for (const auto & r : c->routes) {
+ auto proxyName = r->operation.substr(0, r->operation.find_last_of('.'));
+ auto operation = r->operation.substr(r->operation.find_last_of('.') + 1);
+ boost::algorithm::replace_all(proxyName, ".", "::");
+ std::string methodName = getEnumString(r->method);
+
+ fprintbf(1, output, "// Route name: %s\n", r->name);
+ fprintbf(1, output, "// path: %s\n", r->path);
+ fprintbf(1, output, "class %s : public IceSpider::IRouteHandler {\n", r->name);
+ fprintbf(2, output, "public:\n");
+ fprintbf(3, output, "%s() :\n", r->name);
+ fprintbf(4, output, "IceSpider::IRouteHandler(UserIceSpider::HttpMethod::%s, \"%s\")", methodName, r->path);
+ for (const auto & p : r->params) {
+ fprintf(output, ",\n");
+ fprintbf(4, output, "_pn_%s(\"%s\")", p->name, *p->key);
+ }
+ fprintf(output, "\n");
+ fprintbf(3, output, "{\n");
+ fprintbf(3, output, "}\n\n");
+ fprintbf(3, output, "void execute(IceSpider::IHttpRequest * request) const\n");
+ fprintbf(3, output, "{\n");
+ auto o = findOperation(r, units);
+ for (const auto & p : r->params) {
+ auto ip = *std::find_if(o->parameters().begin(), o->parameters().end(), [p](const auto & ip) { return ip->name() == p->name; });
+ fprintbf(4, output, "auto _p_%s(request->get%sParam<%s>(_pn_%s));\n",
+ p->name, getEnumString(p->source), Slice::typeToString(ip->type()), p->name);
+ }
+ fprintbf(4, output, "auto prx = getProxy<%s>();\n", proxyName);
+ fprintbf(4, output, "prx->%s(", operation);
+ for (const auto & p : r->params) {
+ fprintbf(output, "_p_%s, ", p->name);
+ }
+ fprintbf(output, "request->getContext());\n");
+ fprintbf(3, output, "}\n\n");
+ fprintbf(2, output, "private:\n");
+ for (const auto & p : r->params) {
+ fprintbf(3, output, "std::string _pn_%s;\n", p->name);
+ }
+ fprintbf(1, output, "};\n\n");
+ }
+ fprintbf(output, "} // namespace %s\n\n", c->name);
+ fprintf(output, "// Register route handlers.\n");
+ for (const auto & r : c->routes) {
+ fprintbf(output, "PLUGIN(%s::%s, IceSpider::IRouteHandler);\n", c->name, r->name);
+ }
+ fprintf(output, "\n// End generated code.\n");
+ }
+ }
+}
+
diff --git a/icespider/compile/routeCompiler.h b/icespider/compile/routeCompiler.h
new file mode 100644
index 0000000..2ac1f4b
--- /dev/null
+++ b/icespider/compile/routeCompiler.h
@@ -0,0 +1,36 @@
+#ifndef ICESPIDER_COMPILE_ROURTECOMPILER_H
+#define ICESPIDER_COMPILE_ROURTECOMPILER_H
+
+#include <boost/filesystem/path.hpp>
+#include <visibility.h>
+#include <set>
+#include <routes.h>
+#include <Slice/Parser.h>
+#include <Ice/BuiltinSequences.h>
+
+namespace IceSpider {
+ namespace Compile {
+ class DLL_PUBLIC RouteCompiler {
+ public:
+ typedef std::map<std::string, Slice::UnitPtr> Units;
+
+ RouteCompiler();
+
+ UserIceSpider::RouteConfigurationPtr loadConfiguration(const boost::filesystem::path &) const;
+ Units loadUnits(UserIceSpider::RouteConfigurationPtr) const;
+
+ void applyDefaults(UserIceSpider::RouteConfigurationPtr, const Units & u) const;
+ void compile(const boost::filesystem::path & input, const boost::filesystem::path & output) const;
+
+ std::set<boost::filesystem::path> searchPath;
+
+ private:
+ void processConfiguration(FILE * output, UserIceSpider::RouteConfigurationPtr, const Units &) const;
+ static Slice::OperationPtr findOperation(UserIceSpider::RoutePtr, const Units &);
+ static Slice::OperationPtr findOperation(UserIceSpider::RoutePtr, const Slice::ContainerPtr &, const Ice::StringSeq & = Ice::StringSeq());
+ };
+ }
+}
+
+#endif
+
diff --git a/icespider/core/Jamfile.jam b/icespider/core/Jamfile.jam
new file mode 100644
index 0000000..45bc30a
--- /dev/null
+++ b/icespider/core/Jamfile.jam
@@ -0,0 +1,9 @@
+lib adhocutil : : : : <include>/usr/include/adhocutil ;
+
+lib icespider-core :
+ [ glob-tree *.cpp : bin ]
+ :
+ <library>../common
+ <library>adhocutil
+ <implicit-dependency>../common
+ ;
diff --git a/icespider/core/ihttpRequest.cpp b/icespider/core/ihttpRequest.cpp
new file mode 100644
index 0000000..423e439
--- /dev/null
+++ b/icespider/core/ihttpRequest.cpp
@@ -0,0 +1,10 @@
+#include "ihttpRequest.h"
+
+namespace IceSpider {
+ Ice::Context
+ IHttpRequest::getContext() const
+ {
+ return Ice::Context();
+ }
+}
+
diff --git a/icespider/core/ihttpRequest.h b/icespider/core/ihttpRequest.h
new file mode 100644
index 0000000..76361b6
--- /dev/null
+++ b/icespider/core/ihttpRequest.h
@@ -0,0 +1,25 @@
+#ifndef ICESPIDER_IHTTPREQUEST_H
+#define ICESPIDER_IHTTPREQUEST_H
+
+#include <IceUtil/Exception.h>
+#include <IceUtil/Optional.h>
+#include <Ice/Current.h>
+#include <visibility.h>
+
+namespace IceSpider {
+ class DLL_PUBLIC IHttpRequest {
+ public:
+ Ice::Context getContext() const;
+ template<typename T>
+ T getURLParam(const std::string & key) const { (void)key; return T(); }
+ template<typename T>
+ T getBodyParam(const std::string & key) const { (void)key; return T(); }
+ template<typename T>
+ T getQueryStringParam(const std::string & key) const { (void)key; return T(); }
+ template<typename T>
+ T getHeaderParam(const std::string & key) const { (void)key; return T(); }
+ };
+}
+
+#endif
+
diff --git a/icespider/core/irouteHandler.cpp b/icespider/core/irouteHandler.cpp
new file mode 100644
index 0000000..2107ff7
--- /dev/null
+++ b/icespider/core/irouteHandler.cpp
@@ -0,0 +1,19 @@
+#include "irouteHandler.h"
+#include <plugins.impl.h>
+
+INSTANTIATEPLUGINOF(IceSpider::IRouteHandler);
+
+namespace IceSpider {
+ IRouteHandler::IRouteHandler(UserIceSpider::HttpMethod m, const std::string & p) :
+ method(m),
+ path(p)
+ {
+ }
+
+ Ice::ObjectPrx
+ IRouteHandler::getProxy(const char *) const
+ {
+ return NULL;
+ }
+}
+
diff --git a/icespider/core/irouteHandler.h b/icespider/core/irouteHandler.h
new file mode 100644
index 0000000..e02d0a3
--- /dev/null
+++ b/icespider/core/irouteHandler.h
@@ -0,0 +1,32 @@
+#ifndef ICESPIDER_IROUTEHANDLER_H
+#define ICESPIDER_IROUTEHANDLER_H
+
+#include "ihttpRequest.h"
+#include <routes.h>
+#include <plugins.h>
+#include <visibility.h>
+
+namespace IceSpider {
+ class DLL_PUBLIC IRouteHandler : public AdHoc::AbstractPluginImplementation {
+ public:
+ IRouteHandler(UserIceSpider::HttpMethod, const std::string & path);
+ virtual void execute(IHttpRequest * request) const = 0;
+
+ const UserIceSpider::HttpMethod method;
+ const std::string path;
+
+ protected:
+ Ice::ObjectPrx getProxy(const char *) const;
+
+ template<typename Interface>
+ typename Interface::ProxyType getProxy() const
+ {
+ return Interface::ProxyType::uncheckedCast(getProxy(typeid(Interface).name()));
+ }
+ };
+ typedef AdHoc::PluginOf<IRouteHandler> RouteHandlers;
+}
+
+#endif
+
+
diff --git a/icespider/fcgi/Jamfile.jam b/icespider/fcgi/Jamfile.jam
new file mode 100644
index 0000000..eb0c3f2
--- /dev/null
+++ b/icespider/fcgi/Jamfile.jam
@@ -0,0 +1,7 @@
+lib fcgi : : <name>fcgi ;
+
+lib icespider-fcgi :
+ [ glob-tree *.cpp : bin ]
+ :
+ <library>fcgi
+ ;
diff --git a/icespider/fcgi/main.cpp b/icespider/fcgi/main.cpp
new file mode 100644
index 0000000..6132769
--- /dev/null
+++ b/icespider/fcgi/main.cpp
@@ -0,0 +1,22 @@
+#include <fcgio.h>
+
+int
+main(void)
+{
+ if (!FCGX_IsCGI()) {
+ FCGX_Request request;
+
+ FCGX_Init();
+ FCGX_InitRequest(&request, 0, 0);
+
+ while (FCGX_Accept_r(&request) == 0) {
+ // app.process(IO, &IO, IO);
+ FCGX_Finish_r(&request);
+ }
+ return 0;
+ }
+ else {
+ return 1;
+ }
+}
+
diff --git a/icespider/unittests/Jamfile.jam b/icespider/unittests/Jamfile.jam
new file mode 100644
index 0000000..92a3434
--- /dev/null
+++ b/icespider/unittests/Jamfile.jam
@@ -0,0 +1,44 @@
+import testing ;
+
+lib adhocutil : : : : <include>/usr/include/adhocutil ;
+lib boost_utf : : <name>boost_unit_test_framework ;
+lib dl ;
+path-constant me : . ;
+
+alias testCommon : :
+ <define>ROOT=\"$(me)\"
+ <library>..//Ice
+ <library>..//IceUtil
+ : :
+ <library>..//IceUtil
+ <library>..//Ice
+ <library>boost_utf
+ <define>ROOT=\"$(me)\"
+ ;
+
+run
+ testCompile.cpp
+ : :
+ test-api.ice
+ testRoutes.json
+ :
+ <define>BOOST_TEST_DYN_LINK
+ <library>testCommon
+ <library>test-api
+ <library>dl
+ <library>adhocutil
+ <library>../core//icespider-core
+ <library>../compile//icespider-compile
+ <implicit-dependency>../compile//icespider-compile
+ <dependency>../core
+ : testCompile :
+ ;
+
+lib test-api :
+ test-api.ice
+ :
+ <library>..//pthread
+ <library>..//Ice
+ <library>..//IceUtil
+ ;
+
diff --git a/icespider/unittests/test-api.ice b/icespider/unittests/test-api.ice
new file mode 100644
index 0000000..4fc87bd
--- /dev/null
+++ b/icespider/unittests/test-api.ice
@@ -0,0 +1,13 @@
+module TestIceSpider {
+ class SomeModel {
+ string value;
+ };
+
+ interface TestApi {
+ SomeModel index();
+ SomeModel withParams(string s, int i);
+ void returnNothing(string s);
+ void complexParam(string s, SomeModel m);
+ };
+};
+
diff --git a/icespider/unittests/testCompile.cpp b/icespider/unittests/testCompile.cpp
new file mode 100644
index 0000000..c53fe65
--- /dev/null
+++ b/icespider/unittests/testCompile.cpp
@@ -0,0 +1,105 @@
+#define BOOST_TEST_MODULE TestCompile
+#include <boost/test/unit_test.hpp>
+
+#include <definedDirs.h>
+#include <plugins.h>
+#include <dlfcn.h>
+#include "../compile/routeCompiler.h"
+#include "../core/irouteHandler.h"
+#include <boost/algorithm/string/join.hpp>
+
+class CoreFixture {
+ protected:
+ CoreFixture() :
+ modeDir(binDir.lexically_relative(rootDir / "bin" / "testCompile.test"))
+ {
+ }
+ boost::filesystem::path modeDir;
+};
+
+BOOST_FIXTURE_TEST_SUITE(cf, CoreFixture)
+
+BOOST_AUTO_TEST_CASE( testLoadConfiguration )
+{
+ IceSpider::Compile::RouteCompiler rc;
+ rc.searchPath.insert(rootDir);
+ auto cfg = rc.loadConfiguration(rootDir / "testRoutes.json");
+ auto u = rc.loadUnits(cfg);
+ rc.applyDefaults(cfg, u);
+
+ BOOST_REQUIRE_EQUAL("common", cfg->name);
+ BOOST_REQUIRE_EQUAL(4, cfg->routes.size());
+
+ BOOST_REQUIRE_EQUAL("index", cfg->routes[0]->name);
+ BOOST_REQUIRE_EQUAL("/", cfg->routes[0]->path);
+ BOOST_REQUIRE_EQUAL(UserIceSpider::HttpMethod::GET, cfg->routes[0]->method);
+ BOOST_REQUIRE_EQUAL("TestIceSpider.TestApi.index", cfg->routes[0]->operation);
+ BOOST_REQUIRE_EQUAL(0, cfg->routes[0]->params.size());
+
+ BOOST_REQUIRE_EQUAL("item", cfg->routes[1]->name);
+ BOOST_REQUIRE_EQUAL("/{s}/{i}", cfg->routes[1]->path);
+ BOOST_REQUIRE_EQUAL(2, cfg->routes[1]->params.size());
+
+ BOOST_REQUIRE_EQUAL("del", cfg->routes[2]->name);
+ BOOST_REQUIRE_EQUAL(UserIceSpider::HttpMethod::DELETE, cfg->routes[2]->method);
+ BOOST_REQUIRE_EQUAL(1, cfg->routes[2]->params.size());
+
+ BOOST_REQUIRE_EQUAL("update", cfg->routes[3]->name);
+ BOOST_REQUIRE_EQUAL(UserIceSpider::HttpMethod::POST, cfg->routes[3]->method);
+ BOOST_REQUIRE_EQUAL(2, cfg->routes[3]->params.size());
+
+ BOOST_REQUIRE_EQUAL(1, cfg->slices.size());
+ BOOST_REQUIRE_EQUAL("test-api.ice", cfg->slices[0]);
+}
+
+BOOST_AUTO_TEST_CASE( testCompile )
+{
+ IceSpider::Compile::RouteCompiler rc;
+ rc.searchPath.insert(rootDir);
+ auto input = rootDir / "testRoutes.json";
+ auto output = binDir / "testRoutes.cpp";
+ auto outputso = boost::filesystem::change_extension(output, ".so");
+ auto libGenDir = (rootDir / "bin" / modeDir);
+ rc.compile(input, output);
+
+ auto compileCommand = boost::algorithm::join<std::vector<std::string>>({
+ "gcc", "-shared", "-std=c++1y", "-fPIC", " -fvisibility=hidden", "-O3", "-Wl,--warn-once,--gc-sections",
+ "-I", "/usr/include/adhocutil",
+ "-I", (rootDir.parent_path() / "core").string(),
+ "-I", (rootDir.parent_path() / "common").string(),
+ "-I", (rootDir.parent_path() / "common" / "bin" / modeDir).string(),
+ "-I", libGenDir.string(),
+ "-o", outputso.string(),
+ output.string(),
+ }, " ");
+ BOOST_TEST_INFO("Compile command: " << compileCommand);
+ int compileResult = system(compileCommand.c_str());
+ BOOST_REQUIRE_EQUAL(0, compileResult);
+}
+
+class DuffRequest : public IceSpider::IHttpRequest {
+
+};
+
+BOOST_AUTO_TEST_CASE( testLoad )
+{
+ auto outputso = binDir / "testRoutes.so";
+ auto lib = dlopen(outputso.c_str(), RTLD_NOW);
+ BOOST_TEST_INFO(dlerror());
+ BOOST_REQUIRE(lib);
+
+ BOOST_REQUIRE_EQUAL(4, AdHoc::PluginManager::getDefault()->getAll<IceSpider::IRouteHandler>().size());
+ // smoke test (block ensure dlclose dones't cause segfault)
+ {
+ auto route = AdHoc::PluginManager::getDefault()->get<IceSpider::IRouteHandler>("common::index");
+ BOOST_REQUIRE(route);
+ BOOST_REQUIRE_THROW({
+ route->implementation()->execute(new DuffRequest());
+ }, IceUtil::NullHandleException);
+ }
+
+ BOOST_REQUIRE_EQUAL(0, dlclose(lib));
+}
+
+BOOST_AUTO_TEST_SUITE_END();
+
diff --git a/icespider/unittests/testRoutes.json b/icespider/unittests/testRoutes.json
new file mode 100644
index 0000000..b4eb54f
--- /dev/null
+++ b/icespider/unittests/testRoutes.json
@@ -0,0 +1,44 @@
+{
+ "name": "common",
+ "routes": [
+ {
+ "name": "index",
+ "path": "/",
+ "method": "GET",
+ "operation": "TestIceSpider.TestApi.index"
+ },
+ {
+ "name": "item",
+ "path": "/{s}/{i}",
+ "method": "GET",
+ "operation": "TestIceSpider.TestApi.withParams"
+ },
+ {
+ "name": "del",
+ "path": "/{s}",
+ "method": "DELETE",
+ "operation": "TestIceSpider.TestApi.returnNothing"
+ },
+ {
+ "name": "update",
+ "path": "/{id}",
+ "method": "POST",
+ "operation": "TestIceSpider.TestApi.complexParam",
+ "params": [
+ {
+ "name": "s",
+ "source": "URL",
+ "key": "id"
+ },
+ {
+ "name": "m",
+ "source": "Body"
+ }
+ ]
+ }
+ ],
+ "slices": [
+ "test-api.ice"
+ ]
+}
+