Parcourir la source

Merge pull request #2115 from cutelyst/cutelyst_on_14

Add Cutelyst framework tests
Nate il y a 9 ans
Parent
commit
70bba34c73

+ 1 - 0
.travis.yml

@@ -28,6 +28,7 @@ env:
     - "TESTDIR=CSharp/revenj"
     - "TESTDIR=CSharp/servicestack"
     - "TESTDIR=C++/cpoll_cppsp"
+    - "TESTDIR=C++/cutelyst"
     - "TESTDIR=C++/silicon"
     - "TESTDIR=C++/treefrog"
     - "TESTDIR=C++/ulib"

+ 29 - 0
frameworks/C++/cutelyst/CMakeLists.txt

@@ -0,0 +1,29 @@
+project(cutelyst_benchmarks)
+
+cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
+if (POLICY CMP0043)
+  cmake_policy(SET CMP0043 NEW)
+endif()
+
+find_package(Qt5 COMPONENTS Core Network Sql REQUIRED)
+find_package(CutelystQt5 REQUIRED)
+
+# Auto generate moc files
+set(CMAKE_AUTOMOC ON)
+
+# As moc files are generated in the binary dir, tell CMake
+# to always look for includes there:
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+# Enable C++11 features
+add_definitions(-std=c++11)
+
+include_directories(
+    ${CMAKE_SOURCE_DIR}
+    ${CMAKE_CURRENT_BINARY_DIR}
+    ${CutelystQt5_INCLUDE_DIR}
+)
+
+file(GLOB_RECURSE TEMPLATES_SRC root/*)
+
+add_subdirectory(src)

+ 127 - 0
frameworks/C++/cutelyst/benchmark_config.json

@@ -0,0 +1,127 @@
+{
+    "framework": "cutelyst",
+    "tests": [{
+            "default": {
+                "setup_file": "setup",
+                "json_url": "/json",
+                "plaintext_url": "/plaintext",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Fullstack",
+                "database": "None",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi",
+                "notes": "",
+                "versus": ""
+            },
+            "postgres-raw": {
+                "setup_file": "setup",
+                "db_url": "/db_postgres",
+                "query_url": "/query_postgres?queries=",
+                "update_url": "/updates_postgres?queries=",
+                "fortune_url": "/fortunes_raw_postgres",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Platform",
+                "database": "Postgres",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi-pg-raw",
+                "notes": "",
+                "versus": ""
+            },
+            "mysql-raw": {
+                "setup_file": "setup",
+                "db_url": "/db_mysql",
+                "query_url": "/query_mysql?queries=",
+                "update_url": "/updates_mysql?queries=",
+                "fortune_url": "/fortunes_raw_mysql",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Platform",
+                "database": "MySQL",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi-mysql-raw",
+                "notes": "",
+                "versus": ""
+            },
+            "nginx": {
+                "setup_file": "setup_uwsgi_nginx",
+                "json_url": "/json",
+                "plaintext_url": "/plaintext",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Fullstack",
+                "database": "None",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi-nginx",
+                "notes": "",
+                "versus": ""
+            },
+            "nginx-postgres-raw": {
+                "setup_file": "setup_uwsgi_nginx",
+                "db_url": "/db_postgres",
+                "query_url": "/query_postgres?queries=",
+                "update_url": "/updates_postgres?queries=",
+                "fortune_url": "/fortunes_raw_postgres",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Platform",
+                "database": "Postgres",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi-nginx-pg-raw",
+                "notes": "",
+                "versus": ""
+            },
+            "nginx-mysql-raw": {
+                "setup_file": "setup_uwsgi_nginx",
+                "db_url": "/db_mysql",
+                "query_url": "/query_mysql?queries=",
+                "update_url": "/updates_mysql?queries=",
+                "fortune_url": "/fortunes_raw_mysql",
+                "port": 8080,
+                "approach": "Realistic",
+                "classification": "Platform",
+                "database": "MySQL",
+                "framework": "cutelyst",
+                "language": "C++",
+                "orm": "Raw",
+                "platform": "Qt",
+                "webserver": "None",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "cutelyst-uwsgi-nginx-mysql-raw",
+                "notes": "",
+                "versus": ""
+            }
+        }]
+}

+ 21 - 0
frameworks/C++/cutelyst/config/config.ini

@@ -0,0 +1,21 @@
+[uwsgi]
+master
+; Increase listen queue used for nginx connecting to uWSGI. This matches
+; net.ipv4.tcp_max_syn_backlog and net.core.somaxconn.
+; for performance
+disable-logging
+; use UNIX sockets instead of TCP loopback for performance
+http-socket = :8080
+; Avoid thundering herd problem http://uwsgi-docs.readthedocs.org/en/latest/articles/SerializingAccept.html .
+; This is currently disabled because when I tried it with flask, it caused a
+; 20% performance hit. The CPU cores could not be saturated with thunder-lock.
+; I'm not yet sure the full story, so this is presently disabled. Also,
+; disabling this caused bottle to get ~13% faster.
+;thunder-lock
+; used by uwsgi_stop.ini
+pidfile = /tmp/uwsgi.pid
+plugin = cutelyst
+
+[Cutelyst]
+DatabaseHostName=
+SendDate=

+ 23 - 0
frameworks/C++/cutelyst/config/config_socket.ini

@@ -0,0 +1,23 @@
+[uwsgi]
+master
+; Increase listen queue used for nginx connecting to uWSGI. This matches
+; net.ipv4.tcp_max_syn_backlog and net.core.somaxconn.
+; for performance
+disable-logging
+; use UNIX sockets instead of TCP loopback for performance
+socket = /tmp/uwsgi.sock
+; allow nginx to access the UNIX socket
+chmod-socket = 666
+; Avoid thundering herd problem http://uwsgi-docs.readthedocs.org/en/latest/articles/SerializingAccept.html .
+; This is currently disabled because when I tried it with flask, it caused a
+; 20% performance hit. The CPU cores could not be saturated with thunder-lock.
+; I'm not yet sure the full story, so this is presently disabled. Also,
+; disabling this caused bottle to get ~13% faster.
+;thunder-lock
+; used by uwsgi_stop.ini
+pidfile = /tmp/uwsgi.pid
+plugin = cutelyst
+
+[Cutelyst]
+DatabaseHostName=
+SendDate=

+ 48 - 0
frameworks/C++/cutelyst/nginx.conf

@@ -0,0 +1,48 @@
+# This file is based on /usr/local/nginx/conf/nginx.conf.default.
+
+# One worker process per core
+error_log stderr error;
+
+events {
+    # This needed to be increased because the nginx error log said so.
+    # http://nginx.org/en/docs/ngx_core_module.html#worker_connections
+    worker_connections  65535;
+    multi_accept on;
+}
+
+http {
+    default_type  application/octet-stream;
+    client_body_temp_path      /tmp;
+
+    # turn off request logging for performance
+    access_log off;
+
+    # I think these only options affect static file serving
+    sendfile        on;
+    tcp_nopush      on;
+
+    # Allow many HTTP Keep-Alive requests in a single TCP connection before
+    # closing it (the default is 100). This will minimize the total number
+    # of TCP connections opened/closed. The problem is that this may cause
+    # some worker processes to be handling too connections relative to the
+    # other workers based on an initial imbalance, so this is disabled for
+    # now.
+#    keepalive_requests 1000;
+
+    #keepalive_timeout  0;
+    keepalive_timeout  65;
+
+    server {
+        # For information on deferred, see:
+        # http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
+        # http://www.techrepublic.com/article/take-advantage-of-tcp-ip-options-to-optimize-data-transmission/
+        # The backlog argument to listen() is set to match net.ipv4.tcp_max_syn_backlog and net.core.somaxconn
+        listen       8080 default_server deferred backlog=65535;
+        server_name  localhost;
+
+        location / {
+            uwsgi_pass unix:/tmp/uwsgi.sock;
+            include /usr/local/nginx/conf/uwsgi_params;
+        }
+    }
+}

+ 22 - 0
frameworks/C++/cutelyst/setup.sh

@@ -0,0 +1,22 @@
+#!/bin/bash
+
+fw_depends cutelyst
+
+sed -i 's|DatabaseHostName=.*|DatabaseHostName='"$DBHOST"'|g' config/config.ini
+sed -i 's|SendDate=.*|SendDate=true|g' config/config.ini
+
+cd $IROOT
+mkdir cutelyst-benchmarks || true
+cd cutelyst-benchmarks
+rm -rf *
+
+QT_VERSION_MM=56
+export CMAKE_PREFIX_PATH=/opt/qt${QT_VERSION_MM}:${IROOT}
+
+cmake $TROOT -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$IROOT
+
+make clean
+make -j $MAX_THREADS
+
+export LD_LIBRARY_PATH=/opt/qt${QT_VERSION_MM}/lib:${IROOT}/lib/x86_64-linux-gnu/
+uwsgi --ini ${TROOT}/config/config.ini --cutelyst-app ${IROOT}/cutelyst-benchmarks/src/libcutelyst_benchmarks.so -p $(( $MAX_THREADS * 2 )) &

+ 25 - 0
frameworks/C++/cutelyst/setup_uwsgi_nginx.sh

@@ -0,0 +1,25 @@
+#!/bin/bash
+
+fw_depends cutelyst nginx
+
+sed -i 's|DatabaseHostName=.*|DatabaseHostName='"$DBHOST"'|g' config/config_socket.ini
+sed -i 's|SendDate=.*|SendDate=false|g' config/config_socket.ini
+sed -i 's|include .*/conf/uwsgi_params;|include '"${NGINX_HOME}"'/conf/uwsgi_params;|g' nginx.conf
+
+cd $IROOT
+mkdir cutelyst-benchmarks || true
+cd cutelyst-benchmarks
+rm -rf *
+
+QT_VERSION_MM=56
+export CMAKE_PREFIX_PATH=/opt/qt${QT_VERSION_MM}:${IROOT}
+
+cmake $TROOT -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$IROOT
+
+make clean
+make -j $MAX_THREADS
+
+nginx -c $TROOT/nginx.conf
+
+export LD_LIBRARY_PATH=/opt/qt${QT_VERSION_MM}/lib:${IROOT}/lib/x86_64-linux-gnu/
+uwsgi --ini ${TROOT}/config/config_socket.ini --cutelyst-app ${IROOT}/cutelyst-benchmarks/src/libcutelyst_benchmarks.so -p $(( $MAX_THREADS * 2 )) &

+ 20 - 0
frameworks/C++/cutelyst/source_code

@@ -0,0 +1,20 @@
+cutelyst/
+cutelyst/CMakeLists.txt
+cutelyst/src
+cutelyst/src/plaintexttest.cpp
+cutelyst/src/jsontest.cpp
+cutelyst/src/fortunetest.h
+cutelyst/src/CMakeLists.txt
+cutelyst/src/plaintexttest.h
+cutelyst/src/multipledatabasequeriestest.h
+cutelyst/src/multipledatabasequeriestest.cpp
+cutelyst/src/databaseupdatestest.h
+cutelyst/src/databaseupdatestest.cpp
+cutelyst/src/jsontest.h
+cutelyst/src/singledatabasequerytest.cpp
+cutelyst/src/singledatabasequerytest.h
+cutelyst/src/cutelyst-benchmarks.cpp
+cutelyst/src/cutelyst-benchmarks.h
+cutelyst/src/root.h
+cutelyst/src/root.cpp
+cutelyst/src/fortunetest.cpp

+ 19 - 0
frameworks/C++/cutelyst/src/CMakeLists.txt

@@ -0,0 +1,19 @@
+file(GLOB_RECURSE cutelyst_benchmarks_SRCS *.cpp *.h)
+
+set(cutelyst_benchmarks_SRCS
+    ${cutelyst_benchmarks_SRCS}
+    ${TEMPLATES_SRC}
+)
+
+# Create the application
+add_library(cutelyst_benchmarks SHARED ${cutelyst_benchmarks_SRCS})
+
+# Link to Cutelyst
+target_link_libraries(cutelyst_benchmarks
+    Cutelyst::Core
+    Cutelyst::Utils::Sql
+    Qt5::Core
+    Qt5::Network
+    Qt5::Sql
+)
+

+ 78 - 0
frameworks/C++/cutelyst/src/cutelyst-benchmarks.cpp

@@ -0,0 +1,78 @@
+#include "cutelyst-benchmarks.h"
+
+#include <Cutelyst/Plugins/StaticSimple/staticsimple.h>
+
+#include <QtSql/QSqlDatabase>
+#include <QtSql/QSqlError>
+#include <QDebug>
+
+#include "root.h"
+#include "jsontest.h"
+#include "singledatabasequerytest.h"
+#include "multipledatabasequeriestest.h"
+#include "databaseupdatestest.h"
+#include "fortunetest.h"
+#include "plaintexttest.h"
+
+using namespace Cutelyst;
+
+cutelyst_benchmarks::cutelyst_benchmarks(QObject *parent) : Application(parent)
+{
+}
+
+cutelyst_benchmarks::~cutelyst_benchmarks()
+{
+}
+
+bool cutelyst_benchmarks::init()
+{
+    if (config(QLatin1String("SendDate")).value<bool>()) {
+        qDebug() << "Manually send date";
+        new Root(this);
+    }
+
+    new JsonTest(this);
+    new SingleDatabaseQueryTest(this);
+    new MultipleDatabaseQueriesTest(this);
+    new DatabaseUpdatesTest(this);
+    new FortuneTest(this);
+    new PlaintextTest(this);
+
+    defaultHeaders().setServer(QLatin1String("Cutelyst"));
+    defaultHeaders().removeHeader(QLatin1String("X-Cutelyst"));
+
+    return true;
+}
+
+bool cutelyst_benchmarks::postFork()
+{
+    QSqlDatabase db;
+    db = QSqlDatabase::addDatabase(QLatin1String("QPSQL"), QLatin1String("postgres"));
+    db.setDatabaseName(QLatin1String("hello_world"));
+    db.setUserName(QLatin1String("benchmarkdbuser"));
+    db.setPassword(QLatin1String("benchmarkdbpass"));
+    db.setHostName(config(QLatin1String("DatabaseHostName")).toString());
+    if (!db.open()) {
+        qDebug() << "Error opening db:" << db << db.lastError().databaseText();
+        return false;
+    }
+
+    db = QSqlDatabase::addDatabase(QLatin1String("QMYSQL"), QLatin1String("mysql"));
+    db.setDatabaseName(QLatin1String("hello_world"));
+    db.setUserName(QLatin1String("benchmarkdbuser"));
+    db.setPassword(QLatin1String("benchmarkdbpass"));
+    db.setHostName(config(QLatin1String("DatabaseHostName")).toString());
+    if (!db.open()) {
+        qDebug() << "Error opening db:" << db << db.lastError().databaseText();
+        return false;
+    }
+
+//    db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), QLatin1String("sqlite"));
+//    if (!db.open()) {
+//        qDebug() << "Error opening db:" << db << db.lastError().databaseText();
+//        return false;
+//    }
+
+    return true;
+}
+

+ 22 - 0
frameworks/C++/cutelyst/src/cutelyst-benchmarks.h

@@ -0,0 +1,22 @@
+#ifndef CUTELYST_BENCHMARKS_H
+#define CUTELYST_BENCHMARKS_H
+
+#include <Cutelyst/Application>
+
+using namespace Cutelyst;
+
+class cutelyst_benchmarks : public Application
+{
+    Q_OBJECT
+    CUTELYST_APPLICATION(IID "cutelyst_benchmarks")
+public:
+    Q_INVOKABLE explicit cutelyst_benchmarks(QObject *parent = 0);
+    ~cutelyst_benchmarks();
+
+    bool init();
+
+    bool postFork();
+};
+
+#endif //CUTELYST_BENCHMARKS_H
+

+ 74 - 0
frameworks/C++/cutelyst/src/databaseupdatestest.cpp

@@ -0,0 +1,74 @@
+#include "databaseupdatestest.h"
+
+#include <Cutelyst/Plugins/Utils/Sql>
+
+#include <QtSql/QSqlQuery>
+
+#include <QtCore/QThread>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+
+DatabaseUpdatesTest::DatabaseUpdatesTest(QObject *parent) : Controller(parent)
+{
+
+}
+
+void DatabaseUpdatesTest::updates_postgres(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("postgres")));
+    QSqlQuery updateQuery = CPreparedSqlQueryForDatabase(
+                QLatin1String("UPDATE world SET randomNumber = :randomNumber WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("postgres")));
+    processQuery(c, query, updateQuery);
+}
+
+void DatabaseUpdatesTest::updates_mysql(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("mysql")));
+    QSqlQuery updateQuery = CPreparedSqlQueryForDatabase(
+                QLatin1String("UPDATE world SET randomNumber = :randomNumber WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("mysql")));
+    processQuery(c, query, updateQuery);
+}
+
+void DatabaseUpdatesTest::processQuery(Context *c, QSqlQuery &query, QSqlQuery &updateQuery)
+{
+    QJsonArray array;
+
+    int queries = c->request()->queryParam(QStringLiteral("queries"), QStringLiteral("1")).toInt();
+    if (queries < 1) {
+        queries = 1;
+    } else if (queries > 500) {
+        queries = 500;
+    }
+
+    for (int i = 0; i < queries; ++i) {
+        int id = (qrand() % 9999) + 1;
+
+        query.bindValue(QStringLiteral(":id"), id);
+        if (!query.exec() || !query.next()) {
+            c->res()->setStatus(Response::InternalServerError);
+            return;
+        }
+
+        int randomNumber = (qrand() % 9999) + 1;
+        updateQuery.bindValue(QStringLiteral(":id"), id);
+        updateQuery.bindValue(QStringLiteral(":randomNumber"), randomNumber);
+        if (!updateQuery.exec()) {
+            c->res()->setStatus(Response::InternalServerError);
+            return;
+        }
+
+        QJsonObject obj;
+        obj.insert(QStringLiteral("id"), query.value(0).toInt());
+        obj.insert(QStringLiteral("randomNumber"), randomNumber);
+        array.append(obj);
+    }
+
+    c->response()->setJsonBody(QJsonDocument(array));
+}

+ 26 - 0
frameworks/C++/cutelyst/src/databaseupdatestest.h

@@ -0,0 +1,26 @@
+#ifndef DATABASEUPDATESTEST_H
+#define DATABASEUPDATESTEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class QSqlQuery;
+class DatabaseUpdatesTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit DatabaseUpdatesTest(QObject *parent = 0);
+
+    C_ATTR(updates_postgres, :Local :AutoArgs)
+    void updates_postgres(Context *c);
+
+    C_ATTR(updates_mysql, :Local :AutoArgs)
+    void updates_mysql(Context *c);
+
+private:
+    inline void processQuery(Context *c, QSqlQuery &query, QSqlQuery &updateQuery);
+};
+
+#endif // DATABASEUPDATESTEST_H

+ 90 - 0
frameworks/C++/cutelyst/src/fortunetest.cpp

@@ -0,0 +1,90 @@
+#include "fortunetest.h"
+
+#include <Cutelyst/Plugins/Utils/Sql>
+
+#include <QStringBuilder>
+
+#include <QtSql/QSqlQuery>
+
+#include <QtCore/QThread>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+
+FortuneTest::FortuneTest(QObject *parent) : Controller(parent)
+{
+
+}
+
+void FortuneTest::fortunes_raw_postgres(Context *c)
+{
+    QSqlQuery query = postgresQuery();
+    auto fortunes = processQuery(c, query);
+    renderRaw(c, fortunes);
+}
+
+void FortuneTest::fortunes_raw_mysql(Context *c)
+{
+    QSqlQuery query = mysqlQuery();
+    auto fortunes = processQuery(c, query);
+    renderRaw(c, fortunes);
+}
+
+QSqlQuery FortuneTest::postgresQuery()
+{
+    return CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, message FROM fortune"),
+                QSqlDatabase::database(QLatin1String("postgres")));
+}
+
+QSqlQuery FortuneTest::mysqlQuery()
+{
+    return CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, message FROM fortune"),
+                QSqlDatabase::database(QLatin1String("mysql")));
+}
+
+static bool caseSensitiveLessThan(const Fortune &a1, const Fortune &a2)
+{
+    return a1.second < a2.second;
+}
+
+FortuneList FortuneTest::processQuery(Context *c, QSqlQuery &query)
+{
+    FortuneList fortunes;
+
+    if (!query.exec()) {
+        c->res()->setStatus(Response::InternalServerError);
+        return fortunes;
+    }
+
+    while (query.next()) {
+        fortunes.append(qMakePair(query.value(0).toInt(), query.value(1).toString()));
+    }
+    fortunes.append(qMakePair(0, QStringLiteral("Additional fortune added at request time.")));
+
+    qSort(fortunes.begin(), fortunes.end(), caseSensitiveLessThan);
+
+    c->response()->setContentType(QStringLiteral("text/html; charset=UTF-8"));
+
+    return fortunes;
+}
+
+void FortuneTest::renderRaw(Context *c, const FortuneList &fortunes)
+{
+    QString out;
+    out.append(QStringLiteral("<!DOCTYPE html>"
+                              "<html>"
+                              "<head><title>Fortunes</title></head>"
+                              "<body>"
+                              "<table>"
+                              "<tr><th>id</th><th>message</th></tr>"));
+
+    Q_FOREACH (const Fortune &fortune, fortunes) {
+        out.append(QLatin1String("<tr><td>") % QString::number(fortune.first) % QLatin1String("</td><td>") % fortune.second.toHtmlEscaped() % QLatin1String("</td></tr>"));
+    }
+
+    out.append(QStringLiteral("</table></body></html>"));
+
+    c->response()->setBody(out);
+}

+ 32 - 0
frameworks/C++/cutelyst/src/fortunetest.h

@@ -0,0 +1,32 @@
+#ifndef FORTUNETEST_H
+#define FORTUNETEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+typedef QPair<int, QString> Fortune;
+typedef QList<Fortune> FortuneList;
+
+class QSqlQuery;
+class FortuneTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit FortuneTest(QObject *parent = 0);
+
+    C_ATTR(fortunes_raw_postgres, :Local :AutoArgs)
+    void fortunes_raw_postgres(Context *c);
+
+    C_ATTR(fortunes_raw_mysql, :Local :AutoArgs)
+    void fortunes_raw_mysql(Context *c);
+
+private:
+    inline QSqlQuery postgresQuery();
+    inline QSqlQuery mysqlQuery();
+    inline FortuneList processQuery(Context *c, QSqlQuery &query);
+    inline void renderRaw(Context *c, const FortuneList &fortunes);
+};
+
+#endif // FORTUNETEST_H

+ 17 - 0
frameworks/C++/cutelyst/src/jsontest.cpp

@@ -0,0 +1,17 @@
+#include "jsontest.h"
+
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+
+JsonTest::JsonTest(QObject *parent) : Controller(parent)
+{
+
+}
+
+void JsonTest::json(Context *c)
+{
+    QJsonObject obj;
+    obj.insert(QStringLiteral("message"), QStringLiteral("Hello, World!"));
+
+    c->response()->setJsonBody(QJsonDocument(obj));
+}

+ 19 - 0
frameworks/C++/cutelyst/src/jsontest.h

@@ -0,0 +1,19 @@
+#ifndef JSONTEST_H
+#define JSONTEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class JsonTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit JsonTest(QObject *parent = 0);
+
+    C_ATTR(json, :Local :AutoArgs)
+    void json(Context *c);
+};
+
+#endif // JSONTEST_H

+ 60 - 0
frameworks/C++/cutelyst/src/multipledatabasequeriestest.cpp

@@ -0,0 +1,60 @@
+#include "multipledatabasequeriestest.h"
+
+#include <Cutelyst/Plugins/Utils/Sql>
+
+#include <QtSql/QSqlQuery>
+
+#include <QtCore/QThread>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QJsonArray>
+
+MultipleDatabaseQueriesTest::MultipleDatabaseQueriesTest(QObject *parent) : Controller(parent)
+{
+
+}
+
+void MultipleDatabaseQueriesTest::query_postgres(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("postgres")));
+    processQuery(c, query);
+}
+
+void MultipleDatabaseQueriesTest::query_mysql(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("mysql")));
+    processQuery(c, query);
+}
+
+void MultipleDatabaseQueriesTest::processQuery(Context *c, QSqlQuery &query)
+{
+    QJsonArray array;
+
+    int queries = c->request()->queryParam(QStringLiteral("queries"), QStringLiteral("1")).toInt();
+    if (queries < 1) {
+        queries = 1;
+    } else if (queries > 500) {
+        queries = 500;
+    }
+
+    for (int i = 0; i < queries; ++i) {
+        int id = qrand() % 9999;
+
+        query.bindValue(QStringLiteral(":id"), id + 1);
+        if (!query.exec() || !query.next()) {
+            c->res()->setStatus(Response::InternalServerError);
+            return;
+        }
+
+        QJsonObject obj;
+        obj.insert(QStringLiteral("id"), query.value(0).toInt());
+        obj.insert(QStringLiteral("randomNumber"), query.value(1).toInt());
+        array.append(obj);
+    }
+
+    c->response()->setJsonBody(QJsonDocument(array));
+}

+ 26 - 0
frameworks/C++/cutelyst/src/multipledatabasequeriestest.h

@@ -0,0 +1,26 @@
+#ifndef MULTIPLEDATABASEQUERIESTEST_H
+#define MULTIPLEDATABASEQUERIESTEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class QSqlQuery;
+class MultipleDatabaseQueriesTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit MultipleDatabaseQueriesTest(QObject *parent = 0);
+
+    C_ATTR(query_postgres, :Local :AutoArgs)
+    void query_postgres(Context *c);
+
+    C_ATTR(query_mysql, :Local :AutoArgs)
+    void query_mysql(Context *c);
+
+private:
+    inline void processQuery(Context *c, QSqlQuery &query);
+};
+
+#endif // MULTIPLEDATABASEQUERIESTEST_H

+ 13 - 0
frameworks/C++/cutelyst/src/plaintexttest.cpp

@@ -0,0 +1,13 @@
+#include "plaintexttest.h"
+
+PlaintextTest::PlaintextTest(QObject *parent) : Controller(parent)
+{
+
+}
+
+void PlaintextTest::plaintext(Context *c)
+{
+    Response *res = c->response();
+    res->setBody(QByteArrayLiteral("Hello, World!"));
+    res->setContentType(QStringLiteral("text/plain"));
+}

+ 19 - 0
frameworks/C++/cutelyst/src/plaintexttest.h

@@ -0,0 +1,19 @@
+#ifndef PLAINTEXTTEST_H
+#define PLAINTEXTTEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class PlaintextTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit PlaintextTest(QObject *parent = 0);
+
+    C_ATTR(plaintext, :Local :AutoArgs)
+    void plaintext(Context *c);
+};
+
+#endif // PLAINTEXTTEST_H

+ 37 - 0
frameworks/C++/cutelyst/src/root.cpp

@@ -0,0 +1,37 @@
+#include "root.h"
+
+#include <QElapsedTimer>
+
+using namespace Cutelyst;
+
+Root::Root(QObject *parent) : Controller(parent)
+{
+}
+
+Root::~Root()
+{
+}
+
+QElapsedTimer timerSetup(Context *c)
+{
+    QElapsedTimer timer;
+    timer.start();
+    return timer;
+}
+
+QString setupHeader(Context *c)
+{
+    return c->response()->headers().setDateWithDateTime(QDateTime::currentDateTimeUtc());
+}
+
+void Root::End(Context *c)
+{
+    static QString lastDate = setupHeader(c);
+    static QElapsedTimer timer = timerSetup(c);
+    if (timer.hasExpired(1000)) {
+        lastDate = setupHeader(c);
+        timer.restart();
+    } else {
+        c->response()->setHeader(QStringLiteral("date"), lastDate);
+    }
+}

+ 22 - 0
frameworks/C++/cutelyst/src/root.h

@@ -0,0 +1,22 @@
+#ifndef ROOT_H
+#define ROOT_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class Root : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit Root(QObject *parent = 0);
+    ~Root();
+
+private:
+    C_ATTR(End, :AutoArgs)
+    void End(Context *c);
+};
+
+#endif //ROOT_H
+

+ 47 - 0
frameworks/C++/cutelyst/src/singledatabasequerytest.cpp

@@ -0,0 +1,47 @@
+#include "singledatabasequerytest.h"
+
+#include <Cutelyst/Plugins/Utils/Sql>
+
+#include <QtSql/QSqlQuery>
+
+#include <QtCore/QThread>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+
+SingleDatabaseQueryTest::SingleDatabaseQueryTest(QObject *parent) : Controller(parent)
+{
+    qsrand(QDateTime::currentMSecsSinceEpoch());
+}
+
+void SingleDatabaseQueryTest::db_postgres(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("postgres")));
+    processQuery(c, query);
+}
+
+void SingleDatabaseQueryTest::db_mysql(Context *c)
+{
+    QSqlQuery query = CPreparedSqlQueryForDatabase(
+                QLatin1String("SELECT id, randomNumber FROM world WHERE id = :id"),
+                QSqlDatabase::database(QLatin1String("mysql")));
+    processQuery(c, query);
+}
+
+void SingleDatabaseQueryTest::processQuery(Context *c, QSqlQuery &query)
+{
+    int id = qrand() % 9999;
+
+    query.bindValue(QStringLiteral(":id"), id + 1);
+    if (!query.exec() || !query.next()) {
+        c->res()->setStatus(Response::InternalServerError);
+        return;
+    }
+
+    QJsonObject obj;
+    obj.insert(QStringLiteral("id"), query.value(0).toInt());
+    obj.insert(QStringLiteral("randomNumber"), query.value(1).toInt());
+
+    c->response()->setJsonBody(QJsonDocument(obj));
+}

+ 26 - 0
frameworks/C++/cutelyst/src/singledatabasequerytest.h

@@ -0,0 +1,26 @@
+#ifndef SINGLEDATABASEQUERYTEST_H
+#define SINGLEDATABASEQUERYTEST_H
+
+#include <Cutelyst/Controller>
+
+using namespace Cutelyst;
+
+class QSqlQuery;
+class SingleDatabaseQueryTest : public Controller
+{
+    Q_OBJECT
+    C_NAMESPACE("")
+public:
+    explicit SingleDatabaseQueryTest(QObject *parent = 0);
+
+    C_ATTR(db_postgres, :Local :AutoArgs)
+    void db_postgres(Context *c);
+
+    C_ATTR(db_mysql, :Local :AutoArgs)
+    void db_mysql(Context *c);
+
+private:
+    inline void processQuery(Context *c, QSqlQuery &query);
+};
+
+#endif // SINGLEDATABASEQUERYTEST_H

+ 33 - 0
toolset/setup/linux/frameworks/cutelyst.sh

@@ -0,0 +1,33 @@
+#!/bin/bash
+
+CUTELYST_VER=0.12.0
+RETCODE=$(fw_exists ${IROOT}/cutelyst.installed)
+[ ! "$RETCODE" == 0 ] || { \
+  source $IROOT/cutelyst.installed
+  return 0; }
+
+QT_VERSION_MM=56
+QT_VERSION_FULL=561-trusty
+
+sudo apt-add-repository --yes ppa:george-edison55/cmake-3.x
+sudo apt-add-repository --yes ppa:beineri/opt-qt$QT_VERSION_FULL
+sudo apt-get update -qq
+sudo apt-get install -qqy cmake
+sudo apt-get install -qqy uwsgi uuid-dev libcap-dev libzmq3-dev
+sudo apt-get install -qqy clearsilver-dev 
+sudo apt-get install -qqy qt${QT_VERSION_MM}base qt${QT_VERSION_MM}script qt${QT_VERSION_MM}tools
+export CMAKE_PREFIX_PATH=/opt/qt${QT_VERSION_MM};
+
+fw_get -O https://github.com/cutelyst/cutelyst/archive/r$CUTELYST_VER.tar.gz
+fw_untar r$CUTELYST_VER.tar.gz
+
+cd cutelyst-r$CUTELYST_VER
+mkdir build && cd build
+
+cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$IROOT
+
+make -j $MAX_THREADS && sudo make install
+
+echo "" > $IROOT/cutelyst.installed
+
+source $IROOT/cutelyst.installed