Removes testing::internal::String::Format(), which causes problems as it truncates the result at 4096 chars. Also update an obsolete link in comment.

This commit is contained in:
kosak 2013-02-22 20:10:40 +00:00
parent 65b5c22436
commit cc1fdb58ca
13 changed files with 176 additions and 232 deletions

View File

@ -130,6 +130,7 @@ if (gtest_build_tests)
cxx_test(gtest_repeat_test gtest) cxx_test(gtest_repeat_test gtest)
cxx_test(gtest_sole_header_test gtest_main) cxx_test(gtest_sole_header_test gtest_main)
cxx_test(gtest_stress_test gtest) cxx_test(gtest_stress_test gtest)
cxx_test(gtest_test_macro_stack_footprint_test gtest)
cxx_test(gtest-test-part_test gtest_main) cxx_test(gtest-test-part_test gtest_main)
cxx_test(gtest_throw_on_failure_ex_test gtest) cxx_test(gtest_throw_on_failure_ex_test gtest)
cxx_test(gtest-typed-test_test gtest_main cxx_test(gtest-typed-test_test gtest_main

View File

@ -82,6 +82,7 @@ EXTRA_DIST += \
test/gtest_shuffle_test_.cc \ test/gtest_shuffle_test_.cc \
test/gtest_sole_header_test.cc \ test/gtest_sole_header_test.cc \
test/gtest_stress_test.cc \ test/gtest_stress_test.cc \
test/gtest_test_macro_stack_footprint_test.cc \
test/gtest_throw_on_failure_ex_test.cc \ test/gtest_throw_on_failure_ex_test.cc \
test/gtest_throw_on_failure_test_.cc \ test/gtest_throw_on_failure_test_.cc \
test/gtest_uninitialized_test_.cc \ test/gtest_uninitialized_test_.cc \

View File

@ -680,7 +680,8 @@ class GTEST_API_ TestInfo {
friend class TestCase; friend class TestCase;
friend class internal::UnitTestImpl; friend class internal::UnitTestImpl;
friend TestInfo* internal::MakeAndRegisterTestInfo( friend TestInfo* internal::MakeAndRegisterTestInfo(
const char* test_case_name, const char* name, const char* test_case_name,
const char* name,
const char* type_param, const char* type_param,
const char* value_param, const char* value_param,
internal::TypeId fixture_class_id, internal::TypeId fixture_class_id,
@ -690,9 +691,10 @@ class GTEST_API_ TestInfo {
// Constructs a TestInfo object. The newly constructed instance assumes // Constructs a TestInfo object. The newly constructed instance assumes
// ownership of the factory object. // ownership of the factory object.
TestInfo(const char* test_case_name, const char* name, TestInfo(const std::string& test_case_name,
const char* a_type_param, const std::string& name,
const char* a_value_param, const char* a_type_param, // NULL if not a type-parameterized test
const char* a_value_param, // NULL if not a value-parameterized test
internal::TypeId fixture_class_id, internal::TypeId fixture_class_id,
internal::TestFactoryBase* factory); internal::TestFactoryBase* factory);

View File

@ -503,7 +503,8 @@ typedef void (*TearDownTestCaseFunc)();
// The newly created TestInfo instance will assume // The newly created TestInfo instance will assume
// ownership of the factory object. // ownership of the factory object.
GTEST_API_ TestInfo* MakeAndRegisterTestInfo( GTEST_API_ TestInfo* MakeAndRegisterTestInfo(
const char* test_case_name, const char* name, const char* test_case_name,
const char* name,
const char* type_param, const char* type_param,
const char* value_param, const char* value_param,
TypeId fixture_class_id, TypeId fixture_class_id,
@ -591,8 +592,8 @@ class TypeParameterizedTest {
// First, registers the first type-parameterized test in the type // First, registers the first type-parameterized test in the type
// list. // list.
MakeAndRegisterTestInfo( MakeAndRegisterTestInfo(
String::Format("%s%s%s/%d", prefix, prefix[0] == '\0' ? "" : "/", (std::string(prefix) + (prefix[0] == '\0' ? "" : "/") + case_name + "/"
case_name, index).c_str(), + StreamableToString(index)).c_str(),
GetPrefixUntilComma(test_names).c_str(), GetPrefixUntilComma(test_names).c_str(),
GetTypeName<Type>().c_str(), GetTypeName<Type>().c_str(),
NULL, // No value parameter. NULL, // No value parameter.

View File

@ -494,10 +494,10 @@ class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase {
const string& instantiation_name = gen_it->first; const string& instantiation_name = gen_it->first;
ParamGenerator<ParamType> generator((*gen_it->second)()); ParamGenerator<ParamType> generator((*gen_it->second)());
Message test_case_name_stream; string test_case_name;
if ( !instantiation_name.empty() ) if ( !instantiation_name.empty() )
test_case_name_stream << instantiation_name << "/"; test_case_name = instantiation_name + "/";
test_case_name_stream << test_info->test_case_base_name; test_case_name += test_info->test_case_base_name;
int i = 0; int i = 0;
for (typename ParamGenerator<ParamType>::iterator param_it = for (typename ParamGenerator<ParamType>::iterator param_it =
@ -506,7 +506,7 @@ class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase {
Message test_name_stream; Message test_name_stream;
test_name_stream << test_info->test_base_name << "/" << i; test_name_stream << test_info->test_base_name << "/" << i;
MakeAndRegisterTestInfo( MakeAndRegisterTestInfo(
test_case_name_stream.GetString().c_str(), test_case_name.c_str(),
test_name_stream.GetString().c_str(), test_name_stream.GetString().c_str(),
NULL, // No type parameter. NULL, // No type parameter.
PrintToString(*param_it).c_str(), PrintToString(*param_it).c_str(),

View File

@ -144,16 +144,14 @@ class GTEST_API_ String {
static bool EndsWithCaseInsensitive( static bool EndsWithCaseInsensitive(
const std::string& str, const std::string& suffix); const std::string& str, const std::string& suffix);
// Formats a list of arguments to an std::string, using the same format // Formats an int value as "%02d".
// spec string as for printf. static std::string FormatIntWidth2(int value); // "%02d" for width == 2
//
// We do not use the StringPrintf class as it is not universally // Formats an int value as "%X".
// available. static std::string FormatHexInt(int value);
//
// The result is limited to 4096 characters (including the tailing // Formats a byte as "%02X".
// 0). If 4096 characters are not enough to format the input, static std::string FormatByte(unsigned char value);
// "<buffer exceeded>" is returned.
static std::string Format(const char* format, ...);
private: private:
String(); // Not meant to be instantiated. String(); // Not meant to be instantiated.

View File

@ -272,9 +272,10 @@ void DeathTestAbort(const std::string& message) {
# define GTEST_DEATH_TEST_CHECK_(expression) \ # define GTEST_DEATH_TEST_CHECK_(expression) \
do { \ do { \
if (!::testing::internal::IsTrue(expression)) { \ if (!::testing::internal::IsTrue(expression)) { \
DeathTestAbort(::testing::internal::String::Format( \ DeathTestAbort( \
"CHECK failed: File %s, line %d: %s", \ ::std::string("CHECK failed: File ") + __FILE__ + ", line " \
__FILE__, __LINE__, #expression)); \ + ::testing::internal::StreamableToString(__LINE__) + ": " \
+ #expression); \
} \ } \
} while (::testing::internal::AlwaysFalse()) } while (::testing::internal::AlwaysFalse())
@ -292,9 +293,10 @@ void DeathTestAbort(const std::string& message) {
gtest_retval = (expression); \ gtest_retval = (expression); \
} while (gtest_retval == -1 && errno == EINTR); \ } while (gtest_retval == -1 && errno == EINTR); \
if (gtest_retval == -1) { \ if (gtest_retval == -1) { \
DeathTestAbort(::testing::internal::String::Format( \ DeathTestAbort( \
"CHECK failed: File %s, line %d: %s != -1", \ ::std::string("CHECK failed: File ") + __FILE__ + ", line " \
__FILE__, __LINE__, #expression)); \ + ::testing::internal::StreamableToString(__LINE__) + ": " \
+ #expression + " != -1"); \
} \ } \
} while (::testing::internal::AlwaysFalse()) } while (::testing::internal::AlwaysFalse())
@ -716,14 +718,14 @@ DeathTest::TestRole WindowsDeathTest::AssumeRole() {
info->test_case_name() + "." + info->name(); info->test_case_name() + "." + info->name();
const std::string internal_flag = const std::string internal_flag =
std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag +
"=" + file_ + "|" + String::Format("%d|%d|%u|%Iu|%Iu", line_, "=" + file_ + "|" + StreamableToString(line_) + "|" +
death_test_index, StreamableToString(death_test_index) + "|" +
static_cast<unsigned int>(::GetCurrentProcessId()), StreamableToString(static_cast<unsigned int>(::GetCurrentProcessId())) +
// size_t has the same with as pointers on both 32-bit and 64-bit // size_t has the same width as pointers on both 32-bit and 64-bit
// Windows platforms. // Windows platforms.
// See http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx. // See http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx.
reinterpret_cast<size_t>(write_handle), "|" + StreamableToString(reinterpret_cast<size_t>(write_handle)) +
reinterpret_cast<size_t>(event_handle_.Get())); "|" + StreamableToString(reinterpret_cast<size_t>(event_handle_.Get()));
char executable_path[_MAX_PATH + 1]; // NOLINT char executable_path[_MAX_PATH + 1]; // NOLINT
GTEST_DEATH_TEST_CHECK_( GTEST_DEATH_TEST_CHECK_(
@ -1114,13 +1116,13 @@ DeathTest::TestRole ExecDeathTest::AssumeRole() {
GTEST_DEATH_TEST_CHECK_(fcntl(pipe_fd[1], F_SETFD, 0) != -1); GTEST_DEATH_TEST_CHECK_(fcntl(pipe_fd[1], F_SETFD, 0) != -1);
const std::string filter_flag = const std::string filter_flag =
String::Format("--%s%s=%s.%s", std::string("--") + GTEST_FLAG_PREFIX_ + kFilterFlag + "="
GTEST_FLAG_PREFIX_, kFilterFlag, + info->test_case_name() + "." + info->name();
info->test_case_name(), info->name());
const std::string internal_flag = const std::string internal_flag =
String::Format("--%s%s=%s|%d|%d|%d", std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + "="
GTEST_FLAG_PREFIX_, kInternalRunDeathTestFlag, + file_ + "|" + StreamableToString(line_) + "|"
file_, line_, death_test_index, pipe_fd[1]); + StreamableToString(death_test_index) + "|"
+ StreamableToString(pipe_fd[1]);
Arguments args; Arguments args;
args.AddArguments(GetArgvsForDeathTestChildProcess()); args.AddArguments(GetArgvsForDeathTestChildProcess());
args.AddArgument(filter_flag.c_str()); args.AddArgument(filter_flag.c_str());
@ -1159,9 +1161,10 @@ bool DefaultDeathTestFactory::Create(const char* statement, const RE* regex,
if (flag != NULL) { if (flag != NULL) {
if (death_test_index > flag->index()) { if (death_test_index > flag->index()) {
DeathTest::set_last_death_test_message(String::Format( DeathTest::set_last_death_test_message(
"Death test count (%d) somehow exceeded expected maximum (%d)", "Death test count (" + StreamableToString(death_test_index)
death_test_index, flag->index())); + ") somehow exceeded expected maximum ("
+ StreamableToString(flag->index()) + ")");
return false; return false;
} }
@ -1190,9 +1193,9 @@ bool DefaultDeathTestFactory::Create(const char* statement, const RE* regex,
# endif // GTEST_OS_WINDOWS # endif // GTEST_OS_WINDOWS
else { // NOLINT - this is more readable than unbalanced brackets inside #if. else { // NOLINT - this is more readable than unbalanced brackets inside #if.
DeathTest::set_last_death_test_message(String::Format( DeathTest::set_last_death_test_message(
"Unknown death test style \"%s\" encountered", "Unknown death test style \"" + GTEST_FLAG(death_test_style)
GTEST_FLAG(death_test_style).c_str())); + "\" encountered");
return false; return false;
} }
@ -1230,8 +1233,8 @@ int GetStatusFileDescriptor(unsigned int parent_process_id,
FALSE, // Non-inheritable. FALSE, // Non-inheritable.
parent_process_id)); parent_process_id));
if (parent_process_handle.Get() == INVALID_HANDLE_VALUE) { if (parent_process_handle.Get() == INVALID_HANDLE_VALUE) {
DeathTestAbort(String::Format("Unable to open parent process %u", DeathTestAbort("Unable to open parent process " +
parent_process_id)); StreamableToString(parent_process_id));
} }
// TODO(vladl@google.com): Replace the following check with a // TODO(vladl@google.com): Replace the following check with a
@ -1251,9 +1254,10 @@ int GetStatusFileDescriptor(unsigned int parent_process_id,
// DUPLICATE_SAME_ACCESS is used. // DUPLICATE_SAME_ACCESS is used.
FALSE, // Request non-inheritable handler. FALSE, // Request non-inheritable handler.
DUPLICATE_SAME_ACCESS)) { DUPLICATE_SAME_ACCESS)) {
DeathTestAbort(String::Format( DeathTestAbort("Unable to duplicate the pipe handle " +
"Unable to duplicate the pipe handle %Iu from the parent process %u", StreamableToString(write_handle_as_size_t) +
write_handle_as_size_t, parent_process_id)); " from the parent process " +
StreamableToString(parent_process_id));
} }
const HANDLE event_handle = reinterpret_cast<HANDLE>(event_handle_as_size_t); const HANDLE event_handle = reinterpret_cast<HANDLE>(event_handle_as_size_t);
@ -1264,17 +1268,18 @@ int GetStatusFileDescriptor(unsigned int parent_process_id,
0x0, 0x0,
FALSE, FALSE,
DUPLICATE_SAME_ACCESS)) { DUPLICATE_SAME_ACCESS)) {
DeathTestAbort(String::Format( DeathTestAbort("Unable to duplicate the event handle " +
"Unable to duplicate the event handle %Iu from the parent process %u", StreamableToString(event_handle_as_size_t) +
event_handle_as_size_t, parent_process_id)); " from the parent process " +
StreamableToString(parent_process_id));
} }
const int write_fd = const int write_fd =
::_open_osfhandle(reinterpret_cast<intptr_t>(dup_write_handle), O_APPEND); ::_open_osfhandle(reinterpret_cast<intptr_t>(dup_write_handle), O_APPEND);
if (write_fd == -1) { if (write_fd == -1) {
DeathTestAbort(String::Format( DeathTestAbort("Unable to convert pipe handle " +
"Unable to convert pipe handle %Iu to a file descriptor", StreamableToString(write_handle_as_size_t) +
write_handle_as_size_t)); " to a file descriptor");
} }
// Signals the parent that the write end of the pipe has been acquired // Signals the parent that the write end of the pipe has been acquired
@ -1311,9 +1316,8 @@ InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() {
|| !ParseNaturalNumber(fields[3], &parent_process_id) || !ParseNaturalNumber(fields[3], &parent_process_id)
|| !ParseNaturalNumber(fields[4], &write_handle_as_size_t) || !ParseNaturalNumber(fields[4], &write_handle_as_size_t)
|| !ParseNaturalNumber(fields[5], &event_handle_as_size_t)) { || !ParseNaturalNumber(fields[5], &event_handle_as_size_t)) {
DeathTestAbort(String::Format( DeathTestAbort("Bad --gtest_internal_run_death_test flag: " +
"Bad --gtest_internal_run_death_test flag: %s", GTEST_FLAG(internal_run_death_test));
GTEST_FLAG(internal_run_death_test).c_str()));
} }
write_fd = GetStatusFileDescriptor(parent_process_id, write_fd = GetStatusFileDescriptor(parent_process_id,
write_handle_as_size_t, write_handle_as_size_t,
@ -1324,9 +1328,8 @@ InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() {
|| !ParseNaturalNumber(fields[1], &line) || !ParseNaturalNumber(fields[1], &line)
|| !ParseNaturalNumber(fields[2], &index) || !ParseNaturalNumber(fields[2], &index)
|| !ParseNaturalNumber(fields[3], &write_fd)) { || !ParseNaturalNumber(fields[3], &write_fd)) {
DeathTestAbort(String::Format( DeathTestAbort("Bad --gtest_internal_run_death_test flag: "
"Bad --gtest_internal_run_death_test flag: %s", + GTEST_FLAG(internal_run_death_test));
GTEST_FLAG(internal_run_death_test).c_str()));
} }
# endif // GTEST_OS_WINDOWS # endif // GTEST_OS_WINDOWS

View File

@ -182,7 +182,7 @@ FilePath FilePath::MakeFileName(const FilePath& directory,
if (number == 0) { if (number == 0) {
file = base_name.string() + "." + extension; file = base_name.string() + "." + extension;
} else { } else {
file = base_name.string() + "_" + String::Format("%d", number).c_str() file = base_name.string() + "_" + StreamableToString(number)
+ "." + extension; + "." + extension;
} }
return ConcatPaths(directory, FilePath(file)); return ConcatPaths(directory, FilePath(file));

View File

@ -222,12 +222,10 @@ class GTestFlagSaver {
// Converts a Unicode code point to a narrow string in UTF-8 encoding. // Converts a Unicode code point to a narrow string in UTF-8 encoding.
// code_point parameter is of type UInt32 because wchar_t may not be // code_point parameter is of type UInt32 because wchar_t may not be
// wide enough to contain a code point. // wide enough to contain a code point.
// The output buffer str must containt at least 32 characters.
// The function returns the address of the output buffer.
// If the code_point is not a valid Unicode code point // If the code_point is not a valid Unicode code point
// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be output // (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted
// as '(Invalid Unicode 0xXXXXXXXX)'. // to "(Invalid Unicode 0xXXXXXXXX)".
GTEST_API_ char* CodePointToUtf8(UInt32 code_point, char* str); GTEST_API_ std::string CodePointToUtf8(UInt32 code_point);
// Converts a wide string to a narrow string in UTF-8 encoding. // Converts a wide string to a narrow string in UTF-8 encoding.
// The wide string is assumed to have the following encoding: // The wide string is assumed to have the following encoding:

View File

@ -454,15 +454,15 @@ const char kUnknownFile[] = "unknown file";
// Formats a source file path and a line number as they would appear // Formats a source file path and a line number as they would appear
// in an error message from the compiler used to compile this code. // in an error message from the compiler used to compile this code.
GTEST_API_ ::std::string FormatFileLocation(const char* file, int line) { GTEST_API_ ::std::string FormatFileLocation(const char* file, int line) {
const char* const file_name = file == NULL ? kUnknownFile : file; const std::string file_name(file == NULL ? kUnknownFile : file);
if (line < 0) { if (line < 0) {
return String::Format("%s:", file_name).c_str(); return file_name + ":";
} }
#ifdef _MSC_VER #ifdef _MSC_VER
return String::Format("%s(%d):", file_name, line).c_str(); return file_name + "(" + StreamableToString(line) + "):";
#else #else
return String::Format("%s:%d:", file_name, line).c_str(); return file_name + ":" + StreamableToString(line) + ":";
#endif // _MSC_VER #endif // _MSC_VER
} }
@ -473,12 +473,12 @@ GTEST_API_ ::std::string FormatFileLocation(const char* file, int line) {
// to the file location it produces, unlike FormatFileLocation(). // to the file location it produces, unlike FormatFileLocation().
GTEST_API_ ::std::string FormatCompilerIndependentFileLocation( GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(
const char* file, int line) { const char* file, int line) {
const char* const file_name = file == NULL ? kUnknownFile : file; const std::string file_name(file == NULL ? kUnknownFile : file);
if (line < 0) if (line < 0)
return file_name; return file_name;
else else
return String::Format("%s:%d", file_name, line).c_str(); return file_name + ":" + StreamableToString(line);
} }

View File

@ -176,7 +176,7 @@ static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) {
*os << static_cast<char>(c); *os << static_cast<char>(c);
return kAsIs; return kAsIs;
} else { } else {
*os << String::Format("\\x%X", static_cast<UnsignedChar>(c)); *os << "\\x" + String::FormatHexInt(static_cast<UnsignedChar>(c));
return kHexEscape; return kHexEscape;
} }
} }
@ -221,7 +221,7 @@ void PrintCharAndCodeTo(Char c, ostream* os) {
// obvious). // obvious).
if (c == 0) if (c == 0)
return; return;
*os << " (" << String::Format("%d", c).c_str(); *os << " (" << static_cast<int>(c);
// For more convenience, we print c's code again in hexidecimal, // For more convenience, we print c's code again in hexidecimal,
// unless c was already printed in the form '\x##' or the code is in // unless c was already printed in the form '\x##' or the code is in
@ -229,8 +229,7 @@ void PrintCharAndCodeTo(Char c, ostream* os) {
if (format == kHexEscape || (1 <= c && c <= 9)) { if (format == kHexEscape || (1 <= c && c <= 9)) {
// Do nothing. // Do nothing.
} else { } else {
*os << String::Format(", 0x%X", *os << ", 0x" << String::FormatHexInt(static_cast<UnsignedChar>(c));
static_cast<UnsignedChar>(c)).c_str();
} }
*os << ")"; *os << ")";
} }

View File

@ -1309,7 +1309,7 @@ AssertionResult HRESULTFailureHelper(const char* expr,
// want inserts expanded. // want inserts expanded.
const DWORD kFlags = FORMAT_MESSAGE_FROM_SYSTEM | const DWORD kFlags = FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS; FORMAT_MESSAGE_IGNORE_INSERTS;
const DWORD kBufSize = 4096; // String::Format can't exceed this length. const DWORD kBufSize = 4096;
// Gets the system's human readable message string for this HRESULT. // Gets the system's human readable message string for this HRESULT.
char error_text[kBufSize] = { '\0' }; char error_text[kBufSize] = { '\0' };
DWORD message_length = ::FormatMessageA(kFlags, DWORD message_length = ::FormatMessageA(kFlags,
@ -1319,7 +1319,7 @@ AssertionResult HRESULTFailureHelper(const char* expr,
error_text, // output buffer error_text, // output buffer
kBufSize, // buf size kBufSize, // buf size
NULL); // no arguments for inserts NULL); // no arguments for inserts
// Trims tailing white space (FormatMessage leaves a trailing cr-lf) // Trims tailing white space (FormatMessage leaves a trailing CR-LF)
for (; message_length && IsSpace(error_text[message_length - 1]); for (; message_length && IsSpace(error_text[message_length - 1]);
--message_length) { --message_length) {
error_text[message_length - 1] = '\0'; error_text[message_length - 1] = '\0';
@ -1327,10 +1327,10 @@ AssertionResult HRESULTFailureHelper(const char* expr,
# endif // GTEST_OS_WINDOWS_MOBILE # endif // GTEST_OS_WINDOWS_MOBILE
const std::string error_hex(String::Format("0x%08X ", hr)); const std::string error_hex("0x" + String::FormatHexInt(hr));
return ::testing::AssertionFailure() return ::testing::AssertionFailure()
<< "Expected: " << expr << " " << expected << ".\n" << "Expected: " << expr << " " << expected << ".\n"
<< " Actual: " << error_hex << error_text << "\n"; << " Actual: " << error_hex << " " << error_text << "\n";
} }
} // namespace } // namespace
@ -1387,12 +1387,15 @@ inline UInt32 ChopLowBits(UInt32* bits, int n) {
// Converts a Unicode code point to a narrow string in UTF-8 encoding. // Converts a Unicode code point to a narrow string in UTF-8 encoding.
// code_point parameter is of type UInt32 because wchar_t may not be // code_point parameter is of type UInt32 because wchar_t may not be
// wide enough to contain a code point. // wide enough to contain a code point.
// The output buffer str must containt at least 32 characters.
// The function returns the address of the output buffer.
// If the code_point is not a valid Unicode code point // If the code_point is not a valid Unicode code point
// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be output // (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted
// as '(Invalid Unicode 0xXXXXXXXX)'. // to "(Invalid Unicode 0xXXXXXXXX)".
char* CodePointToUtf8(UInt32 code_point, char* str) { std::string CodePointToUtf8(UInt32 code_point) {
if (code_point > kMaxCodePoint4) {
return "(Invalid Unicode 0x" + String::FormatHexInt(code_point) + ")";
}
char str[5]; // Big enough for the largest valid code point.
if (code_point <= kMaxCodePoint1) { if (code_point <= kMaxCodePoint1) {
str[1] = '\0'; str[1] = '\0';
str[0] = static_cast<char>(code_point); // 0xxxxxxx str[0] = static_cast<char>(code_point); // 0xxxxxxx
@ -1405,22 +1408,12 @@ char* CodePointToUtf8(UInt32 code_point, char* str) {
str[2] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx str[2] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx
str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx
str[0] = static_cast<char>(0xE0 | code_point); // 1110xxxx str[0] = static_cast<char>(0xE0 | code_point); // 1110xxxx
} else if (code_point <= kMaxCodePoint4) { } else { // code_point <= kMaxCodePoint4
str[4] = '\0'; str[4] = '\0';
str[3] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx str[3] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx
str[2] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx str[2] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx
str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx
str[0] = static_cast<char>(0xF0 | code_point); // 11110xxx str[0] = static_cast<char>(0xF0 | code_point); // 11110xxx
} else {
// The longest string String::Format can produce when invoked
// with these parameters is 28 character long (not including
// the terminating nul character). We are asking for 32 character
// buffer just in case. This is also enough for strncpy to
// null-terminate the destination string.
posix::StrNCpy(
str, String::Format("(Invalid Unicode 0x%X)", code_point).c_str(), 32);
str[31] = '\0'; // Makes sure no change in the format to strncpy leaves
// the result unterminated.
} }
return str; return str;
} }
@ -1479,8 +1472,7 @@ std::string WideStringToUtf8(const wchar_t* str, int num_chars) {
unicode_code_point = static_cast<UInt32>(str[i]); unicode_code_point = static_cast<UInt32>(str[i]);
} }
char buffer[32]; // CodePointToUtf8 requires a buffer this big. stream << CodePointToUtf8(unicode_code_point);
stream << CodePointToUtf8(unicode_code_point, buffer);
} }
return StringStreamToString(&stream); return StringStreamToString(&stream);
} }
@ -1597,47 +1589,26 @@ bool String::EndsWithCaseInsensitive(
suffix.c_str()); suffix.c_str());
} }
// Formats a list of arguments to an std::string, using the same format // Formats an int value as "%02d".
// spec string as for printf. std::string String::FormatIntWidth2(int value) {
// std::stringstream ss;
// We do not use the StringPrintf class as it is not universally ss << std::setfill('0') << std::setw(2) << value;
// available. return ss.str();
//
// The result is limited to 4096 characters (including the tailing 0).
// If 4096 characters are not enough to format the input, or if
// there's an error, "<formatting error or buffer exceeded>" is
// returned.
std::string String::Format(const char * format, ...) {
va_list args;
va_start(args, format);
char buffer[4096];
const int kBufferSize = sizeof(buffer)/sizeof(buffer[0]);
// MSVC 8 deprecates vsnprintf(), so we want to suppress warning
// 4996 (deprecated function) there.
#ifdef _MSC_VER // We are using MSVC.
# pragma warning(push) // Saves the current warning state.
# pragma warning(disable:4996) // Temporarily disables warning 4996.
const int size = vsnprintf(buffer, kBufferSize, format, args);
# pragma warning(pop) // Restores the warning state.
#else // We are not using MSVC.
const int size = vsnprintf(buffer, kBufferSize, format, args);
#endif // _MSC_VER
va_end(args);
// vsnprintf()'s behavior is not portable. When the buffer is not
// big enough, it returns a negative value in MSVC, and returns the
// needed buffer size on Linux. When there is an output error, it
// always returns a negative value. For simplicity, we lump the two
// error cases together.
if (size < 0 || size >= kBufferSize) {
return "<formatting error or buffer exceeded>";
} else {
return std::string(buffer, size);
} }
// Formats an int value as "%X".
std::string String::FormatHexInt(int value) {
std::stringstream ss;
ss << std::hex << std::uppercase << value;
return ss.str();
}
// Formats a byte as "%02X".
std::string String::FormatByte(unsigned char value) {
std::stringstream ss;
ss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase
<< static_cast<unsigned int>(value);
return ss.str();
} }
// Converts the buffer in a stringstream to an std::string, converting NUL // Converts the buffer in a stringstream to an std::string, converting NUL
@ -2091,10 +2062,8 @@ bool Test::HasNonfatalFailure() {
// Constructs a TestInfo object. It assumes ownership of the test factory // Constructs a TestInfo object. It assumes ownership of the test factory
// object. // object.
// TODO(vladl@google.com): Make a_test_case_name and a_name const string&'s TestInfo::TestInfo(const std::string& a_test_case_name,
// to signify they cannot be NULLs. const std::string& a_name,
TestInfo::TestInfo(const char* a_test_case_name,
const char* a_name,
const char* a_type_param, const char* a_type_param,
const char* a_value_param, const char* a_value_param,
internal::TypeId fixture_class_id, internal::TypeId fixture_class_id,
@ -2133,7 +2102,8 @@ namespace internal {
// The newly created TestInfo instance will assume // The newly created TestInfo instance will assume
// ownership of the factory object. // ownership of the factory object.
TestInfo* MakeAndRegisterTestInfo( TestInfo* MakeAndRegisterTestInfo(
const char* test_case_name, const char* name, const char* test_case_name,
const char* name,
const char* type_param, const char* type_param,
const char* value_param, const char* value_param,
TypeId fixture_class_id, TypeId fixture_class_id,
@ -2385,8 +2355,8 @@ void TestCase::UnshuffleTests() {
static std::string FormatCountableNoun(int count, static std::string FormatCountableNoun(int count,
const char * singular_form, const char * singular_form,
const char * plural_form) { const char * plural_form) {
return internal::String::Format("%d %s", count, return internal::StreamableToString(count) + " " +
count == 1 ? singular_form : plural_form); (count == 1 ? singular_form : plural_form);
} }
// Formats the count of tests. // Formats the count of tests.
@ -3056,7 +3026,8 @@ std::string XmlUnitTestResultPrinter::EscapeXml(
default: default:
if (IsValidXmlCharacter(*src)) { if (IsValidXmlCharacter(*src)) {
if (is_attribute && IsNormalizableWhitespace(*src)) if (is_attribute && IsNormalizableWhitespace(*src))
m << String::Format("&#x%02X;", unsigned(*src)); m << "&#x" << String::FormatByte(static_cast<unsigned char>(*src))
<< ";";
else else
m << *src; m << *src;
} }
@ -3121,13 +3092,13 @@ std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms) {
if (time_struct == NULL) if (time_struct == NULL)
return ""; // Invalid ms value return ""; // Invalid ms value
return String::Format("%d-%02d-%02dT%02d:%02d:%02d", // YYYY-MM-DDThh:mm:ss // YYYY-MM-DDThh:mm:ss
time_struct->tm_year + 1900, return StreamableToString(time_struct->tm_year + 1900) + "-" +
time_struct->tm_mon + 1, String::FormatIntWidth2(time_struct->tm_mon + 1) + "-" +
time_struct->tm_mday, String::FormatIntWidth2(time_struct->tm_mday) + "T" +
time_struct->tm_hour, String::FormatIntWidth2(time_struct->tm_hour) + ":" +
time_struct->tm_min, String::FormatIntWidth2(time_struct->tm_min) + ":" +
time_struct->tm_sec); String::FormatIntWidth2(time_struct->tm_sec);
} }
// Streams an XML CDATA section, escaping invalid CDATA sequences as needed. // Streams an XML CDATA section, escaping invalid CDATA sequences as needed.
@ -3268,7 +3239,7 @@ class StreamingListener : public EmptyTestEventListener {
StreamingListener(const string& host, const string& port) StreamingListener(const string& host, const string& port)
: sockfd_(-1), host_name_(host), port_num_(port) { : sockfd_(-1), host_name_(host), port_num_(port) {
MakeConnection(); MakeConnection();
Send("gtest_streaming_protocol_version=1.0\n"); SendLn("gtest_streaming_protocol_version=1.0");
} }
virtual ~StreamingListener() { virtual ~StreamingListener() {
@ -3277,59 +3248,58 @@ class StreamingListener : public EmptyTestEventListener {
} }
void OnTestProgramStart(const UnitTest& /* unit_test */) { void OnTestProgramStart(const UnitTest& /* unit_test */) {
Send("event=TestProgramStart\n"); SendLn("event=TestProgramStart");
} }
void OnTestProgramEnd(const UnitTest& unit_test) { void OnTestProgramEnd(const UnitTest& unit_test) {
// Note that Google Test current only report elapsed time for each // Note that Google Test current only report elapsed time for each
// test iteration, not for the entire test program. // test iteration, not for the entire test program.
Send(String::Format("event=TestProgramEnd&passed=%d\n", SendLn("event=TestProgramEnd&passed=" +
unit_test.Passed())); StreamableToString(unit_test.Passed()));
// Notify the streaming server to stop. // Notify the streaming server to stop.
CloseConnection(); CloseConnection();
} }
void OnTestIterationStart(const UnitTest& /* unit_test */, int iteration) { void OnTestIterationStart(const UnitTest& /* unit_test */, int iteration) {
Send(String::Format("event=TestIterationStart&iteration=%d\n", SendLn("event=TestIterationStart&iteration=" +
iteration)); StreamableToString(iteration));
} }
void OnTestIterationEnd(const UnitTest& unit_test, int /* iteration */) { void OnTestIterationEnd(const UnitTest& unit_test, int /* iteration */) {
Send(String::Format("event=TestIterationEnd&passed=%d&elapsed_time=%sms\n", SendLn("event=TestIterationEnd&passed=" +
unit_test.Passed(), StreamableToString(unit_test.Passed()) + "&elapsed_time=" +
StreamableToString(unit_test.elapsed_time()).c_str())); StreamableToString(unit_test.elapsed_time()) + "ms");
} }
void OnTestCaseStart(const TestCase& test_case) { void OnTestCaseStart(const TestCase& test_case) {
Send(String::Format("event=TestCaseStart&name=%s\n", test_case.name())); SendLn(std::string("event=TestCaseStart&name=") + test_case.name());
} }
void OnTestCaseEnd(const TestCase& test_case) { void OnTestCaseEnd(const TestCase& test_case) {
Send(String::Format("event=TestCaseEnd&passed=%d&elapsed_time=%sms\n", SendLn("event=TestCaseEnd&passed=" + StreamableToString(test_case.Passed())
test_case.Passed(), + "&elapsed_time=" + StreamableToString(test_case.elapsed_time())
StreamableToString(test_case.elapsed_time()).c_str())); + "ms");
} }
void OnTestStart(const TestInfo& test_info) { void OnTestStart(const TestInfo& test_info) {
Send(String::Format("event=TestStart&name=%s\n", test_info.name())); SendLn(std::string("event=TestStart&name=") + test_info.name());
} }
void OnTestEnd(const TestInfo& test_info) { void OnTestEnd(const TestInfo& test_info) {
Send(String::Format( SendLn("event=TestEnd&passed=" +
"event=TestEnd&passed=%d&elapsed_time=%sms\n", StreamableToString((test_info.result())->Passed()) +
(test_info.result())->Passed(), "&elapsed_time=" +
StreamableToString((test_info.result())->elapsed_time()).c_str())); StreamableToString((test_info.result())->elapsed_time()) + "ms");
} }
void OnTestPartResult(const TestPartResult& test_part_result) { void OnTestPartResult(const TestPartResult& test_part_result) {
const char* file_name = test_part_result.file_name(); const char* file_name = test_part_result.file_name();
if (file_name == NULL) if (file_name == NULL)
file_name = ""; file_name = "";
Send(String::Format("event=TestPartResult&file=%s&line=%d&message=", SendLn("event=TestPartResult&file=" + UrlEncode(file_name) +
UrlEncode(file_name).c_str(), "&line=" + StreamableToString(test_part_result.line_number()) +
test_part_result.line_number())); "&message=" + UrlEncode(test_part_result.message()));
Send(UrlEncode(test_part_result.message()) + "\n");
} }
private: private:
@ -3358,6 +3328,11 @@ class StreamingListener : public EmptyTestEventListener {
} }
} }
// Sends a string and a newline to the socket.
void SendLn(const string& message) {
Send(message + "\n");
}
int sockfd_; // socket file descriptor int sockfd_; // socket file descriptor
const string host_name_; const string host_name_;
const string port_num_; const string port_num_;
@ -3379,7 +3354,7 @@ string StreamingListener::UrlEncode(const char* str) {
case '=': case '=':
case '&': case '&':
case '\n': case '\n':
result.append(String::Format("%%%02x", static_cast<unsigned char>(ch))); result.append("%" + String::FormatByte(static_cast<unsigned char>(ch)));
break; break;
default: default:
result.push_back(ch); result.push_back(ch);

View File

@ -454,45 +454,41 @@ TEST(NullLiteralTest, IsFalseForNonNullLiterals) {
// Tests that the NUL character L'\0' is encoded correctly. // Tests that the NUL character L'\0' is encoded correctly.
TEST(CodePointToUtf8Test, CanEncodeNul) { TEST(CodePointToUtf8Test, CanEncodeNul) {
char buffer[32]; EXPECT_EQ("", CodePointToUtf8(L'\0'));
EXPECT_STREQ("", CodePointToUtf8(L'\0', buffer));
} }
// Tests that ASCII characters are encoded correctly. // Tests that ASCII characters are encoded correctly.
TEST(CodePointToUtf8Test, CanEncodeAscii) { TEST(CodePointToUtf8Test, CanEncodeAscii) {
char buffer[32]; EXPECT_EQ("a", CodePointToUtf8(L'a'));
EXPECT_STREQ("a", CodePointToUtf8(L'a', buffer)); EXPECT_EQ("Z", CodePointToUtf8(L'Z'));
EXPECT_STREQ("Z", CodePointToUtf8(L'Z', buffer)); EXPECT_EQ("&", CodePointToUtf8(L'&'));
EXPECT_STREQ("&", CodePointToUtf8(L'&', buffer)); EXPECT_EQ("\x7F", CodePointToUtf8(L'\x7F'));
EXPECT_STREQ("\x7F", CodePointToUtf8(L'\x7F', buffer));
} }
// Tests that Unicode code-points that have 8 to 11 bits are encoded // Tests that Unicode code-points that have 8 to 11 bits are encoded
// as 110xxxxx 10xxxxxx. // as 110xxxxx 10xxxxxx.
TEST(CodePointToUtf8Test, CanEncode8To11Bits) { TEST(CodePointToUtf8Test, CanEncode8To11Bits) {
char buffer[32];
// 000 1101 0011 => 110-00011 10-010011 // 000 1101 0011 => 110-00011 10-010011
EXPECT_STREQ("\xC3\x93", CodePointToUtf8(L'\xD3', buffer)); EXPECT_EQ("\xC3\x93", CodePointToUtf8(L'\xD3'));
// 101 0111 0110 => 110-10101 10-110110 // 101 0111 0110 => 110-10101 10-110110
// Some compilers (e.g., GCC on MinGW) cannot handle non-ASCII codepoints // Some compilers (e.g., GCC on MinGW) cannot handle non-ASCII codepoints
// in wide strings and wide chars. In order to accomodate them, we have to // in wide strings and wide chars. In order to accomodate them, we have to
// introduce such character constants as integers. // introduce such character constants as integers.
EXPECT_STREQ("\xD5\xB6", EXPECT_EQ("\xD5\xB6",
CodePointToUtf8(static_cast<wchar_t>(0x576), buffer)); CodePointToUtf8(static_cast<wchar_t>(0x576)));
} }
// Tests that Unicode code-points that have 12 to 16 bits are encoded // Tests that Unicode code-points that have 12 to 16 bits are encoded
// as 1110xxxx 10xxxxxx 10xxxxxx. // as 1110xxxx 10xxxxxx 10xxxxxx.
TEST(CodePointToUtf8Test, CanEncode12To16Bits) { TEST(CodePointToUtf8Test, CanEncode12To16Bits) {
char buffer[32];
// 0000 1000 1101 0011 => 1110-0000 10-100011 10-010011 // 0000 1000 1101 0011 => 1110-0000 10-100011 10-010011
EXPECT_STREQ("\xE0\xA3\x93", EXPECT_EQ("\xE0\xA3\x93",
CodePointToUtf8(static_cast<wchar_t>(0x8D3), buffer)); CodePointToUtf8(static_cast<wchar_t>(0x8D3)));
// 1100 0111 0100 1101 => 1110-1100 10-011101 10-001101 // 1100 0111 0100 1101 => 1110-1100 10-011101 10-001101
EXPECT_STREQ("\xEC\x9D\x8D", EXPECT_EQ("\xEC\x9D\x8D",
CodePointToUtf8(static_cast<wchar_t>(0xC74D), buffer)); CodePointToUtf8(static_cast<wchar_t>(0xC74D)));
} }
#if !GTEST_WIDE_STRING_USES_UTF16_ #if !GTEST_WIDE_STRING_USES_UTF16_
@ -503,22 +499,19 @@ TEST(CodePointToUtf8Test, CanEncode12To16Bits) {
// Tests that Unicode code-points that have 17 to 21 bits are encoded // Tests that Unicode code-points that have 17 to 21 bits are encoded
// as 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx. // as 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
TEST(CodePointToUtf8Test, CanEncode17To21Bits) { TEST(CodePointToUtf8Test, CanEncode17To21Bits) {
char buffer[32];
// 0 0001 0000 1000 1101 0011 => 11110-000 10-010000 10-100011 10-010011 // 0 0001 0000 1000 1101 0011 => 11110-000 10-010000 10-100011 10-010011
EXPECT_STREQ("\xF0\x90\xA3\x93", CodePointToUtf8(L'\x108D3', buffer)); EXPECT_EQ("\xF0\x90\xA3\x93", CodePointToUtf8(L'\x108D3'));
// 0 0001 0000 0100 0000 0000 => 11110-000 10-010000 10-010000 10-000000 // 0 0001 0000 0100 0000 0000 => 11110-000 10-010000 10-010000 10-000000
EXPECT_STREQ("\xF0\x90\x90\x80", CodePointToUtf8(L'\x10400', buffer)); EXPECT_EQ("\xF0\x90\x90\x80", CodePointToUtf8(L'\x10400'));
// 1 0000 1000 0110 0011 0100 => 11110-100 10-001000 10-011000 10-110100 // 1 0000 1000 0110 0011 0100 => 11110-100 10-001000 10-011000 10-110100
EXPECT_STREQ("\xF4\x88\x98\xB4", CodePointToUtf8(L'\x108634', buffer)); EXPECT_EQ("\xF4\x88\x98\xB4", CodePointToUtf8(L'\x108634'));
} }
// Tests that encoding an invalid code-point generates the expected result. // Tests that encoding an invalid code-point generates the expected result.
TEST(CodePointToUtf8Test, CanEncodeInvalidCodePoint) { TEST(CodePointToUtf8Test, CanEncodeInvalidCodePoint) {
char buffer[32]; EXPECT_EQ("(Invalid Unicode 0x1234ABCD)", CodePointToUtf8(L'\x1234ABCD'));
EXPECT_STREQ("(Invalid Unicode 0x1234ABCD)",
CodePointToUtf8(L'\x1234ABCD', buffer));
} }
#endif // !GTEST_WIDE_STRING_USES_UTF16_ #endif // !GTEST_WIDE_STRING_USES_UTF16_
@ -960,33 +953,6 @@ TEST(StringTest, CaseInsensitiveWideCStringEquals) {
EXPECT_TRUE(String::CaseInsensitiveWideCStringEquals(L"FOOBAR", L"foobar")); EXPECT_TRUE(String::CaseInsensitiveWideCStringEquals(L"FOOBAR", L"foobar"));
} }
// Tests that String::Format() works.
TEST(StringTest, FormatWorks) {
// Normal case: the format spec is valid, the arguments match the
// spec, and the result is < 4095 characters.
EXPECT_STREQ("Hello, 42", String::Format("%s, %d", "Hello", 42).c_str());
// Edge case: the result is 4095 characters.
char buffer[4096];
const size_t kSize = sizeof(buffer);
memset(buffer, 'a', kSize - 1);
buffer[kSize - 1] = '\0';
EXPECT_EQ(buffer, String::Format("%s", buffer));
// The result needs to be 4096 characters, exceeding Format()'s limit.
EXPECT_EQ("<formatting error or buffer exceeded>",
String::Format("x%s", buffer));
#if GTEST_OS_LINUX && !GTEST_OS_LINUX_ANDROID
// On Linux, invalid format spec should lead to an error message.
// In other environment (e.g. MSVC on Windows), String::Format() may
// simply ignore a bad format spec, so this assertion is run on
// Linux only.
EXPECT_EQ("<formatting error or buffer exceeded>",
String::Format("%"));
#endif
}
#if GTEST_OS_WINDOWS #if GTEST_OS_WINDOWS
// Tests String::ShowWideCString(). // Tests String::ShowWideCString().
@ -3731,10 +3697,10 @@ TEST(HRESULTAssertionTest, EXPECT_HRESULT_FAILED) {
EXPECT_NONFATAL_FAILURE(EXPECT_HRESULT_FAILED(OkHRESULTSuccess()), EXPECT_NONFATAL_FAILURE(EXPECT_HRESULT_FAILED(OkHRESULTSuccess()),
"Expected: (OkHRESULTSuccess()) fails.\n" "Expected: (OkHRESULTSuccess()) fails.\n"
" Actual: 0x00000000"); " Actual: 0x0");
EXPECT_NONFATAL_FAILURE(EXPECT_HRESULT_FAILED(FalseHRESULTSuccess()), EXPECT_NONFATAL_FAILURE(EXPECT_HRESULT_FAILED(FalseHRESULTSuccess()),
"Expected: (FalseHRESULTSuccess()) fails.\n" "Expected: (FalseHRESULTSuccess()) fails.\n"
" Actual: 0x00000001"); " Actual: 0x1");
} }
TEST(HRESULTAssertionTest, ASSERT_HRESULT_FAILED) { TEST(HRESULTAssertionTest, ASSERT_HRESULT_FAILED) {
@ -3745,12 +3711,12 @@ TEST(HRESULTAssertionTest, ASSERT_HRESULT_FAILED) {
// ICE's in C++Builder 2007 and 2009. // ICE's in C++Builder 2007 and 2009.
EXPECT_FATAL_FAILURE(ASSERT_HRESULT_FAILED(OkHRESULTSuccess()), EXPECT_FATAL_FAILURE(ASSERT_HRESULT_FAILED(OkHRESULTSuccess()),
"Expected: (OkHRESULTSuccess()) fails.\n" "Expected: (OkHRESULTSuccess()) fails.\n"
" Actual: 0x00000000"); " Actual: 0x0");
# endif # endif
EXPECT_FATAL_FAILURE(ASSERT_HRESULT_FAILED(FalseHRESULTSuccess()), EXPECT_FATAL_FAILURE(ASSERT_HRESULT_FAILED(FalseHRESULTSuccess()),
"Expected: (FalseHRESULTSuccess()) fails.\n" "Expected: (FalseHRESULTSuccess()) fails.\n"
" Actual: 0x00000001"); " Actual: 0x1");
} }
// Tests that streaming to the HRESULT macros works. // Tests that streaming to the HRESULT macros works.