Merge branch 'master' into merge-master-to-taglib2

Conflicts:
	taglib/mp4/mp4properties.cpp
	taglib/mp4/mp4properties.h
	taglib/mpeg/id3v2/id3v2frame.cpp
	taglib/toolkit/tbytevector.cpp
	taglib/toolkit/tlist.tcc
	taglib/toolkit/tmap.tcc
	taglib/toolkit/tstring.h
	taglib/wavpack/wavpackproperties.cpp
	tests/test_mp4.cpp
This commit is contained in:
Tsuda Kageyu
2013-11-16 09:38:38 +09:00
29 changed files with 290 additions and 88 deletions

8
CMakeLists.txt Executable file → Normal file
View File

@ -47,8 +47,8 @@ if (MSVC AND ENABLE_STATIC_RUNTIME)
endif()
set(TAGLIB_LIB_MAJOR_VERSION "1")
set(TAGLIB_LIB_MINOR_VERSION "8")
set(TAGLIB_LIB_PATCH_VERSION "0")
set(TAGLIB_LIB_MINOR_VERSION "9")
set(TAGLIB_LIB_PATCH_VERSION "1")
set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}")
@ -56,9 +56,9 @@ set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VE
# 2. If any interfaces have been added, removed, or changed since the last update, increment current, and set revision to 0.
# 3. If any interfaces have been added since the last public release, then increment age.
# 4. If any interfaces have been removed since the last public release, then set age to 0.
set(TAGLIB_SOVERSION_CURRENT 13)
set(TAGLIB_SOVERSION_CURRENT 15)
set(TAGLIB_SOVERSION_REVISION 0)
set(TAGLIB_SOVERSION_AGE 12)
set(TAGLIB_SOVERSION_AGE 14)
math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}")
math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}")

0
ConfigureChecks.cmake Executable file → Normal file
View File

97
INSTALL
View File

@ -46,15 +46,102 @@ the include folder to the project's User Header Search Paths.
Windows
-------
For building a static library on Windows with Visual Studio 2010, cd to
the TagLib folder then:
It's Windows ... Systems vary!
This means you need to adjust things to suit your system, especially paths.
cmake -DENABLE_STATIC=ON -DENABLE_STATIC_RUNTIME=ON -G "Visual Studio 10" ...
Tested with:
Microsoft Visual Studio 2010
Gcc by mingw-w64.sf.net v4.6.3 (Strawberry Perl 32b)
MinGW32-4.8.0
Requirements:
1. Tool chain, Build Environment, Whatever ya want to call it ...
Installed and working.
2. CMake program. (Available at: www.cmake.org)
Installed and working.
Optional:
1. Zlib library.
Available in some Tool Chains, Not all.
Search the web, Take your choice.
Useful configuration options used with CMake (GUI and/or Command line):
Any of the ZLIB_ variables may be used at the command line, ZLIB_ROOT is only
available on the Command line.
ZLIB_ROOT= Where to find ZLib's root directory.
Assumes parent of: \include and \lib.
ZLIB_INCLUDE_DIR= Where to find ZLib's Include directory.
ZLIB_LIBRARY= Where to find ZLib's Library.
CMAKE_INSTALL_PREFIX= Where to install Taglib.
CMAKE_BUILD_TYPE= Release, Debug, etc ... (Not available in MSVC)
The easiest way is at the Command Prompt.
MSVS Command Prompt for MSVS Users.
(Batch file and/or Shortcuts are your friends)
1. Build the Makefiles:
Replace "GENERATOR" with your needs.
For MSVS : "Visual Studio X" where X is the single or two digit version.
For MinGW: "MinGW Makefiles"
C:\GitRoot\taglib> cmake -G "GENERATOR" -DCMAKE_INSTALL_PREFIX=C:\Libraries\taglib
Or use the CMake GUI:
1. Open CMake GUI.
2. Set Paths.
"Where is the source code" and "Where to build the binaries"
Example, Both would be: C:\GitRoot\taglib
3. Tick: Advanced
4. Select: Configure
5. Select: Generator
6. Tick: Use default native compilers
7. Select: Finish
Wait until done.
5. If using ZLib, Scroll down.
(to the bottom of the list of options ... should go over them all)
1. Edit: ZLIB_INCLUDE_DIR
2. Edit: ZLIB_LIBRARY
6. Select: Generate
2. Build the project:
MSVS:
C:\GitRoot\taglib> msbuild all_build.vcxproj /p:Configuration=Release
OR (Depending on MSVS version or personal choice)
C:\GitRoot\taglib> devenv all_build.vcxproj /build Release
MinGW:
C:\GitRoot\taglib> gmake
OR (Depending on MinGW install)
C:\GitRoot\taglib> mingw32-make
Or in the MSVS GUI:
1. Open MSVS.
2. Open taglib solution.
3. Set build type to: Release (look in the tool bars)
2. Hit F7 to build the solution. (project)
3. Install the project:
(Change 'install' to 'uninstall' to uninstall the project)
MSVS:
C:\GitRoot\taglib> msbuild install.vcxproj
OR (Depending on MSVC version or personal choice)
C:\GitRoot\taglib> devenv install.vcxproj
MinGW:
C:\GitRoot\taglib> gmake install
OR (Depending on MinGW install)
C:\GitRoot\taglib> mingw32-make install
Or in the MSVS GUI:
1. Open project.
2. Open Solution Explorer.
3. Right Click: INSTALL
4. Select: Project Only
5. Select: Build Only INSTALL
To build a static library enable the following two options with CMake.
-DENABLE_STATIC=ON -DENABLE_STATIC_RUNTIME=ON
Including ENABLE_STATIC_RUNTIME=ON indicates you want TagLib built using the
static runtime library, rather than the DLL form of the runtime.
CMake will create a Visual Studio solution, taglib.sln that you can open and
build as normal.
Unit Tests
----------

19
NEWS
View File

@ -1,16 +1,33 @@
TagLib 1.9 (In Development)
TagLib 1.9.1 (Oct 8, 2013)
==========================
* Fixed binary incompatible change in TagLib::Map and TagLib::List.
* Fixed constructing String from ByteVector.
* Fixed compilation on MSVC with the /Zc:wchar_t- option.
* Fixed detecting of RIFF files with invalid chunk sizes.
* Added TagLib::MP4::PropertyMap::codec().
TagLib 1.9 (Oct 6, 2013)
========================
* Added support for the Ogg Opus file format.
* Added support for INFO tags in WAV files.
* Changed FileStream to use Windows file API.
* Included taglib-config.cmd script for Windows.
* New ID3v1::Tag methods for working directly with genre numbers.
* New MPEG::File methods for checking which tags are saved in the file.
* Added support for the PropertyMap API to ASF and MP4 files.
* Added MusicBrainz identifiers to the PropertyMap API.
* Allowed reading of MP4 cover art without an explicitly specified format.
* Better parsing of corrupted FLAC files.
* Fixed saving of PropertyMap comments without description into ID3v2 tags.
* Fixed crash when parsing certain XM files.
* Fixed compilation of unit test with clang.
* Better handling of files that can't be open or have read-only permissions.
* Improved atomic reference counting.
* New hookable API for debug messages.
* More complete Windows install instructions.
* Many smaller bug fixes and performance improvements.
TagLib 1.8 (Sep 6, 2012)
========================

0
taglib-config.cmd.cmake Executable file → Normal file
View File

0
taglib/CMakeLists.txt Executable file → Normal file
View File

0
taglib/fileref.h Executable file → Normal file
View File

View File

@ -41,13 +41,7 @@ public:
channels(0),
bitsPerSample(0),
encrypted(false),
format(Unknown) {}
enum Format {
Unknown = 0,
AAC = 1,
ALAC = 2,
};
codec(Unknown) {}
int length;
int bitrate;
@ -55,9 +49,13 @@ public:
int channels;
int bitsPerSample;
bool encrypted;
Format format;
Codec codec;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
MP4::AudioProperties::AudioProperties(File *file, MP4::Atoms *atoms, ReadStyle style) :
d(new PropertiesPrivate())
{
@ -105,14 +103,20 @@ MP4::AudioProperties::isEncrypted() const
return d->encrypted;
}
MP4::AudioProperties::Codec
MP4::AudioProperties::codec() const
{
return d->codec;
}
String
MP4::AudioProperties::toString() const
{
String format;
if(d->format == PropertiesPrivate::AAC) {
if(d->codec == AAC) {
format = "AAC";
}
else if(d->format == PropertiesPrivate::ALAC) {
else if(d->codec == ALAC) {
format = "ALAC";
}
else {
@ -125,6 +129,10 @@ MP4::AudioProperties::toString() const
return desc.toString(", ");
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void
MP4::AudioProperties::read(File *file, Atoms *atoms)
{
@ -193,7 +201,7 @@ MP4::AudioProperties::read(File *file, Atoms *atoms)
file->seek(atom->offset);
data = file->readBlock(atom->length);
if(data.mid(20, 4) == "mp4a") {
d->format = PropertiesPrivate::AAC;
d->codec = AAC;
d->channels = data.toInt16BE(40);
d->bitsPerSample = data.toInt16BE(42);
d->sampleRate = data.toUInt32BE(46);
@ -214,7 +222,7 @@ MP4::AudioProperties::read(File *file, Atoms *atoms)
}
}
else if (data.mid(20, 4) == "alac") {
d->format = PropertiesPrivate::ALAC;
d->codec = ALAC;
if (atom->length == 88 && data.mid(56, 4) == "alac") {
d->bitsPerSample = data.at(69);
d->channels = data.at(73);

View File

@ -40,6 +40,12 @@ namespace TagLib {
class TAGLIB_EXPORT AudioProperties : public TagLib::AudioProperties
{
public:
enum Codec {
Unknown = 0,
AAC,
ALAC
};
AudioProperties(File *file, Atoms *atoms, ReadStyle style = Average);
virtual ~AudioProperties();
@ -50,6 +56,9 @@ namespace TagLib {
virtual int bitsPerSample() const;
bool isEncrypted() const;
//! Audio codec used in the MP4 file
Codec codec() const;
String toString() const;
private:

View File

@ -153,14 +153,14 @@ namespace TagLib {
/*!
* Returns the genre in number.
*
* /note Normally 255 indicates that this tag contains no genre.
* \note Normally 255 indicates that this tag contains no genre.
*/
TagLib::uint genreNumber() const;
/*!
* Sets the genre in number to \a i.
*
* /note Valid value is from 0 up to 255. Normally 255 indicates that
* \note Valid value is from 0 up to 255. Normally 255 indicates that
* this tag contains no genre.
*/
void setGenreNumber(TagLib::uint i);

View File

@ -325,7 +325,7 @@ namespace
static const TagLib::uint frameTranslationSize = 51;
static const char *frameTranslation[][2] = {
// Text information frames
{ "TALB", "ALBUM"},
{ "TALB", "ALBUM" },
{ "TBPM", "BPM" },
{ "TCOM", "COMPOSER" },
{ "TCON", "GENRE" },
@ -388,7 +388,7 @@ namespace
//{ "USLT", "LYRICS" }, handled specially
};
static const TagLib::uint txxxFrameTranslationSize = 7;
static const TagLib::uint txxxFrameTranslationSize = 8;
static const char *txxxFrameTranslation[][2] = {
{ "MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
{ "MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },

View File

@ -28,6 +28,7 @@
#include <tstring.h>
#include "rifffile.h"
#include <algorithm>
#include <vector>
using namespace TagLib;
@ -293,7 +294,7 @@ void RIFF::File::read()
break;
}
if(tell() + chunkSize > uint(length())) {
if(static_cast<ulonglong>(tell()) + chunkSize > static_cast<ulonglong>(length())) {
debug("RIFF::File::read() -- Chunk '" + chunkName + "' has invalid size (larger than the file size)");
setValid(false);
break;

View File

@ -171,6 +171,11 @@ bool RIFF::Info::Tag::isEmpty() const
return d->fieldListMap.isEmpty();
}
FieldListMap RIFF::Info::Tag::fieldListMap() const
{
return d->fieldListMap;
}
String RIFF::Info::Tag::fieldText(const ByteVector &id) const
{
if(d->fieldListMap.contains(id))

View File

@ -120,6 +120,18 @@ namespace TagLib {
virtual bool isEmpty() const;
/*!
* Returns a copy of the internal fields of the tag. The returned map directly
* reflects the contents of the "INFO" chunk.
*
* \note Modifying this map does not affect the tag's internal data.
* Use setFieldText() and removeField() instead.
*
* \see setFieldText()
* \see removeField()
*/
FieldListMap fieldListMap() const;
/*
* Gets the value of the field with the ID \a id.
*/

View File

@ -27,8 +27,8 @@
#define TAGLIB_H
#define TAGLIB_MAJOR_VERSION 1
#define TAGLIB_MINOR_VERSION 8
#define TAGLIB_PATCH_VERSION 0
#define TAGLIB_MINOR_VERSION 9
#define TAGLIB_PATCH_VERSION 1
#if (defined(_MSC_VER) && _MSC_VER >= 1600)
#define TAGLIB_CONSTRUCT_BITSET(x) static_cast<unsigned long long>(x)

View File

@ -27,11 +27,13 @@
#include <config.h>
#endif
#include <algorithm>
#include <iostream>
#include <limits>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstddef>
#include "tstring.h"
#include "tdebug.h"
@ -464,60 +466,41 @@ ByteVector &ByteVector::replace(const ByteVector &pattern, const ByteVector &wit
if(pattern.size() == 0 || pattern.size() > size())
return *this;
const size_t withSize = with.size();
const size_t withSize = with.size();
const size_t patternSize = pattern.size();
const ptrdiff_t diff = withSize - patternSize;
size_t offset = 0;
while (true)
{
offset = find(pattern, offset);
if(offset == npos)
break;
if(withSize == patternSize) {
// I think this case might be common enough to optimize it
detach();
offset = find(pattern);
while(offset != npos) {
::memcpy(DATA(d) + offset, DATA(with.d), withSize);
offset = find(pattern, offset + withSize);
}
return *this;
}
// calculate new size:
size_t newSize = 0;
for(;;) {
const size_t next = find(pattern, offset);
if(next == npos) {
if(offset == 0)
// pattern not found, do nothing:
return *this;
newSize += size() - offset;
if(diff < 0) {
::memmove(
data() + offset + withSize,
data() + offset + patternSize,
size() - offset - patternSize);
resize(size() + diff);
}
else if(diff > 0) {
resize(size() + diff);
::memmove(
data() + offset + withSize,
data() + offset + patternSize,
size() - diff - offset - patternSize);
}
::memcpy(data() + offset, with.data(), with.size());
offset += withSize;
if(offset > size() - patternSize)
break;
}
newSize += (next - offset) + withSize;
offset = next + patternSize;
}
// new private data of appropriate size:
ByteVectorPrivate newData(newSize, '\0');
char *target = &(*newData.data)[0];
const char *source = DATA(d);
// copy modified data into new private data:
offset = 0;
for(;;) {
const size_t next = find(pattern, offset);
if(next == npos) {
::memcpy(target, source + offset, size() - offset);
break;
}
const size_t chunkSize = next - offset;
::memcpy(target, source + offset, chunkSize);
target += chunkSize;
::memcpy(target, DATA(with.d), withSize);
target += withSize;
offset += chunkSize + patternSize;
}
// replace private data:
*d = newData;
return *this;
}

View File

@ -41,8 +41,12 @@ namespace
bool supportsUnicode()
{
#ifdef UNICODE
return true;
#else
const FARPROC p = GetProcAddress(GetModuleHandleA("kernel32"), "CreateFileW");
return (p != NULL);
#endif
}
// Indicates whether the system supports Unicode file names.

View File

@ -35,6 +35,7 @@
*/
namespace TagLib
{
class TAGLIB_EXPORT RefCounter
{
public:
@ -49,7 +50,9 @@ namespace TagLib
class RefCounterPrivate;
RefCounterPrivate *d;
};
}
#endif // DO_NOT_DOCUMENT
#endif

View File

@ -267,6 +267,9 @@ String::String(const ByteVector &v, Type t)
copyFromUTF8(v.data(), v.size());
else
copyFromUTF16(v.data(), v.size(), t);
// If we hit a null in the ByteVector, shrink the string again.
d->data->resize(::wcslen(d->data->c_str()));
}
////////////////////////////////////////////////////////////////////////////////

View File

@ -231,7 +231,7 @@ namespace TagLib {
* The returned pointer remains valid until this String instance is destroyed
* or any other method of this String is called.
*
* /note This returns a pointer to the String's internal data without any
* \note This returns a pointer to the String's internal data without any
* conversions.
*
* \see toWString()

View File

@ -106,6 +106,11 @@
bit mask & shift operations.
------------------------------------------------------------------------ */
// Workaround for when MSVC doesn't have wchar_t as a built-in type.
#if defined(_MSC_VER) && !defined(_WCHAR_T_DEFINED)
# include <wchar.h>
#endif
/* Some fundamental constants */
#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD
#define UNI_MAX_BMP (UTF32)0x0000FFFF
@ -114,10 +119,10 @@
namespace Unicode {
typedef unsigned long UTF32; /* at least 32 bits */
typedef wchar_t UTF16; /* TagLib assumes that wchar_t is sufficient for UTF-16. */
typedef unsigned long UTF32; /* at least 32 bits */
typedef wchar_t UTF16; /* TagLib assumes that wchar_t is sufficient for UTF-16. */
typedef unsigned char UTF8; /* typically 8 bits */
typedef unsigned char Boolean; /* 0 or 1 */
typedef unsigned char Boolean; /* 0 or 1 */
typedef enum {
conversionOK = 0, /* conversion successful */

View File

@ -112,8 +112,9 @@ TagLib::uint WavPack::AudioProperties::sampleFrames() const
namespace
{
const unsigned int sample_rates[] = { 6000, 8000, 9600, 11025, 12000,
16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 };
static const unsigned int sample_rates[] = {
6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000,
32000, 44100, 48000, 64000, 88200, 96000, 192000, 0 };
}
#define BYTES_STORED 3

View File

@ -65,7 +65,12 @@ namespace TagLib {
virtual int length() const;
virtual int bitrate() const;
/*!
* Returns the sample rate in Hz. 0 means unknown or custom.
*/
virtual int sampleRate() const;
virtual int channels() const;
/*!

View File

@ -55,6 +55,7 @@ SET(test_runner_SRCS
test_ape.cpp
test_apetag.cpp
test_wav.cpp
test_info.cpp
test_wavpack.cpp
test_mp4.cpp
test_mp4item.cpp

View File

@ -232,6 +232,11 @@ public:
a.replace(ByteVector("ab"), ByteVector());
CPPUNIT_ASSERT_EQUAL(ByteVector("cdf"), a);
}
{
ByteVector a("abcdabf");
a.replace(ByteVector("bf"), ByteVector("x"));
CPPUNIT_ASSERT_EQUAL(ByteVector("abcdax"), a);
}
}
};

View File

@ -1,7 +1,10 @@
#include <string>
#include <stdio.h>
#include <tstring.h>
#include <mpegfile.h>
#include <id3v1tag.h>
#include <cppunit/extensions/HelperMacros.h>
#include "utils.h"
using namespace std;
using namespace TagLib;
@ -16,8 +19,20 @@ public:
void testStripWhiteSpace()
{
ID3v1::StringHandler h;
CPPUNIT_ASSERT_EQUAL(String("Foo"), h.parse(ByteVector("Foo ")));
ScopedFileCopy copy("xing", ".mp3");
string newname = copy.fileName();
{
MPEG::File f(newname.c_str());
f.ID3v1Tag(true)->setArtist("Artist ");
f.save();
}
{
MPEG::File f(newname.c_str());
CPPUNIT_ASSERT(f.ID3v1Tag(false));
CPPUNIT_ASSERT_EQUAL(String("Artist"), f.ID3v1Tag(false)->artist());
}
}
};

View File

@ -1,9 +1,7 @@
#include <cppunit/extensions/HelperMacros.h>
#include <string>
#include <stdio.h>
#include <infotag.h>
#include <tpropertymap.h>
#include <tdebug.h>
#include <cppunit/extensions/HelperMacros.h>
#include "utils.h"
using namespace std;
@ -23,7 +21,13 @@ public:
CPPUNIT_ASSERT_EQUAL(String(""), tag.title());
tag.setTitle("Test title 1");
tag.setFieldText("TEST", "Dummy Text");
CPPUNIT_ASSERT_EQUAL(String("Test title 1"), tag.title());
RIFF::Info::FieldListMap map = tag.fieldListMap();
CPPUNIT_ASSERT_EQUAL(String("Test title 1"), map["INAM"]);
CPPUNIT_ASSERT_EQUAL(String("Dummy Text"), map["TEST"]);
}
void testNumericFields()

View File

@ -39,6 +39,7 @@ public:
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT_EQUAL(16, ((MP4::AudioProperties *)f.audioProperties())->bitsPerSample());
CPPUNIT_ASSERT_EQUAL(MP4::AudioProperties::AAC, ((MP4::AudioProperties *)f.audioProperties())->codec());
}
void testPropertiesALAC()
@ -49,6 +50,7 @@ public:
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT_EQUAL(16, ((MP4::AudioProperties *)f.audioProperties())->bitsPerSample());
CPPUNIT_ASSERT_EQUAL(MP4::AudioProperties::ALAC, ((MP4::AudioProperties *)f.audioProperties())->codec());
}
void testCheckValid()

View File

@ -51,6 +51,18 @@ public:
void testString()
{
// Needs to know the system byte order for some Unicode tests.
bool littleEndian;
{
union {
int i;
char c;
} u;
u.i = 1;
littleEndian = (u.c == 1) ? true : false;
}
String s = "taglib string";
ByteVector v = "taglib string";
CPPUNIT_ASSERT(v == s.data(String::Latin1));
@ -74,8 +86,28 @@ public:
String unicode2(unicode.to8Bit(true), String::UTF8);
CPPUNIT_ASSERT(unicode == unicode2);
String unicode3(L"\u65E5\u672C\u8A9E");
CPPUNIT_ASSERT(*(unicode3.toCWString() + 1) == L'\u672C');
String unicode3(L"\u65E5\u672C\u8A9E");
CPPUNIT_ASSERT(*(unicode3.toCWString() + 1) == L'\u672C');
String unicode4(L"\u65e5\u672c\u8a9e");
CPPUNIT_ASSERT(unicode4[1] == L'\u672c');
String unicode5(L"\u65e5\u672c\u8a9e", String::UTF16BE);
CPPUNIT_ASSERT(unicode5[1] == (littleEndian ? L'\u2c67' : L'\u672c'));
String unicode6(L"\u65e5\u672c\u8a9e", String::UTF16LE);
CPPUNIT_ASSERT(unicode6[1] == (littleEndian ? L'\u672c' : L'\u2c67'));
wstring stduni = L"\u65e5\u672c\u8a9e";
String unicode7(stduni);
CPPUNIT_ASSERT(unicode7[1] == L'\u672c');
String unicode8(stduni, String::UTF16BE);
CPPUNIT_ASSERT(unicode8[1] == (littleEndian ? L'\u2c67' : L'\u672c'));
String unicode9(stduni, String::UTF16LE);
CPPUNIT_ASSERT(unicode9[1] == (littleEndian ? L'\u672c' : L'\u2c67'));
CPPUNIT_ASSERT(strcmp(String::number(0).toCString(), "0") == 0);
CPPUNIT_ASSERT(strcmp(String::number(12345678).toCString(), "12345678") == 0);