Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
googletest
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Chen Yisong
googletest
Commits
0bf8ea30
Commit
0bf8ea30
authored
Mar 03, 2020
by
Abseil Team
Committed by
vslashg
Mar 17, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Googletest export
Simplify the fallback printing logic to have a single sequence of trial printers. PiperOrigin-RevId: 298621376
parent
3de76551
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
160 additions
and
259 deletions
+160
-259
gtest-printers.h
googletest/include/gtest/gtest-printers.h
+159
-254
gtest-printers.cc
googletest/src/gtest-printers.cc
+1
-5
No files found.
googletest/include/gtest/gtest-printers.h
View file @
0bf8ea30
...
@@ -119,53 +119,126 @@
...
@@ -119,53 +119,126 @@
namespace
testing
{
namespace
testing
{
// Definitions in the
'internal' and 'internal2' name spaces are
// Definitions in the
internal* namespaces are subject to change without notice.
//
subject to change without notice.
DO NOT USE THEM IN USER CODE!
// DO NOT USE THEM IN USER CODE!
namespace
internal
2
{
namespace
internal
{
// Prints the given number of bytes in the given object to the given
template
<
typename
T
>
// ostream.
void
UniversalPrint
(
const
T
&
value
,
::
std
::
ostream
*
os
);
GTEST_API_
void
PrintBytesInObjectTo
(
const
unsigned
char
*
obj_bytes
,
size_t
count
,
::
std
::
ostream
*
os
);
// For selecting which printer to use when a given type has neither <<
// Used to print an STL-style container when the user doesn't define
// nor PrintTo().
// a PrintTo() for it.
enum
TypeKind
{
struct
ContainerPrinter
{
kProtobuf
,
// a protobuf type
template
<
typename
T
,
kConvertibleToInteger
,
// a type implicitly convertible to BiggestInt
typename
=
typename
std
::
enable_if
<
// (e.g. a named or unnamed enum type)
(
sizeof
(
IsContainerTest
<
T
>
(
0
))
==
sizeof
(
IsContainer
))
&&
#if GTEST_INTERNAL_HAS_STRING_VIEW
!
IsRecursiveContainer
<
T
>::
value
>::
type
>
kConvertibleToStringView
,
// a type implicitly convertible to
static
void
PrintValue
(
const
T
&
container
,
std
::
ostream
*
os
)
{
// absl::string_view or std::string_view
const
size_t
kMaxCount
=
32
;
// The maximum number of elements to print.
#endif
*
os
<<
'{'
;
kOtherType
// anything else
size_t
count
=
0
;
for
(
auto
&&
elem
:
container
)
{
if
(
count
>
0
)
{
*
os
<<
','
;
if
(
count
==
kMaxCount
)
{
// Enough has been printed.
*
os
<<
" ..."
;
break
;
}
}
*
os
<<
' '
;
// We cannot call PrintTo(elem, os) here as PrintTo() doesn't
// handle `elem` being a native array.
internal
::
UniversalPrint
(
elem
,
os
);
++
count
;
}
if
(
count
>
0
)
{
*
os
<<
' '
;
}
*
os
<<
'}'
;
}
};
};
// TypeWithoutFormatter<T, kTypeKind>::PrintValue(value, os) is called
// Used to print a pointer that is neither a char pointer nor a member
// by the universal printer to print a value of type T when neither
// pointer, when the user doesn't define PrintTo() for it. (A member
// operator<< nor PrintTo() is defined for T, where kTypeKind is the
// variable pointer or member function pointer doesn't really point to
// "kind" of T as defined by enum TypeKind.
// a location in the address space. Their representation is
template
<
typename
T
,
TypeKind
kTypeKind
>
// implementation-defined. Therefore they will be printed as raw
class
TypeWithoutFormatter
{
// bytes.)
public
:
struct
FunctionPointerPrinter
{
// This default version is called when kTypeKind is kOtherType.
template
<
typename
T
,
typename
=
typename
std
::
enable_if
<
static
void
PrintValue
(
const
T
&
value
,
::
std
::
ostream
*
os
)
{
std
::
is_function
<
T
>::
value
>::
type
>
PrintBytesInObjectTo
(
static
void
PrintValue
(
T
*
p
,
::
std
::
ostream
*
os
)
{
static_cast
<
const
unsigned
char
*>
(
if
(
p
==
nullptr
)
{
reinterpret_cast
<
const
void
*>
(
std
::
addressof
(
value
))),
*
os
<<
"NULL"
;
sizeof
(
value
),
os
);
}
else
{
// T is a function type, so '*os << p' doesn't do what we want
// (it just prints p as bool). We want to print p as a const
// void*.
*
os
<<
reinterpret_cast
<
const
void
*>
(
p
);
}
}
}
};
};
// We print a protobuf using its ShortDebugString() when the string
struct
PointerPrinter
{
// doesn't exceed this many characters; otherwise we print it using
template
<
typename
T
>
// DebugString() for better readability.
static
void
PrintValue
(
T
*
p
,
::
std
::
ostream
*
os
)
{
const
size_t
kProtobufOneLinerMaxLength
=
50
;
if
(
p
==
nullptr
)
{
*
os
<<
"NULL"
;
}
else
{
// T is not a function type. We just call << to print p,
// relying on ADL to pick up user-defined << for their pointer
// types, if any.
*
os
<<
p
;
}
}
};
namespace
internal_stream
{
struct
Sentinel
;
template
<
typename
Char
,
typename
CharTraits
,
typename
T
>
Sentinel
*
operator
<<
(
::
std
::
basic_ostream
<
Char
,
CharTraits
>&
os
,
const
T
&
x
);
// Check if the user has a user-defined operator<< for their type.
//
// We put this in its own namespace to inject a custom operator<< that allows us
// to probe the type's operator.
//
// Note that this operator<< takes a generic std::basic_ostream<Char,
// CharTraits> type instead of the more restricted std::ostream. If
// we define it to take an std::ostream instead, we'll get an
// "ambiguous overloads" compiler error when trying to print a type
// Foo that supports streaming to std::basic_ostream<Char,
// CharTraits>, as the compiler cannot tell whether
// operator<<(std::ostream&, const T&) or
// operator<<(std::basic_stream<Char, CharTraits>, const Foo&) is more
// specific.
template
<
typename
T
>
template
<
typename
T
>
class
TypeWithoutFormatter
<
T
,
kProtobuf
>
{
constexpr
bool
UseStreamOperator
()
{
public
:
return
!
std
::
is_same
<
decltype
(
std
::
declval
<
std
::
ostream
&>
()
<<
std
::
declval
<
const
T
&>
()),
Sentinel
*>::
value
;
}
}
// namespace internal_stream
struct
StreamPrinter
{
template
<
typename
T
,
typename
=
typename
std
::
enable_if
<
internal_stream
::
UseStreamOperator
<
T
>
()
>::
type
>
static
void
PrintValue
(
const
T
&
value
,
::
std
::
ostream
*
os
)
{
*
os
<<
value
;
}
};
struct
ProtobufPrinter
{
// We print a protobuf using its ShortDebugString() when the string
// doesn't exceed this many characters; otherwise we print it using
// DebugString() for better readability.
static
const
size_t
kProtobufOneLinerMaxLength
=
50
;
template
<
typename
T
,
typename
=
typename
std
::
enable_if
<
internal
::
IsAProtocolMessage
<
T
>::
value
>::
type
>
static
void
PrintValue
(
const
T
&
value
,
::
std
::
ostream
*
os
)
{
static
void
PrintValue
(
const
T
&
value
,
::
std
::
ostream
*
os
)
{
std
::
string
pretty_str
=
value
.
ShortDebugString
();
std
::
string
pretty_str
=
value
.
ShortDebugString
();
if
(
pretty_str
.
length
()
>
kProtobufOneLinerMaxLength
)
{
if
(
pretty_str
.
length
()
>
kProtobufOneLinerMaxLength
)
{
...
@@ -175,9 +248,7 @@ class TypeWithoutFormatter<T, kProtobuf> {
...
@@ -175,9 +248,7 @@ class TypeWithoutFormatter<T, kProtobuf> {
}
}
};
};
template
<
typename
T
>
struct
ConvertibleToIntegerPrinter
{
class
TypeWithoutFormatter
<
T
,
kConvertibleToInteger
>
{
public
:
// Since T has no << operator or PrintTo() but can be implicitly
// Since T has no << operator or PrintTo() but can be implicitly
// converted to BiggestInt, we print it as a BiggestInt.
// converted to BiggestInt, we print it as a BiggestInt.
//
//
...
@@ -185,112 +256,64 @@ class TypeWithoutFormatter<T, kConvertibleToInteger> {
...
@@ -185,112 +256,64 @@ class TypeWithoutFormatter<T, kConvertibleToInteger> {
// case printing it as an integer is the desired behavior. In case
// case printing it as an integer is the desired behavior. In case
// T is not an enum, printing it as an integer is the best we can do
// T is not an enum, printing it as an integer is the best we can do
// given that it has no user-defined printer.
// given that it has no user-defined printer.
static
void
PrintValue
(
const
T
&
value
,
::
std
::
ostream
*
os
)
{
static
void
PrintValue
(
internal
::
BiggestInt
value
,
::
std
::
ostream
*
os
)
{
const
internal
::
BiggestInt
kBigInt
=
value
;
*
os
<<
value
;
*
os
<<
kBigInt
;
}
}
};
};
struct
ConvertibleToStringViewPrinter
{
#if GTEST_INTERNAL_HAS_STRING_VIEW
#if GTEST_INTERNAL_HAS_STRING_VIEW
template
<
typename
T
>
static
void
PrintValue
(
internal
::
StringView
value
,
::
std
::
ostream
*
os
)
{
class
TypeWithoutFormatter
<
T
,
kConvertibleToStringView
>
{
internal
::
UniversalPrint
(
value
,
os
);
public
:
}
// Since T has neither operator<< nor PrintTo() but can be implicitly
// converted to absl::string_view, we print it as a absl::string_view
// (or std::string_view).
//
// Note: the implementation is further below, as it depends on
// internal::PrintTo symbol which is defined later in the file.
static
void
PrintValue
(
const
T
&
value
,
::
std
::
ostream
*
os
);
};
#endif
#endif
};
// Prints the given value to the given ostream. If the value is a
// protocol message, its debug string is printed; if it's an enum or
// of a type implicitly convertible to BiggestInt, it's printed as an
// integer; otherwise the bytes in the value are printed. This is
// what UniversalPrinter<T>::Print() does when it knows nothing about
// type T and T has neither << operator nor PrintTo().
//
// A user can override this behavior for a class type Foo by defining
// a << operator in the namespace where Foo is defined.
//
// We put this operator in namespace 'internal2' instead of 'internal'
// to simplify the implementation, as much code in 'internal' needs to
// use << in STL, which would conflict with our own << were it defined
// in 'internal'.
//
// Note that this operator<< takes a generic std::basic_ostream<Char,
// CharTraits> type instead of the more restricted std::ostream. If
// we define it to take an std::ostream instead, we'll get an
// "ambiguous overloads" compiler error when trying to print a type
// Foo that supports streaming to std::basic_ostream<Char,
// CharTraits>, as the compiler cannot tell whether
// operator<<(std::ostream&, const T&) or
// operator<<(std::basic_stream<Char, CharTraits>, const Foo&) is more
// specific.
template
<
typename
Char
,
typename
CharTraits
,
typename
T
>
::
std
::
basic_ostream
<
Char
,
CharTraits
>&
operator
<<
(
::
std
::
basic_ostream
<
Char
,
CharTraits
>&
os
,
const
T
&
x
)
{
TypeWithoutFormatter
<
T
,
(
internal
::
IsAProtocolMessage
<
T
>::
value
?
kProtobuf
:
std
::
is_convertible
<
const
T
&
,
internal
::
BiggestInt
>::
value
?
kConvertibleToInteger
:
#if GTEST_INTERNAL_HAS_STRING_VIEW
std
::
is_convertible
<
const
T
&
,
internal
::
StringView
>::
value
?
kConvertibleToStringView
:
#endif
kOtherType
)
>::
PrintValue
(
x
,
&
os
);
return
os
;
}
}
// namespace internal2
// Prints the given number of bytes in the given object to the given
}
// namespace testing
// ostream.
GTEST_API_
void
PrintBytesInObjectTo
(
const
unsigned
char
*
obj_bytes
,
size_t
count
,
::
std
::
ostream
*
os
);
struct
FallbackPrinter
{
template
<
typename
T
>
static
void
PrintValue
(
const
T
&
value
,
::
std
::
ostream
*
os
)
{
PrintBytesInObjectTo
(
static_cast
<
const
unsigned
char
*>
(
reinterpret_cast
<
const
void
*>
(
std
::
addressof
(
value
))),
sizeof
(
value
),
os
);
}
};
// T
his namespace MUST NOT BE NESTED IN ::testing, or the name look-up
// T
ry every printer in order and return the first one that works.
// magic needed for implementing UniversalPrinter won't work.
template
<
typename
T
,
typename
E
,
typename
Printer
,
typename
...
Printers
>
namespace
testing_internal
{
struct
FindFirstPrinter
:
FindFirstPrinter
<
T
,
E
,
Printers
...
>
{};
// Used to print a value that is not an STL-style container when the
template
<
typename
T
,
typename
Printer
,
typename
...
Printers
>
// user doesn't define PrintTo() for it.
struct
FindFirstPrinter
<
T
,
decltype
(
Printer
::
PrintValue
(
std
::
declval
<
const
T
&>
(),
nullptr
)),
Printer
,
Printers
...
>
{
using
type
=
Printer
;
};
// Select the best printer in the following order:
// - Print containers (they have begin/end/etc).
// - Print function pointers.
// - Print object pointers.
// - Use the stream operator, if available.
// - Print protocol buffers.
// - Print types convertible to BiggestInt.
// - Print types convertible to StringView, if available.
// - Fallback to printing the raw bytes of the object.
template
<
typename
T
>
template
<
typename
T
>
void
DefaultPrintNonContainerTo
(
const
T
&
value
,
::
std
::
ostream
*
os
)
{
void
PrintWithFallback
(
const
T
&
value
,
::
std
::
ostream
*
os
)
{
// With the following statement, during unqualified name lookup,
using
Printer
=
typename
FindFirstPrinter
<
// testing::internal2::operator<< appears as if it was declared in
T
,
void
,
ContainerPrinter
,
FunctionPointerPrinter
,
PointerPrinter
,
// the nearest enclosing namespace that contains both
StreamPrinter
,
ProtobufPrinter
,
ConvertibleToIntegerPrinter
,
// ::testing_internal and ::testing::internal2, i.e. the global
ConvertibleToStringViewPrinter
,
FallbackPrinter
>::
type
;
// namespace. For more details, refer to the C++ Standard section
Printer
::
PrintValue
(
value
,
os
);
// 7.3.4-1 [namespace.udir]. This allows us to fall back onto
// testing::internal2::operator<< in case T doesn't come with a <<
// operator.
using
::
testing
::
internal2
::
operator
<<
;
// Assuming T is defined in namespace foo, in the next statement,
// the compiler will consider all of:
//
// 1. foo::operator<< (thanks to Koenig look-up),
// 2. ::operator<< (as the current namespace is enclosed in ::),
// 3. testing::internal2::operator<< (thanks to the using statement above).
//
// The operator<< whose type matches T best will be picked.
//
// We deliberately allow #2 to be a candidate, as sometimes it's
// impossible to define #1 (e.g. when foo is ::std, defining
// anything in it is undefined behavior unless you are a compiler
// vendor.).
*
os
<<
value
;
}
}
}
// namespace testing_internal
namespace
testing
{
namespace
internal
{
// FormatForComparison<ToPrint, OtherOperand>::Format(value) formats a
// FormatForComparison<ToPrint, OtherOperand>::Format(value) formats a
// value of type ToPrint that is an operand of a comparison assertion
// value of type ToPrint that is an operand of a comparison assertion
// (e.g. ASSERT_EQ). OtherOperand is the type of the other operand in
// (e.g. ASSERT_EQ). OtherOperand is the type of the other operand in
...
@@ -388,85 +411,6 @@ std::string FormatForComparisonFailureMessage(
...
@@ -388,85 +411,6 @@ std::string FormatForComparisonFailureMessage(
template
<
typename
T
>
template
<
typename
T
>
class
UniversalPrinter
;
class
UniversalPrinter
;
template
<
typename
T
>
void
UniversalPrint
(
const
T
&
value
,
::
std
::
ostream
*
os
);
enum
DefaultPrinterType
{
kPrintContainer
,
kPrintPointer
,
kPrintFunctionPointer
,
kPrintOther
,
};
template
<
DefaultPrinterType
type
>
struct
WrapPrinterType
{};
// Used to print an STL-style container when the user doesn't define
// a PrintTo() for it.
template
<
typename
C
>
void
DefaultPrintTo
(
WrapPrinterType
<
kPrintContainer
>
/* dummy */
,
const
C
&
container
,
::
std
::
ostream
*
os
)
{
const
size_t
kMaxCount
=
32
;
// The maximum number of elements to print.
*
os
<<
'{'
;
size_t
count
=
0
;
for
(
typename
C
::
const_iterator
it
=
container
.
begin
();
it
!=
container
.
end
();
++
it
,
++
count
)
{
if
(
count
>
0
)
{
*
os
<<
','
;
if
(
count
==
kMaxCount
)
{
// Enough has been printed.
*
os
<<
" ..."
;
break
;
}
}
*
os
<<
' '
;
// We cannot call PrintTo(*it, os) here as PrintTo() doesn't
// handle *it being a native array.
internal
::
UniversalPrint
(
*
it
,
os
);
}
if
(
count
>
0
)
{
*
os
<<
' '
;
}
*
os
<<
'}'
;
}
// Used to print a pointer that is neither a char pointer nor a member
// pointer, when the user doesn't define PrintTo() for it. (A member
// variable pointer or member function pointer doesn't really point to
// a location in the address space. Their representation is
// implementation-defined. Therefore they will be printed as raw
// bytes.)
template
<
typename
T
>
void
DefaultPrintTo
(
WrapPrinterType
<
kPrintPointer
>
/* dummy */
,
T
*
p
,
::
std
::
ostream
*
os
)
{
if
(
p
==
nullptr
)
{
*
os
<<
"NULL"
;
}
else
{
// T is not a function type. We just call << to print p,
// relying on ADL to pick up user-defined << for their pointer
// types, if any.
*
os
<<
p
;
}
}
template
<
typename
T
>
void
DefaultPrintTo
(
WrapPrinterType
<
kPrintFunctionPointer
>
/* dummy */
,
T
*
p
,
::
std
::
ostream
*
os
)
{
if
(
p
==
nullptr
)
{
*
os
<<
"NULL"
;
}
else
{
// T is a function type, so '*os << p' doesn't do what we want
// (it just prints p as bool). We want to print p as a const
// void*.
*
os
<<
reinterpret_cast
<
const
void
*>
(
p
);
}
}
// Used to print a non-container, non-pointer value when the user
// doesn't define PrintTo() for it.
template
<
typename
T
>
void
DefaultPrintTo
(
WrapPrinterType
<
kPrintOther
>
/* dummy */
,
const
T
&
value
,
::
std
::
ostream
*
os
)
{
::
testing_internal
::
DefaultPrintNonContainerTo
(
value
,
os
);
}
// Prints the given value using the << operator if it has one;
// Prints the given value using the << operator if it has one;
// otherwise prints the bytes in it. This is what
// otherwise prints the bytes in it. This is what
// UniversalPrinter<T>::Print() does when PrintTo() is not specialized
// UniversalPrinter<T>::Print() does when PrintTo() is not specialized
...
@@ -480,36 +424,7 @@ void DefaultPrintTo(WrapPrinterType<kPrintOther> /* dummy */,
...
@@ -480,36 +424,7 @@ void DefaultPrintTo(WrapPrinterType<kPrintOther> /* dummy */,
// wants).
// wants).
template
<
typename
T
>
template
<
typename
T
>
void
PrintTo
(
const
T
&
value
,
::
std
::
ostream
*
os
)
{
void
PrintTo
(
const
T
&
value
,
::
std
::
ostream
*
os
)
{
// DefaultPrintTo() is overloaded. The type of its first argument
internal
::
PrintWithFallback
(
value
,
os
);
// determines which version will be picked.
//
// Note that we check for container types here, prior to we check
// for protocol message types in our operator<<. The rationale is:
//
// For protocol messages, we want to give people a chance to
// override Google Mock's format by defining a PrintTo() or
// operator<<. For STL containers, other formats can be
// incompatible with Google Mock's format for the container
// elements; therefore we check for container types here to ensure
// that our format is used.
//
// Note that MSVC and clang-cl do allow an implicit conversion from
// pointer-to-function to pointer-to-object, but clang-cl warns on it.
// So don't use ImplicitlyConvertible if it can be helped since it will
// cause this warning, and use a separate overload of DefaultPrintTo for
// function pointers so that the `*os << p` in the object pointer overload
// doesn't cause that warning either.
DefaultPrintTo
(
WrapPrinterType
<
(
sizeof
(
IsContainerTest
<
T
>
(
0
))
==
sizeof
(
IsContainer
))
&&
!
IsRecursiveContainer
<
T
>::
value
?
kPrintContainer
:
!
std
::
is_pointer
<
T
>::
value
?
kPrintOther
:
std
::
is_function
<
typename
std
::
remove_pointer
<
T
>::
type
>::
value
?
kPrintFunctionPointer
:
kPrintPointer
>
(),
value
,
os
);
}
}
// The following list of PrintTo() overloads tells
// The following list of PrintTo() overloads tells
...
@@ -900,16 +815,6 @@ Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) {
...
@@ -900,16 +815,6 @@ Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) {
}
// namespace internal
}
// namespace internal
#if GTEST_INTERNAL_HAS_STRING_VIEW
namespace
internal2
{
template
<
typename
T
>
void
TypeWithoutFormatter
<
T
,
kConvertibleToStringView
>::
PrintValue
(
const
T
&
value
,
::
std
::
ostream
*
os
)
{
internal
::
PrintTo
(
internal
::
StringView
(
value
),
os
);
}
}
// namespace internal2
#endif
template
<
typename
T
>
template
<
typename
T
>
::
std
::
string
PrintToString
(
const
T
&
value
)
{
::
std
::
string
PrintToString
(
const
T
&
value
)
{
::
std
::
stringstream
ss
;
::
std
::
stringstream
ss
;
...
...
googletest/src/gtest-printers.cc
View file @
0bf8ea30
...
@@ -104,7 +104,7 @@ void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count,
...
@@ -104,7 +104,7 @@ void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count,
}
// namespace
}
// namespace
namespace
internal
2
{
namespace
internal
{
// Delegates to PrintBytesInObjectToImpl() to print the bytes in the
// Delegates to PrintBytesInObjectToImpl() to print the bytes in the
// given object. The delegation simplifies the implementation, which
// given object. The delegation simplifies the implementation, which
...
@@ -116,10 +116,6 @@ void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count,
...
@@ -116,10 +116,6 @@ void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count,
PrintBytesInObjectToImpl
(
obj_bytes
,
count
,
os
);
PrintBytesInObjectToImpl
(
obj_bytes
,
count
,
os
);
}
}
}
// namespace internal2
namespace
internal
{
// Depending on the value of a char (or wchar_t), we print it in one
// Depending on the value of a char (or wchar_t), we print it in one
// of three formats:
// of three formats:
// - as is if it's a printable ASCII (e.g. 'a', '2', ' '),
// - as is if it's a printable ASCII (e.g. 'a', '2', ' '),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment