Merge pull request #15 from pmconrad/json_parsing_fix

JSON parsing fix from steem PR 2178
This commit is contained in:
Abit 2018-03-13 21:36:15 +01:00 committed by Miha Čančula
parent 94b046dce6
commit 838da0d8dd
11 changed files with 875 additions and 430 deletions

View file

@ -2,6 +2,8 @@
#include <fc/variant.hpp>
#include <fc/filesystem.hpp>
#define DEFAULT_MAX_RECURSION_DEPTH 200
namespace fc
{
class ostream;
@ -18,62 +20,69 @@ namespace fc
enum parse_type
{
legacy_parser = 0,
#ifdef WITH_EXOTIC_JSON_PARSERS
strict_parser = 1,
relaxed_parser = 2,
legacy_parser_with_string_doubles = 3
legacy_parser_with_string_doubles = 3,
#endif
broken_nul_parser = 4
};
enum output_formatting
{
stringify_large_ints_and_doubles = 0,
#ifdef WITH_EXOTIC_JSON_PARSERS
legacy_generator = 1
#endif
};
static ostream& to_stream( ostream& out, const fc::string& );
static ostream& to_stream( ostream& out, const variant& v, output_formatting format = stringify_large_ints_and_doubles );
static ostream& to_stream( ostream& out, const variants& v, output_formatting format = stringify_large_ints_and_doubles );
static ostream& to_stream( ostream& out, const variant_object& v, output_formatting format = stringify_large_ints_and_doubles );
static ostream& to_stream( ostream& out, const variant& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH );
static ostream& to_stream( ostream& out, const variants& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH );
static ostream& to_stream( ostream& out, const variant_object& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH );
static variant from_stream( buffered_istream& in, parse_type ptype = legacy_parser );
static variant from_stream( buffered_istream& in, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH );
static variant from_string( const string& utf8_str, parse_type ptype = legacy_parser );
static variants variants_from_string( const string& utf8_str, parse_type ptype = legacy_parser );
static string to_string( const variant& v, output_formatting format = stringify_large_ints_and_doubles );
static string to_pretty_string( const variant& v, output_formatting format = stringify_large_ints_and_doubles );
static variant from_string( const string& utf8_str, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH );
static variants variants_from_string( const string& utf8_str, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH );
static string to_string( const variant& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH );
static string to_pretty_string( const variant& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH );
static bool is_valid( const std::string& json_str, parse_type ptype = legacy_parser );
static bool is_valid( const std::string& json_str, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH );
template<typename T>
static void save_to_file( const T& v, const fc::path& fi, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles )
static void save_to_file( const T& v, const fc::path& fi, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH )
{
save_to_file( variant(v), fi, pretty, format );
save_to_file( variant(v), fi, pretty, format, max_depth );
}
static void save_to_file( const variant& v, const fc::path& fi, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles );
static variant from_file( const fc::path& p, parse_type ptype = legacy_parser );
static void save_to_file( const variant& v, const fc::path& fi, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH );
static variant from_file( const fc::path& p, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH );
template<typename T>
static T from_file( const fc::path& p, parse_type ptype = legacy_parser )
static T from_file( const fc::path& p, parse_type ptype = legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH )
{
return json::from_file(p, ptype).as<T>();
return json::from_file(p, ptype, max_depth).as<T>();
}
template<typename T>
static string to_string( const T& v, output_formatting format = stringify_large_ints_and_doubles )
static string to_string( const T& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH )
{
return to_string( variant(v), format );
return to_string( variant(v), format, max_depth );
}
template<typename T>
static string to_pretty_string( const T& v, output_formatting format = stringify_large_ints_and_doubles )
static string to_pretty_string( const T& v, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH )
{
return to_pretty_string( variant(v), format );
return to_pretty_string( variant(v), format, max_depth );
}
template<typename T>
static void save_to_file( const T& v, const std::string& p, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles )
static void save_to_file( const T& v, const std::string& p, bool pretty = true, output_formatting format = stringify_large_ints_and_doubles, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH )
{
save_to_file( variant(v), fc::path(p), pretty );
save_to_file( variant(v), fc::path(p), pretty, format, max_depth );
}
};
} // fc
#undef DEFAULT_MAX_RECURSION_DEPTH

View file

@ -21,7 +21,7 @@
namespace fc { namespace json_relaxed
{
template<typename T, bool strict>
variant variant_from_stream( T& in );
variant variant_from_stream( T& in, uint32_t max_depth );
template<typename T>
fc::string tokenFromStream( T& in )
@ -104,8 +104,15 @@ namespace fc { namespace json_relaxed
if( in.peek() == q )
{
in.get();
try
{
if( in.peek() != q )
return fc::string();
}
catch( const fc::eof_exception& e )
{
return fc::string();
}
// triple quote processing
if( strict )
@ -562,86 +569,18 @@ namespace fc { namespace json_relaxed
} FC_CAPTURE_AND_RETHROW( (token) ) }
template<typename T, bool strict>
variant_object objectFromStream( T& in )
variant_object objectFromStream( T& in, uint32_t max_depth )
{
mutable_variant_object obj;
try
{
char c = in.peek();
if( c != '{' )
FC_THROW_EXCEPTION( parse_error_exception,
"Expected '{', but read '${char}'",
("char",string(&c, &c + 1)) );
in.get();
skip_white_space(in);
while( in.peek() != '}' )
{
if( in.peek() == ',' )
{
in.get();
continue;
}
if( skip_white_space(in) ) continue;
string key = json_relaxed::stringFromStream<T, strict>( in );
skip_white_space(in);
if( in.peek() != ':' )
{
FC_THROW_EXCEPTION( parse_error_exception, "Expected ':' after key \"${key}\"",
("key", key) );
}
in.get();
auto val = json_relaxed::variant_from_stream<T, strict>( in );
obj(std::move(key),std::move(val));
skip_white_space(in);
}
if( in.peek() == '}' )
{
in.get();
return obj;
}
FC_THROW_EXCEPTION( parse_error_exception, "Expected '}' after ${variant}", ("variant", obj ) );
}
catch( const fc::eof_exception& e )
{
FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF: ${e}", ("e", e.to_detail_string() ) );
}
catch( const std::ios_base::failure& e )
{
FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF: ${e}", ("e", e.what() ) );
} FC_RETHROW_EXCEPTIONS( warn, "Error parsing object" );
std::function<std::string(T&)> get_key = []( T& in ){ return json_relaxed::stringFromStream<T, strict>( in ); };
std::function<variant(T&)> get_value = [max_depth]( T& in ){ return json_relaxed::variant_from_stream<T, strict>( in, max_depth ); };
return objectFromStreamBase<T>( in, get_key, get_value );
}
template<typename T, bool strict>
variants arrayFromStream( T& in )
variants arrayFromStream( T& in, uint32_t max_depth )
{
variants ar;
try
{
if( in.peek() != '[' )
FC_THROW_EXCEPTION( parse_error_exception, "Expected '['" );
in.get();
skip_white_space(in);
while( in.peek() != ']' )
{
if( in.peek() == ',' )
{
in.get();
continue;
}
if( skip_white_space(in) ) continue;
ar.push_back( json_relaxed::variant_from_stream<T, strict>(in) );
skip_white_space(in);
}
if( in.peek() != ']' )
FC_THROW_EXCEPTION( parse_error_exception, "Expected ']' after parsing ${variant}",
("variant", ar) );
in.get();
} FC_RETHROW_EXCEPTIONS( warn, "Attempting to parse array ${array}",
("array", ar ) );
return ar;
std::function<variant(T&)> get_value = [max_depth]( T& in ){ return json_relaxed::variant_from_stream<T, strict>( in, max_depth ); };
return arrayFromStreamBase<T>( in, get_value );
}
template<typename T, bool strict>
@ -686,26 +625,20 @@ namespace fc { namespace json_relaxed
}
template<typename T, bool strict>
variant variant_from_stream( T& in )
variant variant_from_stream( T& in, uint32_t max_depth )
{
if( max_depth == 0 )
FC_THROW_EXCEPTION( parse_error_exception, "Too many nested items in JSON input!" );
skip_white_space(in);
variant var;
while( signed char c = in.peek() )
{
signed char c = in.peek();
switch( c )
{
case ' ':
case '\t':
case '\n':
case '\r':
in.get();
continue;
case '"':
return json_relaxed::stringFromStream<T, strict>( in );
case '{':
return json_relaxed::objectFromStream<T, strict>( in );
return json_relaxed::objectFromStream<T, strict>( in, max_depth - 1 );
case '[':
return json_relaxed::arrayFromStream<T, strict>( in );
return json_relaxed::arrayFromStream<T, strict>( in, max_depth - 1 );
case '-':
case '+':
case '.':
@ -734,12 +667,11 @@ namespace fc { namespace json_relaxed
case 0x04: // ^D end of transmission
case EOF:
FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" );
case 0:
default:
FC_THROW_EXCEPTION( parse_error_exception, "Unexpected char '${c}' in \"${s}\"",
("c", c)("s", stringFromToken(in)) );
}
}
return variant();
}
} } // fc::json_relaxed

View file

@ -89,7 +89,7 @@ namespace fc {
template<typename T>
T& smart_ref<T>::operator = ( smart_ref<T>&& u ) {
if( &u == this ) return *impl;
if( impl ) delete impl;
delete impl;
impl = u.impl;
u.impl = nullptr;
return *impl;

View file

@ -162,7 +162,14 @@ namespace fc
for( auto itr = my->_elog.begin(); itr != my->_elog.end(); )
{
ss << itr->get_message() <<"\n"; //fc::format_string( itr->get_format(), itr->get_data() ) <<"\n";
try
{
ss << " " << json::to_string( itr->get_data() )<<"\n";
}
catch( const fc::assert_exception& e )
{
ss << "ERROR: Failed to convert log data to string!\n";
}
ss << " " << itr->get_context().to_string();
++itr;
if( itr != my->_elog.end() ) ss<<"\n";
@ -256,10 +263,16 @@ namespace fc
("source_lineno", lineno)
("expr", expr)
;
try
{
std::cout
<< "FC_ASSERT triggered: "
<< fc::json::to_string( assert_trip_info ) << "\n";
return;
}
catch( const fc::assert_exception& e )
{ // this should never happen. assert_trip_info is flat.
std::cout << "ERROR: Failed to convert info to string?!\n";
}
}
bool enable_record_assert_trip = false;

View file

@ -5,7 +5,7 @@
#include <fc/io/fstream.hpp>
#include <fc/io/sstream.hpp>
#include <fc/log/logger.hpp>
//#include <utfcpp/utf8.h>
#include <cstdint>
#include <iostream>
#include <fstream>
#include <sstream>
@ -15,22 +15,30 @@
namespace fc
{
// forward declarations of provided functions
template<typename T, json::parse_type parser_type> variant variant_from_stream( T& in );
template<typename T, json::parse_type parser_type> variant variant_from_stream( T& in, uint32_t max_depth );
template<typename T> char parseEscape( T& in );
template<typename T> fc::string stringFromStream( T& in );
template<typename T> bool skip_white_space( T& in );
template<typename T> fc::string stringFromToken( T& in );
template<typename T, json::parse_type parser_type> variant_object objectFromStream( T& in );
template<typename T, json::parse_type parser_type> variants arrayFromStream( T& in );
template<typename T> variant_object objectFromStreamBase( T& in, std::function<std::string(T&)>& get_key, std::function<variant(T&)>& get_value );
template<typename T, json::parse_type parser_type> variant_object objectFromStream( T& in, uint32_t max_depth );
template<typename T> variants arrayFromStreamBase( T& in, std::function<variant(T&)>& get_value );
template<typename T, json::parse_type parser_type> variants arrayFromStream( T& in, uint32_t max_depth );
template<typename T, json::parse_type parser_type> variant number_from_stream( T& in );
template<typename T> variant token_from_stream( T& in );
void escape_string( const string& str, ostream& os );
template<typename T> void to_stream( T& os, const variants& a, json::output_formatting format );
template<typename T> void to_stream( T& os, const variant_object& o, json::output_formatting format );
template<typename T> void to_stream( T& os, const variant& v, json::output_formatting format );
template<typename T> void to_stream( T& os, const variants& a, json::output_formatting format, uint32_t max_depth );
template<typename T> void to_stream( T& os, const variant_object& o, json::output_formatting format, uint32_t max_depth );
template<typename T> void to_stream( T& os, const variant& v, json::output_formatting format, uint32_t max_depth );
fc::string pretty_print( const fc::string& v, uint8_t indent );
}
#if __cplusplus > 201402L
#define FALLTHROUGH [[fallthrough]];
#else
#define FALLTHROUGH
#endif
#include <fc/io/json_relaxed.hpp>
namespace fc
@ -167,8 +175,8 @@ namespace fc
("token", token.str() ) );
}
template<typename T, json::parse_type parser_type>
variant_object objectFromStream( T& in )
template<typename T>
variant_object objectFromStreamBase( T& in, std::function<std::string(T&)>& get_key, std::function<variant(T&)>& get_value )
{
mutable_variant_object obj;
try
@ -179,7 +187,6 @@ namespace fc
"Expected '{', but read '${char}'",
("char",string(&c, &c + 1)) );
in.get();
skip_white_space(in);
while( in.peek() != '}' )
{
if( in.peek() == ',' )
@ -188,7 +195,7 @@ namespace fc
continue;
}
if( skip_white_space(in) ) continue;
string key = stringFromStream( in );
string key = get_key( in );
skip_white_space(in);
if( in.peek() != ':' )
{
@ -196,10 +203,9 @@ namespace fc
("key", key) );
}
in.get();
auto val = variant_from_stream<T, parser_type>( in );
auto val = get_value( in );
obj(std::move(key),std::move(val));
skip_white_space(in);
}
if( in.peek() == '}' )
{
@ -219,7 +225,15 @@ namespace fc
}
template<typename T, json::parse_type parser_type>
variants arrayFromStream( T& in )
variant_object objectFromStream( T& in, uint32_t max_depth )
{
std::function<std::string(T&)> get_key = []( T& in ){ return stringFromStream( in ); };
std::function<variant(T&)> get_value = [max_depth]( T& in ){ return variant_from_stream<T, parser_type>( in, max_depth ); };
return objectFromStreamBase<T>( in, get_key, get_value );
}
template<typename T>
variants arrayFromStreamBase( T& in, std::function<variant(T&)>& get_value )
{
variants ar;
try
@ -227,7 +241,6 @@ namespace fc
if( in.peek() != '[' )
FC_THROW_EXCEPTION( parse_error_exception, "Expected '['" );
in.get();
skip_white_space(in);
while( in.peek() != ']' )
{
@ -237,8 +250,7 @@ namespace fc
continue;
}
if( skip_white_space(in) ) continue;
ar.push_back( variant_from_stream<T, parser_type>(in) );
skip_white_space(in);
ar.push_back( get_value(in) );
}
if( in.peek() != ']' )
FC_THROW_EXCEPTION( parse_error_exception, "Expected ']' after parsing ${variant}",
@ -250,6 +262,13 @@ namespace fc
return ar;
}
template<typename T, json::parse_type parser_type>
variants arrayFromStream( T& in, uint32_t max_depth )
{
std::function<variant(T&)> get_value = [max_depth]( T& in ){ return variant_from_stream<T, parser_type>( in, max_depth ); };
return arrayFromStreamBase<T>( in, get_value );
}
template<typename T, json::parse_type parser_type>
variant number_from_stream( T& in )
{
@ -276,6 +295,7 @@ namespace fc
if (dot)
FC_THROW_EXCEPTION(parse_error_exception, "Can't parse a number with two decimal places");
dot = true;
FALLTHROUGH
case '0':
case '1':
case '2':
@ -299,16 +319,20 @@ namespace fc
}
}
catch (fc::eof_exception&)
{
{ // EOF ends the loop
}
catch (const std::ios_base::failure&)
{
{ // read error ends the loop
}
fc::string str = ss.str();
if (str == "-." || str == ".") // check the obviously wrong things we could have encountered
if (str == "-." || str == "." || str == "-") // check the obviously wrong things we could have encountered
FC_THROW_EXCEPTION(parse_error_exception, "Can't parse token \"${token}\" as a JSON numeric constant", ("token", str));
if( dot )
return parser_type == json::legacy_parser_with_string_doubles ? variant(str) : variant(to_double(str));
return
#ifdef WITH_EXOTIC_JSON_PARSERS
parser_type == json::legacy_parser_with_string_doubles ? variant(str) :
#endif
variant(to_double(str));
if( neg )
return to_int64(str);
return to_uint64(str);
@ -379,33 +403,27 @@ namespace fc
// make out ("falfe")
// A strict JSON parser would signal this as an error, but we
// will just treat the malformed token as an un-quoted string.
return str + stringFromToken(in);;
return str + stringFromToken(in);
}
}
}
template<typename T, json::parse_type parser_type>
variant variant_from_stream( T& in )
variant variant_from_stream( T& in, uint32_t max_depth )
{
if( max_depth == 0 )
FC_THROW_EXCEPTION( parse_error_exception, "Too many nested items in JSON input!" );
skip_white_space(in);
variant var;
while( signed char c = in.peek() )
{
signed char c = in.peek();
switch( c )
{
case ' ':
case '\t':
case '\n':
case '\r':
in.get();
continue;
case '"':
return stringFromStream( in );
case '{':
return objectFromStream<T, parser_type>( in );
return objectFromStream<T, parser_type>( in, max_depth - 1 );
case '[':
return arrayFromStream<T, parser_type>( in );
return arrayFromStream<T, parser_type>( in, max_depth - 1 );
case '-':
case '.':
case '0':
@ -426,85 +444,36 @@ namespace fc
return token_from_stream( in );
case 0x04: // ^D end of transmission
case EOF:
case 0:
FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" );
case 0:
if( parser_type == fc::json::broken_nul_parser )
return variant();
FALLTHROUGH
default:
FC_THROW_EXCEPTION( parse_error_exception, "Unexpected char '${c}' in \"${s}\"",
("c", c)("s", stringFromToken(in)) );
}
}
return variant();
}
/** the purpose of this check is to verify that we will not get a stack overflow in the recursive descent parser */
void check_string_depth( const string& utf8_str )
{
int32_t open_object = 0;
int32_t open_array = 0;
for( auto c : utf8_str )
{
switch( c )
{
case '{': open_object++; break;
case '}': open_object--; break;
case '[': open_array++; break;
case ']': open_array--; break;
default: break;
}
FC_ASSERT( open_object < 100 && open_array < 100, "object graph too deep", ("object depth",open_object)("array depth", open_array) );
}
}
variant json::from_string( const std::string& utf8_str, parse_type ptype )
variant json::from_string( const std::string& utf8_str, parse_type ptype, uint32_t max_depth )
{ try {
check_string_depth( utf8_str );
fc::stringstream in( utf8_str );
//in.exceptions( std::ifstream::eofbit );
switch( ptype )
{
case legacy_parser:
return variant_from_stream<fc::stringstream, legacy_parser>( in );
case legacy_parser_with_string_doubles:
return variant_from_stream<fc::stringstream, legacy_parser_with_string_doubles>( in );
case strict_parser:
return json_relaxed::variant_from_stream<fc::stringstream, true>( in );
case relaxed_parser:
return json_relaxed::variant_from_stream<fc::stringstream, false>( in );
default:
FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", ptype) );
}
fc::istream_ptr in( new fc::stringstream( utf8_str ) );
fc::buffered_istream bin( in );
return from_stream( bin, ptype, max_depth );
} FC_RETHROW_EXCEPTIONS( warn, "", ("str",utf8_str) ) }
variants json::variants_from_string( const std::string& utf8_str, parse_type ptype )
{ try {
check_string_depth( utf8_str );
variants json::variants_from_string( const std::string& utf8_str, parse_type ptype, uint32_t max_depth )
{
variants result;
fc::stringstream in( utf8_str );
//in.exceptions( std::ifstream::eofbit );
try {
fc::stringstream in( utf8_str );
while( true )
{
// result.push_back( variant_from_stream( in ));
result.push_back(json_relaxed::variant_from_stream<fc::stringstream, false>( in ));
}
} catch ( const fc::eof_exception& ){}
result.push_back(json_relaxed::variant_from_stream<fc::stringstream, false>( in, max_depth ));
} catch ( const fc::eof_exception& ) {
return result;
} FC_RETHROW_EXCEPTIONS( warn, "", ("str",utf8_str) ) }
/*
void toUTF8( const char str, ostream& os )
{
// validate str == valid utf8
utf8::replace_invalid( &str, &str + 1, ostream_iterator<char>(os) );
} FC_RETHROW_EXCEPTIONS( warn, "", ("str",utf8_str) )
}
void toUTF8( const wchar_t c, ostream& os )
{
utf8::utf16to8( &c, (&c)+1, ostream_iterator<char>(os) );
}
*/
/**
* Convert '\t', '\a', '\n', '\\' and '"' to "\t\a\n\\\""
*
@ -574,7 +543,6 @@ namespace fc
default:
os << *itr;
//toUTF8( *itr, os );
}
}
os << '"';
@ -586,14 +554,14 @@ namespace fc
}
template<typename T>
void to_stream( T& os, const variants& a, json::output_formatting format )
void to_stream( T& os, const variants& a, json::output_formatting format, uint32_t max_depth )
{
os << '[';
auto itr = a.begin();
while( itr != a.end() )
{
to_stream( os, *itr, format );
to_stream( os, *itr, format, max_depth );
++itr;
if( itr != a.end() )
os << ',';
@ -601,7 +569,7 @@ namespace fc
os << ']';
}
template<typename T>
void to_stream( T& os, const variant_object& o, json::output_formatting format )
void to_stream( T& os, const variant_object& o, json::output_formatting format, uint32_t max_depth )
{
os << '{';
auto itr = o.begin();
@ -610,7 +578,7 @@ namespace fc
{
escape_string( itr->key(), os );
os << ':';
to_stream( os, itr->value(), format );
to_stream( os, itr->value(), format, max_depth );
++itr;
if( itr != o.end() )
os << ',';
@ -619,35 +587,28 @@ namespace fc
}
template<typename T>
void to_stream( T& os, const variant& v, json::output_formatting format )
void to_stream( T& os, const variant& v, json::output_formatting format, uint32_t max_depth )
{
FC_ASSERT( max_depth > 0, "Too many nested objects!" );
switch( v.get_type() )
{
case variant::null_type:
os << "null";
return;
case variant::int64_type:
{
int64_t i = v.as_int64();
if( format == json::stringify_large_ints_and_doubles &&
i > 0xffffffff )
( v.as_int64() > INT32_MAX || v.as_int64() < INT32_MIN ) )
os << '"'<<v.as_string()<<'"';
else
os << i;
os << v.as_int64();
return;
}
case variant::uint64_type:
{
uint64_t i = v.as_uint64();
if( format == json::stringify_large_ints_and_doubles &&
i > 0xffffffff )
v.as_uint64() > 0xffffffff )
os << '"'<<v.as_string()<<'"';
else
os << i;
os << v.as_uint64();
return;
}
case variant::double_type:
if (format == json::stringify_large_ints_and_doubles)
os << '"'<<v.as_string()<<'"';
@ -664,24 +625,20 @@ namespace fc
escape_string( v.as_string(), os );
return;
case variant::array_type:
{
const variants& a = v.get_array();
to_stream( os, a, format );
to_stream( os, v.get_array(), format, max_depth - 1 );
return;
}
case variant::object_type:
{
const variant_object& o = v.get_object();
to_stream(os, o, format );
to_stream(os, v.get_object(), format, max_depth - 1 );
return;
}
default:
FC_THROW_EXCEPTION( fc::invalid_arg_exception, "Unsupported variant type: " + v.get_type() );
}
}
fc::string json::to_string( const variant& v, output_formatting format /* = stringify_large_ints_and_doubles */ )
fc::string json::to_string( const variant& v, output_formatting format, uint32_t max_depth )
{
fc::stringstream ss;
fc::to_stream( ss, v, format );
fc::to_stream( ss, v, format, max_depth );
return ss.str();
}
@ -761,7 +718,7 @@ namespace fc
//If we're in quotes and see a \n, just print it literally but unset the escape flag.
if( quote && escape )
escape = false;
//No break; fall through to default case
FALLTHROUGH
default:
if( first ) {
ss<<'\n';
@ -776,100 +733,75 @@ namespace fc
fc::string json::to_pretty_string( const variant& v, output_formatting format /* = stringify_large_ints_and_doubles */ )
fc::string json::to_pretty_string( const variant& v, output_formatting format, uint32_t max_depth )
{
return pretty_print(to_string(v, format), 2);
return pretty_print(to_string(v, format, max_depth), 2);
}
void json::save_to_file( const variant& v, const fc::path& fi, bool pretty, output_formatting format /* = stringify_large_ints_and_doubles */ )
void json::save_to_file( const variant& v, const fc::path& fi, bool pretty, output_formatting format, uint32_t max_depth )
{
if( pretty )
{
auto str = json::to_pretty_string( v, format );
auto str = json::to_pretty_string( v, format, max_depth );
fc::ofstream o(fi);
o.write( str.c_str(), str.size() );
}
else
{
fc::ofstream o(fi);
fc::to_stream( o, v, format );
fc::to_stream( o, v, format, max_depth );
}
}
variant json::from_file( const fc::path& p, parse_type ptype )
variant json::from_file( const fc::path& p, parse_type ptype, uint32_t max_depth )
{
//auto tmp = std::make_shared<fc::ifstream>( p, ifstream::binary );
//auto tmp = std::make_shared<std::ifstream>( p.generic_string().c_str(), std::ios::binary );
//buffered_istream bi( tmp );
boost::filesystem::ifstream bi( p, std::ios::binary );
switch( ptype )
{
case legacy_parser:
return variant_from_stream<boost::filesystem::ifstream, legacy_parser>( bi );
case legacy_parser_with_string_doubles:
return variant_from_stream<boost::filesystem::ifstream, legacy_parser_with_string_doubles>( bi );
case strict_parser:
return json_relaxed::variant_from_stream<boost::filesystem::ifstream, true>( bi );
case relaxed_parser:
return json_relaxed::variant_from_stream<boost::filesystem::ifstream, false>( bi );
default:
FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", ptype) );
fc::istream_ptr in( new fc::ifstream( p ) );
fc::buffered_istream bin( in );
return from_stream( bin, ptype, max_depth );
}
}
variant json::from_stream( buffered_istream& in, parse_type ptype )
variant json::from_stream( buffered_istream& in, parse_type ptype, uint32_t max_depth )
{
switch( ptype )
{
case legacy_parser:
return variant_from_stream<fc::buffered_istream, legacy_parser>( in );
return variant_from_stream<fc::buffered_istream, legacy_parser>( in, max_depth );
#ifdef WITH_EXOTIC_JSON_PARSERS
case legacy_parser_with_string_doubles:
return variant_from_stream<fc::buffered_istream, legacy_parser_with_string_doubles>( in );
return variant_from_stream<fc::buffered_istream, legacy_parser_with_string_doubles>( in, max_depth );
case strict_parser:
return json_relaxed::variant_from_stream<buffered_istream, true>( in );
return json_relaxed::variant_from_stream<buffered_istream, true>( in, max_depth );
case relaxed_parser:
return json_relaxed::variant_from_stream<buffered_istream, false>( in );
return json_relaxed::variant_from_stream<buffered_istream, false>( in, max_depth );
#endif
case broken_nul_parser:
return variant_from_stream<fc::buffered_istream, broken_nul_parser>( in, max_depth );
default:
FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", ptype) );
}
}
ostream& json::to_stream( ostream& out, const variant& v, output_formatting format /* = stringify_large_ints_and_doubles */ )
ostream& json::to_stream( ostream& out, const variant& v, output_formatting format, uint32_t max_depth )
{
fc::to_stream( out, v, format );
fc::to_stream( out, v, format, max_depth );
return out;
}
ostream& json::to_stream( ostream& out, const variants& v, output_formatting format /* = stringify_large_ints_and_doubles */ )
ostream& json::to_stream( ostream& out, const variants& v, output_formatting format, uint32_t max_depth )
{
fc::to_stream( out, v, format );
fc::to_stream( out, v, format, max_depth );
return out;
}
ostream& json::to_stream( ostream& out, const variant_object& v, output_formatting format /* = stringify_large_ints_and_doubles */ )
ostream& json::to_stream( ostream& out, const variant_object& v, output_formatting format, uint32_t max_depth )
{
fc::to_stream( out, v, format );
fc::to_stream( out, v, format, max_depth );
return out;
}
bool json::is_valid( const std::string& utf8_str, parse_type ptype )
bool json::is_valid( const std::string& utf8_str, parse_type ptype, uint32_t max_depth )
{
if( utf8_str.size() == 0 ) return false;
fc::stringstream in( utf8_str );
switch( ptype )
{
case legacy_parser:
variant_from_stream<fc::stringstream, legacy_parser>( in );
break;
case legacy_parser_with_string_doubles:
variant_from_stream<fc::stringstream, legacy_parser_with_string_doubles>( in );
break;
case strict_parser:
json_relaxed::variant_from_stream<fc::stringstream, true>( in );
break;
case relaxed_parser:
json_relaxed::variant_from_stream<fc::stringstream, false>( in );
break;
default:
FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", ptype) );
}
try { in.peek(); } catch ( const eof_exception& e ) { return true; }
fc::istream_ptr in( new fc::stringstream( utf8_str ) );
fc::buffered_istream bin( in );
from_stream( bin, ptype, max_depth );
try { bin.peek(); } catch ( const eof_exception& e ) { return true; }
return false;
}

View file

@ -128,8 +128,15 @@ namespace fc
if (!context.get_task_name().empty())
gelf_message["_task_name"] = context.get_task_name();
string gelf_message_as_string = json::to_string(gelf_message);
//unsigned uncompressed_size = gelf_message_as_string.size();
string gelf_message_as_string;
try
{
gelf_message_as_string = json::to_string(gelf_message);
}
catch( const fc::assert_exception& e )
{
gelf_message_as_string = "{\"level\":3,\"short_message\":\"ERROR while generating log message\"}";
}
gelf_message_as_string = zlib_compress(gelf_message_as_string);
// graylog2 expects the zlib header to be 0x78 0x9c

View file

@ -2,6 +2,8 @@
#include <fc/utility.hpp>
#include <fc/fwd_impl.hpp>
#include <fc/exception/exception.hpp>
#include <fc/io/json.hpp>
#include <fc/io/sstream.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
@ -256,6 +258,60 @@ namespace fc {
}
}
string format_string( const string& format, const variant_object& args )
{
stringstream ss;
size_t prev = 0;
auto next = format.find( '$' );
while( prev < format.size() )
{
ss << format.substr( prev, next == string::npos ? string::npos : next - prev );
// if we got to the end, return it.
if( next == size_t(string::npos) || next == format.size() )
return ss.str();
// if we are not at the end, then update the start
prev = next + 1;
if( format[prev] == '{' )
{
// if the next char is a open, then find close
next = format.find( '}', prev );
// if we found close...
if( next != string::npos )
{
// the key is between prev and next
string key = format.substr( prev+1, (next-prev-1) );
auto val = args.find( key );
if( val != args.end() )
{
if( val->value().is_object() || val->value().is_array() )
{
try
{
ss << json::to_string( val->value() );
}
catch( const fc::assert_exception& e )
{
ss << "[\"ERROR_WHILE_CONVERTING_VALUE_TO_STRING\"]";
}
}
else
ss << val->value().as_string();
}
else
ss << "${"<<key<<"}";
prev = next + 1;
}
}
else
ss << format[next];
next = format.find( '$', prev );
}
return ss.str();
}
} // namespace fc

View file

@ -669,66 +669,6 @@ void from_variant( const variant& var, std::vector<char>& vo )
// vo = std::vector<char>( b64.c_str(), b64.c_str() + b64.size() );
}
string format_string( const string& format, const variant_object& args )
{
stringstream ss;
size_t prev = 0;
auto next = format.find( '$' );
while( prev != size_t(string::npos) && prev < size_t(format.size()) )
{
ss << format.substr( prev, size_t(next-prev) );
// if we got to the end, return it.
if( next == size_t(string::npos) )
return ss.str();
// if we are not at the end, then update the start
prev = next + 1;
if( format[prev] == '{' )
{
// if the next char is a open, then find close
next = format.find( '}', prev );
// if we found close...
if( next != size_t(string::npos) )
{
// the key is between prev and next
string key = format.substr( prev+1, (next-prev-1) );
auto val = args.find( key );
if( val != args.end() )
{
if( val->value().is_object() || val->value().is_array() )
{
ss << json::to_string( val->value() );
}
else
{
ss << val->value().as_string();
}
}
else
{
ss << "${"<<key<<"}";
}
prev = next + 1;
// find the next $
next = format.find( '$', prev );
}
else
{
// we didn't find it.. continue to while...
}
}
else
{
ss << format[prev];
++prev;
next = format.find( '$', prev );
}
}
return ss.str();
}
#ifdef __APPLE__
#elif !defined(_MSC_VER)
void to_variant( long long int s, variant& v ) { v = variant( int64_t(s) ); }

View file

@ -46,6 +46,8 @@ add_executable( all_tests all_tests.cpp
crypto/dh_test.cpp
crypto/rand_test.cpp
crypto/sha_tests.cpp
io/json_tests.cpp
io/stream_tests.cpp
network/http/websocket_test.cpp
thread/task_cancel.cpp
thread/thread_tests.cpp

376
tests/io/json_tests.cpp Normal file
View file

@ -0,0 +1,376 @@
#include <boost/test/unit_test.hpp>
#include <fc/variant.hpp>
#include <fc/variant_object.hpp>
#include <fc/exception/exception.hpp>
#include <fc/io/buffered_iostream.hpp>
#include <fc/io/fstream.hpp>
#include <fc/io/iostream.hpp>
#include <fc/io/json.hpp>
#include <fc/io/sstream.hpp>
#include <fstream>
BOOST_AUTO_TEST_SUITE(json_tests)
static void replace_some( std::string& str )
{
for( size_t i = 0; i < str.length(); i++ )
if( str[i] == '\1' ) str[i] = '\0';
else if( str[i] == '\'' ) str[i] = '"';
}
static void test_fail_string( const std::string& str )
{
try {
fc::json::from_string( str );
BOOST_FAIL( "json::from_string('" + str + "') failed" );
} catch( const fc::parse_error_exception& ) { // ignore, ok
} catch( const fc::eof_exception& ) { // ignore, ok
} FC_CAPTURE_LOG_AND_RETHROW( ("json::from_string failed")(str) )
}
static void test_fail_stream( const std::string& str )
{
fc::temp_file file( fc::temp_directory_path(), true );
{
std::fstream init( file.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc );
init.write( str.c_str(), str.length() );
init.close();
}
try {
fc::istream_ptr in( new fc::ifstream( file.path() ) );
fc::buffered_istream bin( in );
fc::json::from_stream( bin );
BOOST_FAIL( "json::from_stream('" + str + "') failed using ifstream" );
} catch( const fc::parse_error_exception& ) { // ignore, ok
} catch( const fc::eof_exception& ) { // ignore, ok
} FC_CAPTURE_LOG_AND_RETHROW( ("json::from_stream failed using ifstream")(str) )
try {
fc::istream_ptr in( new fc::stringstream( str ) );
fc::buffered_istream bin( in );
fc::json::from_stream( bin );
BOOST_FAIL( "json::from_stream('" + str + "') failed using stringstream" );
} catch( const fc::parse_error_exception& ) { // ignore, ok
} catch( const fc::eof_exception& ) { // ignore, ok
} FC_CAPTURE_LOG_AND_RETHROW( ("json::from_stream failed using stringstream")(str) )
}
static void test_fail_file( const std::string& str )
{
fc::temp_file file( fc::temp_directory_path(), true );
{
std::fstream init( file.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc );
init.write( str.c_str(), str.length() );
init.close();
}
try {
fc::json::from_file( file.path() );
BOOST_FAIL( "json::from_file('" + str + "') failed using" );
} catch( const fc::parse_error_exception& ) { // ignore, ok
} catch( const fc::eof_exception& ) { // ignore, ok
} FC_CAPTURE_LOG_AND_RETHROW( ("json::from_file failed")(str) )
}
BOOST_AUTO_TEST_CASE(imbalanced_test)
{
std::string open40("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[");
std::string close40("]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]");
std::string open80 = open40 + open40;
std::string close80 = close40 + close40;
std::vector<std::string> tests
{ // for easier handling and better readability, in the following test
// strings ' is used instead of " and \1 instead of \0
"",
"{",
"{'",
"{'}",
"{'a'",
"{'a':",
"{'a':5",
"[",
"['",
"[']",
"[ 13",
"' end",
"{ 13: }",
"\1",
"{\1",
"{\1}",
"{'\1",
"{'\1}",
"{'a'\1",
"{'a'\1}",
"{'a': \1",
"{'a': \1}",
"[\1",
"[\1]",
"['\1",
"['\1]",
"[ 13\1",
"[ 13\1]",
"' end\1",
open80 + "'" + close80 + close80 + "'," + open80 + open80
+ close80 + close80 + close80
};
for( std::string test : tests )
{
replace_some( test );
test_fail_string( test );
test_fail_stream( test );
test_fail_file( test );
}
}
static bool equal( const fc::variant& a, const fc::variant& b )
{
auto a_type = a.get_type();
auto b_type = b.get_type();
if( a_type == fc::variant::type_id::int64_type && a.as<int64_t>() > 0 )
a_type = fc::variant::type_id::uint64_type;
if( b_type == fc::variant::type_id::int64_type && b.as<int64_t>() > 0 )
b_type = fc::variant::type_id::uint64_type;
if( a_type != b_type )
{
if( ( a_type == fc::variant::type_id::double_type
&& b_type == fc::variant::type_id::string_type )
|| ( a_type == fc::variant::type_id::string_type
&& b_type == fc::variant::type_id::double_type ) )
return a.as<double>() == b.as<double>();
return false;
}
switch( a_type )
{
case fc::variant::type_id::null_type: return true;
case fc::variant::type_id::int64_type: return a.as<int64_t>() == b.as<int64_t>();
case fc::variant::type_id::uint64_type: return a.as<uint64_t>() == b.as<uint64_t>();
case fc::variant::type_id::double_type: return a.as<double>() == b.as<double>();
case fc::variant::type_id::bool_type: return a.as<bool>() == b.as<bool>();
case fc::variant::type_id::string_type: return a.as<std::string>() == b.as<std::string>();
case fc::variant::type_id::array_type:
if( a.get_array().size() != b.get_array().size() ) return false;
else
{
std::vector<fc::variant>::const_iterator b_it = b.get_array().begin();
for( const auto& a_it : a.get_array() )
{
if( !equal( a_it, *b_it ) ) return false;
b_it++;
}
}
return true;
case fc::variant::type_id::object_type:
if( a.get_object().size() != b.get_object().size() ) return false;
for( const auto& a_it : a.get_object() )
{
const auto& b_obj = b.get_object().find( a_it.key() );
if( b_obj == b.get_object().end() || !equal( a_it.value(), b_obj->value() ) ) return false;
}
return true;
case fc::variant::type_id::blob_type:
default:
FC_THROW_EXCEPTION( fc::invalid_arg_exception, "Unsupported variant type: " + a.get_type() );
}
}
static void test_recursive( const fc::variant& v )
{ try {
const std::string json = fc::json::to_string( v );
BOOST_CHECK( fc::json::is_valid( json ) );
BOOST_CHECK( !fc::json::is_valid( json + " " ) );
const std::string pretty = fc::json::to_pretty_string( v );
BOOST_CHECK( fc::json::is_valid( pretty ) );
BOOST_CHECK( !fc::json::is_valid( pretty + " " ) );
fc::temp_file file( fc::temp_directory_path(), true );
fc::json::save_to_file( v, file.path(), false );
fc::variants list = fc::json::variants_from_string( json );
BOOST_CHECK_EQUAL( 1, list.size() );
BOOST_CHECK( equal( v, list[0] ) );
list = fc::json::variants_from_string( pretty );
BOOST_CHECK_EQUAL( 1, list.size() );
BOOST_CHECK( equal( v, list[0] ) );
BOOST_CHECK( equal( v, fc::json::from_string( json + " " ) ) );
BOOST_CHECK( equal( v, fc::json::from_string( pretty + " " ) ) );
BOOST_CHECK( equal( v, fc::json::from_file( file.path() ) ) );
if( v.get_type() == fc::variant::type_id::array_type )
for( const auto& item : v.get_array() )
test_recursive( item );
else if( v.get_type() == fc::variant::type_id::object_type )
for( const auto& item : v.get_object() )
test_recursive( item.value() );
} FC_CAPTURE_LOG_AND_RETHROW( (v) ) }
BOOST_AUTO_TEST_CASE(structured_test)
{
fc::variant_object v_empty_obj;
fc::variants v_empty_array;
fc::variant v_null;
fc::variant v_true( true );
fc::variant v_false( false );
fc::variant v_empty_str( "" );
fc::variant v_str( "false" );
fc::variant v_int8_1( (int8_t) 1 );
fc::variant v_int8_2( (int8_t) -2 );
fc::variant v_uint8_1( (int8_t) 1 );
fc::variant v_int16_1( (int16_t) 1 );
fc::variant v_int16_2( (int16_t) -2 );
fc::variant v_uint16_1( (int16_t) 1 );
fc::variant v_int32_1( (int32_t) 1 );
fc::variant v_int32_2( (int32_t) -2 );
fc::variant v_uint32_1( (int32_t) 1 );
fc::variant v_int64_1( (int8_t) 1 );
fc::variant v_int64_2( (int8_t) -2 );
fc::variant v_uint64_1( (int8_t) 1 );
fc::variant v_float_1( 0.0f );
fc::variant v_float_2( -2.0f );
fc::variant v_double_1( 0.0d );
fc::variant v_double_2( -2.0d );
fc::variants v_small_array
{
v_empty_obj,
v_empty_array,
v_null,
v_true,
v_false,
v_empty_str
};
fc::mutable_variant_object v_small_obj;
v_small_obj( "", v_empty_str )
( "1", v_empty_array )
( "2", v_null )
( "a", v_true )
( "b", v_false )
( "x", v_small_array )
( "y", v_empty_obj );
fc::variants v_big_array
{
v_empty_obj,
v_empty_array,
v_null,
v_true,
v_false,
v_empty_str,
v_str,
v_int8_1,
v_int8_2,
v_uint8_1,
v_int16_1,
v_int16_2,
v_uint16_1,
v_int32_1,
v_int32_2,
v_uint32_1,
v_int64_1,
v_int64_2,
v_uint64_1,
v_float_1,
v_float_2,
v_double_1,
v_double_2,
v_small_array,
v_small_obj
};
fc::mutable_variant_object v_big_obj;
v_big_obj( "v_empty_obj", v_empty_obj )
( "v_empty_array", v_empty_array )
( "v_null", v_null )
( "v_true", v_true )
( "v_false", v_false )
( "v_empty_str", v_empty_str )
( "v_str", v_str )
( "v_int8_1", v_int8_1 )
( "v_int8_2", v_int8_2 )
( "v_uint8_1", v_uint8_1 )
( "v_int16_1", v_int16_1 )
( "v_int16_2", v_int16_2 )
( "v_uint16_1", v_uint16_1 )
( "v_int32_1", v_int32_1 )
( "v_int32_2", v_int32_2 )
( "v_uint32_1", v_uint32_1 )
( "v_int64_1", v_int64_1 )
( "v_int64_2", v_int64_2 )
( "v_uint64_1", v_uint64_1 )
( "v_float_1", v_float_1 )
( "v_float_2", v_float_2 )
( "v_double_1", v_double_1 )
( "v_double_2", v_double_2 )
( "v_small_array", v_small_array )
( "v_small_obj", v_small_obj );
v_big_array.push_back( v_big_obj );
test_recursive( v_big_array );
}
BOOST_AUTO_TEST_CASE(precision_test)
{
BOOST_CHECK_EQUAL( "\"4294967296\"", fc::json::to_string( fc::variant( 0x100000000LL ) ) );
BOOST_CHECK_EQUAL( "\"-4294967296\"", fc::json::to_string( fc::variant( -0x100000000LL ) ) );
std::string half = fc::json::to_string( fc::variant( 0.5 ) );
BOOST_CHECK_EQUAL( '"', half.front() );
BOOST_CHECK_EQUAL( '"', half.back() );
half = half.substr( 1, half.length() - 2 );
while( '0' == half.back() ) half.erase( half.length() - 1, 1 );
BOOST_CHECK_EQUAL( "0.5", half );
}
BOOST_AUTO_TEST_CASE(recursion_test)
{
std::string ten_levels = "[[[[[[[[[[]]]]]]]]]]";
fc::variant nested = fc::json::from_string( ten_levels );
BOOST_CHECK_THROW( fc::json::from_string( ten_levels, fc::json::legacy_parser, 9 ), fc::parse_error_exception );
std::string back = fc::json::to_string( nested );
BOOST_CHECK_EQUAL( ten_levels, back );
BOOST_CHECK_THROW( fc::json::to_string( nested, fc::json::stringify_large_ints_and_doubles, 9 ), fc::assert_exception );
}
BOOST_AUTO_TEST_CASE(rethrow_test)
{
fc::variants biggie;
for( int i = 0; i < 250; i++ )
{
fc::variant tmp( std::move(biggie) );
biggie.reserve(1);
biggie.push_back( std::move(tmp) );
}
auto test_r = [&biggie](){
try {
FC_THROW_EXCEPTION( fc::unknown_host_exception, "WTF?" );
} FC_RETHROW_EXCEPTIONS( warn, "Argh! ${biggie}", ("biggie",biggie) ) };
BOOST_CHECK_THROW( test_r(), fc::unknown_host_exception );
auto test_lr = [&biggie](){
try {
FC_THROW_EXCEPTION( fc::unknown_host_exception, "WTF?" );
} FC_LOG_AND_RETHROW() };
BOOST_CHECK_THROW( test_lr(), fc::unknown_host_exception );
auto test_clr = [&biggie](){
try {
FC_THROW_EXCEPTION( fc::unknown_host_exception, "WTF?" );
} FC_CAPTURE_LOG_AND_RETHROW( (biggie) ) };
BOOST_CHECK_THROW( test_clr(), fc::unknown_host_exception );
auto test_cl = [&biggie](){
try {
FC_THROW_EXCEPTION( fc::unknown_host_exception, "WTF?" );
} FC_CAPTURE_AND_LOG( (biggie) ) };
test_cl();
auto test_cr = [&biggie](){
try {
FC_THROW_EXCEPTION( fc::unknown_host_exception, "WTF?" );
} FC_CAPTURE_AND_RETHROW( (biggie) ) };
BOOST_CHECK_THROW( test_cr(), fc::unknown_host_exception );
}
BOOST_AUTO_TEST_SUITE_END()

178
tests/io/stream_tests.cpp Normal file
View file

@ -0,0 +1,178 @@
#include <boost/test/unit_test.hpp>
#include <fc/filesystem.hpp>
#include <fc/exception/exception.hpp>
#include <fc/io/buffered_iostream.hpp>
#include <fc/io/fstream.hpp>
#include <fc/io/sstream.hpp>
#include <fstream>
BOOST_AUTO_TEST_SUITE(stream_tests)
BOOST_AUTO_TEST_CASE(stringstream_test)
{
const fc::string constant( "Hello", 6 ); // includes trailing \0
fc::string writable( "World" );
fc::stringstream in1( constant );
fc::stringstream in2( writable );
fc::stringstream out;
std::shared_ptr<char> buf( new char[15], [](char* p){ delete[] p; } );
*buf = 'w';
in2.writesome( buf, 1, 0 );
BOOST_CHECK_EQUAL( 3, in1.readsome( buf, 3, 0 ) );
BOOST_CHECK_EQUAL( 3, out.writesome( buf, 3, 0 ) );
BOOST_CHECK_EQUAL( 'l', in1.peek() );
BOOST_CHECK_EQUAL( 3, in1.readsome( buf, 4, 0 ) );
BOOST_CHECK_EQUAL( '\0', (&(*buf))[2] );
BOOST_CHECK_EQUAL( 2, out.writesome( buf, 2, 0 ) );
*buf = ' ';
out.writesome( buf, 1, 0 );
BOOST_CHECK_THROW( in1.readsome( buf, 3, 0 ), fc::eof_exception );
BOOST_CHECK_EQUAL( 5, in2.readsome( buf, 6, 0 ) );
BOOST_CHECK_EQUAL( 5, out.writesome( buf, 5, 0 ) );
BOOST_CHECK_THROW( in2.readsome( buf, 3, 0 ), fc::eof_exception );
BOOST_CHECK_EQUAL( "Hello world", out.str() );
BOOST_CHECK_THROW( in1.peek(), fc::eof_exception );
BOOST_CHECK( in1.eof() );
BOOST_CHECK_THROW( in2.readsome( buf, 3, 0 ), fc::eof_exception );
// BOOST_CHECK( in2.eof() ); // fails, apparently readsome doesn't set eof
}
BOOST_AUTO_TEST_CASE(buffered_stringstream_test)
{
const fc::string constant( "Hello", 6 ); // includes trailing \0
fc::string writable( "World" );
fc::istream_ptr in1( new fc::stringstream( constant ) );
std::shared_ptr<fc::stringstream> in2( new fc::stringstream( writable ) );
std::shared_ptr<fc::stringstream> out1( new fc::stringstream() );
fc::buffered_istream bin1( in1 );
fc::buffered_istream bin2( in2 );
fc::buffered_ostream bout( out1 );
std::shared_ptr<char> buf( new char[15], [](char* p){ delete[] p; } );
*buf = 'w';
in2->writesome( buf, 1, 0 );
BOOST_CHECK_EQUAL( 3, bin1.readsome( buf, 3, 0 ) );
BOOST_CHECK_EQUAL( 3, bout.writesome( buf, 3, 0 ) );
BOOST_CHECK_EQUAL( 'l', bin1.peek() );
BOOST_CHECK_EQUAL( 3, bin1.readsome( buf, 4, 0 ) );
BOOST_CHECK_EQUAL( '\0', (&(*buf))[2] );
BOOST_CHECK_EQUAL( 2, bout.writesome( buf, 2, 0 ) );
*buf = ' ';
bout.writesome( buf, 1, 0 );
BOOST_CHECK_THROW( bin1.readsome( buf, 3, 0 ), fc::eof_exception );
BOOST_CHECK_EQUAL( 5, bin2.readsome( buf, 6, 0 ) );
BOOST_CHECK_EQUAL( 5, bout.writesome( buf, 5, 0 ) );
BOOST_CHECK_THROW( bin2.readsome( buf, 3, 0 ), fc::eof_exception );
bout.flush();
BOOST_CHECK_EQUAL( "Hello world", out1->str() );
}
BOOST_AUTO_TEST_CASE(fstream_test)
{
fc::temp_file inf1( fc::temp_directory_path(), true );
fc::temp_file inf2( fc::temp_directory_path(), true );
fc::temp_file outf( fc::temp_directory_path(), true );
{
std::fstream init( inf1.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc );
init.write( "Hello", 6 ); // includes trailing \0
init.close();
init.open( inf2.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc );
init.write( "world", 5 );
init.close();
init.open( outf.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc );
init.close();
}
fc::ifstream in1( inf1.path() );
fc::ifstream in2( inf2.path() );
fc::ofstream out( outf.path() );
std::shared_ptr<char> buf( new char[15], [](char* p){ delete[] p; } );
BOOST_CHECK_EQUAL( 3, in1.readsome( buf, 3, 0 ) );
BOOST_CHECK_EQUAL( 3, out.writesome( buf, 3, 0 ) );
BOOST_CHECK_EQUAL( 3, in1.readsome( buf, 4, 0 ) );
BOOST_CHECK_EQUAL( '\0', (&(*buf))[2] );
BOOST_CHECK_EQUAL( 2, out.writesome( buf, 2, 0 ) );
*buf = ' ';
out.writesome( buf, 1, 0 );
BOOST_CHECK_THROW( in1.readsome( buf, 3, 0 ), fc::eof_exception );
BOOST_CHECK_EQUAL( 5, in2.readsome( buf, 6, 0 ) );
BOOST_CHECK_EQUAL( 5, out.writesome( buf, 5, 0 ) );
BOOST_CHECK_THROW( in2.readsome( buf, 3, 0 ), fc::eof_exception );
{
out.flush();
std::fstream test( outf.path().to_native_ansi_path(), std::fstream::in );
BOOST_CHECK_EQUAL( 11, test.readsome( (&(*buf)), 11 ) );
BOOST_CHECK_EQUAL( "Hello world", std::string( (&(*buf)), 11 ) );
BOOST_CHECK_EQUAL( 0, test.readsome( (&(*buf)), 11 ) );
test.close();
}
BOOST_CHECK( in1.eof() );
BOOST_CHECK( in2.eof() );
}
BOOST_AUTO_TEST_CASE(buffered_fstream_test)
{
fc::temp_file inf1( fc::temp_directory_path(), true );
fc::temp_file inf2( fc::temp_directory_path(), true );
fc::temp_file outf( fc::temp_directory_path(), true );
{
std::fstream init( inf1.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc );
init.write( "Hello", 6 ); // includes trailing \0
init.close();
init.open( inf2.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc );
init.write( "world", 5 );
init.close();
init.open( outf.path().to_native_ansi_path(), std::fstream::out | std::fstream::trunc );
init.close();
}
fc::istream_ptr in1( new fc::ifstream( inf1.path() ) );
fc::istream_ptr in2( new fc::ifstream( inf2.path() ) );
fc::ostream_ptr out( new fc::ofstream( outf.path() ) );
fc::buffered_istream bin1( in1 );
fc::buffered_istream bin2( in2 );
fc::buffered_ostream bout( out );
std::shared_ptr<char> buf( new char[15], [](char* p){ delete[] p; } );
BOOST_CHECK_EQUAL( 3, bin1.readsome( buf, 3, 0 ) );
BOOST_CHECK_EQUAL( 3, bout.writesome( buf, 3, 0 ) );
BOOST_CHECK_EQUAL( 'l', bin1.peek() );
BOOST_CHECK_EQUAL( 3, bin1.readsome( buf, 4, 0 ) );
BOOST_CHECK_EQUAL( '\0', (&(*buf))[2] );
BOOST_CHECK_EQUAL( 2, bout.writesome( buf, 2, 0 ) );
*buf = ' ';
bout.writesome( buf, 1, 0 );
BOOST_CHECK_THROW( bin1.readsome( buf, 3, 0 ), fc::eof_exception );
BOOST_CHECK_EQUAL( 5, bin2.readsome( buf, 6, 0 ) );
BOOST_CHECK_EQUAL( 5, bout.writesome( buf, 5, 0 ) );
BOOST_CHECK_THROW( bin2.readsome( buf, 3, 0 ), fc::eof_exception );
{
bout.flush();
std::fstream test( outf.path().to_native_ansi_path(), std::fstream::in );
BOOST_CHECK_EQUAL( 11, test.readsome( (&(*buf)), 11 ) );
BOOST_CHECK_EQUAL( "Hello world", std::string( (&(*buf)), 11 ) );
BOOST_CHECK_EQUAL( 0, test.readsome( (&(*buf)), 11 ) );
test.close();
}
}
BOOST_AUTO_TEST_SUITE_END()