Browse Source

- Added unit test for templates compiler

- Added some "template friendly" interfaces to request, and cookie
  classes
- Fixed handing of "." and ".." in url mapper
master
Artyom Beilis 13 years ago
parent
commit
b2f09ac0c5
19 changed files with 488 additions and 14 deletions
  1. +37
    -0
      CMakeLists.txt
  2. +48
    -5
      bin/cppcms_tmpl_cc
  3. +3
    -3
      cppcms/base_content.h
  4. +4
    -0
      cppcms/http_cookie.h
  5. +4
    -0
      cppcms/http_request.h
  6. +5
    -0
      cppcms/session_interface.h
  7. +1
    -1
      private/cgi_api.h
  8. +2
    -2
      src/application.cpp
  9. +3
    -3
      src/base_content.cpp
  10. +4
    -0
      src/http_cookie.cpp
  11. +11
    -0
      src/http_request.cpp
  12. +9
    -0
      src/session_interface.cpp
  13. +7
    -0
      src/url_mapper.cpp
  14. +92
    -0
      tests/dummy_api.h
  15. +56
    -0
      tests/tc_skin.tmpl
  16. +6
    -0
      tests/tc_skin_a.tmpl
  17. +6
    -0
      tests/tc_skin_b.tmpl
  18. +144
    -0
      tests/tc_test.cpp
  19. +46
    -0
      tests/tc_test_content.h

+ 37
- 0
CMakeLists.txt View File

@@ -415,6 +415,35 @@ add_custom_command(
${CMAKE_CURRENT_SOURCE_DIR}/src/hello_world_skin2.tmpl
${CMAKE_CURRENT_SOURCE_DIR}/src/hello_world_view1.tmpl)

add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/tc_skin_a.cpp
COMMAND ${PYTHON} ${CMAKE_CURRENT_SOURCE_DIR}/bin/cppcms_tmpl_cc
-o ${CMAKE_CURRENT_BINARY_DIR}/tc_skin_a.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/tc_skin_a.tmpl
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/bin/cppcms_tmpl_cc
${CMAKE_CURRENT_SOURCE_DIR}/tests/tc_skin_a.tmpl)

add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/tc_skin_b.cpp
COMMAND ${PYTHON} ${CMAKE_CURRENT_SOURCE_DIR}/bin/cppcms_tmpl_cc
-o ${CMAKE_CURRENT_BINARY_DIR}/tc_skin_b.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/tc_skin_b.tmpl
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/bin/cppcms_tmpl_cc
${CMAKE_CURRENT_SOURCE_DIR}/tests/tc_skin_b.tmpl)

add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/tc_skin.cpp
COMMAND ${PYTHON} ${CMAKE_CURRENT_SOURCE_DIR}/bin/cppcms_tmpl_cc
-o ${CMAKE_CURRENT_BINARY_DIR}/tc_skin.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/tc_skin.tmpl
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/bin/cppcms_tmpl_cc
${CMAKE_CURRENT_SOURCE_DIR}/tests/tc_skin.tmpl)



if(NOT DISABLE_SHARED)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/skin3.cpp
@@ -435,6 +464,11 @@ if(NOT DISABLE_SHARED)
endif(NOT DISABLE_SHARED)


foreach(SKIN tc_skin_a tc_skin_b tc_skin)
add_library(${SKIN} SHARED ${CMAKE_CURRENT_BINARY_DIR}/${SKIN}.cpp)
target_link_libraries(${SKIN} ${CPPCMS_LIB})
endforeach()


add_executable(hello_world src/hello_world.cpp skin1.cpp skin2.cpp)
target_link_libraries(hello_world ${CPPCMS_LIB})
@@ -472,7 +506,10 @@ set(ALL_TESTS
xss_test
url_mapper_test
copy_filter_test
tc_test
)


foreach(TEST ${ALL_TESTS})
add_executable(${TEST} tests/${TEST}.cpp)
target_link_libraries(${TEST} ${CPPCMS_LIB})


+ 48
- 5
bin/cppcms_tmpl_cc View File

@@ -4,8 +4,10 @@ import os
import re
import sys

variable_match=r"\*?([a-zA-Z][a-zA-Z0-9_]*\(?\)?)(((\.|->)([a-zA-Z][a-zA-Z0-9_]*\(?\)?))*)"
str_match=r'"([^"\\]|\\[^"]|\\")*"'
single_var_param_match=r'(?:-?\d+|"(?:[^"\\]|\\[^"]|\\")*")'
call_param_match=r'(?:\(\)|\((?:' + single_var_param_match + r')(?:,' + single_var_param_match + r')*\))'
variable_match=r"\*?([a-zA-Z][a-zA-Z0-9_]*"+ call_param_match +r"?)(((\.|->)([a-zA-Z][a-zA-Z0-9_]*" + call_param_match + r"?))*)"

def interleave(*args):
for idx in range(0, max(map(len,args))):
@@ -80,8 +82,6 @@ class html_type:
else:
html_type_code='as_html'



class view_block:
pattern=r'^<%\s*view\s+(\w+)\s+uses\s+(:?:?\w+(::\w+)*)(\s+extends\s+(:?:?\w+(::\w+)?))?\s*%>$'
@@ -494,6 +494,16 @@ class form_block:
flags = 'cppcms::form_flags::%s,cppcms::form_flags::%s' % ( html_type_code, m.group(1));
output('{ cppcms::form_context _form_context(out(),%s); %s.render(_form_context); }' % (flags , ident))

class render_block:
pattern=r'<%\s*render\s+(?P<name>[a-zA-Z]\w*)(\s+using\s+(?P<content>'+variable_match+r'))?\s*%>'
def use(self,m):
if m.group('content'):
content = make_ident(m.group('content'))
else:
content = 'content';
global namespace_name
output('content.app().render("%s","%s",out(),%s);"' % (content,namespace_name,content))


class filters_show_block(base_show):
pattern=r'^<%\s*('+ variable_match + r'\s*(\|.*)?)%>$'
@@ -518,6 +528,38 @@ def make_format_params(s,default_filter = 'escape'):
error_exit("Seems to be wrong parameters list [%s]" % s_orig)
return []

class cache_block:
pattern=r'<%\s*cache\s+((?P<str>'+ \
str_match +')|(?P<var>'+ variable_match +r'))' + \
r'(\s+for\s+(?P<time>\d+))?(\s+on\s+miss\s+(?P<callback>[a-zA-Z]\w+)\(\))?' \
+ '\s*%>'
type = 'cache'
def use(self,m):
if(m.group('str')):
self.parameter = m.group('str')
else:
self.parameter = make_ident(m.group('var'));
output('{ std::string _cppcms_temp_val')
output(' if(content.app().cache().fetch_frame(%s,_cppcms_temp_val))' % self.parameter);
output(' out() << _cppcms_temp_val;');
output(' else {')
if(m.group('callback')):
output(' '+make_ident(m.group('callback')+'();'))
output(' cppcms::copy_filter _cppcms_cache_flt(out());')
self.timeout = m.group('time');
def on_end(self):
if self.timeout:
output(' content.app().cache().store_frame(%s,_cppcms_cache_flt.detach(),%s);' \
% (self.parameter,self.timeout))
else:
output(' content.app().cache().store_frame(%s,_cppcms_cache_flt.detach());' % self.parameter);
output('}} // cache')






class ngettext_block:
pattern=r'^<%\s*ngt\s*('+str_match+')\s*,\s*('+str_match+')\s*,\s*('+variable_match+')\s*(using(.*))?\s*%>$'
def use(self,m):
@@ -553,9 +595,9 @@ class url_block:
if m.group(3):
params=make_format_params(m.group(4),'urlencode')
if not params:
output( "content.rendering_application().mapper().map(out(),%s);" % s)
output( "content.app().mapper().map(out(),%s);" % s)
else:
output( "content.rendering_application().mapper().map(out(),%s, %s);" % (s , ', '.join(params)))
output( "content.app().mapper().map(out(),%s, %s);" % (s , ', '.join(params)))


class include_block:
@@ -672,6 +714,7 @@ def main():
url_block(), \
foreach_block(), item_block(), empty_block(),separator_block(), \
include_block(), \
cache_block(), \
html_type(), form_block(), \
filters_show_block(), error_com() ]:
m = re.match(c.pattern,x)


+ 3
- 3
cppcms/base_content.h View File

@@ -43,18 +43,18 @@ namespace cppcms {
/// Get the application that renders current
/// content, throw cppcms_error if the application was not set
///
application &rendering_application();
application &app();
///
/// Set the application that renders current
///
/// Called automatically by application::render
///
void rendering_application(application &app);
void app(application &app);

///
/// Resets the application
///
void reset_rendering_application();
void reset_app();

private:
struct _data;


+ 4
- 0
cppcms/http_cookie.h View File

@@ -103,6 +103,10 @@ public:
///
void secure(bool v);

///
/// Check if cookie is not assigned - empty
///
bool empty() const;

cookie();
~cookie();


+ 4
- 0
cppcms/http_request.h View File

@@ -252,6 +252,10 @@ namespace http {
///
cookies_type const &cookies();
///
/// Get cookie by its name, if not assigned returns empty cookie
///
cookie const &cookie_by_name(std::string const &name);
///
/// form-data GET part of request
///
form_type const &get();


+ 5
- 0
cppcms/session_interface.h View File

@@ -114,6 +114,11 @@ public:
/// you call this function.
///
std::string get(std::string const &key);
///
/// Get a value for a session \a key. If it is not set, returns default_value
///
std::string get(std::string const &key,std::string const &default_value);

///
/// Get convert the value that is set for a key \a key to type T using std::iostream. For example you can


+ 1
- 1
private/cgi_api.h View File

@@ -70,7 +70,7 @@ namespace cgi {
virtual ~acceptor(){}
};

class connection :
class CPPCMS_API connection :
public booster::noncopyable,
public booster::enable_shared_from_this<connection>
{


+ 2
- 2
src/application.cpp View File

@@ -205,11 +205,11 @@ namespace {
public:
rnd_guard(base_content &cnt,application *app) : cnt_(&cnt)
{
cnt_->rendering_application(*app);
cnt_->app(*app);
}
~rnd_guard()
{
cnt_->reset_rendering_application();
cnt_->reset_app();
}
private:
base_content *cnt_;


+ 3
- 3
src/base_content.cpp View File

@@ -46,12 +46,12 @@ base_content const &base_content::operator=(base_content const &other)
return *this;
}

void base_content::rendering_application(application &app)
void base_content::app(application &app)
{
app_ = &app;
}

application &base_content::rendering_application()
application &base_content::app()
{
if(!app_) {
throw cppcms_error("Attempt to access to application that wasn't set");
@@ -59,7 +59,7 @@ application &base_content::rendering_application()
return *app_;
}

void base_content::reset_rendering_application()
void base_content::reset_app()
{
app_ = 0;
}


+ 4
- 0
src/http_cookie.cpp View File

@@ -27,6 +27,10 @@
namespace cppcms { namespace http {

struct cookie::_data { };
bool cookie::empty() const
{
return name_.empty() && value_.empty();
}

std::string cookie::name() const { return name_; }
void cookie::name(std::string v) { name_=v; }


+ 11
- 0
src/http_request.cpp View File

@@ -306,6 +306,17 @@ std::map<std::string,cookie> const &request::cookies()
{
return cookies_;
}

static cookie const empty_cookie;

cookie const &request::cookie_by_name(std::string const &name)
{
cookies_type::const_iterator p = cookies_.find(name);
if(p==cookies_.end())
return empty_cookie;
else
return p->second;
}
request::form_type const &request::get()
{


+ 9
- 0
src/session_interface.cpp View File

@@ -282,6 +282,15 @@ void session_interface::clear()
data_.clear();
}

std::string session_interface::get(std::string const &key,std::string const &def)
{
check();
data_type::const_iterator p=data_.find(key);
if(p==data_.end())
return def;
return p->second.value;
}

std::string session_interface::get(std::string const &key)
{
check();


+ 7
- 0
src/url_mapper.cpp View File

@@ -323,6 +323,13 @@ namespace cppcms {
}
}
if(real_key == ".")
real_key.clear();
else if(real_key == "..") {
mapper = &mapper->parent();
real_key.clear();
}
url_mapper *tmp = mapper->d->is_app(real_key);
if(tmp) {
// empty special key


+ 92
- 0
tests/dummy_api.h View File

@@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2008-2010 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// 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. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef CPPCMS_IMPL_DUMMY_API_H
#define CPPCMS_IMPL_DUMMY_API_H
#include "cgi_api.h"

using cppcms::impl::cgi::io_handler;
using cppcms::impl::cgi::handler;
using cppcms::impl::cgi::callback;


class dummy_api : public cppcms::impl::cgi::connection {
public:
dummy_api(cppcms::service &srv,std::map<std::string,std::string> env,std::string &output) :
cppcms::impl::cgi::connection(srv),
env_(env),
output_(&output)
{
}

virtual std::string getenv(std::string const &key)
{
std::map<std::string,std::string>::const_iterator p = env_.find(key);
if(p==env_.end())
return std::string();
return p->second;
}
virtual std::map<std::string,std::string> const &getenv()
{
return env_;
}
void async_read_headers(handler const &)
{
throw std::runtime_error("dummy_api: unsupported");
}

void async_read_eof(callback const &)
{
throw std::runtime_error("dummy_api: unsupported");
}

void async_write_some(void const *,size_t,io_handler const &h)
{
throw std::runtime_error("dummy_api: unsupported");
}
virtual void write_eof(){}
virtual size_t write_some(void const *p,size_t s,booster::system::error_code &e)
{
output_->append(reinterpret_cast<char const *>(p),s);
return s;
}
virtual booster::aio::io_service &get_io_service()
{
throw std::runtime_error("dummy_api: unsupported");
}
bool keep_alive()
{
return false;
}
void close(){}
virtual void async_read_some(void *,size_t,io_handler const &h)
{
throw std::runtime_error("dummy_api: unsupported");
}
virtual void async_write_eof(handler const &h)
{
throw std::runtime_error("dummy_api: unsupported");
}
private:
std::map<std::string,std::string> env_;
std::string *output_;

};


#endif

+ 56
- 0
tests/tc_skin.tmpl View File

@@ -0,0 +1,56 @@
<% c++ #include "tests/tc_test_content.h" %>
<% skin tc_skin %>
<% view test_default_master uses data::master %>
<% template render() %>c<% end template %>
<% end view %>
<% view view_y uses data::master %>
<% template render() %>view y<% end template %>
<% end view %>
<% view view_x uses data::master %>
<% template render() %>view x<% end template %>
<% end view %>
<% view master uses data::master %>
<% template render() %>
TBD
<% end template %>
<% end view %>

<% view master_tmpl uses data::master %>
<% template a() %>A<% end %>
<% template b(int x) %>x=<% x %><% end %>
<% template c(int x,std::string const &y) %>x=<%x%> y=<%y%><% end %>
<% template render() %>
<% include a() %>
<% include b(10) %>
<% include b(integer) %>
<% include c(10,"test") %>
<% include c(integer,text) %>
<% foreach x as data::master::integers_type::iterator in integers %><% separator %>,<% item %><% include b(x) %><% end %><% end %>
<% end template %>
<% end view %>

<% view master_if uses data::master %>
<% template render() %>
<% if integer %>integer<%else%>!integer<% end %>
<% if not integer %>!integer<%else%>!!integer<% end %>
<% if empty text %>text empty<% else %>text not empty<% end %>
<% if not empty text %>!text empty<% else %>!!text not empty<% end %>
<% if (true) %>true<%end%>
<% if (false) %>false<%end%>
<% if (false) %>false<% elif (true) %>true<%end%>
<% end template %>
<% end view %>

<% view master_url uses data::master %>
<% template render () %>
<% url "." %>
<% url "." using integer %>
<% url "." using integer,text %>
<% url "/" using integer %>
<% url "/" using integer,text %>
<% url "/" using integer,text | raw %>
<% url "/foo" %>
<% end %>
<% end view %>

<% end skin %>

+ 6
- 0
tests/tc_skin_a.tmpl View File

@@ -0,0 +1,6 @@
<% c++ #include "tests/tc_test_content.h" %>
<% skin tc_skin_a %>
<% view master uses data::master %>
<% template render() %>a<% end template %>
<% end view %>
<% end skin %>

+ 6
- 0
tests/tc_skin_b.tmpl View File

@@ -0,0 +1,6 @@
<% c++ #include "tests/tc_test_content.h" %>
<% skin tc_skin_b %>
<% view master uses data::master %>
<% template render() %>b<% end template %>
<% end view %>
<% end skin %>

+ 144
- 0
tests/tc_test.cpp View File

@@ -0,0 +1,144 @@
#include "tc_test_content.h"
#include <cppcms/service.h>
#include <cppcms/http_response.h>
#include <cppcms/json.h>
#include <cppcms/url_mapper.h>
#include "dummy_api.h"
#include "test.h"

class test_app : public cppcms::application {
public:
test_app(cppcms::service &srv) :
cppcms::application(srv),
srv_(srv)
{
mapper().assign("foo","/foo");
mapper().assign("/");
mapper().assign("/{1}");
mapper().assign("/{1}/{2}");
}
~test_app()
{
release_context();
}
void set_context()
{
std::map<std::string,std::string> env;
env["HTTP_HOST"]="www.example.com";
env["SCRIPT_NAME"]="/foo";
env["PATH_INFO"]="/bar";
env["REQUEST_METHOD"]="GET";
booster::shared_ptr<dummy_api> api(new dummy_api(srv_,env,output_));
booster::shared_ptr<cppcms::http::context> cnt(new cppcms::http::context(api));
assign_context(cnt);
response().out();
output_.clear();
}

std::string str()
{
response().out() << std::flush;
std::string result = output_;
output_.clear();
return result;
}

void test_skins()
{
std::cout << "- Testing different skins" << std::endl;
data::master m;
render("tc_skin_a","master",m);
TEST(str()=="a");
render("tc_skin_b","master",m);
TEST(str()=="b");
render("test_default_master",m);
TEST(str()=="c");
}
void test_views()
{
std::cout << "- Testing different views" << std::endl;
data::master m;
render("view_x",m);
TEST(str()=="view x");
render("view_y",m);
TEST(str()=="view y");
}
void test_templates()
{
std::cout << "- Testing different template calls" << std::endl;
data::master m;
m.integer = 1;
m.text = "str";
m.integers.push_back(21);
m.integers.push_back(22);
render("master_tmpl",m);
TEST(str()=="\nA\nx=10\nx=1\nx=10 y=test\nx=1 y=str\nx=21,x=22\n");
}
void test_if()
{
std::cout << "- Testing conditions" << std::endl;
data::master m;
m.integer = 1;
m.text = "x";
render("master_if",m);
TEST(str()=="\ninteger\n!!integer\ntext not empty\n!text empty\ntrue\n\ntrue\n");
m.integer = 0;
m.text = "";
render("master_if",m);
TEST(str()=="\n!integer\n!integer\ntext empty\n!!text not empty\ntrue\n\ntrue\n");
}
void test_url()
{
std::cout << "- Testing url" << std::endl;
data::master m;
m.integer = 1;
m.text = "/";
render("master_url",m);
TEST(str()=="\n"
"/\n"
"/1\n"
"/1/%2f\n"
"/1\n"
"/1/%2f\n"
"/1//\n"
"/foo\n");
}

private:
std::string output_;
cppcms::service &srv_;
};


int main()
{
try {
cppcms::json::value cfg;
cfg["views"]["paths"][0]="./";
cfg["views"]["skins"][0]="tc_skin_a";
cfg["views"]["skins"][1]="tc_skin_b";
cfg["views"]["skins"][2]="tc_skin";
cfg["views"]["default_skin"]="tc_skin";
cppcms::service srv(cfg);
test_app app(srv);

app.set_context();

app.test_skins();
app.test_views();
app.test_templates();
app.test_if();
app.test_url();

}
catch(std::exception const &e)
{
std::cerr << "Fail " << e.what() << std::endl;
return 1;
}
std::cout << "Ok" << std::endl;
return 0;
}




+ 46
- 0
tests/tc_test_content.h View File

@@ -0,0 +1,46 @@
#include <cppcms/view.h>
#include <string>
#include <vector>

namespace data {
struct master : public cppcms::base_content {
int integer;
std::string text;
typedef std::vector<int> integers_type;
integers_type integers;
};

struct foo : public master {
double real;
};

struct bar : public master {
typedef std::vector<std::string> set_type;
set_type set;
std::string text2html(std::string const &input)
{
std::string output;
for(unsigned i=0;i<input.size();i++) {
switch(input[i]) {
case '<':
output+="&lt;";
break;
case '>':
output+="&gt;";
break;
case '&':
output+="&amp;";
break;
case '\r':
break;
case '\n':
output+="<br />\n";
break;
default:
output+=input[i];
}
}
return output;
}
};
} // data

Loading…
Cancel
Save